---
 data/spice-protocol.vapi                     |   91 +++++++++++
 gtk/controller/Makefile.am                   |   10 +-
 gtk/controller/custom.vapi                   |    9 +
 gtk/controller/foreign-menu.vala             |  207 ++++++++++++++++++++++++++
 gtk/controller/menu.vala                     |    6 +
 gtk/controller/spice-foreign-menu-listener.c |  159 ++++++++++++++++++++
 gtk/controller/spice-foreign-menu-listener.h |   47 ++++++
 spice-protocol                               |    2 +-
 8 files changed, 529 insertions(+), 2 deletions(-)
 create mode 100644 gtk/controller/foreign-menu.vala
 create mode 100644 gtk/controller/spice-foreign-menu-listener.c
 create mode 100644 gtk/controller/spice-foreign-menu-listener.h

diff --git a/data/spice-protocol.vapi b/data/spice-protocol.vapi
index 4cb1a2f..01b1a81 100644
--- a/data/spice-protocol.vapi
+++ b/data/spice-protocol.vapi
@@ -107,4 +107,95 @@ namespace SpiceProtocol {
                        GRAYED,
                }
        }
+
+    [CCode (cprefix = "FrgMenu", cheader_filename = 
"spice/foreign_menu_prot.h")]
+    namespace ForeignMenu {
+        [CCode (cname = "FOREIGN_MENU_MAGIC")]
+        public const uint32 MAGIC;
+        [CCode (cname = "FOREIGN_MENU_VERSION")]
+        public const int VERSION;
+
+        [Compact]
+        public struct InitHeader {
+            uint32 magic;
+            uint32 version;
+            uint32 size;
+        }
+
+        [Compact]
+        [CCode (has_destroy_function = false)]
+        public struct Init {
+            InitHeader base;
+            uint64 credentials;
+            string title; // utf8
+        }
+
+        [Compact]
+        public struct Msg {
+            uint32 id;
+            uint32 size;
+        }
+
+        [CCode (cprefix = "FOREIGN_MENU_", cname = "int")]
+        public enum MsgId {
+            //extrenal app -> spice client
+            SET_TITLE,
+            ADD_ITEM,
+            MODIFY_ITEM,
+            REMOVE_ITEM,
+            CLEAR,
+
+            //spice client -> external app
+            ITEM_EVENT,
+            APP_ACTIVATED,
+            APP_DEACTIVATED,
+        }
+
+        [Compact]
+        [CCode (cname = "FrgMenuSetTitle")]
+        public struct SetTitle {
+            Msg base;
+            string string; // utf8
+        }
+
+        [CCode (cprefix = "FOREIGN_MENU_ITEM_TYPE_", cname = "unsigned int", 
has_type_id = false)]
+        [Flags]
+        public enum MenuFlags {
+            CHECKED,
+            DIM,
+            SEPARATOR
+        }
+
+        [Compact]
+        [CCode (cname = "FrgMenuAddItem")]
+        public struct AddItem {
+            Msg base;
+            uint32 id;
+            uint32 type;
+            uint32 position;
+            string string; // utf8
+        }
+
+        [Compact]
+        [CCode (cname = "FrgMenuRmItem")]
+        public struct RmItem {
+            Msg base;
+            uint32 id;
+        }
+
+        [CCode (cprefix = "FOREIGN_MENU_EVENT_", cname = "int")]
+        public enum EventType {
+            CLICK,
+            CHECKED,
+            UNCHECKED,
+        }
+
+        [Compact]
+        [CCode (cname = "FrgMenuEvent")]
+        public struct Event {
+            Msg base;
+            uint32 id;
+            uint32 action;
+        }
+    }
 }
diff --git a/gtk/controller/Makefile.am b/gtk/controller/Makefile.am
index 522e014..20f1a48 100644
--- a/gtk/controller/Makefile.am
+++ b/gtk/controller/Makefile.am
@@ -1,6 +1,11 @@
 NULL =
 
-AM_CPPFLAGS = $(GIO_CFLAGS) $(PROTOCOL_CFLAGS)
+AM_CPPFLAGS =                                  \
+       -DG_LOG_DOMAIN=\"GSpiceController\"     \
+       $(GIO_CFLAGS)                           \
+       $(PROTOCOL_CFLAGS)                      \
+       $(NULL)
+
 # 
