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 _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list