/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2010 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#include
#include
#include
#include
#include "glib-compat.h"
#include "spice-client.h"
#include "spice-common.h"
#include "spice-marshal.h"
#include "spice-util-priv.h"
#include "spice-channel-priv.h"
#include "spice-session-priv.h"
/**
* SECTION:channel-main
* @short_description: the main Spice channel
* @title: Main Channel
* @section_id:
* @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
* @stability: Stable
* @include: channel-main.h
*
* The main channel is the Spice session control channel. It handles
* communication initialization (channels list), migrations, mouse
* modes, multimedia time, and agent communication.
*
*
*/
#define SPICE_MAIN_CHANNEL_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelPrivate))
#define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */
typedef struct spice_migrate spice_migrate;
#define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32)
typedef struct SpiceFileXferTask {
uint32_t id;
gboolean pending;
GFile *file;
SpiceMainChannel *channel;
GFileInputStream *file_stream;
GFileCopyFlags flags;
GCancellable *cancellable;
GFileProgressCallback progress_callback;
gpointer progress_callback_data;
GAsyncReadyCallback callback;
gpointer user_data;
char buffer[FILE_XFER_CHUNK_SIZE];
uint64_t read_bytes;
uint64_t file_size;
GError *error;
} SpiceFileXferTask;
struct _SpiceMainChannelPrivate {
enum SpiceMouseMode mouse_mode;
bool agent_connected;
bool agent_caps_received;
gboolean agent_display_config_sent;
guint8 display_color_depth;
gboolean display_disable_wallpaper:1;
gboolean display_disable_font_smooth:1;
gboolean display_disable_animation:1;
gboolean disable_display_position:1;
gboolean disable_display_align:1;
int agent_tokens;
VDAgentMessage agent_msg; /* partial msg reconstruction */
guint8 *agent_msg_data;
guint agent_msg_pos;
uint8_t agent_msg_size;
uint32_t agent_caps[VD_AGENT_CAPS_SIZE];
struct {
int x;
int y;
int width;
int height;
gboolean enabled;
} display[MAX_DISPLAY];
gint timer_id;
GQueue *agent_msg_queue;
GHashTable *file_xfer_tasks;
GSList *flushing;
guint switch_host_delayed_id;
guint migrate_delayed_id;
spice_migrate *migrate_data;
};
struct spice_migrate {
struct coroutine *from;
SpiceMigrationDstInfo *info;
SpiceSession *session;
guint nchannels;
SpiceChannel *src_channel;
SpiceChannel *dst_channel;
bool do_seamless; /* used as input and output for the seamless migration handshake.
input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
output: whether the dest approved seamless migration
(SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
*/
uint32_t src_mig_version;
};
G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
/* Properties */
enum {
PROP_0,
PROP_MOUSE_MODE,
PROP_AGENT_CONNECTED,
PROP_AGENT_CAPS_0,
PROP_DISPLAY_DISABLE_WALLPAPER,
PROP_DISPLAY_DISABLE_FONT_SMOOTH,
PROP_DISPLAY_DISABLE_ANIMATION,
PROP_DISPLAY_COLOR_DEPTH,
PROP_DISABLE_DISPLAY_POSITION,
PROP_DISABLE_DISPLAY_ALIGN,
};
/* Signals */
enum {
SPICE_MAIN_MOUSE_UPDATE,
SPICE_MAIN_AGENT_UPDATE,
SPICE_MAIN_CLIPBOARD,
SPICE_MAIN_CLIPBOARD_GRAB,
SPICE_MAIN_CLIPBOARD_REQUEST,
SPICE_MAIN_CLIPBOARD_RELEASE,
SPICE_MAIN_CLIPBOARD_SELECTION,
SPICE_MAIN_CLIPBOARD_SELECTION_GRAB,
SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
SPICE_MIGRATION_STARTED,
SPICE_MAIN_LAST_SIGNAL,
};
static guint signals[SPICE_MAIN_LAST_SIGNAL];
static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
static void agent_send_msg_queue(SpiceMainChannel *channel);
static void agent_free_msg_queue(SpiceMainChannel *channel);
static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
gpointer data);
static gboolean main_migrate_handshake_done(gpointer data);
static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
static void file_xfer_continue_read(SpiceFileXferTask *task);
static void file_xfer_completed(SpiceFileXferTask *task, GError *error);
static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
/* ------------------------------------------------------------------ */
static const char *agent_msg_types[] = {
[ VD_AGENT_MOUSE_STATE ] = "mouse state",
[ VD_AGENT_MONITORS_CONFIG ] = "monitors config",
[ VD_AGENT_REPLY ] = "reply",
[ VD_AGENT_CLIPBOARD ] = "clipboard",
[ VD_AGENT_DISPLAY_CONFIG ] = "display config",
[ VD_AGENT_ANNOUNCE_CAPABILITIES ] = "announce caps",
[ VD_AGENT_CLIPBOARD_GRAB ] = "clipboard grab",
[ VD_AGENT_CLIPBOARD_REQUEST ] = "clipboard request",
[ VD_AGENT_CLIPBOARD_RELEASE ] = "clipboard release",
};
static const char *agent_caps[] = {
[ VD_AGENT_CAP_MOUSE_STATE ] = "mouse state",
[ VD_AGENT_CAP_MONITORS_CONFIG ] = "monitors config",
[ VD_AGENT_CAP_REPLY ] = "reply",
[ VD_AGENT_CAP_CLIPBOARD ] = "clipboard (old)",
[ VD_AGENT_CAP_DISPLAY_CONFIG ] = "display config",
[ VD_AGENT_CAP_CLIPBOARD_BY_DEMAND ] = "clipboard",
[ VD_AGENT_CAP_CLIPBOARD_SELECTION ] = "clipboard selection",
};
#define NAME(_a, _i) ((_i) < SPICE_N_ELEMENTS(_a) ? (_a[(_i)] ?: "?") : "?")
/* ------------------------------------------------------------------ */
static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
{
spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
}
static void spice_main_channel_init(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c;
c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
c->agent_msg_queue = g_queue_new();
c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
spice_main_channel_reset_capabilties(SPICE_CHANNEL(channel));
}
static void spice_main_get_property(GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(object)->priv;
switch (prop_id) {
case PROP_MOUSE_MODE:
g_value_set_int(value, c->mouse_mode);
break;
case PROP_AGENT_CONNECTED:
g_value_set_boolean(value, c->agent_connected);
break;
case PROP_AGENT_CAPS_0:
g_value_set_int(value, c->agent_caps[0]);
break;
case PROP_DISPLAY_DISABLE_WALLPAPER:
g_value_set_boolean(value, c->display_disable_wallpaper);
break;
case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
g_value_set_boolean(value, c->display_disable_font_smooth);
break;
case PROP_DISPLAY_DISABLE_ANIMATION:
g_value_set_boolean(value, c->display_disable_animation);
break;
case PROP_DISPLAY_COLOR_DEPTH:
g_value_set_uint(value, c->display_color_depth);
break;
case PROP_DISABLE_DISPLAY_POSITION:
g_value_set_boolean(value, c->disable_display_position);
break;
case PROP_DISABLE_DISPLAY_ALIGN:
g_value_set_boolean(value, c->disable_display_align);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void spice_main_set_property(GObject *gobject, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(gobject)->priv;
switch (prop_id) {
case PROP_DISPLAY_DISABLE_WALLPAPER:
c->display_disable_wallpaper = g_value_get_boolean(value);
break;
case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
c->display_disable_font_smooth = g_value_get_boolean(value);
break;
case PROP_DISPLAY_DISABLE_ANIMATION:
c->display_disable_animation = g_value_get_boolean(value);
break;
case PROP_DISPLAY_COLOR_DEPTH: {
guint color_depth = g_value_get_uint(value);
g_return_if_fail(color_depth % 8 == 0);
c->display_color_depth = color_depth;
break;
}
case PROP_DISABLE_DISPLAY_POSITION:
c->disable_display_position = g_value_get_boolean(value);
break;
case PROP_DISABLE_DISPLAY_ALIGN:
c->disable_display_align = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
break;
}
}
static void spice_main_channel_dispose(GObject *obj)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
if (c->timer_id) {
g_source_remove(c->timer_id);
c->timer_id = 0;
}
if (c->switch_host_delayed_id) {
g_source_remove(c->switch_host_delayed_id);
c->switch_host_delayed_id = 0;
}
if (c->migrate_delayed_id) {
g_source_remove(c->migrate_delayed_id);
c->migrate_delayed_id = 0;
}
if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
}
static void spice_main_channel_finalize(GObject *obj)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
g_free(c->agent_msg_data);
agent_free_msg_queue(SPICE_MAIN_CHANNEL(obj));
if (c->file_xfer_tasks)
g_hash_table_unref(c->file_xfer_tasks);
if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
}
/* coroutine context */
static void spice_channel_iterate_write(SpiceChannel *channel)
{
agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
if (SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write)
SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write(channel);
}
/* main or coroutine context */
static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
GError *error;
GList *tasks;
GList *l;
c->agent_connected = FALSE;
c->agent_caps_received = FALSE;
c->agent_display_config_sent = FALSE;
c->agent_msg_pos = 0;
g_free(c->agent_msg_data);
c->agent_msg_data = NULL;
c->agent_msg_size = 0;
tasks = g_hash_table_get_values(c->file_xfer_tasks);
for (l = tasks; l != NULL; l = l->next) {
SpiceFileXferTask *task = (SpiceFileXferTask *)l->data;
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"Agent connection closed");
file_xfer_completed(task, error);
}
g_list_free(tasks);
file_xfer_flushed(channel, FALSE);
}
/* main or coroutine context */
static void spice_main_channel_reset(SpiceChannel *channel, gboolean migrating)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
/* This is not part of reset_agent, since the spice-server expects any
pending multi-chunk messages to be completed by the client, even after
it has send an agent-disconnected msg as that is what the original
spicec did. Also see the TODO in server/reds.c reds_reset_vdp() */
c->agent_tokens = 0;
agent_free_msg_queue(SPICE_MAIN_CHANNEL(channel));
c->agent_msg_queue = g_queue_new();
spice_main_channel_reset_agent(SPICE_MAIN_CHANNEL(channel));
SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->channel_reset(channel, migrating);
}
static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
gobject_class->dispose = spice_main_channel_dispose;
gobject_class->finalize = spice_main_channel_finalize;
gobject_class->get_property = spice_main_get_property;
gobject_class->set_property = spice_main_set_property;
channel_class->handle_msg = spice_main_handle_msg;
channel_class->iterate_write = spice_channel_iterate_write;
channel_class->channel_reset = spice_main_channel_reset;
channel_class->channel_reset_capabilities = spice_main_channel_reset_capabilties;
channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
/**
* SpiceMainChannel:mouse-mode:
*
* Spice protocol specifies two mouse modes, client mode and
* server mode. In client mode (%SPICE_MOUSE_MODE_CLIENT), the
* affective mouse is the client side mouse: the client sends
* mouse position within the display and the server sends mouse
* shape messages. In server mode (%SPICE_MOUSE_MODE_SERVER), the
* client sends relative mouse movements and the server sends
* position and shape commands.
**/
g_object_class_install_property
(gobject_class, PROP_MOUSE_MODE,
g_param_spec_int("mouse-mode",
"Mouse mode",
"Mouse mode",
0, INT_MAX, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_AGENT_CONNECTED,
g_param_spec_boolean("agent-connected",
"Agent connected",
"Whether the agent is connected",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_AGENT_CAPS_0,
g_param_spec_int("agent-caps-0",
"Agent caps 0",
"Agent capability bits 0 -> 31",
0, INT_MAX, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property
(gobject_class, PROP_DISPLAY_DISABLE_WALLPAPER,
g_param_spec_boolean("disable-wallpaper",
"Disable guest wallpaper",
"Disable guest wallpaper",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(gobject_class, PROP_DISPLAY_DISABLE_FONT_SMOOTH,
g_param_spec_boolean("disable-font-smooth",
"Disable guest font smooth",
"Disable guest font smoothing",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(gobject_class, PROP_DISPLAY_DISABLE_ANIMATION,
g_param_spec_boolean("disable-animation",
"Disable guest animations",
"Disable guest animations",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(gobject_class, PROP_DISABLE_DISPLAY_POSITION,
g_param_spec_boolean("disable-display-position",
"Disable display position",
"Disable using display position when setting monitor config",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(gobject_class, PROP_DISPLAY_COLOR_DEPTH,
g_param_spec_uint("color-depth",
"Color depth",
"Color depth", 0, 32, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* SpiceMainChannel:disable-display-align:
*
* Disable automatic horizontal display position alignment.
*
* Since: 0.13
*/
g_object_class_install_property
(gobject_class, PROP_DISABLE_DISPLAY_ALIGN,
g_param_spec_boolean("disable-display-align",
"Disable display align",
"Disable display position alignment",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/* TODO use notify instead */
/**
* SpiceMainChannel::main-mouse-update:
* @main: the #SpiceMainChannel that emitted the signal
*
* Notify when the mouse mode has changed.
**/
signals[SPICE_MAIN_MOUSE_UPDATE] =
g_signal_new("main-mouse-update",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceMainChannelClass, mouse_update),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/* TODO use notify instead */
/**
* SpiceMainChannel::main-agent-update:
* @main: the #SpiceMainChannel that emitted the signal
*
* Notify when the %SpiceMainChannel:agent-connected or
* %SpiceMainChannel:agent-caps-0 property change.
**/
signals[SPICE_MAIN_AGENT_UPDATE] =
g_signal_new("main-agent-update",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceMainChannelClass, agent_update),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* SpiceMainChannel::main-clipboard:
* @main: the #SpiceMainChannel that emitted the signal
* @type: the VD_AGENT_CLIPBOARD data type
* @data: clipboard data
* @size: size of @data in bytes
*
* Provides guest clipboard data requested by spice_main_clipboard_request().
*
* Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection instead.
**/
signals[SPICE_MAIN_CLIPBOARD] =
g_signal_new("main-clipboard",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
0,
NULL, NULL,
g_cclosure_user_marshal_VOID__UINT_POINTER_UINT,
G_TYPE_NONE,
3,
G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-selection:
* @main: the #SpiceMainChannel that emitted the signal
*
* Since: 0.6
**/
signals[SPICE_MAIN_CLIPBOARD_SELECTION] =
g_signal_new("main-clipboard-selection",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_user_marshal_VOID__UINT_UINT_POINTER_UINT,
G_TYPE_NONE,
4,
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-grab:
* @main: the #SpiceMainChannel that emitted the signal
* @types: the VD_AGENT_CLIPBOARD data types
* @ntypes: the number of @types
*
* Inform when clipboard data is available from the guest, and for
* which @types.
*
* Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-grab instead.
**/
signals[SPICE_MAIN_CLIPBOARD_GRAB] =
g_signal_new("main-clipboard-grab",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
0,
NULL, NULL,
g_cclosure_user_marshal_BOOLEAN__POINTER_UINT,
G_TYPE_BOOLEAN,
2,
G_TYPE_POINTER, G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-selection-grab:
* @main: the #SpiceMainChannel that emitted the signal
* @types: the VD_AGENT_CLIPBOARD data types
* @ntypes: the number of @types
*
* Inform when clipboard data is available from the guest, and for
* which @types.
*
* Since: 0.6
**/
signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB] =
g_signal_new("main-clipboard-selection-grab",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_user_marshal_BOOLEAN__UINT_POINTER_UINT,
G_TYPE_BOOLEAN,
3,
G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-request:
* @main: the #SpiceMainChannel that emitted the signal
* @types: the VD_AGENT_CLIPBOARD request type
*
* Return value: %TRUE if the request is successful
*
* Request clipbard data from the client.
*
* Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-request instead.
**/
signals[SPICE_MAIN_CLIPBOARD_REQUEST] =
g_signal_new("main-clipboard-request",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
0,
NULL, NULL,
g_cclosure_user_marshal_BOOLEAN__UINT,
G_TYPE_BOOLEAN,
1,
G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-selection-request:
* @main: the #SpiceMainChannel that emitted the signal
* @types: the VD_AGENT_CLIPBOARD request type
*
* Return value: %TRUE if the request is successful
*
* Request clipbard data from the client.
*
* Since: 0.6
**/
signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST] =
g_signal_new("main-clipboard-selection-request",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_user_marshal_BOOLEAN__UINT_UINT,
G_TYPE_BOOLEAN,
2,
G_TYPE_UINT, G_TYPE_UINT);
/**
* SpiceMainChannel::main-clipboard-release:
* @main: the #SpiceMainChannel that emitted the signal
*
* Inform when the clipboard is released from the guest, when no
* clipboard data is available from the guest.
*
* Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-release instead.
**/
signals[SPICE_MAIN_CLIPBOARD_RELEASE] =
g_signal_new("main-clipboard-release",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* SpiceMainChannel::main-clipboard-selection-release:
* @main: the #SpiceMainChannel that emitted the signal
*
* Inform when the clipboard is released from the guest, when no
* clipboard data is available from the guest.
*
* Since: 0.6
**/
signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE] =
g_signal_new("main-clipboard-selection-release",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE,
1,
G_TYPE_UINT);
/**
* SpiceMainChannel::migration-started:
* @main: the #SpiceMainChannel that emitted the signal
* @session: a migration #SpiceSession
*
* Inform when migration is starting. Application wishing to make
* connections themself can set the #SpiceSession:client-sockets
* to @TRUE, then follow #SpiceSession::channel-new creation, and
* use spice_channel_open_fd() once the socket is created.
*
**/
signals[SPICE_MIGRATION_STARTED] =
g_signal_new("migration-started",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
}
/* signal trampoline---------------------------------------------------------- */
struct SPICE_MAIN_CLIPBOARD_RELEASE {
};
struct SPICE_MAIN_AGENT_UPDATE {
};
struct SPICE_MAIN_MOUSE_UPDATE {
};
struct SPICE_MAIN_CLIPBOARD {
guint type;
gpointer data;
gsize size;
};
struct SPICE_MAIN_CLIPBOARD_GRAB {
gpointer types;
gsize ntypes;
gboolean *ret;
};
struct SPICE_MAIN_CLIPBOARD_REQUEST {
guint type;
gboolean *ret;
};
struct SPICE_MAIN_CLIPBOARD_SELECTION {
guint8 selection;
guint type;
gpointer data;
gsize size;
};
struct SPICE_MAIN_CLIPBOARD_SELECTION_GRAB {
guint8 selection;
gpointer types;
gsize ntypes;
gboolean *ret;
};
struct SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST {
guint8 selection;
guint type;
gboolean *ret;
};
struct SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE {
guint8 selection;
};
/* main context */
static void do_emit_main_context(GObject *object, int signum, gpointer params)
{
switch (signum) {
case SPICE_MAIN_CLIPBOARD_RELEASE:
case SPICE_MAIN_AGENT_UPDATE:
case SPICE_MAIN_MOUSE_UPDATE: {
g_signal_emit(object, signals[signum], 0);
break;
}
case SPICE_MAIN_CLIPBOARD: {
struct SPICE_MAIN_CLIPBOARD *p = params;
g_signal_emit(object, signals[signum], 0,
p->type, p->data, p->size);
break;
}
case SPICE_MAIN_CLIPBOARD_GRAB: {
struct SPICE_MAIN_CLIPBOARD_GRAB *p = params;
g_signal_emit(object, signals[signum], 0,
p->types, p->ntypes, p->ret);
break;
}
case SPICE_MAIN_CLIPBOARD_REQUEST: {
struct SPICE_MAIN_CLIPBOARD_REQUEST *p = params;
g_signal_emit(object, signals[signum], 0,
p->type, p->ret);
break;
}
case SPICE_MAIN_CLIPBOARD_SELECTION: {
struct SPICE_MAIN_CLIPBOARD_SELECTION *p = params;
g_signal_emit(object, signals[signum], 0,
p->selection, p->type, p->data, p->size);
break;
}
case SPICE_MAIN_CLIPBOARD_SELECTION_GRAB: {
struct SPICE_MAIN_CLIPBOARD_SELECTION_GRAB *p = params;
g_signal_emit(object, signals[signum], 0,
p->selection, p->types, p->ntypes, p->ret);
break;
}
case SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST: {
struct SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST *p = params;
g_signal_emit(object, signals[signum], 0,
p->selection, p->type, p->ret);
break;
}
case SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE: {
struct SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE *p = params;
g_signal_emit(object, signals[signum], 0,
p->selection);
break;
}
default:
g_warn_if_reached();
}
}
/* ------------------------------------------------------------------ */
static void agent_free_msg_queue(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
SpiceMsgOut *out;
if (!c->agent_msg_queue)
return;
while (!g_queue_is_empty(c->agent_msg_queue)) {
out = g_queue_pop_head(c->agent_msg_queue);
spice_msg_out_unref(out);
}
g_queue_free(c->agent_msg_queue);
c->agent_msg_queue = NULL;
}
/* Here, flushing algorithm is stolen from spice-channel.c */
static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
{
SpiceMainChannelPrivate *c = channel->priv;
GSList *l;
for (l = c->flushing; l != NULL; l = l->next) {
GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
g_simple_async_result_set_op_res_gboolean(result, success);
g_simple_async_result_complete_in_idle(result);
}
g_slist_free_full(c->flushing, g_object_unref);
c->flushing = NULL;
}
static void file_xfer_flush_async(SpiceMainChannel *channel, GCancellable *cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
GSimpleAsyncResult *simple;
SpiceMainChannelPrivate *c = channel->priv;
gboolean was_empty;
simple = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
file_xfer_flush_async);
was_empty = g_queue_is_empty(c->agent_msg_queue);
if (was_empty) {
g_simple_async_result_set_op_res_gboolean(simple, TRUE);
g_simple_async_result_complete_in_idle(simple);
g_object_unref(simple);
return;
}
c->flushing = g_slist_append(c->flushing, simple);
}
static gboolean file_xfer_flush_finish(SpiceMainChannel *channel, GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
g_return_val_if_fail(g_simple_async_result_is_valid(result,
G_OBJECT(channel), file_xfer_flush_async), FALSE);
if (g_simple_async_result_propagate_error(simple, error)) {
return FALSE;
}
CHANNEL_DEBUG(channel, "flushed finished!");
return g_simple_async_result_get_op_res_gboolean(simple);
}
/* coroutine context */
static void agent_send_msg_queue(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
SpiceMsgOut *out;
while (c->agent_tokens > 0 &&
!g_queue_is_empty(c->agent_msg_queue)) {
c->agent_tokens--;
out = g_queue_pop_head(c->agent_msg_queue);
spice_msg_out_send_internal(out);
}
if (g_queue_is_empty(c->agent_msg_queue) && c->flushing != NULL) {
file_xfer_flushed(channel, TRUE);
}
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue()
expected arguments, pair of data/data_size to send terminated with NULL:
agent_msg_queue_many(main, VD_AGENT_...,
&foo, sizeof(Foo),
data, data_size, NULL);
*/
G_GNUC_NULL_TERMINATED
static void agent_msg_queue_many(SpiceMainChannel *channel, int type, const void *data, ...)
{
va_list args;
SpiceMainChannelPrivate *c = channel->priv;
SpiceMsgOut *out;
VDAgentMessage msg;
guint8 *payload;
gsize paysize, s, mins, size = 0;
const guint8 *d;
G_STATIC_ASSERT(VD_AGENT_MAX_DATA_SIZE > sizeof(VDAgentMessage));
va_start(args, data);
for (d = data; d != NULL; d = va_arg(args, void*)) {
size += va_arg(args, gsize);
}
va_end(args);
msg.protocol = VD_AGENT_PROTOCOL;
msg.type = type;
msg.opaque = 0;
msg.size = size;
paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size + sizeof(VDAgentMessage));
out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
payload = spice_marshaller_reserve_space(out->marshaller, paysize);
memcpy(payload, &msg, sizeof(VDAgentMessage));
payload += sizeof(VDAgentMessage);
paysize -= sizeof(VDAgentMessage);
if (paysize == 0) {
g_queue_push_tail(c->agent_msg_queue, out);
out = NULL;
}
va_start(args, data);
for (d = data; size > 0; d = va_arg(args, void*)) {
s = va_arg(args, gsize);
while (s > 0) {
if (out == NULL) {
paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size);
out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
payload = spice_marshaller_reserve_space(out->marshaller, paysize);
}
mins = MIN(paysize, s);
memcpy(payload, d, mins);
d += mins;
payload += mins;
s -= mins;
size -= mins;
paysize -= mins;
if (paysize == 0) {
g_queue_push_tail(c->agent_msg_queue, out);
out = NULL;
}
}
}
va_end(args);
g_warn_if_fail(out == NULL);
}
static int monitors_cmp(const void *p1, const void *p2, gpointer user_data)
{
const VDAgentMonConfig *m1 = p1;
const VDAgentMonConfig *m2 = p2;
double d1 = sqrt(m1->x * m1->x + m1->y * m1->y);
double d2 = sqrt(m2->x * m2->x + m2->y * m2->y);
int diff = d1 - d2;
return diff == 0 ? (char*)p1 - (char*)p2 : diff;
}
static void monitors_align(VDAgentMonConfig *monitors, int nmonitors)
{
gint i, j, x = 0;
guint32 used = 0;
VDAgentMonConfig *sorted_monitors;
if (nmonitors == 0)
return;
/* sort by distance from origin */
sorted_monitors = g_memdup(monitors, nmonitors * sizeof(VDAgentMonConfig));
g_qsort_with_data(sorted_monitors, nmonitors, sizeof(VDAgentMonConfig), monitors_cmp, NULL);
/* super-KISS ltr alignment, feel free to improve */
for (i = 0; i < nmonitors; i++) {
/* Find where this monitor is in the sorted order */
for (j = 0; j < nmonitors; j++) {
/* Avoid using the same entry twice, this happens with older
virt-viewer versions which always set x and y to 0 */
if (used & (1 << j))
continue;
if (memcmp(&monitors[j], &sorted_monitors[i],
sizeof(VDAgentMonConfig)) == 0)
break;
}
used |= 1 << j;
monitors[j].x = x;
monitors[j].y = 0;
x += monitors[j].width;
if (monitors[j].width || monitors[j].height)
SPICE_DEBUG("#%d +%d+%d-%dx%d", j, monitors[j].x, monitors[j].y,
monitors[j].width, monitors[j].height);
}
g_free(sorted_monitors);
}
#define agent_msg_queue(Channel, Type, Size, Data) \
agent_msg_queue_many((Channel), (Type), (Data), (Size), NULL)
/**
* spice_main_send_monitor_config:
* @channel:
*
* Send monitors configuration previously set with
* spice_main_set_display() and spice_main_set_display_enabled()
*
* Returns: %TRUE on success.
**/
gboolean spice_main_send_monitor_config(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c;
VDAgentMonitorsConfig *mon;
int i, j, monitors;
size_t size;
g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
c = channel->priv;
g_return_val_if_fail(c->agent_connected, FALSE);
if (spice_main_agent_test_capability(channel,
VD_AGENT_CAP_SPARSE_MONITORS_CONFIG)) {
monitors = SPICE_N_ELEMENTS(c->display);
} else {
monitors = 0;
for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
if (c->display[i].enabled)
monitors += 1;
}
}
size = sizeof(VDAgentMonitorsConfig) + sizeof(VDAgentMonConfig) * monitors;
mon = spice_malloc0(size);
mon->num_of_monitors = monitors;
if (c->disable_display_position == FALSE ||
c->disable_display_align == FALSE)
mon->flags |= VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS;
j = 0;
for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
if (!c->display[i].enabled) {
if (spice_main_agent_test_capability(channel,
VD_AGENT_CAP_SPARSE_MONITORS_CONFIG))
j++;
continue;
}
mon->monitors[j].depth = c->display_color_depth ? c->display_color_depth : 32;
mon->monitors[j].width = c->display[i].width;
mon->monitors[j].height = c->display[i].height;
mon->monitors[j].x = c->display[i].x;
mon->monitors[j].y = c->display[i].y;
CHANNEL_DEBUG(channel, "monitor config: #%d %dx%d+%d+%d @ %d bpp", j,
mon->monitors[j].width, mon->monitors[j].height,
mon->monitors[j].x, mon->monitors[j].y,
mon->monitors[j].depth);
j++;
}
if (c->disable_display_align == FALSE)
monitors_align(mon->monitors, mon->num_of_monitors);
agent_msg_queue(channel, VD_AGENT_MONITORS_CONFIG, size, mon);
free(mon);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
if (c->timer_id != 0) {
g_source_remove(c->timer_id);
c->timer_id = 0;
}
return TRUE;
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_display_config(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
VDAgentDisplayConfig config = { 0, };
if (c->display_disable_wallpaper) {
config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER;
}
if (c->display_disable_font_smooth) {
config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH;
}
if (c->display_disable_animation) {
config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION;
}
if (c->display_color_depth != 0) {
config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH;
config.depth = c->display_color_depth;
}
CHANNEL_DEBUG(channel, "display_config: flags: %u, depth: %u", config.flags, config.depth);
agent_msg_queue(channel, VD_AGENT_DISPLAY_CONFIG, sizeof(VDAgentDisplayConfig), &config);
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_announce_caps(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
VDAgentAnnounceCapabilities *caps;
size_t size;
if (!c->agent_connected)
return;
size = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES;
caps = spice_malloc0(size);
if (!c->agent_caps_received)
caps->request = 1;
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);
VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
agent_msg_queue(channel, VD_AGENT_ANNOUNCE_CAPABILITIES, size, caps);
free(caps);
}
#define HAS_CLIPBOARD_SELECTION(c) \
VD_AGENT_HAS_CAPABILITY((c)->agent_caps, G_N_ELEMENTS((c)->agent_caps), VD_AGENT_CAP_CLIPBOARD_SELECTION)
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_clipboard_grab(SpiceMainChannel *channel, guint selection,
guint32 *types, int ntypes)
{
SpiceMainChannelPrivate *c = channel->priv;
guint8 *msg;
VDAgentClipboardGrab *grab;
size_t size;
int i;
if (!c->agent_connected)
return;
g_return_if_fail(VD_AGENT_HAS_CAPABILITY(c->agent_caps,
G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
size = sizeof(VDAgentClipboardGrab) + sizeof(uint32_t) * ntypes;
if (HAS_CLIPBOARD_SELECTION(c))
size += 4;
else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
CHANNEL_DEBUG(channel, "Ignoring clipboard grab");
return;
}
msg = g_alloca(size);
memset(msg, 0, size);
grab = (VDAgentClipboardGrab *)msg;
if (HAS_CLIPBOARD_SELECTION(c)) {
msg[0] = selection;
grab = (VDAgentClipboardGrab *)(msg + 4);
}
for (i = 0; i < ntypes; i++) {
grab->types[i] = types[i];
}
agent_msg_queue(channel, VD_AGENT_CLIPBOARD_GRAB, size, msg);
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_clipboard_notify(SpiceMainChannel *channel, guint selection,
guint32 type, const guchar *data, size_t size)
{
SpiceMainChannelPrivate *c = channel->priv;
VDAgentClipboard *cb;
guint8 *msg;
size_t msgsize;
g_return_if_fail(c->agent_connected);
g_return_if_fail(VD_AGENT_HAS_CAPABILITY(c->agent_caps,
G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
msgsize = sizeof(VDAgentClipboard);
if (HAS_CLIPBOARD_SELECTION(c))
msgsize += 4;
else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
CHANNEL_DEBUG(channel, "Ignoring clipboard notify");
return;
}
msg = g_alloca(msgsize);
memset(msg, 0, msgsize);
cb = (VDAgentClipboard *)msg;
if (HAS_CLIPBOARD_SELECTION(c)) {
msg[0] = selection;
cb = (VDAgentClipboard *)(msg + 4);
}
cb->type = type;
agent_msg_queue_many(channel, VD_AGENT_CLIPBOARD, msg, msgsize, data, size, NULL);
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_clipboard_request(SpiceMainChannel *channel, guint selection, guint32 type)
{
SpiceMainChannelPrivate *c = channel->priv;
VDAgentClipboardRequest *request;
guint8 *msg;
size_t msgsize;
g_return_if_fail(c->agent_connected);
g_return_if_fail(VD_AGENT_HAS_CAPABILITY(c->agent_caps,
G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
msgsize = sizeof(VDAgentClipboardRequest);
if (HAS_CLIPBOARD_SELECTION(c))
msgsize += 4;
else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
SPICE_DEBUG("Ignoring clipboard request");
return;
}
msg = g_alloca(msgsize);
memset(msg, 0, msgsize);
request = (VDAgentClipboardRequest *)msg;
if (HAS_CLIPBOARD_SELECTION(c)) {
msg[0] = selection;
request = (VDAgentClipboardRequest *)(msg + 4);
}
request->type = type;
agent_msg_queue(channel, VD_AGENT_CLIPBOARD_REQUEST, msgsize, msg);
}
/* any context: the message is not flushed immediately,
you can wakeup() the channel coroutine or send_msg_queue() */
static void agent_clipboard_release(SpiceMainChannel *channel, guint selection)
{
SpiceMainChannelPrivate *c = channel->priv;
guint8 msg[4] = { 0, };
guint8 msgsize = 0;
g_return_if_fail(c->agent_connected);
g_return_if_fail(VD_AGENT_HAS_CAPABILITY(c->agent_caps,
G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
if (HAS_CLIPBOARD_SELECTION(c)) {
msg[0] = selection;
msgsize += 4;
} else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
SPICE_DEBUG("Ignoring clipboard release");
return;
}
agent_msg_queue(channel, VD_AGENT_CLIPBOARD_RELEASE, msgsize, msg);
}
/* main context*/
static gboolean timer_set_display(gpointer data)
{
SpiceMainChannel *channel = data;
SpiceMainChannelPrivate *c = channel->priv;
c->timer_id = 0;
if (c->agent_connected)
spice_main_send_monitor_config(channel);
return FALSE;
}
/* any context */
static void update_display_timer(SpiceMainChannel *channel, guint seconds)
{
SpiceMainChannelPrivate *c = channel->priv;
if (c->timer_id)
g_source_remove(c->timer_id);
c->timer_id = g_timeout_add_seconds(seconds, timer_set_display, channel);
}
/* coroutine context */
static void set_agent_connected(SpiceMainChannel *channel, gboolean connected)
{
SpiceMainChannelPrivate *c = channel->priv;
SPICE_DEBUG("agent connected: %s", spice_yes_no(connected));
if (connected != c->agent_connected) {
c->agent_connected = connected;
g_object_notify_main_context(G_OBJECT(channel), "agent-connected");
}
}
/* coroutine context */
static void agent_start(SpiceMainChannel *channel)
{
SpiceMainChannelPrivate *c = channel->priv;
SpiceMsgcMainAgentStart agent_start = {
.num_tokens = ~0,
};
SpiceMsgOut *out;
set_agent_connected(channel, TRUE);
c->agent_caps_received = false;
emit_main_context(channel, SPICE_MAIN_AGENT_UPDATE);
out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_START);
out->marshallers->msgc_main_agent_start(out->marshaller, &agent_start);
spice_msg_out_send_internal(out);
if (c->agent_connected) {
agent_announce_caps(channel);
agent_send_msg_queue(channel);
}
}
/* coroutine context */
static void agent_stopped(SpiceMainChannel *channel)
{
spice_main_channel_reset_agent(channel);
set_agent_connected(channel, FALSE); /* For notify */
emit_main_context(channel, SPICE_MAIN_AGENT_UPDATE);
}
/* coroutine context */
static void set_mouse_mode(SpiceMainChannel *channel, uint32_t supported, uint32_t current)
{
SpiceMainChannelPrivate *c = channel->priv;
if (c->mouse_mode != current) {
c->mouse_mode = current;
emit_main_context(channel, SPICE_MAIN_MOUSE_UPDATE);
g_object_notify_main_context(G_OBJECT(channel), "mouse-mode");
}
/* switch to client mode if possible */
if (!spice_channel_get_read_only(SPICE_CHANNEL(channel)) &&
supported & SPICE_MOUSE_MODE_CLIENT &&
current != SPICE_MOUSE_MODE_CLIENT) {
SpiceMsgcMainMouseModeRequest req = {
.mode = SPICE_MOUSE_MODE_CLIENT,
};
SpiceMsgOut *out;
out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST);
out->marshallers->msgc_main_mouse_mode_request(out->marshaller, &req);
spice_msg_out_send_internal(out);
}
}
/* coroutine context */
static void main_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
SpiceMsgMainInit *init = spice_msg_in_parsed(in);
SpiceSession *session;
SpiceMsgOut *out;
session = spice_channel_get_session(channel);
spice_session_set_connection_id(session, init->session_id);
set_mouse_mode(SPICE_MAIN_CHANNEL(channel), init->supported_mouse_modes,
init->current_mouse_mode);
spice_session_set_mm_time(session, init->multi_media_time);
spice_session_set_caches_hints(session, init->ram_hint, init->display_channels_hint);
c->agent_tokens = init->agent_tokens;
if (init->agent_connected)
agent_start(SPICE_MAIN_CHANNEL(channel));
if (spice_session_migrate_after_main_init(session))
return;
out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
spice_msg_out_send_internal(out);
}
/* coroutine context */
static void main_handle_name(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainName *name = spice_msg_in_parsed(in);
SpiceSession *session = spice_channel_get_session(channel);
SPICE_DEBUG("server name: %s", name->name);
spice_session_set_name(session, (const gchar *)name->name);
}
/* coroutine context */
static void main_handle_uuid(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainUuid *uuid = spice_msg_in_parsed(in);
SpiceSession *session = spice_channel_get_session(channel);
gchar *uuid_str = spice_uuid_to_string(uuid->uuid);
SPICE_DEBUG("server uuid: %s", uuid_str);
spice_session_set_uuid(session, uuid->uuid);
g_free(uuid_str);
}
/* coroutine context */
static void main_handle_mm_time(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceSession *session;
SpiceMsgMainMultiMediaTime *msg = spice_msg_in_parsed(in);
session = spice_channel_get_session(channel);
spice_session_set_mm_time(session, msg->time);
}
typedef struct channel_new {
SpiceSession *session;
int type;
int id;
} channel_new_t;
/* main context */
static gboolean _channel_new(channel_new_t *c)
{
g_return_val_if_fail(c != NULL, FALSE);
spice_channel_new(c->session, c->type, c->id);
g_object_unref(c->session);
g_free(c);
return FALSE;
}
/* coroutine context */
static void main_handle_channels_list(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgChannels *msg = spice_msg_in_parsed(in);
SpiceSession *session;
int i;
session = spice_channel_get_session(channel);
for (i = 0; i < msg->num_of_channels; i++) {
channel_new_t *c;
c = g_new(channel_new_t, 1);
c->session = g_object_ref(session);
c->type = msg->channels[i].type;
c->id = msg->channels[i].id;
/* no need to explicitely switch to main context, since
synchronous call is not needed. */
/* no need to track idle, session is refed */
g_idle_add((GSourceFunc)_channel_new, c);
}
}
/* coroutine context */
static void main_handle_mouse_mode(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainMouseMode *msg = spice_msg_in_parsed(in);
set_mouse_mode(SPICE_MAIN_CHANNEL(channel), msg->supported_modes, msg->current_mode);
}
/* coroutine context */
static void main_handle_agent_connected(SpiceChannel *channel, SpiceMsgIn *in)
{
agent_start(SPICE_MAIN_CHANNEL(channel));
update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
}
/* coroutine context */
static void main_handle_agent_connected_tokens(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
SpiceMsgMainAgentConnectedTokens *msg = spice_msg_in_parsed(in);
c->agent_tokens = msg->num_tokens;
agent_start(SPICE_MAIN_CHANNEL(channel));
update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
}
/* coroutine context */
static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in)
{
agent_stopped(SPICE_MAIN_CHANNEL(channel));
}
static void file_xfer_task_free(SpiceFileXferTask *task)
{
SpiceMainChannelPrivate *c;
g_return_if_fail(task != NULL);
c = task->channel->priv;
g_hash_table_remove(c->file_xfer_tasks, GUINT_TO_POINTER(task->id));
g_clear_object(&task->channel);
g_clear_object(&task->file);
g_clear_object(&task->file_stream);
g_free(task);
}
/* main context */
static void file_xfer_close_cb(GObject *object,
GAsyncResult *close_res,
gpointer user_data)
{
GSimpleAsyncResult *res;
SpiceFileXferTask *task;
GError *error = NULL;
task = user_data;
if (object) {
GInputStream *stream = G_INPUT_STREAM(object);
g_input_stream_close_finish(stream, close_res, &error);
if (error) {
/* This error dont need to report to user, just print a log */
SPICE_DEBUG("close file error: %s", error->message);
g_clear_error(&error);
}
}
/* Notify to user that files have been transferred or something error
happened. */
res = g_simple_async_result_new(G_OBJECT(task->channel),
task->callback,
task->user_data,
spice_main_file_copy_async);
if (task->error) {
g_simple_async_result_take_error(res, task->error);
g_simple_async_result_set_op_res_gboolean(res, FALSE);
} else {
g_simple_async_result_set_op_res_gboolean(res, TRUE);
}
g_simple_async_result_complete_in_idle(res);
g_object_unref(res);
file_xfer_task_free(task);
}
static void file_xfer_data_flushed_cb(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
SpiceFileXferTask *task = user_data;
SpiceMainChannel *channel = (SpiceMainChannel *)source_object;
GError *error = NULL;
task->pending = FALSE;
file_xfer_flush_finish(channel, res, &error);
if (error || task->error) {
file_xfer_completed(task, error);
return;
}
if (task->progress_callback)
task->progress_callback(task->read_bytes, task->file_size,
task->progress_callback_data);
/* Read more data */
file_xfer_continue_read(task);
}
static void file_xfer_queue(SpiceFileXferTask *task, int data_size)
{
VDAgentFileXferDataMessage msg;
SpiceMainChannel *channel = SPICE_MAIN_CHANNEL(task->channel);
msg.id = task->id;
msg.size = data_size;
agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
&msg, sizeof(msg),
task->buffer, data_size, NULL);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
/* main context */
static void file_xfer_read_cb(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
SpiceFileXferTask *task = user_data;
SpiceMainChannel *channel = task->channel;
gssize count;
GError *error = NULL;
task->pending = FALSE;
count = g_input_stream_read_finish(G_INPUT_STREAM(task->file_stream),
res, &error);
/* Check for pending earlier errors */
if (task->error) {
file_xfer_completed(task, error);
return;
}
if (count > 0) {
task->read_bytes += count;
file_xfer_queue(task, count);
file_xfer_flush_async(channel, task->cancellable,
file_xfer_data_flushed_cb, task);
task->pending = TRUE;
} else if (error) {
VDAgentFileXferStatusMessage msg = {
.id = task->id,
.result = VD_AGENT_FILE_XFER_STATUS_ERROR,
};
agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_STATUS,
&msg, sizeof(msg), NULL);
spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
file_xfer_completed(task, error);
}
/* else EOF, do nothing (wait for VD_AGENT_FILE_XFER_STATUS from agent) */
}
/* coroutine context */
static void file_xfer_continue_read(SpiceFileXferTask *task)
{
g_input_stream_read_async(G_INPUT_STREAM(task->file_stream),
task->buffer,
FILE_XFER_CHUNK_SIZE,
G_PRIORITY_DEFAULT,
task->cancellable,
file_xfer_read_cb,
task);
task->pending = TRUE;
}
/* coroutine context */
static void file_xfer_handle_status(SpiceMainChannel *channel,
VDAgentFileXferStatusMessage *msg)
{
SpiceMainChannelPrivate *c = channel->priv;
SpiceFileXferTask *task;
GError *error = NULL;
task = g_hash_table_lookup(c->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
if (task == NULL) {
SPICE_DEBUG("cannot find task %d", msg->id);
return;
}
SPICE_DEBUG("task %d received response %d", msg->id, msg->result);
switch (msg->result) {
case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
if (task->pending) {
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"transfer received CAN_SEND_DATA in pending state");
break;
}
file_xfer_continue_read(task);
return;
case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"transfer is cancelled by spice agent");
break;
case VD_AGENT_FILE_XFER_STATUS_ERROR:
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"some errors occurred in the spice agent");
break;
case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
if (task->pending)
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"transfer received success in pending state");
break;
default:
g_warn_if_reached();
error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"unhandled status type: %u", msg->result);
break;
}
file_xfer_completed(task, error);
}
/* coroutine context */
static void main_agent_handle_msg(SpiceChannel *channel,
VDAgentMessage *msg, gpointer payload)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
guint8 selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
g_return_if_fail(msg->protocol == VD_AGENT_PROTOCOL);
switch (msg->type) {
case VD_AGENT_CLIPBOARD_RELEASE:
case VD_AGENT_CLIPBOARD_REQUEST:
case VD_AGENT_CLIPBOARD_GRAB:
case VD_AGENT_CLIPBOARD:
if (VD_AGENT_HAS_CAPABILITY(c->agent_caps, G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
selection = *((guint8*)payload);
payload = ((guint8*)payload) + 4;
msg->size -= 4;
}
break;
default:
break;
}
switch (msg->type) {
case VD_AGENT_ANNOUNCE_CAPABILITIES:
{
VDAgentAnnounceCapabilities *caps = payload;
int i, size;
size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg->size);
if (size > VD_AGENT_CAPS_SIZE)
size = VD_AGENT_CAPS_SIZE;
memset(c->agent_caps, 0, sizeof(c->agent_caps));
for (i = 0; i < size * 32; i++) {
if (!VD_AGENT_HAS_CAPABILITY(caps->caps, size, i))
continue;
SPICE_DEBUG("%s: cap: %d (%s)", __FUNCTION__,
i, NAME(agent_caps, i));
VD_AGENT_SET_CAPABILITY(c->agent_caps, i);
}
c->agent_caps_received = true;
emit_main_context(channel, SPICE_MAIN_AGENT_UPDATE);
if (caps->request)
agent_announce_caps(SPICE_MAIN_CHANNEL(channel));
if (VD_AGENT_HAS_CAPABILITY(caps->caps, G_N_ELEMENTS(c->agent_caps), VD_AGENT_CAP_DISPLAY_CONFIG) &&
!c->agent_display_config_sent) {
agent_display_config(SPICE_MAIN_CHANNEL(channel));
agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
c->agent_display_config_sent = true;
}
break;
}
case VD_AGENT_CLIPBOARD:
{
VDAgentClipboard *cb = payload;
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_SELECTION, selection,
cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
emit_main_context(channel, SPICE_MAIN_CLIPBOARD,
cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
break;
}
case VD_AGENT_CLIPBOARD_GRAB:
{
gboolean ret;
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_SELECTION_GRAB, selection,
(guint8*)payload, msg->size / sizeof(uint32_t), &ret);
if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_GRAB,
payload, msg->size / sizeof(uint32_t), &ret);
break;
}
case VD_AGENT_CLIPBOARD_REQUEST:
{
gboolean ret;
VDAgentClipboardRequest *req = payload;
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST, selection,
req->type, &ret);
if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_REQUEST,
req->type, &ret);
break;
}
case VD_AGENT_CLIPBOARD_RELEASE:
{
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE, selection);
if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
emit_main_context(channel, SPICE_MAIN_CLIPBOARD_RELEASE);
break;
}
case VD_AGENT_REPLY:
{
VDAgentReply *reply = payload;
SPICE_DEBUG("%s: reply: type %d, %s", __FUNCTION__, reply->type,
reply->error == VD_AGENT_SUCCESS ? "success" : "error");
break;
}
case VD_AGENT_FILE_XFER_STATUS:
file_xfer_handle_status(SPICE_MAIN_CHANNEL(channel), payload);
break;
default:
g_warning("unhandled agent message type: %u (%s), size %u",
msg->type, NAME(agent_msg_types, msg->type), msg->size);
}
}
/* coroutine context */
static void main_handle_agent_data_msg(SpiceChannel* channel, int* msg_size, guchar** msg_pos)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
int n;
if (c->agent_msg_pos < sizeof(VDAgentMessage)) {
n = MIN(sizeof(VDAgentMessage) - c->agent_msg_pos, *msg_size);
memcpy((uint8_t*)&c->agent_msg + c->agent_msg_pos, *msg_pos, n);
c->agent_msg_pos += n;
*msg_size -= n;
*msg_pos += n;
if (c->agent_msg_pos == sizeof(VDAgentMessage)) {
SPICE_DEBUG("agent msg start: msg_size=%d, protocol=%d, type=%d",
c->agent_msg.size, c->agent_msg.protocol, c->agent_msg.type);
g_return_if_fail(c->agent_msg_data == NULL);
c->agent_msg_data = g_malloc(c->agent_msg.size);
}
}
if (c->agent_msg_pos >= sizeof(VDAgentMessage)) {
n = MIN(sizeof(VDAgentMessage) + c->agent_msg.size - c->agent_msg_pos, *msg_size);
memcpy(c->agent_msg_data + c->agent_msg_pos - sizeof(VDAgentMessage), *msg_pos, n);
c->agent_msg_pos += n;
*msg_size -= n;
*msg_pos += n;
}
if (c->agent_msg_pos == sizeof(VDAgentMessage) + c->agent_msg.size) {
main_agent_handle_msg(channel, &c->agent_msg, c->agent_msg_data);
g_free(c->agent_msg_data);
c->agent_msg_data = NULL;
c->agent_msg_pos = 0;
}
}
/* coroutine context */
static void main_handle_agent_data(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
guint8 *data;
int len;
g_warn_if_fail(c->agent_connected);
/* shortcut to avoid extra message allocation & copy if possible */
if (c->agent_msg_pos == 0) {
VDAgentMessage *msg;
guint msg_size;
msg = spice_msg_in_raw(in, &len);
msg_size = msg->size;
if (msg_size + sizeof(VDAgentMessage) == len) {
main_agent_handle_msg(channel, msg, msg->data);
return;
}
}
data = spice_msg_in_raw(in, &len);
while (len > 0) {
main_handle_agent_data_msg(channel, &len, &data);
}
}
/* coroutine context */
static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainAgentTokens *tokens = spice_msg_in_parsed(in);
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
c->agent_tokens += tokens->num_tokens;
agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
}
/* main context */
static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
g_signal_connect(channel, "channel-event",
G_CALLBACK(migrate_channel_event_cb), data);
}
static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int id)
{
SPICE_DEBUG("migrate_channel_connect %d:%d", type, id);
SpiceChannel *newc = spice_channel_new(mig->session, type, id);
spice_channel_connect(newc);
mig->nchannels++;
return newc;
}
/* coroutine context */
static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
c->migrate_data->do_seamless = false;
g_idle_add(main_migrate_handshake_done, c->migrate_data);
} else {
SpiceMsgcMainMigrateDstDoSeamless msg_data;
SpiceMsgOut *msg_out;
msg_data.src_version = c->migrate_data->src_mig_version;
msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
spice_msg_out_send_internal(msg_out);
}
}
/* main context */
static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
gpointer data)
{
spice_migrate *mig = data;
SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
SpiceSession *session;
g_return_if_fail(mig->nchannels > 0);
g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
session = spice_channel_get_session(mig->src_channel);
switch (event) {
case SPICE_CHANNEL_OPENED:
if (c->channel_type == SPICE_CHANNEL_MAIN) {
if (mig->do_seamless) {
SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
mig->dst_channel = channel;
main_priv->migrate_data = mig;
} else {
c->state = SPICE_CHANNEL_STATE_MIGRATING;
mig->nchannels--;
}
/* now connect the rest of the channels */
GList *channels, *l;
l = channels = spice_session_get_channels(session);
while (l != NULL) {
SpiceChannelPrivate *curc = SPICE_CHANNEL(l->data)->priv;
l = l->next;
if (curc->channel_type == SPICE_CHANNEL_MAIN)
continue;
migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
}
g_list_free(channels);
} else {
c->state = SPICE_CHANNEL_STATE_MIGRATING;
mig->nchannels--;
}
SPICE_DEBUG("migration: channel opened chan:%p, left %d", channel, mig->nchannels);
if (mig->nchannels == 0)
coroutine_yieldto(mig->from, NULL);
break;
default:
SPICE_DEBUG("error or unhandled channel event during migration: %d", event);
/* go back to main channel to report error */
coroutine_yieldto(mig->from, NULL);
}
}
/* main context */
static gboolean main_migrate_handshake_done(gpointer data)
{
spice_migrate *mig = data;
SpiceChannelPrivate *c = SPICE_CHANNEL(mig->dst_channel)->priv;
g_return_val_if_fail(c->channel_type == SPICE_CHANNEL_MAIN, FALSE);
g_return_val_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
c->state = SPICE_CHANNEL_STATE_MIGRATING;
mig->nchannels--;
if (mig->nchannels == 0)
coroutine_yieldto(mig->from, NULL);
return FALSE;
}
#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;
/* main context */
static gboolean migrate_connect(gpointer data)
{
spice_migrate *mig = data;
SpiceChannelPrivate *c;
int port, sport;
const char *host;
SpiceSession *session;
g_return_val_if_fail(mig != NULL, FALSE);
g_return_val_if_fail(mig->info != NULL, FALSE);
g_return_val_if_fail(mig->nchannels == 0, FALSE);
c = SPICE_CHANNEL(mig->src_channel)->priv;
g_return_val_if_fail(c != NULL, FALSE);
session = spice_channel_get_session(mig->src_channel);
mig->session = spice_session_new_from_session(session);
mig->session->priv->migration_copy = true;
if ((c->peer_hdr.major_version == 1) &&
(c->peer_hdr.minor_version < 1)) {
OldRedMigrationBegin *info = (OldRedMigrationBegin *)mig->info;
SPICE_DEBUG("migrate_begin old %s %d %d",
info->host, info->port, info->sport);
port = info->port;
sport = info->sport;
host = info->host;
} else {
SpiceMigrationDstInfo *info = mig->info;
SPICE_DEBUG("migrate_begin %d %s %d %d",
info->host_size, info->host_data, info->port, info->sport);
port = info->port;
sport = info->sport;
host = (char*)info->host_data;
if ((c->peer_hdr.major_version == 1) ||
(c->peer_hdr.major_version == 2 && c->peer_hdr.minor_version < 1)) {
GByteArray *pubkey = g_byte_array_new();
g_byte_array_append(pubkey, info->pub_key_data, info->pub_key_size);
g_object_set(mig->session,
"pubkey", pubkey,
"verify", SPICE_SESSION_VERIFY_PUBKEY,
NULL);
g_byte_array_unref(pubkey);
} else if (info->cert_subject_size == 0 ||
strlen((const char*)info->cert_subject_data) == 0) {
/* only verify hostname if no cert subject */
g_object_set(mig->session, "verify", SPICE_SESSION_VERIFY_HOSTNAME, NULL);
} else {
gchar *subject = g_alloca(info->cert_subject_size + 1);
strncpy(subject, (const char*)info->cert_subject_data, info->cert_subject_size);
subject[info->cert_subject_size] = '\0';
// session data are already copied
g_object_set(mig->session,
"cert-subject", subject,
"verify", SPICE_SESSION_VERIFY_SUBJECT,
NULL);
}
}
if (g_getenv("SPICE_MIG_HOST"))
host = g_getenv("SPICE_MIG_HOST");
g_object_set(mig->session, "host", host, NULL);
spice_session_set_port(mig->session, port, FALSE);
spice_session_set_port(mig->session, sport, TRUE);
g_signal_connect(mig->session, "channel-new",
G_CALLBACK(migrate_channel_new_cb), mig);
g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
mig->session);
/* the migration process is in 2 steps, first the main channel and
then the rest of the channels */
mig->session->priv->cmain = migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
return FALSE;
}
/* coroutine context */
static void main_migrate_connect(SpiceChannel *channel,
SpiceMigrationDstInfo *dst_info, bool do_seamless,
uint32_t src_mig_version)
{
SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
spice_migrate mig = { 0, };
SpiceMsgOut *out;
int reply_type;
mig.src_channel = channel;
mig.info = dst_info;
mig.from = coroutine_self();
mig.do_seamless = do_seamless;
mig.src_mig_version = src_mig_version;
main_priv->migrate_data = &mig;
/* no need to track idle, call is sync for this coroutine */
g_idle_add(migrate_connect, &mig);
/* switch to main loop and wait for connections */
coroutine_yield(NULL);
g_return_if_fail(mig.session != NULL);
if (mig.nchannels != 0) {
reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
spice_session_disconnect(mig.session);
} else {
if (mig.do_seamless) {
SPICE_DEBUG("migration (seamless): connections all ok");
reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
} else {
SPICE_DEBUG("migration (semi-seamless): connections all ok");
reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
}
spice_session_set_migration(spice_channel_get_session(channel),
mig.session,
mig.do_seamless);
}
g_object_unref(mig.session);
out = spice_msg_out_new(SPICE_CHANNEL(channel), reply_type);
spice_msg_out_send(out);
}
/* coroutine context */
static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
main_migrate_connect(channel, &msg->dst_info, false, 0);
}
/* coroutine context */
static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
}
static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
main_priv->migrate_data->do_seamless = true;
g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
}
static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
main_priv->migrate_data->do_seamless = false;
g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
}
/* main context */
static gboolean migrate_delayed(gpointer data)
{
SpiceChannel *channel = data;
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
g_warn_if_fail(c->migrate_delayed_id != 0);
c->migrate_delayed_id = 0;
spice_session_migrate_end(channel->priv->session);
return FALSE;
}
/* coroutine context */
static void main_handle_migrate_end(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
SPICE_DEBUG("migrate end");
g_return_if_fail(c->migrate_delayed_id == 0);
g_return_if_fail(spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE));
c->migrate_delayed_id = g_idle_add(migrate_delayed, channel);
}
/* main context */
static gboolean switch_host_delayed(gpointer data)
{
SpiceChannel *channel = data;
SpiceSession *session;
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
g_warn_if_fail(c->switch_host_delayed_id != 0);
c->switch_host_delayed_id = 0;
session = spice_channel_get_session(channel);
spice_channel_disconnect(channel, SPICE_CHANNEL_SWITCHING);
spice_session_switching_disconnect(session);
spice_channel_connect(channel);
spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
return FALSE;
}
/* coroutine context */
static void main_handle_migrate_switch_host(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceMsgMainMigrationSwitchHost *mig = spice_msg_in_parsed(in);
SpiceSession *session;
char *host = (char *)mig->host_data;
char *subject = NULL;
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
g_return_if_fail(host[mig->host_size - 1] == '\0');
if (mig->cert_subject_size) {
subject = (char *)mig->cert_subject_data;
g_return_if_fail(subject[mig->cert_subject_size - 1] == '\0');
}
SPICE_DEBUG("migrate_switch %s %d %d %s",
host, mig->port, mig->sport, subject);
if (c->switch_host_delayed_id != 0) {
g_warning("Switching host already in progress, aborting it");
g_warn_if_fail(g_source_remove(c->switch_host_delayed_id));
c->switch_host_delayed_id = 0;
}
session = spice_channel_get_session(channel);
spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_SWITCHING);
g_object_set(session,
"host", host,
"cert-subject", subject,
NULL);
spice_session_set_port(session, mig->port, FALSE);
spice_session_set_port(session, mig->sport, TRUE);
c->switch_host_delayed_id = g_idle_add(switch_host_delayed, channel);
}
/* coroutine context */
static void main_handle_migrate_cancel(SpiceChannel *channel,
SpiceMsgIn *in G_GNUC_UNUSED)
{
SpiceSession *session;
SPICE_DEBUG("migrate_cancel");
session = spice_channel_get_session(channel);
spice_session_abort_migration(session);
}
static const spice_msg_handler main_handlers[] = {
[ SPICE_MSG_MAIN_INIT ] = main_handle_init,
[ SPICE_MSG_MAIN_NAME ] = main_handle_name,
[ SPICE_MSG_MAIN_UUID ] = main_handle_uuid,
[ SPICE_MSG_MAIN_CHANNELS_LIST ] = main_handle_channels_list,
[ SPICE_MSG_MAIN_MOUSE_MODE ] = main_handle_mouse_mode,
[ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ] = main_handle_mm_time,
[ SPICE_MSG_MAIN_AGENT_CONNECTED ] = main_handle_agent_connected,
[ SPICE_MSG_MAIN_AGENT_DISCONNECTED ] = main_handle_agent_disconnected,
[ SPICE_MSG_MAIN_AGENT_DATA ] = main_handle_agent_data,
[ SPICE_MSG_MAIN_AGENT_TOKEN ] = main_handle_agent_token,
[ SPICE_MSG_MAIN_MIGRATE_BEGIN ] = main_handle_migrate_begin,
[ SPICE_MSG_MAIN_MIGRATE_END ] = main_handle_migrate_end,
[ SPICE_MSG_MAIN_MIGRATE_CANCEL ] = main_handle_migrate_cancel,
[ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
[ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ] = main_handle_agent_connected_tokens,
[ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ] = main_handle_migrate_begin_seamless,
[ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK] = main_handle_migrate_dst_seamless_ack,
[ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
};
/* coroutine context */
static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
{
int type = spice_msg_in_type(msg);
SpiceChannelClass *parent_class;
SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
g_return_if_fail(type < SPICE_N_ELEMENTS(main_handlers));
parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
g_critical("unexpected msg (%d)."
"Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
return;
}
}
if (main_handlers[type] != NULL)
main_handlers[type](channel, msg);
else if (parent_class->handle_msg)
parent_class->handle_msg(channel, msg);
else
g_return_if_reached();
}
/**
* spice_main_agent_test_capability:
* @channel:
* @cap: an agent capability identifier
*
* Test capability of a remote agent.
*
* Returns: %TRUE if @cap (channel kind capability) is available.
**/
gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap)
{
g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
SpiceMainChannelPrivate *c = channel->priv;
if (!c->agent_caps_received)
return FALSE;
return VD_AGENT_HAS_CAPABILITY(c->agent_caps, G_N_ELEMENTS(c->agent_caps), cap);
}
/**
* spice_main_update_display:
* @channel:
* @id: display ID
* @x: x position
* @y: y position
* @width: display width
* @height: display height
* @update: if %TRUE, update guest resolution after 1sec.
*
* Update the display @id resolution.
*
* If @update is %TRUE, the remote configuration will be updated too
* after 1 second without further changes. You can send when you want
* without delay the new configuration to the remote with
* spice_main_send_monitor_config()
**/
void spice_main_update_display(SpiceMainChannel *channel, int id,
int x, int y, int width, int height,
gboolean update)
{
SpiceMainChannelPrivate *c;
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
g_return_if_fail(x >= 0);
g_return_if_fail(y >= 0);
g_return_if_fail(width >= 0);
g_return_if_fail(height >= 0);
c = SPICE_MAIN_CHANNEL(channel)->priv;
g_return_if_fail(id < SPICE_N_ELEMENTS(c->display));
c->display[id].x = x;
c->display[id].y = y;
c->display[id].width = width;
c->display[id].height = height;
if (update)
update_display_timer(channel, 1);
}
/**
* spice_main_set_display:
* @channel:
* @id: display ID
* @x: x position
* @y: y position
* @width: display width
* @height: display height
*
* Notify the guest of screen resolution change. The notification is
* sent 1 second later, if no further changes happen.
**/
void spice_main_set_display(SpiceMainChannel *channel, int id,
int x, int y, int width, int height)
{
spice_main_update_display(channel, id, x, y, width, height, TRUE);
}
/**
* spice_main_clipboard_grab:
* @channel:
* @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
* @ntypes: the number of @types
*
* Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
*
* Deprecated: 0.6: use spice_main_clipboard_selection_grab() instead.
**/
void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes)
{
spice_main_clipboard_selection_grab(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, types, ntypes);
}
/**
* spice_main_clipboard_selection_grab:
* @channel:
* @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
* @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
* @ntypes: the number of @types
*
* Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
*
* Since: 0.6
**/
void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
guint32 *types, int ntypes)
{
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
agent_clipboard_grab(channel, selection, types, ntypes);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
/**
* spice_main_clipboard_release:
* @channel:
*
* Release the clipboard (for example, when the client looses the
* clipboard grab): Inform the guest no clipboard data is available.
*
* Deprecated: 0.6: use spice_main_clipboard_selection_release() instead.
**/
void spice_main_clipboard_release(SpiceMainChannel *channel)
{
spice_main_clipboard_selection_release(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD);
}
/**
* spice_main_clipboard_selection_release:
* @channel:
* @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
*
* Release the clipboard (for example, when the client looses the
* clipboard grab): Inform the guest no clipboard data is available.
*
* Since: 0.6
**/
void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection)
{
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
SpiceMainChannelPrivate *c = channel->priv;
if (!c->agent_connected)
return;
agent_clipboard_release(channel, selection);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
/**
* spice_main_clipboard_notify:
* @channel:
* @type: a #VD_AGENT_CLIPBOARD type
* @data: clipboard data
* @size: data length in bytes
*
* Send the clipboard data to the guest.
*
* Deprecated: 0.6: use spice_main_clipboard_selection_notify() instead.
**/
void spice_main_clipboard_notify(SpiceMainChannel *channel,
guint32 type, const guchar *data, size_t size)
{
spice_main_clipboard_selection_notify(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
type, data, size);
}
/**
* spice_main_clipboard_selection_notify:
* @channel:
* @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
* @type: a #VD_AGENT_CLIPBOARD type
* @data: clipboard data
* @size: data length in bytes
*
* Send the clipboard data to the guest.
*
* Since: 0.6
**/
void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,
guint32 type, const guchar *data, size_t size)
{
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
agent_clipboard_notify(channel, selection, type, data, size);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
/**
* spice_main_clipboard_request:
* @channel:
* @type: a #VD_AGENT_CLIPBOARD type
*
* Request clipboard data of @type from the guest. The reply is sent
* through the #SpiceMainChannel::main-clipboard signal.
*
* Deprecated: 0.6: use spice_main_clipboard_selection_request() instead.
**/
void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type)
{
spice_main_clipboard_selection_request(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, type);
}
/**
* spice_main_clipboard_selection_request:
* @channel:
* @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
* @type: a #VD_AGENT_CLIPBOARD type
*
* Request clipboard data of @type from the guest. The reply is sent
* through the #SpiceMainChannel::main-clipboard signal.
*
* Since: 0.6
**/
void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type)
{
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
agent_clipboard_request(channel, selection, type);
spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
}
/**
* spice_main_set_display_enabled:
* @channel: a #SpiceMainChannel
* @id: display ID (if -1: set all displays)
* @enabled: wether display @id is enabled
*
* When sending monitor configuration to agent guest, don't set
* display @id, which the agent translates to disabling the display
* id. Note: this will take effect next time the monitor
* configuration is sent.
*
* Since: 0.6
**/
void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled)
{
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
g_return_if_fail(id >= -1);
SpiceMainChannelPrivate *c = channel->priv;
if (id == -1) {
gint i;
for (i = 0; i < G_N_ELEMENTS(c->display); i++)
c->display[i].enabled = enabled;
} else {
g_return_if_fail(id < G_N_ELEMENTS(c->display));
c->display[id].enabled = enabled;
}
update_display_timer(channel, 1);
}
static void file_xfer_completed(SpiceFileXferTask *task, GError *error)
{
/* In case of multiple errors we only report the first error */
if (task->error)
g_clear_error(&error);
if (error) {
SPICE_DEBUG("File %s xfer failed: %s",
g_file_get_path(task->file), error->message);
task->error = error;
}
if (task->pending)
return;
if (!task->file_stream) {
file_xfer_close_cb(NULL, NULL, task);
return;
}
g_input_stream_close_async(G_INPUT_STREAM(task->file_stream),
G_PRIORITY_DEFAULT,
task->cancellable,
file_xfer_close_cb,
task);
task->pending = TRUE;
}
static void file_xfer_info_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
{
GFileInfo *info;
GFile *file = G_FILE(obj);
GError *error = NULL;
GKeyFile *keyfile = NULL;
gchar *basename = NULL;
VDAgentFileXferStartMessage msg;
gsize /*msg_size*/ data_len;
gchar *string;
SpiceFileXferTask *task = (SpiceFileXferTask *)data;
task->pending = FALSE;
info = g_file_query_info_finish(file, res, &error);
if (error || task->error)
goto failed;
task->file_size =
g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
keyfile = g_key_file_new();
/* File name */
basename = g_file_get_basename(file);
g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
g_free(basename);
/* File size */
g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", task->file_size);
/* Save keyfile content to memory. TODO: more file attributions
need to be sent to guest */
string = g_key_file_to_data(keyfile, &data_len, &error);
g_key_file_free(keyfile);
if (error)
goto failed;
/* Create file-xfer start message */
msg.id = task->id;
agent_msg_queue_many(task->channel, VD_AGENT_FILE_XFER_START,
&msg, sizeof(msg),
string, data_len + 1, NULL);
g_free(string);
spice_channel_wakeup(SPICE_CHANNEL(task->channel), FALSE);
return;
failed:
file_xfer_completed(task, error);
}
static void file_xfer_read_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
{
GFile *file = G_FILE(obj);
SpiceFileXferTask *task = (SpiceFileXferTask *)data;
GError *error = NULL;
task->pending = FALSE;
task->file_stream = g_file_read_finish(file, res, &error);
if (error || task->error) {
file_xfer_completed(task, error);
return;
}
g_file_query_info_async(task->file,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
task->cancellable,
file_xfer_info_async_cb,
task);
task->pending = TRUE;
}
static void file_xfer_send_start_msg_async(SpiceMainChannel *channel,
GFile *file,
GFileCopyFlags flags,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data)
{
SpiceMainChannelPrivate *c = channel->priv;
SpiceFileXferTask *task;
static uint32_t xfer_id; /* Used to identify task id */
task = spice_malloc0(sizeof(SpiceFileXferTask));
task->id = ++xfer_id;
task->channel = g_object_ref(channel);
task->file = g_object_ref(file);
task->flags = flags;
task->cancellable = cancellable;
task->progress_callback = progress_callback;
task->progress_callback_data = progress_callback_data;
task->callback = callback;
task->user_data = user_data;
CHANNEL_DEBUG(task->channel, "Insert a xfer task:%d to task list", task->id);
g_hash_table_insert(c->file_xfer_tasks, GUINT_TO_POINTER(task->id), task);
g_file_read_async(file,
G_PRIORITY_DEFAULT,
cancellable,
file_xfer_read_async_cb,
task);
task->pending = TRUE;
}
/**
* spice_main_file_copy_async:
* @sources: #GFile to be transfer
* @flags: set of #GFileCopyFlags
* @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
* @progress_callback: (allow-none) (scope call): function to callback with
* progress information, or %NULL if progress information is not needed
* @progress_callback_data: (closure): user data to pass to @progress_callback
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: the data to pass to callback function
*
* Copies the file @sources to guest
*
* If @cancellable is not %NULL, then the operation can be cancelled by
* triggering the cancellable object from another thread. If the operation
* was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
*
* If @progress_callback is not %NULL, then the operation can be monitored by
* setting this to a #GFileProgressCallback function. @progress_callback_data
* will be passed to this function. It is guaranteed that this callback will
* be called after all data has been transferred with the total number of bytes
* copied during the operation.
*
* When the operation is finished, callback will be called. You can then call
* spice_main_file_copy_finish() to get the result of the operation.
*
**/
void spice_main_file_copy_async(SpiceMainChannel *channel,
GFile **sources,
GFileCopyFlags flags,
GCancellable *cancellable,
GFileProgressCallback progress_callback,
gpointer progress_callback_data,
GAsyncReadyCallback callback,
gpointer user_data)
{
SpiceMainChannelPrivate *c = channel->priv;
g_return_if_fail(channel != NULL);
g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
g_return_if_fail(sources != NULL && sources[0] != NULL);
/* At the moment, the copy() method is limited to a single file,
support for copying multi-files will be implemented later. */
g_return_if_fail(sources[1] == NULL);
if (!c->agent_connected) {
g_simple_async_report_error_in_idle(G_OBJECT(channel),
callback,
user_data,
SPICE_CLIENT_ERROR,
SPICE_CLIENT_ERROR_FAILED,
"The agent is not connected");
return;
}
file_xfer_send_start_msg_async(channel,
sources[0],
flags,
cancellable,
progress_callback,
progress_callback_data,
callback,
user_data);
}
/**
* spice_main_file_copy_finish:
* @result: a #GAsyncResult.
* @error: a #GError, or %NULL
*
* Finishes copying the file started with
* spice_main_file_copy_async().
*
* Returns: a %TRUE on success, %FALSE on error.
**/
gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
g_return_val_if_fail(g_simple_async_result_is_valid(result,
G_OBJECT(channel), spice_main_file_copy_async), FALSE);
simple = (GSimpleAsyncResult *)result;
if (g_simple_async_result_propagate_error(simple, error)) {
return FALSE;
}
return g_simple_async_result_get_op_res_gboolean(simple);
}