1 | /* GDK - The GIMP Drawing Kit |
2 | * Copyright (C) 2017 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gdkclipboardprivate.h" |
21 | #include "gdkclipboard-x11.h" |
22 | |
23 | #include "gdkintl.h" |
24 | #include "gdkdisplay-x11.h" |
25 | #include "gdkprivate-x11.h" |
26 | #include "gdkselectioninputstream-x11.h" |
27 | #include "gdkselectionoutputstream-x11.h" |
28 | #include "gdktextlistconverter-x11.h" |
29 | #include "gdk/gdk-private.h" |
30 | |
31 | #include <string.h> |
32 | #include <X11/Xatom.h> |
33 | |
34 | #ifdef HAVE_XFIXES |
35 | #include <X11/extensions/Xfixes.h> |
36 | #endif |
37 | |
38 | #define IDLE_ABORT_TIME 30 /* seconds */ |
39 | |
40 | typedef struct _GdkX11ClipboardClass GdkX11ClipboardClass; |
41 | |
42 | typedef struct _RetrievalInfo RetrievalInfo; |
43 | |
44 | struct _GdkX11Clipboard |
45 | { |
46 | GdkClipboard parent; |
47 | |
48 | char *selection; |
49 | Atom xselection; |
50 | gulong timestamp; |
51 | |
52 | GTask *store_task; |
53 | }; |
54 | |
55 | struct _GdkX11ClipboardClass |
56 | { |
57 | GdkClipboardClass parent_class; |
58 | }; |
59 | |
60 | G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD) |
61 | |
62 | static void |
63 | print_atoms (GdkX11Clipboard *cb, |
64 | const char *prefix, |
65 | const Atom *atoms, |
66 | gsize n_atoms) |
67 | { |
68 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, { |
69 | gsize i; |
70 | GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); |
71 | |
72 | g_printerr ("%s: %s [ " , cb->selection, prefix); |
73 | for (i = 0; i < n_atoms; i++) |
74 | g_printerr ("%s%s" , i > 0 ? ", " : "" , gdk_x11_get_xatom_name_for_display (display , atoms[i])); |
75 | g_printerr (" ]\n" ); |
76 | }); |
77 | } |
78 | |
79 | static void |
80 | gdk_x11_clipboard_default_output_closed (GObject *stream, |
81 | GAsyncResult *result, |
82 | gpointer user_data) |
83 | { |
84 | GError *error = NULL; |
85 | |
86 | if (!g_output_stream_close_finish (G_OUTPUT_STREAM (stream), result, error: &error)) |
87 | { |
88 | GDK_NOTE (CLIPBOARD, |
89 | g_printerr ("-------: failed to close stream: %s\n" , |
90 | error->message)); |
91 | g_error_free (error); |
92 | } |
93 | |
94 | g_object_unref (object: stream); |
95 | } |
96 | |
97 | static void |
98 | gdk_x11_clipboard_default_output_done (GObject *clipboard, |
99 | GAsyncResult *result, |
100 | gpointer user_data) |
101 | { |
102 | GOutputStream *stream = user_data; |
103 | GError *error = NULL; |
104 | |
105 | if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, error: &error)) |
106 | { |
107 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), CLIPBOARD, |
108 | g_printerr ("%s: failed to write stream: %s\n" , |
109 | GDK_X11_CLIPBOARD (clipboard)->selection, error->message)); |
110 | g_error_free (error); |
111 | } |
112 | |
113 | g_output_stream_close_async (stream, |
114 | G_PRIORITY_DEFAULT, |
115 | NULL, |
116 | callback: gdk_x11_clipboard_default_output_closed, |
117 | NULL); |
118 | } |
119 | |
120 | static void |
121 | gdk_x11_clipboard_default_output_handler (GOutputStream *stream, |
122 | const char *mime_type, |
123 | gpointer user_data) |
124 | { |
125 | gdk_clipboard_write_async (GDK_CLIPBOARD (user_data), |
126 | mime_type, |
127 | stream, |
128 | G_PRIORITY_DEFAULT, |
129 | NULL, |
130 | callback: gdk_x11_clipboard_default_output_done, |
131 | user_data: stream); |
132 | } |
133 | |
134 | static GInputStream * |
135 | text_list_convert (GdkX11Clipboard *cb, |
136 | GInputStream *stream, |
137 | const char *encoding, |
138 | int format) |
139 | { |
140 | GInputStream *converter_stream; |
141 | GConverter *converter; |
142 | |
143 | converter = gdk_x11_text_list_converter_to_utf8_new (display: gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), |
144 | encoding, |
145 | format); |
146 | converter_stream = g_converter_input_stream_new (base_stream: stream, converter); |
147 | |
148 | g_object_unref (object: converter); |
149 | g_object_unref (object: stream); |
150 | |
151 | return converter_stream; |
152 | } |
153 | |
154 | static GInputStream * |
155 | no_convert (GdkX11Clipboard *cb, |
156 | GInputStream *stream, |
157 | const char *encoding, |
158 | int format) |
159 | { |
160 | return stream; |
161 | } |
162 | |
163 | static const struct { |
164 | const char *x_target; |
165 | const char *mime_type; |
166 | GInputStream * (* convert) (GdkX11Clipboard *, GInputStream *, const char *, int); |
167 | const char *type; |
168 | int format; |
169 | } special_targets[] = { |
170 | { "UTF8_STRING" , "text/plain;charset=utf-8" , no_convert, "UTF8_STRING" , 8 }, |
171 | { "COMPOUND_TEXT" , "text/plain;charset=utf-8" , text_list_convert, "COMPOUND_TEXT" , 8 }, |
172 | { "TEXT" , "text/plain;charset=utf-8" , text_list_convert, "STRING" , 8 }, |
173 | { "STRING" , "text/plain;charset=utf-8" , text_list_convert, "STRING" , 8 }, |
174 | { "TARGETS" , NULL, NULL, "ATOM" , 32 }, |
175 | { "TIMESTAMP" , NULL, NULL, "INTEGER" , 32 }, |
176 | { "SAVE_TARGETS" , NULL, NULL, "NULL" , 32 } |
177 | }; |
178 | |
179 | GSList * |
180 | gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats) |
181 | { |
182 | GSList *targets; |
183 | const char * const *mime_types; |
184 | gsize i, j, n_mime_types; |
185 | |
186 | targets = NULL; |
187 | mime_types = gdk_content_formats_get_mime_types (formats, n_mime_types: &n_mime_types); |
188 | |
189 | for (i = 0; i < n_mime_types; i++) |
190 | { |
191 | for (j = 0; j < G_N_ELEMENTS (special_targets); j++) |
192 | { |
193 | if (special_targets[j].mime_type == NULL) |
194 | continue; |
195 | |
196 | if (g_str_equal (v1: mime_types[i], v2: special_targets[j].mime_type)) |
197 | targets = g_slist_prepend (list: targets, data: (gpointer) g_intern_string (string: special_targets[j].x_target)); |
198 | } |
199 | targets = g_slist_prepend (list: targets, data: (gpointer) mime_types[i]); |
200 | } |
201 | |
202 | return g_slist_reverse (list: targets); |
203 | } |
204 | |
205 | Atom * |
206 | gdk_x11_clipboard_formats_to_atoms (GdkDisplay *display, |
207 | gboolean include_special, |
208 | GdkContentFormats *formats, |
209 | gsize *n_atoms) |
210 | { |
211 | GSList *l, *targets; |
212 | Atom *atoms; |
213 | gsize i; |
214 | |
215 | targets = gdk_x11_clipboard_formats_to_targets (formats); |
216 | |
217 | if (include_special) |
218 | { |
219 | for (i = 0; i < G_N_ELEMENTS (special_targets); i++) |
220 | { |
221 | if (special_targets[i].mime_type != NULL) |
222 | continue; |
223 | |
224 | targets = g_slist_prepend (list: targets, data: (gpointer) g_intern_string (string: special_targets[i].x_target)); |
225 | } |
226 | } |
227 | |
228 | *n_atoms = g_slist_length (list: targets); |
229 | atoms = g_new (Atom, *n_atoms); |
230 | i = 0; |
231 | for (l = targets; l; l = l->next) |
232 | atoms[i++] = gdk_x11_get_xatom_by_name_for_display (display, atom_name: l->data); |
233 | |
234 | return atoms; |
235 | } |
236 | |
237 | static GdkContentFormats * |
238 | gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display, |
239 | const Atom *atoms, |
240 | gsize n_atoms) |
241 | { |
242 | GdkContentFormatsBuilder *builder; |
243 | gsize i, j; |
244 | |
245 | builder = gdk_content_formats_builder_new (); |
246 | for (i = 0; i < n_atoms; i++) |
247 | { |
248 | const char *name; |
249 | |
250 | name = gdk_x11_get_xatom_name_for_display (display , xatom: atoms[i]); |
251 | if (!name) |
252 | { |
253 | continue; |
254 | } |
255 | if (strchr (s: name, c: '/')) |
256 | { |
257 | gdk_content_formats_builder_add_mime_type (builder, mime_type: name); |
258 | continue; |
259 | } |
260 | |
261 | for (j = 0; j < G_N_ELEMENTS (special_targets); j++) |
262 | { |
263 | if (g_str_equal (v1: name, v2: special_targets[j].x_target)) |
264 | { |
265 | if (special_targets[j].mime_type) |
266 | gdk_content_formats_builder_add_mime_type (builder, mime_type: special_targets[j].mime_type); |
267 | break; |
268 | } |
269 | } |
270 | } |
271 | |
272 | return gdk_content_formats_builder_free_to_formats (builder); |
273 | } |
274 | |
275 | static void |
276 | gdk_x11_clipboard_request_targets_finish (GObject *source_object, |
277 | GAsyncResult *res, |
278 | gpointer user_data) |
279 | { |
280 | GInputStream *stream = G_INPUT_STREAM (source_object); |
281 | GdkX11Clipboard *cb = user_data; |
282 | GdkDisplay *display; |
283 | GdkContentFormats *formats; |
284 | GBytes *bytes; |
285 | GError *error = NULL; |
286 | |
287 | display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); |
288 | |
289 | bytes = g_input_stream_read_bytes_finish (stream, result: res, error: &error); |
290 | if (bytes == NULL) |
291 | { |
292 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
293 | g_printerr ("%s: error reading TARGETS: %s\n" , cb->selection, error->message)); |
294 | g_error_free (error); |
295 | g_object_unref (object: stream); |
296 | g_object_unref (object: cb); |
297 | return; |
298 | } |
299 | else if (g_bytes_get_size (bytes) == 0) |
300 | { |
301 | g_bytes_unref (bytes); |
302 | g_object_unref (object: stream); |
303 | g_object_unref (object: cb); |
304 | return; |
305 | } |
306 | else if (gdk_clipboard_is_local (GDK_CLIPBOARD (cb))) |
307 | { |
308 | /* FIXME: Use a cancellable for this request, so that we don't do he brittle |
309 | * is_local() check */ |
310 | g_bytes_unref (bytes); |
311 | g_object_unref (object: stream); |
312 | g_object_unref (object: cb); |
313 | return; |
314 | } |
315 | |
316 | print_atoms (cb, |
317 | prefix: "received targets" , |
318 | atoms: g_bytes_get_data (bytes, NULL), |
319 | n_atoms: g_bytes_get_size (bytes) / sizeof (Atom)); |
320 | |
321 | formats = gdk_x11_clipboard_formats_from_atoms (display, |
322 | atoms: g_bytes_get_data (bytes, NULL), |
323 | n_atoms: g_bytes_get_size (bytes) / sizeof (Atom)); |
324 | GDK_DISPLAY_NOTE (display, CLIPBOARD, char *s = gdk_content_formats_to_string (formats); g_printerr ("%s: got formats: %s\n" , cb->selection, s); g_free (s)); |
325 | |
326 | /* union with previously loaded formats */ |
327 | formats = gdk_content_formats_union (first: formats, second: gdk_clipboard_get_formats (GDK_CLIPBOARD (cb))); |
328 | gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), formats); |
329 | gdk_content_formats_unref (formats); |
330 | g_bytes_unref (bytes); |
331 | |
332 | g_input_stream_read_bytes_async (stream, |
333 | count: gdk_x11_display_get_max_request_size (display), |
334 | G_PRIORITY_DEFAULT, |
335 | NULL, |
336 | callback: gdk_x11_clipboard_request_targets_finish, |
337 | user_data: cb); |
338 | } |
339 | |
340 | static void |
341 | gdk_x11_clipboard_request_targets_got_stream (GObject *source, |
342 | GAsyncResult *result, |
343 | gpointer data) |
344 | { |
345 | GdkX11Clipboard *cb = data; |
346 | GInputStream *stream; |
347 | GdkDisplay *display; |
348 | GError *error = NULL; |
349 | const char *type; |
350 | int format; |
351 | |
352 | display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); |
353 | stream = gdk_x11_selection_input_stream_new_finish (result, type: &type, format: &format, error: &error); |
354 | if (stream == NULL) |
355 | { |
356 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: can't request TARGETS: %s\n" , cb->selection, error->message)); |
357 | g_object_unref (object: cb); |
358 | g_error_free (error); |
359 | return; |
360 | } |
361 | else if (!g_str_equal (v1: type, v2: "ATOM" ) || format != 32) |
362 | { |
363 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: Wrong reply type to TARGETS: type %s != ATOM or format %d != 32\n" , |
364 | cb->selection, type, format)); |
365 | g_input_stream_close (stream, NULL, NULL); |
366 | g_object_unref (object: stream); |
367 | g_object_unref (object: cb); |
368 | return; |
369 | } |
370 | |
371 | g_input_stream_read_bytes_async (stream, |
372 | count: gdk_x11_display_get_max_request_size (display), |
373 | G_PRIORITY_DEFAULT, |
374 | NULL, |
375 | callback: gdk_x11_clipboard_request_targets_finish, |
376 | user_data: cb); |
377 | } |
378 | |
379 | static void |
380 | gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb) |
381 | { |
382 | gdk_x11_selection_input_stream_new_async (display: gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), |
383 | selection: cb->selection, |
384 | target: "TARGETS" , |
385 | timestamp: cb->timestamp, |
386 | G_PRIORITY_DEFAULT, |
387 | NULL, |
388 | callback: gdk_x11_clipboard_request_targets_got_stream, |
389 | g_object_ref (cb)); |
390 | } |
391 | |
392 | static void |
393 | gdk_x11_clipboard_claim_remote (GdkX11Clipboard *cb, |
394 | guint32 timestamp) |
395 | { |
396 | GdkContentFormats *empty; |
397 | |
398 | empty = gdk_content_formats_new (NULL, n_mime_types: 0); |
399 | gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), formats: empty); |
400 | gdk_content_formats_unref (formats: empty); |
401 | cb->timestamp = timestamp; |
402 | gdk_x11_clipboard_request_targets (cb); |
403 | } |
404 | |
405 | static gboolean |
406 | gdk_x11_clipboard_xevent (GdkDisplay *display, |
407 | const XEvent *xevent, |
408 | gpointer data) |
409 | { |
410 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (data); |
411 | Window xwindow; |
412 | |
413 | xwindow = GDK_X11_DISPLAY (display)->leader_window; |
414 | |
415 | if (xevent->xany.window != xwindow) |
416 | return FALSE; |
417 | |
418 | switch (xevent->type) |
419 | { |
420 | case SelectionClear: |
421 | if (xevent->xselectionclear.selection != cb->xselection) |
422 | return FALSE; |
423 | |
424 | if (xevent->xselectionclear.time < cb->timestamp) |
425 | { |
426 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
427 | g_printerr ("%s: ignoring SelectionClear with too old timestamp (%lu vs %lu)\n" , |
428 | cb->selection, xevent->xselectionclear.time, cb->timestamp)); |
429 | return FALSE; |
430 | } |
431 | |
432 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: got SelectionClear\n" , cb->selection)); |
433 | gdk_x11_clipboard_claim_remote (cb, timestamp: xevent->xselectionclear.time); |
434 | return TRUE; |
435 | |
436 | case SelectionNotify: |
437 | /* This code only checks clipboard manager replies, so... */ |
438 | if (!g_str_equal (v1: cb->selection, v2: "CLIPBOARD" )) |
439 | return FALSE; |
440 | |
441 | /* selection is not for us */ |
442 | if (xevent->xselection.selection != gdk_x11_get_xatom_by_name_for_display (display, atom_name: "CLIPBOARD_MANAGER" ) || |
443 | xevent->xselection.target != gdk_x11_get_xatom_by_name_for_display (display, atom_name: "SAVE_TARGETS" )) |
444 | return FALSE; |
445 | |
446 | /* We already received a selectionNotify before */ |
447 | if (cb->store_task == NULL) |
448 | { |
449 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
450 | g_printerr ("%s: got SelectionNotify for nonexisting task?!\n" , cb->selection)); |
451 | return FALSE; |
452 | } |
453 | |
454 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
455 | g_printerr ("%s: got SelectionNotify for store task\n" , cb->selection)); |
456 | |
457 | if (xevent->xselection.property != None) |
458 | g_task_return_boolean (task: cb->store_task, TRUE); |
459 | else |
460 | g_task_return_new_error (task: cb->store_task, G_IO_ERROR, code: G_IO_ERROR_FAILED, |
461 | _("Clipboard manager could not store selection." )); |
462 | g_clear_object (&cb->store_task); |
463 | |
464 | return FALSE; |
465 | |
466 | case SelectionRequest: |
467 | { |
468 | #ifdef G_ENABLE_DEBUG |
469 | const char *target, *property; |
470 | #endif |
471 | |
472 | if (xevent->xselectionrequest.selection != cb->xselection) |
473 | return FALSE; |
474 | |
475 | #ifdef G_ENABLE_DEBUG |
476 | target = gdk_x11_get_xatom_name_for_display (display, xatom: xevent->xselectionrequest.target); |
477 | if (xevent->xselectionrequest.property == None) |
478 | property = target; |
479 | else |
480 | property = gdk_x11_get_xatom_name_for_display (display, xatom: xevent->xselectionrequest.property); |
481 | #endif |
482 | |
483 | if (!gdk_clipboard_is_local (GDK_CLIPBOARD (cb))) |
484 | { |
485 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
486 | g_printerr ("%s: got SelectionRequest for %s @ %s even though we don't own the selection, huh?\n" , |
487 | cb->selection, target, property)); |
488 | return TRUE; |
489 | } |
490 | if (xevent->xselectionrequest.requestor == None) |
491 | { |
492 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
493 | g_printerr ("%s: got SelectionRequest for %s @ %s with NULL window, ignoring\n" , |
494 | cb->selection, target, property)); |
495 | return TRUE; |
496 | } |
497 | |
498 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
499 | g_printerr ("%s: got SelectionRequest for %s @ %s\n" , cb->selection, target, property)); |
500 | |
501 | gdk_x11_selection_output_streams_create (display, |
502 | formats: gdk_clipboard_get_formats (GDK_CLIPBOARD (cb)), |
503 | requestor: xevent->xselectionrequest.requestor, |
504 | selection: xevent->xselectionrequest.selection, |
505 | target: xevent->xselectionrequest.target, |
506 | property: xevent->xselectionrequest.property ? xevent->xselectionrequest.property |
507 | : xevent->xselectionrequest.target, |
508 | timestamp: xevent->xselectionrequest.time, |
509 | handler: gdk_x11_clipboard_default_output_handler, |
510 | user_data: cb); |
511 | return TRUE; |
512 | } |
513 | |
514 | default: |
515 | #ifdef HAVE_XFIXES |
516 | if (xevent->type - GDK_X11_DISPLAY (display)->xfixes_event_base == XFixesSelectionNotify) |
517 | { |
518 | XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent; |
519 | |
520 | if (sn->selection != cb->xselection) |
521 | return FALSE; |
522 | |
523 | if (sn->selection_timestamp < cb->timestamp) |
524 | { |
525 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
526 | g_printerr ("%s: Ignoring XFixesSelectionNotify with too old timestamp (%lu vs %lu)\n" , |
527 | cb->selection, sn->selection_timestamp, cb->timestamp)); |
528 | return FALSE; |
529 | } |
530 | |
531 | if (sn->owner == GDK_X11_DISPLAY (display)->leader_window) |
532 | { |
533 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
534 | g_printerr ("%s: Ignoring XFixesSelectionNotify for ourselves\n" , cb->selection)); |
535 | return FALSE; |
536 | } |
537 | |
538 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
539 | g_printerr ("%s: Received XFixesSelectionNotify, claiming selection\n" , cb->selection)); |
540 | |
541 | gdk_x11_clipboard_claim_remote (cb, timestamp: sn->selection_timestamp); |
542 | } |
543 | #endif |
544 | return FALSE; |
545 | } |
546 | } |
547 | |
548 | static void |
549 | gdk_x11_clipboard_finalize (GObject *object) |
550 | { |
551 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (object); |
552 | |
553 | g_signal_handlers_disconnect_by_func (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), |
554 | gdk_x11_clipboard_xevent, |
555 | cb); |
556 | g_free (mem: cb->selection); |
557 | |
558 | G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object); |
559 | } |
560 | |
561 | static gboolean |
562 | gdk_x11_clipboard_claim (GdkClipboard *clipboard, |
563 | GdkContentFormats *formats, |
564 | gboolean local, |
565 | GdkContentProvider *content) |
566 | { |
567 | if (local) |
568 | { |
569 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard); |
570 | GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); |
571 | Display *xdisplay = GDK_DISPLAY_XDISPLAY (display); |
572 | Window xwindow = GDK_X11_DISPLAY (display)->leader_window; |
573 | guint32 time; |
574 | |
575 | time = gdk_x11_get_server_time (GDK_X11_DISPLAY (display)->leader_gdk_surface); |
576 | |
577 | if (content) |
578 | { |
579 | XSetSelectionOwner (xdisplay, cb->xselection, xwindow, time); |
580 | |
581 | if (XGetSelectionOwner (xdisplay, cb->xselection) != xwindow) |
582 | { |
583 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: failed XSetSelectionOwner()\n" , cb->selection)); |
584 | return FALSE; |
585 | } |
586 | } |
587 | else |
588 | { |
589 | XSetSelectionOwner (xdisplay, cb->xselection, None, time); |
590 | } |
591 | |
592 | cb->timestamp = time; |
593 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: claimed via XSetSelectionOwner()\n" , cb->selection)); |
594 | } |
595 | |
596 | return GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->claim (clipboard, formats, local, content); |
597 | } |
598 | |
599 | static void |
600 | gdk_x11_clipboard_store_async (GdkClipboard *clipboard, |
601 | int io_priority, |
602 | GCancellable *cancellable, |
603 | GAsyncReadyCallback callback, |
604 | gpointer user_data) |
605 | { |
606 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard); |
607 | GdkDisplay *display = gdk_clipboard_get_display (clipboard); |
608 | Display *xdisplay = GDK_DISPLAY_XDISPLAY (display); |
609 | Atom clipboard_manager, save_targets, property_name; |
610 | GdkContentProvider *content; |
611 | GdkContentFormats *formats; |
612 | Atom *atoms; |
613 | gsize n_atoms; |
614 | int error; |
615 | |
616 | /* clipboard managers don't work on anything but the clipbpoard selection */ |
617 | if (!g_str_equal (v1: cb->selection, v2: "CLIPBOARD" )) |
618 | { |
619 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: can only store on CLIPBOARD\n" , cb->selection)); |
620 | GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->store_async (clipboard, |
621 | io_priority, |
622 | cancellable, |
623 | callback, |
624 | user_data); |
625 | return; |
626 | } |
627 | |
628 | cb->store_task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data); |
629 | g_task_set_priority (task: cb->store_task, priority: io_priority); |
630 | g_task_set_source_tag (cb->store_task, gdk_x11_clipboard_store_async); |
631 | |
632 | clipboard_manager = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "CLIPBOARD_MANAGER" ); |
633 | save_targets = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "SAVE_TARGETS" ); |
634 | |
635 | if (XGetSelectionOwner (xdisplay, clipboard_manager) == None) |
636 | { |
637 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
638 | g_printerr ("%s: XGetSelectionOwner (CLIPBOARD_MANAGER) returned None, aborting.\n" , |
639 | cb->selection)); |
640 | g_task_return_new_error (task: cb->store_task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
641 | _("Cannot store clipboard. No clipboard manager is active." )); |
642 | g_clear_object (&cb->store_task); |
643 | return; |
644 | } |
645 | |
646 | content = gdk_clipboard_get_content (clipboard); |
647 | if (content == NULL) |
648 | { |
649 | GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: storing empty clipboard: SUCCESS!\n" , cb->selection)); |
650 | g_task_return_boolean (task: cb->store_task, TRUE); |
651 | g_clear_object (&cb->store_task); |
652 | return; |
653 | } |
654 | |
655 | formats = gdk_content_provider_ref_storable_formats (provider: content); |
656 | formats = gdk_content_formats_union_serialize_mime_types (formats); |
657 | atoms = gdk_x11_clipboard_formats_to_atoms (display, FALSE, formats, n_atoms: &n_atoms); |
658 | print_atoms (cb, prefix: "requesting store from clipboard manager" , atoms, n_atoms); |
659 | gdk_content_formats_unref (formats); |
660 | |
661 | gdk_x11_display_error_trap_push (display); |
662 | |
663 | if (n_atoms > 0) |
664 | { |
665 | property_name = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "GDK_CLIPBOARD_SAVE_TARGETS" ); |
666 | |
667 | XChangeProperty (xdisplay, GDK_X11_DISPLAY (display)->leader_window, |
668 | property_name, XA_ATOM, |
669 | 32, PropModeReplace, (guchar *)atoms, n_atoms); |
670 | } |
671 | else |
672 | property_name = None; |
673 | |
674 | XConvertSelection (xdisplay, |
675 | clipboard_manager, save_targets, property_name, |
676 | GDK_X11_DISPLAY (display)->leader_window, cb->timestamp); |
677 | |
678 | error = gdk_x11_display_error_trap_pop (display); |
679 | if (error != Success) |
680 | { |
681 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
682 | g_printerr ("%s: X error during ConvertSelection() while storing selection: %d\n" , cb->selection, error)); |
683 | } |
684 | |
685 | g_free (mem: atoms); |
686 | } |
687 | |
688 | static gboolean |
689 | gdk_x11_clipboard_store_finish (GdkClipboard *clipboard, |
690 | GAsyncResult *result, |
691 | GError **error) |
692 | { |
693 | g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE); |
694 | g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_x11_clipboard_store_async, FALSE); |
695 | |
696 | return g_task_propagate_boolean (G_TASK (result), error); |
697 | } |
698 | |
699 | static void |
700 | gdk_x11_clipboard_read_got_stream (GObject *source, |
701 | GAsyncResult *res, |
702 | gpointer data) |
703 | { |
704 | GTask *task = data; |
705 | GError *error = NULL; |
706 | GInputStream *stream; |
707 | const char *type; |
708 | int format; |
709 | |
710 | stream = gdk_x11_selection_input_stream_new_finish (result: res, type: &type, format: &format, error: &error); |
711 | if (stream == NULL) |
712 | { |
713 | GSList *targets, *next; |
714 | |
715 | targets = g_task_get_task_data (task); |
716 | next = targets->next; |
717 | if (next) |
718 | { |
719 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task)); |
720 | |
721 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD(cb)), CLIPBOARD, |
722 | g_printerr ("%s: reading %s failed, trying %s next\n" , |
723 | cb->selection, (char *) targets->data, (char *) next->data)); |
724 | targets->next = NULL; |
725 | g_task_set_task_data (task, task_data: next, task_data_destroy: (GDestroyNotify) g_slist_free); |
726 | gdk_x11_selection_input_stream_new_async (display: gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), |
727 | selection: cb->selection, |
728 | target: next->data, |
729 | timestamp: cb->timestamp, |
730 | io_priority: g_task_get_priority (task), |
731 | cancellable: g_task_get_cancellable (task), |
732 | callback: gdk_x11_clipboard_read_got_stream, |
733 | user_data: task); |
734 | g_error_free (error); |
735 | return; |
736 | } |
737 | |
738 | g_task_return_error (task, error); |
739 | } |
740 | else |
741 | { |
742 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task)); |
743 | const char *mime_type = ((GSList *) g_task_get_task_data (task))->data; |
744 | gsize i; |
745 | |
746 | for (i = 0; i < G_N_ELEMENTS (special_targets); i++) |
747 | { |
748 | if (g_str_equal (v1: mime_type, v2: special_targets[i].x_target)) |
749 | { |
750 | g_assert (special_targets[i].mime_type != NULL); |
751 | |
752 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, |
753 | g_printerr ("%s: reading with converter from %s to %s\n" , |
754 | cb->selection, mime_type, special_targets[i].mime_type)); |
755 | mime_type = g_intern_string (string: special_targets[i].mime_type); |
756 | g_task_set_task_data (task, task_data: g_slist_prepend (NULL, data: (gpointer) mime_type), task_data_destroy: (GDestroyNotify) g_slist_free); |
757 | stream = special_targets[i].convert (cb, stream, type, format); |
758 | break; |
759 | } |
760 | } |
761 | |
762 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, |
763 | g_printerr ("%s: reading clipboard as %s now\n" , cb->selection, mime_type)); |
764 | g_task_return_pointer (task, result: stream, result_destroy: g_object_unref); |
765 | } |
766 | |
767 | g_object_unref (object: task); |
768 | } |
769 | |
770 | static void |
771 | gdk_x11_clipboard_read_async (GdkClipboard *clipboard, |
772 | GdkContentFormats *formats, |
773 | int io_priority, |
774 | GCancellable *cancellable, |
775 | GAsyncReadyCallback callback, |
776 | gpointer user_data) |
777 | { |
778 | GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard); |
779 | GSList *targets; |
780 | GTask *task; |
781 | |
782 | task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data); |
783 | g_task_set_priority (task, priority: io_priority); |
784 | g_task_set_source_tag (task, gdk_x11_clipboard_read_async); |
785 | |
786 | targets = gdk_x11_clipboard_formats_to_targets (formats); |
787 | g_task_set_task_data (task, task_data: targets, task_data_destroy: (GDestroyNotify) g_slist_free); |
788 | if (targets == NULL) |
789 | { |
790 | g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
791 | _("No compatible transfer format found" )); |
792 | return; |
793 | } |
794 | |
795 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (clipboard), CLIPBOARD, |
796 | g_printerr ("%s: new read for %s (%u other options)\n" , |
797 | cb->selection, (char *) targets->data, g_slist_length (targets->next))); |
798 | gdk_x11_selection_input_stream_new_async (display: gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), |
799 | selection: cb->selection, |
800 | target: targets->data, |
801 | timestamp: cb->timestamp, |
802 | io_priority, |
803 | cancellable, |
804 | callback: gdk_x11_clipboard_read_got_stream, |
805 | user_data: task); |
806 | } |
807 | |
808 | static GInputStream * |
809 | gdk_x11_clipboard_read_finish (GdkClipboard *clipboard, |
810 | GAsyncResult *result, |
811 | const char **out_mime_type, |
812 | GError **error) |
813 | { |
814 | GTask *task; |
815 | |
816 | g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL); |
817 | task = G_TASK (result); |
818 | g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_clipboard_read_async, NULL); |
819 | |
820 | if (out_mime_type) |
821 | { |
822 | GSList *targets; |
823 | |
824 | targets = g_task_get_task_data (task); |
825 | *out_mime_type = targets ? targets->data : NULL; |
826 | } |
827 | |
828 | return g_task_propagate_pointer (task, error); |
829 | } |
830 | |
831 | static void |
832 | gdk_x11_clipboard_class_init (GdkX11ClipboardClass *class) |
833 | { |
834 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
835 | GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class); |
836 | |
837 | object_class->finalize = gdk_x11_clipboard_finalize; |
838 | |
839 | clipboard_class->claim = gdk_x11_clipboard_claim; |
840 | clipboard_class->store_async = gdk_x11_clipboard_store_async; |
841 | clipboard_class->store_finish = gdk_x11_clipboard_store_finish; |
842 | clipboard_class->read_async = gdk_x11_clipboard_read_async; |
843 | clipboard_class->read_finish = gdk_x11_clipboard_read_finish; |
844 | } |
845 | |
846 | static void |
847 | gdk_x11_clipboard_init (GdkX11Clipboard *cb) |
848 | { |
849 | cb->timestamp = CurrentTime; |
850 | } |
851 | |
852 | GdkClipboard * |
853 | gdk_x11_clipboard_new (GdkDisplay *display, |
854 | const char *selection) |
855 | { |
856 | GdkX11Clipboard *cb; |
857 | |
858 | cb = g_object_new (GDK_TYPE_X11_CLIPBOARD, |
859 | first_property_name: "display" , display, |
860 | NULL); |
861 | |
862 | cb->selection = g_strdup (str: selection); |
863 | cb->xselection = gdk_x11_get_xatom_by_name_for_display (display, atom_name: selection); |
864 | |
865 | gdk_x11_display_request_selection_notification (display, selection); |
866 | g_signal_connect (display, "xevent" , G_CALLBACK (gdk_x11_clipboard_xevent), cb); |
867 | gdk_x11_clipboard_claim_remote (cb, CurrentTime); |
868 | |
869 | return GDK_CLIPBOARD (cb); |
870 | } |
871 | |
872 | |