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