diff mbox series

[v1] ui/gtk: Add support for extended absolute mode for the pointer device

Message ID 20221118014003.182504-1-vivek.kasireddy@intel.com (mailing list archive)
State New, archived
Headers show
Series [v1] ui/gtk: Add support for extended absolute mode for the pointer device | expand

Commit Message

Kasireddy, Vivek Nov. 18, 2022, 1:40 a.m. UTC
Currently, the range of the absolute pointer device (usb-tablet) is
restricted to any one monitor. This presents a problem when there are
multiple VCs (Guest GTK windows) located on different monitors.
Therefore, it makes sense to extend the range of the absolute pointer
device to span all monitors. This would work nicely as long as the
monitors (aka outputs/connectors/displays) on the Host and Guest are
in alignment with each other (i.e, if the Host has monitor 2, 3, 4
to the right of monitor 1, then Guest's need to be arranged accordingly).

Relative mode could also be used in these situations but the user
experience is not as seamless as the absolute mode.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 include/ui/gtk.h |  1 +
 qapi/ui.json     |  6 ++++-
 qemu-options.hx  |  5 +++-
 ui/gtk.c         | 63 +++++++++++++++++++++++++++++++++++-------------
 4 files changed, 56 insertions(+), 19 deletions(-)
diff mbox series

Patch

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index ae0f53740d..01eb560b0b 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -136,6 +136,7 @@  struct GtkDisplayState {
     VirtualConsole *ptr_owner;
 
     gboolean full_screen;
+    gboolean ext_abs_mode;
 
     GdkCursor *null_cursor;
     Notifier mouse_mode_notifier;
diff --git a/qapi/ui.json b/qapi/ui.json
index 0abba3e930..1ce2945c84 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1201,6 +1201,9 @@ 
 #               Since 7.1
 # @show-menubar: Display the main window menubar. Defaults to "on".
 #                Since 8.0
+# @extend-abs-mode: Extend the absolute mode across all monitors or
+#                   limit it to just one. Defaults to "off".
+#                   Since 8.0
 #
 # Since: 2.12
 ##
@@ -1208,7 +1211,8 @@ 
   'data'    : { '*grab-on-hover' : 'bool',
                 '*zoom-to-fit'   : 'bool',
                 '*show-tabs'     : 'bool',
-                '*show-menubar'  : 'bool'  } }
+                '*show-menubar'  : 'bool',
+                '*extend-abs-mode'  : 'bool'  } }
 
 ##
 # @DisplayEGLHeadless:
diff --git a/qemu-options.hx b/qemu-options.hx
index eb38e5dc40..e6ef0634cd 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1980,7 +1980,7 @@  DEF("display", HAS_ARG, QEMU_OPTION_display,
 #if defined(CONFIG_GTK)
     "-display gtk[,full-screen=on|off][,gl=on|off][,grab-on-hover=on|off]\n"
     "            [,show-tabs=on|off][,show-cursor=on|off][,window-close=on|off]\n"
-    "            [,show-menubar=on|off]\n"
+    "            [,show-menubar=on|off][,extend-abs-mode=on|off]\n"
 #endif
 #if defined(CONFIG_VNC)
     "-display vnc=<display>[,<optargs>]\n"
@@ -2075,6 +2075,9 @@  SRST
 
         ``show-menubar=on|off`` : Display the main window menubar, defaults to "on"
 
+        ``extend-abs-mode=on|off`` : Extend the absolute mode across all
+                                     monitors or limit it to just one.
+
     ``curses[,charset=<encoding>]``
         Display video output via curses. For graphics device models
         which support a text mode, QEMU can display this output using a
diff --git a/ui/gtk.c b/ui/gtk.c
index 2be9755b0a..a4644675b2 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -883,12 +883,42 @@  static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
     return TRUE;
 }
 
+static void gd_calc_width_height(VirtualConsole *vc,
+                                 uint32_t *max_w, uint32_t *max_h)
+{
+    GtkDisplayState *s = vc->s;
+    GdkDisplay *dpy = gtk_widget_get_display(vc->gfx.drawing_area);
+    GdkRectangle geometry;
+    int i;
+
+    if (s->ext_abs_mode) {
+        for (i = 0; i < gdk_display_get_n_monitors(dpy); i++) {
+            gdk_monitor_get_geometry(gdk_display_get_monitor(dpy, i),
+                                     &geometry);
+            if (geometry.x + geometry.width > *max_w) {
+                *max_w = geometry.x + geometry.width;
+            }
+            if (geometry.y + geometry.height > *max_h) {
+                *max_h = geometry.y + geometry.height;
+            }
+        }
+    } else {
+        *max_w = surface_width(vc->gfx.ds);
+        *max_h = surface_height(vc->gfx.ds);
+    }
+}
+
 static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
                                 void *opaque)
 {
     VirtualConsole *vc = opaque;
     GtkDisplayState *s = vc->s;
-    GdkWindow *window;
+    GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
+    GdkDisplay *dpy = gtk_widget_get_display(widget);
+    GdkWindow *window = gtk_widget_get_window(widget);
+    GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, window);
+    GdkRectangle geometry;
+    uint32_t max_w = 0, max_h = 0;
     int x, y;
     int mx, my;
     int fbh, fbw;
@@ -901,7 +931,6 @@  static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
     fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
     fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
 
-    window = gtk_widget_get_window(vc->gfx.drawing_area);
     ww = gdk_window_get_width(window);
     wh = gdk_window_get_height(window);
     ws = gdk_window_get_scale_factor(window);
@@ -916,17 +945,20 @@  static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
 
     x = (motion->x - mx) / vc->gfx.scale_x * ws;
     y = (motion->y - my) / vc->gfx.scale_y * ws;
+    gdk_monitor_get_geometry(monitor, &geometry);
 
     if (qemu_input_is_absolute()) {
-        if (x < 0 || y < 0 ||
-            x >= surface_width(vc->gfx.ds) ||
-            y >= surface_height(vc->gfx.ds)) {
+        if (s->ext_abs_mode) {
+            x = x + geometry.x;
+            y = y + geometry.y;
+        }
+
+        gd_calc_width_height(vc, &max_w, &max_h);
+        if (x < 0 || y < 0 || x >= max_w || y >= max_h) {
             return TRUE;
         }
-        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
-                             0, surface_width(vc->gfx.ds));
-        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
-                             0, surface_height(vc->gfx.ds));
+        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x, 0, max_w);
+        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y, 0, max_h);
         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);
@@ -938,17 +970,9 @@  static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
     s->last_set = 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);
-        GdkWindow *win = gtk_widget_get_window(widget);
-        GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
-        GdkRectangle geometry;
-
         int x = (int)motion->x_root;
         int y = (int)motion->y_root;
 
-        gdk_monitor_get_geometry(monitor, &geometry);
-
         /* In relative mode check to see if client pointer hit
          * one of the monitor edges, and if so move it back to the
          * center of the monitor. This is important because the pointer
@@ -2423,6 +2447,11 @@  static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
         opts->u.gtk.show_tabs) {
         gtk_menu_item_activate(GTK_MENU_ITEM(s->show_tabs_item));
     }
+    if (opts->u.gtk.has_extend_abs_mode &&
+        opts->u.gtk.extend_abs_mode &&
+        qemu_input_is_absolute()) {
+        s->ext_abs_mode = TRUE;
+    }
     gd_clipboard_init(s);
 }