1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2010 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "config.h"
19
20 #include <math.h>
21 #include <spice/vd_agent.h>
22 #include <glib/gstdio.h>
23 #include <glib/gi18n-lib.h>
24
25 #include "spice-client.h"
26 #include "spice-common.h"
27 #include "spice-marshal.h"
28
29 #include "spice-util-priv.h"
30 #include "spice-channel-priv.h"
31 #include "spice-session-priv.h"
32 #include "spice-audio-priv.h"
33 #include "spice-file-transfer-task-priv.h"
34
35 /**
36 * SECTION:channel-main
37 * @short_description: the main Spice channel
38 * @title: Main Channel
39 * @section_id:
40 * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
41 * @stability: Stable
42 * @include: spice-client.h
43 *
44 * The main channel is the Spice session control channel. It handles
45 * communication initialization (channels list), migrations, mouse
46 * modes, multimedia time, and agent communication.
47 *
48 *
49 */
50
51 #define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */
52
53 typedef struct spice_migrate spice_migrate;
54
55 typedef enum {
56 DISPLAY_UNDEFINED,
57 DISPLAY_DISABLED,
58 DISPLAY_ENABLED,
59 } SpiceDisplayState;
60
61 typedef struct {
62 int x;
63 int y;
64 int width;
65 int height;
66 SpiceDisplayState display_state;
67 } SpiceDisplayConfig;
68
69 typedef struct {
70 GHashTable *xfer_task;
71 SpiceMainChannel *channel;
72 GFileProgressCallback progress_callback;
73 gpointer progress_callback_data;
74 GTask *task;
75 struct {
76 goffset total_sent;
77 goffset transfer_size;
78 guint num_files;
79 guint succeed;
80 guint cancelled;
81 guint failed;
82 } stats;
83 } FileTransferOperation;
84
85 struct _SpiceMainChannelPrivate {
86 enum SpiceMouseMode mouse_mode;
87 enum SpiceMouseMode requested_mouse_mode;
88 bool agent_connected;
89 bool agent_caps_received;
90
91 gboolean agent_display_config_sent;
92 gboolean display_disable_wallpaper:1;
93 gboolean display_disable_font_smooth:1;
94 gboolean display_disable_animation:1;
95 gboolean disable_display_position:1;
96 gboolean disable_display_align:1;
97
98 int agent_tokens;
99 VDAgentMessage agent_msg; /* partial msg reconstruction */
100 guint8 *agent_msg_data;
101 guint agent_msg_pos;
102 uint8_t agent_msg_size;
103 uint32_t agent_caps[VD_AGENT_CAPS_SIZE];
104 SpiceDisplayConfig display[MAX_DISPLAY];
105 gint timer_id;
106 GQueue *agent_msg_queue;
107 GHashTable *file_xfer_tasks;
108 GHashTable *flushing;
109
110 guint switch_host_delayed_id;
111 guint migrate_delayed_id;
112 spice_migrate *migrate_data;
113 int max_clipboard;
114
115 gboolean agent_volume_playback_sync;
116 gboolean agent_volume_record_sync;
117 GCancellable *cancellable_volume_info;
118 };
119
120 struct spice_migrate {
121 struct coroutine *from;
122 SpiceMigrationDstInfo *info;
123 SpiceSession *session;
124 guint nchannels;
125 SpiceChannel *src_channel;
126 SpiceChannel *dst_channel;
127 bool do_seamless; /* used as input and output for the seamless migration handshake.
128 input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
129 output: whether the dest approved seamless migration
130 (SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
131 */
132 uint32_t src_mig_version;
133 };
134
135 G_DEFINE_TYPE_WITH_PRIVATE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
136
137 /* Properties */
138 enum {
139 PROP_0,
140 PROP_MOUSE_MODE,
141 PROP_AGENT_CONNECTED,
142 PROP_AGENT_CAPS_0,
143 PROP_DISPLAY_DISABLE_WALLPAPER,
144 PROP_DISPLAY_DISABLE_FONT_SMOOTH,
145 PROP_DISPLAY_DISABLE_ANIMATION,
146 PROP_DISPLAY_COLOR_DEPTH,
147 PROP_DISABLE_DISPLAY_POSITION,
148 PROP_DISABLE_DISPLAY_ALIGN,
149 PROP_MAX_CLIPBOARD,
150 };
151
152 /* Signals */
153 enum {
154 SPICE_MAIN_MOUSE_UPDATE,
155 SPICE_MAIN_AGENT_UPDATE,
156 SPICE_MAIN_CLIPBOARD,
157 SPICE_MAIN_CLIPBOARD_GRAB,
158 SPICE_MAIN_CLIPBOARD_REQUEST,
159 SPICE_MAIN_CLIPBOARD_RELEASE,
160 SPICE_MAIN_CLIPBOARD_SELECTION,
161 SPICE_MAIN_CLIPBOARD_SELECTION_GRAB,
162 SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
163 SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
164 SPICE_MIGRATION_STARTED,
165 SPICE_MAIN_NEW_FILE_TRANSFER,
166 SPICE_MAIN_LAST_SIGNAL,
167 };
168
169 static guint signals[SPICE_MAIN_LAST_SIGNAL];
170
171 static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
172 static void channel_set_handlers(SpiceChannelClass *klass);
173 static void agent_send_msg_queue(SpiceMainChannel *channel);
174 static void agent_free_msg_queue(SpiceMainChannel *channel);
175 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
176 gpointer data);
177 static gboolean main_migrate_handshake_done(gpointer data);
178 static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
179 static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
180 static void file_xfer_read_async_cb(GObject *source_object,
181 GAsyncResult *res,
182 gpointer user_data);
183 static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max);
184 static void set_agent_connected(SpiceMainChannel *channel, gboolean connected);
185
186 static void file_transfer_operation_free(FileTransferOperation *xfer_op);
187 static void spice_main_channel_reset_all_xfer_operations(SpiceMainChannel *channel);
188 static SpiceFileTransferTask *spice_main_channel_find_xfer_task_by_task_id(SpiceMainChannel *channel,
189 guint32 task_id);
190 static void file_transfer_operation_task_finished(SpiceFileTransferTask *xfer_task,
191 GError *error,
192 gpointer userdata);
193 static void file_transfer_operation_send_progress(SpiceFileTransferTask *xfer_task);
194
195 /* ------------------------------------------------------------------ */
196
197 static const char *agent_msg_types[] = {
198 [ VD_AGENT_MOUSE_STATE ] = "mouse state",
199 [ VD_AGENT_MONITORS_CONFIG ] = "monitors config",
200 [ VD_AGENT_REPLY ] = "reply",
201 [ VD_AGENT_CLIPBOARD ] = "clipboard",
202 [ VD_AGENT_DISPLAY_CONFIG ] = "display config",
203 [ VD_AGENT_ANNOUNCE_CAPABILITIES ] = "announce caps",
204 [ VD_AGENT_CLIPBOARD_GRAB ] = "clipboard grab",
205 [ VD_AGENT_CLIPBOARD_REQUEST ] = "clipboard request",
206 [ VD_AGENT_CLIPBOARD_RELEASE ] = "clipboard release",
207 [ VD_AGENT_AUDIO_VOLUME_SYNC ] = "volume-sync",
208 };
209
210 static const char *agent_caps[] = {
211 [ VD_AGENT_CAP_MOUSE_STATE ] = "mouse state",
212 [ VD_AGENT_CAP_MONITORS_CONFIG ] = "monitors config",
213 [ VD_AGENT_CAP_REPLY ] = "reply",
214 [ VD_AGENT_CAP_CLIPBOARD ] = "clipboard (old)",
215 [ VD_AGENT_CAP_DISPLAY_CONFIG ] = "display config",
216 [ VD_AGENT_CAP_CLIPBOARD_BY_DEMAND ] = "clipboard",
217 [ VD_AGENT_CAP_CLIPBOARD_SELECTION ] = "clipboard selection",
218 [ VD_AGENT_CAP_SPARSE_MONITORS_CONFIG ] = "sparse monitors",
219 [ VD_AGENT_CAP_GUEST_LINEEND_LF ] = "line-end lf",
220 [ VD_AGENT_CAP_GUEST_LINEEND_CRLF ] = "line-end crlf",
221 [ VD_AGENT_CAP_MAX_CLIPBOARD ] = "max-clipboard",
222 [ VD_AGENT_CAP_AUDIO_VOLUME_SYNC ] = "volume-sync",
223 [ VD_AGENT_CAP_MONITORS_CONFIG_POSITION ] = "monitors config position",
224 [ VD_AGENT_CAP_FILE_XFER_DISABLED ] = "file transfer disabled",
225 };
226 #define NAME(_a, _i) ((_i) < SPICE_N_ELEMENTS(_a) ? (_a[(_i)] ?: "?") : "?")
227
228 /* ------------------------------------------------------------------ */
229
test_agent_cap(SpiceMainChannel * channel,guint32 cap)230 static gboolean test_agent_cap(SpiceMainChannel *channel, guint32 cap)
231 {
232 SpiceMainChannelPrivate *c = channel->priv;
233
234 if (!c->agent_caps_received)
235 return FALSE;
236
237 return VD_AGENT_HAS_CAPABILITY(c->agent_caps, G_N_ELEMENTS(c->agent_caps), cap);
238 }
239
spice_main_channel_set_capabilties(SpiceChannel * channel)240 static void spice_main_channel_set_capabilties(SpiceChannel *channel)
241 {
242 spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
243 spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
244 spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
245 spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
246 }
247
spice_main_channel_init(SpiceMainChannel * channel)248 static void spice_main_channel_init(SpiceMainChannel *channel)
249 {
250 SpiceMainChannelPrivate *c;
251
252 c = channel->priv = spice_main_channel_get_instance_private(channel);
253 c->agent_msg_queue = g_queue_new();
254 c->file_xfer_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
255 c->flushing = g_hash_table_new(g_direct_hash, g_direct_equal);
256 c->cancellable_volume_info = g_cancellable_new();
257
258 spice_main_channel_set_capabilties(SPICE_CHANNEL(channel));
259 c->requested_mouse_mode = SPICE_MOUSE_MODE_CLIENT;
260 }
261
spice_main_get_max_clipboard(SpiceMainChannel * self)262 static gint spice_main_get_max_clipboard(SpiceMainChannel *self)
263 {
264 g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(self), 0);
265
266 if (g_getenv("SPICE_MAX_CLIPBOARD"))
267 return atoi(g_getenv("SPICE_MAX_CLIPBOARD"));
268
269 return self->priv->max_clipboard;
270 }
271
spice_main_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)272 static void spice_main_get_property(GObject *object,
273 guint prop_id,
274 GValue *value,
275 GParamSpec *pspec)
276 {
277 SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
278 SpiceMainChannelPrivate *c = self->priv;
279
280 switch (prop_id) {
281 case PROP_MOUSE_MODE:
282 g_value_set_int(value, c->mouse_mode);
283 break;
284 case PROP_AGENT_CONNECTED:
285 g_value_set_boolean(value, c->agent_connected);
286 break;
287 case PROP_AGENT_CAPS_0:
288 g_value_set_int(value, c->agent_caps[0]);
289 break;
290 case PROP_DISPLAY_DISABLE_WALLPAPER:
291 g_value_set_boolean(value, c->display_disable_wallpaper);
292 break;
293 case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
294 g_value_set_boolean(value, c->display_disable_font_smooth);
295 break;
296 case PROP_DISPLAY_DISABLE_ANIMATION:
297 g_value_set_boolean(value, c->display_disable_animation);
298 break;
299 case PROP_DISPLAY_COLOR_DEPTH: /* FIXME: deprecated */
300 g_value_set_uint(value, 32);
301 break;
302 case PROP_DISABLE_DISPLAY_POSITION:
303 g_value_set_boolean(value, c->disable_display_position);
304 break;
305 case PROP_DISABLE_DISPLAY_ALIGN:
306 g_value_set_boolean(value, c->disable_display_align);
307 break;
308 case PROP_MAX_CLIPBOARD:
309 g_value_set_int(value, spice_main_get_max_clipboard(self));
310 break;
311 default:
312 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
313 break;
314 }
315 }
316
spice_main_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)317 static void spice_main_set_property(GObject *gobject, guint prop_id,
318 const GValue *value, GParamSpec *pspec)
319 {
320 SpiceMainChannel *self = SPICE_MAIN_CHANNEL(gobject);
321 SpiceMainChannelPrivate *c = self->priv;
322
323 switch (prop_id) {
324 case PROP_DISPLAY_DISABLE_WALLPAPER:
325 c->display_disable_wallpaper = g_value_get_boolean(value);
326 break;
327 case PROP_DISPLAY_DISABLE_FONT_SMOOTH:
328 c->display_disable_font_smooth = g_value_get_boolean(value);
329 break;
330 case PROP_DISPLAY_DISABLE_ANIMATION:
331 c->display_disable_animation = g_value_get_boolean(value);
332 break;
333 case PROP_DISPLAY_COLOR_DEPTH:
334 spice_info("SpiceMainChannel::color-depth has been deprecated. Property is ignored");
335 break;
336 case PROP_DISABLE_DISPLAY_POSITION:
337 c->disable_display_position = g_value_get_boolean(value);
338 break;
339 case PROP_DISABLE_DISPLAY_ALIGN:
340 c->disable_display_align = g_value_get_boolean(value);
341 break;
342 case PROP_MAX_CLIPBOARD:
343 spice_main_set_max_clipboard(self, g_value_get_int(value));
344 break;
345 default:
346 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
347 break;
348 }
349 }
350
spice_main_channel_dispose(GObject * obj)351 static void spice_main_channel_dispose(GObject *obj)
352 {
353 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
354
355 if (c->timer_id) {
356 g_source_remove(c->timer_id);
357 c->timer_id = 0;
358 }
359
360 if (c->switch_host_delayed_id) {
361 g_source_remove(c->switch_host_delayed_id);
362 c->switch_host_delayed_id = 0;
363 }
364
365 if (c->migrate_delayed_id) {
366 g_source_remove(c->migrate_delayed_id);
367 c->migrate_delayed_id = 0;
368 }
369
370 g_clear_pointer(&c->file_xfer_tasks, g_hash_table_unref);
371 g_clear_pointer (&c->flushing, g_hash_table_unref);
372
373 g_cancellable_cancel(c->cancellable_volume_info);
374 g_clear_object(&c->cancellable_volume_info);
375
376 if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
377 G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
378 }
379
spice_main_channel_finalize(GObject * obj)380 static void spice_main_channel_finalize(GObject *obj)
381 {
382 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
383
384 g_free(c->agent_msg_data);
385 agent_free_msg_queue(SPICE_MAIN_CHANNEL(obj));
386
387 if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
388 G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
389 }
390
391 /* coroutine context */
spice_channel_iterate_write(SpiceChannel * channel)392 static void spice_channel_iterate_write(SpiceChannel *channel)
393 {
394 agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
395
396 if (SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write)
397 SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write(channel);
398 }
399
400 /* main or coroutine context */
spice_main_channel_reset_agent(SpiceMainChannel * channel)401 static void spice_main_channel_reset_agent(SpiceMainChannel *channel)
402 {
403 SpiceMainChannelPrivate *c = channel->priv;
404
405 c->agent_connected = FALSE;
406 c->agent_caps_received = FALSE;
407 c->agent_display_config_sent = FALSE;
408 c->agent_msg_pos = 0;
409 g_clear_pointer(&c->agent_msg_data, g_free);
410 c->agent_msg_size = 0;
411
412 spice_main_channel_reset_all_xfer_operations(channel);
413 file_xfer_flushed(channel, FALSE);
414 }
415
416 /* main or coroutine context */
spice_main_channel_reset(SpiceChannel * channel,gboolean migrating)417 static void spice_main_channel_reset(SpiceChannel *channel, gboolean migrating)
418 {
419 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
420
421 /* This is not part of reset_agent, since the spice-server expects any
422 pending multi-chunk messages to be completed by the client, even after
423 it has send an agent-disconnected msg as that is what the original
424 spicec did. Also see the TODO in server/reds.c reds_reset_vdp() */
425 c->agent_tokens = 0;
426 agent_free_msg_queue(SPICE_MAIN_CHANNEL(channel));
427 c->agent_msg_queue = g_queue_new();
428
429 c->agent_volume_playback_sync = FALSE;
430 c->agent_volume_record_sync = FALSE;
431
432 set_agent_connected(SPICE_MAIN_CHANNEL(channel), FALSE);
433
434 SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->channel_reset(channel, migrating);
435 }
436
spice_main_constructed(GObject * object)437 static void spice_main_constructed(GObject *object)
438 {
439 SpiceMainChannel *self = SPICE_MAIN_CHANNEL(object);
440 SpiceMainChannelPrivate *c = self->priv;
441
442 /* update default value */
443 c->max_clipboard = spice_main_get_max_clipboard(self);
444
445 if (G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed)
446 G_OBJECT_CLASS(spice_main_channel_parent_class)->constructed(object);
447 }
448
spice_main_channel_class_init(SpiceMainChannelClass * klass)449 static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
450 {
451 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
452 SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
453
454 gobject_class->dispose = spice_main_channel_dispose;
455 gobject_class->finalize = spice_main_channel_finalize;
456 gobject_class->get_property = spice_main_get_property;
457 gobject_class->set_property = spice_main_set_property;
458 gobject_class->constructed = spice_main_constructed;
459
460 channel_class->handle_msg = spice_main_handle_msg;
461 channel_class->iterate_write = spice_channel_iterate_write;
462 channel_class->channel_reset = spice_main_channel_reset;
463 channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
464
465 /**
466 * SpiceMainChannel:mouse-mode:
467 *
468 * Spice protocol specifies two mouse modes, client mode and
469 * server mode. In client mode (%SPICE_MOUSE_MODE_CLIENT), the
470 * affective mouse is the client side mouse: the client sends
471 * mouse position within the display and the server sends mouse
472 * shape messages. In server mode (%SPICE_MOUSE_MODE_SERVER), the
473 * client sends relative mouse movements and the server sends
474 * position and shape commands.
475 **/
476 g_object_class_install_property
477 (gobject_class, PROP_MOUSE_MODE,
478 g_param_spec_int("mouse-mode",
479 "Mouse mode",
480 "Mouse mode",
481 0, INT_MAX, 0,
482 G_PARAM_READABLE |
483 G_PARAM_STATIC_NAME |
484 G_PARAM_STATIC_NICK |
485 G_PARAM_STATIC_BLURB));
486
487 g_object_class_install_property
488 (gobject_class, PROP_AGENT_CONNECTED,
489 g_param_spec_boolean("agent-connected",
490 "Agent connected",
491 "Whether the agent is connected",
492 FALSE,
493 G_PARAM_READABLE |
494 G_PARAM_STATIC_NAME |
495 G_PARAM_STATIC_NICK |
496 G_PARAM_STATIC_BLURB));
497
498 g_object_class_install_property
499 (gobject_class, PROP_AGENT_CAPS_0,
500 g_param_spec_int("agent-caps-0",
501 "Agent caps 0",
502 "Agent capability bits 0 -> 31",
503 0, INT_MAX, 0,
504 G_PARAM_READABLE |
505 G_PARAM_STATIC_NAME |
506 G_PARAM_STATIC_NICK |
507 G_PARAM_STATIC_BLURB));
508
509 g_object_class_install_property
510 (gobject_class, PROP_DISPLAY_DISABLE_WALLPAPER,
511 g_param_spec_boolean("disable-wallpaper",
512 "Disable guest wallpaper",
513 "Disable guest wallpaper",
514 FALSE,
515 G_PARAM_READWRITE |
516 G_PARAM_CONSTRUCT |
517 G_PARAM_STATIC_STRINGS));
518
519 g_object_class_install_property
520 (gobject_class, PROP_DISPLAY_DISABLE_FONT_SMOOTH,
521 g_param_spec_boolean("disable-font-smooth",
522 "Disable guest font smooth",
523 "Disable guest font smoothing",
524 FALSE,
525 G_PARAM_READWRITE |
526 G_PARAM_CONSTRUCT |
527 G_PARAM_STATIC_STRINGS));
528
529 g_object_class_install_property
530 (gobject_class, PROP_DISPLAY_DISABLE_ANIMATION,
531 g_param_spec_boolean("disable-animation",
532 "Disable guest animations",
533 "Disable guest animations",
534 FALSE,
535 G_PARAM_READWRITE |
536 G_PARAM_CONSTRUCT |
537 G_PARAM_STATIC_STRINGS));
538
539 g_object_class_install_property
540 (gobject_class, PROP_DISABLE_DISPLAY_POSITION,
541 g_param_spec_boolean("disable-display-position",
542 "Disable display position",
543 "Disable using display position when setting monitor config",
544 TRUE,
545 G_PARAM_READWRITE |
546 G_PARAM_CONSTRUCT |
547 G_PARAM_STATIC_STRINGS));
548
549 /**
550 * SpiceMainChannel:color-depth:
551 *
552 * Deprecated: 0.37: Deprecated due lack of support in drivers, only Windows 7 and older.
553 * This option is currently ignored.
554 *
555 **/
556 g_object_class_install_property
557 (gobject_class, PROP_DISPLAY_COLOR_DEPTH,
558 g_param_spec_uint("color-depth",
559 "Color depth",
560 "Color depth", 0, 32, 0,
561 G_PARAM_DEPRECATED |
562 G_PARAM_READWRITE |
563 G_PARAM_CONSTRUCT |
564 G_PARAM_STATIC_STRINGS));
565
566 /**
567 * SpiceMainChannel:disable-display-align:
568 *
569 * Disable automatic horizontal display position alignment.
570 *
571 * Since: 0.13
572 */
573 g_object_class_install_property
574 (gobject_class, PROP_DISABLE_DISPLAY_ALIGN,
575 g_param_spec_boolean("disable-display-align",
576 "Disable display align",
577 "Disable display position alignment",
578 FALSE,
579 G_PARAM_READWRITE |
580 G_PARAM_CONSTRUCT |
581 G_PARAM_STATIC_STRINGS));
582
583 /**
584 * SpiceMainChannel:max-clipboard:
585 *
586 * Maximum size of clipboard operations in bytes (default 100MB,
587 * -1 for unlimited size);
588 *
589 * Since: 0.22
590 **/
591 g_object_class_install_property
592 (gobject_class, PROP_MAX_CLIPBOARD,
593 g_param_spec_int("max-clipboard",
594 "max clipboard",
595 "Maximum clipboard data size",
596 -1, G_MAXINT, 100 * 1024 * 1024,
597 G_PARAM_READWRITE |
598 G_PARAM_CONSTRUCT |
599 G_PARAM_STATIC_STRINGS));
600
601 /* TODO use notify instead */
602 /**
603 * SpiceMainChannel::main-mouse-update:
604 * @main: the #SpiceMainChannel that emitted the signal
605 *
606 * Notify when the mouse mode has changed.
607 **/
608 signals[SPICE_MAIN_MOUSE_UPDATE] =
609 g_signal_new("main-mouse-update",
610 G_OBJECT_CLASS_TYPE(gobject_class),
611 G_SIGNAL_RUN_FIRST,
612 G_STRUCT_OFFSET(SpiceMainChannelClass, mouse_update),
613 NULL, NULL,
614 g_cclosure_marshal_VOID__VOID,
615 G_TYPE_NONE,
616 0);
617
618 /* TODO use notify instead */
619 /**
620 * SpiceMainChannel::main-agent-update:
621 * @main: the #SpiceMainChannel that emitted the signal
622 *
623 * Notify when the %SpiceMainChannel:agent-connected or
624 * %SpiceMainChannel:agent-caps-0 property change.
625 **/
626 signals[SPICE_MAIN_AGENT_UPDATE] =
627 g_signal_new("main-agent-update",
628 G_OBJECT_CLASS_TYPE(gobject_class),
629 G_SIGNAL_RUN_FIRST,
630 G_STRUCT_OFFSET(SpiceMainChannelClass, agent_update),
631 NULL, NULL,
632 g_cclosure_marshal_VOID__VOID,
633 G_TYPE_NONE,
634 0);
635 /**
636 * SpiceMainChannel::main-clipboard:
637 * @main: the #SpiceMainChannel that emitted the signal
638 * @type: the VD_AGENT_CLIPBOARD data type
639 * @data: clipboard data
640 * @size: size of @data in bytes
641 *
642 * Provides guest clipboard data requested by spice_main_clipboard_request().
643 *
644 * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection instead.
645 **/
646 signals[SPICE_MAIN_CLIPBOARD] =
647 g_signal_new("main-clipboard",
648 G_OBJECT_CLASS_TYPE(gobject_class),
649 G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
650 0,
651 NULL, NULL,
652 g_cclosure_user_marshal_VOID__UINT_POINTER_UINT,
653 G_TYPE_NONE,
654 3,
655 G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
656
657 /**
658 * SpiceMainChannel::main-clipboard-selection:
659 * @main: the #SpiceMainChannel that emitted the signal
660 * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
661 * @type: the VD_AGENT_CLIPBOARD data type
662 * @data: clipboard data
663 * @size: size of @data in bytes
664 *
665 * Informs that clipboard selection data are available.
666 *
667 * Since: 0.6
668 **/
669 signals[SPICE_MAIN_CLIPBOARD_SELECTION] =
670 g_signal_new("main-clipboard-selection",
671 G_OBJECT_CLASS_TYPE(gobject_class),
672 G_SIGNAL_RUN_LAST,
673 0,
674 NULL, NULL,
675 g_cclosure_user_marshal_VOID__UINT_UINT_POINTER_UINT,
676 G_TYPE_NONE,
677 4,
678 G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
679
680 /**
681 * SpiceMainChannel::main-clipboard-grab:
682 * @main: the #SpiceMainChannel that emitted the signal
683 * @types: the VD_AGENT_CLIPBOARD data types
684 * @ntypes: the number of @types
685 *
686 * Inform when clipboard data is available from the guest, and for
687 * which @types.
688 *
689 * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-grab instead.
690 **/
691 signals[SPICE_MAIN_CLIPBOARD_GRAB] =
692 g_signal_new("main-clipboard-grab",
693 G_OBJECT_CLASS_TYPE(gobject_class),
694 G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
695 0,
696 NULL, NULL,
697 g_cclosure_user_marshal_BOOLEAN__POINTER_UINT,
698 G_TYPE_BOOLEAN,
699 2,
700 G_TYPE_POINTER, G_TYPE_UINT);
701
702 /**
703 * SpiceMainChannel::main-clipboard-selection-grab:
704 * @main: the #SpiceMainChannel that emitted the signal
705 * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
706 * @types: the VD_AGENT_CLIPBOARD data types
707 * @ntypes: the number of @types
708 *
709 * Inform when clipboard data is available from the guest, and for
710 * which @types.
711 *
712 * Since: 0.6
713 **/
714 signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB] =
715 g_signal_new("main-clipboard-selection-grab",
716 G_OBJECT_CLASS_TYPE(gobject_class),
717 G_SIGNAL_RUN_LAST,
718 0,
719 NULL, NULL,
720 g_cclosure_user_marshal_BOOLEAN__UINT_POINTER_UINT,
721 G_TYPE_BOOLEAN,
722 3,
723 G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
724
725 /**
726 * SpiceMainChannel::main-clipboard-request:
727 * @main: the #SpiceMainChannel that emitted the signal
728 * @types: the VD_AGENT_CLIPBOARD request type
729 *
730 * Request clipboard data from the client.
731 *
732 * Return value: %TRUE if the request is successful
733 *
734 * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-request instead.
735 **/
736 signals[SPICE_MAIN_CLIPBOARD_REQUEST] =
737 g_signal_new("main-clipboard-request",
738 G_OBJECT_CLASS_TYPE(gobject_class),
739 G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
740 0,
741 NULL, NULL,
742 g_cclosure_user_marshal_BOOLEAN__UINT,
743 G_TYPE_BOOLEAN,
744 1,
745 G_TYPE_UINT);
746
747 /**
748 * SpiceMainChannel::main-clipboard-selection-request:
749 * @main: the #SpiceMainChannel that emitted the signal
750 * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
751 * @types: the VD_AGENT_CLIPBOARD request type
752 *
753 * Request clipboard data from the client.
754 *
755 * Return value: %TRUE if the request is successful
756 *
757 * Since: 0.6
758 **/
759 signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST] =
760 g_signal_new("main-clipboard-selection-request",
761 G_OBJECT_CLASS_TYPE(gobject_class),
762 G_SIGNAL_RUN_LAST,
763 0,
764 NULL, NULL,
765 g_cclosure_user_marshal_BOOLEAN__UINT_UINT,
766 G_TYPE_BOOLEAN,
767 2,
768 G_TYPE_UINT, G_TYPE_UINT);
769
770 /**
771 * SpiceMainChannel::main-clipboard-release:
772 * @main: the #SpiceMainChannel that emitted the signal
773 *
774 * Inform when the clipboard is released from the guest, when no
775 * clipboard data is available from the guest.
776 *
777 * Deprecated: 0.6: use SpiceMainChannel::main-clipboard-selection-release instead.
778 **/
779 signals[SPICE_MAIN_CLIPBOARD_RELEASE] =
780 g_signal_new("main-clipboard-release",
781 G_OBJECT_CLASS_TYPE(gobject_class),
782 G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED,
783 0,
784 NULL, NULL,
785 g_cclosure_marshal_VOID__VOID,
786 G_TYPE_NONE,
787 0);
788
789 /**
790 * SpiceMainChannel::main-clipboard-selection-release:
791 * @main: the #SpiceMainChannel that emitted the signal
792 * @selection: a VD_AGENT_CLIPBOARD_SELECTION clipboard
793 *
794 * Inform when the clipboard is released from the guest, when no
795 * clipboard data is available from the guest.
796 *
797 * Since: 0.6
798 **/
799 signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE] =
800 g_signal_new("main-clipboard-selection-release",
801 G_OBJECT_CLASS_TYPE(gobject_class),
802 G_SIGNAL_RUN_LAST,
803 0,
804 NULL, NULL,
805 g_cclosure_marshal_VOID__UINT,
806 G_TYPE_NONE,
807 1,
808 G_TYPE_UINT);
809
810 /**
811 * SpiceMainChannel::migration-started:
812 * @main: the #SpiceMainChannel that emitted the signal
813 * @session: a migration #SpiceSession
814 *
815 * Inform when migration is starting. Application wishing to make
816 * connections themself can set the #SpiceSession:client-sockets
817 * to @TRUE, then follow #SpiceSession::channel-new creation, and
818 * use spice_channel_open_fd() once the socket is created.
819 *
820 **/
821 signals[SPICE_MIGRATION_STARTED] =
822 g_signal_new("migration-started",
823 G_OBJECT_CLASS_TYPE(gobject_class),
824 G_SIGNAL_RUN_LAST,
825 0,
826 NULL, NULL,
827 g_cclosure_marshal_VOID__OBJECT,
828 G_TYPE_NONE,
829 1,
830 G_TYPE_OBJECT);
831
832 /**
833 * SpiceMainChannel::new-file-transfer:
834 * @main: the #SpiceMainChannel that emitted the signal
835 * @task: a #SpiceFileTransferTask
836 *
837 * This signal is emitted when a new file transfer task has been initiated
838 * on this channel. Client applications may take a reference on the @task
839 * object and use it to monitor the status of the file transfer task.
840 *
841 * Since: 0.31
842 **/
843 signals[SPICE_MAIN_NEW_FILE_TRANSFER] =
844 g_signal_new("new-file-transfer",
845 G_OBJECT_CLASS_TYPE(gobject_class),
846 G_SIGNAL_RUN_LAST,
847 0,
848 NULL, NULL,
849 g_cclosure_marshal_VOID__OBJECT,
850 G_TYPE_NONE,
851 1,
852 G_TYPE_OBJECT);
853
854 channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
855 }
856
857 /* ------------------------------------------------------------------ */
858
859
agent_free_msg_queue(SpiceMainChannel * channel)860 static void agent_free_msg_queue(SpiceMainChannel *channel)
861 {
862 SpiceMainChannelPrivate *c = channel->priv;
863 SpiceMsgOut *out;
864
865 if (!c->agent_msg_queue)
866 return;
867
868 while (!g_queue_is_empty(c->agent_msg_queue)) {
869 out = g_queue_pop_head(c->agent_msg_queue);
870 spice_msg_out_unref(out);
871 }
872
873 g_clear_pointer(&c->agent_msg_queue, g_queue_free);
874 }
875
flush_foreach_remove(gpointer key G_GNUC_UNUSED,gpointer value,gpointer user_data)876 static gboolean flush_foreach_remove(gpointer key G_GNUC_UNUSED,
877 gpointer value, gpointer user_data)
878 {
879 gboolean success = GPOINTER_TO_UINT(user_data);
880 GTask *result = value;
881 g_task_return_boolean(result, success);
882
883 return TRUE;
884 }
885
file_xfer_flushed(SpiceMainChannel * channel,gboolean success)886 static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success)
887 {
888 SpiceMainChannelPrivate *c = channel->priv;
889 g_hash_table_foreach_remove(c->flushing, flush_foreach_remove,
890 GUINT_TO_POINTER(success));
891 }
892
file_xfer_flush_async(SpiceFileTransferTask * xfer_task,GAsyncReadyCallback callback,gpointer user_data)893 static void file_xfer_flush_async(SpiceFileTransferTask *xfer_task,
894 GAsyncReadyCallback callback,
895 gpointer user_data)
896 {
897 GTask *task;
898 SpiceMainChannel *channel;
899 SpiceMainChannelPrivate *c;
900 gboolean was_empty;
901
902 channel = spice_file_transfer_task_get_channel(xfer_task);
903 task = g_task_new(xfer_task,
904 spice_file_transfer_task_get_cancellable(xfer_task),
905 callback,
906 user_data);
907
908 c = channel->priv;
909 was_empty = g_queue_is_empty(c->agent_msg_queue);
910 if (was_empty) {
911 g_task_return_boolean(task, TRUE);
912 g_object_unref(task);
913 return;
914 }
915
916 /* wait until the last message currently in the queue has been sent */
917 g_hash_table_insert(c->flushing, g_queue_peek_tail(c->agent_msg_queue), task);
918 }
919
file_xfer_flush_finish(SpiceFileTransferTask * xfer_task,GAsyncResult * result,GError ** error)920 static gboolean file_xfer_flush_finish(SpiceFileTransferTask *xfer_task,
921 GAsyncResult *result,
922 GError **error)
923 {
924 GTask *task = G_TASK(result);
925
926 g_return_val_if_fail(g_task_is_valid(result, xfer_task), FALSE);
927
928 return g_task_propagate_boolean(task, error);
929 }
930
931 /* coroutine context */
agent_send_msg_queue(SpiceMainChannel * channel)932 static void agent_send_msg_queue(SpiceMainChannel *channel)
933 {
934 SpiceMainChannelPrivate *c = channel->priv;
935 SpiceMsgOut *out;
936
937 while (c->agent_tokens > 0 &&
938 !g_queue_is_empty(c->agent_msg_queue)) {
939 GTask *task;
940 c->agent_tokens--;
941 out = g_queue_pop_head(c->agent_msg_queue);
942 spice_msg_out_send_internal(out);
943
944 task = g_hash_table_lookup(c->flushing, out);
945 if (task) {
946 /* if there's a flush task waiting for this message, finish it */
947 g_task_return_boolean(task, TRUE);
948 g_object_unref(task);
949 g_hash_table_remove(c->flushing, out);
950 }
951 }
952 if (g_queue_is_empty(c->agent_msg_queue) &&
953 g_hash_table_size(c->flushing) != 0) {
954 g_warning("unexpected flush task in list, clearing");
955 file_xfer_flushed(channel, TRUE);
956 }
957 }
958
959 /* any context: the message is not flushed immediately,
960 you can wakeup() the channel coroutine or send_msg_queue()
961
962 expected arguments, pair of data/data_size to send terminated with NULL:
963 agent_msg_queue_many(main, VD_AGENT_...,
964 &foo, sizeof(Foo),
965 data, data_size, NULL);
966 */
967 G_GNUC_NULL_TERMINATED
agent_msg_queue_many(SpiceMainChannel * channel,int type,const void * data,...)968 static void agent_msg_queue_many(SpiceMainChannel *channel, int type, const void *data, ...)
969 {
970 va_list args;
971 SpiceMainChannelPrivate *c = channel->priv;
972 SpiceMsgOut *out;
973 VDAgentMessage msg;
974 guint8 *payload;
975 gsize paysize, s, mins, size = 0;
976 const guint8 *d;
977
978 G_STATIC_ASSERT(VD_AGENT_MAX_DATA_SIZE > sizeof(VDAgentMessage));
979
980 va_start(args, data);
981 for (d = data; d != NULL; d = va_arg(args, void*)) {
982 size += va_arg(args, gsize);
983 }
984 va_end(args);
985
986 msg.protocol = VD_AGENT_PROTOCOL;
987 msg.type = type;
988 msg.opaque = 0;
989 msg.size = size;
990
991 paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size + sizeof(VDAgentMessage));
992 out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
993 payload = spice_marshaller_reserve_space(out->marshaller, paysize);
994 memcpy(payload, &msg, sizeof(VDAgentMessage));
995 payload += sizeof(VDAgentMessage);
996 paysize -= sizeof(VDAgentMessage);
997 if (paysize == 0) {
998 g_queue_push_tail(c->agent_msg_queue, out);
999 out = NULL;
1000 }
1001
1002 va_start(args, data);
1003 for (d = data; size > 0; d = va_arg(args, void*)) {
1004 s = va_arg(args, gsize);
1005 while (s > 0) {
1006 if (out == NULL) {
1007 paysize = MIN(VD_AGENT_MAX_DATA_SIZE, size);
1008 out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_DATA);
1009 payload = spice_marshaller_reserve_space(out->marshaller, paysize);
1010 }
1011 mins = MIN(paysize, s);
1012 memcpy(payload, d, mins);
1013 d += mins;
1014 payload += mins;
1015 s -= mins;
1016 size -= mins;
1017 paysize -= mins;
1018 if (paysize == 0) {
1019 g_queue_push_tail(c->agent_msg_queue, out);
1020 out = NULL;
1021 }
1022 }
1023 }
1024 va_end(args);
1025 g_warn_if_fail(out == NULL);
1026 }
1027
monitors_cmp(const void * p1,const void * p2,gpointer user_data)1028 static int monitors_cmp(const void *p1, const void *p2, gpointer user_data)
1029 {
1030 const VDAgentMonConfig *m1 = p1;
1031 const VDAgentMonConfig *m2 = p2;
1032 double d1 = sqrt(m1->x * m1->x + m1->y * m1->y);
1033 double d2 = sqrt(m2->x * m2->x + m2->y * m2->y);
1034 int diff = d1 - d2;
1035
1036 return diff == 0 ? (char*)p1 - (char*)p2 : diff;
1037 }
1038
monitors_align(VDAgentMonConfig * monitors,int nmonitors)1039 static void monitors_align(VDAgentMonConfig *monitors, int nmonitors)
1040 {
1041 gint i, j, x = 0;
1042 guint32 used = 0;
1043 VDAgentMonConfig *sorted_monitors;
1044
1045 if (nmonitors == 0)
1046 return;
1047
1048 /* sort by distance from origin */
1049 sorted_monitors = g_memdup(monitors, nmonitors * sizeof(VDAgentMonConfig));
1050 g_qsort_with_data(sorted_monitors, nmonitors, sizeof(VDAgentMonConfig), monitors_cmp, NULL);
1051
1052 /* super-KISS ltr alignment, feel free to improve */
1053 for (i = 0; i < nmonitors; i++) {
1054 /* Find where this monitor is in the sorted order */
1055 for (j = 0; j < nmonitors; j++) {
1056 /* Avoid using the same entry twice, this happens with older
1057 virt-viewer versions which always set x and y to 0 */
1058 if (used & (1 << j))
1059 continue;
1060 if (memcmp(&monitors[j], &sorted_monitors[i],
1061 sizeof(VDAgentMonConfig)) == 0)
1062 break;
1063 }
1064 used |= 1 << j;
1065 monitors[j].x = x;
1066 monitors[j].y = 0;
1067 x += monitors[j].width;
1068 if (monitors[j].width || monitors[j].height)
1069 SPICE_DEBUG("#%d +%d+%d-%ux%u", j, monitors[j].x, monitors[j].y,
1070 monitors[j].width, monitors[j].height);
1071 }
1072 g_free(sorted_monitors);
1073 }
1074
1075
1076 #define agent_msg_queue(Channel, Type, Size, Data) \
1077 agent_msg_queue_many((Channel), (Type), (Data), (Size), NULL)
1078
1079 /**
1080 * spice_main_send_monitor_config:
1081 * @channel: a #SpiceMainChannel
1082 *
1083 * Send monitors configuration previously set with
1084 * spice_main_set_display() and spice_main_set_display_enabled()
1085 *
1086 * Returns: %TRUE on success.
1087 *
1088 * Deprecated: 0.35: use spice_main_channel_send_monitor_config() instead.
1089 **/
spice_main_send_monitor_config(SpiceMainChannel * channel)1090 gboolean spice_main_send_monitor_config(SpiceMainChannel *channel)
1091 {
1092 return spice_main_channel_send_monitor_config(channel);
1093 }
1094
1095 /**
1096 * spice_main_channel_send_monitor_config:
1097 * @channel: a #SpiceMainChannel
1098 *
1099 * Send monitors configuration previously set with
1100 * spice_main_set_display() and spice_main_set_display_enabled()
1101 *
1102 * Returns: %TRUE on success.
1103 *
1104 * Since: 0.35
1105 **/
spice_main_channel_send_monitor_config(SpiceMainChannel * channel)1106 gboolean spice_main_channel_send_monitor_config(SpiceMainChannel *channel)
1107 {
1108 SpiceMainChannelPrivate *c;
1109 VDAgentMonitorsConfig *mon;
1110 int i, j, monitors;
1111 size_t size;
1112
1113 g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
1114 c = channel->priv;
1115 g_return_val_if_fail(c->agent_connected, FALSE);
1116
1117 if (spice_main_channel_agent_test_capability(channel, VD_AGENT_CAP_SPARSE_MONITORS_CONFIG)) {
1118 monitors = SPICE_N_ELEMENTS(c->display);
1119 } else {
1120 monitors = 0;
1121 for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
1122 if (c->display[i].display_state == DISPLAY_ENABLED)
1123 monitors += 1;
1124 }
1125 }
1126
1127 size = sizeof(VDAgentMonitorsConfig) + sizeof(VDAgentMonConfig) * monitors;
1128 mon = g_malloc0(size);
1129
1130 mon->num_of_monitors = monitors;
1131 if (c->disable_display_position == FALSE ||
1132 c->disable_display_align == FALSE)
1133 mon->flags |= VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS;
1134
1135 CHANNEL_DEBUG(channel, "sending new monitors config to guest");
1136 j = 0;
1137 for (i = 0; i < SPICE_N_ELEMENTS(c->display); i++) {
1138 if (c->display[i].display_state != DISPLAY_ENABLED) {
1139 if (spice_main_channel_agent_test_capability(channel,
1140 VD_AGENT_CAP_SPARSE_MONITORS_CONFIG))
1141 j++;
1142 continue;
1143 }
1144 mon->monitors[j].depth = 32;
1145 mon->monitors[j].width = c->display[i].width;
1146 mon->monitors[j].height = c->display[i].height;
1147 mon->monitors[j].x = c->display[i].x;
1148 mon->monitors[j].y = c->display[i].y;
1149 CHANNEL_DEBUG(channel, "monitor #%d: %ux%u+%d+%d @ %u bpp", j,
1150 mon->monitors[j].width, mon->monitors[j].height,
1151 mon->monitors[j].x, mon->monitors[j].y,
1152 mon->monitors[j].depth);
1153 j++;
1154 }
1155
1156 if (c->disable_display_align == FALSE)
1157 monitors_align(mon->monitors, mon->num_of_monitors);
1158
1159 agent_msg_queue(channel, VD_AGENT_MONITORS_CONFIG, size, mon);
1160 g_free(mon);
1161
1162 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
1163 if (c->timer_id != 0) {
1164 g_source_remove(c->timer_id);
1165 c->timer_id = 0;
1166 }
1167
1168 return TRUE;
1169 }
1170
spice_main_get_audio(const SpiceMainChannel * channel)1171 static SpiceAudio *spice_main_get_audio(const SpiceMainChannel *channel)
1172 {
1173 return spice_audio_get(spice_channel_get_session(SPICE_CHANNEL(channel)), NULL);
1174 }
1175
audio_playback_volume_info_cb(GObject * object,GAsyncResult * res,gpointer user_data)1176 static void audio_playback_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
1177 {
1178 SpiceMainChannel *main_channel = user_data;
1179 SpiceAudio *audio = spice_main_get_audio(main_channel);
1180 VDAgentAudioVolumeSync *avs;
1181 guint16 *volume;
1182 guint8 nchannels;
1183 gboolean mute, ret;
1184 gsize array_size;
1185 GError *error = NULL;
1186
1187 ret = spice_audio_get_playback_volume_info_finish(audio, res, &mute, &nchannels,
1188 &volume, &error);
1189 if (ret == FALSE || volume == NULL || nchannels == 0) {
1190 if (error != NULL) {
1191 SPICE_DEBUG("Failed to get playback async volume info: %s", error->message);
1192 g_error_free(error);
1193 } else {
1194 SPICE_DEBUG("Failed to get playback async volume info");
1195 }
1196 main_channel->priv->agent_volume_playback_sync = FALSE;
1197 return;
1198 }
1199
1200 array_size = sizeof(uint16_t) * nchannels;
1201 avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
1202 avs->is_playback = TRUE;
1203 avs->mute = mute;
1204 avs->nchannels = nchannels;
1205 memcpy(avs->volume, volume, array_size);
1206
1207 SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
1208 __func__, spice_yes_no(mute), nchannels, volume[0]);
1209 g_free(volume);
1210 agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
1211 sizeof(VDAgentAudioVolumeSync) + array_size, avs);
1212 g_free (avs);
1213 }
1214
agent_sync_audio_playback(SpiceMainChannel * main_channel)1215 static void agent_sync_audio_playback(SpiceMainChannel *main_channel)
1216 {
1217 SpiceAudio *audio = spice_main_get_audio(main_channel);
1218 SpiceMainChannelPrivate *c = main_channel->priv;
1219
1220 if (audio == NULL ||
1221 !test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
1222 c->agent_volume_playback_sync == TRUE) {
1223 SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
1224 return;
1225 }
1226 /* only one per connection */
1227 g_cancellable_reset(c->cancellable_volume_info);
1228 c->agent_volume_playback_sync = TRUE;
1229 spice_audio_get_playback_volume_info_async(audio, c->cancellable_volume_info, main_channel,
1230 audio_playback_volume_info_cb, main_channel);
1231 }
1232
audio_record_volume_info_cb(GObject * object,GAsyncResult * res,gpointer user_data)1233 static void audio_record_volume_info_cb(GObject *object, GAsyncResult *res, gpointer user_data)
1234 {
1235 SpiceMainChannel *main_channel = user_data;
1236 SpiceAudio *audio = spice_main_get_audio(main_channel);
1237 VDAgentAudioVolumeSync *avs;
1238 guint16 *volume;
1239 guint8 nchannels;
1240 gboolean ret, mute;
1241 gsize array_size;
1242 GError *error = NULL;
1243
1244 ret = spice_audio_get_record_volume_info_finish(audio, res, &mute, &nchannels, &volume, &error);
1245 if (ret == FALSE || volume == NULL || nchannels == 0) {
1246 if (error != NULL) {
1247 SPICE_DEBUG("Failed to get record async volume info: %s", error->message);
1248 g_error_free(error);
1249 } else {
1250 SPICE_DEBUG("Failed to get record async volume info");
1251 }
1252 main_channel->priv->agent_volume_record_sync = FALSE;
1253 return;
1254 }
1255
1256 array_size = sizeof(uint16_t) * nchannels;
1257 avs = g_malloc0(sizeof(VDAgentAudioVolumeSync) + array_size);
1258 avs->is_playback = FALSE;
1259 avs->mute = mute;
1260 avs->nchannels = nchannels;
1261 memcpy(avs->volume, volume, array_size);
1262
1263 SPICE_DEBUG("%s mute=%s nchannels=%u volume[0]=%u",
1264 __func__, spice_yes_no(mute), nchannels, volume[0]);
1265 g_free(volume);
1266 agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC,
1267 sizeof(VDAgentAudioVolumeSync) + array_size, avs);
1268 g_free (avs);
1269 }
1270
agent_sync_audio_record(SpiceMainChannel * main_channel)1271 static void agent_sync_audio_record(SpiceMainChannel *main_channel)
1272 {
1273 SpiceAudio *audio = spice_main_get_audio(main_channel);
1274 SpiceMainChannelPrivate *c = main_channel->priv;
1275
1276 if (audio == NULL ||
1277 !test_agent_cap(main_channel, VD_AGENT_CAP_AUDIO_VOLUME_SYNC) ||
1278 c->agent_volume_record_sync == TRUE) {
1279 SPICE_DEBUG("%s - is not going to sync audio with guest", __func__);
1280 return;
1281 }
1282 /* only one per connection */
1283 g_cancellable_reset(c->cancellable_volume_info);
1284 c->agent_volume_record_sync = TRUE;
1285 spice_audio_get_record_volume_info_async(audio, c->cancellable_volume_info, main_channel,
1286 audio_record_volume_info_cb, main_channel);
1287 }
1288
1289 /* any context: the message is not flushed immediately,
1290 you can wakeup() the channel coroutine or send_msg_queue() */
agent_display_config(SpiceMainChannel * channel)1291 static void agent_display_config(SpiceMainChannel *channel)
1292 {
1293 SpiceMainChannelPrivate *c = channel->priv;
1294 VDAgentDisplayConfig config = { 0, };
1295
1296 if (c->display_disable_wallpaper) {
1297 config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER;
1298 }
1299
1300 if (c->display_disable_font_smooth) {
1301 config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH;
1302 }
1303
1304 if (c->display_disable_animation) {
1305 config.flags |= VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION;
1306 }
1307
1308 CHANNEL_DEBUG(channel, "display_config: flags: %u, depth: %u", config.flags, config.depth);
1309
1310 agent_msg_queue(channel, VD_AGENT_DISPLAY_CONFIG, sizeof(VDAgentDisplayConfig), &config);
1311 }
1312
1313 /* any context: the message is not flushed immediately,
1314 you can wakeup() the channel coroutine or send_msg_queue() */
agent_announce_caps(SpiceMainChannel * channel)1315 static void agent_announce_caps(SpiceMainChannel *channel)
1316 {
1317 SpiceMainChannelPrivate *c = channel->priv;
1318 VDAgentAnnounceCapabilities *caps;
1319 size_t size;
1320
1321 if (!c->agent_connected)
1322 return;
1323
1324 size = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES;
1325 caps = g_malloc0(size);
1326 if (!c->agent_caps_received)
1327 caps->request = 1;
1328 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
1329 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
1330 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
1331 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
1332 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
1333 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
1334 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG_POSITION);
1335 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS);
1336
1337 agent_msg_queue(channel, VD_AGENT_ANNOUNCE_CAPABILITIES, size, caps);
1338 g_free(caps);
1339 }
1340
1341 /* any context: the message is not flushed immediately,
1342 you can wakeup() the channel coroutine or send_msg_queue() */
agent_clipboard_grab(SpiceMainChannel * channel,guint selection,guint32 * types,int ntypes)1343 static void agent_clipboard_grab(SpiceMainChannel *channel, guint selection,
1344 guint32 *types, int ntypes)
1345 {
1346 SpiceMainChannelPrivate *c = channel->priv;
1347 guint8 *msg;
1348 VDAgentClipboardGrab *grab;
1349 size_t size;
1350 int i;
1351
1352 if (!c->agent_connected)
1353 return;
1354
1355 g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
1356
1357 size = sizeof(VDAgentClipboardGrab) + sizeof(uint32_t) * ntypes;
1358 if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1359 size += 4;
1360 } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
1361 CHANNEL_DEBUG(channel, "Ignoring clipboard grab");
1362 return;
1363 }
1364
1365 msg = g_alloca(size);
1366 memset(msg, 0, size);
1367
1368 grab = (VDAgentClipboardGrab *)msg;
1369
1370 if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1371 msg[0] = selection;
1372 grab = (VDAgentClipboardGrab *)(msg + 4);
1373 }
1374
1375 for (i = 0; i < ntypes; i++) {
1376 grab->types[i] = types[i];
1377 }
1378
1379 agent_msg_queue(channel, VD_AGENT_CLIPBOARD_GRAB, size, msg);
1380 }
1381
1382 /* any context: the message is not flushed immediately,
1383 you can wakeup() the channel coroutine or send_msg_queue() */
agent_clipboard_notify(SpiceMainChannel * self,guint selection,guint32 type,const guchar * data,size_t size)1384 static void agent_clipboard_notify(SpiceMainChannel *self, guint selection,
1385 guint32 type, const guchar *data, size_t size)
1386 {
1387 SpiceMainChannelPrivate *c = self->priv;
1388 VDAgentClipboard *cb;
1389 guint8 *msg;
1390 size_t msgsize;
1391 gint max_clipboard = spice_main_get_max_clipboard(self);
1392
1393 g_return_if_fail(c->agent_connected);
1394 g_return_if_fail(test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
1395 g_return_if_fail(max_clipboard == -1 || size < max_clipboard);
1396
1397 msgsize = sizeof(VDAgentClipboard);
1398 if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1399 msgsize += 4;
1400 } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
1401 CHANNEL_DEBUG(self, "Ignoring clipboard notify");
1402 return;
1403 }
1404
1405 msg = g_alloca(msgsize);
1406 memset(msg, 0, msgsize);
1407
1408 cb = (VDAgentClipboard *)msg;
1409
1410 if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1411 msg[0] = selection;
1412 cb = (VDAgentClipboard *)(msg + 4);
1413 }
1414
1415 cb->type = type;
1416 agent_msg_queue_many(self, VD_AGENT_CLIPBOARD, msg, msgsize, data, size, NULL);
1417 }
1418
1419 /* any context: the message is not flushed immediately,
1420 you can wakeup() the channel coroutine or send_msg_queue() */
agent_clipboard_request(SpiceMainChannel * channel,guint selection,guint32 type)1421 static void agent_clipboard_request(SpiceMainChannel *channel, guint selection, guint32 type)
1422 {
1423 SpiceMainChannelPrivate *c = channel->priv;
1424 VDAgentClipboardRequest *request;
1425 guint8 *msg;
1426 size_t msgsize;
1427
1428 g_return_if_fail(c->agent_connected);
1429 g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
1430
1431 msgsize = sizeof(VDAgentClipboardRequest);
1432 if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1433 msgsize += 4;
1434 } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
1435 SPICE_DEBUG("Ignoring clipboard request");
1436 return;
1437 }
1438
1439 msg = g_alloca(msgsize);
1440 memset(msg, 0, msgsize);
1441
1442 request = (VDAgentClipboardRequest *)msg;
1443
1444 if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1445 msg[0] = selection;
1446 request = (VDAgentClipboardRequest *)(msg + 4);
1447 }
1448
1449 request->type = type;
1450
1451 agent_msg_queue(channel, VD_AGENT_CLIPBOARD_REQUEST, msgsize, msg);
1452 }
1453
1454 /* any context: the message is not flushed immediately,
1455 you can wakeup() the channel coroutine or send_msg_queue() */
agent_clipboard_release(SpiceMainChannel * channel,guint selection)1456 static void agent_clipboard_release(SpiceMainChannel *channel, guint selection)
1457 {
1458 SpiceMainChannelPrivate *c = channel->priv;
1459 guint8 msg[4] = { 0, };
1460 guint8 msgsize = 0;
1461
1462 g_return_if_fail(c->agent_connected);
1463 g_return_if_fail(test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
1464
1465 if (test_agent_cap(channel, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1466 msg[0] = selection;
1467 msgsize += 4;
1468 } else if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
1469 SPICE_DEBUG("Ignoring clipboard release");
1470 return;
1471 }
1472
1473 agent_msg_queue(channel, VD_AGENT_CLIPBOARD_RELEASE, msgsize, msg);
1474 }
1475
any_display_has_dimensions(SpiceMainChannel * channel)1476 static gboolean any_display_has_dimensions(SpiceMainChannel *channel)
1477 {
1478 SpiceMainChannelPrivate *c;
1479 guint i;
1480
1481 g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
1482 c = channel->priv;
1483
1484 for (i = 0; i < MAX_DISPLAY; i++) {
1485 if (c->display[i].width > 0 && c->display[i].height > 0)
1486 return TRUE;
1487 }
1488
1489 return FALSE;
1490 }
1491
1492 /* main context*/
timer_set_display(gpointer data)1493 static gboolean timer_set_display(gpointer data)
1494 {
1495 SpiceMainChannel *channel = data;
1496 SpiceMainChannelPrivate *c = channel->priv;
1497 SpiceSession *session;
1498 gint i;
1499
1500 c->timer_id = 0;
1501 if (!c->agent_connected)
1502 return FALSE;
1503
1504 if (!any_display_has_dimensions(channel)) {
1505 SPICE_DEBUG("Not sending monitors config, at least one monitor must have dimensions");
1506 return FALSE;
1507 }
1508
1509 session = spice_channel_get_session(SPICE_CHANNEL(channel));
1510
1511 if (!spice_main_channel_agent_test_capability(channel, VD_AGENT_CAP_SPARSE_MONITORS_CONFIG)) {
1512 /* ensure we have an explicit monitor configuration at least for
1513 number of display channels */
1514 for (i = 0; i < spice_session_get_n_display_channels(session); i++)
1515 if (c->display[i].display_state == DISPLAY_UNDEFINED) {
1516 SPICE_DEBUG("Not sending monitors config, missing monitors");
1517 return FALSE;
1518 }
1519 }
1520 spice_main_channel_send_monitor_config(channel);
1521
1522 return FALSE;
1523 }
1524
1525 /* any context */
update_display_timer(SpiceMainChannel * channel,guint seconds)1526 static void update_display_timer(SpiceMainChannel *channel, guint seconds)
1527 {
1528 SpiceMainChannelPrivate *c = channel->priv;
1529
1530 if (c->timer_id)
1531 g_source_remove(c->timer_id);
1532
1533 if (seconds != 0) {
1534 c->timer_id = g_timeout_add_seconds(seconds, timer_set_display, channel);
1535 } else {
1536 /* We need to special case 0, as we want the callback to fire as soon
1537 * as possible. g_timeout_add_seconds(0) would set up a timer which would fire
1538 * at the next second boundary, which might be nearly 1 full second later.
1539 */
1540 c->timer_id = g_timeout_add(0, timer_set_display, channel);
1541 }
1542
1543 }
1544
1545 /* coroutine context */
set_agent_connected(SpiceMainChannel * channel,gboolean connected)1546 static void set_agent_connected(SpiceMainChannel *channel, gboolean connected)
1547 {
1548 SpiceMainChannelPrivate *c = channel->priv;
1549
1550 SPICE_DEBUG("agent connected: %s", spice_yes_no(connected));
1551 if (connected != c->agent_connected) {
1552 c->agent_connected = connected;
1553 g_coroutine_object_notify(G_OBJECT(channel), "agent-connected");
1554 }
1555 if (!connected)
1556 spice_main_channel_reset_agent(SPICE_MAIN_CHANNEL(channel));
1557
1558 g_coroutine_signal_emit(channel, signals[SPICE_MAIN_AGENT_UPDATE], 0);
1559 }
1560
1561 /* coroutine context */
agent_start(SpiceMainChannel * channel)1562 static void agent_start(SpiceMainChannel *channel)
1563 {
1564 SpiceMainChannelPrivate *c = channel->priv;
1565 SpiceMsgcMainAgentStart agent_start = {
1566 .num_tokens = ~0,
1567 };
1568 SpiceMsgOut *out;
1569
1570 c->agent_volume_playback_sync = FALSE;
1571 c->agent_volume_record_sync = FALSE;
1572 c->agent_caps_received = false;
1573 set_agent_connected(channel, TRUE);
1574
1575 out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_AGENT_START);
1576 out->marshallers->msgc_main_agent_start(out->marshaller, &agent_start);
1577 spice_msg_out_send_internal(out);
1578
1579 if (c->agent_connected) {
1580 agent_announce_caps(channel);
1581 agent_send_msg_queue(channel);
1582 }
1583 }
1584
1585 /* coroutine context */
agent_stopped(SpiceMainChannel * channel)1586 static void agent_stopped(SpiceMainChannel *channel)
1587 {
1588 set_agent_connected(channel, FALSE);
1589 }
1590
1591 /**
1592 * spice_main_request_mouse_mode:
1593 * @channel: a %SpiceMainChannel
1594 * @mode: a SPICE_MOUSE_MODE
1595 *
1596 * Request a mouse mode to the server. The server may not be able to
1597 * change the mouse mode, but spice-gtk will try to request it
1598 * when possible.
1599 *
1600 * Since: 0.32
1601 * Deprecated: 0.35: use spice_main_channel_request_mouse_mode() instead.
1602 **/
spice_main_request_mouse_mode(SpiceMainChannel * channel,int mode)1603 void spice_main_request_mouse_mode(SpiceMainChannel *channel, int mode)
1604 {
1605 spice_main_channel_request_mouse_mode(channel, mode);
1606 }
1607
1608 /**
1609 * spice_main_channel_request_mouse_mode:
1610 * @channel: a %SpiceMainChannel
1611 * @mode: a SPICE_MOUSE_MODE
1612 *
1613 * Request a mouse mode to the server. The server may not be able to
1614 * change the mouse mode, but spice-gtk will try to request it
1615 * when possible.
1616 *
1617 * Since: 0.35
1618 **/
spice_main_channel_request_mouse_mode(SpiceMainChannel * channel,int mode)1619 void spice_main_channel_request_mouse_mode(SpiceMainChannel *channel, int mode)
1620 {
1621 SpiceMsgcMainMouseModeRequest req = {
1622 .mode = mode,
1623 };
1624 SpiceMsgOut *out;
1625 SpiceMainChannelPrivate *c;
1626
1627 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
1628 c = channel->priv;
1629
1630 if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
1631 return;
1632
1633 CHANNEL_DEBUG(channel, "request mouse mode %d", mode);
1634 c->requested_mouse_mode = mode;
1635
1636 out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST);
1637 out->marshallers->msgc_main_mouse_mode_request(out->marshaller, &req);
1638 spice_msg_out_send(out);
1639 }
1640
1641 /* coroutine context */
set_mouse_mode(SpiceMainChannel * channel,uint32_t supported,uint32_t current)1642 static void set_mouse_mode(SpiceMainChannel *channel, uint32_t supported, uint32_t current)
1643 {
1644 SpiceMainChannelPrivate *c = channel->priv;
1645
1646 if (c->mouse_mode != current) {
1647 c->mouse_mode = current;
1648 g_coroutine_signal_emit(channel, signals[SPICE_MAIN_MOUSE_UPDATE], 0);
1649 g_coroutine_object_notify(G_OBJECT(channel), "mouse-mode");
1650 }
1651
1652 if (c->requested_mouse_mode != c->mouse_mode &&
1653 c->requested_mouse_mode & supported) {
1654 spice_main_channel_request_mouse_mode(SPICE_MAIN_CHANNEL(channel), c->requested_mouse_mode);
1655 }
1656 }
1657
1658 /* coroutine context */
main_handle_init(SpiceChannel * channel,SpiceMsgIn * in)1659 static void main_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
1660 {
1661 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
1662 SpiceMsgMainInit *init = spice_msg_in_parsed(in);
1663 SpiceSession *session;
1664 SpiceMsgOut *out;
1665
1666 session = spice_channel_get_session(channel);
1667 spice_session_set_connection_id(session, init->session_id);
1668
1669 set_mouse_mode(SPICE_MAIN_CHANNEL(channel), init->supported_mouse_modes,
1670 init->current_mouse_mode);
1671
1672 spice_session_set_mm_time(session, init->multi_media_time);
1673 spice_session_set_caches_hints(session, init->ram_hint, init->display_channels_hint);
1674
1675 c->agent_tokens = init->agent_tokens;
1676 if (init->agent_connected)
1677 agent_start(SPICE_MAIN_CHANNEL(channel));
1678
1679 if (spice_session_migrate_after_main_init(session))
1680 return;
1681
1682 out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
1683 spice_msg_out_send_internal(out);
1684 }
1685
1686 /* coroutine context */
main_handle_name(SpiceChannel * channel,SpiceMsgIn * in)1687 static void main_handle_name(SpiceChannel *channel, SpiceMsgIn *in)
1688 {
1689 SpiceMsgMainName *name = spice_msg_in_parsed(in);
1690 SpiceSession *session = spice_channel_get_session(channel);
1691
1692 SPICE_DEBUG("server name: %s", name->name);
1693 spice_session_set_name(session, (const gchar *)name->name);
1694 }
1695
1696 /* coroutine context */
main_handle_uuid(SpiceChannel * channel,SpiceMsgIn * in)1697 static void main_handle_uuid(SpiceChannel *channel, SpiceMsgIn *in)
1698 {
1699 SpiceMsgMainUuid *uuid = spice_msg_in_parsed(in);
1700 SpiceSession *session = spice_channel_get_session(channel);
1701 gchar *uuid_str = spice_uuid_to_string(uuid->uuid);
1702
1703 SPICE_DEBUG("server uuid: %s", uuid_str);
1704 spice_session_set_uuid(session, uuid->uuid);
1705
1706 g_free(uuid_str);
1707 }
1708
1709 /* coroutine context */
main_handle_mm_time(SpiceChannel * channel,SpiceMsgIn * in)1710 static void main_handle_mm_time(SpiceChannel *channel, SpiceMsgIn *in)
1711 {
1712 SpiceSession *session;
1713 SpiceMsgMainMultiMediaTime *msg = spice_msg_in_parsed(in);
1714
1715 session = spice_channel_get_session(channel);
1716 spice_session_set_mm_time(session, msg->time);
1717 }
1718
1719 typedef struct channel_new {
1720 SpiceSession *session;
1721 int type;
1722 int id;
1723 } channel_new_t;
1724
1725 /* main context */
_channel_new(channel_new_t * c)1726 static gboolean _channel_new(channel_new_t *c)
1727 {
1728 g_return_val_if_fail(c != NULL, FALSE);
1729
1730 spice_channel_new(c->session, c->type, c->id);
1731
1732 g_object_unref(c->session);
1733 g_free(c);
1734
1735 return FALSE;
1736 }
1737
1738 /* coroutine context */
main_handle_channels_list(SpiceChannel * channel,SpiceMsgIn * in)1739 static void main_handle_channels_list(SpiceChannel *channel, SpiceMsgIn *in)
1740 {
1741 SpiceMsgChannels *msg = spice_msg_in_parsed(in);
1742 SpiceSession *session;
1743 int i;
1744
1745 session = spice_channel_get_session(channel);
1746
1747 /* guarantee that uuid is notified before setting up the channels, even if
1748 * the server is older and doesn't actually send the uuid */
1749 g_coroutine_object_notify(G_OBJECT(session), "uuid");
1750
1751 for (i = 0; i < msg->num_of_channels; i++) {
1752 channel_new_t *c;
1753
1754 c = g_new(channel_new_t, 1);
1755 c->session = g_object_ref(session);
1756 c->type = msg->channels[i].type;
1757 c->id = msg->channels[i].id;
1758 /* no need to explicitely switch to main context, since
1759 synchronous call is not needed. */
1760 /* no need to track idle, session is refed */
1761 g_idle_add((GSourceFunc)_channel_new, c);
1762 }
1763 }
1764
1765 /* coroutine context */
main_handle_mouse_mode(SpiceChannel * channel,SpiceMsgIn * in)1766 static void main_handle_mouse_mode(SpiceChannel *channel, SpiceMsgIn *in)
1767 {
1768 SpiceMsgMainMouseMode *msg = spice_msg_in_parsed(in);
1769 set_mouse_mode(SPICE_MAIN_CHANNEL(channel), msg->supported_modes, msg->current_mode);
1770 }
1771
1772 /* coroutine context */
main_handle_agent_connected(SpiceChannel * channel,SpiceMsgIn * in)1773 static void main_handle_agent_connected(SpiceChannel *channel, SpiceMsgIn *in)
1774 {
1775 agent_start(SPICE_MAIN_CHANNEL(channel));
1776 }
1777
1778 /* coroutine context */
main_handle_agent_connected_tokens(SpiceChannel * channel,SpiceMsgIn * in)1779 static void main_handle_agent_connected_tokens(SpiceChannel *channel, SpiceMsgIn *in)
1780 {
1781 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
1782 SpiceMsgMainAgentConnectedTokens *msg = spice_msg_in_parsed(in);
1783
1784 c->agent_tokens = msg->num_tokens;
1785 agent_start(SPICE_MAIN_CHANNEL(channel));
1786 }
1787
1788 /* coroutine context */
main_handle_agent_disconnected(SpiceChannel * channel,SpiceMsgIn * in)1789 static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in)
1790 {
1791 agent_stopped(SPICE_MAIN_CHANNEL(channel));
1792 }
1793
file_xfer_data_flushed_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1794 static void file_xfer_data_flushed_cb(GObject *source_object,
1795 GAsyncResult *res,
1796 gpointer user_data)
1797 {
1798 SpiceFileTransferTask *xfer_task = SPICE_FILE_TRANSFER_TASK(source_object);
1799 GError *error = NULL;
1800
1801 file_xfer_flush_finish(xfer_task, res, &error);
1802 if (error) {
1803 spice_file_transfer_task_completed(xfer_task, error);
1804 return;
1805 }
1806
1807 /* task might be completed while on idle */
1808 if (!spice_file_transfer_task_is_completed(xfer_task)) {
1809 file_transfer_operation_send_progress(xfer_task);
1810 /* Read more data */
1811 spice_file_transfer_task_read_async(xfer_task, file_xfer_read_async_cb, user_data);
1812 }
1813 }
1814
file_xfer_queue_msg_to_agent(SpiceMainChannel * channel,guint32 task_id,gchar * buffer,gint data_size)1815 static void file_xfer_queue_msg_to_agent(SpiceMainChannel *channel,
1816 guint32 task_id,
1817 gchar *buffer,
1818 gint data_size)
1819 {
1820 VDAgentFileXferDataMessage msg;
1821
1822 g_return_if_fail(channel != NULL);
1823
1824 msg.id = task_id;
1825 msg.size = data_size;
1826 agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_DATA,
1827 &msg, sizeof(msg),
1828 buffer, data_size, NULL);
1829 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
1830 }
1831
1832 /* main context */
file_xfer_read_async_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1833 static void file_xfer_read_async_cb(GObject *source_object,
1834 GAsyncResult *res,
1835 gpointer user_data)
1836 {
1837 FileTransferOperation *xfer_op;
1838 SpiceFileTransferTask *xfer_task;
1839 SpiceMainChannel *channel;
1840 gssize count;
1841 char *buffer;
1842 GError *error = NULL;
1843
1844 xfer_task = SPICE_FILE_TRANSFER_TASK(source_object);
1845 xfer_op = user_data;
1846
1847 channel = spice_file_transfer_task_get_channel(xfer_task);
1848 count = spice_file_transfer_task_read_finish(xfer_task, res, &buffer, &error);
1849 if (count < 0) {
1850 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
1851 spice_file_transfer_task_completed(xfer_task, error);
1852 return;
1853 }
1854
1855 if (count == 0 && spice_file_transfer_task_get_total_bytes(xfer_task) > 0) {
1856 /* If we have sent all payload to the agent, we should not send 0 bytes
1857 * as it will cause https://bugs.freedesktop.org/show_bug.cgi?id=97227.
1858 * Only when file has 0 bytes of size is when we should send 0 bytes to
1859 * agent, see: https://bugzilla.redhat.com/show_bug.cgi?id=1135099 */
1860 return;
1861 }
1862
1863 file_xfer_queue_msg_to_agent(channel, spice_file_transfer_task_get_id(xfer_task), buffer, count);
1864 if (count == 0 || spice_file_transfer_task_is_completed(xfer_task)) {
1865 /* on EOF just wait for VD_AGENT_FILE_XFER_STATUS from agent
1866 * in case the task was completed, nothing to do. */
1867 return;
1868 }
1869
1870 xfer_op->stats.total_sent += count;
1871
1872 file_xfer_flush_async(xfer_task, file_xfer_data_flushed_cb, xfer_op);
1873 }
1874
1875 /* coroutine context */
main_agent_handle_xfer_status(SpiceMainChannel * channel,VDAgentFileXferStatusMessage * msg)1876 static void main_agent_handle_xfer_status(SpiceMainChannel *channel,
1877 VDAgentFileXferStatusMessage *msg)
1878 {
1879 SpiceFileTransferTask *xfer_task;
1880 FileTransferOperation *xfer_op;
1881 GError *error = NULL;
1882
1883 SPICE_DEBUG("xfer-task %u received response %u", msg->id, msg->result);
1884
1885 xfer_task = spice_main_channel_find_xfer_task_by_task_id(channel, msg->id);
1886 g_return_if_fail(xfer_task != NULL);
1887 xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(msg->id));
1888
1889 switch (msg->result) {
1890 case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
1891 g_return_if_fail(spice_file_transfer_task_is_completed(xfer_task) == FALSE);
1892 spice_file_transfer_task_read_async(xfer_task, file_xfer_read_async_cb, xfer_op);
1893 return;
1894 case VD_AGENT_FILE_XFER_STATUS_CANCELLED:
1895 error = g_error_new_literal(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1896 _("The spice agent cancelled the file transfer"));
1897 break;
1898 case VD_AGENT_FILE_XFER_STATUS_ERROR:
1899 error = g_error_new_literal(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1900 _("The spice agent reported an error during the file transfer"));
1901 break;
1902 case VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE: {
1903 uint64_t *free_space = SPICE_ALIGNED_CAST(uint64_t *, msg->data);
1904 gchar *free_space_str = g_format_size(*free_space);
1905 gchar *file_size_str = g_format_size(spice_file_transfer_task_get_total_bytes(xfer_task));
1906 error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1907 _("File transfer failed due to lack of free space on remote machine "
1908 "(%s free, %s to transfer)"), free_space_str, file_size_str);
1909 g_free(free_space_str);
1910 g_free(file_size_str);
1911 break;
1912 }
1913 case VD_AGENT_FILE_XFER_STATUS_SESSION_LOCKED:
1914 error = g_error_new_literal(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1915 _("User's session is locked and cannot transfer files, "
1916 "unlock it and try again."));
1917 break;
1918 case VD_AGENT_FILE_XFER_STATUS_VDAGENT_NOT_CONNECTED:
1919 error = g_error_new_literal(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1920 _("Session agent not connected."));
1921 break;
1922 case VD_AGENT_FILE_XFER_STATUS_DISABLED:
1923 error = g_error_new_literal(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1924 _("File transfer is disabled."));
1925 break;
1926 case VD_AGENT_FILE_XFER_STATUS_SUCCESS:
1927 break;
1928 default:
1929 g_warn_if_reached();
1930 error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
1931 "unhandled status type: %u", msg->result);
1932 break;
1933 }
1934
1935 spice_file_transfer_task_completed(xfer_task, error);
1936 }
1937
1938
1939 /* any context: the message is not flushed immediately,
1940 you can wakeup() the channel coroutine or send_msg_queue() */
agent_max_clipboard(SpiceMainChannel * self)1941 static void agent_max_clipboard(SpiceMainChannel *self)
1942 {
1943 VDAgentMaxClipboard msg = { .max = spice_main_get_max_clipboard(self) };
1944
1945 if (!test_agent_cap(self, VD_AGENT_CAP_MAX_CLIPBOARD))
1946 return;
1947
1948 agent_msg_queue(self, VD_AGENT_MAX_CLIPBOARD, sizeof(VDAgentMaxClipboard), &msg);
1949 }
1950
spice_main_set_max_clipboard(SpiceMainChannel * self,gint max)1951 static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max)
1952 {
1953 SpiceMainChannelPrivate *c;
1954
1955 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(self));
1956 g_return_if_fail(max >= -1);
1957
1958 c = self->priv;
1959 if (max == spice_main_get_max_clipboard(self))
1960 return;
1961
1962 c->max_clipboard = max;
1963 agent_max_clipboard(self);
1964 spice_channel_wakeup(SPICE_CHANNEL(self), FALSE);
1965 }
1966
1967 /* coroutine context */
main_agent_handle_msg(SpiceChannel * channel,VDAgentMessage * msg,gpointer payload)1968 static void main_agent_handle_msg(SpiceChannel *channel,
1969 VDAgentMessage *msg, gpointer payload)
1970 {
1971 SpiceMainChannel *self = SPICE_MAIN_CHANNEL(channel);
1972 SpiceMainChannelPrivate *c = self->priv;
1973 guint8 selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
1974
1975 g_return_if_fail(msg->protocol == VD_AGENT_PROTOCOL);
1976
1977 switch (msg->type) {
1978 case VD_AGENT_CLIPBOARD_RELEASE:
1979 case VD_AGENT_CLIPBOARD_REQUEST:
1980 case VD_AGENT_CLIPBOARD_GRAB:
1981 case VD_AGENT_CLIPBOARD:
1982 if (test_agent_cap(self, VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
1983 selection = *((guint8*)payload);
1984 payload = ((guint8*)payload) + 4;
1985 msg->size -= 4;
1986 }
1987 break;
1988 default:
1989 break;
1990 }
1991
1992 switch (msg->type) {
1993 case VD_AGENT_ANNOUNCE_CAPABILITIES:
1994 {
1995 VDAgentAnnounceCapabilities *caps = payload;
1996 int i, size;
1997
1998 size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg->size);
1999 if (size > VD_AGENT_CAPS_SIZE)
2000 size = VD_AGENT_CAPS_SIZE;
2001 memset(c->agent_caps, 0, sizeof(c->agent_caps));
2002 for (i = 0; i < size * 32; i++) {
2003 if (!VD_AGENT_HAS_CAPABILITY(caps->caps, size, i))
2004 continue;
2005 SPICE_DEBUG("%s: cap: %d (%s)", __FUNCTION__,
2006 i, NAME(agent_caps, i));
2007 VD_AGENT_SET_CAPABILITY(c->agent_caps, i);
2008 }
2009 c->agent_caps_received = true;
2010 g_coroutine_signal_emit(self, signals[SPICE_MAIN_AGENT_UPDATE], 0);
2011 update_display_timer(SPICE_MAIN_CHANNEL(channel), 0);
2012
2013 if (caps->request)
2014 agent_announce_caps(self);
2015
2016 if (test_agent_cap(self, VD_AGENT_CAP_DISPLAY_CONFIG) &&
2017 !c->agent_display_config_sent) {
2018 agent_display_config(self);
2019 c->agent_display_config_sent = true;
2020 }
2021
2022 agent_sync_audio_playback(self);
2023 agent_sync_audio_record(self);
2024
2025 agent_max_clipboard(self);
2026
2027 agent_send_msg_queue(self);
2028
2029 break;
2030 }
2031 case VD_AGENT_CLIPBOARD:
2032 {
2033 VDAgentClipboard *cb = payload;
2034 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION], 0, selection,
2035 cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
2036
2037 if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
2038 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD], 0,
2039 cb->type, cb->data, msg->size - sizeof(VDAgentClipboard));
2040 break;
2041 }
2042 case VD_AGENT_CLIPBOARD_GRAB:
2043 {
2044 gboolean ret;
2045 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_GRAB], 0, selection,
2046 (guint8*)payload, msg->size / sizeof(uint32_t), &ret);
2047 if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
2048 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_GRAB], 0,
2049 payload, msg->size / sizeof(uint32_t), &ret);
2050 break;
2051 }
2052 case VD_AGENT_CLIPBOARD_REQUEST:
2053 {
2054 gboolean ret;
2055 VDAgentClipboardRequest *req = payload;
2056 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST], 0, selection,
2057 req->type, &ret);
2058
2059 if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
2060 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_REQUEST], 0,
2061 req->type, &ret);
2062 break;
2063 }
2064 case VD_AGENT_CLIPBOARD_RELEASE:
2065 {
2066 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE], 0, selection);
2067
2068 if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD)
2069 g_coroutine_signal_emit(self, signals[SPICE_MAIN_CLIPBOARD_RELEASE], 0);
2070 break;
2071 }
2072 case VD_AGENT_REPLY:
2073 {
2074 VDAgentReply *reply = payload;
2075 SPICE_DEBUG("%s: reply: type %u, %s", __FUNCTION__, reply->type,
2076 reply->error == VD_AGENT_SUCCESS ? "success" : "error");
2077 break;
2078 }
2079 case VD_AGENT_FILE_XFER_STATUS:
2080 main_agent_handle_xfer_status(self, payload);
2081 break;
2082 default:
2083 g_warning("unhandled agent message type: %u (%s), size %u",
2084 msg->type, NAME(agent_msg_types, msg->type), msg->size);
2085 }
2086 }
2087
2088 /* coroutine context */
main_handle_agent_data_msg(SpiceChannel * channel,int * msg_size,guchar ** msg_pos)2089 static void main_handle_agent_data_msg(SpiceChannel* channel, int* msg_size, guchar** msg_pos)
2090 {
2091 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2092 int n;
2093
2094 if (c->agent_msg_pos < sizeof(VDAgentMessage)) {
2095 n = MIN(sizeof(VDAgentMessage) - c->agent_msg_pos, *msg_size);
2096 memcpy((uint8_t*)&c->agent_msg + c->agent_msg_pos, *msg_pos, n);
2097 c->agent_msg_pos += n;
2098 *msg_size -= n;
2099 *msg_pos += n;
2100 if (c->agent_msg_pos == sizeof(VDAgentMessage)) {
2101 SPICE_DEBUG("agent msg start: msg_size=%u, protocol=%u, type=%u",
2102 c->agent_msg.size, c->agent_msg.protocol, c->agent_msg.type);
2103 g_return_if_fail(c->agent_msg_data == NULL);
2104 c->agent_msg_data = g_malloc0(c->agent_msg.size);
2105 }
2106 }
2107
2108 if (c->agent_msg_pos >= sizeof(VDAgentMessage)) {
2109 n = MIN(sizeof(VDAgentMessage) + c->agent_msg.size - c->agent_msg_pos, *msg_size);
2110 memcpy(c->agent_msg_data + c->agent_msg_pos - sizeof(VDAgentMessage), *msg_pos, n);
2111 c->agent_msg_pos += n;
2112 *msg_size -= n;
2113 *msg_pos += n;
2114 }
2115
2116 if (c->agent_msg_pos == sizeof(VDAgentMessage) + c->agent_msg.size) {
2117 main_agent_handle_msg(channel, &c->agent_msg, c->agent_msg_data);
2118 g_free(c->agent_msg_data);
2119 c->agent_msg_data = NULL;
2120 c->agent_msg_pos = 0;
2121 }
2122 }
2123
2124 /* coroutine context */
main_handle_agent_data(SpiceChannel * channel,SpiceMsgIn * in)2125 static void main_handle_agent_data(SpiceChannel *channel, SpiceMsgIn *in)
2126 {
2127 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2128 guint8 *data;
2129 int len;
2130
2131 g_warn_if_fail(c->agent_connected);
2132
2133 /* shortcut to avoid extra message allocation & copy if possible */
2134 if (c->agent_msg_pos == 0) {
2135 VDAgentMessage *msg;
2136 guint msg_size;
2137
2138 msg = spice_msg_in_raw(in, &len);
2139 msg_size = msg->size;
2140
2141 if (msg_size + sizeof(VDAgentMessage) == len) {
2142 main_agent_handle_msg(channel, msg, msg->data);
2143 return;
2144 }
2145 }
2146
2147 data = spice_msg_in_raw(in, &len);
2148 while (len > 0) {
2149 main_handle_agent_data_msg(channel, &len, &data);
2150 }
2151 }
2152
2153 /* coroutine context */
main_handle_agent_token(SpiceChannel * channel,SpiceMsgIn * in)2154 static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
2155 {
2156 SpiceMsgMainAgentTokens *tokens = spice_msg_in_parsed(in);
2157 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2158
2159 c->agent_tokens += tokens->num_tokens;
2160
2161 agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
2162 }
2163
2164 /* main context */
migrate_channel_new_cb(SpiceSession * s,SpiceChannel * channel,gpointer data)2165 static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpointer data)
2166 {
2167 g_signal_connect(channel, "channel-event",
2168 G_CALLBACK(migrate_channel_event_cb), data);
2169 }
2170
migrate_channel_connect(spice_migrate * mig,int type,int id)2171 static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int id)
2172 {
2173 SPICE_DEBUG("migrate_channel_connect %d:%d", type, id);
2174
2175 SpiceChannel *newc = spice_channel_new(mig->session, type, id);
2176 spice_channel_connect(newc);
2177 mig->nchannels++;
2178
2179 return newc;
2180 }
2181
2182 /* coroutine context */
spice_main_channel_send_migration_handshake(SpiceChannel * channel)2183 static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
2184 {
2185 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2186
2187 if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
2188 c->migrate_data->do_seamless = false;
2189 g_idle_add(main_migrate_handshake_done, c->migrate_data);
2190 } else {
2191 SpiceMsgcMainMigrateDstDoSeamless msg_data;
2192 SpiceMsgOut *msg_out;
2193
2194 msg_data.src_version = c->migrate_data->src_mig_version;
2195
2196 msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
2197 msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
2198 spice_msg_out_send_internal(msg_out);
2199 }
2200 }
2201
2202 /* main context */
migrate_channel_event_cb(SpiceChannel * channel,SpiceChannelEvent event,gpointer data)2203 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
2204 gpointer data)
2205 {
2206 spice_migrate *mig = data;
2207 SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
2208
2209 g_return_if_fail(mig->nchannels > 0);
2210 g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
2211
2212 switch (event) {
2213 case SPICE_CHANNEL_OPENED:
2214 if (c->channel_type == SPICE_CHANNEL_MAIN) {
2215 SpiceSession *session = spice_channel_get_session(mig->src_channel);
2216 if (mig->do_seamless) {
2217 SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
2218
2219 c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
2220 mig->dst_channel = channel;
2221 main_priv->migrate_data = mig;
2222 } else {
2223 c->state = SPICE_CHANNEL_STATE_MIGRATING;
2224 mig->nchannels--;
2225 }
2226 /* now connect the rest of the channels */
2227 GList *channels, *l;
2228 l = channels = spice_session_get_channels(session);
2229 while (l != NULL) {
2230 SpiceChannelPrivate *curc = SPICE_CHANNEL(l->data)->priv;
2231 l = l->next;
2232 if (curc->channel_type == SPICE_CHANNEL_MAIN)
2233 continue;
2234 migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
2235 }
2236 g_list_free(channels);
2237 } else {
2238 c->state = SPICE_CHANNEL_STATE_MIGRATING;
2239 mig->nchannels--;
2240 }
2241
2242 SPICE_DEBUG("migration: channel opened chan:%p, left %u", channel, mig->nchannels);
2243 if (mig->nchannels == 0)
2244 coroutine_yieldto(mig->from, NULL);
2245 break;
2246 default:
2247 CHANNEL_DEBUG(channel, "error or unhandled channel event during migration: %u", event);
2248 /* go back to main channel to report error */
2249 coroutine_yieldto(mig->from, NULL);
2250 }
2251 }
2252
2253 /* main context */
main_migrate_handshake_done(gpointer data)2254 static gboolean main_migrate_handshake_done(gpointer data)
2255 {
2256 spice_migrate *mig = data;
2257 SpiceChannelPrivate *c = SPICE_CHANNEL(mig->dst_channel)->priv;
2258
2259 g_return_val_if_fail(c->channel_type == SPICE_CHANNEL_MAIN, FALSE);
2260 g_return_val_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
2261
2262 c->state = SPICE_CHANNEL_STATE_MIGRATING;
2263 mig->nchannels--;
2264 if (mig->nchannels == 0)
2265 coroutine_yieldto(mig->from, NULL);
2266 return FALSE;
2267 }
2268
2269 #ifdef __GNUC__
2270 typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
2271 #else
2272 typedef struct __declspec(align(1)) OldRedMigrationBegin {
2273 #endif
2274 uint16_t port;
2275 uint16_t sport;
2276 char host[0];
2277 } OldRedMigrationBegin;
2278
2279 /* main context */
migrate_connect(gpointer data)2280 static gboolean migrate_connect(gpointer data)
2281 {
2282 spice_migrate *mig = data;
2283 SpiceChannelPrivate *c;
2284 int port, sport;
2285 const char *host;
2286
2287 g_return_val_if_fail(mig != NULL, FALSE);
2288 g_return_val_if_fail(mig->info != NULL, FALSE);
2289 g_return_val_if_fail(mig->nchannels == 0, FALSE);
2290 c = SPICE_CHANNEL(mig->src_channel)->priv;
2291 g_return_val_if_fail(c != NULL, FALSE);
2292 g_return_val_if_fail(mig->session != NULL, FALSE);
2293
2294 spice_session_set_migration_state(mig->session, SPICE_SESSION_MIGRATION_CONNECTING);
2295
2296 SpiceMigrationDstInfo *info = mig->info;
2297 SPICE_DEBUG("migrate_begin %u %s %d %d",
2298 info->host_size, info->host_data, info->port, info->sport);
2299 port = info->port;
2300 sport = info->sport;
2301 host = (char*)info->host_data;
2302
2303 if (info->cert_subject_size == 0 ||
2304 strlen((const char*)info->cert_subject_data) == 0) {
2305 /* only verify hostname if no cert subject */
2306 g_object_set(mig->session, "verify", SPICE_SESSION_VERIFY_HOSTNAME, NULL);
2307 } else {
2308 gchar *subject = g_alloca(info->cert_subject_size + 1);
2309 strncpy(subject, (const char*)info->cert_subject_data, info->cert_subject_size);
2310 subject[info->cert_subject_size] = '\0';
2311
2312 // session data are already copied
2313 g_object_set(mig->session,
2314 "cert-subject", subject,
2315 "verify", SPICE_SESSION_VERIFY_SUBJECT,
2316 NULL);
2317 }
2318
2319 if (g_getenv("SPICE_MIG_HOST"))
2320 host = g_getenv("SPICE_MIG_HOST");
2321
2322 g_object_set(mig->session, "host", host, NULL);
2323 spice_session_set_port(mig->session, port, FALSE);
2324 spice_session_set_port(mig->session, sport, TRUE);
2325 g_signal_connect(mig->session, "channel-new",
2326 G_CALLBACK(migrate_channel_new_cb), mig);
2327
2328 g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
2329 mig->session);
2330
2331 /* the migration process is in 2 steps, first the main channel and
2332 then the rest of the channels */
2333 migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
2334
2335 return FALSE;
2336 }
2337
2338 /* coroutine context */
main_migrate_connect(SpiceChannel * channel,SpiceMigrationDstInfo * dst_info,bool do_seamless,uint32_t src_mig_version)2339 static void main_migrate_connect(SpiceChannel *channel,
2340 SpiceMigrationDstInfo *dst_info, bool do_seamless,
2341 uint32_t src_mig_version)
2342 {
2343 SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
2344 int reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
2345 spice_migrate mig = { 0, };
2346 SpiceMsgOut *out;
2347 SpiceSession *session;
2348
2349 mig.src_channel = channel;
2350 mig.info = dst_info;
2351 mig.from = coroutine_self();
2352 mig.do_seamless = do_seamless;
2353 mig.src_mig_version = src_mig_version;
2354
2355 CHANNEL_DEBUG(channel, "migrate connect");
2356 session = spice_channel_get_session(channel);
2357 mig.session = spice_session_new_from_session(session);
2358 if (mig.session == NULL)
2359 goto end;
2360 if (!spice_session_set_migration_session(session, mig.session))
2361 goto end;
2362
2363 main_priv->migrate_data = &mig;
2364
2365 /* no need to track idle, call is sync for this coroutine */
2366 g_idle_add(migrate_connect, &mig);
2367
2368 /* switch to main loop and wait for connections */
2369 coroutine_yield(NULL);
2370
2371 if (mig.nchannels != 0) {
2372 CHANNEL_DEBUG(channel, "migrate failed: some channels failed to connect");
2373 spice_session_abort_migration(session);
2374 } else {
2375 if (mig.do_seamless) {
2376 SPICE_DEBUG("migration (seamless): connections all ok");
2377 reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
2378 } else {
2379 SPICE_DEBUG("migration (semi-seamless): connections all ok");
2380 reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
2381 }
2382 spice_session_start_migrating(spice_channel_get_session(channel),
2383 mig.do_seamless);
2384 }
2385
2386 end:
2387 CHANNEL_DEBUG(channel, "migrate connect reply %d", reply_type);
2388 out = spice_msg_out_new(SPICE_CHANNEL(channel), reply_type);
2389 spice_msg_out_send(out);
2390 }
2391
2392 /* coroutine context */
main_handle_migrate_begin(SpiceChannel * channel,SpiceMsgIn * in)2393 static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
2394 {
2395 SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
2396
2397 main_migrate_connect(channel, &msg->dst_info, false, 0);
2398 }
2399
2400 /* coroutine context */
main_handle_migrate_begin_seamless(SpiceChannel * channel,SpiceMsgIn * in)2401 static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
2402 {
2403 SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
2404
2405 main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
2406 }
2407
main_handle_migrate_dst_seamless_ack(SpiceChannel * channel,SpiceMsgIn * in)2408 static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
2409 {
2410 SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
2411 SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
2412
2413 g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
2414 main_priv->migrate_data->do_seamless = true;
2415 g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
2416 }
2417
main_handle_migrate_dst_seamless_nack(SpiceChannel * channel,SpiceMsgIn * in)2418 static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
2419 {
2420 SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
2421 SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
2422
2423 g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
2424 main_priv->migrate_data->do_seamless = false;
2425 g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
2426 }
2427
2428 /* main context */
migrate_delayed(gpointer data)2429 static gboolean migrate_delayed(gpointer data)
2430 {
2431 SpiceChannel *channel = data;
2432 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2433
2434 g_warn_if_fail(c->migrate_delayed_id != 0);
2435 c->migrate_delayed_id = 0;
2436
2437 spice_session_migrate_end(channel->priv->session);
2438
2439 return FALSE;
2440 }
2441
2442 /* coroutine context */
main_handle_migrate_end(SpiceChannel * channel,SpiceMsgIn * in)2443 static void main_handle_migrate_end(SpiceChannel *channel, SpiceMsgIn *in)
2444 {
2445 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2446
2447 SPICE_DEBUG("migrate end");
2448
2449 g_return_if_fail(c->migrate_delayed_id == 0);
2450 g_return_if_fail(spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE));
2451
2452 c->migrate_delayed_id = g_idle_add(migrate_delayed, channel);
2453 }
2454
2455 /* main context */
switch_host_delayed(gpointer data)2456 static gboolean switch_host_delayed(gpointer data)
2457 {
2458 SpiceChannel *channel = data;
2459 SpiceSession *session;
2460 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2461
2462 g_warn_if_fail(c->switch_host_delayed_id != 0);
2463 c->switch_host_delayed_id = 0;
2464
2465 session = spice_channel_get_session(channel);
2466
2467 spice_channel_disconnect(channel, SPICE_CHANNEL_SWITCHING);
2468 spice_session_switching_disconnect(session);
2469
2470 return FALSE;
2471 }
2472
2473 /* coroutine context */
main_handle_migrate_switch_host(SpiceChannel * channel,SpiceMsgIn * in)2474 static void main_handle_migrate_switch_host(SpiceChannel *channel, SpiceMsgIn *in)
2475 {
2476 SpiceMsgMainMigrationSwitchHost *mig = spice_msg_in_parsed(in);
2477 SpiceSession *session;
2478 char *host = (char *)mig->host_data;
2479 char *subject = NULL;
2480 SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
2481
2482 g_return_if_fail(host[mig->host_size - 1] == '\0');
2483
2484 if (mig->cert_subject_size) {
2485 subject = (char *)mig->cert_subject_data;
2486 g_return_if_fail(subject[mig->cert_subject_size - 1] == '\0');
2487 }
2488
2489 SPICE_DEBUG("migrate_switch %s %d %d %s",
2490 host, mig->port, mig->sport, subject);
2491
2492 if (c->switch_host_delayed_id != 0) {
2493 g_warning("Switching host already in progress, aborting it");
2494 g_warn_if_fail(g_source_remove(c->switch_host_delayed_id));
2495 c->switch_host_delayed_id = 0;
2496 }
2497
2498 session = spice_channel_get_session(channel);
2499 spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_SWITCHING);
2500 g_object_set(session,
2501 "host", host,
2502 "cert-subject", subject,
2503 NULL);
2504 spice_session_set_port(session, mig->port, FALSE);
2505 spice_session_set_port(session, mig->sport, TRUE);
2506
2507 c->switch_host_delayed_id = g_idle_add(switch_host_delayed, channel);
2508 }
2509
2510 /* coroutine context */
main_handle_migrate_cancel(SpiceChannel * channel,SpiceMsgIn * in G_GNUC_UNUSED)2511 static void main_handle_migrate_cancel(SpiceChannel *channel,
2512 SpiceMsgIn *in G_GNUC_UNUSED)
2513 {
2514 SpiceSession *session;
2515
2516 SPICE_DEBUG("migrate_cancel");
2517 session = spice_channel_get_session(channel);
2518 spice_session_abort_migration(session);
2519 }
2520
channel_set_handlers(SpiceChannelClass * klass)2521 static void channel_set_handlers(SpiceChannelClass *klass)
2522 {
2523 static const spice_msg_handler handlers[] = {
2524 [ SPICE_MSG_MAIN_INIT ] = main_handle_init,
2525 [ SPICE_MSG_MAIN_NAME ] = main_handle_name,
2526 [ SPICE_MSG_MAIN_UUID ] = main_handle_uuid,
2527 [ SPICE_MSG_MAIN_CHANNELS_LIST ] = main_handle_channels_list,
2528 [ SPICE_MSG_MAIN_MOUSE_MODE ] = main_handle_mouse_mode,
2529 [ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ] = main_handle_mm_time,
2530
2531 [ SPICE_MSG_MAIN_AGENT_CONNECTED ] = main_handle_agent_connected,
2532 [ SPICE_MSG_MAIN_AGENT_DISCONNECTED ] = main_handle_agent_disconnected,
2533 [ SPICE_MSG_MAIN_AGENT_DATA ] = main_handle_agent_data,
2534 [ SPICE_MSG_MAIN_AGENT_TOKEN ] = main_handle_agent_token,
2535
2536 [ SPICE_MSG_MAIN_MIGRATE_BEGIN ] = main_handle_migrate_begin,
2537 [ SPICE_MSG_MAIN_MIGRATE_END ] = main_handle_migrate_end,
2538 [ SPICE_MSG_MAIN_MIGRATE_CANCEL ] = main_handle_migrate_cancel,
2539 [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
2540 [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ] = main_handle_agent_connected_tokens,
2541 [ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ] = main_handle_migrate_begin_seamless,
2542 [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK] = main_handle_migrate_dst_seamless_ack,
2543 [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
2544 };
2545
2546 spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
2547 }
2548
2549 /* coroutine context */
spice_main_handle_msg(SpiceChannel * channel,SpiceMsgIn * msg)2550 static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
2551 {
2552 int type = spice_msg_in_type(msg);
2553 SpiceChannelClass *parent_class;
2554 SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
2555
2556 parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
2557
2558 if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
2559 if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
2560 type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
2561 g_critical("unexpected msg (%d)."
2562 "Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
2563 return;
2564 }
2565 }
2566
2567 parent_class->handle_msg(channel, msg);
2568 }
2569
2570 /**
2571 * spice_main_agent_test_capability:
2572 * @channel: a #SpiceMainChannel
2573 * @cap: an agent capability identifier
2574 *
2575 * Test capability of a remote agent.
2576 *
2577 * Returns: %TRUE if @cap (channel kind capability) is available.
2578 *
2579 * Deprecated: 0.35: use spice_main_channel_agent_test_capability() instead.
2580 **/
spice_main_agent_test_capability(SpiceMainChannel * channel,guint32 cap)2581 gboolean spice_main_agent_test_capability(SpiceMainChannel *channel, guint32 cap)
2582 {
2583 return spice_main_channel_agent_test_capability(channel, cap);
2584 }
2585
2586 /**
2587 * spice_main_channel_agent_test_capability:
2588 * @channel: a #SpiceMainChannel
2589 * @cap: an agent capability identifier
2590 *
2591 * Test capability of a remote agent.
2592 *
2593 * Returns: %TRUE if @cap (channel kind capability) is available.
2594 *
2595 * Since: 0.35
2596 **/
spice_main_channel_agent_test_capability(SpiceMainChannel * channel,guint32 cap)2597 gboolean spice_main_channel_agent_test_capability(SpiceMainChannel *channel, guint32 cap)
2598 {
2599 g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
2600
2601 return test_agent_cap(channel, cap);
2602 }
2603
2604 /**
2605 * spice_main_update_display:
2606 * @channel: a #SpiceMainChannel
2607 * @id: display ID
2608 * @x: x position
2609 * @y: y position
2610 * @width: display width
2611 * @height: display height
2612 * @update: if %TRUE, update guest resolution after 1sec.
2613 *
2614 * Update the display @id resolution.
2615 *
2616 * If @update is %TRUE, the remote configuration will be updated too
2617 * after 1 second without further changes. You can send when you want
2618 * without delay the new configuration to the remote with
2619 * spice_main_send_monitor_config()
2620 *
2621 * Deprecated: 0.35: use spice_main_channel_update_display() instead.
2622 **/
spice_main_update_display(SpiceMainChannel * channel,int id,int x,int y,int width,int height,gboolean update)2623 void spice_main_update_display(SpiceMainChannel *channel, int id,
2624 int x, int y, int width, int height,
2625 gboolean update)
2626 {
2627 spice_main_channel_update_display(channel, id, x, y, width, height, update);
2628 }
2629
2630 /**
2631 * spice_main_channel_update_display:
2632 * @channel: a #SpiceMainChannel
2633 * @id: display ID
2634 * @x: x position
2635 * @y: y position
2636 * @width: display width
2637 * @height: display height
2638 * @update: if %TRUE, update guest resolution after 1sec.
2639 *
2640 * Update the display @id resolution.
2641 *
2642 * If @update is %TRUE, the remote configuration will be updated too
2643 * after 1 second without further changes. You can send when you want
2644 * without delay the new configuration to the remote with
2645 * spice_main_send_monitor_config()
2646 *
2647 * Since: 0.35
2648 **/
spice_main_channel_update_display(SpiceMainChannel * channel,int id,int x,int y,int width,int height,gboolean update)2649 void spice_main_channel_update_display(SpiceMainChannel *channel, int id, int x, int y, int width,
2650 int height, gboolean update)
2651 {
2652 SpiceMainChannelPrivate *c;
2653
2654 g_return_if_fail(channel != NULL);
2655 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2656 g_return_if_fail(x >= 0);
2657 g_return_if_fail(y >= 0);
2658 g_return_if_fail(width >= 0);
2659 g_return_if_fail(height >= 0);
2660
2661 c = SPICE_MAIN_CHANNEL(channel)->priv;
2662
2663 g_return_if_fail(id < SPICE_N_ELEMENTS(c->display));
2664
2665 SpiceDisplayConfig display = {
2666 .x = x, .y = y, .width = width, .height = height,
2667 .display_state = c->display[id].display_state
2668 };
2669
2670 if (memcmp(&display, &c->display[id], sizeof(SpiceDisplayConfig)) == 0)
2671 return;
2672
2673 c->display[id] = display;
2674
2675 if (update)
2676 update_display_timer(channel, 1);
2677 }
2678
2679 /**
2680 * spice_main_set_display:
2681 * @channel: a #SpiceMainChannel
2682 * @id: display ID
2683 * @x: x position
2684 * @y: y position
2685 * @width: display width
2686 * @height: display height
2687 *
2688 * Notify the guest of screen resolution change. The notification is
2689 * sent 1 second later, if no further changes happen.
2690 *
2691 * Deprecated: 0.35: use spice_main_channel_update_display() instead.
2692 **/
spice_main_set_display(SpiceMainChannel * channel,int id,int x,int y,int width,int height)2693 void spice_main_set_display(SpiceMainChannel *channel, int id,
2694 int x, int y, int width, int height)
2695 {
2696 spice_main_channel_update_display(channel, id, x, y, width, height, TRUE);
2697 }
2698
2699 /**
2700 * spice_main_clipboard_grab:
2701 * @channel: a #SpiceMainChannel
2702 * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
2703 * @ntypes: the number of @types
2704 *
2705 * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
2706 *
2707 * Deprecated: 0.6: use spice_main_channel_clipboard_selection_grab() instead.
2708 **/
spice_main_clipboard_grab(SpiceMainChannel * channel,guint32 * types,int ntypes)2709 void spice_main_clipboard_grab(SpiceMainChannel *channel, guint32 *types, int ntypes)
2710 {
2711 spice_main_channel_clipboard_selection_grab(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
2712 types, ntypes);
2713 }
2714
2715 /**
2716 * spice_main_clipboard_selection_grab:
2717 * @channel: a #SpiceMainChannel
2718 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2719 * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
2720 * @ntypes: the number of @types
2721 *
2722 * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
2723 *
2724 * Since: 0.6
2725 * Deprecated: 0.35: use spice_main_channel_clipboard_selection_grab() instead.
2726 **/
spice_main_clipboard_selection_grab(SpiceMainChannel * channel,guint selection,guint32 * types,int ntypes)2727 void spice_main_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
2728 guint32 *types, int ntypes)
2729 {
2730 spice_main_channel_clipboard_selection_grab(channel, selection, types, ntypes);
2731 }
2732
2733 /**
2734 * spice_main_channel_clipboard_selection_grab:
2735 * @channel: a #SpiceMainChannel
2736 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2737 * @types: an array of #VD_AGENT_CLIPBOARD types available in the clipboard
2738 * @ntypes: the number of @types
2739 *
2740 * Grab the guest clipboard, with #VD_AGENT_CLIPBOARD @types.
2741 *
2742 * Since: 0.35
2743 **/
spice_main_channel_clipboard_selection_grab(SpiceMainChannel * channel,guint selection,guint32 * types,int ntypes)2744 void spice_main_channel_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
2745 guint32 *types, int ntypes)
2746 {
2747 g_return_if_fail(channel != NULL);
2748 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2749
2750 agent_clipboard_grab(channel, selection, types, ntypes);
2751 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
2752 }
2753
2754 /**
2755 * spice_main_clipboard_release:
2756 * @channel: a #SpiceMainChannel
2757 *
2758 * Release the clipboard (for example, when the client loses the
2759 * clipboard grab): Inform the guest no clipboard data is available.
2760 *
2761 * Deprecated: 0.6: use spice_main_channel_clipboard_selection_release() instead.
2762 **/
spice_main_clipboard_release(SpiceMainChannel * channel)2763 void spice_main_clipboard_release(SpiceMainChannel *channel)
2764 {
2765 spice_main_channel_clipboard_selection_release(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD);
2766 }
2767
2768 /**
2769 * spice_main_clipboard_selection_release:
2770 * @channel: a #SpiceMainChannel
2771 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2772 *
2773 * Release the clipboard (for example, when the client loses the
2774 * clipboard grab): Inform the guest no clipboard data is available.
2775 *
2776 * Since: 0.6
2777 * Deprecated: 0.35: use spice_main_channel_clipboard_selection_release() instead.
2778 **/
spice_main_clipboard_selection_release(SpiceMainChannel * channel,guint selection)2779 void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint selection)
2780 {
2781 spice_main_channel_clipboard_selection_release(channel, selection);
2782 }
2783
2784 /**
2785 * spice_main_channel_clipboard_selection_release:
2786 * @channel: a #SpiceMainChannel
2787 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2788 *
2789 * Release the clipboard (for example, when the client loses the
2790 * clipboard grab): Inform the guest no clipboard data is available.
2791 *
2792 * Since: 0.35
2793 **/
spice_main_channel_clipboard_selection_release(SpiceMainChannel * channel,guint selection)2794 void spice_main_channel_clipboard_selection_release(SpiceMainChannel *channel, guint selection)
2795 {
2796 g_return_if_fail(channel != NULL);
2797 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2798
2799 SpiceMainChannelPrivate *c = channel->priv;
2800
2801 if (!c->agent_connected)
2802 return;
2803
2804 agent_clipboard_release(channel, selection);
2805 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
2806 }
2807
2808 /**
2809 * spice_main_clipboard_notify:
2810 * @channel: a #SpiceMainChannel
2811 * @type: a #VD_AGENT_CLIPBOARD type
2812 * @data: clipboard data
2813 * @size: data length in bytes
2814 *
2815 * Send the clipboard data to the guest.
2816 *
2817 * Deprecated: 0.6: use spice_main_channel_clipboard_selection_notify() instead.
2818 **/
spice_main_clipboard_notify(SpiceMainChannel * channel,guint32 type,const guchar * data,size_t size)2819 void spice_main_clipboard_notify(SpiceMainChannel *channel,
2820 guint32 type, const guchar *data, size_t size)
2821 {
2822 spice_main_channel_clipboard_selection_notify(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
2823 type, data, size);
2824 }
2825
2826 /**
2827 * spice_main_clipboard_selection_notify:
2828 * @channel: a #SpiceMainChannel
2829 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2830 * @type: a #VD_AGENT_CLIPBOARD type
2831 * @data: clipboard data
2832 * @size: data length in bytes
2833 *
2834 * Send the clipboard data to the guest.
2835 *
2836 * Since: 0.6
2837 * Deprecated: 0.35: use spice_main_channel_clipboard_selection_notify() instead.
2838 **/
spice_main_clipboard_selection_notify(SpiceMainChannel * channel,guint selection,guint32 type,const guchar * data,size_t size)2839 void spice_main_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,
2840 guint32 type, const guchar *data, size_t size)
2841 {
2842 spice_main_channel_clipboard_selection_notify(channel, selection, type, data, size);
2843 }
2844
2845 /**
2846 * spice_main_channel_clipboard_selection_notify:
2847 * @channel: a #SpiceMainChannel
2848 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2849 * @type: a #VD_AGENT_CLIPBOARD type
2850 * @data: clipboard data
2851 * @size: data length in bytes
2852 *
2853 * Send the clipboard data to the guest.
2854 *
2855 * Since: 0.35
2856 **/
spice_main_channel_clipboard_selection_notify(SpiceMainChannel * channel,guint selection,guint32 type,const guchar * data,size_t size)2857 void spice_main_channel_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,
2858 guint32 type, const guchar *data, size_t size)
2859 {
2860 g_return_if_fail(channel != NULL);
2861 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2862
2863 agent_clipboard_notify(channel, selection, type, data, size);
2864 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
2865 }
2866
2867 /**
2868 * spice_main_clipboard_request:
2869 * @channel: a #SpiceMainChannel
2870 * @type: a #VD_AGENT_CLIPBOARD type
2871 *
2872 * Request clipboard data of @type from the guest. The reply is sent
2873 * through the #SpiceMainChannel::main-clipboard signal.
2874 *
2875 * Deprecated: 0.6: use spice_main_channel_clipboard_selection_request() instead.
2876 **/
spice_main_clipboard_request(SpiceMainChannel * channel,guint32 type)2877 void spice_main_clipboard_request(SpiceMainChannel *channel, guint32 type)
2878 {
2879 spice_main_channel_clipboard_selection_request(channel, VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD,
2880 type);
2881 }
2882
2883 /**
2884 * spice_main_clipboard_selection_request:
2885 * @channel: a #SpiceMainChannel
2886 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2887 * @type: a #VD_AGENT_CLIPBOARD type
2888 *
2889 * Request clipboard data of @type from the guest. The reply is sent
2890 * through the #SpiceMainChannel::main-clipboard-selection signal.
2891 *
2892 * Since: 0.6
2893 * Deprecated: 0.35: use spice_main_channel_clipboard_selection_request() instead.
2894 **/
spice_main_clipboard_selection_request(SpiceMainChannel * channel,guint selection,guint32 type)2895 void spice_main_clipboard_selection_request(SpiceMainChannel *channel, guint selection, guint32 type)
2896 {
2897 spice_main_channel_clipboard_selection_request(channel, selection, type);
2898 }
2899
2900 /**
2901 * spice_main_channel_clipboard_selection_request:
2902 * @channel: a #SpiceMainChannel
2903 * @selection: one of the clipboard #VD_AGENT_CLIPBOARD_SELECTION_*
2904 * @type: a #VD_AGENT_CLIPBOARD type
2905 *
2906 * Request clipboard data of @type from the guest. The reply is sent
2907 * through the #SpiceMainChannel::main-clipboard-selection signal.
2908 *
2909 * Since: 0.35
2910 **/
spice_main_channel_clipboard_selection_request(SpiceMainChannel * channel,guint selection,guint32 type)2911 void spice_main_channel_clipboard_selection_request(SpiceMainChannel *channel, guint selection,
2912 guint32 type)
2913 {
2914 g_return_if_fail(channel != NULL);
2915 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2916
2917 agent_clipboard_request(channel, selection, type);
2918 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
2919 }
2920
2921 /**
2922 * spice_main_update_display_enabled:
2923 * @channel: a #SpiceMainChannel
2924 * @id: display ID (if -1: set all displays)
2925 * @enabled: wether display @id is enabled
2926 * @update: if %TRUE, update guest display state after 1sec.
2927 *
2928 * When sending monitor configuration to agent guest, if @enabled is %FALSE,
2929 * don't set display @id, which the agent translates to disabling the display
2930 * id. If @enabled is %TRUE, the monitor will be included in the next monitor
2931 * update. Note: this will take effect next time the monitor configuration is
2932 * sent.
2933 *
2934 * If @update is %FALSE, no server update will be triggered by this call, but
2935 * the value will be saved and used in the next configuration update.
2936 *
2937 * Since: 0.30
2938 * Deprecated: 0.35: use spice_main_channel_update_display_enabled() instead.
2939 **/
spice_main_update_display_enabled(SpiceMainChannel * channel,int id,gboolean enabled,gboolean update)2940 void spice_main_update_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled,
2941 gboolean update)
2942 {
2943 spice_main_channel_update_display_enabled(channel, id, enabled, update);
2944 }
2945
2946 /**
2947 * spice_main_channel_update_display_enabled:
2948 * @channel: a #SpiceMainChannel
2949 * @id: display ID (if -1: set all displays)
2950 * @enabled: wether display @id is enabled
2951 * @update: if %TRUE, update guest display state after 1sec.
2952 *
2953 * When sending monitor configuration to agent guest, if @enabled is %FALSE,
2954 * don't set display @id, which the agent translates to disabling the display
2955 * id. If @enabled is %TRUE, the monitor will be included in the next monitor
2956 * update. Note: this will take effect next time the monitor configuration is
2957 * sent.
2958 *
2959 * If @update is %FALSE, no server update will be triggered by this call, but
2960 * the value will be saved and used in the next configuration update.
2961 *
2962 * Since: 0.35
2963 **/
spice_main_channel_update_display_enabled(SpiceMainChannel * channel,int id,gboolean enabled,gboolean update)2964 void spice_main_channel_update_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled,
2965 gboolean update)
2966 {
2967 SpiceDisplayState display_state = enabled ? DISPLAY_ENABLED : DISPLAY_DISABLED;
2968 g_return_if_fail(channel != NULL);
2969 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
2970 g_return_if_fail(id >= -1);
2971
2972 SpiceMainChannelPrivate *c = channel->priv;
2973
2974 if (id == -1) {
2975 gint i;
2976 for (i = 0; i < G_N_ELEMENTS(c->display); i++) {
2977 c->display[i].display_state = display_state;
2978 }
2979 } else {
2980 g_return_if_fail(id < G_N_ELEMENTS(c->display));
2981 if (c->display[id].display_state == display_state)
2982 return;
2983 c->display[id].display_state = display_state;
2984 }
2985
2986 if (update)
2987 update_display_timer(channel, 1);
2988 }
2989
2990 /**
2991 * spice_main_set_display_enabled:
2992 * @channel: a #SpiceMainChannel
2993 * @id: display ID (if -1: set all displays)
2994 * @enabled: wether display @id is enabled
2995 *
2996 * When sending monitor configuration to agent guest, don't set
2997 * display @id, which the agent translates to disabling the display
2998 * id. Note: this will take effect next time the monitor
2999 * configuration is sent.
3000 *
3001 * Since: 0.6
3002 * Deprecated: 0.35: use spice_main_channel_update_display_enabled() instead.
3003 **/
spice_main_set_display_enabled(SpiceMainChannel * channel,int id,gboolean enabled)3004 void spice_main_set_display_enabled(SpiceMainChannel *channel, int id, gboolean enabled)
3005 {
3006 spice_main_channel_update_display_enabled(channel, id, enabled, TRUE);
3007 }
3008
file_xfer_init_task_async_cb(GObject * obj,GAsyncResult * res,gpointer data)3009 static void file_xfer_init_task_async_cb(GObject *obj, GAsyncResult *res, gpointer data)
3010 {
3011 GFileInfo *info;
3012 SpiceFileTransferTask *xfer_task;
3013 SpiceMainChannel *channel;
3014 gchar *string;
3015 const gchar *basename;
3016 GKeyFile *keyfile;
3017 VDAgentFileXferStartMessage msg;
3018 guint64 file_size;
3019 gsize data_len;
3020 FileTransferOperation *xfer_op;
3021 GError *error = NULL;
3022
3023 xfer_task = SPICE_FILE_TRANSFER_TASK(obj);
3024
3025 info = spice_file_transfer_task_init_task_finish(xfer_task, res, &error);
3026 if (info == NULL)
3027 goto failed;
3028
3029 channel = spice_file_transfer_task_get_channel(xfer_task);
3030 basename = g_file_info_get_attribute_byte_string(info, G_FILE_ATTRIBUTE_STANDARD_NAME);
3031 file_size = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
3032
3033 xfer_op = data;
3034 xfer_op->stats.transfer_size += file_size;
3035
3036 keyfile = g_key_file_new();
3037 g_key_file_set_string(keyfile, "vdagent-file-xfer", "name", basename);
3038 g_key_file_set_uint64(keyfile, "vdagent-file-xfer", "size", file_size);
3039
3040 /* Save keyfile content to memory. TODO: more file attributions
3041 need to be sent to guest */
3042 string = g_key_file_to_data(keyfile, &data_len, &error);
3043 g_key_file_free(keyfile);
3044 if (error)
3045 goto failed;
3046
3047 /* Create file-xfer start message */
3048 msg.id = spice_file_transfer_task_get_id(xfer_task);
3049 agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_START,
3050 &msg, sizeof(msg),
3051 string, data_len + 1, NULL);
3052 g_free(string);
3053 spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE);
3054 g_object_unref(info);
3055 return;
3056
3057 failed:
3058 g_clear_object(&info);
3059 spice_file_transfer_task_completed(xfer_task, error);
3060 }
3061
file_transfer_operation_free(FileTransferOperation * xfer_op)3062 static void file_transfer_operation_free(FileTransferOperation *xfer_op)
3063 {
3064 g_return_if_fail(xfer_op != NULL);
3065
3066 if (xfer_op->stats.failed != 0) {
3067 GError *error = g_error_new(SPICE_CLIENT_ERROR,
3068 SPICE_CLIENT_ERROR_FAILED,
3069 "Transferring %u files: %u succeed, %u cancelled, %u failed",
3070 xfer_op->stats.num_files,
3071 xfer_op->stats.succeed,
3072 xfer_op->stats.cancelled,
3073 xfer_op->stats.failed);
3074 SPICE_DEBUG("Transfer failed (%p) %s", xfer_op, error->message);
3075 g_task_return_error(xfer_op->task, error);
3076 } else if (xfer_op->stats.cancelled != 0 && xfer_op->stats.succeed == 0) {
3077 GError *error = g_error_new(G_IO_ERROR,
3078 G_IO_ERROR_CANCELLED,
3079 "Transferring %u files: %u succeed, %u cancelled, %u failed",
3080 xfer_op->stats.num_files,
3081 xfer_op->stats.succeed,
3082 xfer_op->stats.cancelled,
3083 xfer_op->stats.failed);
3084 SPICE_DEBUG("Transfer cancelled (%p) %s", xfer_op, error->message);
3085 g_task_return_error(xfer_op->task, error);
3086 } else {
3087 SPICE_DEBUG("Transfer successful (%p)", xfer_op);
3088 g_task_return_boolean(xfer_op->task, TRUE);
3089 }
3090 g_object_unref(xfer_op->task);
3091 g_hash_table_unref(xfer_op->xfer_task);
3092
3093 spice_debug("Freeing file-transfer-operation %p", xfer_op);
3094 g_free(xfer_op);
3095 }
3096
spice_main_channel_reset_all_xfer_operations(SpiceMainChannel * channel)3097 static void spice_main_channel_reset_all_xfer_operations(SpiceMainChannel *channel)
3098 {
3099 GList *it, *keys;
3100
3101 /* Mark each of SpiceFileTransferTask as completed due error */
3102 keys = g_hash_table_get_keys(channel->priv->file_xfer_tasks);
3103 for (it = keys; it != NULL; it = it->next) {
3104 FileTransferOperation *xfer_op;
3105 SpiceFileTransferTask *xfer_task;
3106 GError *error;
3107
3108 xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, it->data);
3109 if (xfer_op == NULL)
3110 continue;
3111
3112 xfer_task = g_hash_table_lookup(xfer_op->xfer_task, it->data);
3113 if (xfer_task == NULL) {
3114 spice_warning("(reset-all) can't complete task %u - completed already?",
3115 GPOINTER_TO_UINT(it->data));
3116 continue;
3117 }
3118
3119 error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
3120 "Agent connection closed");
3121 spice_file_transfer_task_completed(xfer_task, error);
3122 }
3123 g_list_free(keys);
3124 }
3125
spice_main_channel_find_xfer_task_by_task_id(SpiceMainChannel * channel,guint32 task_id)3126 static SpiceFileTransferTask *spice_main_channel_find_xfer_task_by_task_id(SpiceMainChannel *channel,
3127 guint32 task_id)
3128 {
3129 FileTransferOperation *xfer_op;
3130
3131 xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
3132 g_return_val_if_fail(xfer_op != NULL, NULL);
3133 return g_hash_table_lookup(xfer_op->xfer_task, GUINT_TO_POINTER(task_id));
3134 }
3135
file_transfer_operation_task_finished(SpiceFileTransferTask * xfer_task,GError * error,gpointer userdata)3136 static void file_transfer_operation_task_finished(SpiceFileTransferTask *xfer_task,
3137 GError *error,
3138 gpointer userdata)
3139 {
3140 SpiceMainChannel *channel;
3141 FileTransferOperation *xfer_op;
3142 guint32 task_id;
3143
3144 channel = spice_file_transfer_task_get_channel(xfer_task);
3145 g_return_if_fail(channel != NULL);
3146 task_id = spice_file_transfer_task_get_id(xfer_task);
3147 g_return_if_fail(task_id != 0);
3148
3149 if (error) {
3150 VDAgentFileXferStatusMessage msg;
3151 msg.id = task_id;
3152 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
3153 msg.result = VD_AGENT_FILE_XFER_STATUS_CANCELLED;
3154 } else {
3155 msg.result = VD_AGENT_FILE_XFER_STATUS_ERROR;
3156 }
3157 agent_msg_queue_many(channel, VD_AGENT_FILE_XFER_STATUS,
3158 &msg, sizeof(msg), NULL);
3159 }
3160
3161 xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
3162 if (xfer_op == NULL) {
3163 /* Likely the operation has ended before the remove-task was called. One
3164 * situation that this can easily happen is if the agent is disconnected
3165 * while there are pending files. */
3166 return;
3167 }
3168
3169 if (error) {
3170 /* On error or cancellation of a SpiceFileTransferTask we remove the
3171 * remaining bytes from transfer-size in order to keep the coherence of
3172 * the information we provide to the user (total-sent and transfer-size)
3173 * in the progress-callback */
3174 guint64 file_size = spice_file_transfer_task_get_total_bytes(xfer_task);
3175 guint64 bytes_read = spice_file_transfer_task_get_transferred_bytes(xfer_task);
3176 xfer_op->stats.transfer_size -= (file_size - bytes_read);
3177 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
3178 xfer_op->stats.cancelled++;
3179 } else {
3180 xfer_op->stats.failed++;
3181 }
3182 } else {
3183 xfer_op->stats.succeed++;
3184 }
3185
3186 /* Remove and free SpiceFileTransferTask */
3187 g_hash_table_remove(xfer_op->xfer_task, GUINT_TO_POINTER(task_id));
3188
3189 /* Keep file_xfer_tasks up to date. If no more elements, operation is over */
3190 g_hash_table_remove(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
3191
3192 /* No more pending operations */
3193 if (g_hash_table_size(xfer_op->xfer_task) == 0)
3194 file_transfer_operation_free(xfer_op);
3195 }
3196
file_transfer_operation_send_progress(SpiceFileTransferTask * xfer_task)3197 static void file_transfer_operation_send_progress(SpiceFileTransferTask *xfer_task)
3198 {
3199 FileTransferOperation *xfer_op;
3200 SpiceMainChannel *channel;
3201 guint32 task_id;
3202
3203 channel = spice_file_transfer_task_get_channel(xfer_task);
3204 task_id = spice_file_transfer_task_get_id(xfer_task);
3205 xfer_op = g_hash_table_lookup(channel->priv->file_xfer_tasks, GUINT_TO_POINTER(task_id));
3206 g_return_if_fail(xfer_op != NULL);
3207
3208 if (xfer_op->progress_callback)
3209 xfer_op->progress_callback(xfer_op->stats.total_sent,
3210 xfer_op->stats.transfer_size,
3211 xfer_op->progress_callback_data);
3212 }
3213
3214 /**
3215 * spice_main_file_copy_async:
3216 * @channel: a #SpiceMainChannel
3217 * @sources: (array zero-terminated=1): a %NULL-terminated array of #GFile objects to be transferred
3218 * @flags: set of #GFileCopyFlags
3219 * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
3220 * @progress_callback: (allow-none) (scope call): function to callback with
3221 * progress information, or %NULL if progress information is not needed
3222 * @progress_callback_data: (closure): user data to pass to @progress_callback
3223 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
3224 * @user_data: the data to pass to callback function
3225 *
3226 * See: spice_main_channel_file_copy_async()
3227 *
3228 * Deprecated: 0.35: use spice_main_channel_file_copy_async() instead.
3229 **/
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)3230 void spice_main_file_copy_async(SpiceMainChannel *channel,
3231 GFile **sources,
3232 GFileCopyFlags flags,
3233 GCancellable *cancellable,
3234 GFileProgressCallback progress_callback,
3235 gpointer progress_callback_data,
3236 GAsyncReadyCallback callback,
3237 gpointer user_data)
3238 {
3239 spice_main_channel_file_copy_async(channel, sources, flags, cancellable, progress_callback,
3240 progress_callback_data, callback, user_data);
3241 }
3242
3243 /**
3244 * spice_main_channel_file_copy_async:
3245 * @channel: a #SpiceMainChannel
3246 * @sources: (array zero-terminated=1): a %NULL-terminated array of #GFile objects to be transferred
3247 * @flags: set of #GFileCopyFlags
3248 * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
3249 * @progress_callback: (allow-none) (scope call): function to callback with
3250 * progress information, or %NULL if progress information is not needed
3251 * @progress_callback_data: (closure): user data to pass to @progress_callback
3252 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
3253 * @user_data: the data to pass to callback function
3254 *
3255 * Copies the file @sources to guest
3256 *
3257 * If @cancellable is not %NULL, then the operation can be cancelled by
3258 * triggering the cancellable object from another thread. If the operation
3259 * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
3260 *
3261 * If @progress_callback is not %NULL, then the operation can be monitored by
3262 * setting this to a #GFileProgressCallback function. @progress_callback_data
3263 * will be passed to this function. It is guaranteed that this callback will
3264 * be called after all data has been transferred with the total number of bytes
3265 * copied during the operation. Note that before release 0.31, progress_callback
3266 * was broken since it only provided status for a single file transfer, but did
3267 * not provide a way to determine which file it referred to. In release 0.31,
3268 * this behavior was changed so that progress_callback provides the status of
3269 * all ongoing file transfers. If you need to monitor the status of individual
3270 * files, please connect to the #SpiceMainChannel::new-file-transfer signal.
3271 *
3272 * When the operation is finished, callback will be called. You can then call
3273 * spice_main_file_copy_finish() to get the result of the operation. Note that
3274 * before release 0.33 the callback was called for each file in multiple file
3275 * transfer. This behavior was changed for the same reason as the
3276 * progress_callback (above). If you need to monitor the ending of individual
3277 * files, you can connect to "finished" signal from each SpiceFileTransferTask.
3278 *
3279 * Since: 0.35
3280 **/
spice_main_channel_file_copy_async(SpiceMainChannel * channel,GFile ** sources,GFileCopyFlags flags,GCancellable * cancellable,GFileProgressCallback progress_callback,gpointer progress_callback_data,GAsyncReadyCallback callback,gpointer user_data)3281 void spice_main_channel_file_copy_async(SpiceMainChannel *channel,
3282 GFile **sources,
3283 GFileCopyFlags flags,
3284 GCancellable *cancellable,
3285 GFileProgressCallback progress_callback,
3286 gpointer progress_callback_data,
3287 GAsyncReadyCallback callback,
3288 gpointer user_data)
3289 {
3290 SpiceMainChannelPrivate *c;
3291 FileTransferOperation *xfer_op;
3292 GError *error = NULL;
3293 GList *it, *keys;
3294
3295 g_return_if_fail(channel != NULL);
3296 g_return_if_fail(SPICE_IS_MAIN_CHANNEL(channel));
3297 g_return_if_fail(sources != NULL);
3298
3299 c = channel->priv;
3300 if (!c->agent_connected) {
3301 error = g_error_new(SPICE_CLIENT_ERROR,
3302 SPICE_CLIENT_ERROR_FAILED,
3303 "The agent is not connected");
3304 } else if (test_agent_cap(channel, VD_AGENT_CAP_FILE_XFER_DISABLED)) {
3305 error = g_error_new(SPICE_CLIENT_ERROR,
3306 SPICE_CLIENT_ERROR_FAILED,
3307 _("The file transfer is disabled"));
3308 }
3309
3310 xfer_op = g_new0(FileTransferOperation, 1);
3311 xfer_op->channel = channel;
3312 xfer_op->progress_callback = progress_callback;
3313 xfer_op->progress_callback_data = progress_callback_data;
3314 xfer_op->task = g_task_new(channel, cancellable, callback, user_data);
3315 xfer_op->xfer_task = spice_file_transfer_task_create_tasks(sources,
3316 channel,
3317 flags,
3318 cancellable);
3319 xfer_op->stats.num_files = g_hash_table_size(xfer_op->xfer_task);
3320 keys = g_hash_table_get_keys(xfer_op->xfer_task);
3321 for (it = keys; it != NULL; it = it->next) {
3322 guint32 task_id;
3323 SpiceFileTransferTask *xfer_task = g_hash_table_lookup(xfer_op->xfer_task, it->data);
3324
3325 task_id = spice_file_transfer_task_get_id(xfer_task);
3326
3327 SPICE_DEBUG("Insert a xfer task:%u to task list", task_id);
3328
3329 g_hash_table_insert(c->file_xfer_tasks, it->data, xfer_op);
3330 g_signal_connect(xfer_task, "finished", G_CALLBACK(file_transfer_operation_task_finished), NULL);
3331 g_signal_emit(channel, signals[SPICE_MAIN_NEW_FILE_TRANSFER], 0, xfer_task);
3332
3333 if (error == NULL) {
3334 spice_file_transfer_task_init_task_async(xfer_task,
3335 file_xfer_init_task_async_cb,
3336 xfer_op);
3337 } else {
3338 spice_file_transfer_task_completed(xfer_task, g_error_copy(error));
3339 }
3340 }
3341 g_list_free(keys);
3342 g_clear_error(&error);
3343 }
3344
3345 /**
3346 * spice_main_file_copy_finish:
3347 * @channel: a #SpiceMainChannel
3348 * @result: a #GAsyncResult.
3349 * @error: a #GError, or %NULL
3350 *
3351 * Finishes copying the file started with
3352 * spice_main_file_copy_async().
3353 *
3354 * Returns: a %TRUE on success, %FALSE on error.
3355 **/
spice_main_file_copy_finish(SpiceMainChannel * channel,GAsyncResult * result,GError ** error)3356 gboolean spice_main_file_copy_finish(SpiceMainChannel *channel,
3357 GAsyncResult *result,
3358 GError **error)
3359 {
3360 return spice_main_channel_file_copy_finish(channel, result, error);
3361 }
3362
3363 /**
3364 * spice_main_channel_file_copy_finish:
3365 * @channel: a #SpiceMainChannel
3366 * @result: a #GAsyncResult.
3367 * @error: a #GError, or %NULL
3368 *
3369 * Finishes copying the file started with
3370 * spice_main_file_copy_async().
3371 *
3372 * Returns: a %TRUE on success, %FALSE on error.
3373 **/
spice_main_channel_file_copy_finish(SpiceMainChannel * channel,GAsyncResult * result,GError ** error)3374 gboolean spice_main_channel_file_copy_finish(SpiceMainChannel *channel,
3375 GAsyncResult *result,
3376 GError **error)
3377 {
3378 GTask *task = G_TASK(result);
3379
3380 g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL(channel), FALSE);
3381 g_return_val_if_fail(g_task_is_valid(task, channel), FALSE);
3382
3383 return g_task_propagate_boolean(task, error);
3384 }
3385