1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
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 "nsArrayUtils.h"
11 #include "nsClipboard.h"
12 #include "nsClipboardX11.h"
13 #include "nsSupportsPrimitives.h"
14 #include "nsString.h"
15 #include "nsReadableUtils.h"
16 #include "nsPrimitiveHelpers.h"
17 #include "nsIServiceManager.h"
18 #include "nsImageToPixbuf.h"
19 #include "nsStringStream.h"
20 #include "nsIObserverService.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/RefPtr.h"
23 #include "mozilla/TimeStamp.h"
24 
25 #include "imgIContainer.h"
26 
27 #include <gtk/gtk.h>
28 
29 // For manipulation of the X event queue
30 #include <X11/Xlib.h>
31 #include <gdk/gdkx.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include "X11UndefineNone.h"
37 
38 using namespace mozilla;
39 
HasSelectionSupport(void)40 bool nsRetrievalContextX11::HasSelectionSupport(void) {
41   // yeah, unix supports the selection clipboard on X11.
42   return true;
43 }
44 
selection_request_filter(GdkXEvent * gdk_xevent,GdkEvent * event,gpointer data)45 static GdkFilterReturn selection_request_filter(GdkXEvent *gdk_xevent,
46                                                 GdkEvent *event,
47                                                 gpointer data) {
48   XEvent *xevent = static_cast<XEvent *>(gdk_xevent);
49   if (xevent->xany.type == SelectionRequest) {
50     if (xevent->xselectionrequest.requestor == X11None)
51       return GDK_FILTER_REMOVE;
52 
53     GdkDisplay *display =
54         gdk_x11_lookup_xdisplay(xevent->xselectionrequest.display);
55     if (!display) return GDK_FILTER_REMOVE;
56 
57     GdkWindow *window = gdk_x11_window_foreign_new_for_display(
58         display, xevent->xselectionrequest.requestor);
59     if (!window) return GDK_FILTER_REMOVE;
60 
61     g_object_unref(window);
62   }
63   return GDK_FILTER_CONTINUE;
64 }
65 
nsRetrievalContextX11()66 nsRetrievalContextX11::nsRetrievalContextX11()
67     : mState(INITIAL),
68       mClipboardRequestNumber(0),
69       mClipboardData(nullptr),
70       mClipboardDataLength(0),
71       mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE)) {
72 // A custom event filter to workaround attempting to dereference a null
73 // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
74 #if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
75   if (gtk_check_version(3, 11, 3))
76     gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
77 #endif
78 }
79 
~nsRetrievalContextX11()80 nsRetrievalContextX11::~nsRetrievalContextX11() {
81   gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
82 }
83 
DispatchSelectionNotifyEvent(GtkWidget * widget,XEvent * xevent)84 static void DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent) {
85   GdkEvent event;
86   event.selection.type = GDK_SELECTION_NOTIFY;
87   event.selection.window = gtk_widget_get_window(widget);
88   event.selection.selection =
89       gdk_x11_xatom_to_atom(xevent->xselection.selection);
90   event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
91   event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
92   event.selection.time = xevent->xselection.time;
93 
94   gtk_widget_event(widget, &event);
95 }
96 
DispatchPropertyNotifyEvent(GtkWidget * widget,XEvent * xevent)97 static void DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent) {
98   GdkWindow *window = gtk_widget_get_window(widget);
99   if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
100     GdkEvent event;
101     event.property.type = GDK_PROPERTY_NOTIFY;
102     event.property.window = window;
103     event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
104     event.property.time = xevent->xproperty.time;
105     event.property.state = xevent->xproperty.state;
106 
107     gtk_widget_event(widget, &event);
108   }
109 }
110 
111 struct checkEventContext {
112   GtkWidget *cbWidget;
113   Atom selAtom;
114 };
115 
checkEventProc(Display * display,XEvent * event,XPointer arg)116 static Bool checkEventProc(Display *display, XEvent *event, XPointer arg) {
117   checkEventContext *context = (checkEventContext *)arg;
118 
119   if (event->xany.type == SelectionNotify ||
120       (event->xany.type == PropertyNotify &&
121        event->xproperty.atom == context->selAtom)) {
122     GdkWindow *cbWindow = gdk_x11_window_lookup_for_display(
123         gdk_x11_lookup_xdisplay(display), event->xany.window);
124     if (cbWindow) {
125       GtkWidget *cbWidget = nullptr;
126       gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
127       if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
128         context->cbWidget = cbWidget;
129         return True;
130       }
131     }
132   }
133 
134   return False;
135 }
136 
WaitForX11Content()137 bool nsRetrievalContextX11::WaitForX11Content() {
138   if (mState == COMPLETED) {  // the request completed synchronously
139     return true;
140   }
141 
142   GdkDisplay *gdkDisplay = gdk_display_get_default();
143   if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
144     Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
145     checkEventContext context;
146     context.cbWidget = nullptr;
147     context.selAtom =
148         gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", FALSE));
149 
150     // Send X events which are relevant to the ongoing selection retrieval
151     // to the clipboard widget.  Wait until either the operation completes, or
152     // we hit our timeout.  All other X events remain queued.
153 
154     int select_result;
155 
156     int cnumber = ConnectionNumber(xDisplay);
157     fd_set select_set;
158     FD_ZERO(&select_set);
159     FD_SET(cnumber, &select_set);
160     ++cnumber;
161     TimeStamp start = TimeStamp::Now();
162 
163     do {
164       XEvent xevent;
165 
166       while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
167                            (XPointer)&context)) {
168         if (xevent.xany.type == SelectionNotify)
169           DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
170         else
171           DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
172 
173         if (mState == COMPLETED) {
174           return true;
175         }
176       }
177 
178       TimeStamp now = TimeStamp::Now();
179       struct timeval tv;
180       tv.tv_sec = 0;
181       tv.tv_usec = std::max<int32_t>(
182           0, kClipboardTimeout - (now - start).ToMicroseconds());
183       select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
184     } while (select_result == 1 || (select_result == -1 && errno == EINTR));
185   }
186 #ifdef DEBUG_CLIPBOARD
187   printf("exceeded clipboard timeout\n");
188 #endif
189   mState = TIMED_OUT;
190   return false;
191 }
192 
193 // Call this when data has been retrieved.
Complete(ClipboardDataType aDataType,const void * aData,int aDataRequestNumber)194 void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
195                                      const void *aData,
196                                      int aDataRequestNumber) {
197   if (mClipboardRequestNumber != aDataRequestNumber) {
198     NS_WARNING(
199         "nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
200     return;
201   }
202 
203   if (mState == INITIAL) {
204     mState = COMPLETED;
205 
206     MOZ_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
207                "We're leaking clipboard data!");
208 
209     switch (aDataType) {
210       case CLIPBOARD_TEXT: {
211         const char *text = static_cast<const char *>(aData);
212         if (text) {
213           mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
214           mClipboardData = moz_xmalloc(mClipboardDataLength);
215           memcpy(mClipboardData, text, mClipboardDataLength);
216         }
217       } break;
218       case CLIPBOARD_TARGETS: {
219         const GtkSelectionData *selection =
220             static_cast<const GtkSelectionData *>(aData);
221 
222         gint n_targets = 0;
223         GdkAtom *targets = nullptr;
224 
225         if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
226             !n_targets) {
227           return;
228         }
229 
230         mClipboardData = targets;
231         mClipboardDataLength = n_targets;
232       } break;
233       case CLIPBOARD_DATA: {
234         const GtkSelectionData *selection =
235             static_cast<const GtkSelectionData *>(aData);
236 
237         gint dataLength = gtk_selection_data_get_length(selection);
238         if (dataLength > 0) {
239           mClipboardDataLength = dataLength;
240           mClipboardData = moz_xmalloc(dataLength);
241           memcpy(mClipboardData, gtk_selection_data_get_data(selection),
242                  dataLength);
243         }
244       } break;
245     }
246   } else {
247     // Already timed out
248     MOZ_ASSERT(mState == TIMED_OUT);
249   }
250 }
251 
clipboard_contents_received(GtkClipboard * clipboard,GtkSelectionData * selection_data,gpointer data)252 static void clipboard_contents_received(GtkClipboard *clipboard,
253                                         GtkSelectionData *selection_data,
254                                         gpointer data) {
255   ClipboardRequestHandler *handler =
256       static_cast<ClipboardRequestHandler *>(data);
257   handler->Complete(selection_data);
258   delete handler;
259 }
260 
clipboard_text_received(GtkClipboard * clipboard,const gchar * text,gpointer data)261 static void clipboard_text_received(GtkClipboard *clipboard, const gchar *text,
262                                     gpointer data) {
263   ClipboardRequestHandler *handler =
264       static_cast<ClipboardRequestHandler *>(data);
265   handler->Complete(text);
266   delete handler;
267 }
268 
WaitForClipboardData(ClipboardDataType aDataType,GtkClipboard * clipboard,const char * aMimeType)269 bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
270                                                  GtkClipboard *clipboard,
271                                                  const char *aMimeType) {
272   mState = INITIAL;
273   NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
274 
275   // Call ClipboardRequestHandler() with unique clipboard request number.
276   // The request number pairs gtk_clipboard_request_contents() data request
277   // with clipboard_contents_received() callback where the data
278   // is provided by Gtk.
279   mClipboardRequestNumber++;
280   ClipboardRequestHandler *handler =
281       new ClipboardRequestHandler(this, aDataType, mClipboardRequestNumber);
282 
283   switch (aDataType) {
284     case CLIPBOARD_DATA:
285       gtk_clipboard_request_contents(clipboard,
286                                      gdk_atom_intern(aMimeType, FALSE),
287                                      clipboard_contents_received, handler);
288       break;
289     case CLIPBOARD_TEXT:
290       gtk_clipboard_request_text(clipboard, clipboard_text_received, handler);
291       break;
292     case CLIPBOARD_TARGETS:
293       gtk_clipboard_request_contents(clipboard, mTargetMIMEType,
294                                      clipboard_contents_received, handler);
295       break;
296   }
297 
298   return WaitForX11Content();
299 }
300 
GetTargets(int32_t aWhichClipboard,int * aTargetNums)301 GdkAtom *nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard,
302                                            int *aTargetNums) {
303   GtkClipboard *clipboard =
304       gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
305 
306   if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard)) return nullptr;
307 
308   *aTargetNums = mClipboardDataLength;
309   GdkAtom *targets = static_cast<GdkAtom *>(mClipboardData);
310 
311   // We don't hold the target list internally but we transfer the ownership.
312   mClipboardData = nullptr;
313   mClipboardDataLength = 0;
314 
315   return targets;
316 }
317 
GetClipboardData(const char * aMimeType,int32_t aWhichClipboard,uint32_t * aContentLength)318 const char *nsRetrievalContextX11::GetClipboardData(const char *aMimeType,
319                                                     int32_t aWhichClipboard,
320                                                     uint32_t *aContentLength) {
321   GtkClipboard *clipboard;
322   clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
323 
324   if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
325     return nullptr;
326 
327   *aContentLength = mClipboardDataLength;
328   return static_cast<const char *>(mClipboardData);
329 }
330 
GetClipboardText(int32_t aWhichClipboard)331 const char *nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard) {
332   GtkClipboard *clipboard;
333   clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
334 
335   if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard)) return nullptr;
336 
337   return static_cast<const char *>(mClipboardData);
338 }
339 
ReleaseClipboardData(const char * aClipboardData)340 void nsRetrievalContextX11::ReleaseClipboardData(const char *aClipboardData) {
341   NS_ASSERTION(aClipboardData == mClipboardData,
342                "Releasing unknown clipboard data!");
343   free((void *)aClipboardData);
344 
345   mClipboardData = nullptr;
346   mClipboardDataLength = 0;
347 }
348