1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "mozilla/ArrayUtils.h"
9 
10 #include "AsyncGtkClipboardRequest.h"
11 #include "nsClipboardX11.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/TimeStamp.h"
14 #include "mozilla/WidgetUtilsGtk.h"
15 
16 #include <gtk/gtk.h>
17 
18 // For manipulation of the X event queue
19 #include <X11/Xlib.h>
20 #include <poll.h>
21 #include <gdk/gdkx.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include "X11UndefineNone.h"
27 
28 using namespace mozilla;
29 
30 nsRetrievalContextX11::nsRetrievalContextX11() = default;
31 
DispatchSelectionNotifyEvent(GtkWidget * widget,XEvent * xevent)32 static void DispatchSelectionNotifyEvent(GtkWidget* widget, XEvent* xevent) {
33   GdkEvent event = {};
34   event.selection.type = GDK_SELECTION_NOTIFY;
35   event.selection.window = gtk_widget_get_window(widget);
36   event.selection.selection =
37       gdk_x11_xatom_to_atom(xevent->xselection.selection);
38   event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
39   event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
40   event.selection.time = xevent->xselection.time;
41 
42   gtk_widget_event(widget, &event);
43 }
44 
DispatchPropertyNotifyEvent(GtkWidget * widget,XEvent * xevent)45 static void DispatchPropertyNotifyEvent(GtkWidget* widget, XEvent* xevent) {
46   GdkWindow* window = gtk_widget_get_window(widget);
47   if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
48     GdkEvent event = {};
49     event.property.type = GDK_PROPERTY_NOTIFY;
50     event.property.window = window;
51     event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
52     event.property.time = xevent->xproperty.time;
53     event.property.state = xevent->xproperty.state;
54 
55     gtk_widget_event(widget, &event);
56   }
57 }
58 
59 struct checkEventContext {
60   GtkWidget* cbWidget;
61   Atom selAtom;
62 };
63 
checkEventProc(Display * display,XEvent * event,XPointer arg)64 static Bool checkEventProc(Display* display, XEvent* event, XPointer arg) {
65   checkEventContext* context = (checkEventContext*)arg;
66 
67   if (event->xany.type == SelectionNotify ||
68       (event->xany.type == PropertyNotify &&
69        event->xproperty.atom == context->selAtom)) {
70     GdkWindow* cbWindow = gdk_x11_window_lookup_for_display(
71         gdk_x11_lookup_xdisplay(display), event->xany.window);
72     if (cbWindow) {
73       GtkWidget* cbWidget = nullptr;
74       gdk_window_get_user_data(cbWindow, (gpointer*)&cbWidget);
75       if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
76         context->cbWidget = cbWidget;
77         return X11True;
78       }
79     }
80   }
81 
82   return X11False;
83 }
84 
WaitForClipboardData(ClipboardDataType aDataType,int32_t aWhichClipboard,const char * aMimeType)85 ClipboardData nsRetrievalContextX11::WaitForClipboardData(
86     ClipboardDataType aDataType, int32_t aWhichClipboard,
87     const char* aMimeType) {
88   AsyncGtkClipboardRequest request(aDataType, aWhichClipboard, aMimeType);
89   if (request.HasCompleted()) {
90     // the request completed synchronously
91     return request.TakeResult();
92   }
93 
94   GdkDisplay* gdkDisplay = gdk_display_get_default();
95   // gdk_display_get_default() returns null on headless
96   if (widget::GdkIsX11Display(gdkDisplay)) {
97     Display* xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
98     checkEventContext context;
99     context.cbWidget = nullptr;
100     context.selAtom =
101         gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", FALSE));
102 
103     // Send X events which are relevant to the ongoing selection retrieval
104     // to the clipboard widget.  Wait until either the operation completes, or
105     // we hit our timeout.  All other X events remain queued.
106 
107     int poll_result;
108 
109     struct pollfd pfd;
110     pfd.fd = ConnectionNumber(xDisplay);
111     pfd.events = POLLIN;
112     TimeStamp start = TimeStamp::Now();
113 
114     do {
115       XEvent xevent;
116 
117       while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
118                            (XPointer)&context)) {
119         if (xevent.xany.type == SelectionNotify)
120           DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
121         else
122           DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
123 
124         if (request.HasCompleted()) {
125           return request.TakeResult();
126         }
127       }
128 
129       TimeStamp now = TimeStamp::Now();
130       int timeout = std::max<int>(
131           0, kClipboardTimeout / 1000 - (now - start).ToMilliseconds());
132       poll_result = poll(&pfd, 1, timeout);
133     } while ((poll_result == 1 && (pfd.revents & (POLLHUP | POLLERR)) == 0) ||
134              (poll_result == -1 && errno == EINTR));
135   }
136 
137   LOGCLIP("exceeded clipboard timeout");
138   return {};
139 }
140 
GetTargetsImpl(int32_t aWhichClipboard)141 ClipboardTargets nsRetrievalContextX11::GetTargetsImpl(
142     int32_t aWhichClipboard) {
143   LOGCLIP("nsRetrievalContextX11::GetTargetsImpl(%s)\n",
144           aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
145                                                               : "clipboard");
146   return WaitForClipboardData(ClipboardDataType::Targets, aWhichClipboard)
147       .ExtractTargets();
148 }
149 
GetClipboardData(const char * aMimeType,int32_t aWhichClipboard)150 ClipboardData nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
151                                                       int32_t aWhichClipboard) {
152   LOGCLIP("nsRetrievalContextX11::GetClipboardData(%s) MIME %s\n",
153           aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
154                                                               : "clipboard",
155           aMimeType);
156 
157   return WaitForClipboardData(ClipboardDataType::Data, aWhichClipboard,
158                               aMimeType);
159 }
160 
GetClipboardText(int32_t aWhichClipboard)161 GUniquePtr<char> nsRetrievalContextX11::GetClipboardText(
162     int32_t aWhichClipboard) {
163   LOGCLIP("nsRetrievalContextX11::GetClipboardText(%s)\n",
164           aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
165                                                               : "clipboard");
166 
167   return WaitForClipboardData(ClipboardDataType::Text, aWhichClipboard)
168       .ExtractText();
169 }
170