From nobody Mon Nov 25 07:26:51 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass header.i=@intel.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=intel.com ARC-Seal: i=1; a=rsa-sha256; t=1717181985; cv=none; d=zohomail.com; s=zohoarc; b=EQ4EsAt6F30HqkCl6lix1LQDIZstotateetw/DLYsqxWHqEycQ9hQiSke8Ol9WawSWVvW+YM8d8TKvd2s87XqlWxmRRVd7WQ/LaZFW/PDXs2NzQDLE3QRmQI4RvHkpiXE5rZ/ywx1DnBOGZUvmVbf0XLZ5Rs31fayfXE3belP8o= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1717181985; h=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To:Cc; bh=Q+n8nNtdGZ5rsF7rC7XKhobban2vAL1OivVfDxVd2Ak=; b=O+lekT8fR9qcXKSZqEgU0xDgEmHpMvS6klZX8dY1bRKJcGrs7qtDZW67sraf1Z7c5Ut1UkolZ/Ek0OiDjXP/oGuy2zxw1jJjk5P9MpmT2VZO5zCw8K8jv0CiyghR1PkT5f/kHVRcaSTSaNuFbwN26yCSYAvC+Wys8h099hSWFsU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=@intel.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17171819855941003.9860636111172; Fri, 31 May 2024 11:59:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sD7Sp-0004KS-EJ; Fri, 31 May 2024 14:58:55 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sD7So-0004K7-DB for qemu-devel@nongnu.org; Fri, 31 May 2024 14:58:54 -0400 Received: from mgamail.intel.com ([192.198.163.14]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sD7Sl-0002c3-I4 for qemu-devel@nongnu.org; Fri, 31 May 2024 14:58:54 -0400 Received: from fmviesa001.fm.intel.com ([10.60.135.141]) by fmvoesa108.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 31 May 2024 11:58:44 -0700 Received: from dongwonk-z390-aorus-ultra.fm.intel.com ([10.105.129.124]) by fmviesa001.fm.intel.com with ESMTP; 31 May 2024 11:58:44 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1717181931; x=1748717931; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=0jRuG99r3weW55HC+j9O07i4Eg0aFlyoqe0lac2duko=; b=P7+Rm3iFU+n1rdmFLXFyCwAdthoUI6w8slmWpVYaf7KbH8KTbrP78tff 2mEJeaOvoYXFHlUTJY9PlNDvWG9tnQ3fpCpxTIiruxZ/KpaxhYEsTSuWo Dq2q+MTMjVwgemyP+wGOksWy8qEUYdQTl0Sq6J7IJLuUJ9K5wMckGhUwY ow3OftOnARNGXhf15as0LaOzGPvIX1qs4qbzRX1rjBflP5hNh7f+VVqzt bBxLNqJvJ0yTQCDVeLVlPLF/RtkY7rfgWOR5Aba//ZFLA5jK4Xvv4rj8j 4Erd90oLirS08Z/v1z9+WU1S/Qzyb8MMuLvAbEmOEEViPrRav+pdLaS5T g==; X-CSE-ConnectionGUID: 2vOKQRP5T8aHz42j6/D8SQ== X-CSE-MsgGUID: 1SaEk3F2S5iW6n0i5XppXg== X-IronPort-AV: E=McAfee;i="6600,9927,11089"; a="13965363" X-IronPort-AV: E=Sophos;i="6.08,205,1712646000"; d="scan'208";a="13965363" X-CSE-ConnectionGUID: lfY4CEsSSAGMVXepbUTf8w== X-CSE-MsgGUID: EaINb3qNTNqtDSXmeMPTRA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.08,205,1712646000"; d="scan'208";a="67442165" From: dongwon.kim@intel.com To: qemu-devel@nongnu.org Subject: [PATCH RFC v2 2/2] ui/gtk: Add a new parameter to assign connectors/monitors Date: Fri, 31 May 2024 11:58:04 -0700 Message-Id: <20240531185804.119557-3-dongwon.kim@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240531185804.119557-1-dongwon.kim@intel.com> References: <20240531185804.119557-1-dongwon.kim@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=192.198.163.14; envelope-from=dongwon.kim@intel.com; helo=mgamail.intel.com X-Spam_score_int: -44 X-Spam_score: -4.5 X-Spam_bar: ---- X-Spam_report: (-4.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.085, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @intel.com) X-ZM-MESSAGEID: 1717181987018100004 From: Vivek Kasireddy 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 hot-plugged, the associated GTK window would be moved to it. If the monitor is disconnected or unplugged, the associated GTK window would be hidden and a relevant disconnect event would be sent to the Guest. One usage example is here: -display gtk,gl=3Don,connectors=3DDP-1:eDP-1:HDMI-2... With this, the first graphic virtual console will be placed on DP-1 display, second on eDP-1 and the third on HDMI-2. v2: Connectors is now in a string format that includes all connector names separated with a colon (previously it was a linked list) Code refactoring Cc: Gerd Hoffmann Cc: Marc-Andr=C3=A9 Lureau Cc: Daniel P. Berrang=C3=A9 Cc: Markus Armbruster Signed-off-by: Vivek Kasireddy Signed-off-by: Dongwon Kim --- qapi/ui.json | 25 ++++- include/ui/gtk.h | 1 + ui/gtk.c | 250 +++++++++++++++++++++++++++++++++++++++++++++++ qemu-options.hx | 4 + 4 files changed, 279 insertions(+), 1 deletion(-) diff --git a/qapi/ui.json b/qapi/ui.json index f610bce118..46ed9e76fc 100644 --- a/qapi/ui.json +++ b/qapi/ui.json @@ -1335,13 +1335,36 @@ # @show-menubar: Display the main window menubar. Defaults to "on". # (Since 8.0) # +# @connectors: A list of physical monitor/connector names where the +# GTK windows containing the respective GFX VCs (virtual consoles) +# are to be placed. The connector names should be provided as +# a string, with each name separated by a colon +# (e.g., DP-1:DP-2:HDMI-1:HDMI-2). Each connector name in the +# string will be used as a label for each VC in order. +# VCs can be skipped by leaving an empty spot between colons +# (e.g., DP-1::HDMI-2). If a connector name is not provided for +# a VC, that VC will not be placed on any physical display and +# the guest will see it as disconnected. If a valid connector name +# is provided for a VC but its display cable is not plugged in +# when the guest is launched, the VC will not be displayed initially. +# It will appear on the display when the cable is plugged in +# (hot-plug). If the cable is disconnected, the VC will be hidden +# and the guest will see its virtual display as disconnected. +# Multiple VCs can share the same connector name. In this case, +# all VCs with that name will be displayed on the same physical +# monitor. However, a single VC cannot have multiple connector +# names. +# +# (Since 9.1) +# # Since: 2.12 ## { 'struct' : 'DisplayGTK', 'data' : { '*grab-on-hover' : 'bool', '*zoom-to-fit' : 'bool', '*show-tabs' : 'bool', - '*show-menubar' : 'bool' } } + '*show-menubar' : 'bool', + '*connectors' : 'str' } } =20 ## # @DisplayEGLHeadless: diff --git a/include/ui/gtk.h b/include/ui/gtk.h index aa3d637029..3f78ee5996 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/ui/gtk.c b/ui/gtk.c index 3bc84090c8..dc356e1dcf 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -38,6 +38,7 @@ #include "qemu/cutils.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" +#include "qemu/option.h" =20 #include "ui/console.h" #include "ui/gtk.h" @@ -1446,6 +1447,248 @@ static void gd_menu_untabify(GtkMenuItem *item, voi= d *opaque) } } =20 +static void gd_ui_mon_enable(VirtualConsole *vc) +{ + GdkWindow *window =3D gtk_widget_get_window(vc->gfx.drawing_area); + QemuUIInfo info; + + if (!dpy_ui_info_supported(vc->gfx.dcl.con)) { + return; + } + + info =3D *dpy_get_ui_info(vc->gfx.dcl.con); + info.width =3D gdk_window_get_width(window); + info.height =3D gdk_window_get_height(window); + dpy_set_ui_info(vc->gfx.dcl.con, &info, false); +} + +static void gd_ui_mon_disable(VirtualConsole *vc) +{ + QemuUIInfo info; + + if (!dpy_ui_info_supported(vc->gfx.dcl.con)) { + return; + } + + info =3D *dpy_get_ui_info(vc->gfx.dcl.con); + info.width =3D 0; + info.height =3D 0; + dpy_set_ui_info(vc->gfx.dcl.con, &info, false); +} + +static void gd_window_show_on_monitor(GdkDisplay *dpy, VirtualConsole *vc, + gint monitor_num) +{ + GtkDisplayState *s =3D vc->s; + GdkMonitor *monitor =3D gdk_display_get_monitor(dpy, monitor_num); + GdkRectangle geometry; + if (!vc->window) { + gd_tab_window_create(vc); + } + + gdk_window_show(gtk_widget_get_window(vc->window)); + gd_update_windowsize(vc); + gdk_monitor_get_geometry(monitor, &geometry); + + gtk_window_move(GTK_WINDOW(vc->window), geometry.x, geometry.y); + + if (s->opts->has_full_screen && s->opts->full_screen) { + gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1); + gtk_window_fullscreen(GTK_WINDOW(vc->window)); + } else if ((s->window =3D=3D vc->window) && s->full_screen) { + gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s); + if (gtk_check_menu_item_get_active( + GTK_CHECK_MENU_ITEM(s->show_menubar_item))) { + gtk_widget_show(s->menu_bar); + } + s->full_screen =3D false; + } + + vc->monitor =3D monitor; + gd_ui_mon_enable(vc); + gd_update_cursor(vc); +} + +static int gd_monitor_lookup(GdkDisplay *dpy, char *label) +{ + GdkMonitor *monitor; + int total_monitors =3D gdk_display_get_n_monitors(dpy); + const char *model; + int i; + + for (i =3D 0; i < total_monitors; i++) { + monitor =3D gdk_display_get_monitor(dpy, i); + model =3D gdk_monitor_get_model(monitor); + if (!model) { + g_warning("retrieving connector name using\n" + "gdk_monitor_get_model isn't supported\n" + "please do not use connectors param in\n" + "current environment\n"); + return -1; + } + + if (monitor && !g_strcmp0(model, label)) { + return i; + } + } + return -1; +} + +static gboolean gd_vc_is_misplaced(GdkDisplay *dpy, GdkMonitor *monitor, + VirtualConsole *vc) +{ + GdkWindow *window =3D gtk_widget_get_window(vc->gfx.drawing_area); + GdkMonitor *mon =3D gdk_display_get_monitor_at_window(dpy, window); + const char *model =3D gdk_monitor_get_model(monitor); + + if (!vc->monitor) { + if (!g_strcmp0(model, vc->label)) { + return TRUE; + } + } else { + if (mon && mon !=3D vc->monitor) { + return TRUE; + } + } + return FALSE; +} + +static void gd_vc_windows_reposition(GdkDisplay *dpy, GtkDisplayState *s) +{ + VirtualConsole *vc; + GdkMonitor *monitor; + gint monitor_num; + int i; + + for (i =3D 0; i < s->nb_vcs; i++) { + vc =3D &s->vc[i]; + if (vc->label) { + monitor_num =3D gd_monitor_lookup(dpy, vc->label); + if (monitor_num >=3D 0) { + monitor =3D gdk_display_get_monitor(dpy, monitor_num); + if (gd_vc_is_misplaced(dpy, monitor, vc)) { + gd_window_show_on_monitor(dpy, vc, monitor_num); + } + } else if (vc->monitor) { + vc->monitor =3D NULL; + gd_ui_mon_disable(vc); + + /* if window exist, hide it */ + if (vc->window) { + gdk_window_hide(gtk_widget_get_window(vc->window)); + } + } + } + } +} + +static void gd_monitors_reset_timer(void *opaque) +{ + GtkDisplayState *s =3D opaque; + GdkDisplay *dpy =3D gdk_display_get_default(); + + gd_vc_windows_reposition(dpy, s); +} + +static void gd_monitors_changed(GdkScreen *scr, void *opaque) +{ + GtkDisplayState *s =3D opaque; + QEMUTimer *mon_reset_timer; + + /* This timer setup ensures the compositor finishes placing + * all QEMU windows after a display hot plug event + * before QEMU rearranges the windows based on connectors + * setting. + */ + mon_reset_timer =3D timer_new_ms(QEMU_CLOCK_REALTIME, + gd_monitors_reset_timer, s); + timer_mod(mon_reset_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000); +} + +static VirtualConsole *gd_next_gfx_vc(GtkDisplayState *s) +{ + VirtualConsole *vc; + int i; + + for (i =3D 0; i < s->nb_vcs; i++) { + vc =3D &s->vc[i]; + if (vc->type =3D=3D 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 =3D 0; i < s->nb_vcs; i++) { + vc =3D &s->vc[i]; + if (vc->type =3D=3D GD_VC_GFX && + qemu_console_is_graphic(vc->gfx.dcl.con)) { + g_free(vc->label); + vc->label =3D NULL; + } + } +} + +static void gd_connectors_init(GdkDisplay *dpy, GtkDisplayState *s) +{ + VirtualConsole *vc; + gint monitor_num; + gboolean first_vc =3D TRUE; + char *conn =3D s->opts->u.gtk.connectors; + char *this, *ptr; + + 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); + + ptr =3D conn; + + while (ptr < conn + strlen(conn)) { + this =3D strchr(ptr, ':'); + + vc =3D gd_next_gfx_vc(s); + if (!vc) { + break; + } + if (first_vc) { + vc->window =3D s->window; + first_vc =3D FALSE; + } + + if (this =3D=3D NULL) { + vc->label =3D g_strdup(ptr); + } else { + vc->label =3D g_strndup(ptr, this - ptr); + } + + monitor_num =3D gd_monitor_lookup(dpy, vc->label); + if (monitor_num >=3D 0) { + gd_window_show_on_monitor(dpy, vc, monitor_num); + } else { + gd_ui_mon_disable(vc); + + if (vc->window) { + gdk_window_hide(gtk_widget_get_window(vc->window)); + } + } + + if (this =3D=3D NULL) { + break; + } else { + ptr =3D this + 1; + } + } +} + static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque) { GtkDisplayState *s =3D opaque; @@ -2102,6 +2345,10 @@ 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.connectors) { + g_signal_connect(gdk_screen_get_default(), "monitors-changed", + G_CALLBACK(gd_monitors_changed), s); + } } =20 static GtkWidget *gd_create_menu_machine(GtkDisplayState *s) @@ -2489,6 +2736,9 @@ static void gtk_display_init(DisplayState *ds, Displa= yOptions *opts) opts->u.gtk.show_tabs) { gtk_menu_item_activate(GTK_MENU_ITEM(s->show_tabs_item)); } + if (s->opts->u.gtk.connectors) { + gd_connectors_init(window_display, s); + } #ifdef CONFIG_GTK_CLIPBOARD gd_clipboard_init(s); #endif /* CONFIG_GTK_CLIPBOARD */ diff --git a/qemu-options.hx b/qemu-options.hx index 8ca7f34ef0..ebc7181472 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2099,6 +2099,7 @@ DEF("display", HAS_ARG, QEMU_OPTION_display, "-display gtk[,full-screen=3Don|off][,gl=3Don|off][,grab-on-hover=3Don= |off]\n" " [,show-tabs=3Don|off][,show-cursor=3Don|off][,window-clos= e=3Don|off]\n" " [,show-menubar=3Don|off][,zoom-to-fit=3Don|off]\n" + " [,connectors=3Dconn1:conn2:...:connN]]\n" #endif #if defined(CONFIG_VNC) "-display vnc=3D[,]\n" @@ -2195,6 +2196,9 @@ SRST ``zoom-to-fit=3Don|off`` : Expand video output to the window size, defaults to "off" =20 + ``connectors=3Dconn1:conn2...`` : VC to connector mappings to disp= lay the VC + window on a specific monitor + ``curses[,charset=3D]`` Display video output via curses. For graphics device models which support a text mode, QEMU can display this output using a --=20 2.34.1