[Spice-devel] [PATCH spice 9/9] spicec: add controller

Arnon Gilboa agilboa at redhat.com
Sun Oct 17 07:13:47 PDT 2010


Spice client controller enables external control (e.g., by XPI or ActiveX) of
the client functionality.

The controller protocol enables setting parameters (host, port, sport, pwd,
secure channels, disabled channels, title, menus, hotkeys etc.), connecting
the server, showing and hiding the client etc.

The controller is based on the cross-platform named pipe.
---
 client/Makefile.am         |    3 +
 client/application.cpp     |  145 ++++++++++++---
 client/application.h       |   15 ++-
 client/controller.cpp      |  443 ++++++++++++++++++++++++++++++++++++++++++++
 client/controller.h        |  116 ++++++++++++
 client/controller_prot.h   |  115 ++++++++++++
 client/windows/redc.vcproj |   12 ++
 client/x11/Makefile.am     |    3 +
 8 files changed, 821 insertions(+), 31 deletions(-)
 create mode 100644 client/controller.cpp
 create mode 100644 client/controller.h
 create mode 100644 client/controller_prot.h

diff --git a/client/Makefile.am b/client/Makefile.am
index 0b3109b..8cceaf4 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -54,6 +54,9 @@ RED_COMMON_SRCS =			\
 	marshaller.cpp			\
 	generated_marshallers.cpp	\
 	generated_marshallers1.cpp	\
+	controller.cpp			\
+	controller.h			\
+	controller_prot.h		\
 	cursor_channel.cpp		\
 	cursor_channel.h		\
 	cursor.cpp			\
diff --git a/client/application.cpp b/client/application.cpp
index 9a9a3f6..3aaa3e3 100644
--- a/client/application.cpp
+++ b/client/application.cpp
@@ -357,6 +357,7 @@ Application::Application()
     , _monitors (NULL)
     , _title (L"SPICEc:%d")
     , _sys_key_intercept_mode (false)
+	, _enable_controller (false)
 #ifdef USE_GUI
     , _gui_mode (GUI_MODE_FULL)
 #endif // USE_GUI
@@ -595,6 +596,11 @@ int Application::run()
 void Application::on_start_running()
 {
     _foreign_menu.reset(new ForeignMenu(this));
+    if (_enable_controller) {
+        _controller.reset(new Controller(this));
+        return;
+    }
+    //FIXME: _client.connect() or use the following instead?
 #ifdef USE_GUI
     if (_gui_mode != GUI_MODE_FULL) {
         connect();
@@ -988,6 +994,9 @@ void Application::do_command(int command)
         if (item->type == APP_MENU_ITEM_TYPE_FOREIGN) {
             ASSERT(*_foreign_menu);
             (*_foreign_menu)->on_command(item->conn_ref, item->ext_id);
+        } else if (item->type == APP_MENU_ITEM_TYPE_CONTROLLER) {
+            ASSERT(*_controller);
+            (*_controller)->on_command(item->conn_ref, item->ext_id);
         }
     }
 }
@@ -1736,6 +1745,11 @@ void Application::update_menu()
 
 //controller interface begin
 
+void Application::set_auto_display_res(bool auto_display_res)
+{
+   _client.set_auto_display_res(auto_display_res);
+}
+
 bool Application::connect(const std::string& host, int port, int sport, const std::string& password)
 {
     if (_state != DISCONNECTED) {
@@ -1743,6 +1757,10 @@ bool Application::connect(const std::string& host, int port, int sport, const st
     }
     _client.set_target(host, port, sport);
     _client.set_password(password);
+    if (!set_channels_security(port, sport)) {
+        return false;
+    }
+    register_channels();
     connect();
     return true;
 }
@@ -1757,9 +1775,52 @@ void Application::quit()
     ProcessLoop::quit(SPICEC_ERROR_CODE_SUCCESS);
 }
 
+void Application::show_me(bool full_screen)
+{
+    if (full_screen) {
+        enter_full_screen();
+    } else {
+        _main_screen->show(true, NULL);
+    }
+}
+
 void Application::hide_me()
 {
-    hide_gui();
+//  hide_gui();
+//  FIXME: this instead?
+    if (_full_screen) {
+        exit_full_screen();
+    }
+    hide();
+}
+
+void Application::set_hotkeys(const std::string& hotkeys)
+{
+    std::auto_ptr<HotKeysParser> parser(new HotKeysParser(hotkeys, _commands_map));
+    _hot_keys = parser->get();
+}
+
+int Application::get_controller_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id)
+{
+    return get_menu_item_id(APP_MENU_ITEM_TYPE_CONTROLLER, opaque_conn_ref, msg_id);
+}
+
+void Application::set_menu(Menu* menu)
+{
+    if (menu) {
+        _app_menu.reset(menu->ref());
+    } else {
+        init_menu();
+    }
+    if (*_foreign_menu) {
+        (*_foreign_menu)->add_sub_menus();
+    }
+    update_menu();
+}
+
+void Application::delete_menu()
+{
+    set_menu(NULL);
 }
 
 #ifdef USE_GUI