http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
 AM_LDFLAGS =                                   \
        -no-undefined                           \
@@ -22,11 +27,14 @@ BUILT_SOURCES = controller.vala.stamp
 libspice_controller_la_VALASOURCES =           \
        menu.vala                               \
        controller.vala                         \
+       foreign-menu.vala                       \
        $(NULL)
 libspice_controller_la_SOURCES =               \
        custom.h                                \
        spice-controller-listener.c             \
        spice-controller-listener.h             \
+       spice-foreign-menu-listener.c           \
+       spice-foreign-menu-listener.h           \
        $(libspice_controller_la_VALASOURCES:.vala=.c) \
        $(NULL)
 
diff --git a/gtk/controller/custom.vapi b/gtk/controller/custom.vapi
index 7a94b82..a12fdec 100644
--- a/gtk/controller/custom.vapi
+++ b/gtk/controller/custom.vapi
@@ -16,4 +16,13 @@ namespace Spice {
                [CCode (cname = "spice_controller_listener_accept_async", 
cheader_filename = "spice-controller-listener.h")]
                public async unowned GLib.IOStream accept_async 
(GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) 
throws GLib.Error;
        }
+
+       [CCode (cname = "GObject", ref_function = "g_object_ref", 
unref_function = "g_object_unref", free_function = "")]
+       class ForeignMenuListener {
+               [CCode (cname = "spice_foreign_menu_listener_new", 
cheader_filename = "spice-foreign-menu-listener.h")]
+               public static ForeignMenuListener new_listener (string addr) 
throws GLib.Error;
+
+               [CCode (cname = "spice_foreign_menu_listener_accept_async", 
cheader_filename = "spice-foreign-menu-listener.h")]
+               public async unowned GLib.IOStream accept_async 
(GLib.Cancellable? cancellable = null, out GLib.Object? source_object = null) 
throws GLib.Error;
+       }
 }
