diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2012-02-20 20:27:29 +0100 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2012-02-29 17:32:47 +0100 |
commit | d748bbc86ddfe0fcde475d63cdab0e2d80d2a05b (patch) | |
tree | 24896cd3230b5532f1a2d2a8234a01aee1d7a58d | |
parent | c10b36855ccf2abeb52c7c2be5b64c16ca54d137 (diff) |
Add controller foreign menu support
-rw-r--r-- | data/spice-protocol.vapi | 95 | ||||
-rw-r--r-- | gtk/controller/Makefile.am | 10 | ||||
-rw-r--r-- | gtk/controller/custom.vapi | 9 | ||||
-rw-r--r-- | gtk/controller/foreign-menu.vala | 207 | ||||
-rw-r--r-- | gtk/controller/menu.vala | 6 | ||||
-rw-r--r-- | gtk/controller/spice-foreign-menu-listener.c | 159 | ||||
-rw-r--r-- | gtk/controller/spice-foreign-menu-listener.h | 47 | ||||
m--------- | spice-protocol | 0 |
8 files changed, 530 insertions, 3 deletions
diff --git a/data/spice-protocol.vapi b/data/spice-protocol.vapi index 4cb1a2f..6626176 100644 --- a/data/spice-protocol.vapi +++ b/data/spice-protocol.vapi @@ -34,7 +34,7 @@ namespace SpiceProtocol { [CCode (cprefix = "CONTROLLER_")] public enum MsgId { - //extrenal app -> spice client + //external app -> spice client HOST, PORT, SPORT, @@ -62,7 +62,7 @@ namespace SpiceProtocol { ENABLE_SMARTCARD, - //spice client -> extrenal app + //spice client -> external app MENU_ITEM_CLICK, } @@ -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 { + //external 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 -Subproject cda88623d0754aeeda005ddc048dd113d279845 +Subproject d5edafd28ab762b1b5f663aec449d3e3743f118 |