[Spice-commits] 9 commits - client/application.cpp client/application.h client/cmd_line_parser.cpp client/controller.cpp client/controller.h client/foreign_menu.cpp client/foreign_menu.h client/Makefile.am client/menu.cpp client/menu.h client/process_loop.cpp client/process_loop.h client/red_client.h client/screen.cpp client/screen.h client/windows client/x11

Arnon Gilboa agilboa at kemper.freedesktop.org
Mon Oct 18 01:26:27 PDT 2010


 client/Makefile.am            |    4 
 client/application.cpp        |  219 +++++++++++++++++---
 client/application.h          |   39 +++
 client/cmd_line_parser.cpp    |    5 
 client/controller.cpp         |  443 ++++++++++++++++++++++++++++++++++++++++++
 client/controller.h           |  116 ++++++++++
 client/foreign_menu.cpp       |  364 ++++++++++++++++++++++++++++++++++
 client/foreign_menu.h         |   98 +++++++++
 client/menu.cpp               |   18 +
 client/menu.h                 |    5 
 client/process_loop.cpp       |    2 
 client/process_loop.h         |    1 
 client/red_client.h           |    2 
 client/screen.cpp             |    9 
 client/screen.h               |    1 
 client/windows/named_pipe.cpp |    4 
 client/windows/named_pipe.h   |    3 
 client/windows/red_window.cpp |    2 
 client/windows/redc.vcproj    |   26 +-
 client/x11/Makefile.am        |    4 
 20 files changed, 1315 insertions(+), 50 deletions(-)

New commits:
commit 4d0e6e525c0d2007ff9846ff3593e654b8b2e0ba
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Mon Oct 18 10:17:28 2010 +0200

    spicec: add controller
    
    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.

diff --git a/client/Makefile.am b/client/Makefile.am
index 09f11d5..5a14f5f 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -54,6 +54,8 @@ RED_COMMON_SRCS =			\
 	marshaller.cpp			\
 	generated_marshallers.cpp	\
 	generated_marshallers1.cpp	\
+	controller.cpp			\
+	controller.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..b293771
--- /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 <spice/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/windows/redc.vcproj b/client/windows/redc.vcproj
index 538d2cb..4276b90 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,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\controller.h"
+				>
+			</File>
+			<File
 				RelativePath="..\cursor.h"
 				>
 			</File>
diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am
index f6e9fda..45ff7fc 100644
--- a/client/x11/Makefile.am
+++ b/client/x11/Makefile.am
@@ -54,6 +54,8 @@ 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)/cursor_channel.cpp		\
 	$(CLIENT_DIR)/cursor_channel.h			\
 	$(CLIENT_DIR)/cursor.cpp			\
commit 20c550d278ed5833bb25c3bfb9d35d775fdb5291
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Mon Oct 18 10:03:46 2010 +0200

    spicec: add foreign menu
    
    Spice foreign menu enables external control of the client menu.
    
    The foreignmenu protocol enables an external application to:
    add a submenu, set its title, clear it, add/modify/remove an item etc.
    
    Foreign menu is based on the cross-platform named pipe.

diff --git a/client/Makefile.am b/client/Makefile.am
index 185518a..09f11d5 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -61,6 +61,8 @@ RED_COMMON_SRCS =			\
 	debug.h				\
 	display_channel.cpp		\
 	display_channel.h		\
+	foreign_menu.cpp		\
+	foreign_menu.h			\
 	glz_decoded_image.h		\
 	glz_decoder_config.h		\
 	glz_decoder.cpp			\
diff --git a/client/application.cpp b/client/application.cpp
index c5d34ff..9a9a3f6 100644
--- a/client/application.cpp
+++ b/client/application.cpp
@@ -335,6 +335,8 @@ enum AppCommands {
 #ifdef USE_GUI
     APP_CMD_SHOW_GUI,
 #endif // USE_GUI
+    APP_CMD_EXTERNAL_BEGIN = 0x400,
+    APP_CMD_EXTERNAL_END = 0x800,
 };
 
 Application::Application()
