diff options
Diffstat (limited to 'client/foreign_menu.cpp')
-rw-r--r-- | client/foreign_menu.cpp | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/client/foreign_menu.cpp b/client/foreign_menu.cpp new file mode 100644 index 00000000..e5d7459d --- /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; +} |