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