All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2)
@ 2022-11-18  1:44 Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 1/6] ui/gtk: Consider the scaling factor when getting the root coordinates Vivek Kasireddy
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Vivek Kasireddy, Dongwon Kim, Gerd Hoffmann, Markus Armbruster,
	Marc-André Lureau

There is a need (expressed by several customers/users) to assign
ownership of one or more physical monitors/connectors to individual
Guests such that there is a clear notion of which Guest's contents
are being displayed on any given monitor. Given that there is always
a Display Server/Compositor running on the Host, monitor ownership
can never truly be transferred to Guests. However, the closest we
can come to realizing this concept is to request the Host compositor
to fullscreen the Guest's windows on individual monitors. This way,
it would become possible to have 4 different Guests' windows be
displayed on 4 different monitors or a single Guest's windows (or
virtual consoles/outputs) be displayed on 4 monitors or any such
combination.

This patch series attempts to accomplish this by introducing a new
parameter named "connector" to assign the monitors to the GFX VCs
associated with a Guest. If the assigned monitor is not connected,
then the Guest's window would not be displayed anywhere similar to
how a Host compositor would behave when the connectors are not
connected. Once the monitor is hotplugged, the Guest's window(s)
would be positioned on the assigned monitor.

The first 3 patches are bug fixes associated with pointer positioning
in relative mode. The 4th patch is also a bug fix to ensure that 
context related objects are destroyed when an associated window is
destroyed. The 5th patch is a minor refactor and the last patch
introduces the new parameter. This patch series is expected to
supersede a similar series from Dongwon Kim here:
https://lists.nongnu.org/archive/html/qemu-devel/2022-07/msg03214.html

Example Usage: -device virtio-gpu-pci,max_outputs=2,blob=true......
               -display gtk,gl=on,connector.0=eDP-1,connector.1=DP-1.....

Cc: Dongwon Kim <dongwon.kim@intel.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>

Vivek Kasireddy (6):
  ui/gtk: Consider the scaling factor when getting the root coordinates
  ui/gtk-gl-area: Don't forget to calculate the scaling factors in draw
  ui/gtk: Handle relative mode events correctly with Wayland compositors
  ui/gtk: Disable the scanout when a detached tab is closed
  ui/gtk: Factor out tab window creation into a separate function
  ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs
    (v2)

 include/ui/gtk.h |   3 +
 qapi/ui.json     |  10 +-
 qemu-options.hx  |   5 +-
 ui/gtk-egl.c     |   2 +
 ui/gtk-gl-area.c |   7 +
 ui/gtk.c         | 383 +++++++++++++++++++++++++++++++++++++++++++----
 6 files changed, 375 insertions(+), 35 deletions(-)

-- 
2.37.2



^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2 1/6] ui/gtk: Consider the scaling factor when getting the root coordinates
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 2/6] ui/gtk-gl-area: Don't forget to calculate the scaling factors in draw Vivek Kasireddy
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Vivek Kasireddy, Gerd Hoffmann, Dongwon Kim

Since gdk_window_get_root_coords() expects a position within the window,
we need to translate Guest's cooridinates to window local coordinates
by multiplying them with the scaling factor.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 ui/gtk.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ui/gtk.c b/ui/gtk.c
index 92daaa6a6e..6c23903173 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -456,7 +456,8 @@ static void gd_mouse_set(DisplayChangeListener *dcl,
 
     dpy = gtk_widget_get_display(vc->gfx.drawing_area);
     gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
-                               x, y, &x_root, &y_root);
+                               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);
-- 
2.37.2



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 2/6] ui/gtk-gl-area: Don't forget to calculate the scaling factors in draw
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 1/6] ui/gtk: Consider the scaling factor when getting the root coordinates Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 3/6] ui/gtk: Handle relative mode events correctly with Wayland compositors Vivek Kasireddy
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Vivek Kasireddy, Gerd Hoffmann, Dongwon Kim

Just like it is done in gtk-egl.c, we need to ensure that the scaling
factors are correctly calculated in draw callback. Otherwise, they
would just be set to 1.0. And, use gtk_widget_get_allocated_width/height
variants to determine width and height in the Wayland case similar to
how it is done in draw.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 ui/gtk-gl-area.c | 5 +++++
 ui/gtk.c         | 9 +++++++--
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index 682638a197..6799805f8e 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -81,6 +81,9 @@ void gd_gl_area_draw(VirtualConsole *vc)
             egl_dmabuf_create_sync(dmabuf);
         }
 #endif
