mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 14:41:25 +00:00
If you try to connect to a linux guest with WAN options, SPICE window opens up and is blank - it then fails with vdagent timeout message. It should give a warning that this is only applicable for windows guest and still connect to guest. It all starts in RedClient::handle_init This function checks whether we have an agent or not, because if we have an agent, there will be some kind of handshake to check both sides capabilities before all the spice channels are created. When there is no agent running, the startup process goes on with SPICE_MSGC_MAIN_ATTACH_CHANNELS When there is a windows agent running, VD_AGENT_ANNOUNCE_CAPABILITIES and VD_AGENT_DISPLAY_CONFIG messages are sent to the agent, and when processing the agent answer to the VD_AGENT_DISPLAY_CONFIG message, SPICE_MSGC_MAIN_ATTACH_CHANNELS will be sent and the startup process will go on. However, when there is no agent running but --color-depth was used, handle_init won't send the SPICE_MSGC_MAIN_ATTACH_CHANNELS message but will wait for the agent handshake to proceed to its end, which won't happen, so it will timeout waiting for agent answers. Similarly, the linux agent handles VD_AGENT_ANNOUNCE_CAPABILITIES messages, but it doesn't handle VD_AGENT_DISPLAY_CONFIG messages, so we'll never reach the point where a SPICE_MSGC_MAIN_ATTACH_CHANNELS will be sent. This commit fixes this in 2 places: - unconditionnally send SPICE_MSGC_ATTACH_CHANNELS when no agent is running in handle_init - send SPICE_MSGC_MAIN_ATTACH_CHANNELS in RedClient::on_agent_announce_capabilities if the agent doesn't have the VD_AGENT_CAP_DISPLAY_CONFIG capability This fixes RH bug #712938
1275 lines
40 KiB
C++
1275 lines
40 KiB
C++
/*
|
|
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/>.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include <math.h>
|
|
#include "red_client.h"
|
|
#include "application.h"
|
|
#include "process_loop.h"
|
|
#include "utils.h"
|
|
#include "debug.h"
|
|
#include "marshallers.h"
|
|
|
|
#ifndef INFINITY
|
|
#define INFINITY HUGE
|
|
#endif
|
|
|
|
#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<DisplayChannel&>(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;
|
|
};
|
|
|
|
uint32_t default_agent_caps[] = {
|
|
(1 << VD_AGENT_CAP_MOUSE_STATE) |
|
|
(1 << VD_AGENT_CAP_MONITORS_CONFIG) |
|
|
(1 << VD_AGENT_CAP_REPLY)
|
|
};
|
|
|
|
void ClipboardGrabEvent::response(AbstractProcessLoop& events_loop)
|
|
{
|
|
static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_message(
|
|
VD_AGENT_CLIPBOARD_GRAB, _type_count * sizeof(uint32_t), _types);
|
|
Platform::set_clipboard_owner(Platform::owner_client);
|
|
}
|
|
|
|
void ClipboardRequestEvent::response(AbstractProcessLoop& events_loop)
|
|
{
|
|
if (Platform::get_clipboard_owner() != Platform::owner_guest) {
|
|
LOG_WARN("received clipboard req from client while clipboard is not owned by guest");
|
|
Platform::on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
VDAgentClipboardRequest request = {_type};
|
|
static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_message(
|
|
VD_AGENT_CLIPBOARD_REQUEST, sizeof(request), &request);
|
|
}
|
|
|
|
void ClipboardNotifyEvent::response(AbstractProcessLoop& events_loop)
|
|
{
|
|
static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_notify_message(
|
|
_type, _data, _size);
|
|
}
|
|
|
|
void ClipboardReleaseEvent::response(AbstractProcessLoop& events_loop)
|
|
{
|
|
if (Platform::get_clipboard_owner() != Platform::owner_client)
|
|
return;
|
|
|
|
static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_message(
|
|
VD_AGENT_CLIPBOARD_RELEASE, 0, NULL);
|
|
}
|
|
|
|
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() < 1)) {
|
|
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 = SPICE_SSL_VERIFY_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<Application*>(events_loop.get_owner());
|
|
app->deactivate_interval_timer(this);
|
|
THROW_ERR(SPICEC_ERROR_CODE_AGENT_TIMEOUT, "vdagent timeout");
|
|
}
|
|
|
|
class MainChannelLoop: public MessageHandlerImp<RedClient, SPICE_CHANNEL_MAIN> {
|
|
public:
|
|
MainChannelLoop(RedClient& client): MessageHandlerImp<RedClient, SPICE_CHANNEL_MAIN>(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 (VD_AGENT_END_MESSAGE)
|
|
, _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())
|
|
, _agent_caps_size(0)
|
|
, _agent_caps(NULL)
|
|
, _migrate (*this)
|
|
, _glz_window (_glz_debug)
|
|
{
|
|
Platform::set_clipboard_listener(this);
|
|
MainChannelLoop* message_loop = static_cast<MainChannelLoop*>(get_message_handler());
|
|
uint32_t default_caps_size = SPICE_N_ELEMENTS(default_agent_caps);
|
|
|
|
_agent_caps_size = VD_AGENT_CAPS_SIZE;
|
|
ASSERT(VD_AGENT_CAPS_SIZE >= default_caps_size);
|
|
_agent_caps = new uint32_t[_agent_caps_size];
|
|
memcpy(_agent_caps, default_agent_caps, default_caps_size);
|
|
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;
|
|
delete[] _agent_caps;
|
|
}
|
|
|
|
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<ConnectedEvent> 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<SyncEvent> sync_event(new SyncEvent());
|
|
get_client().push_event(*sync_event);
|
|
(*sync_event)->wait();
|
|
}
|
|
|
|
void RedClient::delete_channels()
|
|
{
|
|
Lock lock(_channels_lock);
|
|
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<DisconnectedEvent> 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) {
|
|
Platform::set_clipboard_listener(NULL);
|
|
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<MonitorsQuery > qury(new MonitorsQuery());
|
|
push_event(*qury);
|
|
(*qury)->wait();
|
|
if (!(*qury)->success()) {
|
|
THROW(" monitors query failed");
|
|
}
|
|
|
|
double min_distance = INFINITY;
|
|
int dx = 0;
|
|
int dy = 0;
|
|
int i;
|
|
|
|
std::vector<MonitorInfo>& monitors = (*qury)->get_monitors();
|
|
std::vector<MonitorInfo>::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_announce_capabilities(bool request)
|
|
{
|
|
Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA);
|
|
VDAgentMessage* msg = (VDAgentMessage*)
|
|
spice_marshaller_reserve_space(message->marshaller(),
|
|
sizeof(VDAgentMessage));
|
|
VDAgentAnnounceCapabilities* caps;
|
|
|
|
msg->protocol = VD_AGENT_PROTOCOL;
|
|
msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
|
|
msg->opaque = 0;
|
|
msg->size = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES;
|
|
|
|
caps = (VDAgentAnnounceCapabilities*)
|
|
spice_marshaller_reserve_space(message->marshaller(), msg->size);
|
|
|
|
caps->request = request;
|
|
memset(caps->caps, 0, VD_AGENT_CAPS_BYTES);
|
|
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
|
|
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
|
|
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
|
|
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
|
|
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
|
|
ASSERT(_agent_tokens)
|
|
_agent_tokens--;
|
|
post_message(message);
|
|
}
|
|
|
|
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);
|
|
uint64_t max_cache_size = display_channels_hint * MAX_DISPLAY_PIXMAP_CACHE;
|
|
uint64_t 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_OPENGL
|
|
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
|
|
}
|
|
|
|
void RedClient::do_send_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t size, void* data)
|
|
{
|
|
if (!_agent_connected)
|
|
return;
|
|
|
|
if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
|
|
VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
|
|
return;
|
|
|
|
Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA);
|
|
VDAgentMessage* msg = (VDAgentMessage*)
|
|
spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage) + size);
|
|
msg->protocol = VD_AGENT_PROTOCOL;
|
|
msg->type = message_type;
|
|
msg->opaque = 0;
|
|
msg->size = size;
|
|
if (size && data) {
|
|
memcpy(msg->data, data, size);
|
|
}
|
|
ASSERT(_agent_tokens)
|
|
_agent_tokens--;
|
|
post_message(message);
|
|
}
|
|
|
|
void RedClient::on_clipboard_grab(uint32_t *types, uint32_t type_count)
|
|
{
|
|
AutoRef<ClipboardGrabEvent> event(new ClipboardGrabEvent(types, type_count));
|
|
get_process_loop().push_event(*event);
|
|
}
|
|
|
|
void RedClient::on_clipboard_request(uint32_t type)
|
|
{
|
|
AutoRef<ClipboardRequestEvent> event(new ClipboardRequestEvent(type));
|
|
get_process_loop().push_event(*event);
|
|
}
|
|
|
|
void RedClient::on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size)
|
|
{
|
|
AutoRef<ClipboardNotifyEvent> event(new ClipboardNotifyEvent(type, data, size));
|
|
get_process_loop().push_event(*event);
|
|
}
|
|
|
|
void RedClient::on_clipboard_release()
|
|
{
|
|
AutoRef<ClipboardReleaseEvent> event(new ClipboardReleaseEvent());
|
|
get_process_loop().push_event(*event);
|
|
}
|
|
|
|
void RedClient::send_agent_clipboard_notify_message(uint32_t type, uint8_t *data, uint32_t size)
|
|
{
|
|
ASSERT(data || !size);
|
|
if (!_agent_connected) {
|
|
return;
|
|
}
|
|
if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
|
|
VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
|
|
return;
|
|
if (_agent_out_msg) {
|
|
DBG(0, "clipboard change is already pending");
|
|
return;
|
|
}
|
|
if (Platform::get_clipboard_owner() != Platform::owner_client) {
|
|
LOG_WARN("received clipboard data from client while clipboard is not owned by client");
|
|
type = VD_AGENT_CLIPBOARD_NONE;
|
|
size = 0;
|
|
}
|
|
_agent_out_msg_pos = 0;
|
|
_agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + 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) + size;
|
|
VDAgentClipboard* clipboard = (VDAgentClipboard*)_agent_out_msg->data;
|
|
clipboard->type = type;
|
|
memcpy(clipboard->data, data, size);
|
|
if (_agent_tokens) {
|
|
do_send_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<MouseModeEvent> 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::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);
|
|
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);
|
|
send_agent_announce_capabilities(true);
|
|
if (_auto_display_res) {
|
|
send_agent_monitors_config();
|
|
}
|
|
if (_auto_display_res || !_display_setting.is_empty()) {
|
|
_application.activate_interval_timer(*_agent_timer, AGENT_TIMEOUT);
|
|
} else {
|
|
post_message(new Message(SPICE_MSGC_MAIN_ATTACH_CHANNELS));
|
|
}
|
|
} else {
|
|
if (_auto_display_res || !_display_setting.is_empty()) {
|
|
LOG_WARN("no agent running, display options have been ignored");
|
|
}
|
|
post_message(new Message(SPICE_MSGC_MAIN_ATTACH_CHANNELS));
|
|
}
|
|
}
|
|
|
|
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);
|
|
send_agent_announce_capabilities(false);
|
|
|
|
if (_auto_display_res && !_agent_mon_config_sent) {
|
|
send_agent_monitors_config();
|
|
}
|
|
}
|
|
|
|
void RedClient::handle_agent_disconnected(RedPeer::InMessage* message)
|
|
{
|
|
DBG(0, "");
|
|
_agent_connected = false;
|
|
}
|
|
|
|
void RedClient::on_agent_announce_capabilities(
|
|
VDAgentAnnounceCapabilities* caps, uint32_t msg_size)
|
|
{
|
|
uint32_t caps_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg_size);
|
|
|
|
if (_agent_caps_size != caps_size) {
|
|
delete[] _agent_caps;
|
|
_agent_caps = new uint32_t[caps_size];
|
|
ASSERT(_agent_caps != NULL);
|
|
_agent_caps_size = caps_size;
|
|
}
|
|
memcpy(_agent_caps, caps->caps, sizeof(_agent_caps[0]) * caps_size);
|
|
|
|
if (caps->request) {
|
|
send_agent_announce_capabilities(false);
|
|
}
|
|
if (VD_AGENT_HAS_CAPABILITY(caps->caps, caps_size,
|
|
VD_AGENT_CAP_DISPLAY_CONFIG) && !_agent_disp_config_sent) {
|
|
// not sending the color depth through send_agent_monitors_config, since
|
|
// it applies only for attached screens.
|
|
send_agent_display_config();
|
|
} else if (!_auto_display_res) {
|
|
/* some agents don't support monitors/displays agent messages, so
|
|
* we'll never reach on_agent_reply which sends this
|
|
* ATTACH_CHANNELS message which is needed for client startup to go
|
|
* on.
|
|
*/
|
|
if (!_display_setting.is_empty()) {
|
|
LOG_WARN("display options have been requested, but the agent doesn't support these options");
|
|
}
|
|
post_message(new Message(SPICE_MSGC_MAIN_ATTACH_CHANNELS));
|
|
_application.deactivate_interval_timer(*_agent_timer);
|
|
}
|
|
}
|
|
|
|
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 = VD_AGENT_END_MESSAGE;
|
|
}
|
|
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)) {
|
|
DBG(0, "agent msg start: msg_size=%d, protocol=%d, type=%d",
|
|
_agent_msg->size, _agent_msg->protocol, _agent_msg->type);
|
|
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) {
|
|
DBG(0, "agent msg end");
|
|
dispatch_agent_message(_agent_msg, _agent_msg_data);
|
|
delete[] _agent_msg_data;
|
|
_agent_msg_data = NULL;
|
|
_agent_msg_pos = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
|
|
{
|
|
switch (msg->type) {
|
|
case VD_AGENT_ANNOUNCE_CAPABILITIES: {
|
|
on_agent_announce_capabilities((VDAgentAnnounceCapabilities*)data, msg->size);
|
|
break;
|
|
}
|
|
case VD_AGENT_REPLY: {
|
|
on_agent_reply((VDAgentReply*)data);
|
|
break;
|
|
}
|
|
case VD_AGENT_CLIPBOARD: {
|
|
if (Platform::get_clipboard_owner() != Platform::owner_guest) {
|
|
LOG_WARN("received clipboard data from guest while clipboard is not owned by guest");
|
|
Platform::on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
|
|
break;
|
|
}
|
|
|
|
VDAgentClipboard* clipboard = (VDAgentClipboard*)data;
|
|
Platform::on_clipboard_notify(clipboard->type, clipboard->data,
|
|
msg->size - sizeof(VDAgentClipboard));
|
|
break;
|
|
}
|
|
case VD_AGENT_CLIPBOARD_GRAB:
|
|
Platform::on_clipboard_grab((uint32_t *)data,
|
|
msg->size / sizeof(uint32_t));
|
|
break;
|
|
case VD_AGENT_CLIPBOARD_REQUEST:
|
|
if (Platform::get_clipboard_owner() != Platform::owner_client) {
|
|
LOG_WARN("received clipboard req from guest while clipboard is not owned by client");
|
|
on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
|
|
break;
|
|
}
|
|
|
|
if (!Platform::on_clipboard_request(((VDAgentClipboardRequest*)data)->type)) {
|
|
on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
|
|
}
|
|
break;
|
|
case VD_AGENT_CLIPBOARD_RELEASE:
|
|
if (Platform::get_clipboard_owner() != Platform::owner_guest) {
|
|
LOG_INFO("received clipboard release from guest while clipboard is not owned by guest");
|
|
break;
|
|
}
|
|
|
|
Platform::on_clipboard_release();
|
|
break;
|
|
default:
|
|
DBG(0, "Unsupported message type %u size %u", msg->type, msg->size);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
do_send_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<SwitchHostEvent> 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);
|
|
}
|
|
|