@@ -1833,6 +1894,40 @@ bool Application::set_channels_security(CmdLineParser& parser, bool on, char *va
     return true;
 }
 
+bool Application::set_channels_security(int port, int sport)
+{
+    PeerConnectionOptMap::iterator iter = _peer_con_opt.begin();
+
+    for (; iter != _peer_con_opt.end(); iter++) {
+        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_SECURE) {
+            continue;
+        }
+
+        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_UNSECURE) {
+            continue;
+        }
+
+        if (port != -1 && sport != -1) {
+            (*iter).second = RedPeer::ConnectionOptions::CON_OP_BOTH;
+            continue;
+        }
+
+        if (port != -1) {
+            (*iter).second = RedPeer::ConnectionOptions::CON_OP_UNSECURE;
+            continue;
+        }
+
+        if (sport != -1) {
+            (*iter).second = RedPeer::ConnectionOptions::CON_OP_SECURE;
+            continue;
+        }
+
+        _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
+        return false;
+    }
+    return true;
+}
+
 bool Application::set_connection_ciphers(const char* ciphers, const char* arg0)
 {
     _con_ciphers = ciphers;
@@ -2026,7 +2121,7 @@ void Application::register_channels()
 
 bool Application::process_cmd_line(int argc, char** argv)
 {
-    std::string host;
+    std::string host = "";
     int sport = -1;
     int port = -1;
     bool auto_display_res = false;
@@ -2050,6 +2145,7 @@ bool Application::process_cmd_line(int argc, char** argv)
         SPICE_OPT_CANVAS_TYPE,
         SPICE_OPT_DISPLAY_COLOR_DEPTH,
         SPICE_OPT_DISABLE_DISPLAY_EFFECTS,
+        SPICE_OPT_CONTROLLER,
     };
 
 #ifdef USE_GUI
@@ -2068,7 +2164,6 @@ bool Application::process_cmd_line(int argc, char** argv)
     CmdLineParser parser("Spice client", false);
 
     parser.add(SPICE_OPT_HOST, "host", "spice server address", "host", true, 'h');
-    parser.set_reqired(SPICE_OPT_HOST);
     parser.add(SPICE_OPT_PORT, "port", "spice server port", "port", true, 'p');
     parser.add(SPICE_OPT_SPORT, "secure-port", "spice server secure port", "port", true, 's');
     parser.add(SPICE_OPT_SECURE_CHANNELS, "secure-channels",
@@ -2107,6 +2202,8 @@ bool Application::process_cmd_line(int argc, char** argv)
                "disable guest display effects", "wallpaper/font-smooth/animation/all", true);
     parser.set_multi(SPICE_OPT_DISABLE_DISPLAY_EFFECTS, ',');
 
+    parser.add(SPICE_OPT_CONTROLLER, "controller", "enable external controller");
+
     for (int i = SPICE_CHANNEL_MAIN; i < SPICE_END_CHANNEL; i++) {
         _peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_INVALID;
     }
@@ -2203,6 +2300,15 @@ bool Application::process_cmd_line(int argc, char** argv)
                 return false;
             }
             break;
+        case SPICE_OPT_CONTROLLER:
+            if (argc > 2) {
+                Platform::term_printf("%s: controller cannot be combined with other options\n",
+                                      argv[0]);
+                _exit_code = SPICEC_ERROR_CODE_INVALID_ARG;
+                return false;
+            }
+            _enable_controller = true;
+            return true;
         case CmdLineParser::OPTION_HELP:
             parser.show_help();
             return false;
@@ -2214,42 +2320,21 @@ bool Application::process_cmd_line(int argc, char** argv)
         }
     }
 
+    if (host.empty()) {
+        Platform::term_printf("%s: missing --host\n", argv[0]);
+        return false;
+    }
+
     if (parser.is_set(SPICE_OPT_SECURE_CHANNELS) && !parser.is_set(SPICE_OPT_SPORT)) {
         Platform::term_printf("%s: missing --secure-port\n", argv[0]);
         _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
         return false;
     }
 
