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

Hans de Goede hdegoede at redhat.com
Mon Oct 18 01:40:57 PDT 2010


Ack.

On 10/18/2010 10:21 AM, Arnon Gilboa wrote:
> 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         |    2 +
>   client/application.cpp     |  145 ++++++++++++---
>   client/application.h       |   15 ++-
>   client/controller.cpp      |  443 ++++++++++++++++++++++++++++++++++++++++++++
>   client/controller.h        |  116 ++++++++++++
>   client/windows/redc.vcproj |    8 +
>   client/x11/Makefile.am     |    2 +
>   7 files changed, 700 insertions(+), 31 deletions(-)
>   create mode 100644 client/controller.cpp
>   create mode 100644 client/controller.h
>
> 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			\


More information about the Spice-devel mailing list