[Spice-devel] [PATCH spice-gtk 1/2] Add helper class to handle cursor image

Pavel Grunt pgrunt at redhat.com
Thu Apr 27 17:21:38 UTC 2017


Implement internal SpiceCursorChannelGtk class. Its purpose is to handle
the graphical (GTK/GDK) representation of the remote cursor.

The intention behind it is sharing of the cursor between multiple
SpiceDisplay widgets (case of a linux guest which has a single display
channel serving multiple monitors).

Related: rhbz#1411380
---
 src/Makefile.am          |   2 +
 src/channel-cursor-gtk.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++
 src/channel-cursor-gtk.h |  51 +++++++++
 src/spice-widget.c       |  26 ++---
 4 files changed, 327 insertions(+), 15 deletions(-)
 create mode 100644 src/channel-cursor-gtk.c
 create mode 100644 src/channel-cursor-gtk.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 4fa7357..f816fc2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -134,6 +134,8 @@ SPICE_GTK_SOURCES_COMMON =		\
 	desktop-integration.c		\
 	desktop-integration.h		\
 	usb-device-widget.c		\
+	channel-cursor-gtk.c		\
+	channel-cursor-gtk.h		\
 	$(NULL)
 
 nodist_SPICE_GTK_SOURCES_COMMON =	\