+        vc->gfx.scale_x = (double)ww / vc->gfx.w;
+        vc->gfx.scale_y = (double)wh / vc->gfx.h;
+
         glFlush();
 #ifdef CONFIG_GBM
         if (dmabuf) {
@@ -100,6 +103,8 @@ void gd_gl_area_draw(VirtualConsole *vc)
 
         surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
         surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
+        vc->gfx.scale_x = (double)ww / vc->gfx.w;
+        vc->gfx.scale_y = (double)wh / vc->gfx.h;
     }
 }
 
diff --git a/ui/gtk.c b/ui/gtk.c
index 6c23903173..9d0c27c9e7 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -883,9 +883,14 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
     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);
+    if (GDK_IS_WAYLAND_DISPLAY(dpy)) {
+        ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area);
+        wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area);
+    } else {
+        ww = gdk_window_get_width(window);
+        wh = gdk_window_get_height(window);
+    }
 
     mx = my = 0;
     if (ww > fbw) {
-- 
2.37.2



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 3/6] ui/gtk: Handle relative mode events correctly with Wayland compositors
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 1/6] ui/gtk: Consider the scaling factor when getting the root coordinates Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 2/6] ui/gtk-gl-area: Don't forget to calculate the scaling factors in draw Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 4/6] ui/gtk: Disable the scanout when a detached tab is closed Vivek Kasireddy
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Vivek Kasireddy, Gerd Hoffmann, Dongwon Kim

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 <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@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



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 4/6] ui/gtk: Disable the scanout when a detached tab is closed
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
                   ` (2 preceding siblings ...)
  2022-11-18  1:44 ` [PATCH v2 3/6] ui/gtk: Handle relative mode events correctly with Wayland compositors Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 5/6] ui/gtk: Factor out tab window creation into a separate function Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 6/6] ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs (v2) Vivek Kasireddy
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Vivek Kasireddy, Gerd Hoffmann, Dongwon Kim

When a detached tab window is closed, the underlying (EGL) context
is destroyed; therefore, disable the scanout which also destroys the
underlying framebuffer (id) and other objects. Also add calls to
make the context current in disable scanout and other missing places.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 ui/gtk-egl.c     | 2 ++
 ui/gtk-gl-area.c | 2 ++
 ui/gtk.c         | 1 +
 3 files changed, 5 insertions(+)

diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index 35f917ceb1..396cb6590a 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -214,6 +214,8 @@ void gd_egl_scanout_disable(DisplayChangeListener *dcl)
 {
     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 
+    eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+                   vc->gfx.esurface, vc->gfx.ectx);
     vc->gfx.w = 0;
     vc->gfx.h = 0;
     gtk_egl_set_scanout_mode(vc, false);
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index 6799805f8e..816f213158 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -275,6 +275,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl)
 {
     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 
+    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
     gtk_gl_area_set_scanout_mode(vc, false);
 }
 