-    PeerConnectionOptMap::iterator iter = _peer_con_opt.begin();
-    for (; iter != _peer_con_opt.end(); iter++) {
-        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_SECURE) {
-            continue;
-        }
-
-        if ((*iter).second == RedPeer::ConnectionOptions::CON_OP_UNSECURE) {
-            continue;
-        }
-
-        if (parser.is_set(SPICE_OPT_PORT) && parser.is_set(SPICE_OPT_SPORT)) {
-            (*iter).second = RedPeer::ConnectionOptions::CON_OP_BOTH;
-            continue;
-        }
-
-        if (parser.is_set(SPICE_OPT_PORT)) {
-            (*iter).second = RedPeer::ConnectionOptions::CON_OP_UNSECURE;
-            continue;
-        }
-
-        if (parser.is_set(SPICE_OPT_SPORT)) {
-            (*iter).second = RedPeer::ConnectionOptions::CON_OP_SECURE;
-            continue;
-        }
-
+    if (!set_channels_security(port, sport)) {
         Platform::term_printf("%s: missing --port or --sport\n", argv[0]);
-        _exit_code = SPICEC_ERROR_CODE_CMD_LINE_ERROR;
         return false;
     }
-
     register_channels();
 
     _client.set_target(host, port, sport);
diff --git a/client/application.h b/client/application.h
index d6355ca..2cff2d5 100644
--- a/client/application.h
+++ b/client/application.h
@@ -27,6 +27,7 @@
 #include "hot_keys.h"
 #include "process_loop.h"
 #include "foreign_menu.h"
+#include "controller.h"
 
 class RedScreen;
 class Application;
