mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 14:41:25 +00:00
Remove all uses of @end in the marshaller, instead just using the C struct array-at-end-of-struct. To make this work we also remove all use of @end for switches (making them C unions). We drop the zero member of the notify message so that we can avoid this use of @end for a primitive in the marshaller (plus its useless to send over the wire). We change the offsets and stuff in the migration messages to real pointers.
1092 lines
33 KiB
C++
1092 lines
33 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/>.
|
|
*/
|
|
|
|
#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"
|
|
|
|
#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;
|
|
};
|
|
|
|
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<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 (-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<MainChannelLoop*>(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<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);
|
|
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<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) {
|
|
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 = HUGE;
|
|
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_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<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::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<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);
|
|
}
|
|
|