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 first, on Windows will override winsock definitions */
21 #include <gio/gnetworking.h>
22 #include <gio/gio.h>
23 #include <glib.h>
24 #ifdef G_OS_UNIX
25 #include <gio/gunixsocketaddress.h>
26 #endif
27 
28 #include "spice-client.h"
29 #include "spice-common.h"
30 #include "spice-channel-priv.h"
31 #include "spice-util-priv.h"
32 #include "spice-session-priv.h"
33 #include "gio-coroutine.h"
34 #include "spice-uri-priv.h"
35 #include "channel-playback-priv.h"
36 #include "spice-audio-priv.h"
37 
38 #if !defined(SOL_TCP) && defined(IPPROTO_TCP)
39 #define SOL_TCP IPPROTO_TCP
40 #endif
41 #if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) && defined(__APPLE__)
42 #define TCP_KEEPIDLE TCP_KEEPALIVE
43 #endif
44 
45 #define IMAGES_CACHE_SIZE_DEFAULT (1024 * 1024 * 80)
46 #define MIN_GLZ_WINDOW_SIZE_DEFAULT (1024 * 1024 * 12)
47 #define MAX_GLZ_WINDOW_SIZE_DEFAULT MIN((LZ_MAX_WINDOW_SIZE * 4), 1024 * 1024 * 64)
48 
49 struct _SpiceSessionPrivate {
50     char              *host;
51     char              *unix_path;
52     char              *port;
53     char              *tls_port;
54     char              *username;
55     char              *password;
56     char              *ca_file;
57     char              *ciphers;
58     GByteArray        *pubkey;
59     GByteArray        *ca;
60     char              *cert_subject;
61     guint             verify;
62     gboolean          read_only;
63     SpiceURI          *proxy;
64     gchar             *shared_dir;
65     gboolean          share_dir_ro;
66 
67     /* whether to enable audio */
68     gboolean          audio;
69 
70     /* whether to enable smartcard event forwarding to the server */
71     gboolean          smartcard;
72 
73     /* whether to enable GL scanout */
74     gboolean          gl_scanout;
75 
76     /* list of certificates to use for the software smartcard reader if
77      * enabled. For now, it has to contain exactly 3 certificates for
78      * the software reader to be functional
79      */
80     GStrv             smartcard_certificates;
81 
82     /* path to the local certificate database to use to lookup the
83      * certificates stored in 'certificates'. If NULL, libcacard will
84      * fallback to using a default database.
85      */
86     char *            smartcard_db;
87 
88     /* whether to enable USB redirection */
89     gboolean          usbredir;
90 
91     /* Set when a usbredir channel has requested the keyboard grab to be
92        temporarily released (because it is going to invoke policykit) */
93     gboolean          inhibit_keyboard_grab;
94 
95     GStrv             disable_effects;
96     GStrv             secure_channels;
97 
98     int               connection_id;
99     int               protocol;
100     SpiceChannel      *cmain; /* weak reference */
101     GList             *channels;
102     guint             channels_destroying;
103     gboolean          client_provided_sockets;
104     guint64           mm_time_offset;
105     SpiceSession      *migration;
106     GList             *migration_left;
107     SpiceSessionMigration migration_state;
108     gboolean          full_migration; /* seamless migration indicator */
109     guint             disconnecting;
110     gboolean          migrate_wait_init;
111     guint             after_main_init;
112     gboolean          for_migration;
113 
114     display_cache     *images;
115     display_cache     *palettes;
116     SpiceGlzDecoderWindow *glz_window;
117     int               images_cache_size;
118     int               glz_window_size;
119     uint32_t          pci_ram_size;
120     uint32_t          n_display_channels;
121     guint8            uuid[16];
122     gchar             *name;
123     SpiceImageCompression preferred_compression;
124 
125     /* associated objects */
126     SpiceAudio        *audio_manager;
127     SpiceUsbDeviceManager *usb_manager;
128     SpicePlaybackChannel *playback_channel;
129     PhodavServer      *webdav;
130 };
131 
132 
133 /**
134  * SECTION:spice-session
135  * @short_description: handles connection details, and active channels
136  * @title: Spice Session
137  * @section_id:
138  * @see_also: #SpiceChannel, and the GTK widget #SpiceDisplay
139  * @stability: Stable
140  * @include: spice-client.h
141  *
142  * The #SpiceSession class handles all the #SpiceChannel connections.
143  * It's also the class that contains connections informations, such as
144  * #SpiceSession:host and #SpiceSession:port.
145  *
146  * You can simply set the property #SpiceSession:uri to something like
147  * "spice://127.0.0.1?port=5930" to configure your connection details.
148  *
149  * You may want to connect to #SpiceSession::channel-new signal, to be
150  * informed of the availability of channels and to interact with
151  * them.
152  *
153  * For example, when the #SpiceInputsChannel is available and get the
154  * event #SPICE_CHANNEL_OPENED, you can send key events with
155  * spice_inputs_key_press(). When the #SpiceMainChannel is available,
156  * you can start sharing the clipboard...  .
157  *
158  *
159  * Once #SpiceSession properties set, you can call
160  * spice_session_connect() to start connecting and communicating with
161  * a Spice server.
162  */
163 
164 G_DEFINE_TYPE_WITH_PRIVATE(SpiceSession, spice_session, G_TYPE_OBJECT)
165 
166 /* Properties */
167 enum {
168     PROP_0,
169     PROP_HOST,
170     PROP_PORT,
171     PROP_TLS_PORT,
172     PROP_PASSWORD,
173     PROP_CA_FILE,
174     PROP_CIPHERS,
175     PROP_IPV4,
176     PROP_IPV6,
177     PROP_PROTOCOL,
178     PROP_URI,
179     PROP_CLIENT_SOCKETS,
180     PROP_PUBKEY,
181     PROP_CERT_SUBJECT,
182     PROP_VERIFY,
183     PROP_MIGRATION_STATE,
184     PROP_AUDIO,
185     PROP_SMARTCARD,
186     PROP_SMARTCARD_CERTIFICATES,
187     PROP_SMARTCARD_DB,
188     PROP_USBREDIR,
189     PROP_INHIBIT_KEYBOARD_GRAB,
190     PROP_DISABLE_EFFECTS,
191     PROP_COLOR_DEPTH,
192     PROP_READ_ONLY,
193     PROP_CACHE_SIZE,
194     PROP_GLZ_WINDOW_SIZE,
195     PROP_UUID,
196     PROP_NAME,
197     PROP_CA,
198     PROP_PROXY,
199     PROP_SECURE_CHANNELS,
200     PROP_SHARED_DIR,
201     PROP_SHARE_DIR_RO,
202     PROP_USERNAME,
203     PROP_UNIX_PATH,
204     PROP_PREF_COMPRESSION,
205     PROP_GL_SCANOUT,
206 };
207 
208 /* signals */
209 enum {
210     SPICE_SESSION_CHANNEL_NEW,
211     SPICE_SESSION_CHANNEL_DESTROY,
212     SPICE_SESSION_MM_TIME_RESET,
213     SPICE_SESSION_DISCONNECTED,
214     SPICE_SESSION_LAST_SIGNAL,
215 };
216 
217 /* Register SpiceImageCompress */
218 #define SPICE_TYPE_IMAGE_COMPRESSION spice_image_compress_get_type()
219 GType spice_image_compress_get_type (void);
220 
221 static const GEnumValue _spice_image_compress_values[] = {
222   { SPICE_IMAGE_COMPRESSION_INVALID, "SPICE_IMAGE_COMPRESSION_INVALID", "invalid" },
223   { SPICE_IMAGE_COMPRESSION_OFF, "SPICE_IMAGE_COMPRESSION_OFF", "off" },
224   { SPICE_IMAGE_COMPRESSION_AUTO_GLZ, "SPICE_IMAGE_COMPRESSION_AUTO_GLZ", "auto-glz" },
225   { SPICE_IMAGE_COMPRESSION_AUTO_LZ, "SPICE_IMAGE_COMPRESSION_AUTO_LZ", "auto-lz" },
226   { SPICE_IMAGE_COMPRESSION_QUIC, "SPICE_IMAGE_COMPRESSION_QUIC", "quic" },
227   { SPICE_IMAGE_COMPRESSION_GLZ, "SPICE_IMAGE_COMPRESSION_GLZ", "glz" },
228   { SPICE_IMAGE_COMPRESSION_LZ, "SPICE_IMAGE_COMPRESSION_LZ", "lz" },
229   { SPICE_IMAGE_COMPRESSION_LZ4, "SPICE_IMAGE_COMPRESSION_LZ4", "lz4" },
230   { 0, NULL, NULL }
231 };
232 
233 G_STATIC_ASSERT(G_N_ELEMENTS(_spice_image_compress_values) == SPICE_IMAGE_COMPRESSION_ENUM_END + 1);
234 
235 static const gchar* spice_session_get_shared_dir(SpiceSession *session);
236 static void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir);
237 
238 GType
spice_image_compress_get_type(void)239 spice_image_compress_get_type (void)
240 {
241   static GType type = 0;
242   static volatile gsize type_volatile = 0;
243 
244   if (g_once_init_enter(&type_volatile)) {
245     type = g_enum_register_static ("SpiceImageCompress", _spice_image_compress_values);
246     g_once_init_leave(&type_volatile, type);
247   }
248 
249   return type;
250 }
251 
252 static guint signals[SPICE_SESSION_LAST_SIGNAL];
253 
254 static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel);
255 
update_proxy(SpiceSession * self,const gchar * str)256 static void update_proxy(SpiceSession *self, const gchar *str)
257 {
258     SpiceSessionPrivate *s = self->priv;
259     SpiceURI *proxy = NULL;
260     GError *error = NULL;
261 
262     if (str == NULL)
263         str = g_getenv("SPICE_PROXY");
264     if (str == NULL || *str == 0) {
265         g_clear_object(&s->proxy);
266         return;
267     }
268 
269     proxy = spice_uri_new();
270     if (!spice_uri_parse(proxy, str, &error))
271         g_clear_object(&proxy);
272     if (error) {
273         g_warning("%s", error->message);
274         g_clear_error(&error);
275     }
276 
277     if (proxy != NULL) {
278         g_clear_object(&s->proxy);
279         s->proxy = proxy;
280     }
281 }
282 
spice_session_init(SpiceSession * session)283 static void spice_session_init(SpiceSession *session)
284 {
285     SpiceSessionPrivate *s;
286     gchar *channels;
287 
288     SPICE_DEBUG("New session (compiled from package " PACKAGE_STRING ")");
289     s = session->priv = spice_session_get_instance_private(session);
290 
291     channels = spice_channel_supported_string();
292     SPICE_DEBUG("Supported channels: %s", channels);
293     g_free(channels);
294 
295     s->images = cache_image_new((GDestroyNotify)pixman_image_unref);
296     s->glz_window = glz_decoder_window_new();
297     update_proxy(session, NULL);
298 }
299 
300 static void
session_disconnect(SpiceSession * self,gboolean keep_main)301 session_disconnect(SpiceSession *self, gboolean keep_main)
302 {
303     SpiceSessionPrivate *s;
304 
305     s = self->priv;
306 
307     for (GList *l = s->channels; l != NULL; ) {
308         SpiceChannel *channel = l->data;
309         l = l->next;
310 
311         if (keep_main && channel == s->cmain) {
312             spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
313         } else {
314             spice_session_channel_destroy(self, channel);
315         }
316     }
317 
318     s->connection_id = 0;
319 
320     g_clear_pointer(&s->name, g_free);
321     memset(s->uuid, 0, sizeof(s->uuid));
322 
323     spice_session_abort_migration(self);
324 }
325 
326 static void
spice_session_dispose(GObject * gobject)327 spice_session_dispose(GObject *gobject)
328 {
329     SpiceSession *session = SPICE_SESSION(gobject);
330     SpiceSessionPrivate *s = session->priv;
331 
332     SPICE_DEBUG("session dispose");
333 
334     session_disconnect(session, FALSE);
335 
336     g_warn_if_fail(s->migration == NULL);
337     g_warn_if_fail(s->migration_left == NULL);
338     g_warn_if_fail(s->after_main_init == 0);
339     g_warn_if_fail(s->disconnecting == 0);
340     g_warn_if_fail(s->channels_destroying == 0);
341     g_warn_if_fail(s->channels == NULL);
342 
343     g_clear_object(&s->audio_manager);
344     g_clear_object(&s->usb_manager);
345     g_clear_object(&s->proxy);
346     g_clear_object(&s->webdav);
347 
348     /* Chain up to the parent class */
349     if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
350         G_OBJECT_CLASS(spice_session_parent_class)->dispose(gobject);
351 }
352 
353 static void
spice_session_finalize(GObject * gobject)354 spice_session_finalize(GObject *gobject)
355 {
356     SpiceSession *session = SPICE_SESSION(gobject);
357     SpiceSessionPrivate *s = session->priv;
358 
359     /* release stuff */
360     g_free(s->unix_path);
361     g_free(s->host);
362     g_free(s->port);
363     g_free(s->tls_port);
364     g_free(s->username);
365     g_free(s->password);
366     g_free(s->ca_file);
367     g_free(s->ciphers);
368     g_free(s->cert_subject);
369     g_strfreev(s->smartcard_certificates);
370     g_free(s->smartcard_db);
371     g_strfreev(s->disable_effects);
372     g_strfreev(s->secure_channels);
373     g_free(s->shared_dir);
374 
375     g_clear_pointer(&s->images, cache_free);
376     glz_decoder_window_destroy(s->glz_window);
377 
378     g_clear_pointer(&s->pubkey, g_byte_array_unref);
379     g_clear_pointer(&s->ca, g_byte_array_unref);
380 
381     /* Chain up to the parent class */
382     if (G_OBJECT_CLASS(spice_session_parent_class)->finalize)
383         G_OBJECT_CLASS(spice_session_parent_class)->finalize(gobject);
384 }
385 
386 #define URI_SCHEME_SPICE "spice://"
387 #define URI_SCHEME_SPICE_UNIX "spice+unix://"
388 #define URI_SCHEME_SPICE_TLS "spice+tls://"
389 #define URI_QUERY_START ";?"
390 #define URI_QUERY_SEP   ";&"
391 
spice_uri_create(SpiceSession * session)392 static gchar* spice_uri_create(SpiceSession *session)
393 {
394     SpiceSessionPrivate *s = session->priv;
395 
396     if (s->unix_path != NULL) {
397         return g_strdup_printf(URI_SCHEME_SPICE_UNIX "%s", s->unix_path);
398     } else if (s->host != NULL) {
399         const char *port, *scheme;
400         g_return_val_if_fail(s->port != NULL || s->tls_port != NULL, NULL);
401 
402         if (s->tls_port && s->port) {
403             /* both set, use spice://foo?port=4390&tls-port= form */
404             return g_strdup_printf(URI_SCHEME_SPICE "%s?port=%s&tls-port=%s",
405                                    s->host, s->port, s->tls_port);
406         }
407 
408         /* one set, use spice://foo:4390 or spice+tls://.. form */
409         if (s->tls_port) {
410             scheme = URI_SCHEME_SPICE_TLS;
411             port = s->tls_port;
412         } else {
413             scheme = URI_SCHEME_SPICE;
414             port = s->port;
415         }
416         return g_strdup_printf("%s%s:%s", scheme, s->host, port);
417     }
418 
419     g_return_val_if_reached(NULL);
420 }
421 
spice_parse_uri(SpiceSession * session,const char * original_uri)422 static int spice_parse_uri(SpiceSession *session, const char *original_uri)
423 {
424     SpiceSessionPrivate *s = session->priv;
425     gchar *host = NULL, *port = NULL, *tls_port = NULL, *uri = NULL, *username = NULL, *password = NULL;
426     gchar *path = NULL;
427     gchar *authority = NULL;
428     gchar *query = NULL;
429     gchar *tmp = NULL;
430     bool tls_scheme = false;
431 
432     g_return_val_if_fail(original_uri != NULL, -1);
433 
434     uri = g_strdup(original_uri);
435 
436     if (g_str_has_prefix(uri, URI_SCHEME_SPICE_UNIX)) {
437         path = uri + strlen(URI_SCHEME_SPICE_UNIX);
438         goto end;
439     }
440 
441     /* Break up the URI into its various parts, scheme, authority,
442      * path (ignored) and query
443      */
444     if (g_str_has_prefix(uri, URI_SCHEME_SPICE)) {
445         authority = uri + strlen(URI_SCHEME_SPICE);
446     } else if (g_str_has_prefix(uri, URI_SCHEME_SPICE_TLS)) {
447         authority = uri + strlen(URI_SCHEME_SPICE_TLS);
448         tls_scheme = true;
449     } else {
450         g_warning("Expected a URI scheme of '%s' in URI '%s'",
451                   URI_SCHEME_SPICE, uri);
452         goto fail;
453     }
454 
455     tmp = strchr(authority, '@');
456     if (tmp) {
457         tmp[0] = '\0';
458         username = g_uri_unescape_string(authority, NULL);
459         authority = ++tmp;
460         tmp = NULL;
461     }
462 
463     path = strchr(authority, '/');
464     if (path) {
465         path[0] = '\0';
466         path++;
467     }
468 
469     if (path) {
470         size_t prefix = strcspn(path, URI_QUERY_START);
471         query = path + prefix;
472     } else {
473         size_t prefix = strcspn(authority, URI_QUERY_START);
474         query = authority + prefix;
475     }
476 
477     if (query && query[0]) {
478         query[0] = '\0';
479         query++;
480     }
481 
482     /* Now process the individual parts */
483 
484     if (authority[0] == '[') {
485         tmp = strchr(authority, ']');
486         if (!tmp) {
487             g_warning("Missing closing ']' in authority for URI '%s'", uri);
488             goto fail;
489         }
490         tmp[0] = '\0';
491         tmp++;
492         host = g_strdup_printf("[%s]", authority + 1);
493         if (tmp[0] == ':')
494             port = g_strdup(tmp + 1);
495     } else {
496         tmp = strchr(authority, ':');
497         if (tmp) {
498             *tmp = '\0';
499             tmp++;
500             port = g_strdup(tmp);
501         }
502         host = g_uri_unescape_string(authority, NULL);
503     }
504 
505     if (path && !(g_str_equal(path, "") ||
506                   g_str_equal(path, "/"))) {
507         g_warning("Unexpected path data '%s' for URI '%s'", path, uri);
508         /* don't fail, just ignore */
509     }
510     path = NULL;
511 
512     while (query && query[0] != '\0') {
513         gchar key[32], value[128];
514         gchar **target_key;
515 
516         int len;
517         if (sscanf(query, "%31[-a-zA-Z0-9]=%n", key, &len) != 1) {
518             g_warning("Failed to parse key in URI '%s'", query);
519             goto fail;
520         }
521 
522         query += len;
523         if (*query == '\0') {
524             SPICE_DEBUG("key '%s' without value", key);
525             break;
526         } else if (*query == ';' || *query == '&') {
527             /* another argument */
528             query++;
529             continue;
530         }
531 
532         if (sscanf(query, "%127[^;&]%n", value, &len) != 1) {
533             g_warning("Failed to parse value of key '%s' in URI '%s'", key, query);
534             goto fail;
535         }
536 
537         query += len;
538         if (*query)
539             query++;
540 
541         if (tls_scheme && (g_str_equal(key, "port") || g_str_equal(key, "tls-port"))) {
542             g_warning(URI_SCHEME_SPICE_TLS " scheme doesn't accept '%s'", key);
543             continue;
544         }
545 
546         target_key = NULL;
547         if (g_str_equal(key, "port")) {
548             target_key = &port;
549         } else if (g_str_equal(key, "tls-port")) {
550             target_key = &tls_port;
551         } else if (g_str_equal(key, "password")) {
552             target_key = &password;
553             g_warning("password may be visible in process listings");
554         } else {
555             g_warning("unknown key in spice URI parsing: '%s'", key);
556             goto fail;
557         }
558         if (target_key) {
559             if (*target_key) {
560                 g_warning("Double set of '%s' in URI '%s'", key, original_uri);
561                 goto fail;
562             }
563             *target_key = g_uri_unescape_string(value, NULL);
564         }
565     }
566 
567     if (port == NULL && tls_port == NULL) {
568         g_warning("Missing port or tls-port in spice URI '%s'", original_uri);
569         goto fail;
570     }
571 
572 end:
573     /* parsed ok -> apply */
574     g_free(s->unix_path);
575     g_free(s->host);
576     g_free(s->port);
577     g_free(s->tls_port);
578     g_free(s->username);
579     g_free(s->password);
580     s->unix_path = g_strdup(path);
581     g_free(uri);
582     s->host = host;
583     if (tls_scheme) {
584         s->tls_port = port;
585     } else {
586         s->port = port;
587         s->tls_port = tls_port;
588     }
589     s->username = username;
590     s->password = password;
591     return 0;
592 
593 fail:
594     g_free(uri);
595     g_free(host);
596     g_free(port);
597     g_free(tls_port);
598     g_free(username);
599     g_free(password);
600     return -1;
601 }
602 
spice_session_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)603 static void spice_session_get_property(GObject    *gobject,
604                                        guint       prop_id,
605                                        GValue     *value,
606                                        GParamSpec *pspec)
607 {
608     SpiceSession *session = SPICE_SESSION(gobject);
609     SpiceSessionPrivate *s = session->priv;
610 
611     switch (prop_id) {
612     case PROP_HOST:
613         g_value_set_string(value, s->host);
614 	break;
615     case PROP_UNIX_PATH:
616         g_value_set_string(value, s->unix_path);
617         break;
618     case PROP_PORT:
619         g_value_set_string(value, s->port);
620 	break;
621     case PROP_TLS_PORT:
622         g_value_set_string(value, s->tls_port);
623 	break;
624     case PROP_USERNAME:
625         g_value_set_string(value, s->username);
626 	break;
627     case PROP_PASSWORD:
628         g_value_set_string(value, s->password);
629 	break;
630     case PROP_CA_FILE:
631         g_value_set_string(value, s->ca_file);
632 	break;
633     case PROP_CIPHERS:
634         g_value_set_string(value, s->ciphers);
635 	break;
636     case PROP_PROTOCOL:
637         g_value_set_int(value, s->protocol);
638 	break;
639     case PROP_URI:
640         g_value_take_string(value, spice_uri_create(session));
641         break;
642     case PROP_CLIENT_SOCKETS:
643         g_value_set_boolean(value, s->client_provided_sockets);
644 	break;
645     case PROP_PUBKEY:
646         g_value_set_boxed(value, s->pubkey);
647 	break;
648     case PROP_CA:
649         g_value_set_boxed(value, s->ca);
650 	break;
651     case PROP_CERT_SUBJECT:
652         g_value_set_string(value, s->cert_subject);
653 	break;
654     case PROP_VERIFY:
655         g_value_set_flags(value, s->verify);
656         break;
657     case PROP_MIGRATION_STATE:
658         g_value_set_enum(value, s->migration_state);
659         break;
660     case PROP_SMARTCARD:
661         g_value_set_boolean(value, s->smartcard);
662         break;
663     case PROP_SMARTCARD_CERTIFICATES:
664         g_value_set_boxed(value, s->smartcard_certificates);
665         break;
666     case PROP_SMARTCARD_DB:
667         g_value_set_string(value, s->smartcard_db);
668         break;
669     case PROP_USBREDIR:
670         g_value_set_boolean(value, s->usbredir);
671         break;
672     case PROP_INHIBIT_KEYBOARD_GRAB:
673         g_value_set_boolean(value, s->inhibit_keyboard_grab);
674         break;
675     case PROP_DISABLE_EFFECTS:
676         g_value_set_boxed(value, s->disable_effects);
677         break;
678     case PROP_SECURE_CHANNELS:
679         g_value_set_boxed(value, s->secure_channels);
680         break;
681     case PROP_COLOR_DEPTH: /* FIXME: deprecated */
682         g_value_set_int(value, 0);
683         break;
684     case PROP_AUDIO:
685         g_value_set_boolean(value, s->audio);
686         break;
687     case PROP_READ_ONLY:
688         g_value_set_boolean(value, s->read_only);
689         break;
690     case PROP_CACHE_SIZE:
691         g_value_set_int(value, s->images_cache_size);
692         break;
693     case PROP_GLZ_WINDOW_SIZE:
694         g_value_set_int(value, s->glz_window_size);
695         break;
696     case PROP_NAME:
697         g_value_set_string(value, s->name);
698 	break;
699     case PROP_UUID:
700         g_value_set_pointer(value, s->uuid);
701 	break;
702     case PROP_PROXY:
703         g_value_take_string(value, spice_uri_to_string(s->proxy));
704 	break;
705     case PROP_SHARED_DIR:
706         g_value_set_string(value, spice_session_get_shared_dir(session));
707         break;
708     case PROP_SHARE_DIR_RO:
709         g_value_set_boolean(value, s->share_dir_ro);
710         break;
711     case PROP_PREF_COMPRESSION:
712         g_value_set_enum(value, s->preferred_compression);
713         break;
714     case PROP_GL_SCANOUT:
715         g_value_set_boolean(value, s->gl_scanout);
716         break;
717     default:
718 	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
719 	break;
720     }
721 }
722 
spice_session_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)723 static void spice_session_set_property(GObject      *gobject,
724                                        guint         prop_id,
725                                        const GValue *value,
726                                        GParamSpec   *pspec)
727 {
728     SpiceSession *session = SPICE_SESSION(gobject);
729     SpiceSessionPrivate *s = session->priv;
730     const char *str;
731 
732     switch (prop_id) {
733     case PROP_HOST:
734         g_free(s->host);
735         s->host = g_value_dup_string(value);
736         break;
737     case PROP_UNIX_PATH:
738         g_free(s->unix_path);
739         s->unix_path = g_value_dup_string(value);
740         break;
741     case PROP_PORT:
742         g_free(s->port);
743         s->port = g_value_dup_string(value);
744         break;
745     case PROP_TLS_PORT:
746         g_free(s->tls_port);
747         s->tls_port = g_value_dup_string(value);
748         break;
749     case PROP_USERNAME:
750         g_free(s->username);
751         s->username = g_value_dup_string(value);
752         break;
753     case PROP_PASSWORD:
754         g_free(s->password);
755         s->password = g_value_dup_string(value);
756         break;
757     case PROP_CA_FILE:
758         g_free(s->ca_file);
759         s->ca_file = g_value_dup_string(value);
760         break;
761     case PROP_CIPHERS:
762         g_free(s->ciphers);
763         s->ciphers = g_value_dup_string(value);
764         break;
765     case PROP_PROTOCOL:
766         s->protocol = g_value_get_int(value);
767         break;
768     case PROP_URI:
769         str = g_value_get_string(value);
770         if (str != NULL)
771             spice_parse_uri(session, str);
772         break;
773     case PROP_CLIENT_SOCKETS:
774         s->client_provided_sockets = g_value_get_boolean(value);
775         break;
776     case PROP_PUBKEY:
777         if (s->pubkey)
778             g_byte_array_unref(s->pubkey);
779         s->pubkey = g_value_dup_boxed(value);
780         if (s->pubkey)
781             s->verify |= SPICE_SESSION_VERIFY_PUBKEY;
782         else
783             s->verify &= ~SPICE_SESSION_VERIFY_PUBKEY;
784 	break;
785     case PROP_CERT_SUBJECT:
786         g_free(s->cert_subject);
787         s->cert_subject = g_value_dup_string(value);
788         if (s->cert_subject)
789             s->verify |= SPICE_SESSION_VERIFY_SUBJECT;
790         else
791             s->verify &= ~SPICE_SESSION_VERIFY_SUBJECT;
792         break;
793     case PROP_VERIFY:
794         s->verify = g_value_get_flags(value);
795         break;
796     case PROP_MIGRATION_STATE:
797         s->migration_state = g_value_get_enum(value);
798         break;
799     case PROP_SMARTCARD:
800         s->smartcard = g_value_get_boolean(value);
801         break;
802     case PROP_SMARTCARD_CERTIFICATES:
803         g_strfreev(s->smartcard_certificates);
804         s->smartcard_certificates = g_value_dup_boxed(value);
805         break;
806     case PROP_SMARTCARD_DB:
807         g_free(s->smartcard_db);
808         s->smartcard_db = g_value_dup_string(value);
809         break;
810     case PROP_USBREDIR:
811         s->usbredir = g_value_get_boolean(value);
812         break;
813     case PROP_INHIBIT_KEYBOARD_GRAB:
814         s->inhibit_keyboard_grab = g_value_get_boolean(value);
815         break;
816     case PROP_DISABLE_EFFECTS:
817         g_strfreev(s->disable_effects);
818         s->disable_effects = g_value_dup_boxed(value);
819         break;
820     case PROP_SECURE_CHANNELS:
821         g_strfreev(s->secure_channels);
822         s->secure_channels = g_value_dup_boxed(value);
823         break;
824     case PROP_COLOR_DEPTH:
825         spice_info("SpiceSession::color-depth has been deprecated. Property is ignored");
826         break;
827     case PROP_AUDIO:
828         s->audio = g_value_get_boolean(value);
829         break;
830     case PROP_READ_ONLY:
831         s->read_only = g_value_get_boolean(value);
832         g_coroutine_object_notify(gobject, "read-only");
833         break;
834     case PROP_CACHE_SIZE:
835         s->images_cache_size = g_value_get_int(value);
836         break;
837     case PROP_GLZ_WINDOW_SIZE:
838         s->glz_window_size = g_value_get_int(value);
839         break;
840     case PROP_CA:
841         g_clear_pointer(&s->ca, g_byte_array_unref);
842         s->ca = g_value_dup_boxed(value);
843         break;
844     case PROP_PROXY:
845         update_proxy(session, g_value_get_string(value));
846         break;
847     case PROP_SHARED_DIR:
848         spice_session_set_shared_dir(session, g_value_get_string(value));
849         break;
850     case PROP_SHARE_DIR_RO:
851         s->share_dir_ro = g_value_get_boolean(value);
852         break;
853     case PROP_PREF_COMPRESSION:
854         s->preferred_compression = g_value_get_enum(value);
855         break;
856     case PROP_GL_SCANOUT:
857 #ifdef G_OS_UNIX
858         s->gl_scanout = g_value_get_boolean(value);
859 #else
860         g_warning("SpiceSession:gl-scanout is only available on Unix");
861 #endif
862         break;
863     default:
864         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
865         break;
866     }
867 }
868 
spice_session_class_init(SpiceSessionClass * klass)869 static void spice_session_class_init(SpiceSessionClass *klass)
870 {
871     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
872 
873     gobject_class->dispose      = spice_session_dispose;
874     gobject_class->finalize     = spice_session_finalize;
875     gobject_class->get_property = spice_session_get_property;
876     gobject_class->set_property = spice_session_set_property;
877 
878     /**
879      * SpiceSession:host:
880      *
881      * URL of the SPICE host to connect to
882      *
883      **/
884     g_object_class_install_property
885         (gobject_class, PROP_HOST,
886          g_param_spec_string("host",
887                              "Host",
888                              "Remote host",
889                              "localhost",
890                              G_PARAM_READWRITE |
891                              G_PARAM_CONSTRUCT |
892                              G_PARAM_STATIC_STRINGS));
893 
894     /**
895      * SpiceSession:unix-path:
896      *
897      * Path of the Unix socket to connect to
898      *
899      * Since: 0.28
900      **/
901     g_object_class_install_property
902         (gobject_class, PROP_UNIX_PATH,
903          g_param_spec_string("unix-path",
904                              "Unix path",
905                              "Unix path",
906                              NULL,
907                              G_PARAM_READWRITE |
908                              G_PARAM_CONSTRUCT |
909                              G_PARAM_STATIC_STRINGS));
910 
911     /**
912      * SpiceSession:port:
913      *
914      * Port to connect to for unencrypted sessions
915      *
916      **/
917     g_object_class_install_property
918         (gobject_class, PROP_PORT,
919          g_param_spec_string("port",
920                              "Port",
921                              "Remote port (plaintext)",
922                              NULL,
923                              G_PARAM_READWRITE |
924                              G_PARAM_STATIC_STRINGS));
925 
926     /**
927      * SpiceSession:tls-port:
928      *
929      * Port to connect to for TLS sessions
930      *
931      **/
932     g_object_class_install_property
933         (gobject_class, PROP_TLS_PORT,
934          g_param_spec_string("tls-port",
935                              "TLS port",
936                              "Remote port (encrypted)",
937                              NULL,
938                              G_PARAM_READWRITE |
939                              G_PARAM_STATIC_STRINGS));
940 
941     /**
942      * SpiceSession:username:
943      *
944      * Username to use
945      *
946      **/
947     g_object_class_install_property
948         (gobject_class, PROP_USERNAME,
949          g_param_spec_string("username",
950                              "Username",
951                              "Username used for SASL connections",
952                              NULL,
953                              G_PARAM_READWRITE |
954                              G_PARAM_STATIC_STRINGS));
955 
956     /**
957      * SpiceSession:password:
958      *
959      * TLS password to use
960      *
961      **/
962     g_object_class_install_property
963         (gobject_class, PROP_PASSWORD,
964          g_param_spec_string("password",
965                              "Password",
966                              "",
967                              NULL,
968                              G_PARAM_READWRITE |
969                              G_PARAM_STATIC_STRINGS));
970 
971     /**
972      * SpiceSession:ca-file:
973      *
974      * File holding the CA certificates for the host the client is
975      * connecting to
976      *
977      **/
978     g_object_class_install_property
979         (gobject_class, PROP_CA_FILE,
980          g_param_spec_string("ca-file",
981                              "CA file",
982                              "File holding the CA certificates",
983                              NULL,
984                              G_PARAM_READWRITE |
985                              G_PARAM_STATIC_STRINGS));
986 
987     /**
988      * SpiceSession:ciphers:
989      *
990      **/
991     g_object_class_install_property
992         (gobject_class, PROP_CIPHERS,
993          g_param_spec_string("ciphers",
994                              "Ciphers",
995                              "SSL cipher list",
996                              NULL,
997                              G_PARAM_READWRITE |
998                              G_PARAM_STATIC_STRINGS));
999 
1000     /**
1001      * SpiceSession:protocol:
1002      *
1003      * Version of the SPICE protocol to use
1004      *
1005      **/
1006     g_object_class_install_property
1007         (gobject_class, PROP_PROTOCOL,
1008          g_param_spec_int("protocol",
1009                           "Protocol",
1010                           "Spice protocol major version",
1011                           1, 2, 2,
1012                           G_PARAM_READWRITE |
1013                           G_PARAM_CONSTRUCT |
1014                           G_PARAM_STATIC_STRINGS));
1015 
1016     /**
1017      * SpiceSession:uri:
1018      *
1019      * URI of the SPICE host to connect to. The URI is of the form
1020      * spice://hostname?port=XXX or spice://hostname?tls_port=XXX
1021      *
1022      **/
1023     g_object_class_install_property
1024         (gobject_class, PROP_URI,
1025          g_param_spec_string("uri",
1026                              "URI",
1027                              "Spice connection URI",
1028                              NULL,
1029                              G_PARAM_READWRITE |
1030                              G_PARAM_STATIC_STRINGS));
1031 
1032     /**
1033      * SpiceSession:client-sockets:
1034      *
1035      **/
1036     g_object_class_install_property
1037         (gobject_class, PROP_CLIENT_SOCKETS,
1038          g_param_spec_boolean("client-sockets",
1039                           "Client sockets",
1040                           "Sockets are provided by the client",
1041                           FALSE,
1042                           G_PARAM_READWRITE |
1043                           G_PARAM_STATIC_STRINGS));
1044 
1045     /**
1046      * SpiceSession:pubkey:
1047      *
1048      **/
1049     g_object_class_install_property
1050         (gobject_class, PROP_PUBKEY,
1051          g_param_spec_boxed("pubkey",
1052                             "Pub Key",
1053                             "Public key to check",
1054                             G_TYPE_BYTE_ARRAY,
1055                             G_PARAM_READWRITE |
1056                             G_PARAM_STATIC_STRINGS));
1057 
1058     /**
1059      * SpiceSession:cert-subject:
1060      *
1061      **/
1062     g_object_class_install_property
1063         (gobject_class, PROP_CERT_SUBJECT,
1064          g_param_spec_string("cert-subject",
1065                              "Cert Subject",
1066                              "Certificate subject to check",
1067                              NULL,
1068                              G_PARAM_READWRITE |
1069                              G_PARAM_STATIC_STRINGS));
1070 
1071     /**
1072      * SpiceSession:verify:
1073      *
1074      * #SpiceSessionVerify bit field indicating which parts of the peer
1075      * certificate should be checked
1076      **/
1077     g_object_class_install_property
1078         (gobject_class, PROP_VERIFY,
1079          g_param_spec_flags("verify",
1080                             "Verify",
1081                             "Certificate verification parameters",
1082                             SPICE_TYPE_SESSION_VERIFY,
1083                             SPICE_SESSION_VERIFY_HOSTNAME,
1084                             G_PARAM_READWRITE |
1085                             G_PARAM_CONSTRUCT |
1086                             G_PARAM_STATIC_STRINGS));
1087 
1088     /**
1089      * SpiceSession:migration-state:
1090      *
1091      * #SpiceSessionMigration bit field indicating if a migration is in
1092      * progress
1093      *
1094      **/
1095     g_object_class_install_property
1096         (gobject_class, PROP_MIGRATION_STATE,
1097          g_param_spec_enum("migration-state",
1098                            "Migration state",
1099                            "Migration state",
1100                            SPICE_TYPE_SESSION_MIGRATION,
1101                            SPICE_SESSION_MIGRATION_NONE,
1102                            G_PARAM_READABLE |
1103                            G_PARAM_STATIC_STRINGS));
1104 
1105     /**
1106      * SpiceSession:disable-effects:
1107      *
1108      * A string array of effects to disable. The settings will
1109      * be applied on new display channels. The following effets can be
1110      * disabled "wallpaper", "font-smooth", "animation", and "all",
1111      * which will disable all the effects. If NULL, don't apply changes.
1112      *
1113      * Since: 0.7
1114      **/
1115     g_object_class_install_property
1116         (gobject_class, PROP_DISABLE_EFFECTS,
1117          g_param_spec_boxed ("disable-effects",
1118                              "Disable effects",
1119                              "Comma-separated effects to disable",
1120                              G_TYPE_STRV,
1121                              G_PARAM_READWRITE |
1122                              G_PARAM_STATIC_STRINGS));
1123 
1124     /**
1125      * SpiceSession:color-depth:
1126      *
1127      * Display color depth to set on new display channels. If 0, don't set.
1128      *
1129      * Since: 0.7
1130      *
1131      * Deprecated: 0.37: Deprecated due lack of support in drivers, only Windows 7 and older.
1132      * This option is currently ignored.
1133      **/
1134     g_object_class_install_property
1135         (gobject_class, PROP_COLOR_DEPTH,
1136          g_param_spec_int("color-depth",
1137                           "Color depth",
1138                           "Display channel color depth",
1139                           0, 32, 0,
1140                           G_PARAM_DEPRECATED |
1141                           G_PARAM_READWRITE |
1142                           G_PARAM_STATIC_STRINGS));
1143 
1144     /**
1145      * SpiceSession:enable-smartcard:
1146      *
1147      * If set to TRUE, the smartcard channel will be enabled and smartcard
1148      * events will be forwarded to the guest
1149      *
1150      * Since: 0.7
1151      **/
1152     g_object_class_install_property
1153         (gobject_class, PROP_SMARTCARD,
1154          g_param_spec_boolean("enable-smartcard",
1155                           "Enable smartcard event forwarding",
1156                           "Forward smartcard events to the SPICE server",
1157                           FALSE,
1158                           G_PARAM_READWRITE |
1159                           G_PARAM_STATIC_STRINGS));
1160 
1161     /**
1162      * SpiceSession:enable-audio:
1163      *
1164      * If set to TRUE, the audio channels will be enabled for
1165      * playback and recording.
1166      *
1167      * Since: 0.8
1168      **/
1169     g_object_class_install_property
1170         (gobject_class, PROP_AUDIO,
1171          g_param_spec_boolean("enable-audio",
1172                           "Enable audio channels",
1173                           "Enable audio channels",
1174                           TRUE,
1175                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
1176                           G_PARAM_STATIC_STRINGS));
1177 
1178     /**
1179      * SpiceSession:smartcard-certificates:
1180      *
1181      * This property is used when one wants to simulate a smartcard with no
1182      * hardware smartcard reader. If it's set to a NULL-terminated string
1183      * array containing the names of 3 valid certificates, these will be
1184      * used to simulate a smartcard in the guest
1185      * See also spice_smartcard_manager_insert_card()
1186      *
1187      * Since: 0.7
1188      **/
1189     g_object_class_install_property
1190         (gobject_class, PROP_SMARTCARD_CERTIFICATES,
1191          g_param_spec_boxed("smartcard-certificates",
1192                             "Smartcard certificates",
1193                             "Smartcard certificates for software-based smartcards",
1194                             G_TYPE_STRV,
1195                             G_PARAM_READABLE |
1196                             G_PARAM_WRITABLE |
1197                             G_PARAM_STATIC_STRINGS));
1198 
1199     /**
1200      * SpiceSession:smartcard-db:
1201      *
1202      * Path to the NSS certificate database containing the certificates to
1203      * use to simulate a software smartcard
1204      *
1205      * Since: 0.7
1206      **/
1207     g_object_class_install_property
1208         (gobject_class, PROP_SMARTCARD_DB,
1209          g_param_spec_string("smartcard-db",
1210                               "Smartcard certificate database",
1211                               "Path to the database for smartcard certificates",
1212                               NULL,
1213                               G_PARAM_READABLE |
1214                               G_PARAM_WRITABLE |
1215                               G_PARAM_STATIC_STRINGS));
1216 
1217     /**
1218      * SpiceSession:enable-usbredir:
1219      *
1220      * If set to TRUE, the usbredir channel will be enabled and USB devices
1221      * can be redirected to the guest
1222      *
1223      * Since: 0.8
1224      **/
1225     g_object_class_install_property
1226         (gobject_class, PROP_USBREDIR,
1227          g_param_spec_boolean("enable-usbredir",
1228                           "Enable USB device redirection",
1229                           "Forward USB devices to the SPICE server",
1230                           TRUE,
1231                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
1232                           G_PARAM_STATIC_STRINGS));
1233 
1234     /**
1235      * SpiceSession::inhibit-keyboard-grab:
1236      *
1237      * This boolean is set by the usbredir channel to indicate to #SpiceDisplay
1238      * that the keyboard grab should be temporarily released, because it is
1239      * going to invoke policykit. It will get reset when the usbredir channel
1240      * is done with polickit.
1241      *
1242      * Since: 0.8
1243      **/
1244     g_object_class_install_property
1245         (gobject_class, PROP_INHIBIT_KEYBOARD_GRAB,
1246          g_param_spec_boolean("inhibit-keyboard-grab",
1247                         "Inhibit Keyboard Grab",
1248                         "Request that SpiceDisplays don't grab the keyboard",
1249                         FALSE,
1250                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1251 
1252     /**
1253      * SpiceSession:ca:
1254      *
1255      * CA certificates in PEM format. The text data can contain
1256      * several CA certificates identified by:
1257      *
1258      *  -----BEGIN CERTIFICATE-----
1259      *  ... (CA certificate in base64 encoding) ...
1260      *  -----END CERTIFICATE-----
1261      *
1262      * Since: 0.15
1263      **/
1264     g_object_class_install_property
1265         (gobject_class, PROP_CA,
1266          g_param_spec_boxed("ca",
1267                             "CA",
1268                             "The CA certificates data",
1269                             G_TYPE_BYTE_ARRAY,
1270                             G_PARAM_READWRITE |
1271                             G_PARAM_STATIC_STRINGS));
1272 
1273     /**
1274      * SpiceSession:secure-channels:
1275      *
1276      * A string array of channel types to be secured.
1277      *
1278      * Since: 0.20
1279      **/
1280     g_object_class_install_property
1281         (gobject_class, PROP_SECURE_CHANNELS,
1282          g_param_spec_boxed ("secure-channels",
1283                              "Secure channels",
1284                              "Array of channel type to secure",
1285                              G_TYPE_STRV,
1286                              G_PARAM_READWRITE |
1287                              G_PARAM_STATIC_STRINGS));
1288 
1289 
1290     /**
1291      * SpiceSession::channel-new:
1292      * @session: the session that emitted the signal
1293      * @channel: the new #SpiceChannel
1294      *
1295      * The #SpiceSession::channel-new signal is emitted each time a #SpiceChannel is created.
1296      **/
1297     signals[SPICE_SESSION_CHANNEL_NEW] =
1298         g_signal_new("channel-new",
1299                      G_OBJECT_CLASS_TYPE(gobject_class),
1300                      G_SIGNAL_RUN_FIRST,
1301                      G_STRUCT_OFFSET(SpiceSessionClass, channel_new),
1302                      NULL, NULL,
1303                      g_cclosure_marshal_VOID__OBJECT,
1304                      G_TYPE_NONE,
1305                      1,
1306                      SPICE_TYPE_CHANNEL);
1307 
1308     /**
1309      * SpiceSession::channel-destroy:
1310      * @session: the session that emitted the signal
1311      * @channel: the destroyed #SpiceChannel
1312      *
1313      * The #SpiceSession::channel-destroy signal is emitted each time a #SpiceChannel is destroyed.
1314      **/
1315     signals[SPICE_SESSION_CHANNEL_DESTROY] =
1316         g_signal_new("channel-destroy",
1317                      G_OBJECT_CLASS_TYPE(gobject_class),
1318                      G_SIGNAL_RUN_FIRST,
1319                      G_STRUCT_OFFSET(SpiceSessionClass, channel_destroy),
1320                      NULL, NULL,
1321                      g_cclosure_marshal_VOID__OBJECT,
1322                      G_TYPE_NONE,
1323                      1,
1324                      SPICE_TYPE_CHANNEL);
1325 
1326     /**
1327      * SpiceSession::disconnected:
1328      * @session: the session that emitted the signal
1329      *
1330      * The #SpiceSession::disconnected signal is emitted when all channels have been destroyed.
1331      * Since: 0.35
1332      **/
1333     signals[SPICE_SESSION_DISCONNECTED] =
1334         g_signal_new("disconnected",
1335                      G_OBJECT_CLASS_TYPE(gobject_class),
1336                      G_SIGNAL_RUN_FIRST,
1337                      0, NULL, NULL,
1338                      g_cclosure_marshal_VOID__VOID,
1339                      G_TYPE_NONE,
1340                      0,
1341                      NULL);
1342 
1343     /**
1344      * SpiceSession::mm-time-reset:
1345      * @session: the session that emitted the signal
1346      *
1347      * The #SpiceSession::mm-time-reset is emitted when we identify discontinuity in mm-time
1348      *
1349      * Since 0.20
1350      **/
1351     signals[SPICE_SESSION_MM_TIME_RESET] =
1352         g_signal_new("mm-time-reset",
1353                      G_OBJECT_CLASS_TYPE(gobject_class),
1354                      G_SIGNAL_RUN_FIRST,
1355                      0, NULL, NULL,
1356                      g_cclosure_marshal_VOID__VOID,
1357                      G_TYPE_NONE,
1358                      0);
1359 
1360     /**
1361      * SpiceSession:read-only:
1362      *
1363      * Whether this connection is read-only mode.
1364      *
1365      * Since: 0.8
1366      **/
1367     g_object_class_install_property
1368         (gobject_class, PROP_READ_ONLY,
1369          g_param_spec_boolean("read-only", "Read-only",
1370                               "Whether this connection is read-only mode",
1371                               FALSE,
1372                               G_PARAM_READWRITE |
1373                               G_PARAM_CONSTRUCT |
1374                               G_PARAM_STATIC_STRINGS));
1375 
1376     /**
1377      * SpiceSession:cache-size:
1378      *
1379      * Images cache size. If 0, don't set.
1380      *
1381      * Since: 0.9
1382      **/
1383     g_object_class_install_property
1384         (gobject_class, PROP_CACHE_SIZE,
1385          g_param_spec_int("cache-size",
1386                           "Cache size",
1387                           "Images cache size (bytes)",
1388                           0, G_MAXINT, 0,
1389                           G_PARAM_READWRITE |
1390                           G_PARAM_STATIC_STRINGS));
1391 
1392     /**
1393      * SpiceSession:glz-window-size:
1394      *
1395      * Glz window size. If 0, don't set.
1396      *
1397      * Since: 0.9
1398      **/
1399     g_object_class_install_property
1400         (gobject_class, PROP_GLZ_WINDOW_SIZE,
1401          g_param_spec_int("glz-window-size",
1402                           "Glz window size",
1403                           "Glz window size (bytes)",
1404                           0, LZ_MAX_WINDOW_SIZE * 4, 0,
1405                           G_PARAM_READWRITE |
1406                           G_PARAM_STATIC_STRINGS));
1407 
1408     /**
1409      * SpiceSession:name:
1410      *
1411      * Spice server name.
1412      *
1413      * Since: 0.11
1414      **/
1415     g_object_class_install_property
1416         (gobject_class, PROP_NAME,
1417          g_param_spec_string("name",
1418                              "Name",
1419                              "Spice server name",
1420                              NULL,
1421                              G_PARAM_READABLE |
1422                              G_PARAM_STATIC_STRINGS));
1423 
1424     /**
1425      * SpiceSession:uuid:
1426      *
1427      * Spice server uuid.
1428      *
1429      * Since: 0.11
1430      **/
1431     g_object_class_install_property
1432         (gobject_class, PROP_UUID,
1433          g_param_spec_pointer("uuid",
1434                               "UUID",
1435                               "Spice server uuid",
1436                               G_PARAM_READABLE |
1437                               G_PARAM_STATIC_STRINGS));
1438 
1439     /**
1440      * SpiceSession:proxy:
1441      *
1442      * URI to the proxy server to use when doing network connection.
1443      * of the form <![CDATA[ [protocol://]<host>[:port] ]]>
1444      *
1445      * Since: 0.17
1446      **/
1447     g_object_class_install_property
1448         (gobject_class, PROP_PROXY,
1449          g_param_spec_string("proxy",
1450                              "Proxy",
1451                              "The proxy server",
1452                              NULL,
1453                              G_PARAM_READWRITE |
1454                              G_PARAM_STATIC_STRINGS));
1455 
1456     /**
1457      * SpiceSession:shared-dir:
1458      *
1459      * Location of the shared directory
1460      *
1461      * Since: 0.24
1462      **/
1463     g_object_class_install_property
1464         (gobject_class, PROP_SHARED_DIR,
1465          g_param_spec_string("shared-dir",
1466                              "Shared directory",
1467                              "Shared directory",
1468                              g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE),
1469                              G_PARAM_READWRITE |
1470                              G_PARAM_CONSTRUCT |
1471                              G_PARAM_STATIC_STRINGS));
1472 
1473     /**
1474      * SpiceSession:share-dir-ro:
1475      *
1476      * Whether to share the directory read-only.
1477      *
1478      * Since: 0.28
1479      **/
1480     g_object_class_install_property
1481         (gobject_class, PROP_SHARE_DIR_RO,
1482          g_param_spec_boolean("share-dir-ro",
1483                               "Share directory read-only",
1484                               "Share directory read-only",
1485                               FALSE,
1486                               G_PARAM_READWRITE |
1487                               G_PARAM_CONSTRUCT |
1488                               G_PARAM_STATIC_STRINGS));
1489 
1490     /**
1491      * SpiceSession:preferred-compression:
1492      *
1493      * The image compression algorithm the client prefers to use. It is
1494      * reported to the server.
1495      *
1496      * Since: 0.29
1497      **/
1498     g_object_class_install_property
1499         (gobject_class, PROP_PREF_COMPRESSION,
1500          g_param_spec_enum("preferred-compression",
1501                            "Preferred image compression algorithm",
1502                            "Preferred image compression algorithm",
1503                            SPICE_TYPE_IMAGE_COMPRESSION,
1504                            SPICE_IMAGE_COMPRESSION_INVALID,
1505                            G_PARAM_READWRITE |
1506                            G_PARAM_STATIC_STRINGS));
1507 
1508     /**
1509      * SpiceSession:gl-scanout:
1510      *
1511      * Whether to enable gl-scanout (Unix only).  Set to TRUE by
1512      * default on EGL-enabled host, unless SPICE_DISABLE_GL_SCANOUT
1513      * environment variable is set.
1514      *
1515      * Since: 0.36
1516      **/
1517     g_object_class_install_property
1518         (gobject_class, PROP_GL_SCANOUT,
1519          g_param_spec_boolean("gl-scanout",
1520                               "Enable GL scanout support",
1521                               "Enable GL scanout support",
1522 #ifdef HAVE_EGL
1523                               g_getenv("SPICE_DISABLE_GL_SCANOUT") == NULL,
1524                               G_PARAM_CONSTRUCT |
1525 #else
1526                               false,
1527 #endif
1528                               G_PARAM_READWRITE |
1529                               G_PARAM_STATIC_STRINGS));
1530 }
1531 
1532 G_GNUC_INTERNAL
spice_session_get_gl_scanout_enabled(SpiceSession * session)1533 gboolean spice_session_get_gl_scanout_enabled(SpiceSession *session)
1534 {
1535     return session->priv->gl_scanout;
1536 }
1537 
1538 /* ------------------------------------------------------------------ */
1539 /* public functions                                                   */
1540 
1541 /**
1542  * spice_session_new:
1543  *
1544  * Creates a new Spice session.
1545  *
1546  * Returns: a new #SpiceSession
1547  **/
spice_session_new(void)1548 SpiceSession *spice_session_new(void)
1549 {
1550     SpiceSession *self = SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION, NULL));
1551     SpiceSessionPrivate *priv = self->priv;
1552     GError *err = NULL;
1553 
1554     priv->usb_manager = spice_usb_device_manager_get(self, &err);
1555     if (err != NULL) {
1556         SPICE_DEBUG("Could not initialize SpiceUsbDeviceManager - %s", err->message);
1557         g_clear_error(&err);
1558     }
1559 
1560     return self;
1561 }
1562 
1563 G_GNUC_INTERNAL
spice_session_new_from_session(SpiceSession * session)1564 SpiceSession *spice_session_new_from_session(SpiceSession *session)
1565 {
1566     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
1567 
1568     SpiceSessionPrivate *s = session->priv;
1569     SpiceSession *copy;
1570     SpiceSessionPrivate *c;
1571 
1572     if (s->client_provided_sockets) {
1573         g_warning("migration with client provided fd is not supported yet");
1574         return NULL;
1575     }
1576 
1577     copy = SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION,
1578                                       "host", NULL,
1579                                       "ca-file", NULL,
1580                                       NULL));
1581     c = copy->priv;
1582     g_clear_object(&c->proxy);
1583 
1584     g_warn_if_fail(c->host == NULL);
1585     g_warn_if_fail(c->unix_path == NULL);
1586     g_warn_if_fail(c->tls_port == NULL);
1587     g_warn_if_fail(c->username == NULL);
1588     g_warn_if_fail(c->password == NULL);
1589     g_warn_if_fail(c->ca_file == NULL);
1590     g_warn_if_fail(c->ciphers == NULL);
1591     g_warn_if_fail(c->cert_subject == NULL);
1592     g_warn_if_fail(c->pubkey == NULL);
1593     g_warn_if_fail(c->pubkey == NULL);
1594     g_warn_if_fail(c->proxy == NULL);
1595 
1596     g_object_get(session,
1597                  "host", &c->host,
1598                  "unix-path", &c->unix_path,
1599                  "tls-port", &c->tls_port,
1600                  "username", &c->username,
1601                  "password", &c->password,
1602                  "ca-file", &c->ca_file,
1603                  "ciphers", &c->ciphers,
1604                  "cert-subject", &c->cert_subject,
1605                  "pubkey", &c->pubkey,
1606                  "verify", &c->verify,
1607                  "smartcard-certificates", &c->smartcard_certificates,
1608                  "smartcard-db", &c->smartcard_db,
1609                  "enable-smartcard", &c->smartcard,
1610                  "enable-audio", &c->audio,
1611                  "enable-usbredir", &c->usbredir,
1612                  "ca", &c->ca,
1613                  NULL);
1614 
1615     c->client_provided_sockets = s->client_provided_sockets;
1616     c->protocol = s->protocol;
1617     c->connection_id = s->connection_id;
1618     if (s->proxy)
1619         c->proxy = g_object_ref(s->proxy);
1620 
1621     return copy;
1622 }
1623 
1624 /**
1625  * spice_session_connect:
1626  * @session: a #SpiceSession
1627  *
1628  * Open the session using the #SpiceSession:host and
1629  * #SpiceSession:port.
1630  *
1631  * Returns: %FALSE if the session state is invalid for connection
1632  * request. %TRUE if the connection is initiated. To know whether the
1633  * connection is established, you must watch for channels creation
1634  * (#SpiceSession::channel-new) and the channels state
1635  * (#SpiceChannel::channel-event).
1636  **/
spice_session_connect(SpiceSession * session)1637 gboolean spice_session_connect(SpiceSession *session)
1638 {
1639     SpiceSessionPrivate *s;
1640 
1641     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
1642 
1643     s = session->priv;
1644     g_return_val_if_fail(!s->disconnecting, FALSE);
1645 
1646     session_disconnect(session, TRUE);
1647 
1648     s->client_provided_sockets = FALSE;
1649 
1650     if (s->cmain == NULL)
1651         s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
1652 
1653     glz_decoder_window_clear(s->glz_window);
1654     return spice_channel_connect(s->cmain);
1655 }
1656 
1657 /**
1658  * spice_session_open_fd:
1659  * @session: a #SpiceSession
1660  * @fd: a file descriptor (socket) or -1
1661  *
1662  * Open the session using the provided @fd socket file
1663  * descriptor. This is useful if you create the fd yourself, for
1664  * example to setup a SSH tunnel.
1665  *
1666  * Note however that additional sockets will be needed by all the channels
1667  * created for @session so users of this API should hook into
1668  * SpiceChannel::open-fd signal for each channel they are interested in, and
1669  * create and pass a new socket to the channel using #spice_channel_open_fd, in
1670  * the signal callback.
1671  *
1672  * If @fd is -1, a valid fd will be requested later via the
1673  * SpiceChannel::open-fd signal. Typically, you would want to just pass -1 as
1674  * @fd this call since you will have to hook to SpiceChannel::open-fd signal
1675  * anyway.
1676  *
1677  * Returns: %TRUE on success.
1678  **/
spice_session_open_fd(SpiceSession * session,int fd)1679 gboolean spice_session_open_fd(SpiceSession *session, int fd)
1680 {
1681     SpiceSessionPrivate *s;
1682 
1683     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
1684     g_return_val_if_fail(fd >= -1, FALSE);
1685 
1686     s = session->priv;
1687     g_return_val_if_fail(!s->disconnecting, FALSE);
1688 
1689     session_disconnect(session, TRUE);
1690 
1691     s->client_provided_sockets = TRUE;
1692 
1693     if (s->cmain == NULL)
1694         s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
1695 
1696     glz_decoder_window_clear(s->glz_window);
1697     return spice_channel_open_fd(s->cmain, fd);
1698 }
1699 
1700 G_GNUC_INTERNAL
spice_session_get_client_provided_socket(SpiceSession * session)1701 gboolean spice_session_get_client_provided_socket(SpiceSession *session)
1702 {
1703     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
1704 
1705     SpiceSessionPrivate *s = session->priv;
1706 
1707     return s->client_provided_sockets;
1708 }
1709 
cache_clear_all(SpiceSession * self)1710 static void cache_clear_all(SpiceSession *self)
1711 {
1712     SpiceSessionPrivate *s = self->priv;
1713 
1714     cache_clear(s->images);
1715     glz_decoder_window_clear(s->glz_window);
1716 }
1717 
1718 G_GNUC_INTERNAL
spice_session_switching_disconnect(SpiceSession * self)1719 void spice_session_switching_disconnect(SpiceSession *self)
1720 {
1721     g_return_if_fail(SPICE_IS_SESSION(self));
1722 
1723     SpiceSessionPrivate *s = self->priv;
1724 
1725     g_return_if_fail(s->cmain != NULL);
1726 
1727     /* disconnect/destroy all but main channel */
1728 
1729     for (GList *l = s->channels; l != NULL; ) {
1730         SpiceChannel *channel = l->data;
1731         l = l->next;
1732 
1733         if (channel == s->cmain)
1734             continue;
1735         spice_session_channel_destroy(self, channel);
1736     }
1737 
1738     g_warn_if_fail(s->channels != NULL);
1739 
1740     cache_clear_all(self);
1741     s->connection_id = 0;
1742 }
1743 
1744 #define SWAP_STR(x, y) G_STMT_START { \
1745     const gchar *tmp;                 \
1746     const gchar *a = x;               \
1747     const gchar *b = y;               \
1748     tmp = a;                          \
1749     a = b;                            \
1750     b = tmp;                          \
1751 } G_STMT_END
1752 
1753 G_GNUC_INTERNAL
spice_session_start_migrating(SpiceSession * session,gboolean full_migration)1754 void spice_session_start_migrating(SpiceSession *session,
1755                                    gboolean full_migration)
1756 {
1757     g_return_if_fail(SPICE_IS_SESSION(session));
1758 
1759     SpiceSessionPrivate *s = session->priv;
1760     SpiceSessionPrivate *m;
1761 
1762     g_return_if_fail(s->migration != NULL);
1763     m = s->migration->priv;
1764     g_return_if_fail(m->migration_state == SPICE_SESSION_MIGRATION_CONNECTING);
1765 
1766 
1767     s->full_migration = full_migration;
1768     spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_MIGRATING);
1769 
1770     /* swapping connection details happens after MIGRATION_CONNECTING state */
1771     SWAP_STR(s->host, m->host);
1772     SWAP_STR(s->port, m->port);
1773     SWAP_STR(s->tls_port, m->tls_port);
1774     SWAP_STR(s->unix_path, m->unix_path);
1775 
1776     g_warn_if_fail(g_list_length(s->channels) == g_list_length(m->channels));
1777 
1778     SPICE_DEBUG("migration channels left:%u (in migration:%u)",
1779                 g_list_length(s->channels), g_list_length(m->channels));
1780     s->migration_left = spice_session_get_channels(session);
1781 }
1782 #undef SWAP_STR
1783 
1784 G_GNUC_INTERNAL
spice_session_lookup_channel(SpiceSession * session,gint id,gint type)1785 SpiceChannel* spice_session_lookup_channel(SpiceSession *session, gint id, gint type)
1786 {
1787     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
1788 
1789     SpiceSessionPrivate *s = session->priv;
1790     SpiceChannel *channel = NULL;
1791 
1792     for (GList *l = s->channels; l != NULL; ) {
1793         channel = l->data;
1794         l = l->next;
1795 
1796         if (id == spice_channel_get_channel_id(channel) &&
1797             type == spice_channel_get_channel_type(channel)) {
1798             break;
1799         }
1800     }
1801     g_return_val_if_fail(channel != NULL, NULL);
1802 
1803     return channel;
1804 }
1805 
1806 G_GNUC_INTERNAL
spice_session_abort_migration(SpiceSession * session)1807 void spice_session_abort_migration(SpiceSession *session)
1808 {
1809     g_return_if_fail(SPICE_IS_SESSION(session));
1810 
1811     SpiceSessionPrivate *s = session->priv;
1812 
1813     if (s->migration == NULL) {
1814         SPICE_DEBUG("no migration in progress");
1815         return;
1816     }
1817 
1818     SPICE_DEBUG("migration: abort");
1819     if (s->migration_state != SPICE_SESSION_MIGRATION_MIGRATING)
1820         goto end;
1821 
1822     for (GList *l = s->channels; l != NULL; ) {
1823         SpiceChannel *channel = l->data;
1824         l = l->next;
1825 
1826         if (g_list_find(s->migration_left, channel))
1827             continue;
1828 
1829         spice_channel_swap(channel,
1830             spice_session_lookup_channel(s->migration,
1831                                          spice_channel_get_channel_id(channel),
1832                                          spice_channel_get_channel_type(channel)),
1833                                          !s->full_migration);
1834     }
1835 
1836 end:
1837     g_clear_pointer(&s->migration_left, g_list_free);
1838     session_disconnect(s->migration, FALSE);
1839     g_clear_pointer(&s->migration, g_object_unref);
1840 
1841     s->migrate_wait_init = FALSE;
1842     if (s->after_main_init) {
1843         g_source_remove(s->after_main_init);
1844         s->after_main_init = 0;
1845     }
1846 
1847     spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
1848 }
1849 
1850 G_GNUC_INTERNAL
spice_session_channel_migrate(SpiceSession * session,SpiceChannel * channel)1851 void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
1852 {
1853     g_return_if_fail(SPICE_IS_SESSION(session));
1854 
1855     SpiceSessionPrivate *s = session->priv;
1856     SpiceChannel *c;
1857     gint id, type;
1858 
1859     g_return_if_fail(s->migration != NULL);
1860     g_return_if_fail(SPICE_IS_CHANNEL(channel));
1861 
1862     id = spice_channel_get_channel_id(channel);
1863     type = spice_channel_get_channel_type(channel);
1864     CHANNEL_DEBUG(channel, "migrating channel id:%d type:%d", id, type);
1865 
1866     c = spice_session_lookup_channel(s->migration, id, type);
1867     g_return_if_fail(c != NULL);
1868 
1869     if (!g_queue_is_empty(&c->priv->xmit_queue) && s->full_migration) {
1870         CHANNEL_DEBUG(channel, "mig channel xmit queue is not empty. type %s", c->priv->name);
1871     }
1872     spice_channel_swap(channel, c, !s->full_migration);
1873     s->migration_left = g_list_remove(s->migration_left, channel);
1874 
1875     if (g_list_length(s->migration_left) == 0) {
1876         CHANNEL_DEBUG(channel, "migration: all channel migrated, success");
1877         session_disconnect(s->migration, FALSE);
1878         g_clear_pointer(&s->migration, g_object_unref);
1879         spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
1880     }
1881 }
1882 
1883 /* main context */
after_main_init(gpointer data)1884 static gboolean after_main_init(gpointer data)
1885 {
1886     SpiceSession *self = data;
1887     SpiceSessionPrivate *s = self->priv;
1888     GList *l;
1889 
1890     for (l = s->migration_left; l != NULL; ) {
1891         SpiceChannel *channel = l->data;
1892         l = l->next;
1893 
1894         spice_session_channel_migrate(self, channel);
1895         channel->priv->state = SPICE_CHANNEL_STATE_READY;
1896         spice_channel_up(channel);
1897     }
1898 
1899     s->after_main_init = 0;
1900     return FALSE;
1901 }
1902 
1903 /* coroutine context */
1904 G_GNUC_INTERNAL
spice_session_migrate_after_main_init(SpiceSession * self)1905 gboolean spice_session_migrate_after_main_init(SpiceSession *self)
1906 {
1907     g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
1908 
1909     SpiceSessionPrivate *s = self->priv;
1910 
1911     if (!s->migrate_wait_init)
1912         return FALSE;
1913 
1914     g_return_val_if_fail(g_list_length(s->migration_left) != 0, FALSE);
1915     g_return_val_if_fail(s->after_main_init == 0, FALSE);
1916 
1917     s->migrate_wait_init = FALSE;
1918     s->after_main_init = g_idle_add(after_main_init, self);
1919 
1920     return TRUE;
1921 }
1922 
1923 /* main context */
1924 G_GNUC_INTERNAL
spice_session_migrate_end(SpiceSession * self)1925 void spice_session_migrate_end(SpiceSession *self)
1926 {
1927     g_return_if_fail(SPICE_IS_SESSION(self));
1928 
1929     SpiceSessionPrivate *s = self->priv;
1930     SpiceMsgOut *out;
1931 
1932     g_return_if_fail(s->migration);
1933     g_return_if_fail(s->migration->priv->cmain);
1934     g_return_if_fail(g_list_length(s->migration_left) != 0);
1935 
1936     /* disconnect and reset all channels */
1937     for (GList *l = s->migration_left; l != NULL; ) {
1938         SpiceChannel *channel = l->data;
1939         l = l->next;
1940 
1941         if (!SPICE_IS_MAIN_CHANNEL(channel)) {
1942             /* freeze other channels */
1943             channel->priv->state = SPICE_CHANNEL_STATE_MIGRATING;
1944         }
1945 
1946         /* reset for migration, disconnect */
1947         spice_channel_reset(channel, TRUE);
1948 
1949         if (SPICE_IS_MAIN_CHANNEL(channel)) {
1950             /* migrate main to target, so we can start talking */
1951             spice_session_channel_migrate(self, channel);
1952         }
1953     }
1954 
1955     cache_clear_all(self);
1956 
1957     /* send MIGRATE_END to target */
1958     out = spice_msg_out_new(s->cmain, SPICE_MSGC_MAIN_MIGRATE_END);
1959     spice_msg_out_send(out);
1960 
1961     /* now wait after main init for the rest of channels migration */
1962     s->migrate_wait_init = TRUE;
1963 }
1964 
1965 /**
1966  * spice_session_get_read_only:
1967  * @session: a #SpiceSession
1968  *
1969  * Checks whether the @session is read-only.
1970  *
1971  * Returns: whether the @session is in read-only mode.
1972  **/
spice_session_get_read_only(SpiceSession * self)1973 gboolean spice_session_get_read_only(SpiceSession *self)
1974 {
1975     g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
1976 
1977     return self->priv->read_only;
1978 }
1979 
session_disconnect_idle(SpiceSession * self)1980 static gboolean session_disconnect_idle(SpiceSession *self)
1981 {
1982     SpiceSessionPrivate *s = self->priv;
1983 
1984     session_disconnect(self, FALSE);
1985     s->disconnecting = 0;
1986 
1987     g_object_unref(self);
1988 
1989     return FALSE;
1990 }
1991 
1992 /**
1993  * spice_session_disconnect:
1994  * @session: a #SpiceSession
1995  *
1996  * Disconnect the @session, and destroy all channels.
1997  **/
spice_session_disconnect(SpiceSession * session)1998 void spice_session_disconnect(SpiceSession *session)
1999 {
2000     SpiceSessionPrivate *s;
2001 
2002     g_return_if_fail(SPICE_IS_SESSION(session));
2003 
2004     s = session->priv;
2005 
2006     SPICE_DEBUG("session: disconnecting %u", s->disconnecting);
2007     if (s->disconnecting != 0)
2008         return;
2009 
2010     g_object_ref(session);
2011     s->disconnecting = g_idle_add((GSourceFunc)session_disconnect_idle, session);
2012 }
2013 
2014 /**
2015  * spice_session_get_channels:
2016  * @session: a #SpiceSession
2017  *
2018  * Get the list of current channels associated with this @session.
2019  *
2020  * Returns: (element-type SpiceChannel) (transfer container): a #GList
2021  *          of unowned #SpiceChannel channels.
2022  **/
spice_session_get_channels(SpiceSession * session)2023 GList *spice_session_get_channels(SpiceSession *session)
2024 {
2025     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2026     g_return_val_if_fail(session->priv != NULL, NULL);
2027 
2028     return g_list_copy(session->priv->channels);
2029 }
2030 
2031 /**
2032  * spice_session_has_channel_type:
2033  * @session: a #SpiceSession
2034  * @type: a #SpiceChannel:channel-type
2035  *
2036  * See if there is a @type channel in the channels associated with this
2037  * @session.
2038  *
2039  * Returns: TRUE if a @type channel is available otherwise FALSE.
2040  **/
spice_session_has_channel_type(SpiceSession * session,gint type)2041 gboolean spice_session_has_channel_type(SpiceSession *session, gint type)
2042 {
2043     SpiceSessionPrivate *s;
2044 
2045     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2046     g_return_val_if_fail(session->priv != NULL, FALSE);
2047 
2048     s = session->priv;
2049 
2050     for (GList *l = s->channels; l != NULL; l = l->next) {
2051         SpiceChannel *channel = l->data;
2052         if (spice_channel_get_channel_type(channel) == type) {
2053             return TRUE;
2054         }
2055     }
2056     return FALSE;
2057 }
2058 
2059 /* ------------------------------------------------------------------ */
2060 /* private functions                                                  */
2061 
2062 typedef struct spice_open_host spice_open_host;
2063 
2064 struct spice_open_host {
2065     struct coroutine *from;
2066     SpiceSession *session;
2067     SpiceChannel *channel;
2068     SpiceURI *proxy;
2069     int port;
2070     GCancellable *cancellable;
2071     GError *error;
2072     GSocketConnection *connection;
2073     GSocketClient *client;
2074 };
2075 
socket_client_connect_ready(GObject * source_object,GAsyncResult * result,gpointer data)2076 static void socket_client_connect_ready(GObject *source_object, GAsyncResult *result,
2077                                         gpointer data)
2078 {
2079     GSocketClient *client = G_SOCKET_CLIENT(source_object);
2080     spice_open_host *open_host = data;
2081     GSocketConnection *connection = NULL;
2082 
2083     CHANNEL_DEBUG(open_host->channel, "connect ready");
2084     connection = g_socket_client_connect_finish(client, result, &open_host->error);
2085     if (connection == NULL) {
2086         g_warn_if_fail(open_host->error != NULL);
2087         goto end;
2088     }
2089 
2090     open_host->connection = connection;
2091 
2092 end:
2093     coroutine_yieldto(open_host->from, NULL);
2094 }
2095 
2096 /* main context */
open_host_connectable_connect(spice_open_host * open_host,GSocketConnectable * connectable)2097 static void open_host_connectable_connect(spice_open_host *open_host, GSocketConnectable *connectable)
2098 {
2099     CHANNEL_DEBUG(open_host->channel, "connecting %p...", open_host);
2100 
2101     g_socket_client_connect_async(open_host->client, connectable,
2102                                   open_host->cancellable,
2103                                   socket_client_connect_ready, open_host);
2104 }
2105 
2106 /* main context */
proxy_lookup_ready(GObject * source_object,GAsyncResult * result,gpointer data)2107 static void proxy_lookup_ready(GObject *source_object, GAsyncResult *result,
2108                                gpointer data)
2109 {
2110     spice_open_host *open_host = data;
2111     SpiceSession *session = open_host->session;
2112     SpiceSessionPrivate *s = session->priv;
2113     GList *addresses = NULL, *it;
2114     GSocketAddress *address;
2115 
2116     SPICE_DEBUG("proxy lookup ready");
2117     addresses = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object),
2118                                                  result, &open_host->error);
2119     if (addresses == NULL || open_host->error) {
2120         g_prefix_error(&open_host->error, "SPICE proxy: ");
2121         coroutine_yieldto(open_host->from, NULL);
2122         return;
2123     }
2124 
2125     for (it = addresses; it != NULL; it = it->next) {
2126         address = g_proxy_address_new(G_INET_ADDRESS(it->data),
2127                                       spice_uri_get_port(open_host->proxy),
2128                                       spice_uri_get_scheme(open_host->proxy),
2129                                       s->host, open_host->port,
2130                                       spice_uri_get_user(open_host->proxy),
2131                                       spice_uri_get_password(open_host->proxy));
2132         if (address != NULL)
2133             break;
2134     }
2135 
2136     open_host_connectable_connect(open_host, G_SOCKET_CONNECTABLE(address));
2137     g_resolver_free_addresses(addresses);
2138     g_object_unref(address);
2139 }
2140 
2141 /* main context */
open_host_idle_cb(gpointer data)2142 static gboolean open_host_idle_cb(gpointer data)
2143 {
2144     spice_open_host *open_host = data;
2145     SpiceSessionPrivate *s;
2146 
2147     g_return_val_if_fail(open_host != NULL, FALSE);
2148     g_return_val_if_fail(open_host->connection == NULL, FALSE);
2149 
2150     if (spice_channel_get_session(open_host->channel) != open_host->session)
2151         return FALSE;
2152 
2153     s = open_host->session->priv;
2154     open_host->proxy = s->proxy;
2155     if (open_host->error != NULL) {
2156         coroutine_yieldto(open_host->from, NULL);
2157         return FALSE;
2158     }
2159 
2160     if (open_host->proxy) {
2161         g_resolver_lookup_by_name_async(g_resolver_get_default(),
2162                                         spice_uri_get_hostname(open_host->proxy),
2163                                         open_host->cancellable,
2164                                         proxy_lookup_ready, open_host);
2165     } else {
2166         GSocketConnectable *address = NULL;
2167 
2168         if (s->unix_path) {
2169             SPICE_DEBUG("open unix path %s", s->unix_path);
2170 #ifdef G_OS_UNIX
2171             address = G_SOCKET_CONNECTABLE(g_unix_socket_address_new(s->unix_path));
2172 #else
2173             g_set_error_literal(&open_host->error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
2174                                 "Unix path unsupported on this platform");
2175 #endif
2176         } else {
2177             SPICE_DEBUG("open host %s:%d", s->host, open_host->port);
2178             address = g_network_address_parse(s->host, open_host->port, &open_host->error);
2179         }
2180 
2181         if (address == NULL || open_host->error != NULL) {
2182             coroutine_yieldto(open_host->from, NULL);
2183             return FALSE;
2184         }
2185 
2186         open_host_connectable_connect(open_host, address);
2187         g_object_unref(address);
2188     }
2189 
2190     if (open_host->proxy != NULL) {
2191         gchar *str = spice_uri_to_string(open_host->proxy);
2192         SPICE_DEBUG("(with proxy %s)", str);
2193         g_free(str);
2194     }
2195 
2196     return FALSE;
2197 }
2198 
2199 #define SOCKET_TIMEOUT 10
2200 
2201 /* coroutine context */
2202 G_GNUC_INTERNAL
spice_session_channel_open_host(SpiceSession * session,SpiceChannel * channel,gboolean * use_tls,GError ** error)2203 GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
2204                                                    gboolean *use_tls, GError **error)
2205 {
2206     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2207 
2208     SpiceSessionPrivate *s = session->priv;
2209     SpiceChannelPrivate *c = channel->priv;
2210     spice_open_host open_host = { 0, };
2211     gchar *port, *endptr;
2212 
2213     // FIXME: make open_host() cancellable
2214     open_host.from = coroutine_self();
2215     open_host.session = session;
2216     open_host.channel = channel;
2217 
2218     const char *name = spice_channel_type_to_string(c->channel_type);
2219     if (spice_strv_contains(s->secure_channels, "all") ||
2220         spice_strv_contains(s->secure_channels, name))
2221         *use_tls = TRUE;
2222 
2223     if (s->unix_path) {
2224         if (*use_tls) {
2225             CHANNEL_DEBUG(channel, "No TLS for Unix sockets");
2226             return NULL;
2227         }
2228     } else {
2229         port = *use_tls ? s->tls_port : s->port;
2230         if (port == NULL) {
2231             SPICE_DEBUG("Missing port value, not attempting %s connection.",
2232                     *use_tls?"TLS":"unencrypted");
2233             return NULL;
2234         }
2235 
2236         open_host.port = strtol(port, &endptr, 10);
2237         if (*port == '\0' || *endptr != '\0' ||
2238             open_host.port <= 0 || open_host.port > G_MAXUINT16) {
2239             g_warning("Invalid port value %s", port);
2240             return NULL;
2241         }
2242     }
2243     if (*use_tls) {
2244         CHANNEL_DEBUG(channel, "Using TLS, port %d", open_host.port);
2245     } else {
2246         CHANNEL_DEBUG(channel, "Using plain text, port %d", open_host.port);
2247     }
2248 
2249     open_host.client = g_socket_client_new();
2250     g_socket_client_set_enable_proxy(open_host.client, s->proxy != NULL);
2251     g_socket_client_set_timeout(open_host.client, SOCKET_TIMEOUT);
2252 
2253     g_idle_add(open_host_idle_cb, &open_host);
2254     /* switch to main loop and wait for connection */
2255     coroutine_yield(NULL);
2256 
2257     if (open_host.error != NULL) {
2258         CHANNEL_DEBUG(channel, "open host: %s", open_host.error->message);
2259         g_propagate_error(error, open_host.error);
2260     } else if (open_host.connection != NULL) {
2261         GSocket *socket;
2262         socket = g_socket_connection_get_socket(open_host.connection);
2263         g_socket_set_timeout(socket, 0);
2264         g_socket_set_blocking(socket, FALSE);
2265         g_socket_set_keepalive(socket, TRUE);
2266 
2267         /* Make client timeouts a bit more responsive */
2268 #if defined(_WIN32)
2269         /*  Windows does not support setting count */
2270         struct tcp_keepalive keepalive = {
2271             TRUE,
2272             30 * 1000,
2273             5 * 1000
2274         };
2275         DWORD written;
2276         WSAIoctl(g_socket_get_fd(socket), SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive),
2277                  NULL, 0, &written, NULL, NULL);
2278 #elif defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL)
2279         g_socket_set_option(socket, SOL_TCP, TCP_KEEPIDLE, 30, NULL);
2280         g_socket_set_option(socket, SOL_TCP, TCP_KEEPINTVL, 15, NULL);
2281         g_socket_set_option(socket, SOL_TCP, TCP_KEEPCNT, 3, NULL);
2282 #endif
2283     }
2284 
2285     g_clear_object(&open_host.client);
2286     return open_host.connection;
2287 }
2288 
2289 
2290 G_GNUC_INTERNAL
spice_session_channel_new(SpiceSession * session,SpiceChannel * channel)2291 void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
2292 {
2293     g_return_if_fail(SPICE_IS_SESSION(session));
2294     g_return_if_fail(SPICE_IS_CHANNEL(channel));
2295 
2296     SpiceSessionPrivate *s = session->priv;
2297 
2298     s->channels = g_list_prepend(s->channels, channel);
2299 
2300     if (SPICE_IS_MAIN_CHANNEL(channel)) {
2301         gboolean all = spice_strv_contains(s->disable_effects, "all");
2302 
2303         g_object_set(channel,
2304                      "disable-wallpaper", all || spice_strv_contains(s->disable_effects, "wallpaper"),
2305                      "disable-font-smooth", all || spice_strv_contains(s->disable_effects, "font-smooth"),
2306                      "disable-animation", all || spice_strv_contains(s->disable_effects, "animation"),
2307                      NULL);
2308 
2309         CHANNEL_DEBUG(channel, "new main channel, switching");
2310         s->cmain = channel;
2311     } else if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
2312         g_warn_if_fail(s->playback_channel == NULL);
2313         s->playback_channel = SPICE_PLAYBACK_CHANNEL(channel);
2314     }
2315 
2316     g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_NEW], 0, channel);
2317 }
2318 
channel_finally_destroyed(gpointer data,GObject * channel)2319 static void channel_finally_destroyed(gpointer data, GObject *channel)
2320 {
2321     SpiceSession *session = SPICE_SESSION(data);
2322     SpiceSessionPrivate *s = session->priv;
2323     s->channels_destroying--;
2324     if (s->channels == NULL && (s->channels_destroying == 0)) {
2325         g_signal_emit(session, signals[SPICE_SESSION_DISCONNECTED], 0);
2326     }
2327     g_object_unref(session);
2328 }
2329 
spice_session_channel_destroy(SpiceSession * session,SpiceChannel * channel)2330 static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel)
2331 {
2332     g_return_if_fail(SPICE_IS_SESSION(session));
2333     g_return_if_fail(SPICE_IS_CHANNEL(channel));
2334 
2335     SpiceSessionPrivate *s = session->priv;
2336     GList *l;
2337 
2338     if (s->migration_left)
2339         s->migration_left = g_list_remove(s->migration_left, channel);
2340 
2341     for (l = s->channels; l != NULL; l = l->next) {
2342         if (l->data == channel)
2343             break;
2344     }
2345 
2346     g_return_if_fail(l != NULL);
2347 
2348     if (channel == s->cmain) {
2349         CHANNEL_DEBUG(channel, "the session lost the main channel");
2350         s->cmain = NULL;
2351     }
2352 
2353     s->channels = g_list_delete_link(s->channels, l);
2354 
2355     g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_DESTROY], 0, channel);
2356 
2357     g_clear_object(&channel->priv->session);
2358     spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
2359 
2360     /* Wait until the channel is properly freed so that we can emit a
2361      * 'disconnected' signal */
2362     s->channels_destroying++;
2363     g_object_weak_ref(G_OBJECT(channel), channel_finally_destroyed, g_object_ref(session));
2364 
2365     g_object_unref(channel);
2366 }
2367 
2368 G_GNUC_INTERNAL
spice_session_set_connection_id(SpiceSession * session,int id)2369 void spice_session_set_connection_id(SpiceSession *session, int id)
2370 {
2371     g_return_if_fail(SPICE_IS_SESSION(session));
2372 
2373     SpiceSessionPrivate *s = session->priv;
2374 
2375     s->connection_id = id;
2376 }
2377 
2378 G_GNUC_INTERNAL
spice_session_get_connection_id(SpiceSession * session)2379 int spice_session_get_connection_id(SpiceSession *session)
2380 {
2381     g_return_val_if_fail(SPICE_IS_SESSION(session), -1);
2382 
2383     SpiceSessionPrivate *s = session->priv;
2384 
2385     return s->connection_id;
2386 }
2387 
2388 G_GNUC_INTERNAL
spice_session_get_mm_time(SpiceSession * session)2389 guint32 spice_session_get_mm_time(SpiceSession *session)
2390 {
2391     g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
2392 
2393     SpiceSessionPrivate *s = session->priv;
2394 
2395     /* FIXME: we may want to estimate the drift of clocks, and well,
2396        do something better than this trivial approach */
2397     return (g_get_monotonic_time() - s->mm_time_offset) / 1000;
2398 }
2399 
2400 #define MM_TIME_DIFF_RESET_THRESH 500 // 0.5 sec
2401 
2402 G_GNUC_INTERNAL
spice_session_set_mm_time(SpiceSession * session,guint32 time)2403 void spice_session_set_mm_time(SpiceSession *session, guint32 time)
2404 {
2405     g_return_if_fail(SPICE_IS_SESSION(session));
2406 
2407     SpiceSessionPrivate *s = session->priv;
2408     guint32 old_time;
2409 
2410     old_time = spice_session_get_mm_time(session);
2411 
2412     s->mm_time_offset = g_get_monotonic_time() - time * (guint64) 1000;
2413     SPICE_DEBUG("set mm time: %u", time);
2414     if (spice_mmtime_diff(time, old_time + MM_TIME_DIFF_RESET_THRESH) > 0 ||
2415         spice_mmtime_diff(time, old_time) < 0) {
2416         SPICE_DEBUG("%s: mm-time-reset, old %u, new %u", __FUNCTION__, old_time, time);
2417         g_coroutine_signal_emit(session, signals[SPICE_SESSION_MM_TIME_RESET], 0);
2418     }
2419 }
2420 
2421 G_GNUC_INTERNAL
spice_session_set_port(SpiceSession * session,int port,gboolean tls)2422 void spice_session_set_port(SpiceSession *session, int port, gboolean tls)
2423 {
2424     const char *prop = tls ? "tls-port" : "port";
2425     char *tmp;
2426 
2427     g_return_if_fail(SPICE_IS_SESSION(session));
2428 
2429     /* old spicec client doesn't accept port == 0, see Migrate::start */
2430     tmp = port > 0 ? g_strdup_printf("%d", port) : NULL;
2431     g_object_set(session, prop, tmp, NULL);
2432     g_free(tmp);
2433 }
2434 
2435 G_GNUC_INTERNAL
spice_session_get_pubkey(SpiceSession * session,guint8 ** pubkey,guint * size)2436 void spice_session_get_pubkey(SpiceSession *session, guint8 **pubkey, guint *size)
2437 {
2438     g_return_if_fail(SPICE_IS_SESSION(session));
2439     g_return_if_fail(pubkey != NULL);
2440     g_return_if_fail(size != NULL);
2441 
2442     SpiceSessionPrivate *s = session->priv;
2443 
2444     *pubkey = s->pubkey ? s->pubkey->data : NULL;
2445     *size = s->pubkey ? s->pubkey->len : 0;
2446 }
2447 
2448 G_GNUC_INTERNAL
spice_session_get_ca(SpiceSession * session,guint8 ** ca,guint * size)2449 void spice_session_get_ca(SpiceSession *session, guint8 **ca, guint *size)
2450 {
2451     g_return_if_fail(SPICE_IS_SESSION(session));
2452     g_return_if_fail(ca != NULL);
2453     g_return_if_fail(size != NULL);
2454 
2455     SpiceSessionPrivate *s = session->priv;
2456 
2457     *ca = s->ca ? s->ca->data : NULL;
2458     *size = s->ca ? s->ca->len : 0;
2459 }
2460 
2461 G_GNUC_INTERNAL
spice_session_get_verify(SpiceSession * session)2462 guint spice_session_get_verify(SpiceSession *session)
2463 {
2464     g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
2465 
2466     SpiceSessionPrivate *s = session->priv;
2467 
2468     return s->verify;
2469 }
2470 
2471 G_GNUC_INTERNAL
spice_session_set_migration_state(SpiceSession * session,SpiceSessionMigration state)2472 void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state)
2473 {
2474     g_return_if_fail(SPICE_IS_SESSION(session));
2475 
2476     SpiceSessionPrivate *s = session->priv;
2477 
2478     if (state == SPICE_SESSION_MIGRATION_CONNECTING)
2479         s->for_migration = true;
2480 
2481     s->migration_state = state;
2482     g_coroutine_object_notify(G_OBJECT(session), "migration-state");
2483 }
2484 
2485 G_GNUC_INTERNAL
spice_session_get_username(SpiceSession * session)2486 const gchar* spice_session_get_username(SpiceSession *session)
2487 {
2488     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2489 
2490     SpiceSessionPrivate *s = session->priv;
2491 
2492     return s->username;
2493 }
2494 
2495 G_GNUC_INTERNAL
spice_session_get_password(SpiceSession * session)2496 const gchar* spice_session_get_password(SpiceSession *session)
2497 {
2498     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2499 
2500     SpiceSessionPrivate *s = session->priv;
2501 
2502     return s->password;
2503 }
2504 
2505 G_GNUC_INTERNAL
spice_session_get_host(SpiceSession * session)2506 const gchar* spice_session_get_host(SpiceSession *session)
2507 {
2508     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2509 
2510     SpiceSessionPrivate *s = session->priv;
2511 
2512     return s->host;
2513 }
2514 
2515 G_GNUC_INTERNAL
spice_session_get_cert_subject(SpiceSession * session)2516 const gchar* spice_session_get_cert_subject(SpiceSession *session)
2517 {
2518     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2519 
2520     SpiceSessionPrivate *s = session->priv;
2521 
2522     return s->cert_subject;
2523 }
2524 
2525 G_GNUC_INTERNAL
spice_session_get_ciphers(SpiceSession * session)2526 const gchar* spice_session_get_ciphers(SpiceSession *session)
2527 {
2528     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2529 
2530     SpiceSessionPrivate *s = session->priv;
2531 
2532     return s->ciphers;
2533 }
2534 
2535 G_GNUC_INTERNAL
spice_session_get_ca_file(SpiceSession * session)2536 const gchar* spice_session_get_ca_file(SpiceSession *session)
2537 {
2538     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2539 
2540     SpiceSessionPrivate *s = session->priv;
2541 
2542     return s->ca_file;
2543 }
2544 
2545 G_GNUC_INTERNAL
spice_session_get_caches(SpiceSession * session,display_cache ** images,SpiceGlzDecoderWindow ** glz_window)2546 void spice_session_get_caches(SpiceSession *session,
2547                               display_cache **images,
2548                               SpiceGlzDecoderWindow **glz_window)
2549 {
2550     g_return_if_fail(SPICE_IS_SESSION(session));
2551 
2552     SpiceSessionPrivate *s = session->priv;
2553 
2554     if (images)
2555         *images = s->images;
2556     if (glz_window)
2557         *glz_window = s->glz_window;
2558 }
2559 
2560 G_GNUC_INTERNAL
spice_session_set_caches_hints(SpiceSession * session,uint32_t pci_ram_size,uint32_t n_display_channels)2561 void spice_session_set_caches_hints(SpiceSession *session,
2562                                     uint32_t pci_ram_size,
2563                                     uint32_t n_display_channels)
2564 {
2565     g_return_if_fail(SPICE_IS_SESSION(session));
2566 
2567     SpiceSessionPrivate *s = session->priv;
2568 
2569     s->pci_ram_size = pci_ram_size;
2570     s->n_display_channels = n_display_channels;
2571 
2572     /* TODO: when setting cache and window size, we should consider the client's
2573      *       available memory and the number of display channels */
2574     if (s->images_cache_size == 0) {
2575         s->images_cache_size = IMAGES_CACHE_SIZE_DEFAULT;
2576     }
2577 
2578     if (s->glz_window_size == 0) {
2579         s->glz_window_size = MIN(MAX_GLZ_WINDOW_SIZE_DEFAULT, pci_ram_size / 2);
2580         s->glz_window_size = MAX(MIN_GLZ_WINDOW_SIZE_DEFAULT, s->glz_window_size);
2581     }
2582 }
2583 
2584 G_GNUC_INTERNAL
spice_session_get_n_display_channels(SpiceSession * session)2585 guint spice_session_get_n_display_channels(SpiceSession *session)
2586 {
2587     g_return_val_if_fail(session != NULL, 0);
2588 
2589     return session->priv->n_display_channels;
2590 }
2591 
2592 G_GNUC_INTERNAL
spice_session_set_uuid(SpiceSession * session,guint8 uuid[16])2593 void spice_session_set_uuid(SpiceSession *session, guint8 uuid[16])
2594 {
2595     g_return_if_fail(SPICE_IS_SESSION(session));
2596 
2597     SpiceSessionPrivate *s = session->priv;
2598 
2599     memcpy(s->uuid, uuid, sizeof(s->uuid));
2600 
2601     g_coroutine_object_notify(G_OBJECT(session), "uuid");
2602 }
2603 
2604 G_GNUC_INTERNAL
spice_session_set_name(SpiceSession * session,const gchar * name)2605 void spice_session_set_name(SpiceSession *session, const gchar *name)
2606 {
2607     g_return_if_fail(SPICE_IS_SESSION(session));
2608 
2609     SpiceSessionPrivate *s = session->priv;
2610 
2611     g_free(s->name);
2612     s->name = g_strdup(name);
2613 
2614     g_coroutine_object_notify(G_OBJECT(session), "name");
2615 }
2616 
2617 G_GNUC_INTERNAL
spice_session_sync_playback_latency(SpiceSession * session)2618 void spice_session_sync_playback_latency(SpiceSession *session)
2619 {
2620     g_return_if_fail(SPICE_IS_SESSION(session));
2621 
2622     SpiceSessionPrivate *s = session->priv;
2623 
2624     if (s->playback_channel &&
2625         spice_playback_channel_is_active(s->playback_channel)) {
2626         spice_playback_channel_sync_latency(s->playback_channel);
2627     } else {
2628         SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
2629     }
2630 }
2631 
2632 G_GNUC_INTERNAL
spice_session_is_playback_active(SpiceSession * session)2633 gboolean spice_session_is_playback_active(SpiceSession *session)
2634 {
2635     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2636 
2637     SpiceSessionPrivate *s = session->priv;
2638 
2639     return (s->playback_channel &&
2640         spice_playback_channel_is_active(s->playback_channel));
2641 }
2642 
2643 G_GNUC_INTERNAL
spice_session_get_playback_latency(SpiceSession * session)2644 guint32 spice_session_get_playback_latency(SpiceSession *session)
2645 {
2646     g_return_val_if_fail(SPICE_IS_SESSION(session), 0);
2647 
2648     SpiceSessionPrivate *s = session->priv;
2649 
2650     if (s->playback_channel &&
2651         spice_playback_channel_is_active(s->playback_channel)) {
2652         return spice_playback_channel_get_latency(s->playback_channel);
2653     } else {
2654         SPICE_DEBUG("%s: not implemented when there isn't audio playback", __FUNCTION__);
2655         return 0;
2656     }
2657 }
2658 
spice_session_get_shared_dir(SpiceSession * session)2659 static const gchar* spice_session_get_shared_dir(SpiceSession *session)
2660 {
2661     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2662 
2663     SpiceSessionPrivate *s = session->priv;
2664 
2665     return s->shared_dir;
2666 }
2667 
spice_session_set_shared_dir(SpiceSession * session,const gchar * dir)2668 static void spice_session_set_shared_dir(SpiceSession *session, const gchar *dir)
2669 {
2670     g_return_if_fail(SPICE_IS_SESSION(session));
2671 
2672     SpiceSessionPrivate *s = session->priv;
2673 
2674     g_free(s->shared_dir);
2675     s->shared_dir = g_strdup(dir);
2676 }
2677 
2678 G_GNUC_INTERNAL
spice_audio_data_mode_to_string(gint mode)2679 const gchar* spice_audio_data_mode_to_string(gint mode)
2680 {
2681     static const char *str[] = {
2682         [ SPICE_AUDIO_DATA_MODE_INVALID ] = "invalid",
2683         [ SPICE_AUDIO_DATA_MODE_RAW ] = "raw",
2684         [ SPICE_AUDIO_DATA_MODE_CELT_0_5_1 ] = "celt",
2685         [ SPICE_AUDIO_DATA_MODE_OPUS ] = "opus",
2686     };
2687     return (mode >= 0 && mode < G_N_ELEMENTS(str)) ? str[mode] : "unknown audio codec";
2688 }
2689 
2690 
2691 /**
2692  * spice_session_get_proxy_uri:
2693  * @session: a #SpiceSession
2694  *
2695  * Gets the @session proxy uri.
2696  *
2697  * Returns: (transfer none): the session proxy #SpiceURI or %NULL.
2698  * Since: 0.24
2699  **/
spice_session_get_proxy_uri(SpiceSession * session)2700 SpiceURI *spice_session_get_proxy_uri(SpiceSession *session)
2701 {
2702     SpiceSessionPrivate *s;
2703 
2704     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2705     g_return_val_if_fail(session->priv != NULL, NULL);
2706 
2707     s = session->priv;
2708 
2709     return s->proxy;
2710 }
2711 
2712 /**
2713  * spice_audio_get:
2714  * @session: the #SpiceSession to connect to
2715  * @context: (allow-none): a #GMainContext to attach to (or %NULL for default).
2716  *
2717  * Gets the #SpiceAudio associated with the passed in #SpiceSession.
2718  * A new #SpiceAudio instance will be created the first time this
2719  * function is called for a certain #SpiceSession.
2720  *
2721  * Note that this function returns a weak reference, which should not be used
2722  * after the #SpiceSession itself has been unref-ed by the caller.
2723  *
2724  * Returns: (transfer none): a weak reference to a #SpiceAudio
2725  * instance or %NULL if failed.
2726  **/
spice_audio_get(SpiceSession * session,GMainContext * context)2727 SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context)
2728 {
2729     static GMutex mutex;
2730     SpiceAudio *self;
2731 
2732     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2733 
2734     g_mutex_lock(&mutex);
2735     self = session->priv->audio_manager;
2736     if (self == NULL) {
2737         self = spice_audio_new_priv(session, context, NULL);
2738         session->priv->audio_manager = self;
2739     }
2740     g_mutex_unlock(&mutex);
2741 
2742     return self;
2743 }
2744 
2745 /**
2746  * spice_usb_device_manager_get:
2747  * @session: #SpiceSession for which to get the #SpiceUsbDeviceManager
2748  * @err: (allow-none): a return location for #GError, or %NULL.
2749  *
2750  * Gets the #SpiceUsbDeviceManager associated with the passed in #SpiceSession.
2751  * A new #SpiceUsbDeviceManager instance will be created the first time this
2752  * function is called for a certain #SpiceSession.
2753  *
2754  * Note that this function returns a weak reference, which should not be used
2755  * after the #SpiceSession itself has been unref-ed by the caller.
2756  *
2757  * Returns: (transfer none): a weak reference to the #SpiceUsbDeviceManager associated with the passed in #SpiceSession
2758  */
spice_usb_device_manager_get(SpiceSession * session,GError ** err)2759 SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
2760                                                     GError **err)
2761 {
2762     SpiceUsbDeviceManager *self;
2763     static GMutex mutex;
2764 
2765     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2766     g_return_val_if_fail(err == NULL || *err == NULL, NULL);
2767 
2768     g_mutex_lock(&mutex);
2769     self = session->priv->usb_manager;
2770     if (self == NULL) {
2771         self = g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, err,
2772                               "session", session, NULL);
2773         session->priv->usb_manager = self;
2774     }
2775     g_mutex_unlock(&mutex);
2776 
2777     return self;
2778 }
2779 
2780 G_GNUC_INTERNAL
spice_session_get_audio_enabled(SpiceSession * session)2781 gboolean spice_session_get_audio_enabled(SpiceSession *session)
2782 {
2783     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2784 
2785     return session->priv->audio;
2786 }
2787 
2788 G_GNUC_INTERNAL
spice_session_get_usbredir_enabled(SpiceSession * session)2789 gboolean spice_session_get_usbredir_enabled(SpiceSession *session)
2790 {
2791     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2792 
2793     return session->priv->usbredir;
2794 }
2795 
2796 G_GNUC_INTERNAL
spice_session_get_smartcard_enabled(SpiceSession * session)2797 gboolean spice_session_get_smartcard_enabled(SpiceSession *session)
2798 {
2799     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2800 
2801     return session->priv->smartcard;
2802 }
2803 
2804 G_GNUC_INTERNAL
spice_session_get_webdav_server(SpiceSession * session)2805 PhodavServer* spice_session_get_webdav_server(SpiceSession *session)
2806 {
2807     SpiceSessionPrivate *priv;
2808 
2809     g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
2810     priv = session->priv;
2811 
2812 #ifdef USE_PHODAV
2813     static GMutex mutex;
2814 
2815     const gchar *shared_dir = spice_session_get_shared_dir(session);
2816     if (shared_dir == NULL) {
2817         SPICE_DEBUG("No shared dir set, not creating webdav server");
2818         return NULL;
2819     }
2820 
2821     g_mutex_lock(&mutex);
2822 
2823     if (priv->webdav)
2824         goto end;
2825 
2826     priv->webdav = phodav_server_new(shared_dir);
2827     g_object_bind_property(session,  "share-dir-ro",
2828                            priv->webdav, "read-only",
2829                            G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
2830     g_object_bind_property(session,  "shared-dir",
2831                            priv->webdav, "root",
2832                            G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL);
2833 
2834 end:
2835     g_mutex_unlock(&mutex);
2836 #endif
2837 
2838     return priv->webdav;
2839 }
2840 
2841 /**
2842  * spice_session_is_for_migration:
2843  * @session: a Spice session
2844  *
2845  * During seamless migration, channels may be created to establish a
2846  * connection with the target, but they are temporary and should only
2847  * handle migration steps. In order to avoid other interactions with
2848  * the client, channels should check this value.
2849  *
2850  * Returns: %TRUE if the session is a copy created during migration
2851  * Since: 0.27
2852  **/
spice_session_is_for_migration(SpiceSession * session)2853 gboolean spice_session_is_for_migration(SpiceSession *session)
2854 {
2855     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2856 
2857     return session->priv->for_migration;
2858 }
2859 
2860 G_GNUC_INTERNAL
spice_session_set_migration_session(SpiceSession * session,SpiceSession * mig_session)2861 gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session)
2862 {
2863     g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
2864     g_return_val_if_fail(SPICE_IS_SESSION(mig_session), FALSE);
2865     g_return_val_if_fail(session->priv->migration == NULL, FALSE);
2866 
2867     session->priv->migration = mig_session;
2868 
2869     return TRUE;
2870 }
2871