x11: Implement gdk_drag_context_manage_dnd()

This includes managing input events and source-side DND events,
as well as setting the appropriate cursor and emitting the signals
that are expected in this mode of operation.
This commit is contained in:
Carlos Garnacho 2016-01-08 21:24:52 +01:00
parent ed5da43afb
commit bfee45e6f9

View File

@ -89,6 +89,12 @@ struct _GdkX11DragContext
GdkWindow *drag_window;
GdkWindow *ipc_window;
GdkCursor *cursor;
GdkSeat *grab_seat;
GdkDragAction actions;
GdkDragAction current_action;
gint hot_x;
gint hot_y;
@ -106,6 +112,35 @@ struct _GdkX11DragContextClass
GdkDragContextClass parent_class;
};
typedef struct {
gint keysym;
gint modifiers;
} GrabKey;
static GrabKey grab_keys[] = {
{ XK_Escape, 0 },
{ XK_space, 0 },
{ XK_KP_Space, 0 },
{ XK_Return, 0 },
{ XK_KP_Enter, 0 },
{ XK_Up, 0 },
{ XK_Up, Mod1Mask },
{ XK_Down, 0 },
{ XK_Down, Mod1Mask },
{ XK_Left, 0 },
{ XK_Left, Mod1Mask },
{ XK_Right, 0 },
{ XK_Right, Mod1Mask },
{ XK_KP_Up, 0 },
{ XK_KP_Up, Mod1Mask },
{ XK_KP_Down, 0 },
{ XK_KP_Down, Mod1Mask },
{ XK_KP_Left, 0 },
{ XK_KP_Left, Mod1Mask },
{ XK_KP_Right, 0 },
{ XK_KP_Right, Mod1Mask }
};
/* Forward declarations */
static GdkWindowCache *gdk_window_cache_get (GdkScreen *screen);
@ -135,6 +170,11 @@ static void xdnd_manage_source_filter (GdkDragContext *context,
GdkWindow *window,
gboolean add_filter);
gboolean gdk_x11_drag_context_handle_event (GdkDragContext *context,
const GdkEvent *event);
void gdk_x11_drag_context_action_changed (GdkDragContext *context,
GdkDragAction action);
static GList *contexts;
static GSList *window_caches;
@ -195,6 +235,14 @@ static void gdk_x11_drag_context_set_hotspot (GdkDragContext *context,
gint hot_y);
static void gdk_x11_drag_context_drop_done (GdkDragContext *context,
gboolean success);
static gboolean gdk_x11_drag_context_manage_dnd (GdkDragContext *context,
GdkWindow *window,
GdkDragAction actions);
static void gdk_x11_drag_context_set_cursor (GdkDragContext *context,
GdkCursor *cursor);
static void gdk_x11_drag_context_cancel (GdkDragContext *context);
static void gdk_x11_drag_context_drop_performed (GdkDragContext *context,
guint32 time);
static void
gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
@ -216,6 +264,12 @@ gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
context_class->get_drag_window = gdk_x11_drag_context_get_drag_window;
context_class->set_hotspot = gdk_x11_drag_context_set_hotspot;
context_class->drop_done = gdk_x11_drag_context_drop_done;
context_class->manage_dnd = gdk_x11_drag_context_manage_dnd;
context_class->set_cursor = gdk_x11_drag_context_set_cursor;
context_class->cancel = gdk_x11_drag_context_cancel;
context_class->drop_performed = gdk_x11_drag_context_drop_performed;
context_class->handle_event = gdk_x11_drag_context_handle_event;
context_class->action_changed = gdk_x11_drag_context_action_changed;
}
static void
@ -2592,3 +2646,487 @@ gdk_x11_drag_context_drop_done (GdkDragContext *context,
gdk_drag_anim_timeout, anim,
(GDestroyNotify) gdk_drag_anim_destroy);
}
static gboolean
drag_context_grab (GdkDragContext *context)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
GdkDevice *device = gdk_drag_context_get_device (context);
GdkWindow *root;
GdkSeat *seat;
gint keycode, i;
if (!x11_context->ipc_window)
return FALSE;
root = gdk_screen_get_root_window (gdk_window_get_screen (x11_context->ipc_window));
seat = gdk_device_get_seat (gdk_drag_context_get_device (context));
if (gdk_seat_grab (seat, x11_context->ipc_window,
GDK_SEAT_CAPABILITY_ALL, FALSE,
x11_context->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS)
return FALSE;
g_set_object (&x11_context->grab_seat, seat);
gdk_error_trap_push ();
for (i = 0; i < G_N_ELEMENTS (grab_keys); ++i)
{
keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
grab_keys[i].keysym);
if (keycode == NoSymbol)
continue;
#ifdef XINPUT_2
if (GDK_IS_X11_DEVICE_XI2 (device))
{
gint deviceid = gdk_x11_device_get_id (gdk_seat_get_keyboard (seat));
unsigned char mask[XIMaskLen(XI_LASTEVENT)];
XIGrabModifiers mods;
XIEventMask evmask;
gint num_mods;
memset (mask, 0, sizeof (mask));
XISetMask (mask, XI_KeyPress);
XISetMask (mask, XI_KeyRelease);
evmask.deviceid = deviceid;
evmask.mask_len = sizeof (mask);
evmask.mask = mask;
num_mods = 1;
mods.modifiers = grab_keys[i].modifiers;
XIGrabKeycode (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
deviceid,
keycode,
GDK_WINDOW_XID (root),
GrabModeAsync,
GrabModeAsync,
False,
&evmask,
num_mods,
&mods);
}
else
#endif
{
XGrabKey (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
keycode, grab_keys[i].modifiers,
GDK_WINDOW_XID (root),
FALSE,
GrabModeAsync,
GrabModeAsync);
}
}
return TRUE;
}
static void
drag_context_ungrab (GdkDragContext *context)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
GdkDevice *keyboard;
GdkWindow *root;
gint keycode, i;
if (!x11_context->grab_seat)
return;
gdk_seat_ungrab (x11_context->grab_seat);
keyboard = gdk_seat_get_keyboard (x11_context->grab_seat);
root = gdk_screen_get_root_window (gdk_window_get_screen (x11_context->ipc_window));
g_clear_object (&x11_context->grab_seat);
for (i = 0; i < G_N_ELEMENTS (grab_keys); ++i)
{
keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
grab_keys[i].keysym);
if (keycode == NoSymbol)
continue;
#ifdef XINPUT_2
if (GDK_IS_X11_DEVICE_XI2 (keyboard))
{
XIGrabModifiers mods;
gint num_mods;
num_mods = 1;
mods.modifiers = grab_keys[i].modifiers;
XIUngrabKeycode (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
gdk_x11_device_get_id (keyboard),
keycode,
GDK_WINDOW_XID (root),
num_mods,
&mods);
}
else
#endif /* XINPUT_2 */
{
XUngrabKey (GDK_WINDOW_XDISPLAY (x11_context->ipc_window),
keycode, grab_keys[i].modifiers,
GDK_WINDOW_XID (root));
}
}
}
static gboolean
gdk_x11_drag_context_manage_dnd (GdkDragContext *context,
GdkWindow *ipc_window,
GdkDragAction actions)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
if (x11_context->ipc_window)
return FALSE;
context->protocol = GDK_DRAG_PROTO_XDND;
x11_context->ipc_window = g_object_ref (ipc_window);
if (drag_context_grab (context))
{
x11_context->actions = actions;
return TRUE;
}
else
{
g_clear_object (&x11_context->ipc_window);
return FALSE;
}
}
static void
gdk_x11_drag_context_set_cursor (GdkDragContext *context,
GdkCursor *cursor)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
if (!g_set_object (&x11_context->cursor, cursor))
return;
if (x11_context->grab_seat)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
gdk_device_grab (gdk_seat_get_pointer (x11_context->grab_seat),
x11_context->ipc_window,
GDK_OWNERSHIP_APPLICATION, FALSE,
GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
cursor, GDK_CURRENT_TIME);
G_GNUC_END_IGNORE_DEPRECATIONS;
}
}
static void
gdk_x11_drag_context_cancel (GdkDragContext *context)
{
drag_context_ungrab (context);
gdk_drag_drop_done (context, FALSE);
}
static void
gdk_x11_drag_context_drop_performed (GdkDragContext *context,
guint32 time_)
{
drag_context_ungrab (context);
}
#define BIG_STEP 20
#define SMALL_STEP 1
static void
gdk_drag_get_current_actions (GdkModifierType state,
gint button,
GdkDragAction actions,
GdkDragAction *suggested_action,
GdkDragAction *possible_actions)
{
*suggested_action = 0;
*possible_actions = 0;
if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
{
*suggested_action = GDK_ACTION_ASK;
*possible_actions = actions;
}
else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
{
if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
{
if (actions & GDK_ACTION_LINK)
{
*suggested_action = GDK_ACTION_LINK;
*possible_actions = GDK_ACTION_LINK;
}
}
else if (state & GDK_CONTROL_MASK)
{
if (actions & GDK_ACTION_COPY)
{
*suggested_action = GDK_ACTION_COPY;
*possible_actions = GDK_ACTION_COPY;
}
}
else
{
if (actions & GDK_ACTION_MOVE)
{
*suggested_action = GDK_ACTION_MOVE;
*possible_actions = GDK_ACTION_MOVE;
}
}
}
else
{
*possible_actions = actions;
if ((state & (GDK_MOD1_MASK)) && (actions & GDK_ACTION_ASK))
*suggested_action = GDK_ACTION_ASK;
else if (actions & GDK_ACTION_COPY)
*suggested_action = GDK_ACTION_COPY;
else if (actions & GDK_ACTION_MOVE)
*suggested_action = GDK_ACTION_MOVE;
else if (actions & GDK_ACTION_LINK)
*suggested_action = GDK_ACTION_LINK;
}
}
static void
gdk_drag_update (GdkDragContext *context,
gdouble x_root,
gdouble y_root,
GdkModifierType mods,
guint32 evtime)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
GdkDragAction action, possible_actions;
GdkWindow *dest_window;
GdkDragProtocol protocol;
gdk_drag_get_current_actions (mods, GDK_BUTTON_PRIMARY, x11_context->actions,
&action, &possible_actions);
gdk_drag_find_window_for_screen (context,
x11_context->drag_window,
gdk_display_get_default_screen (gdk_display_get_default ()),
x_root, y_root, &dest_window, &protocol);
gdk_drag_motion (context, dest_window, protocol, x_root, y_root,
action, possible_actions, evtime);
}
static gboolean
gdk_dnd_handle_motion_event (GdkDragContext *context,
const GdkEventMotion *event)
{
GdkModifierType state;
if (!gdk_event_get_state ((GdkEvent *) event, &state))
return FALSE;
gdk_drag_update (context, event->x_root, event->y_root, state,
gdk_event_get_time ((GdkEvent *) event));
return TRUE;
}
static gboolean
gdk_dnd_handle_key_event (GdkDragContext *context,
const GdkEventKey *event)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
GdkModifierType state;
GdkWindow *root_window;
GdkDevice *pointer;
gint dx, dy;
dx = dy = 0;
state = event->state;
pointer = gdk_device_get_associated_device (gdk_event_get_device ((GdkEvent *) event));
if (event->type == GDK_KEY_PRESS)
{
switch (event->keyval)
{
case GDK_KEY_Escape:
gdk_drag_context_cancel (context);
return TRUE;
case GDK_KEY_space:
case GDK_KEY_Return:
case GDK_KEY_ISO_Enter:
case GDK_KEY_KP_Enter:
case GDK_KEY_KP_Space:
if ((gdk_drag_context_get_selected_action (context) != 0) &&
(gdk_drag_context_get_dest_window (context) != NULL))
{
g_signal_emit_by_name (context, "drop-performed",
gdk_event_get_time ((GdkEvent *) event));
}
else
gdk_drag_context_cancel (context);
return TRUE;
case GDK_KEY_Up:
case GDK_KEY_KP_Up:
dy = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
break;
case GDK_KEY_Down:
case GDK_KEY_KP_Down:
dy = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP;
break;
case GDK_KEY_Left:
case GDK_KEY_KP_Left:
dx = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
break;
case GDK_KEY_Right:
case GDK_KEY_KP_Right:
dx = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP;
break;
}
}
/* The state is not yet updated in the event, so we need
* to query it here. We could use XGetModifierMapping, but
* that would be overkill.
*/
root_window = gdk_screen_get_root_window (gdk_window_get_screen (x11_context->ipc_window));
gdk_window_get_device_position (root_window, pointer, NULL, NULL, &state);
if (dx != 0 || dy != 0)
{
x11_context->last_x += dx;
x11_context->last_y += dy;
gdk_device_warp (pointer,
gdk_window_get_screen (x11_context->ipc_window),
x11_context->last_x, x11_context->last_y);
}
gdk_drag_update (context, x11_context->last_x, x11_context->last_y, state,
gdk_event_get_time ((GdkEvent *) event));
return TRUE;
}
static gboolean
gdk_dnd_handle_grab_broken_event (GdkDragContext *context,
const GdkEventGrabBroken *event)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
/* Don't cancel if we break the implicit grab from the initial button_press.
* Also, don't cancel if we re-grab on the widget or on our IPC window, for
* example, when changing the drag cursor.
*/
if (event->implicit ||
event->grab_window == x11_context->drag_window ||
event->grab_window == x11_context->ipc_window)
return FALSE;
gdk_drag_context_cancel (context);
return TRUE;
}
static gboolean
gdk_dnd_handle_button_event (GdkDragContext *context,
const GdkEventButton *event)
{
#if 0
/* FIXME: Check the button matches */
if (event->button != x11_context->button)
return FALSE;
#endif
if ((gdk_drag_context_get_selected_action (context) != 0) &&
(gdk_drag_context_get_dest_window (context) != NULL))
{
g_signal_emit_by_name (context, "drop-performed",
gdk_event_get_time ((GdkEvent *) event));
}
else
gdk_drag_context_cancel (context);
return TRUE;
}
gboolean
gdk_dnd_handle_drag_status (GdkDragContext *context,
const GdkEventDND *event)
{
GdkX11DragContext *context_x11 = GDK_X11_DRAG_CONTEXT (context);
GdkDragAction action;
if (context != event->context)
return FALSE;
action = gdk_drag_context_get_selected_action (context);
if (action != context_x11->current_action)
{
context_x11->current_action = action;
g_signal_emit_by_name (context, "action-changed", action);
}
return TRUE;
}
static gboolean
gdk_dnd_handle_drop_finished (GdkDragContext *context,
const GdkEventDND *event)
{
if (context != event->context)
return FALSE;
g_signal_emit_by_name (context, "dnd-finished");
gdk_drag_drop_done (context, TRUE);
return TRUE;
}
gboolean
gdk_x11_drag_context_handle_event (GdkDragContext *context,
const GdkEvent *event)
{
GdkX11DragContext *x11_context = GDK_X11_DRAG_CONTEXT (context);
if (!context->is_source)
return FALSE;
if (!x11_context->grab_seat && event->type != GDK_DROP_FINISHED)
return FALSE;
switch (event->type)
{
case GDK_MOTION_NOTIFY:
return gdk_dnd_handle_motion_event (context, &event->motion);
case GDK_BUTTON_RELEASE:
return gdk_dnd_handle_button_event (context, &event->button);
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
return gdk_dnd_handle_key_event (context, &event->key);
case GDK_GRAB_BROKEN:
return gdk_dnd_handle_grab_broken_event (context, &event->grab_broken);
case GDK_DRAG_STATUS:
return gdk_dnd_handle_drag_status (context, &event->dnd);
case GDK_DROP_FINISHED:
return gdk_dnd_handle_drop_finished (context, &event->dnd);
default:
break;
}
return FALSE;
}
void
gdk_x11_drag_context_action_changed (GdkDragContext *context,
GdkDragAction action)
{
GdkCursor *cursor;
cursor = gdk_drag_get_cursor (action);
gdk_drag_context_set_cursor (context, cursor);
}