gdk_device_warp() is a no-op when running in a Host environment that is Wayland based as the Wayland protocol does not allow clients to move the cursor to a specific location. This presents a problem when Qemu is running with GTK UI + relative mouse mode, as we would no longer be able to warp the cursor to the Guest's location like it is done when running in Xorg-based environments. To solve this problem, we just store the Guest's cursor location and add/subtract the difference compared to the Host's cursor location when injecting the next motion event.
Cc: Gerd Hoffmann <kra...@redhat.com> Cc: Dongwon Kim <dongwon....@intel.com> Signed-off-by: Vivek Kasireddy <vivek.kasire...@intel.com> --- include/ui/gtk.h | 2 ++ ui/gtk.c | 71 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/include/ui/gtk.h b/include/ui/gtk.h index ae0f53740d..f8df042f95 100644 --- a/include/ui/gtk.h +++ b/include/ui/gtk.h @@ -130,6 +130,8 @@ struct GtkDisplayState { gboolean last_set; int last_x; int last_y; + int guest_x; + int guest_y; int grab_x_root; int grab_y_root; VirtualConsole *kbd_owner; diff --git a/ui/gtk.c b/ui/gtk.c index 9d0c27c9e7..8ccc948813 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -447,22 +447,44 @@ static void gd_mouse_set(DisplayChangeListener *dcl, int x, int y, int visible) { VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); - GdkDisplay *dpy; + GtkDisplayState *s = vc->s; + GdkDisplay *dpy = gtk_widget_get_display(vc->gfx.drawing_area); gint x_root, y_root; if (qemu_input_is_absolute()) { return; } + /* + * When the mouse cursor moves from one vc (or connector in guest + * terminology) to another, some guest compositors (e.g. Weston) + * set x and y to 0 on the old vc. We check for this condition + * and return right away as we do not want to move the cursor + * back to the old vc (at 0, 0). + */ + if (GDK_IS_WAYLAND_DISPLAY(dpy)) { + if (s->ptr_owner != vc || (x == 0 && y == 0)) { + return; + } + } + /* + * Since Wayland compositors do not support clients warping/moving + * the cursor, we just store the Guest's cursor location here and + * add or subtract the difference when injecting the next motion event. + */ + if (GDK_IS_WAYLAND_DISPLAY(dpy)) { + s->guest_x = x; + s->guest_y = y; + return; + } - dpy = gtk_widget_get_display(vc->gfx.drawing_area); gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area), x * vc->gfx.scale_x, y * vc->gfx.scale_y, &x_root, &y_root); gdk_device_warp(gd_get_pointer(dpy), gtk_widget_get_screen(vc->gfx.drawing_area), x_root, y_root); - vc->s->last_x = x; - vc->s->last_y = y; + s->last_x = x; + s->last_y = y; } static void gd_cursor_define(DisplayChangeListener *dcl, @@ -869,6 +891,7 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, { VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; + GdkDisplay *dpy = gtk_widget_get_display(vc->gfx.drawing_area); GdkWindow *window; int x, y; int mx, my; @@ -915,14 +938,41 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, 0, surface_height(vc->gfx.ds)); qemu_input_event_sync(); } else if (s->last_set && s->ptr_owner == vc) { - qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x); - qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y); + int dx = x - s->last_x; + int dy = y - s->last_y; + + /* + * To converge/sync the Guest's and Host's cursor locations more + * accurately, we can avoid doing the / 2 below but it appears + * some Guest compositors (e.g. Weston) do not like large jumps; + * so we just do / 2 which seems to work reasonably well. + */ + if (GDK_IS_WAYLAND_DISPLAY(dpy)) { + dx += s->guest_x ? (x - s->guest_x) / 2 : 0; + dy += s->guest_y ? (y - s->guest_y) / 2 : 0; + s->guest_x = 0; + s->guest_y = 0; + } + qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, dx); + qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, dy); qemu_input_event_sync(); } s->last_x = x; s->last_y = y; s->last_set = TRUE; + /* + * When running in Wayland environment, we don't grab the cursor; so, + * we want to return right away as it would not make sense to warp it + * (below). + */ + if (GDK_IS_WAYLAND_DISPLAY(dpy)) { + if (s->ptr_owner != vc) { + s->ptr_owner = vc; + } + return TRUE; + } + if (!qemu_input_is_absolute() && s->ptr_owner == vc) { GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area); GdkDisplay *dpy = gtk_widget_get_display(widget); @@ -961,11 +1011,16 @@ static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button, { VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; + GdkDisplay *dpy = gtk_widget_get_display(vc->gfx.drawing_area); InputButton btn; - /* implicitly grab the input at the first click in the relative mode */ + /* Implicitly grab the input at the first click in the relative mode. + * However, when running in Wayland environment, some limited testing + * indicates that grabs are not very reliable. + */ if (button->button == 1 && button->type == GDK_BUTTON_PRESS && - !qemu_input_is_absolute() && s->ptr_owner != vc) { + !qemu_input_is_absolute() && s->ptr_owner != vc && + !GDK_IS_WAYLAND_DISPLAY(dpy)) { if (!vc->window) { gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), TRUE); -- 2.37.2