/* 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 . */ #include "common.h" #include #include "red_client.h" #include "application.h" #include "process_loop.h" #include "utils.h" #include "debug.h" #include "marshallers.h" #ifdef __GNUC__ typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin { #else typedef struct __declspec(align(1)) OldRedMigrationBegin { #endif uint16_t port; uint16_t sport; char host[0]; } OldRedMigrationBegin; class MouseModeEvent: public Event { public: MouseModeEvent(RedClient& client) : _client (client) { } class SetModeFunc: public ForEachChannelFunc { public: SetModeFunc(bool capture_mode) : _capture_mode (capture_mode) { } virtual bool operator() (RedChannel& channel) { if (channel.get_type() == SPICE_CHANNEL_DISPLAY) { static_cast(channel).set_capture_mode(_capture_mode); } return true; } public: bool _capture_mode; }; virtual void response(AbstractProcessLoop& events_loop) { bool capture_mode = _client.get_mouse_mode() == SPICE_MOUSE_MODE_SERVER; if (!capture_mode) { _client.get_application().release_mouse_capture(); } SetModeFunc func(capture_mode); _client.for_each_channel(func); } private: RedClient& _client; }; Migrate::Migrate(RedClient& client) : _client (client) , _running (false) , _aborting (false) , _connected (false) , _thread (NULL) , _pending_con (0) { } Migrate::~Migrate() { ASSERT(!_thread); delete_channels(); } void Migrate::delete_channels() { while (!_channels.empty()) { MigChannels::iterator iter = _channels.begin(); delete *iter; _channels.erase(iter); } } void Migrate::clear_channels() { Lock lock(_lock); ASSERT(!_running); delete_channels(); } void Migrate::add_channel(MigChannel* channel) { Lock lock(_lock); _channels.push_back(channel); } void Migrate::swap_peer(RedChannelBase& other) { DBG(0, "channel type %u id %u", other.get_type(), other.get_id()); try { Lock lock(_lock); MigChannels::iterator iter = _channels.begin(); if (_running) { THROW("swap and running"); } if (!_connected) { THROW("not connected"); } for (; iter != _channels.end(); ++iter) { MigChannel* curr = *iter; if (curr->get_type() == other.get_type() && curr->get_id() == other.get_id()) { if (!curr->is_valid()) { THROW("invalid"); } other.swap(curr); curr->set_valid(false); if (!--_pending_con) { lock.unlock(); _client.set_target(_host.c_str(), _port, _sport); abort(); } return; } } THROW("no channel"); } catch (...) { abort(); throw; } } void Migrate::connect_one(MigChannel& channel, const RedPeer::ConnectionOptions& options, uint32_t connection_id) { if (_aborting) { DBG(0, "aborting"); THROW("aborting"); } channel.connect(options, connection_id, _host.c_str(), _password); ++_pending_con; channel.set_valid(true); } void Migrate::run() { uint32_t connection_id; RedPeer::ConnectionOptions::Type conn_type; DBG(0, ""); try { conn_type = _client.get_connection_options(SPICE_CHANNEL_MAIN); RedPeer::ConnectionOptions con_opt(conn_type, _port, _sport, _client.get_protocol(), _auth_options, _con_ciphers); MigChannels::iterator iter = _channels.begin(); connection_id = _client.get_connection_id(); connect_one(**iter, con_opt, connection_id); for (++iter; iter != _channels.end(); ++iter) { conn_type = _client.get_connection_options((*iter)->get_type()); con_opt = RedPeer::ConnectionOptions(conn_type, _port, _sport, _client.get_protocol(), _auth_options, _con_ciphers); connect_one(**iter, con_opt, connection_id); } _connected = true; DBG(0, "connected"); } catch (...) { close_channels(); } Lock lock(_lock); _cond.notify_one(); if (_connected) { Message* message = new Message(SPICE_MSGC_MAIN_MIGRATE_CONNECTED); _client.post_message(message); } else { Message* message = new Message(SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR); _client.post_message(message); } _running = false; } void* Migrate::worker_main(void *data) { Migrate* mig = (Migrate*)data; mig->run(); return NULL; } void Migrate::start(const SpiceMsgMainMigrationBegin* migrate) { DBG(0, ""); abort(); if ((_client.get_peer_major() == 1) && (_client.get_peer_minor() < 2)) { LOG_INFO("server minor version incompatible for destination authentication" "(missing dest pubkey in SpiceMsgMainMigrationBegin)"); OldRedMigrationBegin* old_migrate = (OldRedMigrationBegin*)migrate; _host.assign(old_migrate->host); _port = old_migrate->port ? old_migrate->port : -1; _sport = old_migrate->sport ? old_migrate->sport : -1;; _auth_options = _client.get_host_auth_options(); } else { _host.assign((char *)migrate->host_data); _port = migrate->port ? migrate->port : -1; _sport = migrate->sport ? migrate->sport : -1; _auth_options.type_flags = RedPeer::HostAuthOptions::HOST_AUTH_OP_PUBKEY; _auth_options.host_pubkey.assign(migrate->pub_key_data, migrate->pub_key_data + migrate->pub_key_size); } _con_ciphers = _client.get_connection_ciphers(); _password = _client._password; Lock lock(_lock); _running = true; lock.unlock(); _thread = new Thread(Migrate::worker_main, this); } void Migrate::disconnect_channels() { MigChannels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { (*iter)->disconnect(); (*iter)->set_valid(false); } } void Migrate::close_channels() { MigChannels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { (*iter)->close(); (*iter)->set_valid(false); (*iter)->enable(); } } bool Migrate::abort() { Lock lock(_lock); if (_aborting) { return false; } _aborting = true; for (;;) { disconnect_channels(); if (!_running) { break; } uint64_t timout = 1000 * 1000 * 10; /*10ms*/ _cond.timed_wait(lock, timout); } close_channels(); _pending_con = 0; _connected = false; _aborting = false; if (_thread) { _thread->join(); delete _thread; _thread = NULL; } return true; } #define AGENT_TIMEOUT (1000 * 30) void AgentTimer::response(AbstractProcessLoop& events_loop) { Application* app = static_cast(events_loop.get_owner()); app->deactivate_interval_timer(this); THROW_ERR(SPICEC_ERROR_CODE_AGENT_TIMEOUT, "vdagent timeout"); } class MainChannelLoop: public MessageHandlerImp { public: MainChannelLoop(RedClient& client): MessageHandlerImp(client) {} }; RedClient::RedClient(Application& application) : RedChannel(*this, SPICE_CHANNEL_MAIN, 0, new MainChannelLoop(*this)) , _application (application) , _port (-1) , _sport (-1) , _protocol (0) , _connection_id (0) , _mouse_mode (SPICE_MOUSE_MODE_SERVER) , _notify_disconnect (false) , _auto_display_res (false) , _agent_reply_wait_type (-1) , _aborting (false) , _agent_connected (false) , _agent_mon_config_sent (false) , _agent_disp_config_sent (false) , _agent_msg (new VDAgentMessage) , _agent_msg_data (NULL) , _agent_msg_pos (0) , _agent_out_msg (NULL) , _agent_out_msg_size (0) , _agent_out_msg_pos (0) , _agent_tokens (0) , _agent_timer (new AgentTimer()) , _migrate (*this) , _glz_window (0, _glz_debug) { MainChannelLoop* message_loop = static_cast(get_message_handler()); message_loop->set_handler(SPICE_MSG_MIGRATE, &RedClient::handle_migrate); message_loop->set_handler(SPICE_MSG_SET_ACK, &RedClient::handle_set_ack); message_loop->set_handler(SPICE_MSG_PING, &RedClient::handle_ping); message_loop->set_handler(SPICE_MSG_WAIT_FOR_CHANNELS, &RedClient::handle_wait_for_channels); message_loop->set_handler(SPICE_MSG_DISCONNECTING, &RedClient::handle_disconnect); message_loop->set_handler(SPICE_MSG_NOTIFY, &RedClient::handle_notify); message_loop->set_handler(SPICE_MSG_MAIN_MIGRATE_BEGIN, &RedClient::handle_migrate_begin); message_loop->set_handler(SPICE_MSG_MAIN_MIGRATE_CANCEL, &RedClient::handle_migrate_cancel); message_loop->set_handler(SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, &RedClient::handle_migrate_switch_host); message_loop->set_handler(SPICE_MSG_MAIN_INIT, &RedClient::handle_init); message_loop->set_handler(SPICE_MSG_MAIN_CHANNELS_LIST, &RedClient::handle_channels); message_loop->set_handler(SPICE_MSG_MAIN_MOUSE_MODE, &RedClient::handle_mouse_mode); message_loop->set_handler(SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &RedClient::handle_mm_time); message_loop->set_handler(SPICE_MSG_MAIN_AGENT_CONNECTED, &RedClient::handle_agent_connected); message_loop->set_handler(SPICE_MSG_MAIN_AGENT_DISCONNECTED, &RedClient::handle_agent_disconnected); message_loop->set_handler(SPICE_MSG_MAIN_AGENT_DATA, &RedClient::handle_agent_data); message_loop->set_handler(SPICE_MSG_MAIN_AGENT_TOKEN, &RedClient::handle_agent_tokens); start(); } RedClient::~RedClient() { ASSERT(_channels.empty()); _application.deactivate_interval_timer(*_agent_timer); delete _agent_msg; } void RedClient::set_target(const std::string& host, int port, int sport) { _port = port; _sport = sport; _host.assign(host); } void RedClient::push_event(Event* event) { _application.push_event(event); } void RedClient::activate_interval_timer(Timer* timer, unsigned int millisec) { _application.activate_interval_timer(timer, millisec); } void RedClient::deactivate_interval_timer(Timer* timer) { _application.deactivate_interval_timer(timer); } void RedClient::on_connecting() { _notify_disconnect = true; } void RedClient::on_connect() { AutoRef event(new ConnectedEvent()); push_event(*event); _migrate.add_channel(new MigChannel(SPICE_CHANNEL_MAIN, 0, get_common_caps(), get_caps())); } void RedClient::on_disconnect() { _migrate.abort(); _connection_id = 0; _application.deactivate_interval_timer(*_agent_timer); // todo: if migration remains not seemless, we shouldn't // resend monitors and display setting to the agent _agent_mon_config_sent = false; _agent_disp_config_sent = false; delete[] _agent_msg_data; _agent_msg_data = NULL; _agent_msg_pos = 0; _agent_tokens = 0; AutoRef sync_event(new SyncEvent()); get_client().push_event(*sync_event); (*sync_event)->wait(); } void RedClient::delete_channels() { Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); while (!_channels.empty()) { RedChannel *channel = *_channels.begin(); _channels.pop_front(); delete channel; } } void RedClient::for_each_channel(ForEachChannelFunc& func) { Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); for (; iter != _channels.end() && func(**iter) ;iter++); } void RedClient::on_mouse_capture_trigger(RedScreen& screen) { _application.capture_mouse(); } RedPeer::ConnectionOptions::Type RedClient::get_connection_options(uint32_t channel_type) { return _con_opt_map[channel_type]; } void RedClient::connect() { connect(false); } void RedClient::connect(bool wait_main_disconnect) { // assumption: read _connection_id is atomic if (_connection_id) { if (!wait_main_disconnect) { return; } } while (!abort_channels() || _connection_id) { _application.process_events_queue(); Platform::msleep(100); } _pixmap_cache.clear(); _glz_window.clear(); memset(_sync_info, 0, sizeof(_sync_info)); _aborting = false; _migrate.clear_channels(); delete_channels(); enable(); _con_opt_map.clear(); PeerConnectionOptMap::const_iterator iter = _application.get_con_opt_map().begin(); PeerConnectionOptMap::const_iterator end = _application.get_con_opt_map().end(); for (; iter != end; iter++) { _con_opt_map[(*iter).first] = (*iter).second; } _host_auth_opt = _application.get_host_auth_opt(); _con_ciphers = _application.get_connection_ciphers(); RedChannel::connect(); } void RedClient::disconnect() { _migrate.abort(); RedChannel::disconnect(); } void RedClient::disconnect_channels() { Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { (*iter)->RedPeer::disconnect(); } } void RedClient::on_channel_disconnected(RedChannel& channel) { Lock lock(_notify_lock); if (_notify_disconnect) { _notify_disconnect = false; int connection_error = channel.get_connection_error(); AutoRef disconn_event(new DisconnectedEvent(connection_error)); push_event(*disconn_event); } disconnect_channels(); RedPeer::disconnect(); } bool RedClient::abort_channels() { Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { if (!(*iter)->abort()) { return false; } } return true; } bool RedClient::abort() { if (!_aborting) { Lock lock(_sync_lock); _aborting = true; _sync_condition.notify_all(); } _pixmap_cache.abort(); _glz_window.abort(); if (RedChannel::abort() && abort_channels()) { delete_channels(); _migrate.abort(); return true; } else { return false; } } void RedClient::handle_migrate_begin(RedPeer::InMessage* message) { DBG(0, ""); SpiceMsgMainMigrationBegin* migrate = (SpiceMsgMainMigrationBegin*)message->data(); //add mig channels _migrate.start(migrate); } void RedClient::handle_migrate_cancel(RedPeer::InMessage* message) { _migrate.abort(); } ChannelFactory* RedClient::find_factory(uint32_t type) { Factorys::iterator iter = _factorys.begin(); for (; iter != _factorys.end(); ++iter) { if ((*iter)->type() == type) { return *iter; } } LOG_WARN("no factory for %u", type); return NULL; } void RedClient::create_channel(uint32_t type, uint32_t id) { ChannelFactory* factory = find_factory(type); if (!factory) { return; } RedChannel* channel = factory->construct(*this, id); ASSERT(channel); Lock lock(_channels_lock); _channels.push_back(channel); channel->start(); channel->connect(); _migrate.add_channel(new MigChannel(type, id, channel->get_common_caps(), channel->get_caps())); } void RedClient::send_agent_monitors_config() { AutoRef qury(new MonitorsQuery()); push_event(*qury); (*qury)->wait(); if (!(*qury)->success()) { THROW(" monitors query failed"); } double min_distance = HUGE; int dx = 0; int dy = 0; int i; std::vector& monitors = (*qury)->get_monitors(); std::vector::iterator iter = monitors.begin(); for (; iter != monitors.end(); iter++) { double distance = sqrt(pow((double)(*iter).position.x, 2) + pow((double)(*iter).position.y, 2)); if (distance < min_distance) { min_distance = distance; dx = -(*iter).position.x; dy = -(*iter).position.y; } } Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA); VDAgentMessage* msg = (VDAgentMessage*) spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage)); msg->protocol = VD_AGENT_PROTOCOL; msg->type = VD_AGENT_MONITORS_CONFIG; msg->opaque = 0; msg->size = sizeof(VDAgentMonitorsConfig) + monitors.size() * sizeof(VDAgentMonConfig); VDAgentMonitorsConfig* mon_config = (VDAgentMonitorsConfig*) spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMonitorsConfig) + monitors.size() * sizeof(VDAgentMonConfig)); mon_config->num_of_monitors = monitors.size(); mon_config->flags = 0; if (Platform::is_monitors_pos_valid()) { mon_config->flags = VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS; } for (iter = monitors.begin(), i = 0; iter != monitors.end(); iter++, i++) { mon_config->monitors[i].depth = (*iter).depth; mon_config->monitors[i].width = (*iter).size.x; mon_config->monitors[i].height = (*iter).size.y; mon_config->monitors[i].x = (*iter).position.x + dx; mon_config->monitors[i].y = (*iter).position.y + dy; } ASSERT(_agent_tokens) _agent_tokens--; post_message(message); _agent_mon_config_sent = true; _agent_reply_wait_type = VD_AGENT_MONITORS_CONFIG; } void RedClient::send_agent_display_config() { Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA); VDAgentMessage* msg = (VDAgentMessage*) spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage)); VDAgentDisplayConfig* disp_config; DBG(0,""); msg->protocol = VD_AGENT_PROTOCOL; msg->type = VD_AGENT_DISPLAY_CONFIG; msg->opaque = 0; msg->size = sizeof(VDAgentDisplayConfig); disp_config = (VDAgentDisplayConfig*) spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentDisplayConfig)); disp_config->flags = 0; if (_display_setting._disable_wallpaper) { disp_config->flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER; } if (_display_setting._disable_font_smooth) { disp_config->flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH; } if (_display_setting._disable_animation) { disp_config->flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION; } if (_display_setting._set_color_depth) { disp_config->flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH; disp_config->depth = _display_setting._color_depth; } ASSERT(_agent_tokens) _agent_tokens--; post_message(message); _agent_disp_config_sent = true; if (!_display_setting.is_empty()) { _agent_reply_wait_type = VD_AGENT_DISPLAY_CONFIG; } } #define MIN_DISPLAY_PIXMAP_CACHE (1024 * 1024 * 20) #define MAX_DISPLAY_PIXMAP_CACHE (1024 * 1024 * 80) #define MIN_MEM_FOR_OTHERS (1024 * 1024 * 40) // tmp till the pci mem will be shared by the qxls #define MIN_GLZ_WINDOW_SIZE (1024 * 1024 * 12) #define MAX_GLZ_WINDOW_SIZE MIN((LZ_MAX_WINDOW_SIZE * 4), 1024 * 1024 * 64) void RedClient::calc_pixmap_cach_and_glz_window_size(uint32_t display_channels_hint, uint32_t pci_mem_hint) { #ifdef WIN32 display_channels_hint = MAX(1, display_channels_hint); int max_cache_size = display_channels_hint * MAX_DISPLAY_PIXMAP_CACHE; int min_cache_size = display_channels_hint * MIN_DISPLAY_PIXMAP_CACHE; MEMORYSTATUSEX mem_status; mem_status.dwLength = sizeof(mem_status); if (!GlobalMemoryStatusEx(&mem_status)) { THROW("get mem status failed %u", GetLastError()); } //ullTotalPageFile is physical memory plus the size of the page file, minus a small overhead uint64_t free_mem = mem_status.ullAvailPageFile; if (free_mem < (min_cache_size + MIN_MEM_FOR_OTHERS + MIN_GLZ_WINDOW_SIZE)) { THROW_ERR(SPICEC_ERROR_CODE_NOT_ENOUGH_MEMORY, "low memory condition"); } free_mem -= MIN_MEM_FOR_OTHERS; _glz_window_size = MIN(MAX_GLZ_WINDOW_SIZE, pci_mem_hint / 2); _glz_window_size = (int)MIN(free_mem / 3, _glz_window_size); _glz_window_size = MAX(MIN_GLZ_WINDOW_SIZE, _glz_window_size); free_mem -= _glz_window_size; _pixmap_cache_size = MIN(free_mem, mem_status.ullAvailVirtual); _pixmap_cache_size = MIN(free_mem, max_cache_size); #else //for now _glz_window_size = (int)MIN(MAX_GLZ_WINDOW_SIZE, pci_mem_hint / 2); _glz_window_size = MAX(MIN_GLZ_WINDOW_SIZE, _glz_window_size); _pixmap_cache_size = MAX_DISPLAY_PIXMAP_CACHE; #endif _pixmap_cache_size /= 4; _glz_window_size /= 4; } void RedClient::on_display_mode_change() { #ifdef USE_OGL Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { if ((*iter)->get_type() == SPICE_CHANNEL_DISPLAY) { ((DisplayChannel *)(*iter))->recreate_ogl_context(); } } #endif } uint32_t get_agent_clipboard_type(uint32_t type) { switch (type) { case Platform::CLIPBOARD_UTF8_TEXT: return VD_AGENT_CLIPBOARD_UTF8_TEXT; default: return 0; } } void RedClient::post_agent_clipboard() { uint32_t size; while (_agent_tokens && (size = MIN(VD_AGENT_MAX_DATA_SIZE, _agent_out_msg_size - _agent_out_msg_pos))) { Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA); void* data = spice_marshaller_reserve_space(message->marshaller(), size); memcpy(data, (uint8_t*)_agent_out_msg + _agent_out_msg_pos, size); _agent_tokens--; post_message(message); _agent_out_msg_pos += size; if (_agent_out_msg_pos == _agent_out_msg_size) { delete[] (uint8_t *)_agent_out_msg; _agent_out_msg = NULL; _agent_out_msg_size = 0; _agent_out_msg_pos = 0; } } } //FIXME: currently supports text only; better name - poll_clipboard? void RedClient::on_clipboard_change() { //FIXME: check connected - assert on disconnect uint32_t clip_type = Platform::CLIPBOARD_UTF8_TEXT; int32_t clip_size = Platform::get_clipboard_data_size(clip_type); if (!clip_size || !_agent_connected) { return; } if (_agent_out_msg) { DBG(0, "clipboard change is already pending"); return; } _agent_out_msg_pos = 0; _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + clip_size; _agent_out_msg = (VDAgentMessage*)new uint8_t[_agent_out_msg_size]; _agent_out_msg->protocol = VD_AGENT_PROTOCOL; _agent_out_msg->type = VD_AGENT_CLIPBOARD; _agent_out_msg->opaque = 0; _agent_out_msg->size = sizeof(VDAgentClipboard) + clip_size; VDAgentClipboard* clipboard = (VDAgentClipboard*)_agent_out_msg->data; clipboard->type = get_agent_clipboard_type(clip_type); if (!Platform::get_clipboard_data(clip_type, clipboard->data, clip_size)) { delete[] (uint8_t *)_agent_out_msg; _agent_out_msg = NULL; _agent_out_msg_size = 0; return; } if (_agent_tokens) { post_agent_clipboard(); } } void RedClient::set_mouse_mode(uint32_t supported_modes, uint32_t current_mode) { if (current_mode != _mouse_mode) { _mouse_mode = current_mode; Lock lock(_channels_lock); Channels::iterator iter = _channels.begin(); for (; iter != _channels.end(); ++iter) { if ((*iter)->get_type() == SPICE_CHANNEL_CURSOR) { ((CursorChannel *)(*iter))->on_mouse_mode_change(); } } AutoRef event(new MouseModeEvent(*this)); push_event(*event); } // FIXME: use configured mouse mode (currently, use client mouse mode if supported by server) if ((supported_modes & SPICE_MOUSE_MODE_CLIENT) && (current_mode != SPICE_MOUSE_MODE_CLIENT)) { Message* message = new Message(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST); SpiceMsgcMainMouseModeRequest mouse_mode_request; mouse_mode_request.mode = SPICE_MOUSE_MODE_CLIENT; _marshallers->msgc_main_mouse_mode_request(message->marshaller(), &mouse_mode_request); post_message(message); } } void RedClient::on_agent_clipboard(VDAgentClipboard* clipboard, uint32_t size) { switch (clipboard->type) { case VD_AGENT_CLIPBOARD_UTF8_TEXT: Platform::set_clipboard_data(Platform::CLIPBOARD_UTF8_TEXT, clipboard->data, size); break; default: THROW("unexpected vdagent clipboard data type"); } } void RedClient::handle_init(RedPeer::InMessage* message) { SpiceMsgMainInit *init = (SpiceMsgMainInit *)message->data(); _connection_id = init->session_id; set_mm_time(init->multi_media_time); calc_pixmap_cach_and_glz_window_size(init->display_channels_hint, init->ram_hint); _glz_window.set_pixels_capacity(_glz_window_size); set_mouse_mode(init->supported_mouse_modes, init->current_mouse_mode); _agent_tokens = init->agent_tokens; _agent_connected = !!init->agent_connected; if (_agent_connected) { Message* msg = new Message(SPICE_MSGC_MAIN_AGENT_START); SpiceMsgcMainAgentStart agent_start; agent_start.num_tokens = ~0; _marshallers->msgc_main_agent_start(msg->marshaller(), &agent_start); post_message(msg); } if (_agent_connected) { if (_auto_display_res) { send_agent_monitors_config(); } // not sending the color depth through send_agent_monitors_config, since // it applies only for attached screens. send_agent_display_config(); } if (!_auto_display_res && _display_setting.is_empty()) { post_message(new Message(SPICE_MSGC_MAIN_ATTACH_CHANNELS)); } else { _application.activate_interval_timer(*_agent_timer, AGENT_TIMEOUT); } } void RedClient::handle_channels(RedPeer::InMessage* message) { SpiceMsgChannels *init = (SpiceMsgChannels *)message->data(); SpiceChannelId* channels = init->channels; for (unsigned int i = 0; i < init->num_of_channels; i++) { create_channel(channels[i].type, channels[i].id); } } void RedClient::handle_mouse_mode(RedPeer::InMessage* message) { SpiceMsgMainMouseMode *mouse_mode = (SpiceMsgMainMouseMode *)message->data(); set_mouse_mode(mouse_mode->supported_modes, mouse_mode->current_mode); } void RedClient::handle_mm_time(RedPeer::InMessage* message) { SpiceMsgMainMultiMediaTime *mm_time = (SpiceMsgMainMultiMediaTime *)message->data(); set_mm_time(mm_time->time); } void RedClient::handle_agent_connected(RedPeer::InMessage* message) { DBG(0, ""); _agent_connected = true; Message* msg = new Message(SPICE_MSGC_MAIN_AGENT_START); SpiceMsgcMainAgentStart agent_start; agent_start.num_tokens = ~0; _marshallers->msgc_main_agent_start(msg->marshaller(), &agent_start); post_message(msg); if (_auto_display_res && !_agent_mon_config_sent) { send_agent_monitors_config(); } if (!_agent_disp_config_sent) { send_agent_display_config(); } } void RedClient::handle_agent_disconnected(RedPeer::InMessage* message) { DBG(0, ""); _agent_connected = false; } void RedClient::on_agent_reply(VDAgentReply* reply) { DBG(0, "agent reply type: %d", reply->type); switch (reply->error) { case VD_AGENT_SUCCESS: break; case VD_AGENT_ERROR: THROW_ERR(SPICEC_ERROR_CODE_AGENT_ERROR, "vdagent error"); default: THROW("unknown vdagent error"); } switch (reply->type) { case VD_AGENT_MONITORS_CONFIG: case VD_AGENT_DISPLAY_CONFIG: if (_agent_reply_wait_type == reply->type) { post_message(new Message(SPICE_MSGC_MAIN_ATTACH_CHANNELS)); _application.deactivate_interval_timer(*_agent_timer); _agent_reply_wait_type = -1; } break; default: THROW("unexpected vdagent reply type"); } } void RedClient::handle_agent_data(RedPeer::InMessage* message) { uint32_t msg_size = message->size(); uint8_t* msg_pos = message->data(); uint32_t n; DBG(0, ""); while (msg_size) { if (_agent_msg_pos < sizeof(VDAgentMessage)) { n = MIN(sizeof(VDAgentMessage) - _agent_msg_pos, msg_size); memcpy((uint8_t*)_agent_msg + _agent_msg_pos, msg_pos, n); _agent_msg_pos += n; msg_size -= n; msg_pos += n; if (_agent_msg_pos == sizeof(VDAgentMessage)) { if (_agent_msg->protocol != VD_AGENT_PROTOCOL) { THROW("Invalid protocol %u", _agent_msg->protocol); } _agent_msg_data = new uint8_t[_agent_msg->size]; } } if (_agent_msg_pos >= sizeof(VDAgentMessage)) { n = MIN(sizeof(VDAgentMessage) + _agent_msg->size - _agent_msg_pos, msg_size); memcpy(_agent_msg_data + _agent_msg_pos - sizeof(VDAgentMessage), msg_pos, n); _agent_msg_pos += n; msg_size -= n; msg_pos += n; } if (_agent_msg_pos == sizeof(VDAgentMessage) + _agent_msg->size) { switch (_agent_msg->type) { case VD_AGENT_REPLY: { on_agent_reply((VDAgentReply*)_agent_msg_data); break; } case VD_AGENT_CLIPBOARD: { on_agent_clipboard((VDAgentClipboard*)_agent_msg_data, _agent_msg->size - sizeof(VDAgentClipboard)); break; } default: DBG(0, "Unsupported message type %u size %u", _agent_msg->type, _agent_msg->size); } delete[] _agent_msg_data; _agent_msg_data = NULL; _agent_msg_pos = 0; } } } void RedClient::handle_agent_tokens(RedPeer::InMessage* message) { SpiceMsgMainAgentTokens *token = (SpiceMsgMainAgentTokens *)message->data(); _agent_tokens += token->num_tokens; if (_agent_out_msg_pos < _agent_out_msg_size) { post_agent_clipboard(); } } void RedClient::handle_migrate_switch_host(RedPeer::InMessage* message) { SpiceMsgMainMigrationSwitchHost* migrate = (SpiceMsgMainMigrationSwitchHost*)message->data(); char* host = (char *)migrate->host_data; char* subject = NULL; if (host[migrate->host_size - 1] != '\0') { THROW("host is not a null-terminated string"); } if (migrate->cert_subject_size) { subject = (char *)migrate->cert_subject_data; if (subject[migrate->cert_subject_size - 1] != '\0') { THROW("cert subject is not a null-terminated string"); } } AutoRef switch_event(new SwitchHostEvent(host, migrate->port, migrate->sport, subject)); push_event(*switch_event); } void RedClient::migrate_channel(RedChannel& channel) { DBG(0, "channel type %u id %u", channel.get_type(), channel.get_id()); _migrate.swap_peer(channel); } void RedClient::get_sync_info(uint8_t channel_type, uint8_t channel_id, SyncInfo& info) { info.lock = &_sync_lock; info.condition = &_sync_condition; info.message_serial = &_sync_info[channel_type][channel_id]; } void RedClient::wait_for_channels(int wait_list_size, SpiceWaitForChannel* wait_list) { for (int i = 0; i < wait_list_size; i++) { if (wait_list[i].channel_type >= SPICE_END_CHANNEL) { THROW("invalid channel type %u", wait_list[i].channel_type); } uint64_t& sync_cell = _sync_info[wait_list[i].channel_type][wait_list[i].channel_id]; #ifndef RED64 Lock lock(_sync_lock); #endif if (sync_cell >= wait_list[i].message_serial) { continue; } #ifdef RED64 Lock lock(_sync_lock); #endif for (;;) { if (sync_cell >= wait_list[i].message_serial) { break; } if (_aborting) { THROW("aborting"); } _sync_condition.wait(lock); continue; } } } void RedClient::set_mm_time(uint32_t time) { Lock lock(_mm_clock_lock); _mm_clock_last_update = Platform::get_monolithic_time(); _mm_time = time; } uint32_t RedClient::get_mm_time() { Lock lock(_mm_clock_lock); return uint32_t((Platform::get_monolithic_time() - _mm_clock_last_update) / 1000 / 1000 + _mm_time); } void RedClient::register_channel_factory(ChannelFactory& factory) { _factorys.push_back(&factory); }