1 | /* GIO - GLib Output, Output and Streaming Library |
2 | * |
3 | * Copyright (C) 2017 Red Hat, Inc. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General |
16 | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Author: Benjamin Otte <otte@gnome.org> |
19 | * Christian Kellner <gicmo@gnome.org> |
20 | */ |
21 | |
22 | #include "config.h" |
23 | |
24 | #include "gdkselectionoutputstream-x11.h" |
25 | |
26 | #include "gdkclipboard-x11.h" |
27 | #include "gdkdisplay-x11.h" |
28 | #include "gdkintl.h" |
29 | #include "gdktextlistconverter-x11.h" |
30 | #include "gdkx11display.h" |
31 | #include "gdkx11property.h" |
32 | #include "gdkx11surface.h" |
33 | |
34 | typedef struct _GdkX11PendingSelectionNotify GdkX11PendingSelectionNotify; |
35 | typedef struct _GdkX11SelectionOutputStreamPrivate GdkX11SelectionOutputStreamPrivate; |
36 | |
37 | struct _GdkX11SelectionOutputStreamPrivate { |
38 | GdkDisplay *display; |
39 | GdkX11PendingSelectionNotify *notify; |
40 | Window xwindow; |
41 | char *selection; |
42 | Atom xselection; |
43 | char *target; |
44 | Atom xtarget; |
45 | char *property; |
46 | Atom xproperty; |
47 | char *type; |
48 | Atom xtype; |
49 | int format; |
50 | gulong timestamp; |
51 | |
52 | GMutex mutex; |
53 | GCond cond; |
54 | GByteArray *data; |
55 | guint flush_requested : 1; |
56 | |
57 | GTask *pending_task; |
58 | |
59 | guint incr : 1; |
60 | guint sent_end_of_stream : 1; |
61 | guint delete_pending : 1; /* owns a reference */ |
62 | }; |
63 | |
64 | struct _GdkX11PendingSelectionNotify |
65 | { |
66 | gsize n_pending; |
67 | |
68 | XSelectionEvent xevent; |
69 | }; |
70 | |
71 | G_DEFINE_TYPE_WITH_PRIVATE (GdkX11SelectionOutputStream, gdk_x11_selection_output_stream, G_TYPE_OUTPUT_STREAM); |
72 | |
73 | static GdkX11PendingSelectionNotify * |
74 | gdk_x11_pending_selection_notify_new (Window window, |
75 | Atom selection, |
76 | Atom target, |
77 | Atom property, |
78 | Time timestamp) |
79 | { |
80 | GdkX11PendingSelectionNotify *pending; |
81 | |
82 | pending = g_slice_new0 (GdkX11PendingSelectionNotify); |
83 | pending->n_pending = 1; |
84 | |
85 | pending->xevent.type = SelectionNotify; |
86 | pending->xevent.serial = 0; |
87 | pending->xevent.send_event = True; |
88 | pending->xevent.requestor = window; |
89 | pending->xevent.selection = selection; |
90 | pending->xevent.target = target; |
91 | pending->xevent.property = property; |
92 | pending->xevent.time = timestamp; |
93 | |
94 | return pending; |
95 | } |
96 | |
97 | static void |
98 | gdk_x11_pending_selection_notify_free (GdkX11PendingSelectionNotify *notify) |
99 | { |
100 | g_slice_free (GdkX11PendingSelectionNotify, notify); |
101 | } |
102 | |
103 | static void |
104 | gdk_x11_pending_selection_notify_require (GdkX11PendingSelectionNotify *notify, |
105 | guint n_sends) |
106 | { |
107 | notify->n_pending += n_sends; |
108 | } |
109 | |
110 | static void |
111 | gdk_x11_pending_selection_notify_send (GdkX11PendingSelectionNotify *notify, |
112 | GdkDisplay *display, |
113 | gboolean success) |
114 | { |
115 | Display *xdisplay; |
116 | int error; |
117 | |
118 | notify->n_pending--; |
119 | if (notify->n_pending) |
120 | { |
121 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: not sending SelectionNotify yet, %zu streams still pending\n" , |
122 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection), |
123 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.target), |
124 | notify->n_pending)); |
125 | return; |
126 | } |
127 | |
128 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: sending SelectionNotify reporting %s\n" , |
129 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection), |
130 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.target), |
131 | success ? "success" : "failure" )); |
132 | if (!success) |
133 | notify->xevent.property = None; |
134 | |
135 | xdisplay = gdk_x11_display_get_xdisplay (display); |
136 | |
137 | gdk_x11_display_error_trap_push (display); |
138 | |
139 | if (XSendEvent (xdisplay, notify->xevent.requestor, False, NoEventMask, (XEvent*) ¬ify->xevent) == 0) |
140 | { |
141 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: failed to XSendEvent()\n" , |
142 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection), |
143 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.target))); |
144 | } |
145 | XSync (xdisplay, False); |
146 | |
147 | error = gdk_x11_display_error_trap_pop (display); |
148 | if (error != Success) |
149 | { |
150 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: X error during write: %d\n" , |
151 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection), |
152 | gdk_x11_get_xatom_name_for_display (display, notify->xevent.target), |
153 | error)); |
154 | } |
155 | |
156 | gdk_x11_pending_selection_notify_free (notify); |
157 | } |
158 | |
159 | static gboolean |
160 | gdk_x11_selection_output_stream_xevent (GdkDisplay *display, |
161 | const XEvent *xevent, |
162 | gpointer data); |
163 | |
164 | static gboolean |
165 | gdk_x11_selection_output_stream_can_flush (GdkX11SelectionOutputStream *stream) |
166 | { |
167 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
168 | |
169 | if (priv->delete_pending) |
170 | return FALSE; |
171 | |
172 | return TRUE; |
173 | } |
174 | |
175 | static gboolean |
176 | gdk_x11_selection_output_stream_needs_flush_unlocked (GdkX11SelectionOutputStream *stream) |
177 | { |
178 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
179 | |
180 | if (priv->sent_end_of_stream) |
181 | return FALSE; |
182 | |
183 | if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)) || |
184 | g_output_stream_is_closed (G_OUTPUT_STREAM (stream))) |
185 | return TRUE; |
186 | |
187 | if (priv->data->len == 0 && priv->notify == NULL) |
188 | return FALSE; |
189 | |
190 | if (priv->flush_requested) |
191 | return TRUE; |
192 | |
193 | return priv->data->len >= gdk_x11_display_get_max_request_size (display: priv->display); |
194 | } |
195 | |
196 | static gboolean |
197 | gdk_x11_selection_output_stream_needs_flush (GdkX11SelectionOutputStream *stream) |
198 | { |
199 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
200 | |
201 | gboolean result; |
202 | |
203 | g_mutex_lock (mutex: &priv->mutex); |
204 | |
205 | result = gdk_x11_selection_output_stream_needs_flush_unlocked (stream); |
206 | |
207 | g_mutex_unlock (mutex: &priv->mutex); |
208 | |
209 | return result; |
210 | } |
211 | |
212 | static gsize |
213 | get_element_size (int format) |
214 | { |
215 | switch (format) |
216 | { |
217 | case 8: |
218 | return 1; |
219 | |
220 | case 16: |
221 | return sizeof (short); |
222 | |
223 | case 32: |
224 | return sizeof (long); |
225 | |
226 | default: |
227 | g_warning ("Unknown format %u" , format); |
228 | return 1; |
229 | } |
230 | } |
231 | |
232 | static void |
233 | gdk_x11_selection_output_stream_perform_flush (GdkX11SelectionOutputStream *stream) |
234 | { |
235 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
236 | Display *xdisplay; |
237 | gsize element_size, n_elements; |
238 | int error; |
239 | |
240 | g_assert (!priv->delete_pending); |
241 | |
242 | xdisplay = gdk_x11_display_get_xdisplay (display: priv->display); |
243 | |
244 | /* We operate on a foreign window, better guard against catastrophe */ |
245 | gdk_x11_display_error_trap_push (display: priv->display); |
246 | |
247 | g_mutex_lock (mutex: &priv->mutex); |
248 | |
249 | element_size = get_element_size (format: priv->format); |
250 | n_elements = priv->data->len / element_size; |
251 | |
252 | if (priv->notify && !g_output_stream_is_closing (G_OUTPUT_STREAM (stream))) |
253 | { |
254 | XWindowAttributes attrs; |
255 | |
256 | priv->incr = TRUE; |
257 | GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: initiating INCR transfer\n" , |
258 | priv->selection, priv->target)); |
259 | |
260 | XGetWindowAttributes (xdisplay, |
261 | priv->xwindow, |
262 | &attrs); |
263 | if (!(attrs.your_event_mask & PropertyChangeMask)) |
264 | { |
265 | XSelectInput (xdisplay, priv->xwindow, attrs.your_event_mask | PropertyChangeMask); |
266 | } |
267 | |
268 | XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display), |
269 | priv->xwindow, |
270 | priv->xproperty, |
271 | gdk_x11_get_xatom_by_name_for_display (display: priv->display, atom_name: "INCR" ), |
272 | 32, |
273 | PropModeReplace, |
274 | (guchar *) &(long) { n_elements }, |
275 | 1); |
276 | } |
277 | else |
278 | { |
279 | XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display), |
280 | priv->xwindow, |
281 | priv->xproperty, |
282 | priv->xtype, |
283 | priv->format, |
284 | PropModeReplace, |
285 | priv->data->data, |
286 | n_elements); |
287 | GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: wrote %zu/%u bytes\n" , |
288 | priv->selection, priv->target, n_elements * element_size, priv->data->len)); |
289 | g_byte_array_remove_range (array: priv->data, index_: 0, length: n_elements * element_size); |
290 | if (priv->data->len < element_size) |
291 | priv->flush_requested = FALSE; |
292 | if (!priv->incr || n_elements == 0) |
293 | priv->sent_end_of_stream = TRUE; |
294 | } |
295 | |
296 | if (priv->notify) |
297 | { |
298 | gdk_x11_pending_selection_notify_send (notify: priv->notify, display: priv->display, TRUE); |
299 | priv->notify = NULL; |
300 | } |
301 | |
302 | g_object_ref (stream); |
303 | priv->delete_pending = TRUE; |
304 | g_cond_broadcast (cond: &priv->cond); |
305 | g_mutex_unlock (mutex: &priv->mutex); |
306 | |
307 | /* XXX: handle failure here and report EPIPE for future operations on the stream? */ |
308 | error = gdk_x11_display_error_trap_pop (display: priv->display); |
309 | if (error != Success) |
310 | { |
311 | GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: X error during write: %d\n" , |
312 | priv->selection, priv->target, error)); |
313 | } |
314 | |
315 | if (priv->pending_task) |
316 | { |
317 | g_task_return_int (task: priv->pending_task, GPOINTER_TO_SIZE (g_task_get_task_data (priv->pending_task))); |
318 | g_object_unref (object: priv->pending_task); |
319 | priv->pending_task = NULL; |
320 | } |
321 | } |
322 | |
323 | static gboolean |
324 | gdk_x11_selection_output_stream_invoke_flush (gpointer data) |
325 | { |
326 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data); |
327 | |
328 | if (gdk_x11_selection_output_stream_needs_flush (stream) && |
329 | gdk_x11_selection_output_stream_can_flush (stream)) |
330 | gdk_x11_selection_output_stream_perform_flush (stream); |
331 | |
332 | return G_SOURCE_REMOVE; |
333 | } |
334 | |
335 | static gssize |
336 | gdk_x11_selection_output_stream_write (GOutputStream *output_stream, |
337 | const void *buffer, |
338 | gsize count, |
339 | GCancellable *cancellable, |
340 | GError **error) |
341 | { |
342 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream); |
343 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
344 | |
345 | g_mutex_lock (mutex: &priv->mutex); |
346 | g_byte_array_append (array: priv->data, data: buffer, len: count); |
347 | GDK_NOTE (SELECTION, g_printerr ("%s:%s: wrote %zu bytes, %u total now\n" , |
348 | priv->selection, priv->target, count, priv->data->len)); |
349 | g_mutex_unlock (mutex: &priv->mutex); |
350 | |
351 | g_main_context_invoke (NULL, function: gdk_x11_selection_output_stream_invoke_flush, data: stream); |
352 | |
353 | g_mutex_lock (mutex: &priv->mutex); |
354 | if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream)) |
355 | g_cond_wait (cond: &priv->cond, mutex: &priv->mutex); |
356 | g_mutex_unlock (mutex: &priv->mutex); |
357 | |
358 | return count; |
359 | } |
360 | |
361 | static void |
362 | gdk_x11_selection_output_stream_write_async (GOutputStream *output_stream, |
363 | const void *buffer, |
364 | gsize count, |
365 | int io_priority, |
366 | GCancellable *cancellable, |
367 | GAsyncReadyCallback callback, |
368 | gpointer user_data) |
369 | { |
370 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream); |
371 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
372 | GTask *task; |
373 | |
374 | task = g_task_new (source_object: stream, cancellable, callback, callback_data: user_data); |
375 | g_task_set_source_tag (task, gdk_x11_selection_output_stream_write_async); |
376 | g_task_set_priority (task, priority: io_priority); |
377 | |
378 | g_mutex_lock (mutex: &priv->mutex); |
379 | g_byte_array_append (array: priv->data, data: buffer, len: count); |
380 | GDK_NOTE (SELECTION, g_printerr ("%s:%s: async wrote %zu bytes, %u total now\n" , |
381 | priv->selection, priv->target, count, priv->data->len)); |
382 | g_mutex_unlock (mutex: &priv->mutex); |
383 | |
384 | if (!gdk_x11_selection_output_stream_needs_flush (stream)) |
385 | { |
386 | g_task_return_int (task, result: count); |
387 | g_object_unref (object: task); |
388 | return; |
389 | } |
390 | else if (!gdk_x11_selection_output_stream_can_flush (stream)) |
391 | { |
392 | g_assert (priv->pending_task == NULL); |
393 | priv->pending_task = task; |
394 | g_task_set_task_data (task, GSIZE_TO_POINTER (count), NULL); |
395 | return; |
396 | } |
397 | else |
398 | { |
399 | gdk_x11_selection_output_stream_perform_flush (stream); |
400 | g_task_return_int (task, result: count); |
401 | g_object_unref (object: task); |
402 | return; |
403 | } |
404 | } |
405 | |
406 | static gssize |
407 | gdk_x11_selection_output_stream_write_finish (GOutputStream *stream, |
408 | GAsyncResult *result, |
409 | GError **error) |
410 | { |
411 | g_return_val_if_fail (g_task_is_valid (result, stream), -1); |
412 | g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_x11_selection_output_stream_write_async, -1); |
413 | |
414 | return g_task_propagate_int (G_TASK (result), error); |
415 | } |
416 | |
417 | static gboolean |
418 | gdk_x11_selection_output_request_flush (GdkX11SelectionOutputStream *stream) |
419 | { |
420 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
421 | gboolean needs_flush; |
422 | |
423 | g_mutex_lock (mutex: &priv->mutex); |
424 | |
425 | if (priv->data->len >= get_element_size (format: priv->format)) |
426 | priv->flush_requested = TRUE; |
427 | |
428 | needs_flush = gdk_x11_selection_output_stream_needs_flush_unlocked (stream); |
429 | g_mutex_unlock (mutex: &priv->mutex); |
430 | |
431 | GDK_NOTE (SELECTION, g_printerr ("%s:%s: requested flush%s\n" , |
432 | priv->selection, priv->target, needs_flush ?"" : ", but not needed" )); |
433 | return needs_flush; |
434 | } |
435 | |
436 | static gboolean |
437 | gdk_x11_selection_output_stream_flush (GOutputStream *output_stream, |
438 | GCancellable *cancellable, |
439 | GError **error) |
440 | { |
441 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream); |
442 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
443 | |
444 | if (!gdk_x11_selection_output_request_flush (stream)) |
445 | return TRUE; |
446 | |
447 | g_main_context_invoke (NULL, function: gdk_x11_selection_output_stream_invoke_flush, data: stream); |
448 | |
449 | g_mutex_lock (mutex: &priv->mutex); |
450 | if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream)) |
451 | g_cond_wait (cond: &priv->cond, mutex: &priv->mutex); |
452 | g_mutex_unlock (mutex: &priv->mutex); |
453 | |
454 | return TRUE; |
455 | } |
456 | |
457 | static void |
458 | gdk_x11_selection_output_stream_flush_async (GOutputStream *output_stream, |
459 | int io_priority, |
460 | GCancellable *cancellable, |
461 | GAsyncReadyCallback callback, |
462 | gpointer user_data) |
463 | { |
464 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream); |
465 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
466 | GTask *task; |
467 | |
468 | task = g_task_new (source_object: stream, cancellable, callback, callback_data: user_data); |
469 | g_task_set_source_tag (task, gdk_x11_selection_output_stream_flush_async); |
470 | g_task_set_priority (task, priority: io_priority); |
471 | |
472 | if (!gdk_x11_selection_output_stream_can_flush (stream)) |
473 | { |
474 | if (gdk_x11_selection_output_request_flush (stream)) |
475 | { |
476 | g_assert (priv->pending_task == NULL); |
477 | priv->pending_task = task; |
478 | return; |
479 | } |
480 | else |
481 | { |
482 | g_task_return_boolean (task, TRUE); |
483 | g_object_unref (object: task); |
484 | return; |
485 | } |
486 | } |
487 | |
488 | gdk_x11_selection_output_stream_perform_flush (stream); |
489 | g_task_return_boolean (task, TRUE); |
490 | g_object_unref (object: task); |
491 | return; |
492 | } |
493 | |
494 | static gboolean |
495 | gdk_x11_selection_output_stream_flush_finish (GOutputStream *stream, |
496 | GAsyncResult *result, |
497 | GError **error) |
498 | { |
499 | g_return_val_if_fail (g_task_is_valid (result, stream), FALSE); |
500 | g_return_val_if_fail (g_async_result_is_tagged (result, gdk_x11_selection_output_stream_flush_async), FALSE); |
501 | |
502 | return g_task_propagate_boolean (G_TASK (result), error); |
503 | } |
504 | |
505 | static void |
506 | gdk_x11_selection_output_stream_finalize (GObject *object) |
507 | { |
508 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (object); |
509 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
510 | |
511 | /* not sending a notify is terrible */ |
512 | g_assert (priv->notify == NULL); |
513 | |
514 | GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: finalizing\n" , |
515 | priv->selection, priv->target)); |
516 | GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, data: stream); |
517 | g_signal_handlers_disconnect_by_func (priv->display, |
518 | gdk_x11_selection_output_stream_xevent, |
519 | stream); |
520 | |
521 | g_byte_array_unref (array: priv->data); |
522 | g_cond_clear (cond: &priv->cond); |
523 | g_mutex_clear (mutex: &priv->mutex); |
524 | |
525 | g_free (mem: priv->selection); |
526 | g_free (mem: priv->target); |
527 | g_free (mem: priv->property); |
528 | g_free (mem: priv->type); |
529 | |
530 | G_OBJECT_CLASS (gdk_x11_selection_output_stream_parent_class)->finalize (object); |
531 | } |
532 | |
533 | static void |
534 | gdk_x11_selection_output_stream_class_init (GdkX11SelectionOutputStreamClass *klass) |
535 | { |
536 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
537 | GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass); |
538 | |
539 | object_class->finalize = gdk_x11_selection_output_stream_finalize; |
540 | |
541 | output_stream_class->write_fn = gdk_x11_selection_output_stream_write; |
542 | output_stream_class->flush = gdk_x11_selection_output_stream_flush; |
543 | |
544 | output_stream_class->write_async = gdk_x11_selection_output_stream_write_async; |
545 | output_stream_class->write_finish = gdk_x11_selection_output_stream_write_finish; |
546 | output_stream_class->flush_async = gdk_x11_selection_output_stream_flush_async; |
547 | output_stream_class->flush_finish = gdk_x11_selection_output_stream_flush_finish; |
548 | } |
549 | |
550 | static void |
551 | gdk_x11_selection_output_stream_init (GdkX11SelectionOutputStream *stream) |
552 | { |
553 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
554 | |
555 | g_mutex_init (mutex: &priv->mutex); |
556 | g_cond_init (cond: &priv->cond); |
557 | priv->data = g_byte_array_new (); |
558 | } |
559 | |
560 | static gboolean |
561 | gdk_x11_selection_output_stream_xevent (GdkDisplay *display, |
562 | const XEvent *xevent, |
563 | gpointer data) |
564 | { |
565 | GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data); |
566 | GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
567 | Display *xdisplay; |
568 | |
569 | xdisplay = gdk_x11_display_get_xdisplay (display: priv->display); |
570 | |
571 | if (xevent->xany.display != xdisplay || |
572 | xevent->xany.window != priv->xwindow) |
573 | return FALSE; |
574 | |
575 | switch (xevent->type) |
576 | { |
577 | case PropertyNotify: |
578 | if (!priv->incr || |
579 | xevent->xproperty.atom != priv->xproperty || |
580 | xevent->xproperty.state != PropertyDelete) |
581 | return FALSE; |
582 | |
583 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: got PropertyNotify Delete during INCR\n" , |
584 | priv->selection, priv->target)); |
585 | priv->delete_pending = FALSE; |
586 | if (gdk_x11_selection_output_stream_needs_flush (stream) && |
587 | gdk_x11_selection_output_stream_can_flush (stream)) |
588 | gdk_x11_selection_output_stream_perform_flush (stream); |
589 | g_object_unref (object: stream); /* from unsetting the delete_pending */ |
590 | return FALSE; |
591 | |
592 | default: |
593 | return FALSE; |
594 | } |
595 | } |
596 | |
597 | static GOutputStream * |
598 | gdk_x11_selection_output_stream_new (GdkDisplay *display, |
599 | GdkX11PendingSelectionNotify *notify, |
600 | Window window, |
601 | const char *selection, |
602 | const char *target, |
603 | const char *property, |
604 | const char *type, |
605 | int format, |
606 | gulong timestamp) |
607 | { |
608 | GdkX11SelectionOutputStream *stream; |
609 | GdkX11SelectionOutputStreamPrivate *priv; |
610 | |
611 | stream = g_object_new (GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, NULL); |
612 | priv = gdk_x11_selection_output_stream_get_instance_private (self: stream); |
613 | |
614 | priv->display = display; |
615 | GDK_X11_DISPLAY (display)->streams = g_slist_prepend (GDK_X11_DISPLAY (display)->streams, data: stream); |
616 | priv->notify = notify; |
617 | priv->xwindow = window; |
618 | priv->selection = g_strdup (str: selection); |
619 | priv->xselection = gdk_x11_get_xatom_by_name_for_display (display, atom_name: priv->selection); |
620 | priv->target = g_strdup (str: target); |
621 | priv->xtarget = gdk_x11_get_xatom_by_name_for_display (display, atom_name: priv->target); |
622 | priv->property = g_strdup (str: property); |
623 | priv->xproperty = gdk_x11_get_xatom_by_name_for_display (display, atom_name: priv->property); |
624 | priv->type = g_strdup (str: type); |
625 | priv->xtype = gdk_x11_get_xatom_by_name_for_display (display, atom_name: priv->type); |
626 | priv->format = format; |
627 | priv->timestamp = timestamp; |
628 | |
629 | g_signal_connect (display, |
630 | "xevent" , |
631 | G_CALLBACK (gdk_x11_selection_output_stream_xevent), |
632 | stream); |
633 | |
634 | return G_OUTPUT_STREAM (stream); |
635 | } |
636 | |
637 | static void |
638 | print_atoms (GdkDisplay *display, |
639 | const char *selection, |
640 | const char *prefix, |
641 | const Atom *atoms, |
642 | gsize n_atoms) |
643 | { |
644 | GDK_DISPLAY_NOTE (display, CLIPBOARD, |
645 | gsize i; |
646 | |
647 | g_printerr ("%s: %s [ " , selection, prefix); |
648 | for (i = 0; i < n_atoms; i++) |
649 | { |
650 | g_printerr ("%s%s" , i > 0 ? ", " : "" , gdk_x11_get_xatom_name_for_display (display , atoms[i])); |
651 | } |
652 | g_printerr (" ]\n" ); |
653 | ); |
654 | } |
655 | |
656 | static void |
657 | handle_targets_done (GObject *stream, |
658 | GAsyncResult *result, |
659 | gpointer user_data) |
660 | { |
661 | GError *error = NULL; |
662 | gsize bytes_written; |
663 | |
664 | if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, bytes_written: &bytes_written, error: &error)) |
665 | { |
666 | GDK_NOTE (CLIPBOARD, g_printerr ("---: failed to send targets after %zu bytes: %s\n" , |
667 | bytes_written, error->message)); |
668 | g_error_free (error); |
669 | } |
670 | |
671 | g_free (mem: user_data); |
672 | } |
673 | |
674 | static void |
675 | handle_targets (GOutputStream *stream, |
676 | GdkDisplay *display, |
677 | GdkContentFormats *formats, |
678 | const char *target, |
679 | const char *encoding, |
680 | int format, |
681 | gulong timestamp, |
682 | GdkX11SelectionOutputHandler handler, |
683 | gpointer user_data) |
684 | { |
685 | Atom *atoms; |
686 | gsize n_atoms; |
687 | |
688 | atoms = gdk_x11_clipboard_formats_to_atoms (display, |
689 | TRUE, |
690 | formats, |
691 | n_atoms: &n_atoms); |
692 | print_atoms (display, selection: "---" , prefix: "sending targets" , atoms, n_atoms); |
693 | g_output_stream_write_all_async (stream, |
694 | buffer: atoms, |
695 | count: n_atoms * sizeof (Atom), |
696 | G_PRIORITY_DEFAULT, |
697 | NULL, |
698 | callback: handle_targets_done, |
699 | user_data: atoms); |
700 | g_object_unref (object: stream); |
701 | } |
702 | |
703 | static void |
704 | handle_timestamp_done (GObject *stream, |
705 | GAsyncResult *result, |
706 | gpointer user_data) |
707 | { |
708 | GError *error = NULL; |
709 | gsize bytes_written; |
710 | |
711 | if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, bytes_written: &bytes_written, error: &error)) |
712 | { |
713 | GDK_NOTE (CLIPBOARD, g_printerr ("---: failed to send timestamp after %zu bytes: %s\n" , |
714 | bytes_written, error->message)); |
715 | g_error_free (error); |
716 | } |
717 | |
718 | g_slice_free (gulong, user_data); |
719 | } |
720 | |
721 | static void |
722 | handle_timestamp (GOutputStream *stream, |
723 | GdkDisplay *display, |
724 | GdkContentFormats *formats, |
725 | const char *target, |
726 | const char *encoding, |
727 | int format, |
728 | gulong timestamp, |
729 | GdkX11SelectionOutputHandler handler, |
730 | gpointer user_data) |
731 | { |
732 | gulong *time_; |
733 | |
734 | time_ = g_slice_new (gulong); |
735 | *time_ = timestamp; |
736 | |
737 | g_output_stream_write_all_async (stream, |
738 | buffer: time_, |
739 | count: sizeof (gulong), |
740 | G_PRIORITY_DEFAULT, |
741 | NULL, |
742 | callback: handle_timestamp_done, |
743 | user_data: time_); |
744 | g_object_unref (object: stream); |
745 | } |
746 | |
747 | static void |
748 | handle_save_targets (GOutputStream *stream, |
749 | GdkDisplay *display, |
750 | GdkContentFormats *formats, |
751 | const char *target, |
752 | const char *encoding, |
753 | int format, |
754 | gulong timestamp, |
755 | GdkX11SelectionOutputHandler handler, |
756 | gpointer user_data) |
757 | { |
758 | /* Don't do anything */ |
759 | |
760 | g_object_unref (object: stream); |
761 | } |
762 | |
763 | static void |
764 | handle_text_list (GOutputStream *stream, |
765 | GdkDisplay *display, |
766 | GdkContentFormats *formats, |
767 | const char *target, |
768 | const char *encoding, |
769 | int format, |
770 | gulong timestamp, |
771 | GdkX11SelectionOutputHandler handler, |
772 | gpointer user_data) |
773 | { |
774 | GOutputStream *converter_stream; |
775 | GConverter *converter; |
776 | |
777 | converter = gdk_x11_text_list_converter_to_utf8_new (display, |
778 | encoding, |
779 | format); |
780 | converter_stream = g_converter_output_stream_new (base_stream: stream, converter); |
781 | |
782 | g_object_unref (object: converter); |
783 | g_object_unref (object: stream); |
784 | |
785 | handler (converter_stream, gdk_intern_mime_type (string: "text/plain;charset=utf-8" ), user_data); |
786 | } |
787 | |
788 | static void |
789 | handle_utf8 (GOutputStream *stream, |
790 | GdkDisplay *display, |
791 | GdkContentFormats *formats, |
792 | const char *target, |
793 | const char *encoding, |
794 | int format, |
795 | gulong timestamp, |
796 | GdkX11SelectionOutputHandler handler, |
797 | gpointer user_data) |
798 | { |
799 | handler (stream, gdk_intern_mime_type (string: "text/plain;charset=utf-8" ), user_data); |
800 | } |
801 | |
802 | typedef void (* MimeTypeHandleFunc) (GOutputStream *, GdkDisplay *, GdkContentFormats *, const char *, const char *, int, gulong, GdkX11SelectionOutputHandler, gpointer); |
803 | |
804 | static const struct { |
805 | const char *x_target; |
806 | const char *mime_type; |
807 | const char *type; |
808 | int format; |
809 | MimeTypeHandleFunc handler; |
810 | } special_targets[] = { |
811 | { "UTF8_STRING" , "text/plain;charset=utf-8" , "UTF8_STRING" , 8, handle_utf8 }, |
812 | { "COMPOUND_TEXT" , "text/plain;charset=utf-8" , "COMPOUND_TEXT" , 8, handle_text_list }, |
813 | { "TEXT" , "text/plain;charset=utf-8" , "STRING" , 8, handle_text_list }, |
814 | { "STRING" , "text/plain;charset=utf-8" , "STRING" , 8, handle_text_list }, |
815 | { "TARGETS" , NULL, "ATOM" , 32, handle_targets }, |
816 | { "TIMESTAMP" , NULL, "INTEGER" , 32, handle_timestamp }, |
817 | { "SAVE_TARGETS" , NULL, "NULL" , 32, handle_save_targets } |
818 | }; |
819 | |
820 | static gboolean |
821 | gdk_x11_selection_output_streams_request (GdkDisplay *display, |
822 | GdkX11PendingSelectionNotify *notify, |
823 | GdkContentFormats *formats, |
824 | Window requestor, |
825 | Atom xselection, |
826 | Atom xtarget, |
827 | Atom xproperty, |
828 | gulong timestamp, |
829 | GdkX11SelectionOutputHandler handler, |
830 | gpointer user_data) |
831 | { |
832 | const char *mime_type, *selection, *target, *property; |
833 | |
834 | selection = gdk_x11_get_xatom_name_for_display (display, xatom: xselection); |
835 | target = gdk_x11_get_xatom_name_for_display (display, xatom: xtarget); |
836 | property = gdk_x11_get_xatom_name_for_display (display, xatom: xproperty); |
837 | mime_type = gdk_intern_mime_type (string: target); |
838 | |
839 | if (mime_type) |
840 | { |
841 | if (gdk_content_formats_contain_mime_type (formats, mime_type)) |
842 | { |
843 | GOutputStream *stream; |
844 | |
845 | stream = gdk_x11_selection_output_stream_new (display, |
846 | notify, |
847 | window: requestor, |
848 | selection, |
849 | target, |
850 | property, |
851 | type: target, |
852 | format: 8, |
853 | timestamp); |
854 | handler (stream, mime_type, user_data); |
855 | return TRUE; |
856 | } |
857 | } |
858 | else if (g_str_equal (v1: target, v2: "MULTIPLE" )) |
859 | { |
860 | gulong n_atoms; |
861 | gulong nbytes; |
862 | Atom prop_type; |
863 | int prop_format; |
864 | Atom *atoms = NULL; |
865 | int error; |
866 | |
867 | error = XGetWindowProperty (gdk_x11_display_get_xdisplay (display), |
868 | requestor, |
869 | xproperty, |
870 | 0, 0x1FFFFFFF, False, |
871 | AnyPropertyType, |
872 | &prop_type, &prop_format, |
873 | &n_atoms, &nbytes, (guchar **) &atoms); |
874 | if (error != Success) |
875 | { |
876 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: XGetProperty() during MULTIPLE failed with %d\n" , |
877 | selection, error)); |
878 | } |
879 | else if (prop_format != 32 || |
880 | prop_type != gdk_x11_get_xatom_by_name_for_display (display, atom_name: "ATOM_PAIR" )) |
881 | { |
882 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: XGetProperty() type/format should be ATOM_PAIR/32 but is %s/%d\n" , |
883 | selection, gdk_x11_get_xatom_name_for_display (display, prop_type), prop_format)); |
884 | } |
885 | else if (n_atoms < 2) |
886 | { |
887 | print_atoms (display, selection, prefix: "ignoring MULTIPLE request with too little elements" , atoms, n_atoms); |
888 | } |
889 | else |
890 | { |
891 | gulong i; |
892 | |
893 | print_atoms (display, selection, prefix: "MULTIPLE request" , atoms, n_atoms); |
894 | if (n_atoms % 2) |
895 | { |
896 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: Number of atoms is uneven at %lu, ignoring last element\n" , |
897 | selection, n_atoms)); |
898 | n_atoms &= ~1; |
899 | } |
900 | |
901 | gdk_x11_pending_selection_notify_require (notify, n_sends: n_atoms / 2); |
902 | |
903 | for (i = 0; i < n_atoms / 2; i++) |
904 | { |
905 | gboolean success; |
906 | |
907 | if (atoms[2 * i] == None || atoms[2 * i + 1] == None) |
908 | { |
909 | success = FALSE; |
910 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: None not allowed as atom in MULTIPLE request\n" , |
911 | selection)); |
912 | gdk_x11_pending_selection_notify_send (notify, display, FALSE); |
913 | } |
914 | else if (atoms[2 * i] == gdk_x11_get_xatom_by_name_for_display (display, atom_name: "MULTIPLE" )) |
915 | { |
916 | success = FALSE; |
917 | GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: MULTIPLE as target in MULTIPLE request would cause recursion\n" , |
918 | selection)); |
919 | gdk_x11_pending_selection_notify_send (notify, display, FALSE); |
920 | } |
921 | else |
922 | { |
923 | success = gdk_x11_selection_output_streams_request (display, |
924 | notify, |
925 | formats, |
926 | requestor, |
927 | xselection, |
928 | xtarget: atoms[2 * i], |
929 | xproperty: atoms[2 * i + 1], |
930 | timestamp, |
931 | handler, |
932 | user_data); |
933 | } |
934 | |
935 | if (!success) |
936 | atoms[2 * i + 1] = None; |
937 | } |
938 | } |
939 | |
940 | XChangeProperty (gdk_x11_display_get_xdisplay (display), |
941 | requestor, |
942 | xproperty, |
943 | prop_type, 32, |
944 | PropModeReplace, (guchar *)atoms, n_atoms); |
945 | |
946 | if (atoms) |
947 | XFree (atoms); |
948 | |
949 | gdk_x11_pending_selection_notify_send (notify, display, TRUE); |
950 | return TRUE; |
951 | } |
952 | else |
953 | { |
954 | gsize i; |
955 | |
956 | for (i = 0; i < G_N_ELEMENTS (special_targets); i++) |
957 | { |
958 | if (g_str_equal (v1: target, v2: special_targets[i].x_target) && |
959 | special_targets[i].handler) |
960 | { |
961 | GOutputStream *stream; |
962 | |
963 | if (special_targets[i].mime_type) |
964 | mime_type = gdk_intern_mime_type (string: special_targets[i].mime_type); |
965 | stream = gdk_x11_selection_output_stream_new (display, |
966 | notify, |
967 | window: requestor, |
968 | selection, |
969 | target, |
970 | property, |
971 | type: special_targets[i].type, |
972 | format: special_targets[i].format, |
973 | timestamp); |
974 | special_targets[i].handler (stream, |
975 | display, |
976 | formats, |
977 | target, |
978 | special_targets[i].type, |
979 | special_targets[i].format, |
980 | timestamp, |
981 | handler, |
982 | user_data); |
983 | return TRUE; |
984 | } |
985 | } |
986 | } |
987 | |
988 | gdk_x11_pending_selection_notify_send (notify, display, FALSE); |
989 | return FALSE; |
990 | } |
991 | |
992 | void |
993 | gdk_x11_selection_output_streams_create (GdkDisplay *display, |
994 | GdkContentFormats *formats, |
995 | Window requestor, |
996 | Atom selection, |
997 | Atom target, |
998 | Atom property, |
999 | gulong timestamp, |
1000 | GdkX11SelectionOutputHandler handler, |
1001 | gpointer user_data) |
1002 | { |
1003 | GdkX11PendingSelectionNotify *notify; |
1004 | |
1005 | notify = gdk_x11_pending_selection_notify_new (window: requestor, |
1006 | selection, |
1007 | target, |
1008 | property, |
1009 | timestamp); |
1010 | gdk_x11_selection_output_streams_request (display, |
1011 | notify, |
1012 | formats, |
1013 | requestor, |
1014 | xselection: selection, |
1015 | xtarget: target, |
1016 | xproperty: property, |
1017 | timestamp, |
1018 | handler, |
1019 | user_data); |
1020 | } |
1021 | |