1 | /* GDK - The GIMP Drawing Kit |
2 | * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald |
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 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | |
27 | #include "gdkdropprivate.h" |
28 | |
29 | #include "gdk-private.h" |
30 | #include "gdkasync.h" |
31 | #include "gdkclipboardprivate.h" |
32 | #include "gdkclipboard-x11.h" |
33 | #include "gdkdeviceprivate.h" |
34 | #include "gdkdisplay-x11.h" |
35 | #include "gdkdragprivate.h" |
36 | #include "gdkintl.h" |
37 | #include "gdkprivate-x11.h" |
38 | #include "gdkscreen-x11.h" |
39 | #include "gdkselectioninputstream-x11.h" |
40 | #include "gdkselectionoutputstream-x11.h" |
41 | #include "gdktextlistconverter-x11.h" |
42 | |
43 | #include <X11/Xlib.h> |
44 | #include <X11/Xutil.h> |
45 | #include <X11/Xatom.h> |
46 | #include <X11/extensions/shape.h> |
47 | |
48 | #include <string.h> |
49 | |
50 | #define GDK_TYPE_X11_DROP (gdk_x11_drop_get_type()) |
51 | G_DECLARE_FINAL_TYPE (GdkX11Drop, gdk_x11_drop, GDK, X11_DROP, GdkDrop) |
52 | |
53 | struct _GdkX11Drop |
54 | { |
55 | GdkDrop parent_instance; |
56 | |
57 | Window source_window; |
58 | |
59 | guint16 last_x; /* Coordinates from last event */ |
60 | guint16 last_y; |
61 | gulong timestamp; /* Timestamp we claimed the DND selection with */ |
62 | guint version; /* Xdnd protocol version */ |
63 | |
64 | GdkDragAction xdnd_actions; /* What is currently set in XdndActionList */ |
65 | GdkDragAction suggested_action; |
66 | |
67 | guint xdnd_targets_set : 1; /* Whether we've already set XdndTypeList */ |
68 | guint xdnd_have_actions : 1; /* Whether an XdndActionList was provided */ |
69 | guint enter_emitted : 1; /* Set after gdk_drop_emit_enter() */ |
70 | }; |
71 | |
72 | struct _GdkX11DropClass |
73 | { |
74 | GdkDropClass parent_class; |
75 | }; |
76 | |
77 | /* Forward declarations */ |
78 | |
79 | static gboolean xdnd_source_surface_filter (GdkDisplay *display, |
80 | const XEvent *xevent, |
81 | gpointer data); |
82 | static gboolean xdnd_enter_filter (GdkSurface *surface, |
83 | const XEvent *xevent); |
84 | static gboolean xdnd_leave_filter (GdkSurface *surface, |
85 | const XEvent *xevent); |
86 | static gboolean xdnd_position_filter (GdkSurface *surface, |
87 | const XEvent *xevent); |
88 | static gboolean xdnd_drop_filter (GdkSurface *surface, |
89 | const XEvent *xevent); |
90 | |
91 | static const struct { |
92 | const char *atom_name; |
93 | gboolean (* func) (GdkSurface *surface, const XEvent *event); |
94 | } xdnd_filters[] = { |
95 | { "XdndEnter" , xdnd_enter_filter }, |
96 | { "XdndLeave" , xdnd_leave_filter }, |
97 | { "XdndPosition" , xdnd_position_filter }, |
98 | { "XdndDrop" , xdnd_drop_filter }, |
99 | }; |
100 | |
101 | G_DEFINE_TYPE (GdkX11Drop, gdk_x11_drop, GDK_TYPE_DROP) |
102 | |
103 | static GInputStream * |
104 | text_list_convert (GdkDisplay *display, |
105 | GInputStream *stream, |
106 | const char *encoding, |
107 | int format) |
108 | { |
109 | GInputStream *converter_stream; |
110 | GConverter *converter; |
111 | |
112 | converter = gdk_x11_text_list_converter_to_utf8_new (display, encoding, format); |
113 | converter_stream = g_converter_input_stream_new (base_stream: stream, converter); |
114 | |
115 | g_object_unref (object: converter); |
116 | g_object_unref (object: stream); |
117 | |
118 | return converter_stream; |
119 | } |
120 | |
121 | static GInputStream * |
122 | no_convert (GdkDisplay *display, |
123 | GInputStream *stream, |
124 | const char *encoding, |
125 | int format) |
126 | { |
127 | return stream; |
128 | } |
129 | |
130 | static const struct { |
131 | const char *x_target; |
132 | const char *mime_type; |
133 | GInputStream * (* convert) (GdkDisplay *, GInputStream *, const char *, int); |
134 | const char *type; |
135 | int format; |
136 | } special_targets[] = { |
137 | { "UTF8_STRING" , "text/plain;charset=utf-8" , no_convert, "UTF8_STRING" , 8 }, |
138 | { "COMPOUND_TEXT" , "text/plain;charset=utf-8" , text_list_convert, "COMPOUND_TEXT" , 8 }, |
139 | { "TEXT" , "text/plain;charset=utf-8" , text_list_convert, "STRING" , 8 }, |
140 | { "STRING" , "text/plain;charset=utf-8" , text_list_convert, "STRING" , 8 }, |
141 | { "TARGETS" , NULL, NULL, "ATOM" , 32 }, |
142 | { "TIMESTAMP" , NULL, NULL, "INTEGER" , 32 }, |
143 | { "SAVE_TARGETS" , NULL, NULL, "NULL" , 32 } |
144 | }; |
145 | |
146 | static void |
147 | gdk_x11_drop_read_got_stream (GObject *source, |
148 | GAsyncResult *res, |
149 | gpointer data) |
150 | { |
151 | GTask *task = data; |
152 | GError *error = NULL; |
153 | GInputStream *stream; |
154 | const char *type; |
155 | int format; |
156 | |
157 | stream = gdk_x11_selection_input_stream_new_finish (result: res, type: &type, format: &format, error: &error); |
158 | if (stream == NULL) |
159 | { |
160 | GSList *targets, *next; |
161 | |
162 | targets = g_task_get_task_data (task); |
163 | next = targets->next; |
164 | if (next) |
165 | { |
166 | GdkDrop *drop = GDK_DROP (g_task_get_source_object (task)); |
167 | |
168 | GDK_DISPLAY_NOTE (gdk_drop_get_display (drop), DND, g_printerr ("reading %s failed, trying %s next\n" , |
169 | (char *) targets->data, (char *) next->data)); |
170 | targets->next = NULL; |
171 | g_task_set_task_data (task, task_data: next, task_data_destroy: (GDestroyNotify) g_slist_free); |
172 | gdk_x11_selection_input_stream_new_async (display: gdk_drop_get_display (self: drop), |
173 | selection: "XdndSelection" , |
174 | target: next->data, |
175 | CurrentTime, |
176 | io_priority: g_task_get_priority (task), |
177 | cancellable: g_task_get_cancellable (task), |
178 | callback: gdk_x11_drop_read_got_stream, |
179 | user_data: task); |
180 | g_error_free (error); |
181 | return; |
182 | } |
183 | |
184 | g_task_return_error (task, error); |
185 | } |
186 | else |
187 | { |
188 | gsize i; |
189 | const char *mime_type = ((GSList *) g_task_get_task_data (task))->data; |
190 | GdkDrop *drop = GDK_DROP (g_task_get_source_object (task)); |
191 | |
192 | for (i = 0; i < G_N_ELEMENTS (special_targets); i++) |
193 | { |
194 | if (g_str_equal (v1: mime_type, v2: special_targets[i].x_target)) |
195 | { |
196 | g_assert (special_targets[i].mime_type != NULL); |
197 | |
198 | mime_type = g_intern_string (string: special_targets[i].mime_type); |
199 | g_task_set_task_data (task, task_data: g_slist_prepend (NULL, data: (gpointer) mime_type), task_data_destroy: (GDestroyNotify) g_slist_free); |
200 | stream = special_targets[i].convert (gdk_drop_get_display (self: drop), stream, type, format); |
201 | break; |
202 | } |
203 | } |
204 | |
205 | GDK_NOTE (DND, g_printerr ("reading DND as %s now\n" , |
206 | (const char *)((GSList *) g_task_get_task_data (task))->data)); |
207 | g_task_return_pointer (task, result: stream, result_destroy: g_object_unref); |
208 | } |
209 | |
210 | g_object_unref (object: task); |
211 | } |
212 | |
213 | static void |
214 | gdk_x11_drop_read_async (GdkDrop *drop, |
215 | GdkContentFormats *formats, |
216 | int io_priority, |
217 | GCancellable *cancellable, |
218 | GAsyncReadyCallback callback, |
219 | gpointer user_data) |
220 | { |
221 | GSList *targets; |
222 | GTask *task; |
223 | |
224 | task = g_task_new (source_object: drop, cancellable, callback, callback_data: user_data); |
225 | g_task_set_priority (task, priority: io_priority); |
226 | g_task_set_source_tag (task, gdk_x11_drop_read_async); |
227 | |
228 | targets = gdk_x11_clipboard_formats_to_targets (formats); |
229 | g_task_set_task_data (task, task_data: targets, task_data_destroy: (GDestroyNotify) g_slist_free); |
230 | if (targets == NULL) |
231 | { |
232 | g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
233 | _("No compatible transfer format found" )); |
234 | return; |
235 | } |
236 | |
237 | GDK_DISPLAY_NOTE (gdk_drop_get_display (drop), DND, g_printerr ("new read for %s (%u other options)\n" , |
238 | (char *) targets->data, g_slist_length (targets->next))); |
239 | gdk_x11_selection_input_stream_new_async (display: gdk_drop_get_display (self: drop), |
240 | selection: "XdndSelection" , |
241 | target: targets->data, |
242 | CurrentTime, |
243 | io_priority, |
244 | cancellable, |
245 | callback: gdk_x11_drop_read_got_stream, |
246 | user_data: task); |
247 | } |
248 | |
249 | static GInputStream * |
250 | gdk_x11_drop_read_finish (GdkDrop *drop, |
251 | GAsyncResult *result, |
252 | const char **out_mime_type, |
253 | GError **error) |
254 | { |
255 | GTask *task; |
256 | |
257 | g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (drop)), NULL); |
258 | task = G_TASK (result); |
259 | g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_drop_read_async, NULL); |
260 | |
261 | if (out_mime_type) |
262 | { |
263 | GSList *targets; |
264 | |
265 | targets = g_task_get_task_data (task); |
266 | *out_mime_type = targets ? targets->data : NULL; |
267 | } |
268 | |
269 | return g_task_propagate_pointer (task, error); |
270 | } |
271 | |
272 | static void |
273 | gdk_x11_drop_finalize (GObject *object) |
274 | { |
275 | GdkX11Drop *drop_x11 = GDK_X11_DROP (ptr: object); |
276 | |
277 | if (gdk_drop_get_drag (GDK_DROP (drop_x11)) == NULL) |
278 | { |
279 | g_signal_handlers_disconnect_by_func (gdk_drop_get_display (GDK_DROP (drop_x11)), |
280 | xdnd_source_surface_filter, |
281 | drop_x11); |
282 | /* Should we remove the GDK_PROPERTY_NOTIFY mask? |
283 | * but we might want it for other reasons. (Like |
284 | * INCR selection transactions). |
285 | */ |
286 | } |
287 | |
288 | G_OBJECT_CLASS (gdk_x11_drop_parent_class)->finalize (object); |
289 | } |
290 | |
291 | /* Utility functions */ |
292 | |
293 | #ifdef G_ENABLE_DEBUG |
294 | static void |
295 | print_target_list (GdkContentFormats *formats) |
296 | { |
297 | char *name = gdk_content_formats_to_string (formats); |
298 | g_message ("DND formats: %s" , name); |
299 | g_free (mem: name); |
300 | } |
301 | #endif /* G_ENABLE_DEBUG */ |
302 | |
303 | /************************************************************* |
304 | ***************************** XDND ************************** |
305 | *************************************************************/ |
306 | |
307 | /* Utility functions */ |
308 | |
309 | static struct { |
310 | const char *name; |
311 | GdkDragAction action; |
312 | } xdnd_actions_table[] = { |
313 | { "XdndActionCopy" , GDK_ACTION_COPY }, |
314 | { "XdndActionMove" , GDK_ACTION_MOVE }, |
315 | { "XdndActionLink" , GDK_ACTION_LINK }, |
316 | { "XdndActionAsk" , GDK_ACTION_ASK }, |
317 | { "XdndActionPrivate" , GDK_ACTION_COPY }, |
318 | }; |
319 | |
320 | static const int xdnd_n_actions = G_N_ELEMENTS (xdnd_actions_table); |
321 | |
322 | static GdkDragAction |
323 | xdnd_action_from_atom (GdkDisplay *display, |
324 | Atom xatom) |
325 | { |
326 | const char *name; |
327 | int i; |
328 | |
329 | if (xatom == None) |
330 | return 0; |
331 | |
332 | name = gdk_x11_get_xatom_name_for_display (display, xatom); |
333 | |
334 | for (i = 0; i < xdnd_n_actions; i++) |
335 | if (g_str_equal (v1: name, v2: xdnd_actions_table[i].name)) |
336 | return xdnd_actions_table[i].action; |
337 | |
338 | return 0; |
339 | } |
340 | |
341 | static Atom |
342 | xdnd_action_to_atom (GdkDisplay *display, |
343 | GdkDragAction action) |
344 | { |
345 | int i; |
346 | |
347 | for (i = 0; i < xdnd_n_actions; i++) |
348 | if (action == xdnd_actions_table[i].action) |
349 | return gdk_x11_get_xatom_by_name_for_display (display, atom_name: xdnd_actions_table[i].name); |
350 | |
351 | return None; |
352 | } |
353 | |
354 | /* Target side */ |
355 | |
356 | static void |
357 | gdk_x11_drop_update_actions (GdkX11Drop *drop_x11) |
358 | { |
359 | GdkDragAction actions; |
360 | |
361 | if (!drop_x11->xdnd_have_actions) |
362 | actions = drop_x11->suggested_action; |
363 | else if (drop_x11->suggested_action & GDK_ACTION_ASK) |
364 | actions = drop_x11->xdnd_actions | GDK_ACTION_ASK; |
365 | else |
366 | actions = drop_x11->xdnd_actions & GDK_ACTION_ALL; |
367 | |
368 | gdk_drop_set_actions (GDK_DROP (drop_x11), actions); |
369 | } |
370 | |
371 | void |
372 | gdk_x11_drop_read_actions (GdkDrop *drop) |
373 | { |
374 | GdkX11Drop *drop_x11 = GDK_X11_DROP (ptr: drop); |
375 | GdkDisplay *display = gdk_drop_get_display (self: drop); |
376 | GdkDrag *drag; |
377 | GdkDragAction actions = GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK; |
378 | Atom type; |
379 | int format; |
380 | gulong nitems, after; |
381 | guchar *data; |
382 | Atom *atoms; |
383 | int i; |
384 | |
385 | drag = gdk_drop_get_drag (self: drop); |
386 | |
387 | drop_x11->xdnd_have_actions = FALSE; |
388 | |
389 | if (drag == NULL) |
390 | { |
391 | /* Get the XdndActionList, if set */ |
392 | |
393 | gdk_x11_display_error_trap_push (display); |
394 | if (XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), |
395 | drop_x11->source_window, |
396 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndActionList" ), |
397 | 0, 65536, |
398 | False, XA_ATOM, &type, &format, &nitems, |
399 | &after, &data) == Success && |
400 | type == XA_ATOM) |
401 | { |
402 | actions = 0; |
403 | |
404 | atoms = (Atom *)data; |
405 | |
406 | for (i = 0; i < nitems; i++) |
407 | actions |= xdnd_action_from_atom (display, xatom: atoms[i]); |
408 | |
409 | drop_x11->xdnd_have_actions = TRUE; |
410 | |
411 | #ifdef G_ENABLE_DEBUG |
412 | if (GDK_DISPLAY_DEBUG_CHECK (display, DND)) |
413 | { |
414 | GString *action_str = g_string_new (NULL); |
415 | GdkDragAction drop_actions = gdk_drop_get_actions (self: drop); |
416 | if (drop_actions & GDK_ACTION_MOVE) |
417 | g_string_append(string: action_str, val: "MOVE " ); |
418 | if (drop_actions & GDK_ACTION_COPY) |
419 | g_string_append(string: action_str, val: "COPY " ); |
420 | if (drop_actions & GDK_ACTION_LINK) |
421 | g_string_append(string: action_str, val: "LINK " ); |
422 | if (drop_actions & GDK_ACTION_ASK) |
423 | g_string_append(string: action_str, val: "ASK " ); |
424 | |
425 | g_message("Xdnd actions = %s" , action_str->str); |
426 | g_string_free (string: action_str, TRUE); |
427 | } |
428 | #endif /* G_ENABLE_DEBUG */ |
429 | } |
430 | |
431 | if (data) |
432 | XFree (data); |
433 | |
434 | gdk_x11_display_error_trap_pop_ignored (display); |
435 | } |
436 | else |
437 | { |
438 | actions = gdk_drag_get_actions (drag); |
439 | drop_x11->xdnd_have_actions = TRUE; |
440 | } |
441 | |
442 | drop_x11->xdnd_actions = actions; |
443 | gdk_x11_drop_update_actions (drop_x11); |
444 | } |
445 | |
446 | /* We have to make sure that the XdndActionList we keep internally |
447 | * is up to date with the XdndActionList on the source window |
448 | * because we get no notification, because Xdnd wasn’t meant |
449 | * to continually send actions. So we select on PropertyChangeMask |
450 | * and add this filter. |
451 | */ |
452 | static gboolean |
453 | xdnd_source_surface_filter (GdkDisplay *display, |
454 | const XEvent *xevent, |
455 | gpointer data) |
456 | { |
457 | GdkX11Drop *drop_x11 = data; |
458 | |
459 | if ((xevent->xany.type == PropertyNotify) && |
460 | (xevent->xany.window == drop_x11->source_window) && |
461 | (xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndActionList" ))) |
462 | { |
463 | gdk_x11_drop_read_actions (GDK_DROP (drop_x11)); |
464 | } |
465 | |
466 | return FALSE; |
467 | } |
468 | |
469 | static gboolean |
470 | xdnd_enter_filter (GdkSurface *surface, |
471 | const XEvent *xevent) |
472 | { |
473 | GdkDisplay *display; |
474 | GdkX11Display *display_x11; |
475 | GdkDrop *drop; |
476 | GdkX11Drop *drop_x11; |
477 | GdkDrag *drag; |
478 | GdkSeat *seat; |
479 | int i; |
480 | Atom type; |
481 | int format; |
482 | gulong nitems, after; |
483 | guchar *data; |
484 | Atom *atoms; |
485 | GdkContentFormats *content_formats; |
486 | GPtrArray *formats; |
487 | Window source_window; |
488 | gboolean get_types; |
489 | int version; |
490 | |
491 | source_window = xevent->xclient.data.l[0]; |
492 | get_types = ((xevent->xclient.data.l[1] & 1) != 0); |
493 | version = (xevent->xclient.data.l[1] & 0xff000000) >> 24; |
494 | |
495 | display = gdk_surface_get_display (surface); |
496 | display_x11 = GDK_X11_DISPLAY (display); |
497 | |
498 | GDK_DISPLAY_NOTE (display, DND, |
499 | g_message ("XdndEnter: source_window: %#lx, version: %#x" , |
500 | source_window, version)); |
501 | |
502 | if (version < 3) |
503 | { |
504 | /* Old source ignore */ |
505 | GDK_DISPLAY_NOTE (display, DND, g_message ("Ignored old XdndEnter message" )); |
506 | return TRUE; |
507 | } |
508 | |
509 | if (display_x11->current_drop) |
510 | { |
511 | if (GDK_X11_DROP (ptr: display_x11->current_drop)->enter_emitted) |
512 | gdk_drop_emit_leave_event (self: display_x11->current_drop, FALSE, GDK_CURRENT_TIME); |
513 | g_clear_object (&display_x11->current_drop); |
514 | } |
515 | |
516 | seat = gdk_display_get_default_seat (display); |
517 | |
518 | formats = g_ptr_array_new (); |
519 | if (get_types) |
520 | { |
521 | gdk_x11_display_error_trap_push (display); |
522 | XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), |
523 | source_window, |
524 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndTypeList" ), |
525 | 0, 65536, |
526 | False, XA_ATOM, &type, &format, &nitems, |
527 | &after, &data); |
528 | |
529 | if (gdk_x11_display_error_trap_pop (display) || (format != 32) || (type != XA_ATOM)) |
530 | { |
531 | if (data) |
532 | XFree (data); |
533 | |
534 | return TRUE; |
535 | } |
536 | |
537 | atoms = (Atom *)data; |
538 | for (i = 0; i < nitems; i++) |
539 | g_ptr_array_add (array: formats, |
540 | data: (gpointer) gdk_x11_get_xatom_name_for_display (display, xatom: atoms[i])); |
541 | |
542 | XFree (atoms); |
543 | } |
544 | else |
545 | { |
546 | for (i = 0; i < 3; i++) |
547 | if (xevent->xclient.data.l[2 + i]) |
548 | g_ptr_array_add (array: formats, |
549 | data: (gpointer) gdk_x11_get_xatom_name_for_display (display, |
550 | xatom: xevent->xclient.data.l[2 + i])); |
551 | } |
552 | content_formats = gdk_content_formats_new (mime_types: (const char **) formats->pdata, n_mime_types: formats->len); |
553 | g_ptr_array_unref (array: formats); |
554 | |
555 | #ifdef G_ENABLE_DEBUG |
556 | if (GDK_DISPLAY_DEBUG_CHECK (display, DND)) |
557 | print_target_list (formats: content_formats); |
558 | #endif /* G_ENABLE_DEBUG */ |
559 | |
560 | drag = gdk_x11_drag_find (display, source_xid: source_window, GDK_SURFACE_XID (surface)); |
561 | |
562 | drop_x11 = g_object_new (GDK_TYPE_X11_DROP, |
563 | first_property_name: "device" , gdk_seat_get_pointer (seat), |
564 | "drag" , drag, |
565 | "formats" , content_formats, |
566 | "surface" , surface, |
567 | NULL); |
568 | drop = GDK_DROP (drop_x11); |
569 | |
570 | drop_x11->version = version; |
571 | |
572 | /* FIXME: Should extend DnD protocol to have device info */ |
573 | |
574 | drop_x11->source_window = source_window; |
575 | if (drag == NULL) |
576 | { |
577 | Display *xdisplay = gdk_x11_display_get_xdisplay (display); |
578 | XWindowAttributes attrs; |
579 | |
580 | gdk_x11_display_error_trap_push (display); |
581 | XGetWindowAttributes (xdisplay, source_window, &attrs); |
582 | if (!(attrs.your_event_mask & PropertyChangeMask)) |
583 | { |
584 | XSelectInput (xdisplay, source_window, attrs.your_event_mask | PropertyChangeMask); |
585 | } |
586 | gdk_x11_display_error_trap_pop_ignored (display); |
587 | |
588 | g_signal_connect (display, "xevent" , G_CALLBACK (xdnd_source_surface_filter), drop); |
589 | } |
590 | gdk_x11_drop_read_actions (drop); |
591 | |
592 | display_x11->current_drop = drop; |
593 | |
594 | gdk_content_formats_unref (formats: content_formats); |
595 | |
596 | return TRUE; |
597 | } |
598 | |
599 | static gboolean |
600 | xdnd_leave_filter (GdkSurface *surface, |
601 | const XEvent *xevent) |
602 | { |
603 | Window source_window = xevent->xclient.data.l[0]; |
604 | GdkDisplay *display; |
605 | GdkX11Display *display_x11; |
606 | |
607 | display = gdk_surface_get_display (surface); |
608 | display_x11 = GDK_X11_DISPLAY (display); |
609 | |
610 | GDK_DISPLAY_NOTE (display, DND, |
611 | g_message ("XdndLeave: source_window: %#lx" , |
612 | source_window)); |
613 | |
614 | if ((display_x11->current_drop != NULL) && |
615 | (GDK_X11_DROP (ptr: display_x11->current_drop)->source_window == source_window)) |
616 | { |
617 | if (GDK_X11_DROP (ptr: display_x11->current_drop)->enter_emitted) |
618 | gdk_drop_emit_leave_event (self: display_x11->current_drop, FALSE, GDK_CURRENT_TIME); |
619 | |
620 | g_clear_object (&display_x11->current_drop); |
621 | } |
622 | |
623 | return TRUE; |
624 | } |
625 | |
626 | static gboolean |
627 | xdnd_position_filter (GdkSurface *surface, |
628 | const XEvent *xevent) |
629 | { |
630 | GdkX11Surface *impl; |
631 | Window source_window = xevent->xclient.data.l[0]; |
632 | gint16 x_root = xevent->xclient.data.l[2] >> 16; |
633 | gint16 y_root = xevent->xclient.data.l[2] & 0xffff; |
634 | guint32 time = xevent->xclient.data.l[3]; |
635 | Atom action = xevent->xclient.data.l[4]; |
636 | GdkDisplay *display; |
637 | GdkX11Display *display_x11; |
638 | GdkDrop *drop; |
639 | GdkX11Drop *drop_x11; |
640 | |
641 | display = gdk_surface_get_display (surface); |
642 | display_x11 = GDK_X11_DISPLAY (display); |
643 | |
644 | GDK_DISPLAY_NOTE (display, DND, |
645 | g_message ("XdndPosition: source_window: %#lx position: (%d, %d) time: %d action: %ld" , |
646 | source_window, x_root, y_root, time, action)); |
647 | |
648 | drop = display_x11->current_drop; |
649 | drop_x11 = GDK_X11_DROP (ptr: drop); |
650 | |
651 | if ((drop != NULL) && |
652 | (drop_x11->source_window == source_window)) |
653 | { |
654 | surface = gdk_drop_get_surface (self: drop); |
655 | impl = GDK_X11_SURFACE (surface); |
656 | |
657 | drop_x11->suggested_action = xdnd_action_from_atom (display, xatom: action); |
658 | gdk_x11_drop_update_actions (drop_x11); |
659 | |
660 | drop_x11->last_x = x_root / impl->surface_scale; |
661 | drop_x11->last_y = y_root / impl->surface_scale; |
662 | |
663 | if (drop_x11->enter_emitted) |
664 | { |
665 | gdk_drop_emit_motion_event (self: drop, FALSE, x: drop_x11->last_x - impl->abs_x, y: drop_x11->last_y - impl->abs_y, time); |
666 | } |
667 | else |
668 | { |
669 | gdk_drop_emit_enter_event (self: drop, FALSE, x: drop_x11->last_x - impl->abs_x, y: drop_x11->last_y - impl->abs_y, time); |
670 | drop_x11->enter_emitted = TRUE; |
671 | } |
672 | } |
673 | |
674 | return TRUE; |
675 | } |
676 | |
677 | static gboolean |
678 | xdnd_drop_filter (GdkSurface *surface, |
679 | const XEvent *xevent) |
680 | { |
681 | Window source_window = xevent->xclient.data.l[0]; |
682 | guint32 time = xevent->xclient.data.l[2]; |
683 | GdkDisplay *display; |
684 | GdkX11Display *display_x11; |
685 | GdkDrop *drop; |
686 | GdkX11Drop *drop_x11; |
687 | |
688 | display = gdk_surface_get_display (surface); |
689 | display_x11 = GDK_X11_DISPLAY (display); |
690 | |
691 | GDK_DISPLAY_NOTE (display, DND, |
692 | g_message ("XdndDrop: source_window: %#lx time: %d" , |
693 | source_window, time)); |
694 | |
695 | drop = display_x11->current_drop; |
696 | drop_x11 = GDK_X11_DROP (ptr: drop); |
697 | |
698 | if ((drop != NULL) && |
699 | (drop_x11->source_window == source_window)) |
700 | { |
701 | GdkSurface *s = gdk_drop_get_surface (self: drop); |
702 | GdkX11Surface *si = GDK_X11_SURFACE (s); |
703 | gdk_x11_surface_set_user_time (surface: s, timestamp: time); |
704 | |
705 | gdk_drop_emit_drop_event (self: drop, FALSE, x: drop_x11->last_x - si->abs_x, y: drop_x11->last_y - si->abs_y, time); |
706 | } |
707 | |
708 | return TRUE; |
709 | } |
710 | |
711 | gboolean |
712 | gdk_x11_drop_filter (GdkSurface *surface, |
713 | const XEvent *xevent) |
714 | |
715 | { |
716 | GdkDisplay *display; |
717 | int i; |
718 | |
719 | if (!GDK_IS_X11_SURFACE (surface)) |
720 | return GDK_FILTER_CONTINUE; |
721 | |
722 | if (xevent->type != ClientMessage) |
723 | return GDK_FILTER_CONTINUE; |
724 | |
725 | display = GDK_SURFACE_DISPLAY (surface); |
726 | |
727 | for (i = 0; i < G_N_ELEMENTS (xdnd_filters); i++) |
728 | { |
729 | if (xevent->xclient.message_type != gdk_x11_get_xatom_by_name_for_display (display, atom_name: xdnd_filters[i].atom_name)) |
730 | continue; |
731 | |
732 | if (xdnd_filters[i].func (surface, xevent)) |
733 | return TRUE; |
734 | else |
735 | return FALSE; |
736 | } |
737 | |
738 | return FALSE; |
739 | } |
740 | |
741 | /* Destination side */ |
742 | |
743 | static void |
744 | gdk_x11_drop_do_nothing (Window window, |
745 | gboolean success, |
746 | gpointer data) |
747 | { |
748 | #ifdef G_ENABLE_DEBUG |
749 | GdkDisplay *display = data; |
750 | |
751 | if (!success) |
752 | { |
753 | GDK_DISPLAY_NOTE (display, DND, g_message ("Send event to %lx failed" , window)); |
754 | } |
755 | #endif |
756 | } |
757 | |
758 | static void |
759 | gdk_x11_drop_status (GdkDrop *drop, |
760 | GdkDragAction actions, |
761 | GdkDragAction preferred) |
762 | { |
763 | GdkX11Drop *drop_x11 = GDK_X11_DROP (ptr: drop); |
764 | GdkDragAction possible_actions, suggested_action; |
765 | XEvent xev; |
766 | GdkDisplay *display; |
767 | |
768 | display = gdk_drop_get_display (self: drop); |
769 | |
770 | possible_actions = actions & gdk_drop_get_actions (self: drop); |
771 | |
772 | if (preferred & possible_actions) |
773 | suggested_action = preferred; |
774 | else if (drop_x11->suggested_action & possible_actions) |
775 | suggested_action = drop_x11->suggested_action; |
776 | else if (possible_actions & GDK_ACTION_COPY) |
777 | suggested_action = GDK_ACTION_COPY; |
778 | else if (possible_actions & GDK_ACTION_MOVE) |
779 | suggested_action = GDK_ACTION_MOVE; |
780 | else if (possible_actions & GDK_ACTION_ASK) |
781 | suggested_action = GDK_ACTION_ASK; |
782 | else |
783 | suggested_action = 0; |
784 | |
785 | xev.xclient.type = ClientMessage; |
786 | xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndStatus" ); |
787 | xev.xclient.format = 32; |
788 | xev.xclient.window = drop_x11->source_window; |
789 | |
790 | xev.xclient.data.l[0] = GDK_SURFACE_XID (gdk_drop_get_surface (drop)); |
791 | xev.xclient.data.l[1] = (possible_actions != 0) ? (2 | 1) : 0; |
792 | xev.xclient.data.l[2] = 0; |
793 | xev.xclient.data.l[3] = 0; |
794 | xev.xclient.data.l[4] = xdnd_action_to_atom (display, action: suggested_action); |
795 | |
796 | if (gdk_drop_get_drag (self: drop)) |
797 | { |
798 | gdk_x11_drag_handle_status (display, xevent: &xev); |
799 | } |
800 | else |
801 | { |
802 | _gdk_x11_send_client_message_async (display, |
803 | window: drop_x11->source_window, |
804 | FALSE, event_mask: 0, |
805 | event_send: &xev.xclient, |
806 | callback: gdk_x11_drop_do_nothing, |
807 | data: display); |
808 | } |
809 | } |
810 | |
811 | static void |
812 | gdk_x11_drop_finish (GdkDrop *drop, |
813 | GdkDragAction action) |
814 | { |
815 | GdkX11Drop *drop_x11 = GDK_X11_DROP (ptr: drop); |
816 | GdkDisplay *display = gdk_drop_get_display (self: drop); |
817 | XEvent xev; |
818 | |
819 | if (action == GDK_ACTION_MOVE) |
820 | { |
821 | XConvertSelection (GDK_DISPLAY_XDISPLAY (display), |
822 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndSelection" ), |
823 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "DELETE" ), |
824 | gdk_x11_get_xatom_by_name_for_display (display, atom_name: "GDK_SELECTION" ), |
825 | drop_x11->source_window, |
826 | GDK_X11_DROP (ptr: drop)->timestamp); |
827 | /* XXX: Do we need to wait for a reply here before sending the next message? */ |
828 | } |
829 | |
830 | xev.xclient.type = ClientMessage; |
831 | xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "XdndFinished" ); |
832 | xev.xclient.format = 32; |
833 | xev.xclient.window = drop_x11->source_window; |
834 | |
835 | xev.xclient.data.l[0] = GDK_SURFACE_XID (gdk_drop_get_surface (drop)); |
836 | if (action != 0) |
837 | { |
838 | xev.xclient.data.l[1] = 1; |
839 | xev.xclient.data.l[2] = xdnd_action_to_atom (display, action); |
840 | } |
841 | else |
842 | { |
843 | xev.xclient.data.l[1] = 0; |
844 | xev.xclient.data.l[2] = None; |
845 | } |
846 | xev.xclient.data.l[3] = 0; |
847 | xev.xclient.data.l[4] = 0; |
848 | |
849 | if (gdk_drop_get_drag (self: drop)) |
850 | { |
851 | gdk_x11_drag_handle_finished (display, xevent: &xev); |
852 | } |
853 | else |
854 | { |
855 | _gdk_x11_send_client_message_async (display, |
856 | window: drop_x11->source_window, |
857 | FALSE, event_mask: 0, |
858 | event_send: &xev.xclient, |
859 | callback: gdk_x11_drop_do_nothing, |
860 | data: display); |
861 | } |
862 | } |
863 | |
864 | static void |
865 | gdk_x11_drop_class_init (GdkX11DropClass *klass) |
866 | { |
867 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
868 | GdkDropClass *drop_class = GDK_DROP_CLASS (klass); |
869 | |
870 | object_class->finalize = gdk_x11_drop_finalize; |
871 | |
872 | drop_class->status = gdk_x11_drop_status; |
873 | drop_class->finish = gdk_x11_drop_finish; |
874 | drop_class->read_async = gdk_x11_drop_read_async; |
875 | drop_class->read_finish = gdk_x11_drop_read_finish; |
876 | } |
877 | |
878 | static void |
879 | gdk_x11_drop_init (GdkX11Drop *drag) |
880 | { |
881 | } |
882 | |
883 | |