@@ -586,6 +588,13 @@ void Application::switch_host(const std::string& host, int port, int sport,
 
 int Application::run()
 {
+    _exit_code = ProcessLoop::run();
+    return _exit_code;
+}
+
+void Application::on_start_running()
+{
+    _foreign_menu.reset(new ForeignMenu(this));
 #ifdef USE_GUI
     if (_gui_mode != GUI_MODE_FULL) {
         connect();
@@ -595,8 +604,6 @@ int Application::run()
 #else
     connect();
 #endif // HAVE_GUI
-    _exit_code = ProcessLoop::run();
-    return _exit_code;
 }
 
 RedScreen* Application::find_screen(int id)
@@ -974,6 +981,14 @@ void Application::do_command(int command)
         show_gui();
         break;
 #endif // USE_GUI
+    default:
+        AppMenuItemMap::iterator iter = _app_menu_items.find(command);
+        ASSERT(iter != _app_menu_items.end());
+        AppMenuItem* item = &(*iter).second;
+        if (item->type == APP_MENU_ITEM_TYPE_FOREIGN) {
+            ASSERT(*_foreign_menu);
+            (*_foreign_menu)->on_command(item->conn_ref, item->ext_id);
+        }
     }
 }
 
@@ -1318,12 +1333,18 @@ void Application::on_app_activated()
 {
     _active = true;
     _key_handler->on_focus_in();
+    if (*_foreign_menu) {
+        (*_foreign_menu)->on_activate();
+    }
 }
 
 void Application::on_app_deactivated()
 {
     _active = false;
     _key_handler->on_focus_out();
+    if (*_foreign_menu) {
+        (*_foreign_menu)->on_deactivate();
+    }
 #ifdef WIN32
     if (!_changing_screens) {
         exit_full_screen();
@@ -1573,7 +1594,7 @@ uint32_t Application::get_mouse_mode()
     return _client.get_mouse_mode();
 }
 
-void Application::set_title(std::wstring& title)
+void Application::set_title(const std::wstring& title)
 {
     _title = title;
 
@@ -1665,6 +1686,53 @@ void Application::send_hotkey_key_set(const HotkeySet& key_set)
     }
 }
 
+int Application::get_menu_item_id(AppMenuItemType type, int32_t conn_ref, uint32_t ext_id)
+{
+    int free_id = APP_CMD_EXTERNAL_BEGIN;
+    AppMenuItem item = {type, conn_ref, ext_id};
+    AppMenuItemMap::iterator iter = _app_menu_items.begin();
+    for (; iter != _app_menu_items.end(); iter++) {
+        if (!memcmp(&(*iter).second, &item, sizeof(item))) {
+            return (*iter).first;
+        } else if (free_id == (*iter).first && ++free_id > APP_CMD_EXTERNAL_END) {
+            return APP_CMD_INVALID;
+        }
+    }
+    _app_menu_items[free_id] = item;
+    return free_id;
+}
+
+void Application::clear_menu_items(int32_t opaque_conn_ref)
+{
+    AppMenuItemMap::iterator iter = _app_menu_items.begin();
+    AppMenuItemMap::iterator curr;
+
+    while (iter != _app_menu_items.end()) {
+        curr = iter++;
+        if (((*curr).second).conn_ref == opaque_conn_ref) {
+            _app_menu_items.erase(curr);
+        }
+    }
+}
+
+void Application::remove_menu_item(int item_id)
+{
+    _app_menu_items.erase(item_id);
+}
+
+int Application::get_foreign_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id)
+{
+    return get_menu_item_id(APP_MENU_ITEM_TYPE_FOREIGN, opaque_conn_ref, msg_id);
+}
+
+void Application::update_menu()
+{
+    for (size_t i = 0; i < _screens.size(); ++i) {
+        if (_screens[i]) {
+            _screens[i]->update_menu();
+        }
+    }
+}
 
 //controller interface begin
 
diff --git a/client/application.h b/client/application.h
index 36ae86e..d6355ca 100644
--- a/client/application.h
+++ b/client/application.h
@@ -26,6 +26,7 @@
 #include "menu.h"
 #include "hot_keys.h"
 #include "process_loop.h"
+#include "foreign_menu.h"
 
 class RedScreen;
 class Application;
@@ -138,10 +139,23 @@ typedef std::list<KeyHandler*> KeyHandlersStack;
 typedef std::list<GUIBarrier*> GUIBarriers;
 #endif // USE_GUI
 
+enum AppMenuItemType {
+    APP_MENU_ITEM_TYPE_INVALID,
+    APP_MENU_ITEM_TYPE_FOREIGN,
+};
+
+typedef struct AppMenuItem {
+    AppMenuItemType type;
+    int32_t conn_ref;
+    uint32_t ext_id;
+} AppMenuItem;
+
+typedef std::map<int, AppMenuItem> AppMenuItemMap;
+
 class Application : public ProcessLoop,
                     public Platform::EventListener,
                     public Platform::DisplayModeListener,
