dnd: Fix issues with drag icons under Wayland

The Wayland dnd surface must remain in place until the drag
is over. Setting it directly as the hardcoded window of the
widget we construct carries the danger that it might get
destroyed prematurely, e.g. when the application calls
gtk_drag_set_icon_name more than once and we recreate the
widget.

Instead, create a dedicated toplevel, and reparent the widget
into it. To keep the code simple, we use the same approach
under X11 as well, and make it the responsibility of the
GDK dnd code to keep the window position updated. We already
pass the current pointer position to gdk_drag_motion, which
makes this very easy.

As a side-effect of these changes, it is now possible to use
non-toplevel widgets as drag icons.

https://bugzilla.gnome.org/show_bug.cgi?id=748763
This commit is contained in:
Matthias Clasen 2015-12-01 10:34:26 -05:00
parent fff8297a50
commit 5bb12474d9

View File

@ -96,6 +96,7 @@ struct _GtkDragSourceInfo
GdkDragAction possible_actions; /* Actions allowed by source */ GdkDragAction possible_actions; /* Actions allowed by source */
GdkDragContext *context; /* drag context */ GdkDragContext *context; /* drag context */
GtkWidget *icon_window; /* Window for drag */ GtkWidget *icon_window; /* Window for drag */
GtkWidget *icon_widget; /* Widget for drag */
GtkWidget *fallback_icon; /* Window for drag used on other screens */ GtkWidget *fallback_icon; /* Window for drag used on other screens */
GtkWidget *ipc_widget; /* GtkInvisible for grab, message passing */ GtkWidget *ipc_widget; /* GtkInvisible for grab, message passing */
GdkCursor *cursor; /* Cursor for drag */ GdkCursor *cursor; /* Cursor for drag */
@ -116,7 +117,7 @@ struct _GtkDragSourceInfo
guint update_idle; /* Idle function to update the drag */ guint update_idle; /* Idle function to update the drag */
guint drop_timeout; /* Timeout for aborting drop */ guint drop_timeout; /* Timeout for aborting drop */
guint destroy_icon : 1; /* If true, destroy icon_window */ guint destroy_icon : 1; /* If true, destroy icon_widget */
guint have_grab : 1; /* Do we still have the pointer grab */ guint have_grab : 1; /* Do we still have the pointer grab */
GtkIconHelper *icon_helper; GtkIconHelper *icon_helper;
GdkCursor *drag_cursors[6]; GdkCursor *drag_cursors[6];
@ -2450,6 +2451,7 @@ gtk_drag_begin_internal (GtkWidget *widget,
info->last_event = NULL; info->last_event = NULL;
info->selections = NULL; info->selections = NULL;
info->icon_window = NULL; info->icon_window = NULL;
info->icon_widget = NULL;
info->destroy_icon = FALSE; info->destroy_icon = FALSE;
/* Set cur_x, cur_y here so if the "drag-begin" signal shows /* Set cur_x, cur_y here so if the "drag-begin" signal shows
@ -2483,7 +2485,7 @@ gtk_drag_begin_internal (GtkWidget *widget,
* application may have set one in ::drag_begin, or it may * application may have set one in ::drag_begin, or it may
* not have set one. * not have set one.
*/ */
if (!info->icon_window && !info->icon_helper) if (!info->icon_widget && !info->icon_helper)
{ {
if (icon) if (icon)
{ {
@ -2642,22 +2644,6 @@ gtk_drag_begin (GtkWidget *widget,
actions, button, event, -1, -1); actions, button, event, -1, -1);
} }
static void
gtk_drag_update_icon (GtkDragSourceInfo *info)
{
if (info->icon_window)
{
gtk_window_move (GTK_WINDOW (info->icon_window),
info->cur_x - info->hot_x,
info->cur_y - info->hot_y);
if (gtk_widget_get_visible (info->icon_window))
gdk_window_raise (gtk_widget_get_window (info->icon_window));
else
gtk_widget_show (info->icon_window);
}
}
static void static void
gtk_drag_set_icon_window (GdkDragContext *context, gtk_drag_set_icon_window (GdkDragContext *context,
GtkWidget *widget, GtkWidget *widget,
@ -2666,7 +2652,6 @@ gtk_drag_set_icon_window (GdkDragContext *context,
gboolean destroy_on_release) gboolean destroy_on_release)
{ {
GtkDragSourceInfo *info; GtkDragSourceInfo *info;
GdkDisplay *display;
info = gtk_drag_get_source_info (context, FALSE); info = gtk_drag_get_source_info (context, FALSE);
if (info == NULL) if (info == NULL)
@ -2679,31 +2664,51 @@ gtk_drag_set_icon_window (GdkDragContext *context,
gtk_drag_remove_icon (info); gtk_drag_remove_icon (info);
if (widget) if (widget)
g_object_ref (widget); g_object_ref (widget);
info->icon_window = widget; info->icon_widget = widget;
info->hot_x = hot_x; info->hot_x = hot_x;
info->hot_y = hot_y; info->hot_y = hot_y;
info->destroy_icon = destroy_on_release; info->destroy_icon = destroy_on_release;
display = gdk_window_get_display (gdk_drag_context_get_source_window (context)); if (!widget)
goto out;
#ifdef GDK_WINDOWING_WAYLAND if (!info->icon_window)
if (GTK_IS_WINDOW (widget) && GDK_IS_WAYLAND_DISPLAY (display))
{ {
if (gtk_widget_get_realized (widget)) GdkScreen *screen;
gtk_widget_unrealize (widget); GdkVisual *visual;
gtk_window_set_hardcoded_window (GTK_WINDOW (widget), screen = gdk_window_get_screen (gdk_drag_context_get_source_window (context));
gdk_wayland_drag_context_get_dnd_window (context)); visual = gdk_screen_get_rgba_visual (screen);
info->icon_window = gtk_window_new (GTK_WINDOW_POPUP);
gtk_window_set_type_hint (GTK_WINDOW (info->icon_window), GDK_WINDOW_TYPE_HINT_DND);
gtk_window_set_screen (GTK_WINDOW (info->icon_window), screen);
gtk_widget_set_size_request (info->icon_window, 24, 24);
if (visual)
gtk_widget_set_visual (info->icon_window, visual);
gtk_widget_set_events (info->icon_window, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
gtk_widget_set_app_paintable (info->icon_window, TRUE);
gtk_window_set_hardcoded_window (GTK_WINDOW (info->icon_window),
gdk_drag_context_get_drag_window (context));
gtk_widget_show (info->icon_window);
} }
#endif
if (widget && info->icon_helper) if (GTK_IS_WINDOW (widget))
g_clear_object (&info->icon_helper); {
gtk_widget_hide (widget);
gtk_widget_unrealize (widget);
gtk_widget_set_parent_window (widget, gtk_widget_get_window (info->icon_window));
gtk_widget_show (widget);
}
gtk_container_add (GTK_CONTAINER (info->icon_window), widget);
g_clear_object (&info->icon_helper);
out:
gtk_drag_update_cursor (info); gtk_drag_update_cursor (info);
gtk_drag_update_icon (info);
} }
/** /**
@ -2757,17 +2762,20 @@ set_icon_helper (GdkDragContext *context,
gint width, height; gint width, height;
GdkScreen *screen; GdkScreen *screen;
GdkDisplay *display; GdkDisplay *display;
GdkVisual *visual;
g_return_if_fail (context != NULL); g_return_if_fail (context != NULL);
g_return_if_fail (def != NULL); g_return_if_fail (def != NULL);
info = gtk_drag_get_source_info (context, FALSE); info = gtk_drag_get_source_info (context, FALSE);
screen = gdk_window_get_screen (gdk_drag_context_get_source_window (context)); screen = gdk_window_get_screen (gdk_drag_context_get_source_window (context));
visual = gdk_screen_get_rgba_visual (screen);
window = gtk_window_new (GTK_WINDOW_POPUP); window = gtk_window_new (GTK_WINDOW_POPUP);
gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND); gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND);
gtk_window_set_screen (GTK_WINDOW (window), screen); gtk_window_set_screen (GTK_WINDOW (window), screen);
if (visual)
gtk_widget_set_visual (window, visual);
gtk_widget_set_events (window, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); gtk_widget_set_events (window, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
gtk_widget_set_app_paintable (window, TRUE); gtk_widget_set_app_paintable (window, TRUE);
@ -2827,7 +2835,6 @@ set_icon_helper (GdkDragContext *context,
cairo_region_t *region; cairo_region_t *region;
surface = cairo_image_surface_create (CAIRO_FORMAT_A1, width, height); surface = cairo_image_surface_create (CAIRO_FORMAT_A1, width, height);
cr = cairo_create (surface); cr = cairo_create (surface);
cairo_set_source_surface (cr, source, 0, 0); cairo_set_source_surface (cr, source, 0, 0);
cairo_paint (cr); cairo_paint (cr);
@ -3299,22 +3306,26 @@ gtk_drag_drop_finished (GtkDragSourceInfo *info,
} }
else else
{ {
GtkDragAnim *anim = g_slice_new0 (GtkDragAnim); GtkDragAnim *anim;
/* Mark the context as dead, so if the destination decides
* to respond really late, we still are OK.
*/
gtk_drag_clear_source_info (info->context);
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (info->widget)))
return; /* cancel animation is done by the compositor */ ;
#endif
anim = g_slice_new0 (GtkDragAnim);
anim->info = info; anim->info = info;
anim->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (info->widget)); anim->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (info->widget));
info->cur_screen = gtk_widget_get_screen (info->widget); info->cur_screen = gtk_widget_get_screen (info->widget);
if (!info->icon_window) if (!info->icon_window)
set_icon_helper (info->context, gtk_icon_helper_get_definition (info->icon_helper), set_icon_helper (info->context, gtk_icon_helper_get_definition (info->icon_helper), 0, 0, TRUE);
0, 0, TRUE);
gtk_drag_update_icon (info);
/* Mark the context as dead, so if the destination decides
* to respond really late, we still are OK.
*/
gtk_drag_clear_source_info (info->context);
gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, 17, gtk_drag_anim_timeout, anim, (GDestroyNotify) gtk_drag_anim_destroy); gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, 17, gtk_drag_anim_timeout, anim, (GDestroyNotify) gtk_drag_anim_destroy);
} }
} }
@ -3365,12 +3376,12 @@ gtk_drag_drop (GtkDragSourceInfo *info,
selection_data.target = pair->target; selection_data.target = pair->target;
selection_data.data = NULL; selection_data.data = NULL;
selection_data.length = -1; selection_data.length = -1;
g_signal_emit_by_name (info->widget, "drag-data-get", g_signal_emit_by_name (info->widget, "drag-data-get",
info->context, &selection_data, info->context, &selection_data,
pair->info, pair->info,
time); time);
/* FIXME: Should we check for length >= 0 here? */ /* FIXME: Should we check for length >= 0 here? */
gtk_drag_drop_finished (info, GTK_DRAG_RESULT_SUCCESS, time); gtk_drag_drop_finished (info, GTK_DRAG_RESULT_SUCCESS, time);
return; return;
@ -3383,7 +3394,7 @@ gtk_drag_drop (GtkDragSourceInfo *info,
{ {
if (info->icon_window) if (info->icon_window)
gtk_widget_hide (info->icon_window); gtk_widget_hide (info->icon_window);
gdk_drag_drop (info->context, time); gdk_drag_drop (info->context, time);
info->drop_timeout = gdk_threads_add_timeout (DROP_ABORT_TIME, info->drop_timeout = gdk_threads_add_timeout (DROP_ABORT_TIME,
gtk_drag_abort_timeout, gtk_drag_abort_timeout,
@ -3500,12 +3511,12 @@ gtk_drag_anim_timeout (gpointer data)
static void static void
gtk_drag_remove_icon (GtkDragSourceInfo *info) gtk_drag_remove_icon (GtkDragSourceInfo *info)
{ {
if (info->icon_window) if (info->icon_widget)
{ {
gtk_widget_hide (info->icon_window); gtk_widget_hide (info->icon_widget);
gtk_widget_set_opacity (info->icon_window, 1.0); gtk_widget_set_opacity (info->icon_widget, 1.0);
if (info->destroy_icon) if (info->destroy_icon)
gtk_widget_destroy (info->icon_window); gtk_widget_destroy (info->icon_widget);
if (info->fallback_icon) if (info->fallback_icon)
{ {
@ -3513,8 +3524,8 @@ gtk_drag_remove_icon (GtkDragSourceInfo *info)
info->fallback_icon = NULL; info->fallback_icon = NULL;
} }
g_object_unref (info->icon_window); g_object_unref (info->icon_widget);
info->icon_window = NULL; info->icon_widget = NULL;
} }
} }
@ -3557,8 +3568,10 @@ gtk_drag_source_info_destroy (GtkDragSourceInfo *info)
if (!info->proxy_dest) if (!info->proxy_dest)
g_signal_emit_by_name (info->widget, "drag-end", info->context); g_signal_emit_by_name (info->widget, "drag-end", info->context);
if (info->widget) g_clear_object (&info->widget);
g_object_unref (info->widget);
if (info->icon_window)
gtk_widget_destroy (info->icon_window);
gtk_selection_remove_all (info->ipc_widget); gtk_selection_remove_all (info->ipc_widget);
g_object_set_data (G_OBJECT (info->ipc_widget), I_("gtk-info"), NULL); g_object_set_data (G_OBJECT (info->ipc_widget), I_("gtk-info"), NULL);
@ -3600,7 +3613,7 @@ gtk_drag_update_idle (gpointer data)
info->button, info->button,
info->possible_actions, info->possible_actions,
&action, &possible_actions); &action, &possible_actions);
gtk_drag_update_icon (info);
gdk_drag_find_window_for_screen (info->context, gdk_drag_find_window_for_screen (info->context,
info->icon_window ? gtk_widget_get_window (info->icon_window) : NULL, info->icon_window ? gtk_widget_get_window (info->icon_window) : NULL,
info->cur_screen, info->cur_x, info->cur_y, info->cur_screen, info->cur_x, info->cur_y,