From bfee45e6f9f13506f97c8765255c7dc0dd698420 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 8 Jan 2016 21:24:52 +0100 Subject: [PATCH] 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. --- gdk/x11/gdkdnd-x11.c | 538 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) diff --git a/gdk/x11/gdkdnd-x11.c b/gdk/x11/gdkdnd-x11.c index 69f1b94c91..b8468d3ab0 100644 --- a/gdk/x11/gdkdnd-x11.c +++ b/gdk/x11/gdkdnd-x11.c @@ -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); +}