[virt-tools-list] [PATCH 1/2] Add ability to define custom display->monitor mapping per vm

Jonathon Jongsma jjongsma at redhat.com
Fri Nov 8 21:52:09 UTC 2013


Fullscreen mode generally just assigns display 1 to monitor 1, display 2 to
monitor 2, etc. For custom setups, you can define a monitor mapping in the
settings keyfile per-vm. This requires a vm uuid (so only works in virt-viewer
or on versions of spice-server that send the uuid over the wire).  The format is
pretty basic:

    [6485b20f-e9da-614c-72b0-60a7857e7886]
    monitor-mapping=2;3

The group name ("6485b20f-e9da-614c-72b0-60a7857e7886") is the uuid id of the
vm. This group has a single key: monitor-mapping. This key is an array of
integers describing the order in which to assign the monitors to a guest
display. Any monitors that are not listed in this array will not be configured
at startup.  For instance:

    monitor-mapping=2;1

will attempt to configure 2 displays on the guest and assign the first display
to monitor 2 and the second display to monitor 1.

    monitor-mapping=2

will only configure a single display on the guest and place it on the second
monitor.  Any monitor numbers listed in the keyfile are greater than the number
of monitors that are physically present, they will be ignored.
---
 src/virt-viewer-app.c           | 111 ++++++++++++++++++++++++++++++++++------
 src/virt-viewer-app.h           |   3 ++
 src/virt-viewer-session-spice.c |  63 ++++++++++++++++++-----
 src/virt-viewer-window.c        |   7 +++
 src/virt-viewer.c               |   7 +++
 5 files changed, 163 insertions(+), 28 deletions(-)

diff --git a/src/virt-viewer-app.c b/src/virt-viewer-app.c
index cdf6104..7962dc5 100644
--- a/src/virt-viewer-app.c
+++ b/src/virt-viewer-app.c
@@ -108,6 +108,7 @@ struct _VirtViewerAppPrivate {
     VirtViewerWindow *main_window;
     GtkWidget *main_notebook;
     GHashTable *windows;
+    GArray *initial_display_map;
     gchar *clipboard;
 
     gboolean direct;
@@ -273,6 +274,96 @@ virt_viewer_app_quit(VirtViewerApp *self)
     gtk_main_quit();
 }
 
