From: Ondrej Holy <oholy@xxxxxxxxxx> Seamless mode is a way to use guest applications directly on the client system desktop side-by-side with client applications. Add toggle button to enable/disable seamless mode in spicy. If seamless mode is enabled, client receive list of visible areas periodicaly. Spice widget draw only visible areas (rest is transparent). Spicy hide window decorations and show only maximized spice widget. Spicy also set window shape to match visible areas. Window shape is slightly bigger then visible areas to avoid losing focus when resizing. https://bugs.freedesktop.org/show_bug.cgi?id=39238 Co-authors: Lukáš Venhoda, Jakub Janků --- src/Makefile.am | 2 +- src/channel-main.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++ src/channel-main.h | 4 ++ src/map-file | 3 ++ src/spice-glib-sym-file | 2 + src/spice-gtk-sym-file | 1 + src/spice-session.c | 1 + src/spice-widget-cairo.c | 17 ++++++- src/spice-widget-priv.h | 2 + src/spice-widget.c | 118 ++++++++++++++++++++++++++++++++++++++++++++ src/spice-widget.h | 1 + tools/spicy.c | 90 +++++++++++++++++++++++++++++++++ 12 files changed, 365 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 5430d84..2a915de 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -552,7 +552,7 @@ gtk_introspection_files = \ $(NULL) SpiceClientGLib-2.0.gir: libspice-client-glib-2.0.la -SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0 +SpiceClientGLib_2_0_gir_INCLUDES = GObject-2.0 Gio-2.0 Gdk-3.0 SpiceClientGLib_2_0_gir_CFLAGS = $(SPICE_COMMON_CPPFLAGS) SpiceClientGLib_2_0_gir_LIBS = libspice-client-glib-2.0.la SpiceClientGLib_2_0_gir_FILES = $(glib_introspection_files) diff --git a/src/channel-main.c b/src/channel-main.c index 4edd575..0844a22 100644 --- a/src/channel-main.c +++ b/src/channel-main.c @@ -21,6 +21,7 @@ #include <spice/vd_agent.h> #include <glib/gstdio.h> #include <glib/gi18n-lib.h> +#include <gdk/gdk.h> #include "spice-client.h" #include "spice-common.h" @@ -119,6 +120,10 @@ struct _SpiceMainChannelPrivate { gboolean agent_volume_playback_sync; gboolean agent_volume_record_sync; GCancellable *cancellable_volume_info; + + GMutex seamless_mode_lock; + GList *seamless_mode_list; + gboolean seamless_mode; }; struct spice_migrate { @@ -151,6 +156,8 @@ enum { PROP_DISABLE_DISPLAY_POSITION, PROP_DISABLE_DISPLAY_ALIGN, PROP_MAX_CLIPBOARD, + PROP_SEAMLESS_MODE, + PROP_SEAMLESS_MODE_LIST, }; /* Signals */ @@ -209,6 +216,8 @@ static const char *agent_msg_types[] = { [ VD_AGENT_CLIPBOARD_REQUEST ] = "clipboard request", [ VD_AGENT_CLIPBOARD_RELEASE ] = "clipboard release", [ VD_AGENT_AUDIO_VOLUME_SYNC ] = "volume-sync", + [ VD_AGENT_SEAMLESS_MODE ] = "seamless mode", + [ VD_AGENT_SEAMLESS_MODE_LIST ] = "seamless mode list", }; static const char *agent_caps[] = { @@ -224,6 +233,7 @@ static const char *agent_caps[] = { [ VD_AGENT_CAP_GUEST_LINEEND_CRLF ] = "line-end crlf", [ VD_AGENT_CAP_MAX_CLIPBOARD ] = "max-clipboard", [ VD_AGENT_CAP_AUDIO_VOLUME_SYNC ] = "volume-sync", + [ VD_AGENT_CAP_SEAMLESS_MODE ] = "seamless mode", [ VD_AGENT_CAP_MONITORS_CONFIG_POSITION ] = "monitors config position", [ VD_AGENT_CAP_FILE_XFER_DISABLED ] = "file transfer disabled", }; @@ -258,6 +268,7 @@ static void spice_main_channel_init(SpiceMainChannel *channel) c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal); c->flushing = g_hash_table_new(g_direct_hash, g_direct_equal); c->cancellable_volume_info = g_cancellable_new(); + g_mutex_init(&c->seamless_mode_lock); spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel)); c->requested_mouse_mode = SPICE_MOUSE_MODE_CLIENT; @@ -312,6 +323,12 @@ static void spice_main_get_property(GObject *object, case PROP_MAX_CLIPBOARD: g_value_set_int(value, spice_main_get_max_clipboard(self)); break; + case PROP_SEAMLESS_MODE: + g_value_set_boolean(value, c->seamless_mode); + break; + case PROP_SEAMLESS_MODE_LIST: + g_value_set_pointer(value, spice_main_get_seamless_mode_list(self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -349,6 +366,9 @@ static void spice_main_set_property(GObject *gobject, guint prop_id, case PROP_MAX_CLIPBOARD: spice_main_set_max_clipboard(self, g_value_get_int(value)); break; + case PROP_SEAMLESS_MODE: + spice_main_set_seamless_mode(self, g_value_get_boolean(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); break; @@ -380,6 +400,10 @@ static void spice_main_channel_dispose(GObject *obj) g_cancellable_cancel(c->cancellable_volume_info); g_clear_object(&c->cancellable_volume_info); + g_mutex_clear(&c->seamless_mode_lock); + g_list_free_full(c->seamless_mode_list, g_free); + c->seamless_mode_list = NULL; + if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose) G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj); } @@ -598,6 +622,23 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass) G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property + (gobject_class, PROP_SEAMLESS_MODE, + g_param_spec_boolean("seamless-mode", + "Seamless mode", + "Seamless mode", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (gobject_class, PROP_SEAMLESS_MODE_LIST, + g_param_spec_pointer ("seamless-mode-list", + "Seamless mode list", + "Seamless mode window list", + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + /* TODO use notify instead */ /** * SpiceMainChannel::main-mouse-update: @@ -1320,6 +1361,7 @@ static void agent_announce_caps(SpiceMainChannel *channel) VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION); + VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_SEAMLESS_MODE); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG_POSITION); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS); @@ -2051,6 +2093,30 @@ static void main_agent_handle_msg(SpiceChannel *channel, case VD_AGENT_FILE_XFER_STATUS: main_agent_handle_xfer_status(self, payload); break; + case VD_AGENT_SEAMLESS_MODE_LIST: + { + VDAgentSeamlessModeList *list = payload; + int i; + + g_mutex_lock(&self->priv->seamless_mode_lock); + + g_list_free_full(c->seamless_mode_list, g_free); + c->seamless_mode_list = NULL; + + for (i = 0; i < list->num_of_windows; i++) { + VDAgentSeamlessModeWindow *win; + + win = g_new0(VDAgentSeamlessModeWindow, 1); + memcpy(win, &(list->windows[i]), sizeof(VDAgentSeamlessModeWindow)); + + c->seamless_mode_list = g_list_prepend(c->seamless_mode_list, win); + } + + g_mutex_unlock(&self->priv->seamless_mode_lock); + + g_coroutine_object_notify(G_OBJECT(self), "seamless-mode-list"); + break; + } default: g_warning("unhandled agent message type: %u (%s), size %u", msg->type, NAME(agent_msg_types, msg->type), msg->size); @@ -3185,3 +3251,63 @@ gboolean spice_main_file_copy_finish(SpiceMainChannel *channel, return g_task_propagate_boolean(task, error); } + +/** + * spice_main_set_seamless_mode: + * @channel: a #SpiceMainChannel + * @enabled: whether seamless mode is enabled + * + * Send message to agent to enable/disable seamless mode list updates. + **/ +void spice_main_set_seamless_mode(SpiceMainChannel *channel, + gboolean enabled) +{ + SpiceMainChannelPrivate *c = channel->priv; + VDAgentSeamlessMode msg; + + g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel)); + g_return_if_fail(spice_main_get_seamless_mode_supported(channel)); + + if (c->seamless_mode == enabled) + return; + + c->seamless_mode = msg.enabled = enabled; + agent_msg_queue(channel, VD_AGENT_SEAMLESS_MODE, sizeof(msg), &msg); + spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE); +} + +/** + * spice_main_get_seamless_mode_supported: + * @channel: a #SpiceMainChannel + * + * Returns: %TRUE if seamless mode is supported by the agent. + **/ +gboolean spice_main_get_seamless_mode_supported(SpiceMainChannel *channel) +{ + g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE); + + return test_agent_cap(channel, VD_AGENT_CAP_SEAMLESS_MODE); +} + +/** + * spice_main_get_seamless_mode_list: + * @channel: a #SpiceMainChannel + * + * Seamless mode has to be enabled using spice_main_set_seamless_mode() to get + * updated list of visible areas. + * + * Returns: (transfer full) (element-type GdkRectangle): a newly allocated + * list of GdkRectangle structs. + **/ +GList *spice_main_get_seamless_mode_list(SpiceMainChannel *channel) +{ + GList *list; + + g_mutex_lock(&channel->priv->seamless_mode_lock); + list = g_list_copy_deep(channel->priv->seamless_mode_list, + (GCopyFunc)g_memdup, + (gpointer)sizeof(GdkRectangle)); + g_mutex_unlock(&channel->priv->seamless_mode_lock); + + return list; +} diff --git a/src/channel-main.h b/src/channel-main.h index 2bb6d10..50d7615 100644 --- a/src/channel-main.h +++ b/src/channel-main.h @@ -111,6 +111,10 @@ G_DEPRECATED_FOR(spice_main_clipboard_selection_request) void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type); #endif +void spice_main_set_seamless_mode(SpiceMainChannel *channel, gboolean enabled); +gboolean spice_main_get_seamless_mode_supported(SpiceMainChannel *channel); +GList *spice_main_get_seamless_mode_list(SpiceMainChannel *channel); + G_END_DECLS #endif /* __SPICE_CLIENT_MAIN_CHANNEL_H__ */ diff --git a/src/map-file b/src/map-file index 668ff41..dcfd16d 100644 --- a/src/map-file +++ b/src/map-file @@ -31,6 +31,7 @@ spice_display_get_primary; spice_display_get_type; spice_display_gl_draw_done; spice_display_key_event_get_type; +spice_display_update_seamless_mode; spice_display_mouse_ungrab; spice_display_new; spice_display_new_with_monitor; @@ -79,6 +80,8 @@ spice_main_clipboard_selection_request; spice_main_file_copy_async; spice_main_file_copy_finish; spice_main_request_mouse_mode; +spice_main_get_seamless_mode_list; +spice_main_get_seamless_mode_supported; spice_main_send_monitor_config; spice_main_set_display; spice_main_set_display_enabled; diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file index e061744..08c8c60 100644 --- a/src/spice-glib-sym-file +++ b/src/spice-glib-sym-file @@ -58,6 +58,8 @@ spice_main_clipboard_selection_request spice_main_file_copy_async spice_main_file_copy_finish spice_main_request_mouse_mode +spice_main_get_seamless_mode_list +spice_main_get_seamless_mode_supported spice_main_send_monitor_config spice_main_set_display spice_main_set_display_enabled diff --git a/src/spice-gtk-sym-file b/src/spice-gtk-sym-file index e52334b..4c6a640 100644 --- a/src/spice-gtk-sym-file +++ b/src/spice-gtk-sym-file @@ -2,6 +2,7 @@ spice_display_get_grab_keys spice_display_get_pixbuf spice_display_get_type spice_display_key_event_get_type +spice_display_update_seamless_mode spice_display_mouse_ungrab spice_display_new spice_display_new_with_monitor diff --git a/src/spice-session.c b/src/spice-session.c index 6f8cf5e..c6ddaca 100644 --- a/src/spice-session.c +++ b/src/spice-session.c @@ -22,6 +22,7 @@ #ifdef G_OS_UNIX #include <gio/gunixsocketaddress.h> #endif +#include <spice/vd_agent.h> #include "common/ring.h" #include "spice-client.h" diff --git a/src/spice-widget-cairo.c b/src/spice-widget-cairo.c index 0e84649..1063e4c 100644 --- a/src/spice-widget-cairo.c +++ b/src/spice-widget-cairo.c @@ -101,12 +101,27 @@ void spice_cairo_draw_event(SpiceDisplay *display, cairo_t *cr) /* Need to set a real solid color, because the default is usually transparent these days, and non-double buffered windows can't render transparently */ - cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_source_rgba (cr, 0, 0, 0, 0); cairo_fill(cr); /* Draw the display */ if (d->canvas.surface) { + gboolean seamless_mode; + GList *list, *l; + cairo_translate(cr, x, y); + + list = spice_main_get_seamless_mode_list(d->main); + g_object_get(d->main, "seamless-mode", &seamless_mode, NULL); + if (seamless_mode && list) { + for (l = list; l != NULL; l = l->next) { + GdkRectangle *r = l->data; + cairo_rectangle(cr, r->x, r->y, r->width, r->height); + } + cairo_clip(cr); + } + g_list_free_full(list, g_free); + cairo_rectangle(cr, 0, 0, w, h); cairo_scale(cr, s, s); if (!d->canvas.convert) diff --git a/src/spice-widget-priv.h b/src/spice-widget-priv.h index ea7ed8e..4d5c782 100644 --- a/src/spice-widget-priv.h +++ b/src/spice-widget-priv.h @@ -87,6 +87,7 @@ struct _SpiceDisplayPrivate { gboolean allow_scaling; gboolean only_downscale; gboolean disable_inputs; + gboolean seamless_mode; SpiceSession *session; SpiceGtkSession *gtk_session; @@ -120,6 +121,7 @@ struct _SpiceDisplayPrivate { gboolean *activeseq; /* the currently pressed keys */ gboolean seq_pressed; gboolean keyboard_grab_released; + gboolean mouse_button_down; gint mark; #ifdef WIN32 HHOOK keyboard_hook; diff --git a/src/spice-widget.c b/src/spice-widget.c index 6f4abc0..bac9c97 100644 --- a/src/spice-widget.c +++ b/src/spice-widget.c @@ -38,6 +38,8 @@ #endif #endif +#include <spice/vd_agent.h> + #include "spice-widget.h" #include "spice-widget-priv.h" #include "spice-gtk-session-priv.h" @@ -70,6 +72,7 @@ */ G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_EVENT_BOX) +#define SEAMLESS_MODE_BORDER 10 /* Properties */ enum { @@ -120,6 +123,9 @@ static void size_allocate(GtkWidget *widget, GtkAllocation *conf, gpointer data) static gboolean draw_event(GtkWidget *widget, cairo_t *cr, gpointer data); static void update_size_request(SpiceDisplay *display); static GdkDevice *spice_gdk_window_get_pointing_device(GdkWindow *window); +static gboolean draw_seamless(GtkWidget *widget, GdkEventExpose *event, gpointer userdata); +static void main_seamless_mode_update(SpiceChannel *channel, GParamSpec *pspec, SpiceDisplay *display); +static void set_seamless_mode(SpiceChannel *channel, GParamSpec *pspec, SpiceDisplay *display); /* ---------------------------------------------------------------- */ @@ -2089,11 +2095,15 @@ static gboolean button_event(GtkWidget *widget, GdkEventButton *button) switch (button->type) { case GDK_BUTTON_PRESS: + d->mouse_button_down = TRUE; + spice_display_update_seamless_mode(display); spice_inputs_button_press(d->inputs, button_gdk_to_spice(button->button), button_mask_gdk_to_spice(button->state)); break; case GDK_BUTTON_RELEASE: + d->mouse_button_down = FALSE; + spice_display_update_seamless_mode(display); spice_inputs_button_release(d->inputs, button_gdk_to_spice(button->button), button_mask_gdk_to_spice(button->state)); @@ -2944,6 +2954,8 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) d->main = SPICE_MAIN_CHANNEL(channel); spice_g_signal_connect_object(channel, "main-mouse-update", G_CALLBACK(update_mouse_mode), display, 0); + spice_g_signal_connect_object(channel, "notify::seamless-mode", + G_CALLBACK(set_seamless_mode), display, 0); update_mouse_mode(channel, display); return; } @@ -3067,6 +3079,56 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat return; } +static gboolean draw_seamless(GtkWidget *widget, GdkEventExpose *event, gpointer userdata) +{ + cairo_t *cr; + + cr = gdk_cairo_create(gtk_widget_get_window(widget)); + + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.0); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + + cairo_destroy(cr); + + return FALSE; +} + +static void main_seamless_mode_update(SpiceChannel *channel, + GParamSpec *pspec, + SpiceDisplay *display) +{ + spice_display_update_seamless_mode(display); +} + +static void set_seamless_mode(SpiceChannel *channel, + GParamSpec *pspec, + SpiceDisplay *display) +{ + gboolean enabled; + g_object_get(display->priv->main, "seamless-mode", &enabled, NULL); + + if (enabled) + { + GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(display)); + + g_signal_connect(G_OBJECT(toplevel), "draw", + G_CALLBACK(draw_seamless), NULL); + + g_signal_connect(display->priv->main, "notify::seamless-mode-list", + G_CALLBACK(main_seamless_mode_update), display); + } else { + g_signal_handlers_disconnect_by_func(display->priv->main, + G_CALLBACK(draw_seamless), + NULL); + + g_signal_handlers_disconnect_by_func(display->priv->main, + G_CALLBACK(main_seamless_mode_update), + display); + } +} + /** * spice_display_new: * @session: a #SpiceSession @@ -3103,6 +3165,62 @@ SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel } /** + * spice_display_update_seamless_mode: + * @display: a #SpiceDisplay + * + * Updates SpiceDisplay when using seamless mode. + **/ +void spice_display_update_seamless_mode(SpiceDisplay *display) +{ + SpiceDisplayPrivate *d = display->priv; + GtkWidget *toplevel = NULL; + GList *l = NULL, *list = NULL; + cairo_region_t *region = NULL; + gboolean enabled; + + toplevel = gtk_widget_get_toplevel(GTK_WIDGET(display)); + + g_object_get(display->priv->main, "seamless-mode", &enabled, NULL); + if (!enabled) { + //disable window click-through + gdk_window_input_shape_combine_region(gtk_widget_get_window(toplevel), NULL, 0, 0); + return; + } + + list = spice_main_get_seamless_mode_list(d->main); + + if (list != NULL) { + GdkRectangle *window; + + if (d->mouse_button_down) { + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(display), &alloc); + window = list->data; + window->x = 0; + window->y = 0; + window->width = alloc.width; + window->height = alloc.height; + region = cairo_region_create_rectangle((cairo_rectangle_int_t *)window); + } else { + region = cairo_region_create(); + for (l = list; l != NULL; l = l->next) { + window = l->data; + window->x -= SEAMLESS_MODE_BORDER; + window->y -= SEAMLESS_MODE_BORDER; + window->width += 2 * SEAMLESS_MODE_BORDER; + window->height += 2 * SEAMLESS_MODE_BORDER; + cairo_region_union_rectangle(region, window); + } + } + + gdk_window_input_shape_combine_region(gtk_widget_get_window(toplevel), region, 0, 0); + cairo_region_destroy(region); + } + + g_list_free_full(list, g_free); +} + +/** * spice_display_mouse_ungrab: * @display: a #SpiceDisplay * diff --git a/src/spice-widget.h b/src/spice-widget.h index d93737e..8a911e8 100644 --- a/src/spice-widget.h +++ b/src/spice-widget.h @@ -74,6 +74,7 @@ GType spice_display_get_type(void); SpiceDisplay* spice_display_new(SpiceSession *session, int channel_id); SpiceDisplay* spice_display_new_with_monitor(SpiceSession *session, gint channel_id, gint monitor_id); +void spice_display_update_seamless_mode(SpiceDisplay *display); void spice_display_mouse_ungrab(SpiceDisplay *display); void spice_display_set_grab_keys(SpiceDisplay *display, SpiceGrabSequence *seq); SpiceGrabSequence *spice_display_get_grab_keys(SpiceDisplay *display); diff --git a/tools/spicy.c b/tools/spicy.c index 40cd6b3..c1bfe9f 100644 --- a/tools/spicy.c +++ b/tools/spicy.c @@ -72,6 +72,7 @@ struct _SpiceWindow { GtkActionGroup *ag; GtkUIManager *ui; bool fullscreen; + bool seamless_mode; bool mouse_grabbed; SpiceChannel *display_channel; #ifdef G_OS_WIN32 @@ -124,6 +125,7 @@ static void usb_connect_failed(GObject *object, gpointer data); static gboolean is_gtk_session_property(const gchar *property); static void del_window(spice_connection *conn, SpiceWindow *win); +static void window_set_seamless_mode(SpiceWindow *win, gboolean enabled); /* options */ static gboolean fullscreen = false; @@ -226,6 +228,7 @@ static void update_edit_menu_window(SpiceWindow *win) { int i; GtkAction *toggle; + gboolean state; if (win == NULL) { return; @@ -239,6 +242,14 @@ static void update_edit_menu_window(SpiceWindow *win) gtk_action_set_sensitive(toggle, win->conn->agent_connected); } } + + toggle = gtk_action_group_get_action(win->ag, "SeamlessMode"); + state = spice_main_get_seamless_mode_supported(win->conn->main) && + win->conn->agent_connected; + gtk_action_set_sensitive(toggle, state); + + if (!state && win->seamless_mode) + window_set_seamless_mode(win, FALSE); } static void update_edit_menu(struct spice_connection *conn) @@ -303,6 +314,68 @@ static void menu_cb_fullscreen(GtkAction *action, void *data) window_set_fullscreen(win, !win->fullscreen); } +static void window_set_seamless_mode(SpiceWindow *win, gboolean enabled) +{ + gboolean state; + GError *error = NULL; + + win->seamless_mode = enabled; + g_object_set(win->conn->main, "seamless-mode", win->seamless_mode, NULL); + spice_display_update_seamless_mode(SPICE_DISPLAY(win->spice)); + + gtk_window_set_decorated(GTK_WINDOW(win->toplevel), !win->seamless_mode); + gtk_widget_set_visible(win->menubar, !win->seamless_mode); + gtk_widget_set_app_paintable(win->toplevel, win->seamless_mode); + + if (win->seamless_mode) { + gtk_window_maximize(GTK_WINDOW(win->toplevel)); + + gtk_widget_set_visible(win->toolbar, FALSE); + gtk_widget_set_visible(win->statusbar, FALSE); + + g_object_set (win->spice, "grab-keyboard", FALSE, NULL); + g_object_set (win->spice, "resize-guest", TRUE, NULL); + g_object_set (win->spice, "scaling", FALSE, NULL); + + spice_display_update_seamless_mode(SPICE_DISPLAY(win->spice)); + + gtk_window_present(GTK_WINDOW(win->toplevel)); + } else { + // TODO: Restore window geometry properly + gtk_window_unmaximize(GTK_WINDOW(win->toplevel)); + + state = g_key_file_get_boolean(keyfile, "ui", "toolbar", &error); + gtk_widget_set_visible(win->toolbar, error ? TRUE : state); + g_clear_error (&error); + + state = g_key_file_get_boolean(keyfile, "ui", "statusbar", &error); + gtk_widget_set_visible(win->statusbar, error ? TRUE : state); + g_clear_error (&error); + + state = g_key_file_get_boolean(keyfile, "general", "grab-keyboard", &error); + if (!error) + g_object_set (win->spice, "grab-keyboard", state, NULL); + g_clear_error (&error); + + state = g_key_file_get_boolean(keyfile, "general", "resize-guest", &error); + if (!error) + g_object_set (win->spice, "resize-guest", state, NULL); + g_clear_error (&error); + + state = g_key_file_get_boolean(keyfile, "general", "scaling", &error); + if (!error) + g_object_set (win->spice, "scaling", state, NULL); + g_clear_error (&error); + } +} + +static void menu_cb_seamless_mode(GtkAction *action, void *data) +{ + SpiceWindow *win = data; + + window_set_seamless_mode (win, !win->seamless_mode); +} + #ifdef USE_SMARTCARD static void enable_smartcard_actions(SpiceWindow *win, VReader *reader, gboolean can_insert, gboolean can_remove) @@ -757,6 +830,11 @@ static const GtkActionEntry entries[] = { .callback = G_CALLBACK(menu_cb_resize_to), .accelerator = "", },{ + .name = "SeamlessMode", + .label = "_Seamless mode", + .callback = G_CALLBACK(menu_cb_seamless_mode), + .accelerator = "<control><shift>S", + },{ #ifdef USE_SMARTCARD .name = "InsertSmartcard", .label = "_Insert Smartcard", @@ -919,6 +997,7 @@ static char ui_xml[] = " </menu>\n" " <menu action='ViewMenu'>\n" " <menuitem action='Fullscreen'/>\n" +" <menuitem action='SeamlessMode'/>\n" " <menuitem action='Toolbar'/>\n" " <menuitem action='Statusbar'/>\n" " </menu>\n" @@ -973,6 +1052,7 @@ static char ui_xml[] = " <separator/>\n" " <toolitem action='ResizeTo'/>\n" " <separator/>\n" +" <toolitem action='SeamlessMode'/>\n" " </toolbar>\n" "</ui>\n"; @@ -1041,6 +1121,8 @@ static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *ch GError *err = NULL; int i; SpiceGrabSequence *seq; + GdkScreen *screen; + GdkVisual *visual; win = g_object_new(SPICE_TYPE_WINDOW, NULL); win->id = id; @@ -1056,6 +1138,14 @@ static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *ch snprintf(title, sizeof(title), "%s", spicy_title); } + screen = gtk_widget_get_screen(win->toplevel); + visual = gdk_screen_get_rgba_visual(screen); + if (visual) + gtk_widget_set_visual(win->toplevel, visual); + else + // TODO: Hide seamless mode toggle + g_warning ("Transparency is not supported!"); + gtk_window_set_title(GTK_WINDOW(win->toplevel), title); g_signal_connect(G_OBJECT(win->toplevel), "window-state-event", G_CALLBACK(window_state_cb), win); -- 2.13.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel