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