+gint virt_viewer_app_get_n_initial_displays(VirtViewerApp* self)
+{
+    if (self->priv->initial_display_map)
+        return self->priv->initial_display_map->len;
+
+    return gdk_screen_get_n_monitors(gdk_screen_get_default());
+}
+
+gint virt_viewer_app_get_initial_monitor_for_display(VirtViewerApp* self, gint display)
+{
+    gint monitor = -1;
+
+    if (self->priv->initial_display_map) {
+        if (display < self->priv->initial_display_map->len)
+            monitor = g_array_index(self->priv->initial_display_map, gint, display);
+    } else {
+        monitor = display;
+    }
+
+    return monitor;
+}
+
+static void
+app_window_try_fullscreen(VirtViewerApp *self G_GNUC_UNUSED,
+                          VirtViewerWindow *win, gint nth)
+{
+    GdkScreen *screen = gdk_screen_get_default();
+
+    if (nth >= gdk_screen_get_n_monitors(screen)) {
+        DEBUG_LOG("skipping display %d", nth);
+        return;
+    }
+
+    virt_viewer_window_enter_fullscreen(win, nth);
+}
+
+
+void virt_viewer_app_set_uuid_string(VirtViewerApp* self, const gchar* uuid_string)
+{
+    GArray* mapping = NULL;
+    GError* error = NULL;
+    gsize ndisplays = 0;
+    gint* displays = NULL;
+    gint nmonitors = gdk_screen_get_n_monitors(gdk_screen_get_default());
+
+    DEBUG_LOG("%s: UUID changed to %s", G_STRFUNC, uuid_string);
+
+    displays = g_key_file_get_integer_list(self->priv->config,
+                                           uuid_string, "monitor-mapping", &ndisplays, &error);
+    if (error) {
+        if (error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
+            g_warning("Error reading monitor assignments: %s", error->message);
+        g_clear_error(&error);
+    } else {
+        int i = 0;
+        mapping = g_array_sized_new(FALSE, FALSE, sizeof(displays[0]), ndisplays);
+        // config file format is 1-based, not 0-based
+        for (i = 0; i < ndisplays; i++) {
+            gint val = displays[i] - 1;
+
+            // sanity check
+            if (val >= nmonitors)
+                g_warning("Initial monitor #%i for display #%i does not exist, skipping...", val, i);
+            else
+                g_array_append_val(mapping, val);
+        }
+        g_free(displays);
+    }
+
+    if (self->priv->initial_display_map)
+        g_array_unref(self->priv->initial_display_map);
+
+    self->priv->initial_display_map = mapping;
+
+    // if we're changing our initial display map, move any existing windows to
+    // the appropriate monitors according to the per-vm configuration
+    if (mapping && self->priv->fullscreen) {
+        GHashTableIter iter;
+        gpointer value;
+        gint i = 0;
+
+        g_hash_table_iter_init(&iter, self->priv->windows);
+        while (g_hash_table_iter_next(&iter, NULL, &value)) {
+            gint monitor = virt_viewer_app_get_initial_monitor_for_display(self, i);
+            app_window_try_fullscreen(self, VIRT_VIEWER_WINDOW(value), monitor);
+            i++;
+        }
+    }
+}
+
 void
 virt_viewer_app_maybe_quit(VirtViewerApp *self, VirtViewerWindow *window)
 {
@@ -648,20 +739,6 @@ viewer_window_focus_out_cb(GtkWindow *window G_GNUC_UNUSED,
     return FALSE;
 }
 
-static void
-app_window_try_fullscreen(VirtViewerApp *self G_GNUC_UNUSED,
-                          VirtViewerWindow *win, gint nth)
-{
-    GdkScreen *screen = gdk_screen_get_default();
-
-    if (nth >= gdk_screen_get_n_monitors(screen)) {
-        DEBUG_LOG("skipping display %d", nth);
-        return;
-    }
-
-    virt_viewer_window_enter_fullscreen(win, nth);
-}
-
 static VirtViewerWindow*
 virt_viewer_app_window_new(VirtViewerApp *self, gint nth)
 {
@@ -678,7 +755,8 @@ virt_viewer_app_window_new(VirtViewerApp *self, gint nth)
         virt_viewer_window_set_zoom_level(window, virt_viewer_window_get_zoom_level(self->priv->main_window));
     virt_viewer_app_set_nth_window(self, nth, window);
     if (self->priv->fullscreen)
-        app_window_try_fullscreen(self, window, nth);
+        app_window_try_fullscreen(self, window,
+                                  virt_viewer_app_get_initial_monitor_for_display(self, nth));
 
     w = virt_viewer_window_get_window(window);
     g_signal_connect(w, "hide", G_CALLBACK(viewer_window_visible_cb), self);
@@ -1445,6 +1523,7 @@ virt_viewer_app_dispose (GObject *object)
     g_free(priv->config_file);
     priv->config_file = NULL;
     g_clear_pointer(&priv->config, g_key_file_free);
+    g_clear_pointer(&priv->initial_display_map, g_array_unref);
 
     virt_viewer_app_free_connect_info(self);
 
@@ -1883,8 +1962,8 @@ static void fullscreen_cb(gpointer key,
                           gpointer value,
                           gpointer user_data)
 {
-    gint nth = *(gint*)key;
     FullscreenOptions *options = (FullscreenOptions *)user_data;
+    gint nth = virt_viewer_app_get_initial_monitor_for_display(options->app, *(gint*)key);
     VirtViewerWindow *vwin = VIRT_VIEWER_WINDOW(value);
 
     DEBUG_LOG("fullscreen display %d: %d", nth, options->fullscreen);
diff --git a/src/virt-viewer-app.h b/src/virt-viewer-app.h
index f72f5b3..737b0af 100644
--- a/src/virt-viewer-app.h
+++ b/src/virt-viewer-app.h
@@ -100,6 +100,9 @@ gboolean virt_viewer_app_get_fullscreen(VirtViewerApp *app);
 gboolean virt_viewer_app_get_fullscreen_auto_conf(VirtViewerApp *app);
 const GOptionEntry* virt_viewer_app_get_options(void);
 void virt_viewer_app_clear_hotkeys(VirtViewerApp *app);
+gint virt_viewer_app_get_n_initial_displays(VirtViewerApp* self);
+gint virt_viewer_app_get_initial_monitor_for_display(VirtViewerApp* self, gint display);
+void virt_viewer_app_set_uuid_string(VirtViewerApp* self, const gchar* uuid_string);
 
 G_END_DECLS
 
diff --git a/src/virt-viewer-session-spice.c b/src/virt-viewer-session-spice.c
index b42d48e..41467cf 100644
--- a/src/virt-viewer-session-spice.c
+++ b/src/virt-viewer-session-spice.c
@@ -28,6 +28,7 @@
 #include <glib/gi18n.h>
 
 #include <spice-option.h>
+#include <spice-util.h>
 #include <usb-device-widget.h>
 #include "virt-viewer-file.h"
 #include "virt-viewer-util.h"
@@ -579,8 +580,6 @@ agent_connected_changed(SpiceChannel *cmain G_GNUC_UNUSED,
 {
     // this will force refresh of application menu
     g_signal_emit_by_name(self, "session-display-updated");
-
-    virt_viewer_session_spice_fullscreen_auto_conf(self);
 }
 
 static void
@@ -677,7 +676,6 @@ virt_viewer_session_spice_channel_new(SpiceSession *s,
         self->priv->main_channel = SPICE_MAIN_CHANNEL(channel);
 
         g_signal_connect(channel, "notify::agent-connected", G_CALLBACK(agent_connected_changed), self);
-        virt_viewer_session_spice_fullscreen_auto_conf(self);
     }
 
     if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
@@ -709,6 +707,14 @@ virt_viewer_session_spice_channel_new(SpiceSession *s,
     self->priv->channel_count++;
 }
 
+static void
+property_notify_do_auto_conf(GObject *gobject G_GNUC_UNUSED,
+                             GParamSpec *pspec G_GNUC_UNUSED,
+                             VirtViewerSessionSpice *self)
+{
+    virt_viewer_session_spice_fullscreen_auto_conf(self);
+}
+
 static gboolean
 virt_viewer_session_spice_fullscreen_auto_conf(VirtViewerSessionSpice *self)
 {
@@ -718,6 +724,7 @@ virt_viewer_session_spice_fullscreen_auto_conf(VirtViewerSessionSpice *self)
     GdkRectangle dest;
     gboolean auto_conf, agent_connected;
     gint i;
+    gsize ndisplays = 0;
 
     app = virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self));
     g_return_val_if_fail(VIRT_VIEWER_IS_APP(app), TRUE);
@@ -739,18 +746,23 @@ virt_viewer_session_spice_fullscreen_auto_conf(VirtViewerSessionSpice *self)
     g_object_get(cmain, "agent-connected", &agent_connected, NULL);
     if (!agent_connected) {
         DEBUG_LOG("Agent not connected, skipping autoconf");
+        g_signal_connect(cmain, "notify::agent-connected", G_CALLBACK(property_notify_do_auto_conf), self);
         return FALSE;
     }
 
-    DEBUG_LOG("Performing full screen auto-conf, %d host monitors",
-              gdk_screen_get_n_monitors(screen));
+
     g_object_set(G_OBJECT(cmain),
                  "disable-display-position", FALSE,
                  "disable-display-align", TRUE,
                  NULL);
     spice_main_set_display_enabled(cmain, -1, FALSE);
-    for (i = 0; i < gdk_screen_get_n_monitors(screen); i++) {
-        gdk_screen_get_monitor_geometry(screen, i, &dest);
+
+    ndisplays = virt_viewer_app_get_n_initial_displays(app);
+    DEBUG_LOG("Performing full screen auto-conf, %zd host monitors", ndisplays);
+
+    for (i = 0; i < ndisplays; i++) {
+        gint j = virt_viewer_app_get_initial_monitor_for_display(app, i);
+        gdk_screen_get_monitor_geometry(screen, j, &dest);
         DEBUG_LOG("Set SPICE display %d to (%d,%d)-(%dx%d)",
                   i, dest.x, dest.y, dest.width, dest.height);
         spice_main_set_display(cmain, i, dest.x, dest.y, dest.width, dest.height);
@@ -802,11 +814,34 @@ virt_viewer_session_spice_channel_destroy(G_GNUC_UNUSED SpiceSession *s,
         g_signal_emit_by_name(self, "session-disconnected");
 }
 
+#define UUID_LEN 16
 static void
-fullscreen_changed(GObject *gobject G_GNUC_UNUSED,
-                   GParamSpec *pspec G_GNUC_UNUSED,
-                   VirtViewerSessionSpice *self)
-{
+uuid_changed(GObject *gobject G_GNUC_UNUSED,
+             GParamSpec *pspec G_GNUC_UNUSED,
+             VirtViewerSessionSpice *self)
+{
+    guint8* uuid = NULL;
+    VirtViewerApp* app = virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self));
+
+    g_object_get(self->priv->session, "uuid", &uuid, NULL);
+    if (uuid) {
+        int i;
+        gboolean uuid_empty = TRUE;
+
+        for (i = 0; i < UUID_LEN; i++) {
+            if (uuid[i] != 0) {
+                uuid_empty = FALSE;
+                break;
+            }
+        }
+
+        if (!uuid_empty) {
+            gchar* uuid_str = spice_uuid_to_string(uuid);
+            virt_viewer_app_set_uuid_string(app, uuid_str);
+            g_free(uuid_str);
+        }
+    }
+
     virt_viewer_session_spice_fullscreen_auto_conf(self);
 }
 
@@ -820,7 +855,11 @@ virt_viewer_session_spice_new(VirtViewerApp *app, GtkWindow *main_window)
     create_spice_session(self);
     self->priv->main_window = g_object_ref(main_window);
 
-    g_signal_connect(app, "notify::fullscreen", G_CALLBACK(fullscreen_changed),  self);
+    g_signal_connect(app, "notify::fullscreen", G_CALLBACK(property_notify_do_auto_conf), self);
+
+    /* notify::uuid is guaranteed to be emitted during connection startup even
+     * if the server is too old to support sending uuid */
+    g_signal_connect(self->priv->session, "notify::uuid", G_CALLBACK(uuid_changed), self);
 
     return VIRT_VIEWER_SESSION(self);
 }
diff --git a/src/virt-viewer-window.c b/src/virt-viewer-window.c
index 5ce1d98..468e61c 100644
--- a/src/virt-viewer-window.c
+++ b/src/virt-viewer-window.c
@@ -496,7 +496,9 @@ virt_viewer_window_leave_fullscreen(VirtViewerWindow *self)
     if (!priv->fullscreen)
         return;
 
+    g_signal_handlers_block_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
     gtk_check_menu_item_set_active(check, FALSE);
+    g_signal_handlers_unblock_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
     priv->fullscreen = FALSE;
     priv->fullscreen_monitor = -1;
     if (priv->display) {
@@ -528,6 +530,9 @@ virt_viewer_window_enter_fullscreen(VirtViewerWindow *self, gint monitor)
     GtkWidget *menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "top-menu"));
     GtkCheckMenuItem *check = GTK_CHECK_MENU_ITEM(gtk_builder_get_object(priv->builder, "menu-view-fullscreen"));
 
+    if (priv->fullscreen && priv->fullscreen_monitor != monitor)
+        virt_viewer_window_leave_fullscreen(self);
+
     if (priv->fullscreen)
         return;
 
@@ -539,7 +544,9 @@ virt_viewer_window_enter_fullscreen(VirtViewerWindow *self, gint monitor)
         return;
     }
 
+    g_signal_handlers_block_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
     gtk_check_menu_item_set_active(check, TRUE);
+    g_signal_handlers_unblock_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
     gtk_widget_hide(menu);
     gtk_widget_show(priv->toolbar);
     ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(priv->layout), TRUE);
diff --git a/src/virt-viewer.c b/src/virt-viewer.c
index ae25fc6..e1553fd 100644
--- a/src/virt-viewer.c
+++ b/src/virt-viewer.c
@@ -530,6 +530,7 @@ virt_viewer_initial_connect(VirtViewerApp *app, GError **error)
     gboolean ret = FALSE;
     VirtViewer *self = VIRT_VIEWER(app);
     VirtViewerPrivate *priv = self->priv;
+    char uuid_string[VIR_UUID_STRING_BUFLEN];
 
     DEBUG_LOG("initial connect");
 
@@ -555,6 +556,12 @@ virt_viewer_initial_connect(VirtViewerApp *app, GError **error)
         }
     }
 
+    if (virDomainGetUUIDString(dom, uuid_string) < 0) {
+        DEBUG_LOG("Couldn't get uuid from libvirt");
+    } else {
+        virt_viewer_app_set_uuid_string(app, uuid_string);
+    }
+
     virt_viewer_app_show_status(app, _("Checking guest domain status"));
     if (virDomainGetInfo(dom, &info) < 0) {
         DEBUG_LOG("Cannot get guest state");
-- 
1.8.3.1




More information about the virt-tools-list mailing list