diff --git a/gtk/controller/foreign-menu.vala b/gtk/controller/foreign-menu.vala
new file mode 100644
index 0000000..677e2ad
--- /dev/null
+++ b/gtk/controller/foreign-menu.vala
@@ -0,0 +1,207 @@
+// Copyright (C) 2012 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/>.
+
+using Custom;
+
+namespace SpiceCtrl {
+
+public class ForeignMenu: Object {
+
+       public Menu menu { get; private set; }
+    public string title { get; private set; }
+
+       private int nclients;
+       private List<IOStream> clients;
+
+       public ForeignMenu() {
+               menu = new Menu ();
+       }
+
+       public void menu_item_click_msg (int32 item_id) {
+               debug ("clicked id: %d".printf (item_id));
+
+               var msg = SpiceProtocol.ForeignMenu.Event ();
+               msg.base.size = (uint32)sizeof 
(SpiceProtocol.ForeignMenu.Event);
+               msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
+               msg.id = item_id;
+               msg.action = SpiceProtocol.ForeignMenu.EventType.CLICK;
+
+               unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
+               send_msg (p);
+       }
+
+       public void menu_item_checked_msg (int32 item_id, bool checked = true) {
+               debug ("%schecked id: %d".printf (checked ? "" : "un", 
item_id));
+
+               var msg = SpiceProtocol.ForeignMenu.Event ();
+               msg.base.size = (uint32)sizeof 
(SpiceProtocol.ForeignMenu.Event);
+               msg.base.id = SpiceProtocol.ForeignMenu.MsgId.ITEM_EVENT;
+               msg.id = item_id;
+               msg.action = checked ?
+                       SpiceProtocol.ForeignMenu.EventType.CHECKED :
+                       SpiceProtocol.ForeignMenu.EventType.UNCHECKED;
+
+               unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size];
+               send_msg (p);
+       }
+
+       public void app_activated_msg (bool activated = true) {
+               var msg = SpiceProtocol.ForeignMenu.Msg ();
+               msg.size = (uint32)sizeof (SpiceProtocol.ForeignMenu.Event);
+               msg.id = activated ?
+                       SpiceProtocol.ForeignMenu.MsgId.APP_ACTIVATED :
+                       SpiceProtocol.ForeignMenu.MsgId.APP_DEACTIVATED;
+
+               unowned uint8[] p = ((uint8[])(&msg))[0:msg.size];
+               send_msg (p);
+       }
+
+       public async bool send_msg (uint8[] p) throws GLib.Error {
+               // vala FIXME: pass Controller.Msg instead
+               // vala doesn't keep reference on the struct in async methods
+               // it copies only base, which is not enough to transmit the 
whole
+               // message.
+               try {
+                       foreach (var c in clients)
+                               yield c.output_stream.write_async (p);
+               } catch (GLib.Error e) {
+                       warning (e.message);
+               }
+
+               return true;
+       }
+
+       SpiceProtocol.Controller.MenuFlags get_menu_flags (uint32 type) {
+               SpiceProtocol.Controller.MenuFlags flags = 0;
+
+               if ((SpiceProtocol.ForeignMenu.MenuFlags.CHECKED & type) != 0)
+                       flags |= SpiceProtocol.Controller.MenuFlags.CHECKED;
+               if ((SpiceProtocol.ForeignMenu.MenuFlags.DIM & type) != 0)
+                       flags |= SpiceProtocol.Controller.MenuFlags.GRAYED;
+
+               return flags;
+       }
+
+       private bool handle_message (SpiceProtocol.ForeignMenu.Msg* msg) {
+               switch (msg.id) {
+               case SpiceProtocol.ForeignMenu.MsgId.SET_TITLE:
+                       var t = (SpiceProtocol.ForeignMenu.SetTitle*)(msg);
+                       title = t.string;
+                       break;
+               case SpiceProtocol.ForeignMenu.MsgId.ADD_ITEM:
+                       var i = (SpiceProtocol.ForeignMenu.AddItem*)(msg);
+                       debug ("add id:%u type:%u position:%u title:%s", i.id, 
i.type, i.position, i.string);
+                       menu.items.append (new MenuItem ((int)i.id, i.string, 
get_menu_flags (i.type)));
+                       notify_property ("menu");
+                       break;
+               case SpiceProtocol.ForeignMenu.MsgId.MODIFY_ITEM:
+                       debug ("deprecated: modify item");
+                       break;
+               case SpiceProtocol.ForeignMenu.MsgId.REMOVE_ITEM:
+                       var i = (SpiceProtocol.ForeignMenu.RmItem*)(msg);
+                       debug ("not implemented: remove id:%u".printf (i.id));
+                       break;
+               case SpiceProtocol.ForeignMenu.MsgId.CLEAR:
+                       menu = new Menu ();
+                       break;
+               default:
+                       warn_if_reached ();
+                       return false;
+               }
+               return true;
+       }
+
+       private async void handle_client (IOStream c) throws GLib.Error {
+               var header = SpiceProtocol.ForeignMenu.InitHeader ();
+               unowned uint8[] p = null;
+
+               debug ("new socket client, reading init header");
+
+               p = 
((uint8[])(&header))[0:sizeof(SpiceProtocol.ForeignMenu.InitHeader)]; // FIXME 
vala
+               var read = yield c.input_stream.read_async (p);
+               if (warn_if (read != sizeof 
(SpiceProtocol.ForeignMenu.InitHeader)))
+                       return;
+               if (warn_if (header.magic != SpiceProtocol.ForeignMenu.MAGIC))
+                       return;
+               if (warn_if (header.version != 
SpiceProtocol.ForeignMenu.VERSION))
+                       return;
+               if (warn_if (header.size < sizeof 
(SpiceProtocol.ForeignMenu.Init)))
+                       return;
+
+               uint64 credentials = 0;
+               p = ((uint8[])(&credentials))[0:sizeof(uint64)];
+               read = yield c.input_stream.read_async (p);
+               if (warn_if (read != sizeof(uint64)))
+                       return;
+               if (warn_if (credentials != 0))
+                       return;
+
+               var title_size = header.size - 
sizeof(SpiceProtocol.ForeignMenu.Init);
+               var title = new uint8[title_size + 1];
+               read = yield c.input_stream.read_async (title[0:title_size]);
+               this.title = (string)title;
+
+               var t = new uint8[sizeof(SpiceProtocol.ForeignMenu.Msg)];
+               for (;;) {
+                       read = yield c.input_stream.read_async 
(t[0:sizeof(SpiceProtocol.ForeignMenu.Msg)]);
+                       if (read == 0)
+                               break;
+
+                       if (warn_if (read != sizeof 
(SpiceProtocol.ForeignMenu.Msg))) {
+                               warning ("read only: " + read.to_string ());
+                               break;
+                       }
+
+                       var msg = (SpiceProtocol.ForeignMenu.Msg*)t;
+                       if (warn_if (msg.size < sizeof 
(SpiceProtocol.ForeignMenu.Msg)))
+                               break;
+
+                       if (msg.size > sizeof (SpiceProtocol.ForeignMenu.Msg)) {
+                               t.resize ((int)msg.size);
+                               msg = (SpiceProtocol.ForeignMenu.Msg*)t;
+                               read = yield c.input_stream.read_async 
(t[sizeof(SpiceProtocol.ForeignMenu.Msg):msg.size]);
+                               if (read == 0)
+                                       break;
+                               if (warn_if (read != msg.size - 
sizeof(SpiceProtocol.ForeignMenu.Msg)))
+                                       break;
+                       }
+
+                       handle_message (msg);
+               }
+
+       }
+
+       public async void listen (string? addr = null) throws GLib.Error, 
SpiceCtrl.Error
+       {
+               var listener = Spice.ForeignMenuListener.new_listener (addr);
+
+               for (;;) {
+                       var c = yield listener.accept_async ();
+                       nclients += 1;
+                       clients.append (c);
+                       try {
+                               yield handle_client (c);
+                       } catch (GLib.Error e) {
+                               warning (e.message);
+                       }
+                       c.close ();
+                       clients.remove (c);
+                       nclients -= 1;
+               }
+       }
+
+}
+
+} // SpiceCtrl
diff --git a/gtk/controller/menu.vala b/gtk/controller/menu.vala
index 7f2f42a..7e8fc16 100644
--- a/gtk/controller/menu.vala
+++ b/gtk/controller/menu.vala
@@ -28,6 +28,12 @@ public class MenuItem: Object {
        public string accel;
        public SpiceProtocol.Controller.MenuFlags flags;
 
+       public MenuItem (int id, string text, 
SpiceProtocol.Controller.MenuFlags flags) {
+               this.id = id;
+               this.text = text;
+               this.flags = flags;
+       }
+
        public MenuItem.from_string (string str) throws SpiceCtrl.Error {
                var params = str.split 
(SpiceProtocol.Controller.MENU_PARAM_DELIMITER);
                if (warn_if (params.length != 5))
diff --git a/gtk/controller/spice-foreign-menu-listener.c 
b/gtk/controller/spice-foreign-menu-listener.c
new file mode 100644
index 0000000..8322a13
--- /dev/null
+++ b/gtk/controller/spice-foreign-menu-listener.c
@@ -0,0 +1,159 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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 <glib.h>
+#include <glib/gstdio.h>
+
+#include "spice-foreign-menu-listener.h"
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include "namedpipe.h"
+#include "namedpipelistener.h"
+#endif
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/**
+ * SpiceForeignMenuListenerError:
+ * @SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE: invalid value.
+ *
+ * Possible errors of foreign menu listener related functions.
+ **/
+
+/**
+ * SPICE_FOREIGN_MENU_LISTENER_ERROR:
+ *
+ * The error domain of the foreign menu listener subsystem.
+ **/
+GQuark
+spice_foreign_menu_listener_error_quark (void)
+{
+  return g_quark_from_static_string ("spice-foreign-menu-listener-error");
+}
+
+GObject*
+spice_foreign_menu_listener_new (const gchar *address, GError **error)
+{
+    GObject *listener = NULL;
+    gchar *addr = NULL;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+    addr = g_strdup (address);
+
+#ifdef G_OS_WIN32
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_NAMEDPIPE"));
+    if (addr == NULL)
+        addr = g_strdup_printf ("\\\\.\\pipe\\SpiceForeignMenu-%" 
G_GUINT64_FORMAT, (guint64)GetCurrentProcessId ());
+#else
+    if (addr == NULL)
+        addr = g_strdup (g_getenv ("SPICE_FOREIGN_MENU_SOCKET"));
+    if (addr == NULL)
+        addr = g_strdup_printf ("/tmp/SpiceForeignMenu-%" G_GUINT64_FORMAT 
".uds", (guint64)getpid ());
+#endif
+    if (addr == NULL) {
+        g_set_error (error,
+                     SPICE_FOREIGN_MENU_LISTENER_ERROR,
+                     SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE,
+#ifdef G_OS_WIN32
+                     "Missing namedpipe address"
+#else
+                     "Missing socket address"
+#endif
+                     );
+        goto end;
+    }
+
+    g_unlink (addr);
+
+#ifdef G_OS_WIN32
+    {
+        SpiceNamedPipe *np;
+
+        listener = G_OBJECT (spice_named_pipe_listener_new ());
+
+        np = spice_named_pipe_new (addr, error);
+        if (!np) {
+            g_object_unref (listener);
+            listener = NULL;
+            goto end;
+        }
+
+        spice_named_pipe_listener_add_named_pipe (SPICE_NAMED_PIPE_LISTENER 
(listener), np);
+    }
+#else
+    {
+        listener = G_OBJECT (g_socket_listener_new ());
+
+        if (!g_socket_listener_add_address (G_SOCKET_LISTENER (listener),
+                                            G_SOCKET_ADDRESS 
(g_unix_socket_address_new (addr)),
+                                            G_SOCKET_TYPE_STREAM,
+                                            G_SOCKET_PROTOCOL_DEFAULT,
+                                            NULL,
+                                            NULL,
+                                            error))
+            g_warning ("failed to add address");
+    }
+#endif
+
+end:
+    g_free (addr);
+    return listener;
+}
+
+void
+spice_foreign_menu_listener_accept_async (GObject *listener,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data)
+{
+    g_return_if_fail(G_IS_OBJECT(listener));
+
+#ifdef G_OS_WIN32
+    spice_named_pipe_listener_accept_async (SPICE_NAMED_PIPE_LISTENER 
(listener), cancellable, callback, user_data);
+#else
+    g_socket_listener_accept_async (G_SOCKET_LISTENER (listener), cancellable, 
callback, user_data);
+#endif
+}
+
+GIOStream*
+spice_foreign_menu_listener_accept_finish (GObject *listener,
+                                           GAsyncResult *result,
+                                           GObject **source_object,
+                                           GError **error)
+{
+    g_return_val_if_fail(G_IS_OBJECT(listener), NULL);
+
+#ifdef G_OS_WIN32
+    SpiceNamedPipeConnection *np;
+    np = spice_named_pipe_listener_accept_finish (SPICE_NAMED_PIPE_LISTENER 
(listener), result, source_object, error);
+    if (np)
+        return G_IO_STREAM (np);
+#else
+    GSocketConnection *socket;
+    socket = g_socket_listener_accept_finish (G_SOCKET_LISTENER (listener), 
result, source_object, error);
+    if (socket)
+        return G_IO_STREAM (socket);
+#endif
+
+    return NULL;
+}
diff --git a/gtk/controller/spice-foreign-menu-listener.h 
b/gtk/controller/spice-foreign-menu-listener.h
new file mode 100644
index 0000000..1071528
--- /dev/null
+++ b/gtk/controller/spice-foreign-menu-listener.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 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_FOREIGN_MENU_LISTENER_H__
+#define __SPICE_FOREIGN_MENU_LISTENER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_FOREIGN_MENU_LISTENER_ERROR 
spice_foreign_menu_listener_error_quark ()
+GQuark spice_foreign_menu_listener_error_quark (void);
+
+typedef enum
+{
+    SPICE_FOREIGN_MENU_LISTENER_ERROR_VALUE /* incorrect value */
+} SpiceForeignMenuListenerError;
+
+
+GObject* spice_foreign_menu_listener_new (const gchar *address, GError 
**error);
+
+void spice_foreign_menu_listener_accept_async (GObject *listener,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data);
+
+GIOStream* spice_foreign_menu_listener_accept_finish (GObject *listener,
+                                                    GAsyncResult *result,
+                                                    GObject **source_object,
+                                                    GError **error);
+G_END_DECLS
+
+#endif /* __SPICE_FOREIGN_MENU_LISTENER_H__ */
diff --git a/spice-protocol b/spice-protocol
index cda8862..d5edafd 160000
--- a/spice-protocol
+++ b/spice-protocol
@@ -1 +1 @@
-Subproject commit cda88623d0754aeeda005ddc048dd113d279845b
+Subproject commit d5edafd28ab762b1b5f663aec449d3e3743f1184
-- 
1.7.7.6

_______________________________________________
Spice-devel mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/spice-devel

Reply via email to