// Copyright (C) 2011 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 GLib; using Custom; using Win32; using Spice; using SpiceProtocol; namespace SpiceCtrl { public errordomain Error { VALUE, } public class Controller: Object { public string host { private set; get; } public uint32 port { private set; get; } public uint32 sport { private set; get; } public string password { private set; get; } public SpiceProtocol.Controller.Display display_flags { private set; get; } public string tls_ciphers { private set; get; } public string host_subject { private set; get; } public string ca_file { private set; get; } public string title { private set; get; } public string hotkeys { private set; get; } public string[] secure_channels { private set; get; } public string[] disable_channels { private set; get; } public SpiceCtrl.Menu? menu { private set; get; } public bool enable_smartcard { private set; get; } public bool send_cad { private set; get; } public string[] disable_effects {private set; get; } public uint32 color_depth {private set; get; } public bool enable_usbredir { private set; get; } public bool enable_usb_autoshare { private set; get; } public string usb_filter { private set; get; } public string proxy { private set; get; } public signal void do_connect (); public signal void show (); public signal void hide (); public signal void client_connected (); public void menu_item_click_msg (int32 item_id) { var msg = SpiceProtocol.Controller.MsgValue (); msg.base.size = (uint32)sizeof (SpiceProtocol.Controller.MsgValue); msg.base.id = SpiceProtocol.Controller.MsgId.MENU_ITEM_CLICK; msg.value = item_id; unowned uint8[] p = ((uint8[])(&msg))[0:msg.base.size]; send_msg.begin (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 { if (excl_connection != null) { yield output_stream_write (excl_connection.output_stream, p); } else { foreach (var c in clients) yield output_stream_write (c.output_stream, p); } } catch (GLib.Error e) { warning (e.message); } return true; } private GLib.IOStream? excl_connection; private int nclients; List clients; private bool handle_message (SpiceProtocol.Controller.Msg* msg) { var v = (SpiceProtocol.Controller.MsgValue*)(msg); var d = (SpiceProtocol.Controller.MsgData*)(msg); unowned string str = (string)(&d.data); switch (msg.id) { case SpiceProtocol.Controller.MsgId.HOST: host = str; debug ("got HOST: %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.PORT: port = v.value; debug ("got PORT: %u".printf (port)); break; case SpiceProtocol.Controller.MsgId.SPORT: sport = v.value; debug ("got SPORT: %u".printf (sport)); break; case SpiceProtocol.Controller.MsgId.PASSWORD: password = str; debug ("got PASSWORD"); break; case SpiceProtocol.Controller.MsgId.SECURE_CHANNELS: secure_channels = str.split(","); debug ("got SECURE_CHANNELS %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.DISABLE_CHANNELS: disable_channels = str.split(","); debug ("got DISABLE_CHANNELS %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.TLS_CIPHERS: tls_ciphers = str; debug ("got TLS_CIPHERS %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.CA_FILE: ca_file = str; debug ("got CA_FILE %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.HOST_SUBJECT: host_subject = str; debug ("got HOST_SUBJECT %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.FULL_SCREEN: display_flags = (SpiceProtocol.Controller.Display)v.value; debug ("got FULL_SCREEN 0x%x".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.SET_TITLE: title = str; debug ("got TITLE %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.ENABLE_SMARTCARD: enable_smartcard = (bool)v.value; debug ("got ENABLE_SMARTCARD 0x%x".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.CREATE_MENU: menu = new SpiceCtrl.Menu.from_string (str); debug ("got CREATE_MENU %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.DELETE_MENU: menu = null; debug ("got DELETE_MENU request"); break; case SpiceProtocol.Controller.MsgId.SEND_CAD: send_cad = (bool)v.value; debug ("got SEND_CAD %u".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.HOTKEYS: hotkeys = str; debug ("got HOTKEYS %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.COLOR_DEPTH: color_depth = v.value; debug ("got COLOR_DEPTH %u".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.DISABLE_EFFECTS: disable_effects = str.split(","); debug ("got DISABLE_EFFECTS %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.CONNECT: do_connect (); debug ("got CONNECT request"); break; case SpiceProtocol.Controller.MsgId.SHOW: show (); debug ("got SHOW request"); break; case SpiceProtocol.Controller.MsgId.HIDE: hide (); debug ("got HIDE request"); break; case SpiceProtocol.Controller.MsgId.ENABLE_USB: enable_usbredir = (bool)v.value; debug ("got ENABLE_USB %u".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.ENABLE_USB_AUTOSHARE: enable_usb_autoshare = (bool)v.value; debug ("got ENABLE_USB_AUTOSHARE %u".printf (v.value)); break; case SpiceProtocol.Controller.MsgId.USB_FILTER: usb_filter = str; debug ("got USB_FILTER %s".printf (str)); break; case SpiceProtocol.Controller.MsgId.PROXY: proxy = str; debug ("got PROXY %s".printf (str)); break; default: debug ("got unknown msg.id %u".printf (msg.id)); warn_if_reached (); return false; } return true; } private async void handle_client (IOStream c) throws GLib.Error { var excl = false; debug ("new socket client, reading init header"); var p = new uint8[sizeof(SpiceProtocol.Controller.Init)]; var init = (SpiceProtocol.Controller.Init*)p; yield input_stream_read (c.input_stream, p); if (warn_if (init.base.magic != SpiceProtocol.Controller.MAGIC)) return; if (warn_if (init.base.version != SpiceProtocol.Controller.VERSION)) return; if (warn_if (init.base.size < sizeof (SpiceProtocol.Controller.Init))) return; if (warn_if (init.credentials != 0)) return; if (warn_if (excl_connection != null)) return; excl = (bool)(init.flags & SpiceProtocol.Controller.Flag.EXCLUSIVE); if (excl) { if (nclients > 1) { warning (@"Can't make the client exclusive, there is already $nclients connected clients"); return; } excl_connection = c; } client_connected (); for (;;) { var t = new uint8[sizeof(SpiceProtocol.Controller.Msg)]; yield input_stream_read (c.input_stream, t); var msg = (SpiceProtocol.Controller.Msg*)t; debug ("new message " + msg.id.to_string () + "size " + msg.size.to_string ()); if (warn_if (msg.size < sizeof (SpiceProtocol.Controller.Msg))) break; if (msg.size > sizeof (SpiceProtocol.Controller.Msg)) { t.resize ((int)msg.size); msg = (SpiceProtocol.Controller.Msg*)t; yield input_stream_read (c.input_stream, t[sizeof(SpiceProtocol.Controller.Msg):msg.size]); } handle_message (msg); } if (excl) excl_connection = null; } public Controller() { } public async void listen (string? addr = null) throws GLib.Error, SpiceCtrl.Error { var listener = ControllerListener.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