[Spice-devel] [PATCH spice-gtk 03/12] Add initial seamless mode support

Jakub Janků janku.jakub.jj at gmail.com
Tue Aug 8 12:56:53 UTC 2017


From: Ondrej Holy <oholy at redhat.com>

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



More information about the Spice-devel mailing list