spice/client/red_client.cpp
Christophe Fergeau 492f7a9b84 client: don't crash when agent is missing WAN support
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
2011-07-18 18:15:40 +02:00

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);
}