@@ -283,6 +284,7 @@ void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
 {
     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 
+    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
     if (vc->gfx.guest_fb.dmabuf) {
         graphic_hw_gl_block(vc->gfx.dcl.con, true);
         vc->gfx.guest_fb.dmabuf->draw_submitted = true;
diff --git a/ui/gtk.c b/ui/gtk.c
index 8ccc948813..4ac3655694 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1367,6 +1367,7 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
     VirtualConsole *vc = opaque;
     GtkDisplayState *s = vc->s;
 
+    dpy_gl_scanout_disable(vc->gfx.dcl.con);
     gtk_widget_set_sensitive(vc->menu_item, true);
     gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
     gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
-- 
2.37.2



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 5/6] ui/gtk: Factor out tab window creation into a separate function
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
                   ` (3 preceding siblings ...)
  2022-11-18  1:44 ` [PATCH v2 4/6] ui/gtk: Disable the scanout when a detached tab is closed Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  2022-11-18  1:44 ` [PATCH v2 6/6] ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs (v2) Vivek Kasireddy
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Vivek Kasireddy, Gerd Hoffmann, Dongwon Kim

Pull the code that creates a new window associated with a notebook
tab into a separate function. This new function can be useful not
just when user wants to detach a tab but also in the future when
a new window creation is needed in other scenarios.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 ui/gtk.c | 65 +++++++++++++++++++++++++++++++-------------------------
 1 file changed, 36 insertions(+), 29 deletions(-)

diff --git a/ui/gtk.c b/ui/gtk.c
index 4ac3655694..6b0369e3ed 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1400,6 +1400,41 @@ static gboolean gd_win_grab(void *opaque)
     return TRUE;
 }
 
+static void gd_tab_window_create(VirtualConsole *vc)
+{
+    GtkDisplayState *s = vc->s;
+
+    gtk_widget_set_sensitive(vc->menu_item, false);
+    vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#if defined(CONFIG_OPENGL)
+    if (vc->gfx.esurface) {
+	eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
+	vc->gfx.esurface = NULL;
+    }
+    if (vc->gfx.esurface) {
+	eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
+	vc->gfx.ectx = NULL;
+    }
+#endif
+    gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
+
+    g_signal_connect(vc->window, "delete-event",
+		     G_CALLBACK(gd_tab_window_close), vc);
+    gtk_widget_show_all(vc->window);
+
+    if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
+	GtkAccelGroup *ag = gtk_accel_group_new();
+	gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
+
+	GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
+					   vc, NULL);
+	gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
+    }
+
+    gd_update_geometry_hints(vc);
+    gd_update_caption(s);
+}
+
 static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
 {
     GtkDisplayState *s = opaque;
@@ -1411,35 +1446,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
                                        FALSE);
     }
     if (!vc->window) {
-        gtk_widget_set_sensitive(vc->menu_item, false);
-        vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-#if defined(CONFIG_OPENGL)
-        if (vc->gfx.esurface) {
-            eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
-            vc->gfx.esurface = NULL;
-        }
-        if (vc->gfx.esurface) {
-            eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
-            vc->gfx.ectx = NULL;
-        }
-#endif
-        gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
-
-        g_signal_connect(vc->window, "delete-event",
-                         G_CALLBACK(gd_tab_window_close), vc);
-        gtk_widget_show_all(vc->window);
-
-        if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
-            GtkAccelGroup *ag = gtk_accel_group_new();
-            gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
-
-            GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
-                                               vc, NULL);
-            gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
-        }
-
-        gd_update_geometry_hints(vc);
-        gd_update_caption(s);
+        gd_tab_window_create(vc);
     }
 }
 
-- 
2.37.2



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 6/6] ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs (v2)
  2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
                   ` (4 preceding siblings ...)
  2022-11-18  1:44 ` [PATCH v2 5/6] ui/gtk: Factor out tab window creation into a separate function Vivek Kasireddy
@ 2022-11-18  1:44 ` Vivek Kasireddy
  5 siblings, 0 replies; 7+ messages in thread
From: Vivek Kasireddy @ 2022-11-18  1:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Vivek Kasireddy, Dongwon Kim, Gerd Hoffmann, Markus Armbruster,
	Marc-André Lureau

The new parameter named "connector" can be used to assign physical
monitors/connectors to individual GFX VCs such that when the monitor
is connected or hotplugged, the associated GTK window would be
moved to it. If the monitor is disconnected or unplugged, the
associated GTK window would be destroyed and a relevant disconnect
event would be sent to the Guest.

Usage: -device virtio-gpu-pci,max_outputs=2,blob=true,...
       -display gtk,gl=on,connectors.0=eDP-1,connectors.1=DP-1.....

v2:
- Make various style improvements suggested by Markus.
- Fullscreen the window only if the fullscreen option is enabled.

Cc: Dongwon Kim <dongwon.kim@intel.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Acked-by: Markus Armbruster <armbru@redhat.com> (QAPI schema)
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
---
 include/ui/gtk.h |   1 +
 qapi/ui.json     |  10 +-
 qemu-options.hx  |   5 +-
 ui/gtk.c         | 260 +++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 268 insertions(+), 8 deletions(-)

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index f8df042f95..bbea0316fb 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -83,6 +83,7 @@ typedef struct VirtualConsole {
     GtkWidget *menu_item;
     GtkWidget *tab_item;
     GtkWidget *focus;
+    GdkMonitor *monitor;
     VirtualConsoleType type;
     union {
         VirtualGfxConsole gfx;
diff --git a/qapi/ui.json b/qapi/ui.json
index 0abba3e930..a4d1616445 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1201,6 +1201,13 @@
 #               Since 7.1
 # @show-menubar: Display the main window menubar. Defaults to "on".
 #                Since 8.0
+# @connectors:  List of physical monitor/connector names where the GTK
+#               windows containing the respective graphics virtual consoles
+#               (VCs) are to be placed. If a mapping exists for a VC, it
+#               will be moved to that specific monitor or else it would
+#               not be displayed anywhere and would appear disconnected
+#               to the guest.
+#               Since 8.0
 #
 # Since: 2.12
 ##
@@ -1208,7 +1215,8 @@
   'data'    : { '*grab-on-hover' : 'bool',
                 '*zoom-to-fit'   : 'bool',
                 '*show-tabs'     : 'bool',
-                '*show-menubar'  : 'bool'  } }
+                '*show-menubar'  : 'bool',
+                '*connectors'    : ['str'] } }
 
 ##
 # @DisplayEGLHeadless:
diff --git a/qemu-options.hx b/qemu-options.hx
index eb38e5dc40..ae6289e4f3 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][,connectors.<index>=<connector name>]\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"
 
+        ``connectors=<conn name>`` : VC to connector mappings to display the VC
+                                     window on a specific monitor
+
     ``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 6b0369e3ed..03fc454a1f 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -37,6 +37,7 @@
 #include "qapi/qapi-commands-misc.h"
 #include "qemu/cutils.h"
 #include "qemu/main-loop.h"
+#include "qemu/option.h"
 
 #include "ui/console.h"
 #include "ui/gtk.h"
@@ -116,6 +117,11 @@
 
 #define HOTKEY_MODIFIERS        (GDK_CONTROL_MASK | GDK_MOD1_MASK)
 
+/* Upper limit on number of times to check for a valid monitor name */
+#define MAX_NUM_RETRIES 5
+/* Max num of milliseconds to wait before checking for a valid monitor name */
+#define WAIT_MS 50
+
 static const guint16 *keycode_map;
 static size_t keycode_maplen;
 
@@ -126,6 +132,14 @@ struct VCChardev {
 };
 typedef struct VCChardev VCChardev;
 
+typedef struct gd_monitor_data {
+    GtkDisplayState *s;
+    GdkDisplay *dpy;
+    GdkMonitor *monitor;
+    QEMUTimer *hp_timer;
+    int num_retries;
+} gd_monitor_data;
+
 #define TYPE_CHARDEV_VC "chardev-vc"
 DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
                          TYPE_CHARDEV_VC)
@@ -461,7 +475,7 @@ static void gd_mouse_set(DisplayChangeListener *dcl,
      * 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 (GDK_IS_WAYLAND_DISPLAY(dpy) || s->opts->u.gtk.has_connectors) {
         if (s->ptr_owner != vc || (x == 0 && y == 0)) {
             return;
         }
@@ -962,11 +976,11 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
     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).
+     * When running in Wayland environment or when has_connectors is set,
+     * 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 (GDK_IS_WAYLAND_DISPLAY(dpy) || s->opts->u.gtk.has_connectors) {
         if (s->ptr_owner != vc) {
             s->ptr_owner = vc;
         }
@@ -1017,10 +1031,13 @@ static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
     /* 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.
+     * And, when has_connectors is set, we also do not want to grab the cursor
+     * as it would be tedious to grab/ungrab when drag-and-dropping or moving
+     * apps from one vc to another -- which may be on a different monitor.
      */
     if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
         !qemu_input_is_absolute() && s->ptr_owner != vc &&
-        !GDK_IS_WAYLAND_DISPLAY(dpy)) {
+        !GDK_IS_WAYLAND_DISPLAY(dpy) && !s->opts->u.gtk.has_connectors) {
         if (!vc->window) {
             gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                            TRUE);
@@ -1450,6 +1467,228 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
     }
 }
 
