// 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 . using Custom; namespace SpiceCtrl { public class ForeignMenu: Object { public Menu menu { get; private set; } public string title { get; private set; } public signal void client_connected (); private int nclients; private List 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.begin (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.begin (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.begin (p); } public async bool send_msg (owned 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 output_stream_write (c.output_stream, 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 { debug ("new socket client, reading init header"); var p = new uint8[sizeof(SpiceProtocol.ForeignMenu.InitHeader)]; var header = (SpiceProtocol.ForeignMenu.InitHeader*)p; yield input_stream_read (c.input_stream, p); 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; var cp = new uint8[sizeof(uint64)]; yield input_stream_read (c.input_stream, cp); uint64 credentials = *(uint64*)cp; if (warn_if (credentials != 0)) return; var title_size = header.size - sizeof(SpiceProtocol.ForeignMenu.Init); var title = new uint8[title_size + 1]; yield c.input_stream.read_async (title[0:title_size]); this.title = (string)title; client_connected (); for (;;) { var t = new uint8[sizeof(SpiceProtocol.ForeignMenu.Msg)]; yield input_stream_read (c.input_stream, t); var msg = (SpiceProtocol.ForeignMenu.Msg*)t; debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ()); 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; yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.ForeignMenu.Msg):msg.size]); } 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