-                    public CommandTarget {
+                    public ForeignMenuInterface {
 public:
 
     enum State {
@@ -186,6 +200,7 @@ public:
     void on_activate_screen(RedScreen* screen);
     void on_start_screen_key_interception(RedScreen* screen);
     void on_stop_screen_key_interception(RedScreen* screen);
+    virtual void on_start_running();
     virtual void on_app_activated();
     virtual void on_app_deactivated();
     virtual void on_monitors_change();
@@ -200,7 +215,7 @@ public:
     void exit_full_screen();
     bool toggle_full_screen();
     void minimize();
-    void set_title(std::wstring& title);
+    void set_title(const std::wstring& title);
     void hide();
     void show();
     void external_show();
@@ -216,6 +231,10 @@ public:
     Menu* get_app_menu();
     virtual void do_command(int command);
 
+    int get_foreign_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id);
+    void clear_menu_items(int32_t opaque_conn_ref);
+    void remove_menu_item(int item_id);
+    void update_menu();
 
     //controller interface begin
     bool connect(const std::string& host, int port, int sport, const std::string& password);
@@ -276,6 +295,7 @@ private:
     void send_command_hotkey(int command);
     void send_hotkey_key_set(const HotkeySet& key_set);
     void menu_item_callback(unsigned int item_id);
+    int get_menu_item_id(AppMenuItemType type, int32_t conn_ref, uint32_t ext_id);
     int get_hotkeys_commnad();
     bool is_key_set_pressed(const HotkeySet& key_set);
     void do_on_key_up(RedKey key);
@@ -341,6 +361,8 @@ private:
     StickyInfo _sticky_info;
     std::vector<int> _canvas_types;
     AutoRef<Menu> _app_menu;
+    AutoRef<ForeignMenu> _foreign_menu;
+    AppMenuItemMap _app_menu_items;
 #ifdef USE_GUI
     std::auto_ptr<GUI> _gui;
     AutoRef<GUITimer> _gui_timer;
diff --git a/client/foreign_menu.cpp b/client/foreign_menu.cpp
new file mode 100644
index 0000000..e5d7459
--- /dev/null
+++ b/client/foreign_menu.cpp
@@ -0,0 +1,364 @@
+/*
+   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 "foreign_menu.h"
+#include <spice/foreign_menu_prot.h>
+#include "menu.h"
+#include "utils.h"
+#include "debug.h"
+#include "platform.h"
+
+#define PIPE_NAME_MAX_LEN 50
+
+#ifdef WIN32
+#define PIPE_NAME "SpiceForeignMenu-%lu"
+#elif defined(__i386__)
+#define PIPE_NAME "/tmp/SpiceForeignMenu-%llu.uds"
+#else
+#define PIPE_NAME "/tmp/SpiceForeignMenu-%lu.uds"
+#endif
+
+ForeignMenu::ForeignMenu(ForeignMenuInterface *handler)
+    : _handler (handler)
+    , _active (false)
+    , _refs (1)
+{
+    char pipe_name[PIPE_NAME_MAX_LEN];
+
+    ASSERT(_handler != NULL);
+    snprintf(pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME, Platform::get_process_id());
+    LOG_INFO("Creating a foreign menu connection %s", pipe_name);
+    _foreign_menu = NamedPipe::create(pipe_name, *this);
+    if (!_foreign_menu) {
+        LOG_ERROR("Failed to create a foreign menu connection");
+    }
+}
+
+ForeignMenu::~ForeignMenu()
+{
+    std::map<NamedPipe::ConnectionRef, ForeignMenuConnection*>::const_iterator conn;
+    for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
+        conn->second->reset_handler();
+        delete conn->second;
+    }
+    if (_foreign_menu) {
+        NamedPipe::destroy(_foreign_menu);
+    }
+}
+
+NamedPipe::ConnectionInterface& ForeignMenu::create()
+{
+    ForeignMenuConnection *conn = new ForeignMenuConnection(_handler, *this);
+
+    if (conn == NULL) {
+        throw Exception("Error allocating a new foreign menu connection");
+    }
+    return *conn;
+}
+
+void ForeignMenu::add_connection(NamedPipe::ConnectionRef conn_ref, ForeignMenuConnection *conn)
+{
+    _connections[conn_ref] = conn;
+    if (_active) {
+        send_active_state(conn, FOREIGN_MENU_APP_ACTIVATED);
+    }
+    conn->on_data();
+}
+
+void ForeignMenu::remove_connection(NamedPipe::ConnectionRef conn_ref)
+{
+    ForeignMenuConnection *conn = _connections[conn_ref];
+    _connections.erase(conn_ref);
+    delete conn;
+}
+
+void ForeignMenu::add_sub_menus()
+{
+    std::map<NamedPipe::ConnectionRef, ForeignMenuConnection*>::const_iterator conn;
+    for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
+        conn->second->add_sub_menu();
+    }
+}
+
+void ForeignMenu::on_command(NamedPipe::ConnectionRef conn_ref, int32_t id)
+{
+    ForeignMenuConnection *conn = _connections[conn_ref];
+    FrgMenuEvent msg;
+
+    ASSERT(conn);
+    msg.base.id = FOREIGN_MENU_ITEM_EVENT;
+    msg.base.size = sizeof(FrgMenuEvent);
+    msg.id = id;
+    msg.action = FOREIGN_MENU_EVENT_CLICK;
+    conn->write_msg(&msg.base, msg.base.size);
+}
+
+void ForeignMenu::on_activate()
+{
+    std::map<NamedPipe::ConnectionRef, ForeignMenuConnection*>::const_iterator conn;
+    _active = true;
+    for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
+        send_active_state(conn->second, FOREIGN_MENU_APP_ACTIVATED);
+    }
+}
+
+void ForeignMenu::on_deactivate()
+{
+    std::map<NamedPipe::ConnectionRef, ForeignMenuConnection*>::const_iterator conn;
+    _active = false;
+    for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
+        send_active_state(conn->second, FOREIGN_MENU_APP_DEACTIVATED);
+    }
+}
+
+void ForeignMenu::send_active_state(ForeignMenuConnection *conn, int32_t cmd)
+{
+    FrgMenuMsg msg;
+
+    ASSERT(conn != NULL);
+    msg.id = cmd;
+    msg.size = sizeof(FrgMenuMsg);
+    conn->write_msg(&msg, msg.size);
+}
+
+ForeignMenuConnection::ForeignMenuConnection(ForeignMenuInterface *handler, ForeignMenu& parent)
+    : _handler (handler)
+    , _parent (parent)
+    , _sub_menu (NULL)
+    , _initialized (false)
+    , _write_pending (0)
+    , _write_pos (_write_buf)
+    , _read_pos (_read_buf)
+{
+}
+
+ForeignMenuConnection::~ForeignMenuConnection()
+{
+    if (_opaque != NamedPipe::INVALID_CONNECTION) {
+        NamedPipe::destroy_connection(_opaque);
+    }
+    if (_handler) {
+        AutoRef<Menu> app_menu(_handler->get_app_menu());
+        (*app_menu)->remove_sub(_sub_menu);
+        _handler->update_menu();
+        _handler->clear_menu_items(_opaque);
+    }
+    if (_sub_menu) {
+        _sub_menu->unref();
+    }
+}
+
+void ForeignMenuConnection::bind(NamedPipe::ConnectionRef conn_ref)
+{
+    _opaque = conn_ref;
+    _parent.add_connection(conn_ref, this);
+}
+
+void ForeignMenuConnection::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 ForeignMenuConnection::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(FrgMenuInitHeader)) {
+            FrgMenuInitHeader *init = (FrgMenuInitHeader *)pos;
+            if (init->magic != FOREIGN_MENU_MAGIC || init->version != FOREIGN_MENU_VERSION) {
+                LOG_ERROR("Bad foreign menu init, magic=0x%x version=%u", init->magic,
+                          init->version);
+                _parent.remove_connection(_opaque);
+                return false;
+            }
+            if (nread < init->size) {
+                break;
+            }
+            if (!handle_init((FrgMenuInit*)init)) {
+                _parent.remove_connection(_opaque);
+                return false;
+            }
+            nread -= init->size;
+            pos += init->size;
+            _initialized = true;
+        }
+        if (!_initialized || nread < sizeof(FrgMenuMsg)) {
+            break;
+        }
+        FrgMenuMsg *hdr = (FrgMenuMsg *)pos;
+        if (hdr->size < sizeof(FrgMenuMsg)) {
+            LOG_ERROR("Bad foreign menu 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 ForeignMenuConnection::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 ForeignMenuConnection::handle_init(FrgMenuInit *init)
+{
+    std::string title = "Untitled";
+
+    ASSERT(_handler);
+    if (_sub_menu) {
+        LOG_ERROR("Foreign menu already initialized");
+        return false;
+    }
+    if (init->credentials != 0) {
+        LOG_ERROR("Foreign menu has wrong credentials 0x%x", init->credentials);
+        return false;
+    }
+    if (init->base.size > offsetof(FrgMenuInit, title)) {
+        ((char*)init)[init->base.size - 1] = '\0';
+        title = (char*)init->title;
+    }
+    _sub_menu = new Menu((CommandTarget&)*_handler, title);
+    add_sub_menu();
+    _handler->update_menu();
+    return true;
+}
+
+void ForeignMenuConnection::add_sub_menu()
+{
+    if (_sub_menu) {
+        AutoRef<Menu> app_menu(_handler->get_app_menu());
+        (*app_menu)->add_sub(_sub_menu);
+    }
+}
+
+bool ForeignMenuConnection::handle_message(FrgMenuMsg *hdr)
+{
+    ASSERT(_sub_menu);
+    ASSERT(_handler);
+    switch (hdr->id) {
+    case FOREIGN_MENU_SET_TITLE:
+        ((char*)hdr)[hdr->size - 1] = '\0';
+        _sub_menu->set_name((char*)((FrgMenuSetTitle*)hdr)->string);
+        break;
+    case FOREIGN_MENU_ADD_ITEM: {
+        FrgMenuAddItem *msg = (FrgMenuAddItem*)hdr;
+        ((char*)hdr)[hdr->size - 1] = '\0';
+        int id = _handler->get_foreign_menu_item_id(_opaque, msg->id);
+        _sub_menu->add_command((char*)msg->string, id, get_item_state(msg->type));
+        break;
+    }
+    case FOREIGN_MENU_REMOVE_ITEM: {
+        int id = _handler->get_foreign_menu_item_id(_opaque, ((FrgMenuRmItem*)hdr)->id);
+        _sub_menu->remove_command(id);
+        _handler->remove_menu_item(id);
+        break;
+    }
+    case FOREIGN_MENU_CLEAR:
+        _sub_menu->clear();
+        _handler->clear_menu_items(_opaque);
+        break;
+    case FOREIGN_MENU_MODIFY_ITEM:
+    default:
+        LOG_ERROR("Ignoring an unknown foreign menu identifier %u", hdr->id);
+        return false;
+    }
+    _handler->update_menu();
+    return true;
+}
+
+int ForeignMenuConnection::get_item_state(int item_type)
+{
+    int state = 0;
+
+    if (item_type & FOREIGN_MENU_ITEM_TYPE_CHECKED) {
+        state |= Menu::MENU_ITEM_STATE_CHECKED;
+    }
+    if (item_type & FOREIGN_MENU_ITEM_TYPE_DIM) {
+        state |= Menu::MENU_ITEM_STATE_DIM;
+    }
+    return state;
+}
diff --git a/client/foreign_menu.h b/client/foreign_menu.h
new file mode 100644
index 0000000..2fc4e53
--- /dev/null
+++ b/client/foreign_menu.h
@@ -0,0 +1,98 @@
+/*
+   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_FOREIGN_MENU
+#define _H_FOREIGN_MENU
+
+#include "named_pipe.h"
+#include "menu.h"
+
+class ForeignMenuConnection;
+struct FrgMenuInit;
+struct FrgMenuMsg;
+
+class ForeignMenuInterface : public CommandTarget {
+public:
+    virtual ~ForeignMenuInterface() {}
+
+    virtual int get_foreign_menu_item_id(int32_t opaque_conn_ref, uint32_t msg_id) = 0;
+    virtual void clear_menu_items(int32_t opaque_conn_ref) = 0;
+    virtual void remove_menu_item(int item_id) = 0;
+    virtual Menu* get_app_menu() = 0;
+    virtual void update_menu() = 0;
+};
+
+class ForeignMenu : public NamedPipe::ListenerInterface {
+public:
+    ForeignMenu(ForeignMenuInterface *handler);
+    virtual ~ForeignMenu();
+
+    ForeignMenu* ref() { _refs++; return this;}
+    void unref() { if (!--_refs) delete this;}
+
+    virtual NamedPipe::ConnectionInterface &create();
+    void add_connection(NamedPipe::ConnectionRef conn_ref, ForeignMenuConnection *conn);
+    void remove_connection(NamedPipe::ConnectionRef conn_ref);
+    void add_sub_menus();
+    void on_command(NamedPipe::ConnectionRef conn_ref, int32_t id);
+    void on_activate();
+    void on_deactivate();
+
+private:
+    void send_active_state(ForeignMenuConnection *conn, int32_t cmd);
+
+private:
+    ForeignMenuInterface *_handler;
+    std::map<NamedPipe::ConnectionRef, ForeignMenuConnection*> _connections;
+    NamedPipe::ListenerRef _foreign_menu;
+    bool _active;
+    int _refs;
+};
+
+#define FOREIGN_MENU_BUF_SIZE 4096
+
+class ForeignMenuConnection : public NamedPipe::ConnectionInterface {
+public:
+    ForeignMenuConnection(ForeignMenuInterface *handler, ForeignMenu& parent);
+    virtual ~ForeignMenuConnection();
+
+    virtual void bind(NamedPipe::ConnectionRef conn_ref);
+    virtual void on_data();
+    bool write_msg(const void *buf, int len);
+    void reset_handler() { _handler = NULL;}
+    void add_sub_menu();
+
+private:
+    bool read_msgs();
+    bool handle_init(FrgMenuInit *init);
+    bool handle_message(FrgMenuMsg *hdr);
+    int get_item_state(int item_type);
+
+private:
+    ForeignMenuInterface *_handler;
+    ForeignMenu& _parent;
+    Menu* _sub_menu;
+    bool _initialized;
+    int _write_pending;
+    uint8_t *_write_pos;
+    uint8_t *_read_pos;
+    uint8_t _write_buf[FOREIGN_MENU_BUF_SIZE];
+    uint8_t _read_buf[FOREIGN_MENU_BUF_SIZE];
+    RecurciveMutex _write_lock;
+};
+
+#endif
diff --git a/client/windows/redc.vcproj b/client/windows/redc.vcproj
index 6b5182c..538d2cb 100644
--- a/client/windows/redc.vcproj
+++ b/client/windows/redc.vcproj
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="windows-1255"?>
 <VisualStudioProject
 	ProjectType="Visual C++"
-	Version="9,00"
+	Version="9.00"
 	Name="redc"
 	ProjectGUID="{4F03BAF9-DFBC-4CA7-B860-8929555981AE}"
 	RootNamespace="redc"
@@ -228,6 +228,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\foreign_menu.cpp"
+				>
+			</File>
+			<File
 				RelativePath="..\gdi_canvas.cpp"
 				>
 			</File>
@@ -508,6 +512,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\foreign_menu.h"
+				>
+			</File>
+			<File
 				RelativePath="..\..\common\gdi_canvas.h"
 				>
 			</File>
@@ -711,7 +719,7 @@
 				<Tool
 					Name="VCCustomBuildTool"
 					Description="Generating demarshaller"
-					CommandLine="generate.bat"
+					CommandLine="generate.bat&#x0D;&#x0A;"
 					AdditionalDependencies=""
 					Outputs="$(ProjectDir)/../generated_demarshallers.cpp;$(ProjectDir)/../generated_marshallers.cpp"
 				/>
@@ -722,7 +730,7 @@
 				<Tool
 					Name="VCCustomBuildTool"
 					Description="Generating demarshaller"
-					CommandLine="generate.bat"
+					CommandLine="generate.bat&#x0D;&#x0A;"
 					Outputs="$(ProjectDir)/../generated_demarshallers.cpp;$(ProjectDir)/../generated_marshallers.cpp"
 				/>
 			</FileConfiguration>
@@ -736,7 +744,7 @@
 				<Tool
 					Name="VCCustomBuildTool"
 					Description="Generating old demarshaller"
-					CommandLine="generate1.bat"
+					CommandLine="generate1.bat&#x0D;&#x0A;"
 					AdditionalDependencies=""
 					Outputs="$(ProjectDir)/../generated_demarshallers1.cpp;$(ProjectDir)/../generated_marshallers1.cpp"
 				/>
@@ -747,7 +755,7 @@
 				<Tool
 					Name="VCCustomBuildTool"
 					Description="Generating old demarshaller"
-					CommandLine="generate1.bat"
+					CommandLine="generate1.bat&#x0D;&#x0A;"
 					Outputs="$(ProjectDir)/../generated_demarshallers1.cpp;$(ProjectDir)/../generated_marshallers1.cpp"
 				/>
 			</FileConfiguration>
diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am
index 02aa8eb..f6e9fda 100644
--- a/client/x11/Makefile.am
+++ b/client/x11/Makefile.am
@@ -61,6 +61,8 @@ RED_COMMON_SRCS =					\
 	$(CLIENT_DIR)/debug.h				\
 	$(CLIENT_DIR)/display_channel.cpp		\
 	$(CLIENT_DIR)/display_channel.h			\
+ 	$(CLIENT_DIR)/foreign_menu.cpp			\
+ 	$(CLIENT_DIR)/foreign_menu.h			\
 	$(CLIENT_DIR)/glz_decoded_image.h		\
 	$(CLIENT_DIR)/glz_decoder_config.h		\
 	$(CLIENT_DIR)/glz_decoder.cpp			\
commit e789c8b9aa645d0adedb569be27daab8cdbf376f
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:50:53 2010 +0200

    spicec-win: move named_pipe defines

diff --git a/client/windows/named_pipe.cpp b/client/windows/named_pipe.cpp
index 7836147..856235a 100644
--- a/client/windows/named_pipe.cpp
+++ b/client/windows/named_pipe.cpp
@@ -20,6 +20,10 @@
 #include "utils.h"
 #include "debug.h"
 
+#define PIPE_TIMEOUT 5000
+#define PIPE_MAX_NAME_LEN 256
+#define PIPE_PREFIX TEXT("\\\\.\\pipe\\")
+
 PipeBuffer::PipeBuffer(HANDLE pipe, ProcessLoop& process_loop)
     : _handler (NULL)
     , _pipe (pipe)
diff --git a/client/windows/named_pipe.h b/client/windows/named_pipe.h
index e9b9bcc..4b7cfe6 100644
--- a/client/windows/named_pipe.h
+++ b/client/windows/named_pipe.h
@@ -23,10 +23,7 @@
 #include "event_sources.h"
 #include "platform.h"
 
-#define PIPE_TIMEOUT 5000
 #define PIPE_BUF_SIZE 8192
-#define PIPE_MAX_NAME_LEN 256
-#define PIPE_PREFIX TEXT("\\\\.\\pipe\\")
 
 class WinConnection;
 
commit b04bf7f9b078c57ac05a5ca81579fa71c98a7f90
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:50:27 2010 +0200

    spicec-win: fix menu id push to free_sys_menu_id

diff --git a/client/windows/red_window.cpp b/client/windows/red_window.cpp
index 4ca98fb..bab2d97 100644
--- a/client/windows/red_window.cpp
+++ b/client/windows/red_window.cpp
@@ -972,7 +972,7 @@ static int alloc_sys_cmd_id()
 
 static void free_sys_cmd_id(int id)
 {
-    free_sys_menu_id.push_back(id >> 4);
+    free_sys_menu_id.push_back(id);
 }
 
 static void insert_menu(Menu* menu, HMENU native, CommandMap& _commands_map)
commit 1d08cc14586beaf8a263a233f5e55902098b99fc
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:47:34 2010 +0200

    spicec: enable multiple CmdLineParser instantiations
    
    Used by controller. One instance at a time, not thread-safe.
    Add basename() for win32.

diff --git a/client/cmd_line_parser.cpp b/client/cmd_line_parser.cpp
index 3f45551..a813629 100644
--- a/client/cmd_line_parser.cpp
+++ b/client/cmd_line_parser.cpp
@@ -53,6 +53,11 @@ CmdLineParser::CmdLineParser(std::string description, bool allow_positional_args
     , _positional_args (allow_positional_args)
     , _done (false)
 {
+    //Enables multiple instantiations. One at a time, not thread-safe.
+    optind = 1;
+    opterr = 1;
+    optopt = 0;
+    optarg = 0;
 }
 
 CmdLineParser::~CmdLineParser()
commit e8d475745105c526cc5f98ddb0c4dc2d67af8f61
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:46:37 2010 +0200

    spicec: name host param

diff --git a/client/red_client.h b/client/red_client.h
index dd64682..ae52d9f 100644
--- a/client/red_client.h
+++ b/client/red_client.h
@@ -227,7 +227,7 @@ public:
     void activate_interval_timer(Timer* timer, unsigned int millisec);
     void deactivate_interval_timer(Timer* timer);
 
-    void set_target(const std::string&, int port, int sport);
+    void set_target(const std::string& host, int port, int sport);
     void set_password(const std::string& password) { _password = password;}
     void set_auto_display_res(bool auto_display_res) { _auto_display_res = auto_display_res;}
     void set_display_setting(DisplaySetting& setting) { _display_setting = setting;}
commit 5808a99052229b69707c0fdc0abcdf37dd6c3b60
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:45:50 2010 +0200

    spicec: add ProcessLoop::on_start_running()

diff --git a/client/process_loop.cpp b/client/process_loop.cpp
index 5337c36..ec9cdd2 100644
--- a/client/process_loop.cpp
+++ b/client/process_loop.cpp
@@ -263,7 +263,6 @@ ProcessLoop::ProcessLoop(void* owner)
     , _quitting (false)
     , _exit_code (0)
     , _started (false)
-
 {
     _event_sources.add_trigger(_wakeup_trigger);
 }
@@ -277,6 +276,7 @@ int ProcessLoop::run()
 {
     _thread = pthread_self();
     _started = true;
+    on_start_running();
     for (;;) {
         if (_event_sources.wait_events(_timers_queue.get_soonest_timeout())) {
             _quitting = true;
diff --git a/client/process_loop.h b/client/process_loop.h
index d9337c7..2d355f7 100644
--- a/client/process_loop.h
+++ b/client/process_loop.h
@@ -220,6 +220,7 @@ protected:
         virtual void on_event() {}
     };
 
+    virtual void on_start_running() {}
     void wakeup();
     void do_quit(int error_code);
 
commit e50c565b44f9bce105a4b38e93169234ad27ffe1
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:45:13 2010 +0200

    spicec: extract RedScreen::update_menu()

diff --git a/client/screen.cpp b/client/screen.cpp
index 7520571..7c4e1e3 100644
--- a/client/screen.cpp
+++ b/client/screen.cpp
@@ -113,8 +113,7 @@ RedScreen::RedScreen(Application& owner, int id, const std::wstring& name, int w
         THROW("create inactive cursor failed");
     }
     _window.set_cursor(_default_cursor);
-    AutoRef<Menu> menu(_owner.get_app_menu());
-    _window.set_menu(*menu);
+    update_menu();
     AutoRef<Icon> icon(Platform::load_icon(RED_ICON_RES_ID));
     _window.set_icon(*icon);
     _window.start_key_interception();
@@ -870,6 +869,12 @@ void RedScreen::external_show()
     _window.external_show();
 }
 
+void RedScreen::update_menu()
+{
+    AutoRef<Menu> menu(_owner.get_app_menu());
+    _window.set_menu(*menu);
+}
+
 void RedScreen::on_exposed_rect(const SpiceRect& area)
 {
     if (is_out_of_sync()) {
diff --git a/client/screen.h b/client/screen.h
index 8d9a81c..dfef989 100644
--- a/client/screen.h
+++ b/client/screen.h
@@ -89,6 +89,7 @@ public:
     void show();
     void activate();
     void external_show();
+    void update_menu();
 
     int get_id() { return _id;}
     int get_screen_id();
commit 793dd31cad280ed331e174b52829aa82c5281fad
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Sun Oct 17 11:44:13 2010 +0200

    spicec: add menu id & find_sub()

diff --git a/client/menu.cpp b/client/menu.cpp
index 0f7a7e8..1743821 100644
--- a/client/menu.cpp
+++ b/client/menu.cpp
@@ -20,10 +20,11 @@
 #include "utils.h"
 #include "debug.h"
 
-Menu::Menu(CommandTarget& target, const std::string& name)
+Menu::Menu(CommandTarget& target, const std::string& name, int id)
     : _refs (1)
     , _target (target)
     , _name (name)
+    , _id (id)
 {
 }
 
@@ -115,6 +116,21 @@ Menu* Menu::sub_at(int pos)
     return ((Menu*)_items[pos].obj)->ref();
 }
 
+Menu* Menu::find_sub(int id)
+{
+    Menu* sub;
+
+    if (_id == id) {
+        return ref();
+    }
+    for (unsigned int i = 0; i < _items.size(); i++) {
+        if (_items[i].type == MENU_ITEM_TYPE_MENU && (sub = ((Menu*)_items[i].obj)->find_sub(id))) {
+            return sub;
+        }
+    }
+    return NULL;
+}
+
 void Menu::clear()
 {
     for (unsigned int i = 0; i < _items.size(); i++) {
diff --git a/client/menu.h b/client/menu.h
index 809e798..6e3d2f1 100644
--- a/client/menu.h
+++ b/client/menu.h
@@ -26,7 +26,7 @@ public:
 
 class Menu {
 public:
-    Menu(CommandTarget& target, const std::string& name);
+    Menu(CommandTarget& target, const std::string& name, int id = 0);
 
     enum ItemType {
         MENU_ITEM_TYPE_INVALID,
@@ -46,6 +46,7 @@ public:
     void set_name(const std::string& name) { _name = name;}
     const std::string& get_name() { return _name;}
     CommandTarget& get_target() { return _target;}
+    int get_id() { return _id;}
 
     void add_command(const std::string& name, int cmd_id, int state = 0);
     void add_separator();
@@ -57,6 +58,7 @@ public:
     ItemType item_type_at(int pos);
     void command_at(int pos, std::string& name, int& cmd_id, int& state);
     Menu* sub_at(int pos);
+    Menu* find_sub(int id);
 
     void clear();
 
@@ -94,6 +96,7 @@ private:
     CommandTarget& _target;
     std::string _name;
     std::vector<MenuItem> _items;
+    int _id;
 };
 
 #endif


More information about the Spice-commits mailing list