+static void gd_window_show_on_monitor(GdkDisplay *dpy, VirtualConsole *vc,
+                                      gint monitor_num)
+{
+    GtkDisplayState *s = vc->s;
+    GdkMonitor *monitor = gdk_display_get_monitor(dpy, monitor_num);
+    GdkWindow *window;
+    GdkRectangle geometry;
+
+    if (!vc->window) {
+        gd_tab_window_create(vc);
+    }
+    if (s->opts->has_full_screen && s->opts->full_screen) {
+        s->full_screen = TRUE;
+        gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
+        gtk_window_fullscreen_on_monitor(GTK_WINDOW(vc->window),
+                                         gdk_display_get_default_screen(dpy),
+                                         monitor_num);
+    } else {
+        gd_update_windowsize(vc);
+        gdk_monitor_get_geometry(monitor, &geometry);
+        /*
+         * Note: some compositors (mainly Wayland ones) may not honor a
+         * request to move to a particular location. The user is expected
+         * to drag the window to the preferred location in this case.
+         */
+        gtk_window_move(GTK_WINDOW(vc->window), geometry.x, geometry.y);
+    }
+
+    vc->monitor = monitor;
+    window = gtk_widget_get_window(vc->gfx.drawing_area);
+    gd_set_ui_size(vc, gdk_window_get_width(window),
+                   gdk_window_get_height(window));
+    gd_update_cursor(vc);
+}
+
+static int gd_monitor_lookup(GdkDisplay *dpy, char *label)
+{
+    GdkMonitor *monitor;
+    int total_monitors = gdk_display_get_n_monitors(dpy);
+    int i;
+
+    for (i = 0; i < total_monitors; i++) {
+        monitor = gdk_display_get_monitor(dpy, i);
+        if (monitor && !g_strcmp0(gdk_monitor_get_model(monitor), label)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static gboolean gd_vc_is_misplaced(GdkDisplay *dpy, GdkMonitor *monitor,
+                                   VirtualConsole *vc)
+{
+    GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area);
+    GdkMonitor *mon = gdk_display_get_monitor_at_window(dpy, window);
+    const char *monitor_name = gdk_monitor_get_model(monitor);
+
+    if (!vc->monitor) {
+        if (!g_strcmp0(monitor_name, vc->label)) {
+            return TRUE;
+        }
+    } else {
+        if (vc->monitor != mon) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static void gd_monitor_check_vcs(GdkDisplay *dpy, GdkMonitor *monitor,
+                                 GtkDisplayState *s)
+{
+    VirtualConsole *vc;
+    gint monitor_num;
+    int i;
+
+    /*
+     * We need to call gd_vc_is_misplaced() after a monitor is added to
+     * ensure that the Host compositor has not moved our windows around.
+     */
+    for (i = 0; i < s->nb_vcs; i++) {
+        vc = &s->vc[i];
+        monitor_num = vc->label ? gd_monitor_lookup(dpy, vc->label) : -1;
+        if (monitor_num >= 0 && gd_vc_is_misplaced(dpy, monitor, vc)) {
+            gd_window_show_on_monitor(dpy, vc, monitor_num);
+        }
+    }
+}
+
+static void gd_monitor_hotplug_timer(void *opaque)
+{
+    gd_monitor_data *data = opaque;
+    const char *monitor_name;
+
+    monitor_name = GDK_IS_MONITOR(data->monitor) ?
+                   gdk_monitor_get_model(data->monitor) : NULL;
+    if (monitor_name) {
+        gd_monitor_check_vcs(data->dpy, data->monitor, data->s);
+    }
+    if (monitor_name || data->num_retries == MAX_NUM_RETRIES) {
+        timer_del(data->hp_timer);
+        g_free(data);
+    } else {
+        data->num_retries++;
+        timer_mod(data->hp_timer,
+                  qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + WAIT_MS);
+    }
+}
+
+static void gd_monitor_add(GdkDisplay *dpy, GdkMonitor *monitor,
+                           void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    gd_monitor_data *data;
+
+    /*
+     * It is possible that the Host Compositor or GTK would not have
+     * had a chance to fully process the hotplug event and as a result
+     * gdk_monitor_get_model() could return NULL. Therefore, check for
+     * this case and try again later.
+     */
+    if (GDK_IS_MONITOR(monitor) && gdk_monitor_get_model(monitor)) {
+        gd_monitor_check_vcs(dpy, monitor, s);
+    } else {
+        data = g_new0(gd_monitor_data, 1);
+        data->s = s;
+        data->dpy = dpy;
+        data->monitor = monitor;
+        data->hp_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+                                      gd_monitor_hotplug_timer, data);
+        timer_mod(data->hp_timer,
+                  qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + WAIT_MS);
+    }
+}
+
+static void gd_monitor_remove(GdkDisplay *dpy, GdkMonitor *monitor,
+                              void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    VirtualConsole *vc;
+    int i;
+
+    for (i = 0; i < s->nb_vcs; i++) {
+        vc = &s->vc[i];
+        if (vc->monitor == monitor) {
+            vc->monitor = NULL;
+            if (vc->window == s->window) {
+                gdk_window_hide(gtk_widget_get_window(vc->window));
+            } else {
+                gd_tab_window_close(NULL, NULL, vc);
+            }
+            gd_set_ui_size(vc, 0, 0);
+            break;
+        }
+    }
+}
+
+static VirtualConsole *gd_next_gfx_vc(GtkDisplayState *s)
+{
+    VirtualConsole *vc;
+    int i;
+
+    for (i = 0; i < s->nb_vcs; i++) {
+        vc = &s->vc[i];
+        if (vc->type == GD_VC_GFX &&
+            qemu_console_is_graphic(vc->gfx.dcl.con) &&
+            !vc->label) {
+            return vc;
+        }
+    }
+    return NULL;
+}
+
+static void gd_vc_free_labels(GtkDisplayState *s)
+{
+    VirtualConsole *vc;
+    int i;
+
+    for (i = 0; i < s->nb_vcs; i++) {
+        vc = &s->vc[i];
+        if (vc->type == GD_VC_GFX &&
+            qemu_console_is_graphic(vc->gfx.dcl.con)) {
+            g_free(vc->label);
+            vc->label = NULL;
+        }
+    }
+}
+
+static void gd_connectors_init(GdkDisplay *dpy, GtkDisplayState *s)
+{
+    VirtualConsole *vc;
+    strList *conn;
+    gint monitor_num;
+    gboolean first_vc = TRUE;
+
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+                                   FALSE);
+    gd_vc_free_labels(s);
+    for (conn = s->opts->u.gtk.connectors; conn; conn = conn->next) {
+        vc = gd_next_gfx_vc(s);
+        if (!vc) {
+            break;
+        }
+        if (first_vc) {
+            vc->window = s->window;
+            first_vc = FALSE;
+        }
+
+        vc->label = g_strdup(conn->value);
+        monitor_num = gd_monitor_lookup(dpy, vc->label);
+        if (monitor_num >= 0) {
+            gd_window_show_on_monitor(dpy, vc, monitor_num);
+        } else {
+            if (vc->window) {
+                gdk_window_hide(gtk_widget_get_window(vc->window));
+            }
+            gd_set_ui_size(vc, 0, 0);
+        }
+    }
+}
+
 static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque)
 {
     GtkDisplayState *s = opaque;
@@ -2103,6 +2342,12 @@ static void gd_connect_signals(GtkDisplayState *s)
                      G_CALLBACK(gd_menu_grab_input), s);
     g_signal_connect(s->notebook, "switch-page",
                      G_CALLBACK(gd_change_page), s);
+    if (s->opts->u.gtk.has_connectors) {
+        g_signal_connect(gtk_widget_get_display(s->window), "monitor-added",
+                         G_CALLBACK(gd_monitor_add), s);
+        g_signal_connect(gtk_widget_get_display(s->window), "monitor-removed",
+                         G_CALLBACK(gd_monitor_remove), s);
+    }
 }
 
 static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
@@ -2472,6 +2717,9 @@ 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 (s->opts->u.gtk.has_connectors) {
+        gd_connectors_init(window_display, s);
+    }
     gd_clipboard_init(s);
 }
 
-- 
2.37.2



^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-11-18  2:06 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-18  1:44 [PATCH v2 0/6] ui/gtk: Add a new parameter to assign connectors/monitors (v2) Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 1/6] ui/gtk: Consider the scaling factor when getting the root coordinates Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 2/6] ui/gtk-gl-area: Don't forget to calculate the scaling factors in draw Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 3/6] ui/gtk: Handle relative mode events correctly with Wayland compositors Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 4/6] ui/gtk: Disable the scanout when a detached tab is closed Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 5/6] ui/gtk: Factor out tab window creation into a separate function Vivek Kasireddy
2022-11-18  1:44 ` [PATCH v2 6/6] ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs (v2) Vivek Kasireddy

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.