gtk2/gdk/x11/gdkdrop-x11.c
Matthias Clasen c78036fc51 x11: Handle X-specific targets in drops
This code is very similar to the handling for these
targets in the clipboard case.

Fixes: #3642
2021-02-03 14:17:04 -05:00

891 lines
28 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gdkdropprivate.h"
#include "gdk-private.h"
#include "gdkasync.h"
#include "gdkclipboardprivate.h"
#include "gdkclipboard-x11.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplay-x11.h"
#include "gdkdragprivate.h"
#include "gdkinternals.h"
#include "gdkintl.h"
#include "gdkprivate-x11.h"
#include "gdkscreen-x11.h"
#include "gdkselectioninputstream-x11.h"
#include "gdkselectionoutputstream-x11.h"
#include "gdktextlistconverter-x11.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#ifdef HAVE_XCOMPOSITE
#include <X11/extensions/Xcomposite.h>
#endif
#include <string.h>
#define GDK_TYPE_X11_DROP (gdk_x11_drop_get_type())
G_DECLARE_FINAL_TYPE (GdkX11Drop, gdk_x11_drop, GDK, X11_DROP, GdkDrop)
struct _GdkX11Drop
{
GdkDrop parent_instance;
Window source_window;
guint16 last_x; /* Coordinates from last event */
guint16 last_y;
gulong timestamp; /* Timestamp we claimed the DND selection with */
guint version; /* Xdnd protocol version */
GdkDragAction xdnd_actions; /* What is currently set in XdndActionList */
GdkDragAction suggested_action;
guint xdnd_targets_set : 1; /* Whether we've already set XdndTypeList */
guint xdnd_have_actions : 1; /* Whether an XdndActionList was provided */
guint enter_emitted : 1; /* Set after gdk_drop_emit_enter() */
};
struct _GdkX11DropClass
{
GdkDropClass parent_class;
};
/* Forward declarations */
static gboolean xdnd_source_surface_filter (GdkDisplay *display,
const XEvent *xevent,
gpointer data);
static gboolean xdnd_enter_filter (GdkSurface *surface,
const XEvent *xevent);
static gboolean xdnd_leave_filter (GdkSurface *surface,
const XEvent *xevent);
static gboolean xdnd_position_filter (GdkSurface *surface,
const XEvent *xevent);
static gboolean xdnd_drop_filter (GdkSurface *surface,
const XEvent *xevent);
static const struct {
const char *atom_name;
gboolean (* func) (GdkSurface *surface, const XEvent *event);
} xdnd_filters[] = {
{ "XdndEnter", xdnd_enter_filter },
{ "XdndLeave", xdnd_leave_filter },
{ "XdndPosition", xdnd_position_filter },
{ "XdndDrop", xdnd_drop_filter },
};
G_DEFINE_TYPE (GdkX11Drop, gdk_x11_drop, GDK_TYPE_DROP)
static GInputStream *
text_list_convert (GdkDisplay *display,
GInputStream *stream,
const char *encoding,
int format)
{
GInputStream *converter_stream;
GConverter *converter;
converter = gdk_x11_text_list_converter_to_utf8_new (display, encoding, format);
converter_stream = g_converter_input_stream_new (stream, converter);
g_object_unref (converter);
g_object_unref (stream);
return converter_stream;
}
static GInputStream *
no_convert (GdkDisplay *display,
GInputStream *stream,
const char *encoding,
int format)
{
return stream;
}
static const struct {
const char *x_target;
const char *mime_type;
GInputStream * (* convert) (GdkDisplay *, GInputStream *, const char *, int);
const char *type;
int format;
} special_targets[] = {
{ "UTF8_STRING", "text/plain;charset=utf-8", no_convert, "UTF8_STRING", 8 },
{ "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert, "COMPOUND_TEXT", 8 },
{ "TEXT", "text/plain;charset=utf-8", text_list_convert, "STRING", 8 },
{ "STRING", "text/plain;charset=utf-8", text_list_convert, "STRING", 8 },
{ "TARGETS", NULL, NULL, "ATOM", 32 },
{ "TIMESTAMP", NULL, NULL, "INTEGER", 32 },
{ "SAVE_TARGETS", NULL, NULL, "NULL", 32 }
};
static void
gdk_x11_drop_read_got_stream (GObject *source,
GAsyncResult *res,
gpointer data)
{
GTask *task = data;
GError *error = NULL;
GInputStream *stream;
const char *type;
int format;
stream = gdk_x11_selection_input_stream_new_finish (res, &type, &format, &error);
if (stream == NULL)
{
GSList *targets, *next;
targets = g_task_get_task_data (task);
next = targets->next;
if (next)
{
GdkDrop *drop = GDK_DROP (g_task_get_source_object (task));
GDK_DISPLAY_NOTE (gdk_drop_get_display (drop), DND, g_printerr ("reading %s failed, trying %s next\n",
(char *) targets->data, (char *) next->data));
targets->next = NULL;
g_task_set_task_data (task, next, (GDestroyNotify) g_slist_free);
gdk_x11_selection_input_stream_new_async (gdk_drop_get_display (drop),
"XdndSelection",
next->data,
CurrentTime,
g_task_get_priority (task),
g_task_get_cancellable (task),
gdk_x11_drop_read_got_stream,
task);
g_error_free (error);
return;
}
g_task_return_error (task, error);
}
else
{
gsize i;
const char *mime_type = ((GSList *) g_task_get_task_data (task))->data;
GdkDrop *drop = GDK_DROP (g_task_get_source_object (task));
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
{
if (g_str_equal (mime_type, special_targets[i].x_target))
{
g_assert (special_targets[i].mime_type != NULL);
mime_type = g_intern_string (special_targets[i].mime_type);
g_task_set_task_data (task, g_slist_prepend (NULL, (gpointer) mime_type), (GDestroyNotify) g_slist_free);
stream = special_targets[i].convert (gdk_drop_get_display (drop), stream, type, format);
break;
}
}
GDK_NOTE (DND, g_printerr ("reading DND as %s now\n",
(const char *)((GSList *) g_task_get_task_data (task))->data));
g_task_return_pointer (task, stream, g_object_unref);
}
g_object_unref (task);
}
static void
gdk_x11_drop_read_async (GdkDrop *drop,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSList *targets;
GTask *task;
task = g_task_new (drop, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, gdk_x11_drop_read_async);
targets = gdk_x11_clipboard_formats_to_targets (formats);
g_task_set_task_data (task, targets, (GDestroyNotify) g_slist_free);
if (targets == NULL)
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("No compatible transfer format found"));
return;
}
GDK_DISPLAY_NOTE (gdk_drop_get_display (drop), DND, g_printerr ("new read for %s (%u other options)\n",
(char *) targets->data, g_slist_length (targets->next)));
gdk_x11_selection_input_stream_new_async (gdk_drop_get_display (drop),
"XdndSelection",
targets->data,
CurrentTime,
io_priority,
cancellable,
gdk_x11_drop_read_got_stream,
task);
}
static GInputStream *
gdk_x11_drop_read_finish (GdkDrop *drop,
GAsyncResult *result,
const char **out_mime_type,
GError **error)
{
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (drop)), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_drop_read_async, NULL);
if (out_mime_type)
{
GSList *targets;
targets = g_task_get_task_data (task);
*out_mime_type = targets ? targets->data : NULL;
}
return g_task_propagate_pointer (task, error);
}
static void
gdk_x11_drop_finalize (GObject *object)
{
GdkX11Drop *drop_x11 = GDK_X11_DROP (object);
if (gdk_drop_get_drag (GDK_DROP (drop_x11)) == NULL)
{
g_signal_handlers_disconnect_by_func (gdk_drop_get_display (GDK_DROP (drop_x11)),
xdnd_source_surface_filter,
drop_x11);
/* Should we remove the GDK_PROPERTY_NOTIFY mask?
* but we might want it for other reasons. (Like
* INCR selection transactions).
*/
}
G_OBJECT_CLASS (gdk_x11_drop_parent_class)->finalize (object);
}
/* Utility functions */
#ifdef G_ENABLE_DEBUG
static void
print_target_list (GdkContentFormats *formats)
{
char *name = gdk_content_formats_to_string (formats);
g_message ("DND formats: %s", name);
g_free (name);
}
#endif /* G_ENABLE_DEBUG */
/*************************************************************
***************************** XDND **************************
*************************************************************/
/* Utility functions */
static struct {
const char *name;
GdkDragAction action;
} xdnd_actions_table[] = {
{ "XdndActionCopy", GDK_ACTION_COPY },
{ "XdndActionMove", GDK_ACTION_MOVE },
{ "XdndActionLink", GDK_ACTION_LINK },
{ "XdndActionAsk", GDK_ACTION_ASK },
{ "XdndActionPrivate", GDK_ACTION_COPY },
};
static const int xdnd_n_actions = G_N_ELEMENTS (xdnd_actions_table);
static GdkDragAction
xdnd_action_from_atom (GdkDisplay *display,
Atom xatom)
{
const char *name;
int i;
if (xatom == None)
return 0;
name = gdk_x11_get_xatom_name_for_display (display, xatom);
for (i = 0; i < xdnd_n_actions; i++)
if (g_str_equal (name, xdnd_actions_table[i].name))
return xdnd_actions_table[i].action;
return 0;
}
static Atom
xdnd_action_to_atom (GdkDisplay *display,
GdkDragAction action)
{
int i;
for (i = 0; i < xdnd_n_actions; i++)
if (action == xdnd_actions_table[i].action)
return gdk_x11_get_xatom_by_name_for_display (display, xdnd_actions_table[i].name);
return None;
}
/* Target side */
static void
gdk_x11_drop_update_actions (GdkX11Drop *drop_x11)
{
GdkDragAction actions;
if (!drop_x11->xdnd_have_actions)
actions = drop_x11->suggested_action;
else if (drop_x11->suggested_action & GDK_ACTION_ASK)
actions = drop_x11->xdnd_actions & GDK_ACTION_ALL;
else
actions = drop_x11->suggested_action;
gdk_drop_set_actions (GDK_DROP (drop_x11), actions);
}
void
gdk_x11_drop_read_actions (GdkDrop *drop)
{
GdkX11Drop *drop_x11 = GDK_X11_DROP (drop);
GdkDisplay *display = gdk_drop_get_display (drop);
GdkDrag *drag;
GdkDragAction actions = GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK;
Atom type;
int format;
gulong nitems, after;
guchar *data;
Atom *atoms;
int i;
drag = gdk_drop_get_drag (drop);
drop_x11->xdnd_have_actions = FALSE;
if (drag == NULL)
{
/* Get the XdndActionList, if set */
gdk_x11_display_error_trap_push (display);
if (XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
drop_x11->source_window,
gdk_x11_get_xatom_by_name_for_display (display, "XdndActionList"),
0, 65536,
False, XA_ATOM, &type, &format, &nitems,
&after, &data) == Success &&
type == XA_ATOM)
{
actions = 0;
atoms = (Atom *)data;
for (i = 0; i < nitems; i++)
actions |= xdnd_action_from_atom (display, atoms[i]);
drop_x11->xdnd_have_actions = TRUE;
#ifdef G_ENABLE_DEBUG
if (GDK_DISPLAY_DEBUG_CHECK (display, DND))
{
GString *action_str = g_string_new (NULL);
GdkDragAction drop_actions = gdk_drop_get_actions (drop);
if (drop_actions & GDK_ACTION_MOVE)
g_string_append(action_str, "MOVE ");
if (drop_actions & GDK_ACTION_COPY)
g_string_append(action_str, "COPY ");
if (drop_actions & GDK_ACTION_LINK)
g_string_append(action_str, "LINK ");
if (drop_actions & GDK_ACTION_ASK)
g_string_append(action_str, "ASK ");
g_message("Xdnd actions = %s", action_str->str);
g_string_free (action_str, TRUE);
}
#endif /* G_ENABLE_DEBUG */
}
if (data)
XFree (data);
gdk_x11_display_error_trap_pop_ignored (display);
}
else
{
actions = gdk_drag_get_actions (drag);
drop_x11->xdnd_have_actions = TRUE;
}
drop_x11->xdnd_actions = actions;
gdk_x11_drop_update_actions (drop_x11);
}
/* We have to make sure that the XdndActionList we keep internally
* is up to date with the XdndActionList on the source window
* because we get no notification, because Xdnd wasnt meant
* to continually send actions. So we select on PropertyChangeMask
* and add this filter.
*/
static gboolean
xdnd_source_surface_filter (GdkDisplay *display,
const XEvent *xevent,
gpointer data)
{
GdkX11Drop *drop_x11 = data;
if ((xevent->xany.type == PropertyNotify) &&
(xevent->xany.window == drop_x11->source_window) &&
(xevent->xproperty.atom == gdk_x11_get_xatom_by_name_for_display (display, "XdndActionList")))
{
gdk_x11_drop_read_actions (GDK_DROP (drop_x11));
}
return FALSE;
}
static gboolean
xdnd_enter_filter (GdkSurface *surface,
const XEvent *xevent)
{
GdkDisplay *display;
GdkX11Display *display_x11;
GdkDrop *drop;
GdkX11Drop *drop_x11;
GdkDrag *drag;
GdkSeat *seat;
int i;
Atom type;
int format;
gulong nitems, after;
guchar *data;
Atom *atoms;
GdkContentFormats *content_formats;
GPtrArray *formats;
Window source_window;
gboolean get_types;
int version;
source_window = xevent->xclient.data.l[0];
get_types = ((xevent->xclient.data.l[1] & 1) != 0);
version = (xevent->xclient.data.l[1] & 0xff000000) >> 24;
display = gdk_surface_get_display (surface);
display_x11 = GDK_X11_DISPLAY (display);
GDK_DISPLAY_NOTE (display, DND,
g_message ("XdndEnter: source_window: %#lx, version: %#x",
source_window, version));
if (version < 3)
{
/* Old source ignore */
GDK_DISPLAY_NOTE (display, DND, g_message ("Ignored old XdndEnter message"));
return TRUE;
}
if (display_x11->current_drop)
{
if (GDK_X11_DROP (display_x11->current_drop)->enter_emitted)
gdk_drop_emit_leave_event (display_x11->current_drop, FALSE, GDK_CURRENT_TIME);
g_clear_object (&display_x11->current_drop);
}
seat = gdk_display_get_default_seat (display);
formats = g_ptr_array_new ();
if (get_types)
{
gdk_x11_display_error_trap_push (display);
XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
source_window,
gdk_x11_get_xatom_by_name_for_display (display, "XdndTypeList"),
0, 65536,
False, XA_ATOM, &type, &format, &nitems,
&after, &data);
if (gdk_x11_display_error_trap_pop (display) || (format != 32) || (type != XA_ATOM))
{
if (data)
XFree (data);
return TRUE;
}
atoms = (Atom *)data;
for (i = 0; i < nitems; i++)
g_ptr_array_add (formats,
(gpointer) gdk_x11_get_xatom_name_for_display (display, atoms[i]));
XFree (atoms);
}
else
{
for (i = 0; i < 3; i++)
if (xevent->xclient.data.l[2 + i])
g_ptr_array_add (formats,
(gpointer) gdk_x11_get_xatom_name_for_display (display,
xevent->xclient.data.l[2 + i]));
}
content_formats = gdk_content_formats_new ((const char **) formats->pdata, formats->len);
g_ptr_array_unref (formats);
#ifdef G_ENABLE_DEBUG
if (GDK_DISPLAY_DEBUG_CHECK (display, DND))
print_target_list (content_formats);
#endif /* G_ENABLE_DEBUG */
drag = gdk_x11_drag_find (display, source_window, GDK_SURFACE_XID (surface));
drop_x11 = g_object_new (GDK_TYPE_X11_DROP,
"device", gdk_seat_get_pointer (seat),
"drag", drag,
"formats", content_formats,
"surface", surface,
NULL);
drop = GDK_DROP (drop_x11);
drop_x11->version = version;
/* FIXME: Should extend DnD protocol to have device info */
drop_x11->source_window = source_window;
if (drag == NULL)
{
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
XWindowAttributes attrs;
gdk_x11_display_error_trap_push (display);
XGetWindowAttributes (xdisplay, source_window, &attrs);
if (!(attrs.your_event_mask & PropertyChangeMask))
{
XSelectInput (xdisplay, source_window, attrs.your_event_mask | PropertyChangeMask);
}
gdk_x11_display_error_trap_pop_ignored (display);
g_signal_connect (display, "xevent", G_CALLBACK (xdnd_source_surface_filter), drop);
}
gdk_x11_drop_read_actions (drop);
display_x11->current_drop = drop;
gdk_content_formats_unref (content_formats);
return TRUE;
}
static gboolean
xdnd_leave_filter (GdkSurface *surface,
const XEvent *xevent)
{
Window source_window = xevent->xclient.data.l[0];
GdkDisplay *display;
GdkX11Display *display_x11;
display = gdk_surface_get_display (surface);
display_x11 = GDK_X11_DISPLAY (display);
GDK_DISPLAY_NOTE (display, DND,
g_message ("XdndLeave: source_window: %#lx",
source_window));
if ((display_x11->current_drop != NULL) &&
(GDK_X11_DROP (display_x11->current_drop)->source_window == source_window))
{
if (GDK_X11_DROP (display_x11->current_drop)->enter_emitted)
gdk_drop_emit_leave_event (display_x11->current_drop, FALSE, GDK_CURRENT_TIME);
g_clear_object (&display_x11->current_drop);
}
return TRUE;
}
static gboolean
xdnd_position_filter (GdkSurface *surface,
const XEvent *xevent)
{
GdkX11Surface *impl;
Window source_window = xevent->xclient.data.l[0];
gint16 x_root = xevent->xclient.data.l[2] >> 16;
gint16 y_root = xevent->xclient.data.l[2] & 0xffff;
guint32 time = xevent->xclient.data.l[3];
Atom action = xevent->xclient.data.l[4];
GdkDisplay *display;
GdkX11Display *display_x11;
GdkDrop *drop;
GdkX11Drop *drop_x11;
display = gdk_surface_get_display (surface);
display_x11 = GDK_X11_DISPLAY (display);
GDK_DISPLAY_NOTE (display, DND,
g_message ("XdndPosition: source_window: %#lx position: (%d, %d) time: %d action: %ld",
source_window, x_root, y_root, time, action));
drop = display_x11->current_drop;
drop_x11 = GDK_X11_DROP (drop);
if ((drop != NULL) &&
(drop_x11->source_window == source_window))
{
surface = gdk_drop_get_surface (drop);
impl = GDK_X11_SURFACE (surface);
drop_x11->suggested_action = xdnd_action_from_atom (display, action);
gdk_x11_drop_update_actions (drop_x11);
drop_x11->last_x = x_root / impl->surface_scale;
drop_x11->last_y = y_root / impl->surface_scale;
if (drop_x11->enter_emitted)
{
gdk_drop_emit_motion_event (drop, FALSE, drop_x11->last_x - impl->abs_x, drop_x11->last_y - impl->abs_y, time);
}
else
{
gdk_drop_emit_enter_event (drop, FALSE, drop_x11->last_x - impl->abs_x, drop_x11->last_y - impl->abs_y, time);
drop_x11->enter_emitted = TRUE;
}
}
return TRUE;
}
static gboolean
xdnd_drop_filter (GdkSurface *surface,
const XEvent *xevent)
{
Window source_window = xevent->xclient.data.l[0];
guint32 time = xevent->xclient.data.l[2];
GdkDisplay *display;
GdkX11Display *display_x11;
GdkDrop *drop;
GdkX11Drop *drop_x11;
display = gdk_surface_get_display (surface);
display_x11 = GDK_X11_DISPLAY (display);
GDK_DISPLAY_NOTE (display, DND,
g_message ("XdndDrop: source_window: %#lx time: %d",
source_window, time));
drop = display_x11->current_drop;
drop_x11 = GDK_X11_DROP (drop);
if ((drop != NULL) &&
(drop_x11->source_window == source_window))
{
GdkSurface *s = gdk_drop_get_surface (drop);
GdkX11Surface *si = GDK_X11_SURFACE (s);
gdk_x11_surface_set_user_time (s, time);
gdk_drop_emit_drop_event (drop, FALSE, drop_x11->last_x - si->abs_x, drop_x11->last_y - si->abs_y, time);
}
return TRUE;
}
gboolean
gdk_x11_drop_filter (GdkSurface *surface,
const XEvent *xevent)
{
GdkDisplay *display;
int i;
if (!GDK_IS_X11_SURFACE (surface))
return GDK_FILTER_CONTINUE;
if (xevent->type != ClientMessage)
return GDK_FILTER_CONTINUE;
display = GDK_SURFACE_DISPLAY (surface);
for (i = 0; i < G_N_ELEMENTS (xdnd_filters); i++)
{
if (xevent->xclient.message_type != gdk_x11_get_xatom_by_name_for_display (display, xdnd_filters[i].atom_name))
continue;
if (xdnd_filters[i].func (surface, xevent))
return TRUE;
else
return FALSE;
}
return FALSE;
}
/* Destination side */
static void
gdk_x11_drop_do_nothing (Window window,
gboolean success,
gpointer data)
{
#ifdef G_ENABLE_DEBUG
GdkDisplay *display = data;
if (!success)
{
GDK_DISPLAY_NOTE (display, DND, g_message ("Send event to %lx failed", window));
}
#endif
}
static void
gdk_x11_drop_status (GdkDrop *drop,
GdkDragAction actions,
GdkDragAction preferred)
{
GdkX11Drop *drop_x11 = GDK_X11_DROP (drop);
GdkDragAction possible_actions, suggested_action;
XEvent xev;
GdkDisplay *display;
display = gdk_drop_get_display (drop);
possible_actions = actions & gdk_drop_get_actions (drop);
if (drop_x11->suggested_action != 0)
suggested_action = drop_x11->suggested_action;
else
suggested_action = preferred & possible_actions;
if (suggested_action == 0 && possible_actions != 0)
{
if (possible_actions & GDK_ACTION_COPY)
suggested_action = GDK_ACTION_COPY;
else if (possible_actions & GDK_ACTION_MOVE)
suggested_action = GDK_ACTION_MOVE;
else if (possible_actions & GDK_ACTION_ASK)
suggested_action = GDK_ACTION_ASK;
else
suggested_action = 0;
}
xev.xclient.type = ClientMessage;
xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "XdndStatus");
xev.xclient.format = 32;
xev.xclient.window = drop_x11->source_window;
xev.xclient.data.l[0] = GDK_SURFACE_XID (gdk_drop_get_surface (drop));
xev.xclient.data.l[1] = (possible_actions != 0) ? (2 | 1) : 0;
xev.xclient.data.l[2] = 0;
xev.xclient.data.l[3] = 0;
xev.xclient.data.l[4] = xdnd_action_to_atom (display, suggested_action);
if (gdk_drop_get_drag (drop))
{
gdk_x11_drag_handle_status (display, &xev);
}
else
{
_gdk_x11_send_client_message_async (display,
drop_x11->source_window,
FALSE, 0,
&xev.xclient,
gdk_x11_drop_do_nothing,
display);
}
}
static void
gdk_x11_drop_finish (GdkDrop *drop,
GdkDragAction action)
{
GdkX11Drop *drop_x11 = GDK_X11_DROP (drop);
GdkDisplay *display = gdk_drop_get_display (drop);
XEvent xev;
if (action == GDK_ACTION_MOVE)
{
XConvertSelection (GDK_DISPLAY_XDISPLAY (display),
gdk_x11_get_xatom_by_name_for_display (display, "XdndSelection"),
gdk_x11_get_xatom_by_name_for_display (display, "DELETE"),
gdk_x11_get_xatom_by_name_for_display (display, "GDK_SELECTION"),
drop_x11->source_window,
GDK_X11_DROP (drop)->timestamp);
/* XXX: Do we need to wait for a reply here before sending the next message? */
}
xev.xclient.type = ClientMessage;
xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "XdndFinished");
xev.xclient.format = 32;
xev.xclient.window = drop_x11->source_window;
xev.xclient.data.l[0] = GDK_SURFACE_XID (gdk_drop_get_surface (drop));
if (action != 0)
{
xev.xclient.data.l[1] = 1;
xev.xclient.data.l[2] = xdnd_action_to_atom (display, action);
}
else
{
xev.xclient.data.l[1] = 0;
xev.xclient.data.l[2] = None;
}
xev.xclient.data.l[3] = 0;
xev.xclient.data.l[4] = 0;
if (gdk_drop_get_drag (drop))
{
gdk_x11_drag_handle_finished (display, &xev);
}
else
{
_gdk_x11_send_client_message_async (display,
drop_x11->source_window,
FALSE, 0,
&xev.xclient,
gdk_x11_drop_do_nothing,
display);
}
}
static void
gdk_x11_drop_class_init (GdkX11DropClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GdkDropClass *drop_class = GDK_DROP_CLASS (klass);
object_class->finalize = gdk_x11_drop_finalize;
drop_class->status = gdk_x11_drop_status;
drop_class->finish = gdk_x11_drop_finish;
drop_class->read_async = gdk_x11_drop_read_async;
drop_class->read_finish = gdk_x11_drop_read_finish;
}
static void
gdk_x11_drop_init (GdkX11Drop *drag)
{
}