@@ -142,6 +143,7 @@ typedef std::list<GUIBarrier*> GUIBarriers;
 enum AppMenuItemType {
     APP_MENU_ITEM_TYPE_INVALID,
     APP_MENU_ITEM_TYPE_FOREIGN,
+    APP_MENU_ITEM_TYPE_CONTROLLER,
 };
 
 typedef struct AppMenuItem {
@@ -155,7 +157,8 @@ typedef std::map<int, AppMenuItem> AppMenuItemMap;
 class Application : public ProcessLoop,
                     public Platform::EventListener,
                     public Platform::DisplayModeListener,
-                    public ForeignMenuInterface {
+                    public ForeignMenuInterface,
+                    public ControllerInterface {
 public:
 
     enum State {
@@ -237,11 +240,18 @@ public:
     void update_menu();
 
     //controller interface begin
+    void set_auto_display_res(bool auto_display_res);
     bool connect(const std::string& host, int port, int sport, const std::string& password);
     void disconnect();
     void quit();
+    void show_me(bool full_screen);
     void hide_me();
+    void set_hotkeys(const std::string& hotkeys);
+    int get_controller_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id);
+    void set_menu(Menu* menu);
+    void delete_menu();
     void beep();
+
 #ifdef USE_GUI
     bool is_disconnect_allowed();
 #endif
@@ -260,6 +270,7 @@ public:
 
 private:
     bool set_channels_security(CmdLineParser& parser, bool on, char *val, const char* arg0);
+    bool set_channels_security(int port, int sport);
     bool set_connection_ciphers(const char* ciphers, const char* arg0);
     bool set_ca_file(const char* ca_file, const char* arg0);
     bool set_host_cert_subject(const char* subject, const char* arg0);
@@ -362,6 +373,8 @@ private:
     std::vector<int> _canvas_types;
     AutoRef<Menu> _app_menu;
     AutoRef<ForeignMenu> _foreign_menu;
+    bool _enable_controller;
+    AutoRef<Controller> _controller;
     AppMenuItemMap _app_menu_items;
 #ifdef USE_GUI
     std::auto_ptr<GUI> _gui;
diff --git a/client/controller.cpp b/client/controller.cpp
new file mode 100644
index 0000000..92823dc
--- /dev/null
+++ b/client/controller.cpp
@@ -0,0 +1,443 @@
+/*
+   Copyright (C) 2009 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 "common.h"
+#include "controller.h"
+#include "controller_prot.h"
+#include "cmd_line_parser.h"
+#include "menu.h"
+#include "utils.h"
+#include "debug.h"
+#include "platform.h"
+
+#define PIPE_NAME_MAX_LEN 50
+
+#ifdef WIN32
+#define PIPE_NAME "SpiceController-%lu"
+#elif defined(__i386__)
+#define PIPE_NAME "/tmp/SpiceController-%llu.uds"
+#else
+#define PIPE_NAME "/tmp/SpiceController-%lu.uds"
+#endif
+
+Controller::Controller(ControllerInterface *handler)
+    : _handler (handler)
+    , _exclusive (false)
+    , _refs (1)
+{
+    char pipe_name[PIPE_NAME_MAX_LEN];
+
+    ASSERT(_handler);
+    snprintf(pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME, Platform::get_process_id());
+    LOG_INFO("Creating a controller connection %s", pipe_name);
+    _pipe = NamedPipe::create(pipe_name, *this);
+    if (!_pipe) {
+        LOG_ERROR("Failed to create a controller connection");
+    }
+}
+
+Controller::~Controller()
+{
+    std::map<NamedPipe::ConnectionRef, ControllerConnection*>::const_iterator conn;
+    for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
+        conn->second->reset_handler();
+        delete conn->second;
+    }
+    if (_pipe) {
+        NamedPipe::destroy(_pipe);
+    }
+}
+
+NamedPipe::ConnectionInterface& Controller::create()
+{
+    ControllerConnection *conn = new ControllerConnection(_handler, *this);
+
+    if (conn == NULL) {
+        throw Exception("Error allocating a new controller connection");
+    }
+    return *conn;
+}
+
+bool Controller::set_exclusive(bool exclusive)
+{
+    if (_exclusive) {
+        LOG_ERROR("Cannot init controller, an exclusive controller already connected");
+        return false;
+    }
+    if (exclusive && _connections.size() > 1) {
+        LOG_ERROR("Cannot init exclusive controller, other controllers already connected");
+        return false;
+    }
+    _exclusive = exclusive;
+    return true;
+}
+
+void Controller::add_connection(NamedPipe::ConnectionRef conn_ref, ControllerConnection *conn)
+{
+    _connections[conn_ref] = conn;
+    conn->on_data();
+}
+
+void Controller::remove_connection(NamedPipe::ConnectionRef conn_ref)
+{
+    ControllerConnection *conn = _connections[conn_ref];
+    _connections.erase(conn_ref);
+    _exclusive = false;
+    delete conn;
+}
+
+void Controller::on_command(NamedPipe::ConnectionRef conn_ref, int32_t id)
+{
+    ControllerConnection *conn = _connections[conn_ref];
+    ControllerValue msg;
+
+    ASSERT(conn);
+    msg.base.id = CONTROLLER_MENU_ITEM_CLICK;
+    msg.base.size = sizeof(msg);
+    msg.value = id;
+    conn->write_msg(&msg.base, msg.base.size);
+}
+
+ControllerConnection::ControllerConnection(ControllerInterface *handler, Controller& parent)
+    : _handler (handler)
+    , _parent (parent)
+    , _initialized (false)
+    , _write_pending (0)
+    , _write_pos (_write_buf)
+    , _read_pos (_read_buf)
+    , _port (-1)
+    , _sport (-1)
+    , _full_screen (false)
+    , _auto_display_res (false)
+{
+}
+
+ControllerConnection::~ControllerConnection()
+{
+    if (_opaque != NamedPipe::INVALID_CONNECTION) {
+        NamedPipe::destroy_connection(_opaque);
+    }
+    if (_handler) {
+        _handler->clear_menu_items(_opaque);
+        _handler->delete_menu();
+    }
+}
+
+void ControllerConnection::bind(NamedPipe::ConnectionRef conn_ref)
+{
+    _opaque = conn_ref;
+    _parent.add_connection(conn_ref, this);
+}
+
+void ControllerConnection::on_data()
+{
+    if (_write_pending) {
+        LOG_INFO("Resume pending write %d", _write_pending);
+        if (!write_msg(_write_pos, _write_pending)) {
+            return;
+        }
+    }
+    while (read_msgs());
+}
+
+bool ControllerConnection::read_msgs()
+{
+    uint8_t *pos = _read_buf;
+    size_t nread = _read_pos - _read_buf;
+    int32_t size;
+
+    ASSERT(_handler);
+    ASSERT(_opaque != NamedPipe::INVALID_CONNECTION);
+    size = NamedPipe::read(_opaque, (uint8_t*)_read_pos, sizeof(_read_buf) - nread);
+    if (size == 0) {
+        return false;
+    } else if (size < 0) {
+        LOG_ERROR("Error reading from named pipe %d", size);
+        _parent.remove_connection(_opaque);
+        return false;
+    }
+    nread += size;
+    while (nread > 0) {
+        if (!_initialized && nread >= sizeof(ControllerInitHeader)) {
+            ControllerInitHeader *init = (ControllerInitHeader *)pos;
+            if (init->magic != CONTROLLER_MAGIC || init->version != CONTROLLER_VERSION) {
+                LOG_ERROR("Bad controller init, magic=0x%x version=%u", init->magic,
+                          init->version);
+                _parent.remove_connection(_opaque);
+                return false;
+            }
+            if (nread < init->size) {
+                break;
+            }
+            if (!handle_init((ControllerInit*)init)) {
+                _parent.remove_connection(_opaque);
+                return false;
+            }
+            nread -= init->size;
+            pos += init->size;
+            _initialized = true;
+        }
+        if (!_initialized || nread < sizeof(ControllerMsg)) {
+            break;
+        }
+        ControllerMsg *hdr = (ControllerMsg *)pos;
+        if (hdr->size < sizeof(ControllerMsg)) {
+            LOG_ERROR("Bad controller message, size=%u", hdr->size);
+            _parent.remove_connection(_opaque);
+            return false;
+        }
+        if (nread < hdr->size) {
+            break;
+        }
+        handle_message(hdr);
+        nread -= hdr->size;
+        pos += hdr->size;
+    }
+    if (nread > 0 && pos != _read_buf) {
+        memcpy(_read_buf, pos, nread);
+    }
+    _read_pos = _read_buf + nread;
+    return true;
+}
+
+bool ControllerConnection::write_msg(const void *buf, int len)
+{
+    RecurciveLock lock(_write_lock);
+    uint8_t *pos;
+    int32_t written = 0;
+
+    ASSERT(_opaque != NamedPipe::INVALID_CONNECTION);
+    if (_write_pending && buf != _write_pos) {
+        if ((_write_pos + _write_pending + len > _write_buf + sizeof(_write_buf)) &&
+                                              !write_msg(_write_pos, _write_pending)) {
+            return false;
+        }
+        if (_write_pending) {
+            if (_write_pos + _write_pending + len > _write_buf + sizeof(_write_buf)) {
+                DBG(0, "Dropping message, due to insufficient space in write buffer");
+                return true;
+            }
+            memcpy(_write_pos + _write_pending, buf, len);
+            _write_pending += len;
+        }
+    }
+    pos = (uint8_t*)buf;
+    while (len && (written = NamedPipe::write(_opaque, pos, len)) > 0) {
+        pos += written;
+        len -= written;
+    }
+    if (len && written == 0) {
+        if (_write_pending) {
+            _write_pos = pos;
+        } else {
+            _write_pos = _write_buf;
+            memcpy(_write_buf, pos, len);
+        }
+        _write_pending = len;
+    } else if (written < 0) {
+        LOG_ERROR("Error writing to named pipe %d", written);
+        _parent.remove_connection(_opaque);
+        return false;
+    } else {
+        _write_pending = 0;
+    }
+    return true;
+}
+
+bool ControllerConnection::handle_init(ControllerInit *init)
+{
+    ASSERT(_handler);
+    if (init->credentials != 0) {
+        LOG_ERROR("Controller menu has wrong credentials 0x%x", init->credentials);
+        return false;
+    }
+    if (!_parent.set_exclusive(init->flags & CONTROLLER_FLAG_EXCLUSIVE)) {
+        return false;
+    }
+    return true;
+}
+
+bool ControllerConnection::handle_message(ControllerMsg *hdr)
+{
+    uint32_t value = ((ControllerValue*)hdr)->value;
+    char *data = (char*)((ControllerData*)hdr)->data;
+
+    ASSERT(_handler);
+    switch (hdr->id) {
+    case CONTROLLER_HOST:
+        _host.assign(data);
+        break;
+    case CONTROLLER_PORT:
+        _port = value;
+        break;
+    case CONTROLLER_SPORT:
+        _sport = value;
+        break;
+    case CONTROLLER_PASSWORD:
+        _password.assign(data);
+        break;
+    case CONTROLLER_SECURE_CHANNELS:
+    case CONTROLLER_DISABLE_CHANNELS:
+        return set_multi_val(hdr->id, data);
+    case CONTROLLER_TLS_CIPHERS:
+        return _handler->set_connection_ciphers(data, "Controller");
+    case CONTROLLER_CA_FILE:
+        return _handler->set_ca_file(data, "Controller");
+    case CONTROLLER_HOST_SUBJECT:
+        return _handler->set_host_cert_subject(data, "Controller");
+    case CONTROLLER_FULL_SCREEN:
+        _full_screen = !!(value & CONTROLLER_SET_FULL_SCREEN);
+        _handler->set_auto_display_res(!!(value & CONTROLLER_AUTO_DISPLAY_RES));
+        break;
+    case CONTROLLER_SET_TITLE: {
+        std::wstring str;
+#ifdef WIN32
+        wstring_printf(str, L"%s", data);
+#else
+        wstring_printf(str, L"%S", data);
+#endif
+        _handler->set_title(str);
+        break;
+    }
+    case CONTROLLER_HOTKEYS:
+        _handler->set_hotkeys(data);
+        break;
+    case CONTROLLER_CONNECT:
+        _handler->connect(_host, _port, _sport, _password);
+        break;
+    case CONTROLLER_SHOW:
+        _handler->show_me(_full_screen);
+        break;
+    case CONTROLLER_HIDE:
+        _handler->hide_me();
+        break;
+    case CONTROLLER_CREATE_MENU:
+        return create_menu((wchar_t*)data);
+    case CONTROLLER_DELETE_MENU:
+        _handler->delete_menu();
+        break;
+    case CONTROLLER_SEND_CAD:
+    default:
+        LOG_ERROR("Ignoring an unknown controller message %u", hdr->id);
+        return false;
+    }
+    return true;
+}
+
+#ifdef WIN32
+#define next_tok(str, delim, state) wcstok(str, delim)
+#else
+#define next_tok(str, delim, state) wcstok(str, delim, state)
+#endif
+
+bool ControllerConnection::create_menu(wchar_t* resource)
+{
+    bool ret = true;
+    wchar_t* item_state = 0;
+    wchar_t* item_dup;
+    wchar_t* param;
+    std::string text;
+    int parent_id;
+    int flags;
+    int state;
+    int id;
+
+    ASSERT(_handler);
+    AutoRef<Menu> app_menu(_handler->get_app_menu());
+    AutoRef<Menu> menu(new Menu((*app_menu)->get_target(), ""));
+    wchar_t* item = next_tok(resource, CONTROLLER_MENU_ITEM_DELIMITER, &item_state);
+    while (item != NULL) {
+        item_dup = wcsdup(item);
+        ret = ret && (param = next_tok(item_dup, CONTROLLER_MENU_PARAM_DELIMITER, &item_state)) &&
+              swscanf(param, L"%d", &parent_id);
+        ret = ret && (param = next_tok(NULL, CONTROLLER_MENU_PARAM_DELIMITER, &item_state)) &&
+              swscanf(param, L"%d", &id);
+        ret = ret && (param = next_tok(NULL, CONTROLLER_MENU_PARAM_DELIMITER, &item_state));
+        if (ret) {
+            string_printf(text, "%S", param);
+        }
+        ret = ret && (param = next_tok(NULL, CONTROLLER_MENU_PARAM_DELIMITER, &item_state)) &&
+              swscanf(param, L"%d", &flags);
+        free(item_dup);
+
+        if (!ret) {
+            DBG(0, "item parsing failed %S", item);
+            break;
+        }
+        DBG(0, "parent_id=%d, id=%d, text=%s, flags=%d", parent_id, id, text.c_str(), flags);
+
+        AutoRef<Menu> sub_menu((*menu)->find_sub(parent_id));
+        if (!(ret = !!*sub_menu)) {
+            DBG(0, "submenu not found %S", item);
+            break;
+        }
+
+        if (flags & CONTROLLER_MENU_FLAGS_SEPARATOR) {
+            (*sub_menu)->add_separator();
+        } else if (flags & CONTROLLER_MENU_FLAGS_POPUP) {
+            AutoRef<Menu> sub(new Menu((*app_menu)->get_target(), text, id));
+            (*sub_menu)->add_sub(sub.release());
+        } else {
+            state = 0;
+            if (flags & (CONTROLLER_MENU_FLAGS_DISABLED | CONTROLLER_MENU_FLAGS_GRAYED)) {
+                state |= Menu::MENU_ITEM_STATE_DIM;
+            }
+            if (flags & CONTROLLER_MENU_FLAGS_CHECKED) {
+                state |= Menu::MENU_ITEM_STATE_CHECKED;
+            }
+            if (id >= SPICE_MENU_INTERNAL_ID_BASE) {
+                id = ((id - SPICE_MENU_INTERNAL_ID_BASE) >> SPICE_MENU_INTERNAL_ID_SHIFT) + 1;
+            } else {
+                id = _handler->get_controller_menu_item_id(_opaque, id);
+            }
+            (*sub_menu)->add_command(text, id, state);
+        }
+        item = next_tok(item + wcslen(item) + 1, CONTROLLER_MENU_ITEM_DELIMITER, &item_state);
+    }
+    if (ret) {
+        _handler->set_menu(*menu);
+    }
+    return ret;
+}
+
+bool ControllerConnection::set_multi_val(uint32_t op, char* multi_val)
+{
+    CmdLineParser parser("", false);
+    int id = CmdLineParser::OPTION_FIRST_AVILABLE;
+    char* argv[] = {NULL, (char*)"--set", multi_val};
+    char* val;
+
+    ASSERT(_handler);
+    parser.add(id, "set", "none", "none", true);
+    parser.set_multi(id, ',');
+    parser.begin(3, argv);
+    if (parser.get_option(&val) != id) {
+        return false;
+    }
+    switch (op) {
+    case CONTROLLER_SECURE_CHANNELS:
+        _handler->set_channels_security(parser, true, val, "Controller");
+        break;
+    case CONTROLLER_DISABLE_CHANNELS:
+        _handler->set_enable_channels(parser, false, val, "Controller");
+        break;
+    default:
+        DBG(0, "unsupported op %u", op);
+        return false;
+    }
+    return true;
+}
diff --git a/client/controller.h b/client/controller.h
new file mode 100644
index 0000000..89b2c23
--- /dev/null
+++ b/client/controller.h
@@ -0,0 +1,116 @@
+/*
+   Copyright (C) 2009 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 _H_CONTROLLER_MENU
+#define _H_CONTROLLER_MENU
+
+#include "named_pipe.h"
+#include "threads.h"
+
+class ControllerConnection;
+struct ControllerInit;
+struct ControllerMsg;
+class CmdLineParser;
+class Menu;
+
+class ControllerInterface {
+public:
+    virtual ~ControllerInterface() {}
+
+    virtual bool connect(const std::string& host, int port, int sport,
+                         const std::string& password) = 0;
+    virtual void set_title(const std::wstring& title) = 0;
+    virtual void set_auto_display_res(bool auto_display_res) = 0;
+    virtual void show_me(bool full_screen) = 0;
+    virtual void hide_me() = 0;
+    virtual bool set_channels_security(CmdLineParser& parser, bool on, char *val,
+                                       const char* arg0) = 0;
+    virtual bool set_enable_channels(CmdLineParser& parser, bool enable, char *val,
+                                     const char* arg0) = 0;
+    virtual bool set_connection_ciphers(const char* ciphers, const char* arg0) = 0;
+    virtual bool set_ca_file(const char* ca_file, const char* arg0) = 0;
+    virtual bool set_host_cert_subject(const char* subject, const char* arg0) = 0;
+    virtual void set_hotkeys(const std::string& hotkeys) = 0;
+    virtual int get_controller_menu_item_id(int32_t opaque_conn_ref, uint32_t id) = 0;
+    virtual void clear_menu_items(int32_t opaque_conn_ref) = 0;
+    virtual Menu* get_app_menu() = 0;
+    virtual void set_menu(Menu* menu) = 0;
+    virtual void delete_menu() = 0;
+};
+
+class Controller : public NamedPipe::ListenerInterface {
+public:
+    Controller(ControllerInterface *handler);
+    virtual ~Controller();
+
+    Controller* ref() { _refs++; return this;}
+    void unref() { if (!--_refs) delete this;}
+
+    virtual NamedPipe::ConnectionInterface &create();
+    bool set_exclusive(bool exclusive);
+    void add_connection(NamedPipe::ConnectionRef conn_ref, ControllerConnection *conn);
+    void remove_connection(NamedPipe::ConnectionRef conn_ref);
+    void on_command(NamedPipe::ConnectionRef conn_ref, int32_t id);
+
+private:
+    ControllerInterface *_handler;
+    std::map<NamedPipe::ConnectionRef, ControllerConnection*> _connections;
+    NamedPipe::ListenerRef _pipe;
+    bool _exclusive;
+    int _refs;
+};
+
+#define CONTROLLER_BUF_SIZE 4096
+
+class ControllerConnection : public NamedPipe::ConnectionInterface {
+public:
+    ControllerConnection(ControllerInterface *handler, Controller& parent);
+    virtual ~ControllerConnection();
+
+    virtual void bind(NamedPipe::ConnectionRef conn_ref);
+    virtual void on_data();
+    bool write_msg(const void *buf, int len);
+    void reset_handler() { _handler = NULL;}
+
+private:
+    bool read_msgs();
+    bool handle_init(ControllerInit *init);
+    bool handle_message(ControllerMsg *hdr);
+    bool create_menu(wchar_t* resource);
+    bool set_multi_val(uint32_t op, char* multi_val);
+
+private:
+    ControllerInterface *_handler;
+    Controller& _parent;
+    bool _initialized;
+
+    int _write_pending;
+    uint8_t *_write_pos;
+    uint8_t *_read_pos;
+    uint8_t _write_buf[CONTROLLER_BUF_SIZE];
+    uint8_t _read_buf[CONTROLLER_BUF_SIZE];
+    RecurciveMutex _write_lock;
+
+    std::string _host;
+    std::string _password;
+    int _port;
+    int _sport;
+    bool _full_screen;
+    bool _auto_display_res;
+};
+
+#endif
diff --git a/client/controller_prot.h b/client/controller_prot.h
new file mode 100644
index 0000000..6cf4ca0
--- /dev/null
+++ b/client/controller_prot.h
@@ -0,0 +1,115 @@
+/*
+   Copyright (C) 2009 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 _H_CONTROLLER_PROT
+#define _H_CONTROLLER_PROT
+
+#define CONTROLLER_MAGIC      (*(uint32_t*)"CTRL")
+#define CONTROLLER_VERSION    1
+
+#ifdef __GNUC__
+#define ATTR_PACKED __attribute__ ((__packed__))
+#else
+#define ATTR_PACKED __declspec(align(1))
+#endif
+
+typedef struct ATTR_PACKED ControllerInitHeader {
+    uint32_t magic;
+    uint32_t version;
+    uint32_t size;
+} ControllerInitHeader;
+
+typedef struct ATTR_PACKED ControllerInit {
+    ControllerInitHeader base;
+    uint64_t credentials;
+    uint32_t flags;
+} ControllerInit;
+
+enum {
+    CONTROLLER_FLAG_EXCLUSIVE = 1 << 0,
+};
+
+typedef struct ATTR_PACKED ControllerMsg {
+    uint32_t id;
+    uint32_t size;
+} ControllerMsg;
+
+enum {
+    //extrenal app -> spice client
+    CONTROLLER_HOST = 1,
+    CONTROLLER_PORT,
+    CONTROLLER_SPORT,
+    CONTROLLER_PASSWORD,
+
+    CONTROLLER_SECURE_CHANNELS,
+    CONTROLLER_DISABLE_CHANNELS,
+
+    CONTROLLER_TLS_CIPHERS,
+    CONTROLLER_CA_FILE,
+    CONTROLLER_HOST_SUBJECT,
+
+    CONTROLLER_FULL_SCREEN,
+    CONTROLLER_SET_TITLE,
+
+    CONTROLLER_CREATE_MENU,
+    CONTROLLER_DELETE_MENU,
+
+    CONTROLLER_HOTKEYS,
+    CONTROLLER_SEND_CAD,
+
+    CONTROLLER_CONNECT,
+    CONTROLLER_SHOW,
+    CONTROLLER_HIDE,
+
+    //spice client -> extrenal app
+    CONTROLLER_MENU_ITEM_CLICK = 1001,
+};
+
+#define CONTROLLER_TRUE (1 << 0)
+
+enum {
+    CONTROLLER_SET_FULL_SCREEN  = CONTROLLER_TRUE,
+    CONTROLLER_AUTO_DISPLAY_RES = 1 << 1,
+};
+
+typedef struct ATTR_PACKED ControllerValue {
+    ControllerMsg base;
+    uint32_t value;
+} ControllerValue;
+
+typedef struct ATTR_PACKED ControllerData {
+    ControllerMsg base;
+    uint8_t data[0];
+} ControllerData;
+
+#define CONTROLLER_MENU_ITEM_DELIMITER L"\n"
+#define CONTROLLER_MENU_PARAM_DELIMITER L"\r"
+
+enum {
+    CONTROLLER_MENU_FLAGS_SEPARATOR    = 1 << 0,
+    CONTROLLER_MENU_FLAGS_DISABLED     = 1 << 1,
+    CONTROLLER_MENU_FLAGS_POPUP        = 1 << 2,
+    CONTROLLER_MENU_FLAGS_CHECKED      = 1 << 3,
+    CONTROLLER_MENU_FLAGS_GRAYED       = 1 << 4,
+};
+
+#define SPICE_MENU_INTERNAL_ID_BASE   0x1300
+#define SPICE_MENU_INTERNAL_ID_SHIFT  8
+
+#undef ATTR_PACKED
+
+#endif
diff --git a/client/windows/redc.vcproj b/client/windows/redc.vcproj
index 4ed240f..21994da 100644
--- a/client/windows/redc.vcproj
+++ b/client/windows/redc.vcproj
@@ -212,6 +212,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\controller.cpp"
+				>
+			</File>
+			<File
 				RelativePath="..\cursor.cpp"
 				>
 			</File>
@@ -484,6 +488,14 @@
 				>
 			</File>
 			<File
+				RelativePath="..\controller.h"
+				>
+			</File>
+			<File
+				RelativePath="..\controller_prot.h"
+				>
+			</File>
+			<File
 				RelativePath="..\cursor.h"
 				>
 			</File>
diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am
index fd3266b..c6038f0 100644
--- a/client/x11/Makefile.am
+++ b/client/x11/Makefile.am
@@ -54,6 +54,9 @@ RED_COMMON_SRCS =					\
 	$(CLIENT_DIR)/client_net_socket.cpp		\
 	$(CLIENT_DIR)/client_net_socket.h		\
 	$(CLIENT_DIR)/common.h				\
+	$(CLIENT_DIR)/controller.cpp			\
+	$(CLIENT_DIR)/controller.h			\
+	$(CLIENT_DIR)/controller_prot.h			\
 	$(CLIENT_DIR)/cursor_channel.cpp		\
 	$(CLIENT_DIR)/cursor_channel.h			\
 	$(CLIENT_DIR)/cursor.cpp			\
-- 
1.5.5.6



More information about the Spice-devel mailing list