/* 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 .
*/
/*
* 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 "gdkintl.h"
#include "gdkprivate-x11.h"
#include "gdkscreen-x11.h"
#include "gdkselectioninputstream-x11.h"
#include "gdkselectionoutputstream-x11.h"
#include "gdktextlistconverter-x11.h"
#include
#include
#include
#include
#include
#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 wasn’t 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)
{
}