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