diff --git a/src/channel-cursor-gtk.c b/src/channel-cursor-gtk.c
new file mode 100644
index 0000000..26b5319
--- /dev/null
+++ b/src/channel-cursor-gtk.c
@@ -0,0 +1,263 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2017 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "config.h"
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdk.h>
+
+#include "spice-client.h"
+#include "spice-util.h"
+#include "channel-cursor-gtk.h"
+
+/* since GLib 2.38 */
+#ifndef g_assert_true
+#define g_assert_true g_assert
+#endif
+
+struct _SpiceCursorChannelGtk {
+    GObject parent;
+
+    SpiceCursorChannel *cursor;
+
+    GdkPoint hotspot;
+    GdkPixbuf *cursor_pixbuf;
+};
+
+struct _SpiceCursorChannelGtkClass {
+    GObjectClass parent_class;
+};
+
+/* properties */
+enum {
+    PROP_0,
+    PROP_CHANNEL,
+    PROP_CURSOR_PIXBUF,
+};
+
+/* ------------------------------------------------------------------ */
+/* Prototypes for private functions */
+static void cursor_set(SpiceCursorChannel *channel,
+                       gint width, gint height, gint hot_x, gint hot_y,
+                       gpointer rgba, gpointer data);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+G_DEFINE_TYPE (SpiceCursorChannelGtk, spice_cursor_channel_gtk, G_TYPE_OBJECT);
+
+static void spice_cursor_channel_gtk_init(G_GNUC_UNUSED SpiceCursorChannelGtk *self)
+{
+}
+
+static GObject *
+spice_cursor_channel_gtk_constructor(GType                  gtype,
+                                     guint                  n_properties,
+                                     GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceCursorChannelGtk *self;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    self = SPICE_CURSOR_CHANNEL_GTK(obj);
+    if (self->cursor == NULL) {
+        g_error("SpiceCursorChannelGtk constructed without an cursor channel");
+    }
+
+    g_signal_connect(self->cursor, "cursor-set", G_CALLBACK(cursor_set), self);
+
+    return obj;
+}
+
+static void spice_cursor_channel_gtk_finalize(GObject *gobject)
+{
+    SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject);
+
+    g_clear_object(&self->cursor_pixbuf);
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->finalize(gobject);
+}
+
+static void spice_cursor_channel_gtk_dispose(GObject *gobject)
+{
+    SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject);
+
+    if (self->cursor != NULL) {
+        g_signal_handlers_disconnect_by_func(self->cursor,
+                                             G_CALLBACK(cursor_set),
+                                             self);
+        self->cursor = NULL;
+    }
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_cursor_channel_gtk_parent_class)->dispose(gobject);
+}
+
+static void spice_cursor_channel_gtk_get_property(GObject    *gobject,
+                                                  guint       prop_id,
+                                                  GValue     *value,
+                                                  GParamSpec *pspec)
+{
+    SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject);
+
+    switch (prop_id) {
+    case PROP_CHANNEL:
+        g_value_set_object(value, self->cursor);
+        break;
+    case PROP_CURSOR_PIXBUF:
+        g_value_set_object(value, self->cursor_pixbuf);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_cursor_channel_gtk_set_property(GObject      *gobject,
+                                                  guint         prop_id,
+                                                  const GValue *value,
+                                                  GParamSpec   *pspec)
+{
+    SpiceCursorChannelGtk *self = SPICE_CURSOR_CHANNEL_GTK(gobject);
+
+    switch (prop_id) {
+    case PROP_CHANNEL:
+        self->cursor = g_value_get_object(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_cursor_channel_gtk_class_init(SpiceCursorChannelGtkClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    gobject_class->constructor  = spice_cursor_channel_gtk_constructor;
+    gobject_class->dispose      = spice_cursor_channel_gtk_dispose;
+    gobject_class->finalize     = spice_cursor_channel_gtk_finalize;
+    gobject_class->get_property = spice_cursor_channel_gtk_get_property;
+    gobject_class->set_property = spice_cursor_channel_gtk_set_property;
+
+    /**
+     * SpiceCursorChannelGtk:cursor-pixbuf:
+     *
+     * Represents the current #GdkPixbuf to be used for creating a cursor
+     * using gdk_cursor_new_from_pixbuf
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CURSOR_PIXBUF,
+         g_param_spec_object("cursor-pixbuf",
+                             "Pixbuf of the cursor",
+                             "GdkPixbuf to be used for creating a cursor",
+                             GDK_TYPE_PIXBUF,
+                             G_PARAM_READABLE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceCursorChannelGtk:channel:
+     *
+     * #SpiceCursorChannel this #SpiceCursorChannelGtk is associated with
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CHANNEL,
+         g_param_spec_object("channel",
+                             "Pixbuf of the cursor",
+                             "GdkPixbuf to be used for creating a cursor",
+                             SPICE_TYPE_CHANNEL,
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT_ONLY |
+                             G_PARAM_STATIC_STRINGS));
+}
+
+
+static void cursor_set(SpiceCursorChannel *channel,
+                       gint width, gint height, gint x_hot, gint y_hot,
+                       gpointer rgba, gpointer user_data)
+{
+    gchar *hotspot_str;
+    SpiceCursorChannelGtk *self = user_data;
+
+    self->hotspot.x = x_hot;
+    self->hotspot.y = y_hot;
+
+    g_return_if_fail(rgba != NULL);
+
+    g_clear_object(&self->cursor_pixbuf);
+    self->cursor_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4),
+                                                   GDK_COLORSPACE_RGB,
+                                                   TRUE,
+                                                   8,
+                                                   width,
+                                                   height,
+                                                   width * 4,
+                                                   (GdkPixbufDestroyNotify) g_free,
+                                                   NULL);
+    /*
+        It is possible to set cursor cordinates using pixbuf options and use them
+        when creating the cursor.
+        See gdk_cursor_new_from_pixbuf documentation for more information.
+    */
+    hotspot_str = g_strdup_printf("%d", x_hot);
+    g_assert_true(gdk_pixbuf_set_option(self->cursor_pixbuf, "x_hot", hotspot_str));
+    g_free(hotspot_str);
+
+    hotspot_str = g_strdup_printf("%d", y_hot);
+    g_assert_true(gdk_pixbuf_set_option(self->cursor_pixbuf, "y_hot", hotspot_str));
+    g_free(hotspot_str);
+
+    g_object_notify(G_OBJECT(self), "cursor-pixbuf");
+}
+
+SpiceCursorChannelGtk *spice_cursor_channel_gtk_get(SpiceCursorChannel *channel)
+{
+    SpiceCursorChannelGtk *self;
+    static GMutex mutex;
+
+    g_return_val_if_fail(SPICE_IS_CURSOR_CHANNEL(channel), NULL);
+
+    g_mutex_lock(&mutex);
+    self = g_object_get_data(G_OBJECT(channel), "spice-channel-cursor-gtk");
+    if (self == NULL) {
+        self = g_object_new(SPICE_TYPE_CURSOR_CHANNEL_GTK, "channel", channel, NULL);
+        g_object_set_data_full(G_OBJECT(channel), "spice-channel-cursor-gtk", self, g_object_unref);
+    }
+    g_mutex_unlock(&mutex);
+
+    return self;
+}
+
+void spice_cursor_channel_gtk_get_hotspot(SpiceCursorChannelGtk *self, GdkPoint *hotspot)
+{
+    g_return_if_fail(SPICE_IS_CURSOR_CHANNEL_GTK(self));
+    g_return_if_fail(hotspot != NULL);
+
+    *hotspot = self->hotspot;
+}
diff --git a/src/channel-cursor-gtk.h b/src/channel-cursor-gtk.h
new file mode 100644
index 0000000..63d3321
--- /dev/null
+++ b/src/channel-cursor-gtk.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2017 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__
+#define __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_CURSOR_CHANNEL_GTK            (spice_cursor_channel_gtk_get_type())
+#define SPICE_CURSOR_CHANNEL_GTK(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                                                  SPICE_TYPE_CURSOR_CHANNEL_GTK, \
+                                                  SpiceCursorChannelGtk))
+#define SPICE_CURSOR_CHANNEL_GTK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), \
+                                                  SPICE_TYPE_CURSOR_CHANNEL_GTK, \
+                                                  SpiceCursorChannelGtkClass))
+#define SPICE_IS_CURSOR_CHANNEL_GTK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+                                                  SPICE_TYPE_CURSOR_CHANNEL_GTK))
+#define SPICE_IS_CURSOR_CHANNEL_GTK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+                                                  SPICE_TYPE_CURSOR_CHANNEL_GTK))
+#define SPICE_CURSOR_CHANNEL_GTK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), \
+                                                  SPICE_TYPE_CURSOR_CHANNEL_GTK, \
+                                                  SpiceCursorChannelGtkClass))
+
+typedef struct _SpiceCursorChannelGtk SpiceCursorChannelGtk;
+typedef struct _SpiceCursorChannelGtkClass SpiceCursorChannelGtkClass;
+
+GType spice_cursor_channel_gtk_get_type(void);
+
+SpiceCursorChannelGtk *spice_cursor_channel_gtk_get(SpiceCursorChannel *channel);
+void spice_cursor_channel_gtk_get_hotspot(SpiceCursorChannelGtk *self, GdkPoint *hotspot);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_GTK_CHANNEL_CURSOR_H__ */
diff --git a/src/spice-widget.c b/src/spice-widget.c
index 5bbba8f..503644b 100644
--- a/src/spice-widget.c
+++ b/src/spice-widget.c
@@ -43,6 +43,7 @@
 #include "spice-gtk-session-priv.h"
 #include "vncdisplaykeymap.h"
 #include "spice-grabsequence-priv.h"
