summaryrefslogtreecommitdiffstats
path: root/client/controller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'client/controller.cpp')
-rw-r--r--client/controller.cpp443
1 files changed, 443 insertions, 0 deletions
diff --git a/client/controller.cpp b/client/controller.cpp
new file mode 100644
index 00000000..b2937710
--- /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;
+}