+#include "channel-cursor-gtk.h"
 
 
 /**
@@ -2634,9 +2635,9 @@ static void mark(SpiceDisplay *display, gint mark)
     update_ready(display);
 }
 
-static void cursor_set(SpiceCursorChannel *channel,
-                       gint width, gint height, gint hot_x, gint hot_y,
-                       gpointer rgba, gpointer data)
+static void cursor_set(SpiceCursorChannelGtk *channel_gtk,
+                       G_GNUC_UNUSED GParamSpec *pspec,
+                       gpointer data)
 {
     SpiceDisplay *display = data;
     SpiceDisplayPrivate *d = display->priv;
@@ -2646,18 +2647,11 @@ static void cursor_set(SpiceCursorChannel *channel,
 
     g_clear_object(&d->mouse_pixbuf);
 
-    if (rgba != NULL) {
-        d->mouse_pixbuf = gdk_pixbuf_new_from_data(g_memdup(rgba, width * height * 4),
-                                                   GDK_COLORSPACE_RGB,
-                                                   TRUE, 8,
-                                                   width,
-                                                   height,
-                                                   width * 4,
-                                                   (GdkPixbufDestroyNotify)g_free, NULL);
-        d->mouse_hotspot.x = hot_x;
-        d->mouse_hotspot.y = hot_y;
+    g_object_get(G_OBJECT(channel_gtk), "cursor-pixbuf", &d->mouse_pixbuf, NULL);
+    if (d->mouse_pixbuf != NULL) {
+        spice_cursor_channel_gtk_get_hotspot(channel_gtk, &d->mouse_hotspot);
         cursor = gdk_cursor_new_from_pixbuf(gtk_widget_get_display(GTK_WIDGET(display)),
-                                            d->mouse_pixbuf, hot_x, hot_y);
+                                            d->mouse_pixbuf, -1, -1);
     } else
         g_warn_if_reached();
 
@@ -2955,10 +2949,12 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
     }
 
     if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+        SpiceCursorChannelGtk *channel_gtk;
         if (id != d->channel_id)
             return;
         d->cursor = SPICE_CURSOR_CHANNEL(channel);
-        spice_g_signal_connect_object(channel, "cursor-set",
+        channel_gtk = spice_cursor_channel_gtk_get(d->cursor);
+        spice_g_signal_connect_object(channel_gtk, "notify::cursor-pixbuf",
                                       G_CALLBACK(cursor_set), display, 0);
         spice_g_signal_connect_object(channel, "cursor-move",
                                       G_CALLBACK(cursor_move), display, 0);
-- 
2.12.2



More information about the Spice-devel mailing list