1 /*
2 * Virt Viewer: A virtual machine console viewer
3 *
4 * Copyright (C) 2007-2012 Red Hat, Inc.
5 * Copyright (C) 2009-2012 Daniel P. Berrange
6 * Copyright (C) 2010 Marc-André Lureau
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 * Author: Daniel P. Berrange <berrange@redhat.com>
23 */
24
25 #include <config.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtk.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <locale.h>
35 #include <gio/gio.h>
36 #include <glib/gprintf.h>
37 #include <glib/gi18n.h>
38 #include <errno.h>
39
40 #ifndef G_OS_WIN32
41 #include <glib-unix.h>
42 #include <sys/socket.h>
43 #include <sys/un.h>
44 #else
45 #include <windows.h>
46 #endif
47
48 #include "virt-viewer-app.h"
49 #include "virt-viewer-resources.h"
50 #include "virt-viewer-auth.h"
51 #include "virt-viewer-window.h"
52 #include "virt-viewer-session.h"
53 #include "virt-viewer-util.h"
54 #ifdef HAVE_GTK_VNC
55 #include "virt-viewer-session-vnc.h"
56 #endif
57 #ifdef HAVE_SPICE_GTK
58 #include "virt-viewer-session-spice.h"
59 #endif
60
61 #include "virt-viewer-display-vte.h"
62
63 gboolean doDebug = FALSE;
64
65 /* Signal handlers for about dialog */
66 void virt_viewer_app_about_close(GtkWidget *dialog, VirtViewerApp *self);
67 void virt_viewer_app_about_delete(GtkWidget *dialog, void *dummy, VirtViewerApp *self);
68
69
70 /* Internal methods */
71 static void virt_viewer_app_connected(VirtViewerSession *session,
72 VirtViewerApp *self);
73 static void virt_viewer_app_initialized(VirtViewerSession *session,
74 VirtViewerApp *self);
75 static void virt_viewer_app_disconnected(VirtViewerSession *session,
76 const gchar *msg,
77 VirtViewerApp *self);
78 static void virt_viewer_app_auth_refused(VirtViewerSession *session,
79 const char *msg,
80 VirtViewerApp *self);
81 static void virt_viewer_app_auth_unsupported(VirtViewerSession *session,
82 const char *msg,
83 VirtViewerApp *self);
84 static void virt_viewer_app_usb_failed(VirtViewerSession *session,
85 const char *msg,
86 VirtViewerApp *self);
87
88 static void virt_viewer_app_server_cut_text(VirtViewerSession *session,
89 const gchar *text,
90 VirtViewerApp *self);
91 static void virt_viewer_app_bell(VirtViewerSession *session,
92 VirtViewerApp *self);
93
94 static void virt_viewer_app_cancelled(VirtViewerSession *session,
95 VirtViewerApp *self);
96
97 static void virt_viewer_app_channel_open(VirtViewerSession *session,
98 VirtViewerSessionChannel *channel,
99 VirtViewerApp *self);
100 static void virt_viewer_app_update_pretty_address(VirtViewerApp *self);
101 static void virt_viewer_app_set_fullscreen(VirtViewerApp *self, gboolean fullscreen);
102 static void virt_viewer_app_update_menu_displays(VirtViewerApp *self);
103 static void virt_viewer_update_smartcard_accels(VirtViewerApp *self);
104 static void virt_viewer_update_usbredir_accels(VirtViewerApp *self);
105 static void virt_viewer_app_add_option_entries(VirtViewerApp *self, GOptionContext *context, GOptionGroup *group);
106 static VirtViewerWindow *virt_viewer_app_get_nth_window(VirtViewerApp *self, gint nth);
107 static VirtViewerWindow *virt_viewer_app_get_vte_window(VirtViewerApp *self, const gchar *name);
108 static void virt_viewer_app_set_actions_sensitive(VirtViewerApp *self);
109 static void virt_viewer_app_set_display_auto_resize(VirtViewerApp *self,
110 VirtViewerDisplay *display);
111
112 /* Application actions */
113 static void virt_viewer_app_action_monitor(GSimpleAction *act,
114 GVariant *param,
115 gpointer opaque);
116 static void virt_viewer_app_action_vte(GSimpleAction *act,
117 GVariant *state,
118 gpointer opaque);
119
120
121 typedef struct _VirtViewerAppPrivate VirtViewerAppPrivate;
122 struct _VirtViewerAppPrivate {
123 VirtViewerWindow *main_window;
124 GtkWidget *main_notebook;
125 GList *windows;
126 GHashTable *displays; /* !vte */
127 GHashTable *initial_display_map;
128 gchar *clipboard;
129 GtkWidget *preferences;
130 GtkFileChooser *preferences_shared_folder;
131 GResource *resource;
132 gboolean direct;
133 gboolean verbose;
134 gboolean authretry;
135 gboolean started;
136 gboolean fullscreen;
137 gboolean attach;
138 gboolean shared;
139 gboolean quitting;
140 gboolean kiosk;
141 gboolean vm_ui;
142 gboolean vm_running;
143 gboolean initialized;
144
145 VirtViewerSession *session;
146 gboolean active;
147 gboolean connected;
148 gboolean cancelled;
149 char *unixsock;
150 char *guri; /* prefered over ghost:gport */
151 char *ghost;
152 char *gport;
153 char *gtlsport;
154 char *host; /* ssh */
155 int port;/* ssh */
156 char *user; /* ssh */
157 char *transport;
158 char *pretty_address;
159 gchar *guest_name;
160 gboolean grabbed;
161 char *title;
162 char *uuid;
163 VirtViewerCursor cursor;
164
165 GKeyFile *config;
166 gchar *config_file;
167
168 gchar *release_cursor_display_hotkey;
169 gchar **insert_smartcard_accel;
170 gchar **remove_smartcard_accel;
171 gchar **usb_device_reset_accel;
172 gboolean quit_on_disconnect;
173 gboolean supports_share_clipboard;
174 VirtViewerKeyMapping *keyMappings;
175 };
176
177
178 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(VirtViewerApp, virt_viewer_app, GTK_TYPE_APPLICATION)
179
180 enum {
181 PROP_0,
182 PROP_VERBOSE,
183 PROP_SESSION,
184 PROP_GUEST_NAME,
185 PROP_GURI,
186 PROP_FULLSCREEN,
187 PROP_TITLE,
188 PROP_RELEASE_CURSOR_DISPLAY_HOTKEY,
189 PROP_KIOSK,
190 PROP_QUIT_ON_DISCONNECT,
191 PROP_UUID,
192 PROP_VM_UI,
193 PROP_VM_RUNNING,
194 PROP_CONFIG_SHARE_CLIPBOARD,
195 PROP_SUPPORTS_SHARE_CLIPBOARD,
196 };
197
198 void
virt_viewer_app_set_debug(gboolean debug)199 virt_viewer_app_set_debug(gboolean debug)
200 {
201 if (debug) {
202 const gchar *doms = g_getenv("G_MESSAGES_DEBUG");
203 if (!doms) {
204 g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1);
205 } else if (!g_str_equal(doms, "all") &&
206 !strstr(doms, G_LOG_DOMAIN)) {
207 gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN);
208 g_setenv("G_MESSAGES_DEBUG", newdoms, 1);
209 g_free(newdoms);
210 }
211 }
212 doDebug = debug;
213 }
214
215 static GtkWidget* G_GNUC_PRINTF(2, 3)
virt_viewer_app_make_message_dialog(VirtViewerApp * self,const char * fmt,...)216 virt_viewer_app_make_message_dialog(VirtViewerApp *self,
217 const char *fmt, ...)
218 {
219 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
220 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
221 GtkWindow *window = GTK_WINDOW(virt_viewer_window_get_window(priv->main_window));
222 GtkWidget *dialog;
223 char *msg;
224 va_list vargs;
225
226 va_start(vargs, fmt);
227 msg = g_strdup_vprintf(fmt, vargs);
228 va_end(vargs);
229
230 dialog = gtk_message_dialog_new(window,
231 GTK_DIALOG_MODAL |
232 GTK_DIALOG_DESTROY_WITH_PARENT,
233 GTK_MESSAGE_ERROR,
234 GTK_BUTTONS_OK,
235 "%s",
236 msg);
237
238 g_free(msg);
239
240 return dialog;
241 }
242
243 void
virt_viewer_app_simple_message_dialog(VirtViewerApp * self,const char * fmt,...)244 virt_viewer_app_simple_message_dialog(VirtViewerApp *self,
245 const char *fmt, ...)
246 {
247 GtkWidget *dialog;
248 char *msg;
249 va_list vargs;
250
251 va_start(vargs, fmt);
252 msg = g_strdup_vprintf(fmt, vargs);
253 va_end(vargs);
254
255 dialog = virt_viewer_app_make_message_dialog(self, "%s", msg);
256 gtk_dialog_run(GTK_DIALOG(dialog));
257 gtk_widget_destroy(dialog);
258
259 g_free(msg);
260 }
261
262 static void
virt_viewer_app_save_config(VirtViewerApp * self)263 virt_viewer_app_save_config(VirtViewerApp *self)
264 {
265 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
266 GError *error = NULL;
267 gchar *dir, *data;
268
269 dir = g_path_get_dirname(priv->config_file);
270 if (g_mkdir_with_parents(dir, S_IRWXU) == -1)
271 g_warning("failed to create config directory");
272 g_free(dir);
273
274 if (priv->uuid && priv->guest_name && g_key_file_has_group(priv->config, priv->uuid)) {
275 // if there's no comment for this uuid settings group, add a comment
276 // with the vm name so user can make sense of it later.
277 gchar *comment = g_key_file_get_comment(priv->config, priv->uuid, NULL, &error);
278 if (error) {
279 g_debug("Unable to get comment from key file: %s", error->message);
280 g_clear_error(&error);
281 }
282
283 if (comment == NULL ||
284 (comment != NULL && g_strstr_len(comment, -1, priv->guest_name) == NULL)) {
285 /* Note that this function appends the guest's name string as last
286 * comment in case there were comments there already */
287 g_key_file_set_comment(priv->config, priv->uuid, NULL, priv->guest_name, NULL);
288 }
289 g_free(comment);
290 }
291
292 if ((data = g_key_file_to_data(priv->config, NULL, &error)) == NULL ||
293 !g_file_set_contents(priv->config_file, data, -1, &error)) {
294 g_warning("Couldn't save configuration: %s", error->message);
295 g_clear_error(&error);
296 }
297 g_free(data);
298 }
299
300 static void
virt_viewer_app_quit(VirtViewerApp * self)301 virt_viewer_app_quit(VirtViewerApp *self)
302 {
303 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
304 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
305 g_return_if_fail(!priv->kiosk);
306
307 virt_viewer_app_save_config(self);
308
309 if (priv->vm_ui && virt_viewer_session_has_vm_action(priv->session,
310 VIRT_VIEWER_SESSION_VM_ACTION_QUIT)) {
311 virt_viewer_session_vm_action(VIRT_VIEWER_SESSION(priv->session),
312 VIRT_VIEWER_SESSION_VM_ACTION_QUIT);
313 }
314
315 priv->quitting = TRUE;
316 if (priv->session) {
317 virt_viewer_session_close(VIRT_VIEWER_SESSION(priv->session));
318 if (priv->connected) {
319 return;
320 }
321 }
322
323 g_application_quit(G_APPLICATION(self));
324 }
325
326 static gint
get_n_client_monitors(void)327 get_n_client_monitors(void)
328 {
329 return gdk_screen_get_n_monitors(gdk_screen_get_default());
330 }
331
virt_viewer_app_get_initial_displays(VirtViewerApp * self)332 GList* virt_viewer_app_get_initial_displays(VirtViewerApp* self)
333 {
334 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
335
336 if (!priv->initial_display_map) {
337 GList *l = NULL;
338 gint i;
339 gint n = get_n_client_monitors();
340
341 for (i = 0; i < n; i++) {
342 l = g_list_append(l, GINT_TO_POINTER(i));
343 }
344 return l;
345 }
346 return g_hash_table_get_keys(priv->initial_display_map);
347 }
348
virt_viewer_app_get_first_monitor(VirtViewerApp * self)349 static gint virt_viewer_app_get_first_monitor(VirtViewerApp *self)
350 {
351 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
352
353 if (priv->fullscreen && priv->initial_display_map) {
354 gint first = G_MAXINT;
355 GHashTableIter iter;
356 gpointer key, value;
357 g_hash_table_iter_init(&iter, priv->initial_display_map);
358 while (g_hash_table_iter_next(&iter, &key, &value)) {
359 gint monitor = GPOINTER_TO_INT(key);
360 first = MIN(first, monitor);
361 }
362 return first;
363 }
364 return 0;
365 }
366
virt_viewer_app_get_initial_monitor_for_display(VirtViewerApp * self,gint display)367 gint virt_viewer_app_get_initial_monitor_for_display(VirtViewerApp* self, gint display)
368 {
369 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
370 gint monitor = display;
371
372 if (priv->initial_display_map) {
373 gpointer value = NULL;
374 if (g_hash_table_lookup_extended(priv->initial_display_map, GINT_TO_POINTER(display), NULL, &value)) {
375 monitor = GPOINTER_TO_INT(value);
376 } else {
377 monitor = -1;
378 }
379 }
380 if (monitor >= get_n_client_monitors()) {
381 g_debug("monitor for display %d does not exist", display);
382 monitor = -1;
383 }
384
385 return monitor;
386 }
387
388 static void
app_window_try_fullscreen(VirtViewerApp * self G_GNUC_UNUSED,VirtViewerWindow * win,gint nth)389 app_window_try_fullscreen(VirtViewerApp *self G_GNUC_UNUSED,
390 VirtViewerWindow *win, gint nth)
391 {
392 gint monitor = virt_viewer_app_get_initial_monitor_for_display(self, nth);
393 if (monitor == -1) {
394 g_debug("skipping fullscreen for display %d", nth);
395 return;
396 }
397
398 virt_viewer_window_enter_fullscreen(win, monitor);
399 }
400
401 static GHashTable*
virt_viewer_app_get_monitor_mapping_for_section(VirtViewerApp * self,const gchar * section)402 virt_viewer_app_get_monitor_mapping_for_section(VirtViewerApp *self, const gchar *section)
403 {
404 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
405 GError *error = NULL;
406 gsize nmappings = 0;
407 gchar **mappings = NULL;
408 GHashTable *mapping = NULL;
409
410 mappings = g_key_file_get_string_list(priv->config,
411 section, "monitor-mapping", &nmappings, &error);
412 if (error) {
413 if (error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND
414 && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)
415 g_warning("Error reading monitor assignments for %s: %s", section, error->message);
416 g_clear_error(&error);
417 } else {
418 mapping = virt_viewer_parse_monitor_mappings(mappings, nmappings, get_n_client_monitors());
419 }
420 g_strfreev(mappings);
421
422 return mapping;
423 }
424
425 /*
426 * save the association display/monitor in the config for reuse on next connection
427 */
virt_viewer_app_set_monitor_mapping_for_display(VirtViewerApp * self,VirtViewerDisplay * display)428 static void virt_viewer_app_set_monitor_mapping_for_display(VirtViewerApp *self,
429 VirtViewerDisplay *display)
430 {
431 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
432 GError *error = NULL;
433 gsize nmappings = 0;
434 gchar **mappings = NULL;
435 gchar **tokens = NULL;
436
437 int i;
438
439 gint virt_viewer_display = virt_viewer_display_get_nth(display);
440 gint virt_viewer_monitor = virt_viewer_display_get_monitor(display);
441
442 if (virt_viewer_monitor == -1) {
443 // find which monitor the window is on
444 #if GTK_CHECK_VERSION(3, 22, 0)
445 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
446 GdkDisplay *gdk_dpy = gdk_display_get_default();
447 VirtViewerWindow *vvWindow = virt_viewer_app_get_nth_window(self, virt_viewer_display);
448 GdkWindow *window = gtk_widget_get_window(
449 GTK_WIDGET(virt_viewer_window_get_window(vvWindow)));
450 GdkMonitor *pMonitor = gdk_display_get_monitor_at_window(gdk_dpy, window);
451
452 // compare this monitor with the list of monitors from the display
453 gint num_monitors = gdk_display_get_n_monitors(gdk_dpy);
454 if (num_monitors > 0) {
455 for (i = 0; i < num_monitors; i++) {
456 GdkMonitor *tmp = gdk_display_get_monitor(gdk_dpy, i);
457 if (tmp == pMonitor) {
458 virt_viewer_monitor = i;
459 break;
460 }
461 }
462 }
463 G_GNUC_END_IGNORE_DEPRECATIONS
464 #endif
465 if (virt_viewer_monitor == -1) {
466 // could not determine the monitor - abort
467 return;
468 }
469 }
470
471 // IDs are 0 based, but the config uses 1-based numbering
472 virt_viewer_display++;
473 virt_viewer_monitor++;
474
475 mappings = g_key_file_get_string_list(priv->config, priv->uuid,
476 "monitor-mapping", &nmappings, &error);
477 if (error) {
478 if (error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND
479 && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)
480 g_warning("Error reading monitor assignments for %s: %s",
481 priv->uuid, error->message);
482 g_clear_error(&error);
483
484 // no mapping available for the VM: we will create a new one
485 }
486
487 for (i = 0; i < nmappings; i++) {
488 gchar *endptr = NULL;
489 gint disp, monitor;
490
491 tokens = g_strsplit(mappings[i], ":", 2);
492 if (g_strv_length(tokens) != 2) {
493 // config error
494 g_strfreev(tokens);
495 goto end;
496 }
497
498 disp = strtol(tokens[0], &endptr, 10);
499 if ((endptr && *endptr != '\0') || disp < 1) {
500 // config error
501 g_strfreev(tokens);
502 goto end;
503 }
504
505 if (disp == virt_viewer_display) {
506 // found the display we have to save. Verify if it changed mappings
507 monitor = strtol(tokens[1], &endptr, 10);
508 if ((endptr && *endptr != '\0') || monitor < 1) {
509 // config error
510 g_strfreev(tokens);
511 goto end;
512 }
513
514 g_strfreev(tokens);
515 if (monitor == virt_viewer_monitor) {
516 // no change in the config - just exit
517 goto end;
518 }
519
520 // save the modified mapping
521 g_snprintf(mappings[i], strlen(mappings[i]) + 1, "%d:%d",
522 virt_viewer_display, virt_viewer_monitor);
523 break;
524 }
525 g_strfreev(tokens);
526 }
527 if (i == nmappings) {
528 // this display was not saved yet - add it
529 nmappings++;
530 mappings = g_realloc(mappings, (nmappings + 1) * sizeof(gchar *));
531 mappings[nmappings - 1] = g_strdup_printf("%d:%d", virt_viewer_display, virt_viewer_monitor);
532 mappings[nmappings] = NULL;
533 }
534 g_key_file_set_string_list(priv->config, priv->uuid, "monitor-mapping",
535 (const gchar * const *) mappings, nmappings);
536 virt_viewer_app_save_config(self);
537
538 end:
539 g_strfreev(mappings);
540 }
541
542 static
virt_viewer_app_apply_monitor_mapping(VirtViewerApp * self)543 void virt_viewer_app_apply_monitor_mapping(VirtViewerApp *self)
544 {
545 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
546 GHashTable *mapping = NULL;
547
548 // apply mapping only in fullscreen
549 if (!virt_viewer_app_get_fullscreen(self))
550 return;
551
552 mapping = virt_viewer_app_get_monitor_mapping_for_section(self, priv->uuid);
553 if (!mapping) {
554 g_debug("No guest-specific fullscreen config, using fallback");
555 mapping = virt_viewer_app_get_monitor_mapping_for_section(self, "fallback");
556 }
557
558 if (priv->initial_display_map)
559 g_hash_table_unref(priv->initial_display_map);
560
561 priv->initial_display_map = mapping;
562
563 // if we're changing our initial display map, move any existing windows to
564 // the appropriate monitors according to the per-vm configuration
565 if (mapping) {
566 GList *l;
567 gint i = 0;
568
569 for (l = priv->windows; l; l = l->next) {
570 app_window_try_fullscreen(self, VIRT_VIEWER_WINDOW(l->data), i);
571 i++;
572 }
573 }
574 }
575
576 static
virt_viewer_app_set_uuid_string(VirtViewerApp * self,const gchar * uuid_string)577 void virt_viewer_app_set_uuid_string(VirtViewerApp *self, const gchar *uuid_string)
578 {
579 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
580 if (g_strcmp0(priv->uuid, uuid_string) == 0)
581 return;
582
583 g_debug("%s: UUID changed to %s", G_STRFUNC, uuid_string);
584
585 g_free(priv->uuid);
586 priv->uuid = g_strdup(uuid_string);
587
588 virt_viewer_app_apply_monitor_mapping(self);
589 }
590
591 static
virt_viewer_app_set_keymap(VirtViewerApp * self,const gchar * keymap_string)592 void virt_viewer_app_set_keymap(VirtViewerApp *self, const gchar *keymap_string)
593 {
594 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
595 gchar **key, **keymaps = NULL, **valkey, **valuekeys = NULL;
596 VirtViewerKeyMapping *keyMappingArray, *keyMappingPtr;
597 guint *mappedArray, *ptrMove;
598
599 if (keymap_string == NULL) {
600 g_debug("keymap string is empty - nothing to do");
601 priv->keyMappings = NULL;
602 return;
603 }
604
605 g_debug("keymap string set to %s", keymap_string);
606
607 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
608
609 g_debug("keymap command-line set to %s", keymap_string);
610 if (keymap_string) {
611 keymaps = g_strsplit(keymap_string, ",", -1);
612 }
613
614 if (!keymaps || g_strv_length(keymaps) == 0) {
615 g_strfreev(keymaps);
616 return;
617 }
618
619 keyMappingPtr = keyMappingArray = g_new0(VirtViewerKeyMapping, g_strv_length(keymaps));
620
621 g_debug("Allocated %d number of mappings", g_strv_length(keymaps));
622
623 for (key = keymaps; *key != NULL; key++) {
624 gchar *srcKey = strstr(*key, "=");
625 const gchar *value = (srcKey == NULL) ? NULL : (*srcKey = '\0', srcKey + 1);
626 if (value == NULL) {
627 g_warning("Missing mapping value for key '%s'", srcKey);
628 continue;
629 }
630
631 // Key value must be resolved to GDK key code
632 // along with mapped key which can also be void (for no action)
633 guint kcode;
634 kcode = gdk_keyval_from_name(*key);
635 if (kcode == GDK_KEY_VoidSymbol) {
636 g_warning("Unable to lookup '%s' key", *key);
637 continue;
638 }
639 g_debug("Mapped source key '%s' to %x", *key, kcode);
640
641 valuekeys = g_strsplit(value, "+", -1);
642
643 keyMappingPtr->sourceKey = kcode;
644 keyMappingPtr->numMappedKeys = g_strv_length(valuekeys);
645 keyMappingPtr->isLast = FALSE;
646
647 if (!valuekeys || g_strv_length(valuekeys) == 0) {
648 g_debug("No value set for key '%s' it will be blocked", *key);
649 keyMappingPtr->mappedKeys = NULL;
650 keyMappingPtr++;
651 g_strfreev(valuekeys);
652 continue;
653 }
654
655 ptrMove = mappedArray = g_new0(guint, g_strv_length(valuekeys));
656
657 guint mcode;
658 for (valkey = valuekeys; *valkey != NULL; valkey++) {
659 g_debug("Value key to map '%s'", *valkey);
660 mcode = gdk_keyval_from_name(*valkey);
661 if (mcode == GDK_KEY_VoidSymbol) {
662 g_warning("Unable to lookup mapped key '%s' it will be ignored", *valkey);
663 }
664 g_debug("Mapped dest key '%s' to %x", *valkey, mcode);
665 *ptrMove++ = mcode;
666 }
667 keyMappingPtr->mappedKeys = mappedArray;
668 keyMappingPtr++;
669 g_strfreev(valuekeys);
670
671 }
672 keyMappingPtr--;
673 keyMappingPtr->isLast=TRUE;
674
675 priv->keyMappings = keyMappingArray;
676 g_strfreev(keymaps);
677 }
678
679 void
virt_viewer_app_maybe_quit(VirtViewerApp * self,VirtViewerWindow * window)680 virt_viewer_app_maybe_quit(VirtViewerApp *self, VirtViewerWindow *window)
681 {
682 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
683 GError *error = NULL;
684
685 if (priv->kiosk) {
686 g_warning("The app is in kiosk mode and can't quit");
687 return;
688 }
689
690 gboolean ask = g_key_file_get_boolean(priv->config,
691 "virt-viewer", "ask-quit", &error);
692 if (error) {
693 ask = TRUE;
694 g_clear_error(&error);
695 }
696
697 if (ask) {
698 GtkWidget *dialog =
699 gtk_message_dialog_new (virt_viewer_window_get_window(window),
700 GTK_DIALOG_DESTROY_WITH_PARENT,
701 GTK_MESSAGE_QUESTION,
702 GTK_BUTTONS_OK_CANCEL,
703 _("Do you want to close the session?"));
704
705 GtkWidget *check = gtk_check_button_new_with_label(_("Do not ask me again"));
706 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), check);
707 gtk_widget_show(check);
708
709 gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
710 gint result = gtk_dialog_run(GTK_DIALOG(dialog));
711
712 gboolean dont_ask = FALSE;
713 g_object_get(check, "active", &dont_ask, NULL);
714 g_key_file_set_boolean(priv->config,
715 "virt-viewer", "ask-quit", !dont_ask);
716
717 gtk_widget_destroy(dialog);
718 switch (result) {
719 case GTK_RESPONSE_OK:
720 virt_viewer_app_quit(self);
721 break;
722 default:
723 break;
724 }
725 } else {
726 virt_viewer_app_quit(self);
727 }
728
729 }
730
count_window_visible(gpointer value,gpointer user_data)731 static void count_window_visible(gpointer value,
732 gpointer user_data)
733 {
734 GtkWindow *win = virt_viewer_window_get_window(VIRT_VIEWER_WINDOW(value));
735 guint *n = (guint*)user_data;
736
737 if (gtk_widget_get_visible(GTK_WIDGET(win)))
738 *n += 1;
739 }
740
741 static guint
virt_viewer_app_get_n_windows_visible(VirtViewerApp * self)742 virt_viewer_app_get_n_windows_visible(VirtViewerApp *self)
743 {
744 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
745 guint n = 0;
746 g_list_foreach(priv->windows, count_window_visible, &n);
747 return n;
748 }
749
hide_one_window(gpointer value,gpointer user_data G_GNUC_UNUSED)750 static void hide_one_window(gpointer value,
751 gpointer user_data G_GNUC_UNUSED)
752 {
753 VirtViewerApp* self = VIRT_VIEWER_APP(user_data);
754 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
755 gboolean connect_error = !priv->cancelled && !priv->connected;
756 #ifdef HAVE_GTK_VNC
757 if (VIRT_VIEWER_IS_SESSION_VNC(priv->session)) {
758 connect_error = !priv->cancelled && !priv->initialized;
759 }
760 #endif
761
762 if (connect_error || priv->main_window != value)
763 virt_viewer_window_hide(VIRT_VIEWER_WINDOW(value));
764 }
765
766 static void
virt_viewer_app_hide_all_windows(VirtViewerApp * self)767 virt_viewer_app_hide_all_windows(VirtViewerApp *self)
768 {
769 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
770 g_list_foreach(priv->windows, hide_one_window, self);
771 }
772
773 G_MODULE_EXPORT void
virt_viewer_app_about_close(GtkWidget * dialog,VirtViewerApp * self G_GNUC_UNUSED)774 virt_viewer_app_about_close(GtkWidget *dialog,
775 VirtViewerApp *self G_GNUC_UNUSED)
776 {
777 gtk_widget_hide(dialog);
778 gtk_widget_destroy(dialog);
779 }
780
781 G_MODULE_EXPORT void
virt_viewer_app_about_delete(GtkWidget * dialog,void * dummy G_GNUC_UNUSED,VirtViewerApp * self G_GNUC_UNUSED)782 virt_viewer_app_about_delete(GtkWidget *dialog,
783 void *dummy G_GNUC_UNUSED,
784 VirtViewerApp *self G_GNUC_UNUSED)
785 {
786 gtk_widget_hide(dialog);
787 gtk_widget_destroy(dialog);
788 }
789
790 #ifndef G_OS_WIN32
791
792 static int
virt_viewer_app_open_tunnel(const char ** cmd)793 virt_viewer_app_open_tunnel(const char **cmd)
794 {
795 int fd[2];
796 pid_t pid;
797
798 if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0)
799 return -1;
800
801 pid = fork();
802 if (pid == -1) {
803 close(fd[0]);
804 close(fd[1]);
805 return -1;
806 }
807
808 if (pid == 0) { /* child */
809 close(fd[0]);
810 close(0);
811 close(1);
812 if (dup(fd[1]) < 0)
813 _exit(1);
814 if (dup(fd[1]) < 0)
815 _exit(1);
816 close(fd[1]);
817 execvp("ssh", (char *const*)cmd);
818 _exit(1);
819 }
820 close(fd[1]);
821 return fd[0];
822 }
823
824
825 static int
virt_viewer_app_open_tunnel_ssh(const char * sshhost,int sshport,const char * sshuser,const char * host,const char * port,const char * unixsock)826 virt_viewer_app_open_tunnel_ssh(const char *sshhost,
827 int sshport,
828 const char *sshuser,
829 const char *host,
830 const char *port,
831 const char *unixsock)
832 {
833 const char *cmd[10];
834 char portstr[50];
835 int n = 0;
836 GString *cat;
837
838 cmd[n++] = "ssh";
839 if (sshport) {
840 cmd[n++] = "-p";
841 sprintf(portstr, "%d", sshport);
842 cmd[n++] = portstr;
843 }
844 if (sshuser) {
845 cmd[n++] = "-l";
846 cmd[n++] = sshuser;
847 }
848 cmd[n++] = sshhost;
849
850 cat = g_string_new("if (command -v socat) >/dev/null 2>&1");
851
852 g_string_append(cat, "; then socat - ");
853 if (port)
854 g_string_append_printf(cat, "TCP:%s:%s", host, port);
855 else
856 g_string_append_printf(cat, "UNIX-CONNECT:%s", unixsock);
857
858 g_string_append(cat, "; else nc ");
859 if (port)
860 g_string_append_printf(cat, "%s %s", host, port);
861 else
862 g_string_append_printf(cat, "-U %s", unixsock);
863
864 g_string_append(cat, "; fi");
865
866 cmd[n++] = cat->str;
867 cmd[n++] = NULL;
868
869 n = virt_viewer_app_open_tunnel(cmd);
870 g_string_free(cat, TRUE);
871
872 return n;
873 }
874
875 static int
virt_viewer_app_open_unix_sock(const char * unixsock,GError ** error)876 virt_viewer_app_open_unix_sock(const char *unixsock, GError **error)
877 {
878 struct sockaddr_un addr;
879 int fd;
880
881 if (strlen(unixsock) + 1 > sizeof(addr.sun_path)) {
882 g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
883 _("Address is too long for UNIX socket_path: %s"), unixsock);
884 return -1;
885 }
886
887 memset(&addr, 0, sizeof addr);
888 addr.sun_family = AF_UNIX;
889 strcpy(addr.sun_path, unixsock);
890
891 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
892 g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
893 _("Creating UNIX socket failed: %s"), g_strerror(errno));
894 return -1;
895 }
896
897 if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
898 g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
899 _("Connecting to UNIX socket failed: %s"), g_strerror(errno));
900 close(fd);
901 return -1;
902 }
903
904 return fd;
905 }
906
907 #endif /* ! G_OS_WIN32 */
908
909 void
virt_viewer_app_trace(VirtViewerApp * self,const char * fmt,...)910 virt_viewer_app_trace(VirtViewerApp *self,
911 const char *fmt, ...)
912 {
913 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
914 va_list ap;
915 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
916
917 if (doDebug) {
918 va_start(ap, fmt);
919 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fmt, ap);
920 va_end(ap);
921 }
922
923 if (priv->verbose) {
924 va_start(ap, fmt);
925 g_vprintf(fmt, ap);
926 va_end(ap);
927 g_print("\n");
928 }
929 }
930
931 static const gchar*
virt_viewer_app_get_title(VirtViewerApp * self)932 virt_viewer_app_get_title(VirtViewerApp *self)
933 {
934 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
935 const gchar *title;
936 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
937
938 title = priv->title;
939 if (!title)
940 title = priv->guest_name;
941 if (!title)
942 title = priv->guri;
943
944 return title;
945 }
946
947 static void
virt_viewer_app_set_window_subtitle(VirtViewerApp * app,VirtViewerWindow * window,int nth)948 virt_viewer_app_set_window_subtitle(VirtViewerApp *app,
949 VirtViewerWindow *window,
950 int nth)
951 {
952 gchar *subtitle = NULL;
953 const gchar *title = virt_viewer_app_get_title(app);
954
955 if (title != NULL) {
956 VirtViewerDisplay *display = virt_viewer_window_get_display(window);
957 gchar *d = strstr(title, "%d");
958 gchar *desc = NULL;
959
960 if (display && VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
961 g_object_get(display, "name", &desc, NULL);
962 } else {
963 desc = g_strdup_printf("%d", nth + 1);
964 }
965
966 if (d != NULL) {
967 *d = '\0';
968 subtitle = g_strdup_printf("%s%s%s", title, desc, d + 2);
969 *d = '%';
970 } else
971 subtitle = g_strdup_printf("%s (%s)", title, desc);
972 g_free(desc);
973 }
974
975 g_object_set(window, "subtitle", subtitle, NULL);
976 g_free(subtitle);
977 }
978
979 static void
set_subtitle(gpointer value,gpointer user_data)980 set_subtitle(gpointer value,
981 gpointer user_data)
982 {
983 VirtViewerApp *app = user_data;
984 VirtViewerWindow *window = value;
985 VirtViewerDisplay *display = virt_viewer_window_get_display(window);
986
987 if (!display)
988 return;
989
990 virt_viewer_app_set_window_subtitle(app, window,
991 virt_viewer_display_get_nth(display));
992 }
993
994 static void
virt_viewer_app_set_all_window_subtitles(VirtViewerApp * self)995 virt_viewer_app_set_all_window_subtitles(VirtViewerApp *self)
996 {
997 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
998 g_list_foreach(priv->windows, set_subtitle, self);
999 }
1000
update_title(gpointer value,gpointer user_data G_GNUC_UNUSED)1001 static void update_title(gpointer value,
1002 gpointer user_data G_GNUC_UNUSED)
1003 {
1004 virt_viewer_window_update_title(VIRT_VIEWER_WINDOW(value));
1005 }
1006
1007 static void
virt_viewer_app_update_title(VirtViewerApp * self)1008 virt_viewer_app_update_title(VirtViewerApp *self)
1009 {
1010 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1011 g_list_foreach(priv->windows, update_title, NULL);
1012 }
1013
set_usb_options_sensitive(gpointer value,gpointer user_data)1014 static void set_usb_options_sensitive(gpointer value,
1015 gpointer user_data)
1016 {
1017 virt_viewer_window_set_usb_options_sensitive(VIRT_VIEWER_WINDOW(value),
1018 GPOINTER_TO_INT(user_data));
1019 }
1020
1021 static void
virt_viewer_app_set_usb_options_sensitive(VirtViewerApp * self,gboolean sensitive)1022 virt_viewer_app_set_usb_options_sensitive(VirtViewerApp *self, gboolean sensitive)
1023 {
1024 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1025 g_list_foreach(priv->windows, set_usb_options_sensitive,
1026 GINT_TO_POINTER(sensitive));
1027 }
1028
set_usb_reset_sensitive(gpointer value,gpointer user_data)1029 static void set_usb_reset_sensitive(gpointer value,
1030 gpointer user_data)
1031 {
1032 virt_viewer_window_set_usb_reset_sensitive(VIRT_VIEWER_WINDOW(value),
1033 GPOINTER_TO_INT(user_data));
1034 }
1035
1036 static void
virt_viewer_app_set_usb_reset_sensitive(VirtViewerApp * self,gboolean sensitive)1037 virt_viewer_app_set_usb_reset_sensitive(VirtViewerApp *self, gboolean sensitive)
1038 {
1039 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1040 g_list_foreach(priv->windows, set_usb_reset_sensitive,
1041 GINT_TO_POINTER(sensitive));
1042 }
1043
1044 static void
set_actions_sensitive(gpointer value,gpointer user_data)1045 set_actions_sensitive(gpointer value, gpointer user_data)
1046 {
1047 VirtViewerApp *self = VIRT_VIEWER_APP(user_data);
1048 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1049 virt_viewer_window_set_actions_sensitive(VIRT_VIEWER_WINDOW(value),
1050 priv->connected);
1051 }
1052
1053 static void
virt_viewer_app_set_actions_sensitive(VirtViewerApp * self)1054 virt_viewer_app_set_actions_sensitive(VirtViewerApp *self)
1055 {
1056 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1057 GActionMap *map = G_ACTION_MAP(self);
1058 GAction *action;
1059
1060 g_list_foreach(priv->windows, set_actions_sensitive, self);
1061
1062 action = g_action_map_lookup_action(map, "machine-pause");
1063 g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1064 priv->connected &&
1065 priv->vm_ui &&
1066 virt_viewer_session_has_vm_action(priv->session,
1067 VIRT_VIEWER_SESSION_VM_ACTION_PAUSE));
1068
1069 action = g_action_map_lookup_action(map, "machine-reset");
1070 g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1071 priv->connected &&
1072 priv->vm_ui &&
1073 virt_viewer_session_has_vm_action(priv->session,
1074 VIRT_VIEWER_SESSION_VM_ACTION_RESET));
1075
1076 action = g_action_map_lookup_action(map, "machine-powerdown");
1077 g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1078 priv->connected &&
1079 priv->vm_ui &&
1080 virt_viewer_session_has_vm_action(priv->session,
1081 VIRT_VIEWER_SESSION_VM_ACTION_POWER_DOWN));
1082
1083 action = g_action_map_lookup_action(map, "auto-resize");
1084 g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1085 priv->connected);
1086 }
1087
1088 static VirtViewerWindow *
virt_viewer_app_get_nth_window(VirtViewerApp * self,gint nth)1089 virt_viewer_app_get_nth_window(VirtViewerApp *self, gint nth)
1090 {
1091 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1092 GList *l;
1093
1094 if (nth < 0)
1095 return NULL;
1096
1097 for (l = priv->windows; l; l = l->next) {
1098 VirtViewerDisplay *display = virt_viewer_window_get_display(l->data);
1099 if (display
1100 && (virt_viewer_display_get_nth(display) == nth)) {
1101 return l->data;
1102 }
1103 }
1104 return NULL;
1105 }
1106
1107 static VirtViewerWindow *
virt_viewer_app_get_vte_window(VirtViewerApp * self,const gchar * name)1108 virt_viewer_app_get_vte_window(VirtViewerApp *self, const gchar *name)
1109 {
1110 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1111 GList *l;
1112
1113 if (!name)
1114 return NULL;
1115
1116 for (l = priv->windows; l; l = l->next) {
1117 VirtViewerDisplay *display = virt_viewer_window_get_display(l->data);
1118 if (display &&
1119 VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
1120 char *thisname = NULL;
1121 gboolean match;
1122 g_object_get(display, "name", &thisname, NULL);
1123 match = thisname && g_str_equal(name, thisname);
1124 g_free(thisname);
1125 if (match) {
1126 return l->data;
1127 }
1128 }
1129 }
1130 return NULL;
1131 }
1132
1133 static void
viewer_window_visible_cb(GtkWidget * widget G_GNUC_UNUSED,gpointer user_data)1134 viewer_window_visible_cb(GtkWidget *widget G_GNUC_UNUSED,
1135 gpointer user_data)
1136 {
1137 virt_viewer_app_update_menu_displays(VIRT_VIEWER_APP(user_data));
1138 }
1139
1140 static gboolean
virt_viewer_app_has_usbredir(VirtViewerApp * self)1141 virt_viewer_app_has_usbredir(VirtViewerApp *self)
1142 {
1143 return virt_viewer_app_has_session(self) &&
1144 virt_viewer_session_get_has_usbredir(virt_viewer_app_get_session(self));
1145 }
1146
1147 static VirtViewerWindow*
virt_viewer_app_window_new(VirtViewerApp * self,gint nth)1148 virt_viewer_app_window_new(VirtViewerApp *self, gint nth)
1149 {
1150 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1151 VirtViewerWindow* window;
1152 GtkWindow *w;
1153 gboolean has_usbredir;
1154
1155 window = virt_viewer_app_get_nth_window(self, nth);
1156 if (window)
1157 return window;
1158
1159 window = g_object_new(VIRT_VIEWER_TYPE_WINDOW, "app", self, NULL);
1160 virt_viewer_window_set_kiosk(window, priv->kiosk);
1161 if (priv->main_window)
1162 virt_viewer_window_set_zoom_level(window, virt_viewer_window_get_zoom_level(priv->main_window));
1163
1164 priv->windows = g_list_append(priv->windows, window);
1165 virt_viewer_app_set_window_subtitle(self, window, nth);
1166 virt_viewer_app_update_menu_displays(self);
1167
1168 has_usbredir = virt_viewer_app_has_usbredir(self);
1169 virt_viewer_window_set_usb_options_sensitive(window, has_usbredir);
1170 virt_viewer_window_set_usb_reset_sensitive(window, has_usbredir);
1171
1172 w = virt_viewer_window_get_window(window);
1173 g_object_set_data(G_OBJECT(w), "virt-viewer-window", window);
1174 gtk_application_add_window(GTK_APPLICATION(self), w);
1175
1176 if (priv->fullscreen)
1177 app_window_try_fullscreen(self, window, nth);
1178
1179 g_signal_connect(w, "hide", G_CALLBACK(viewer_window_visible_cb), self);
1180 g_signal_connect(w, "show", G_CALLBACK(viewer_window_visible_cb), self);
1181
1182 if (priv->keyMappings) {
1183 g_object_set(window, "keymap", priv->keyMappings, NULL);
1184 }
1185
1186 return window;
1187 }
1188
1189 static void
window_weak_notify(gpointer data,GObject * where_was G_GNUC_UNUSED)1190 window_weak_notify(gpointer data, GObject *where_was G_GNUC_UNUSED)
1191 {
1192 VirtViewerDisplay *display = VIRT_VIEWER_DISPLAY(data);
1193
1194 g_object_set_data(G_OBJECT(display), "virt-viewer-window", NULL);
1195 }
1196
1197 static VirtViewerWindow *
ensure_window_for_display(VirtViewerApp * self,VirtViewerDisplay * display)1198 ensure_window_for_display(VirtViewerApp *self, VirtViewerDisplay *display)
1199 {
1200 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1201 gint nth = virt_viewer_display_get_nth(display);
1202 VirtViewerWindow *win = virt_viewer_app_get_nth_window(self, nth);
1203
1204 if (VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
1205 win = g_object_get_data(G_OBJECT(display), "virt-viewer-window");
1206 }
1207
1208 if (win == NULL) {
1209 GList *l = priv->windows;
1210
1211 /* There should always be at least a main window created at startup */
1212 g_return_val_if_fail(l != NULL, NULL);
1213 /* if there's a window that doesn't yet have an associated display, use
1214 * that window */
1215 for (; l; l = l->next) {
1216 if (virt_viewer_window_get_display(VIRT_VIEWER_WINDOW(l->data)) == NULL)
1217 break;
1218 }
1219 if (l && virt_viewer_window_get_display(VIRT_VIEWER_WINDOW(l->data)) == NULL) {
1220 win = VIRT_VIEWER_WINDOW(l->data);
1221 g_debug("Found a window without a display, reusing for display #%d", nth);
1222 if (priv->fullscreen && !priv->kiosk)
1223 app_window_try_fullscreen(self, win, nth);
1224 } else {
1225 win = virt_viewer_app_window_new(self, nth);
1226 }
1227
1228 virt_viewer_app_set_display_auto_resize(self, display);
1229 virt_viewer_window_set_display(win, display);
1230 if (VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
1231 g_object_set_data(G_OBJECT(display), "virt-viewer-window", win);
1232 g_object_weak_ref(G_OBJECT(win), window_weak_notify, display);
1233 }
1234
1235 virt_viewer_window_set_actions_sensitive(win, priv->connected);
1236 }
1237 virt_viewer_app_set_window_subtitle(self, win, nth);
1238
1239 return win;
1240 }
1241
1242 static VirtViewerWindow *
display_show_notebook_get_window(VirtViewerApp * self,VirtViewerDisplay * display)1243 display_show_notebook_get_window(VirtViewerApp *self, VirtViewerDisplay *display)
1244 {
1245 VirtViewerWindow *win = ensure_window_for_display(self, display);
1246 VirtViewerNotebook *nb = virt_viewer_window_get_notebook(win);
1247
1248 virt_viewer_notebook_show_display(nb);
1249 return win;
1250 }
1251
1252 static void
display_show_hint(VirtViewerDisplay * display,GParamSpec * pspec G_GNUC_UNUSED,gpointer user_data G_GNUC_UNUSED)1253 display_show_hint(VirtViewerDisplay *display,
1254 GParamSpec *pspec G_GNUC_UNUSED,
1255 gpointer user_data G_GNUC_UNUSED)
1256 {
1257 VirtViewerApp *self = virt_viewer_session_get_app(virt_viewer_display_get_session(display));
1258 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1259 VirtViewerNotebook *nb;
1260 VirtViewerWindow *win;
1261 gint nth;
1262 guint hint;
1263
1264 g_object_get(display,
1265 "nth-display", &nth,
1266 "show-hint", &hint,
1267 NULL);
1268
1269 win = virt_viewer_app_get_nth_window(self, nth);
1270
1271 if (priv->fullscreen &&
1272 nth >= get_n_client_monitors()) {
1273 if (win)
1274 virt_viewer_window_hide(win);
1275 } else if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_DISABLED) {
1276 if (win)
1277 virt_viewer_window_hide(win);
1278 } else {
1279 if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY) {
1280 win = display_show_notebook_get_window(self, display);
1281 virt_viewer_window_show(win);
1282 } else {
1283 if (!priv->kiosk && win) {
1284 nb = virt_viewer_window_get_notebook(win);
1285 virt_viewer_notebook_show_status(nb, _("Waiting for display %d..."), nth + 1);
1286 }
1287 }
1288 }
1289 virt_viewer_app_update_menu_displays(self);
1290 }
1291
1292 static void
virt_viewer_app_display_added(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerDisplay * display,VirtViewerApp * self)1293 virt_viewer_app_display_added(VirtViewerSession *session G_GNUC_UNUSED,
1294 VirtViewerDisplay *display,
1295 VirtViewerApp *self)
1296 {
1297 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1298 gint nth;
1299
1300 g_object_get(display, "nth-display", &nth, NULL);
1301 g_debug("Insert display %d %p", nth, display);
1302
1303 if (VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
1304 VirtViewerWindow *win = display_show_notebook_get_window(self, display);
1305 virt_viewer_window_hide(win);
1306 virt_viewer_app_update_menu_displays(self);
1307 return;
1308 }
1309
1310 g_hash_table_insert(priv->displays, GINT_TO_POINTER(nth), g_object_ref(display));
1311 virt_viewer_app_set_display_auto_resize(self, display);
1312
1313 g_signal_connect(display, "notify::show-hint",
1314 G_CALLBACK(display_show_hint), NULL);
1315 g_object_notify(G_OBJECT(display), "show-hint"); /* call display_show_hint */
1316 }
1317
virt_viewer_app_remove_nth_window(VirtViewerApp * self,gint nth)1318 static void virt_viewer_app_remove_nth_window(VirtViewerApp *self,
1319 gint nth)
1320 {
1321 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1322 VirtViewerWindow *win = virt_viewer_app_get_nth_window(self, nth);
1323 if (!win)
1324 return;
1325 virt_viewer_window_set_display(win, NULL);
1326 if (win == priv->main_window) {
1327 g_debug("Not removing main window %d %p", nth, win);
1328 return;
1329 }
1330 virt_viewer_window_hide(win);
1331
1332 g_debug("Remove window %d %p", nth, win);
1333 priv->windows = g_list_remove(priv->windows, win);
1334
1335 g_object_unref(win);
1336 }
1337
1338 static void
virt_viewer_app_display_removed(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerDisplay * display,VirtViewerApp * self)1339 virt_viewer_app_display_removed(VirtViewerSession *session G_GNUC_UNUSED,
1340 VirtViewerDisplay *display,
1341 VirtViewerApp *self)
1342 {
1343 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1344 gint nth;
1345
1346 if(virt_viewer_display_get_fullscreen(display))
1347 virt_viewer_app_set_monitor_mapping_for_display(self, display) ;
1348
1349 g_object_get(display, "nth-display", &nth, NULL);
1350 virt_viewer_app_remove_nth_window(self, nth);
1351 g_hash_table_remove(priv->displays, GINT_TO_POINTER(nth));
1352 virt_viewer_app_update_menu_displays(self);
1353 }
1354
1355 static void
virt_viewer_app_display_updated(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerApp * self)1356 virt_viewer_app_display_updated(VirtViewerSession *session G_GNUC_UNUSED,
1357 VirtViewerApp *self)
1358 {
1359 virt_viewer_app_update_menu_displays(self);
1360 }
1361
1362 static void
virt_viewer_app_has_usbredir_updated(VirtViewerSession * session,GParamSpec * pspec G_GNUC_UNUSED,VirtViewerApp * self)1363 virt_viewer_app_has_usbredir_updated(VirtViewerSession *session,
1364 GParamSpec *pspec G_GNUC_UNUSED,
1365 VirtViewerApp *self)
1366 {
1367 gboolean has_usbredir = virt_viewer_session_get_has_usbredir(session);
1368
1369 virt_viewer_app_set_usb_options_sensitive(self, has_usbredir);
1370 virt_viewer_app_set_usb_reset_sensitive(self, has_usbredir);
1371 virt_viewer_update_usbredir_accels(self);
1372 }
1373
notify_software_reader_cb(GObject * gobject G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer user_data)1374 static void notify_software_reader_cb(GObject *gobject G_GNUC_UNUSED,
1375 GParamSpec *pspec G_GNUC_UNUSED,
1376 gpointer user_data)
1377 {
1378 virt_viewer_update_smartcard_accels(VIRT_VIEWER_APP(user_data));
1379 }
1380
1381 gboolean
virt_viewer_app_create_session(VirtViewerApp * self,const gchar * type,GError ** error)1382 virt_viewer_app_create_session(VirtViewerApp *self, const gchar *type, GError **error)
1383 {
1384 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
1385 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1386 g_return_val_if_fail(priv->session == NULL, FALSE);
1387 g_return_val_if_fail(type != NULL, FALSE);
1388
1389 #ifdef HAVE_GTK_VNC
1390 if (g_ascii_strcasecmp(type, "vnc") == 0) {
1391 GtkWindow *window = virt_viewer_window_get_window(priv->main_window);
1392 virt_viewer_app_trace(self, "Guest %s has a %s display",
1393 priv->guest_name, type);
1394 priv->session = virt_viewer_session_vnc_new(self, window);
1395 } else
1396 #endif
1397 #ifdef HAVE_SPICE_GTK
1398 if (g_ascii_strcasecmp(type, "spice") == 0) {
1399 GtkWindow *window = virt_viewer_window_get_window(priv->main_window);
1400 virt_viewer_app_trace(self, "Guest %s has a %s display",
1401 priv->guest_name, type);
1402 priv->session = virt_viewer_session_spice_new(self, window);
1403 } else
1404 #endif
1405 {
1406 g_set_error(error,
1407 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
1408 _("Unsupported graphic type '%s'"), type);
1409
1410 virt_viewer_app_trace(self, "Guest %s has unsupported %s display type",
1411 priv->guest_name, type);
1412 return FALSE;
1413 }
1414
1415 g_signal_connect(priv->session, "session-initialized",
1416 G_CALLBACK(virt_viewer_app_initialized), self);
1417 g_signal_connect(priv->session, "session-connected",
1418 G_CALLBACK(virt_viewer_app_connected), self);
1419 g_signal_connect(priv->session, "session-disconnected",
1420 G_CALLBACK(virt_viewer_app_disconnected), self);
1421 g_signal_connect(priv->session, "session-channel-open",
1422 G_CALLBACK(virt_viewer_app_channel_open), self);
1423 g_signal_connect(priv->session, "session-auth-refused",
1424 G_CALLBACK(virt_viewer_app_auth_refused), self);
1425 g_signal_connect(priv->session, "session-auth-unsupported",
1426 G_CALLBACK(virt_viewer_app_auth_unsupported), self);
1427 g_signal_connect(priv->session, "session-usb-failed",
1428 G_CALLBACK(virt_viewer_app_usb_failed), self);
1429 g_signal_connect(priv->session, "session-display-added",
1430 G_CALLBACK(virt_viewer_app_display_added), self);
1431 g_signal_connect(priv->session, "session-display-removed",
1432 G_CALLBACK(virt_viewer_app_display_removed), self);
1433 g_signal_connect(priv->session, "session-display-updated",
1434 G_CALLBACK(virt_viewer_app_display_updated), self);
1435 g_signal_connect(priv->session, "notify::has-usbredir",
1436 G_CALLBACK(virt_viewer_app_has_usbredir_updated), self);
1437
1438 g_signal_connect(priv->session, "session-cut-text",
1439 G_CALLBACK(virt_viewer_app_server_cut_text), self);
1440 g_signal_connect(priv->session, "session-bell",
1441 G_CALLBACK(virt_viewer_app_bell), self);
1442 g_signal_connect(priv->session, "session-cancelled",
1443 G_CALLBACK(virt_viewer_app_cancelled), self);
1444
1445 g_signal_connect(priv->session, "notify::software-smartcard-reader",
1446 (GCallback)notify_software_reader_cb, self);
1447 return TRUE;
1448 }
1449
1450 static gboolean
virt_viewer_app_default_open_connection(VirtViewerApp * self G_GNUC_UNUSED,int * fd)1451 virt_viewer_app_default_open_connection(VirtViewerApp *self G_GNUC_UNUSED, int *fd)
1452 {
1453 *fd = -1;
1454 return TRUE;
1455 }
1456
1457
1458 static int
virt_viewer_app_open_connection(VirtViewerApp * self,int * fd)1459 virt_viewer_app_open_connection(VirtViewerApp *self, int *fd)
1460 {
1461 VirtViewerAppClass *klass;
1462
1463 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), -1);
1464 klass = VIRT_VIEWER_APP_GET_CLASS(self);
1465
1466 return klass->open_connection(self, fd);
1467 }
1468
1469
1470 #ifndef G_OS_WIN32
1471 static void
virt_viewer_app_channel_open(VirtViewerSession * session,VirtViewerSessionChannel * channel,VirtViewerApp * self)1472 virt_viewer_app_channel_open(VirtViewerSession *session,
1473 VirtViewerSessionChannel *channel,
1474 VirtViewerApp *self)
1475 {
1476 VirtViewerAppPrivate *priv;
1477 int fd = -1;
1478 gchar *error_message = NULL;
1479
1480 g_return_if_fail(self != NULL);
1481
1482 if (!virt_viewer_app_open_connection(self, &fd))
1483 return;
1484
1485 g_debug("After open connection callback fd=%d", fd);
1486
1487 priv = virt_viewer_app_get_instance_private(self);
1488 if (priv->transport && g_ascii_strcasecmp(priv->transport, "ssh") == 0 &&
1489 !priv->direct && fd == -1) {
1490 if ((fd = virt_viewer_app_open_tunnel_ssh(priv->host, priv->port, priv->user,
1491 priv->ghost, priv->gport, priv->unixsock)) < 0) {
1492 error_message = g_strdup(_("Connect to SSH failed."));
1493 g_debug("channel open ssh tunnel: %s", error_message);
1494 }
1495 }
1496 if (fd < 0 && priv->unixsock) {
1497 GError *error = NULL;
1498 if ((fd = virt_viewer_app_open_unix_sock(priv->unixsock, &error)) < 0) {
1499 g_free(error_message);
1500 error_message = g_strdup(error->message);
1501 g_debug("channel open unix socket: %s", error_message);
1502 }
1503 g_clear_error(&error);
1504 }
1505
1506 if (fd < 0) {
1507 virt_viewer_app_simple_message_dialog(self, _("Can't connect to channel: %s"),
1508 (error_message != NULL) ? error_message :
1509 _("only SSH or UNIX socket connection supported."));
1510 g_free(error_message);
1511 return;
1512 }
1513
1514 if (!virt_viewer_session_channel_open_fd(session, channel, fd)) {
1515 // in case of error, close the file descriptor to prevent a leak
1516 // NOTE: as VNC doesn't support channel_open, this function will always return false for this protocol.
1517 close(fd);
1518 }
1519 }
1520 #else
1521 static void
virt_viewer_app_channel_open(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerSessionChannel * channel G_GNUC_UNUSED,VirtViewerApp * self)1522 virt_viewer_app_channel_open(VirtViewerSession *session G_GNUC_UNUSED,
1523 VirtViewerSessionChannel *channel G_GNUC_UNUSED,
1524 VirtViewerApp *self)
1525 {
1526 virt_viewer_app_simple_message_dialog(self, _("Connect to channel unsupported."));
1527 }
1528 #endif
1529
1530 static gboolean
virt_viewer_app_default_activate(VirtViewerApp * self,GError ** error)1531 virt_viewer_app_default_activate(VirtViewerApp *self, GError **error)
1532 {
1533 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1534 int fd = -1;
1535
1536 if (!virt_viewer_app_open_connection(self, &fd))
1537 return FALSE;
1538
1539 g_debug("After open connection callback fd=%d", fd);
1540
1541 #ifndef G_OS_WIN32
1542 if (priv->transport &&
1543 g_ascii_strcasecmp(priv->transport, "ssh") == 0 &&
1544 !priv->direct &&
1545 fd == -1) {
1546 gchar *p = NULL;
1547
1548 if (priv->gport) {
1549 virt_viewer_app_trace(self, "Opening indirect TCP connection to display at %s:%s",
1550 priv->ghost, priv->gport);
1551 } else {
1552 virt_viewer_app_trace(self, "Opening indirect UNIX connection to display at %s",
1553 priv->unixsock);
1554 }
1555 if (priv->port)
1556 p = g_strdup_printf(":%d", priv->port);
1557
1558 virt_viewer_app_trace(self, "Setting up SSH tunnel via %s%s%s%s",
1559 priv->user ? priv->user : "",
1560 priv->user ? "@" : "",
1561 priv->host, p ? p : "");
1562 g_free(p);
1563
1564 if ((fd = virt_viewer_app_open_tunnel_ssh(priv->host, priv->port,
1565 priv->user, priv->ghost,
1566 priv->gport, priv->unixsock)) < 0)
1567 return FALSE;
1568 } else if (priv->unixsock && fd == -1) {
1569 virt_viewer_app_trace(self, "Opening direct UNIX connection to display at %s",
1570 priv->unixsock);
1571 if ((fd = virt_viewer_app_open_unix_sock(priv->unixsock, error)) < 0)
1572 return FALSE;
1573 }
1574 #endif
1575
1576 if (fd >= 0) {
1577 gboolean ret = virt_viewer_session_open_fd(VIRT_VIEWER_SESSION(priv->session), fd);
1578 if (!ret)
1579 close (fd);
1580 return ret ;
1581 } else if (priv->guri) {
1582 virt_viewer_app_trace(self, "Opening connection to display at %s", priv->guri);
1583 return virt_viewer_session_open_uri(VIRT_VIEWER_SESSION(priv->session), priv->guri, error);
1584 } else if (priv->ghost) {
1585 virt_viewer_app_trace(self, "Opening direct TCP connection to display at %s:%s:%s",
1586 priv->ghost, priv->gport, priv->gtlsport ? priv->gtlsport : "-1");
1587 return virt_viewer_session_open_host(VIRT_VIEWER_SESSION(priv->session),
1588 priv->ghost, priv->gport, priv->gtlsport);
1589 } else {
1590 g_set_error_literal(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
1591 _("Display can only be attached through libvirt with --attach"));
1592 }
1593
1594 return FALSE;
1595 }
1596
1597 gboolean
virt_viewer_app_activate(VirtViewerApp * self,GError ** error)1598 virt_viewer_app_activate(VirtViewerApp *self, GError **error)
1599 {
1600 VirtViewerAppPrivate *priv;
1601 gboolean ret;
1602
1603 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
1604
1605 priv = virt_viewer_app_get_instance_private(self);
1606 if (priv->active)
1607 return FALSE;
1608
1609 ret = VIRT_VIEWER_APP_GET_CLASS(self)->activate(self, error);
1610
1611 if (ret == FALSE) {
1612 if(error != NULL && *error != NULL)
1613 virt_viewer_app_show_status(self, "%s", (*error)->message);
1614 priv->connected = FALSE;
1615 } else {
1616 virt_viewer_app_show_status(self, _("Connecting to graphic server"));
1617 priv->cancelled = FALSE;
1618 priv->active = TRUE;
1619 }
1620
1621 priv->grabbed = FALSE;
1622 virt_viewer_app_update_title(self);
1623
1624 return ret;
1625 }
1626
1627 /* text was actually requested */
1628 static void
virt_viewer_app_clipboard_copy(GtkClipboard * clipboard G_GNUC_UNUSED,GtkSelectionData * data,guint info G_GNUC_UNUSED,VirtViewerApp * self)1629 virt_viewer_app_clipboard_copy(GtkClipboard *clipboard G_GNUC_UNUSED,
1630 GtkSelectionData *data,
1631 guint info G_GNUC_UNUSED,
1632 VirtViewerApp *self)
1633 {
1634 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1635
1636 gtk_selection_data_set_text(data, priv->clipboard, -1);
1637 }
1638
1639 static void
virt_viewer_app_server_cut_text(VirtViewerSession * session G_GNUC_UNUSED,const gchar * text,VirtViewerApp * self)1640 virt_viewer_app_server_cut_text(VirtViewerSession *session G_GNUC_UNUSED,
1641 const gchar *text,
1642 VirtViewerApp *self)
1643 {
1644 GtkClipboard *cb;
1645 gsize a, b;
1646 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1647 GtkTargetEntry targets[] = {
1648 {g_strdup("UTF8_STRING"), 0, 0},
1649 {g_strdup("COMPOUND_TEXT"), 0, 0},
1650 {g_strdup("TEXT"), 0, 0},
1651 {g_strdup("STRING"), 0, 0},
1652 };
1653
1654 if (!text)
1655 return;
1656
1657 g_free (priv->clipboard);
1658 priv->clipboard = g_convert (text, -1, "utf-8", "iso8859-1", &a, &b, NULL);
1659
1660 if (priv->clipboard) {
1661 cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1662
1663 gtk_clipboard_set_with_owner (cb,
1664 targets,
1665 G_N_ELEMENTS(targets),
1666 (GtkClipboardGetFunc)virt_viewer_app_clipboard_copy,
1667 NULL,
1668 G_OBJECT (self));
1669 }
1670 }
1671
1672
virt_viewer_app_bell(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerApp * self)1673 static void virt_viewer_app_bell(VirtViewerSession *session G_GNUC_UNUSED,
1674 VirtViewerApp *self)
1675 {
1676 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1677
1678 gdk_window_beep(gtk_widget_get_window(GTK_WIDGET(virt_viewer_window_get_window(priv->main_window))));
1679 }
1680
1681
1682 static gboolean
virt_viewer_app_default_initial_connect(VirtViewerApp * self,GError ** error)1683 virt_viewer_app_default_initial_connect(VirtViewerApp *self, GError **error)
1684 {
1685 return virt_viewer_app_activate(self, error);
1686 }
1687
1688 gboolean
virt_viewer_app_initial_connect(VirtViewerApp * self,GError ** error)1689 virt_viewer_app_initial_connect(VirtViewerApp *self, GError **error)
1690 {
1691 VirtViewerAppClass *klass;
1692
1693 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
1694 klass = VIRT_VIEWER_APP_GET_CLASS(self);
1695
1696 return klass->initial_connect(self, error);
1697 }
1698
1699 static gboolean
virt_viewer_app_retryauth(gpointer opaque)1700 virt_viewer_app_retryauth(gpointer opaque)
1701 {
1702 VirtViewerApp *self = opaque;
1703
1704 virt_viewer_app_initial_connect(self, NULL);
1705
1706 return FALSE;
1707 }
1708
1709 static void
virt_viewer_app_default_deactivated(VirtViewerApp * self,gboolean connect_error)1710 virt_viewer_app_default_deactivated(VirtViewerApp *self, gboolean connect_error)
1711 {
1712 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1713
1714 if (!connect_error) {
1715 virt_viewer_app_show_status(self, _("Guest domain has shutdown"));
1716 virt_viewer_app_trace(self, "Guest %s display has disconnected, shutting down",
1717 priv->guest_name);
1718 }
1719
1720 if (priv->quit_on_disconnect)
1721 g_application_quit(G_APPLICATION(self));
1722 }
1723
1724 static void
virt_viewer_app_deactivated(VirtViewerApp * self,gboolean connect_error)1725 virt_viewer_app_deactivated(VirtViewerApp *self, gboolean connect_error)
1726 {
1727 VirtViewerAppClass *klass;
1728 klass = VIRT_VIEWER_APP_GET_CLASS(self);
1729
1730 klass->deactivated(self, connect_error);
1731 }
1732
1733 static void
virt_viewer_app_deactivate(VirtViewerApp * self,gboolean connect_error)1734 virt_viewer_app_deactivate(VirtViewerApp *self, gboolean connect_error)
1735 {
1736 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1737
1738 if (!priv->active)
1739 return;
1740
1741 if (priv->session) {
1742 virt_viewer_session_close(VIRT_VIEWER_SESSION(priv->session));
1743 }
1744
1745 priv->initialized = FALSE;
1746 priv->connected = FALSE;
1747 priv->active = FALSE;
1748 priv->started = FALSE;
1749 #if 0
1750 g_free(priv->pretty_address);
1751 priv->pretty_address = NULL;
1752 #endif
1753 priv->grabbed = FALSE;
1754 virt_viewer_app_update_title(self);
1755 virt_viewer_app_set_actions_sensitive(self);
1756
1757 if (priv->authretry) {
1758 priv->authretry = FALSE;
1759 g_idle_add(virt_viewer_app_retryauth, self);
1760 } else {
1761 g_clear_object(&priv->session);
1762 virt_viewer_app_deactivated(self, connect_error);
1763 }
1764
1765 }
1766
1767 static void
virt_viewer_app_connected(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerApp * self)1768 virt_viewer_app_connected(VirtViewerSession *session G_GNUC_UNUSED,
1769 VirtViewerApp *self)
1770 {
1771 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1772
1773 priv->connected = TRUE;
1774
1775 if (priv->kiosk)
1776 virt_viewer_app_show_status(self, "%s", "");
1777 else
1778 virt_viewer_app_show_status(self, _("Connected to graphic server"));
1779
1780 virt_viewer_app_set_actions_sensitive(self);
1781 }
1782
1783 static void
virt_viewer_app_initialized(VirtViewerSession * session G_GNUC_UNUSED,VirtViewerApp * self)1784 virt_viewer_app_initialized(VirtViewerSession *session G_GNUC_UNUSED,
1785 VirtViewerApp *self)
1786 {
1787 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1788 priv->initialized = TRUE;
1789 virt_viewer_app_update_title(self);
1790 }
1791
1792 static void
virt_viewer_app_disconnected(VirtViewerSession * session G_GNUC_UNUSED,const gchar * msg,VirtViewerApp * self)1793 virt_viewer_app_disconnected(VirtViewerSession *session G_GNUC_UNUSED, const gchar *msg,
1794 VirtViewerApp *self)
1795 {
1796 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1797 gboolean connect_error = !priv->cancelled && !priv->connected;
1798 #ifdef HAVE_GTK_VNC
1799 if (VIRT_VIEWER_IS_SESSION_VNC(priv->session)) {
1800 connect_error = !priv->cancelled && !priv->initialized;
1801 }
1802 #endif
1803
1804 if (!priv->kiosk)
1805 virt_viewer_app_hide_all_windows(self);
1806 else if (priv->cancelled)
1807 priv->authretry = TRUE;
1808
1809 if (priv->quitting)
1810 g_application_quit(G_APPLICATION(self));
1811
1812 if (connect_error) {
1813 GtkWidget *dialog = virt_viewer_app_make_message_dialog(self,
1814 _("Unable to connect to the graphic server %s"), priv->pretty_address);
1815
1816 g_object_set(dialog, "secondary-text", msg, NULL);
1817 gtk_dialog_run(GTK_DIALOG(dialog));
1818 gtk_widget_destroy(dialog);
1819 }
1820 virt_viewer_app_set_usb_options_sensitive(self, FALSE);
1821 virt_viewer_app_set_usb_reset_sensitive(self, FALSE);
1822 virt_viewer_app_deactivate(self, connect_error);
1823 }
1824
virt_viewer_app_cancelled(VirtViewerSession * session,VirtViewerApp * self)1825 static void virt_viewer_app_cancelled(VirtViewerSession *session,
1826 VirtViewerApp *self)
1827 {
1828 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1829 priv->cancelled = TRUE;
1830 virt_viewer_app_disconnected(session, NULL, self);
1831 }
1832
virt_viewer_app_auth_refused(VirtViewerSession * session,const char * msg,VirtViewerApp * self)1833 static void virt_viewer_app_auth_refused(VirtViewerSession *session,
1834 const char *msg,
1835 VirtViewerApp *self)
1836 {
1837 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1838
1839 virt_viewer_app_simple_message_dialog(self,
1840 _("Unable to authenticate with remote desktop server at %s: %s\n"),
1841 priv->pretty_address, msg);
1842
1843 /* if the session implementation cannot retry auth automatically, the
1844 * VirtViewerApp needs to schedule a new connection to retry */
1845 priv->authretry = (!virt_viewer_session_can_retry_auth(session) &&
1846 !virt_viewer_session_get_file(session));
1847
1848 /* don't display another dialog in virt_viewer_app_disconnected when using VNC */
1849 priv->initialized = TRUE;
1850 }
1851
virt_viewer_app_auth_unsupported(VirtViewerSession * session G_GNUC_UNUSED,const char * msg,VirtViewerApp * self)1852 static void virt_viewer_app_auth_unsupported(VirtViewerSession *session G_GNUC_UNUSED,
1853 const char *msg,
1854 VirtViewerApp *self)
1855 {
1856 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1857 virt_viewer_app_simple_message_dialog(self,
1858 _("Unable to authenticate with remote desktop server: %s"),
1859 msg);
1860
1861 /* don't display another dialog in virt_viewer_app_disconnected when using VNC */
1862 priv->initialized = TRUE;
1863 }
1864
virt_viewer_app_usb_failed(VirtViewerSession * session G_GNUC_UNUSED,const gchar * msg,VirtViewerApp * self)1865 static void virt_viewer_app_usb_failed(VirtViewerSession *session G_GNUC_UNUSED,
1866 const gchar *msg,
1867 VirtViewerApp *self)
1868 {
1869 virt_viewer_app_simple_message_dialog(self, _("USB redirection error: %s"), msg);
1870 }
1871
1872 static void
virt_viewer_app_set_kiosk(VirtViewerApp * self,gboolean enabled)1873 virt_viewer_app_set_kiosk(VirtViewerApp *self, gboolean enabled)
1874 {
1875 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1876 int i;
1877 GList *l;
1878
1879 priv->kiosk = enabled;
1880 if (!enabled)
1881 return;
1882
1883 virt_viewer_app_set_fullscreen(self, enabled);
1884
1885 /* create windows for each client monitor */
1886 for (i = g_list_length(priv->windows);
1887 i < get_n_client_monitors(); i++) {
1888 virt_viewer_app_window_new(self, i);
1889 }
1890
1891 for (l = priv->windows; l != NULL; l = l ->next) {
1892 VirtViewerWindow *win = l->data;
1893
1894 virt_viewer_window_show(win);
1895 virt_viewer_window_set_kiosk(win, enabled);
1896 }
1897 }
1898
1899
1900 static void
virt_viewer_app_get_property(GObject * object,guint property_id,GValue * value G_GNUC_UNUSED,GParamSpec * pspec)1901 virt_viewer_app_get_property (GObject *object, guint property_id,
1902 GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
1903 {
1904 g_return_if_fail(VIRT_VIEWER_IS_APP(object));
1905 VirtViewerApp *self = VIRT_VIEWER_APP(object);
1906 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1907
1908 switch (property_id) {
1909 case PROP_VERBOSE:
1910 g_value_set_boolean(value, priv->verbose);
1911 break;
1912
1913 case PROP_SESSION:
1914 g_value_set_object(value, priv->session);
1915 break;
1916
1917 case PROP_GUEST_NAME:
1918 g_value_set_string(value, priv->guest_name);
1919 break;
1920
1921 case PROP_GURI:
1922 g_value_set_string(value, priv->guri);
1923 break;
1924
1925 case PROP_FULLSCREEN:
1926 g_value_set_boolean(value, priv->fullscreen);
1927 break;
1928
1929 case PROP_TITLE:
1930 g_value_set_string(value, virt_viewer_app_get_title(self));
1931 break;
1932
1933 case PROP_RELEASE_CURSOR_DISPLAY_HOTKEY:
1934 g_value_set_string(value, virt_viewer_app_get_release_cursor_display_hotkey(self));
1935 break;
1936
1937 case PROP_KIOSK:
1938 g_value_set_boolean(value, priv->kiosk);
1939 break;
1940
1941 case PROP_QUIT_ON_DISCONNECT:
1942 g_value_set_boolean(value, priv->quit_on_disconnect);
1943 break;
1944
1945 case PROP_UUID:
1946 g_value_set_string(value, priv->uuid);
1947 break;
1948
1949 case PROP_VM_UI:
1950 g_value_set_boolean(value, priv->vm_ui);
1951 break;
1952
1953 case PROP_VM_RUNNING:
1954 g_value_set_boolean(value, priv->vm_running);
1955 break;
1956
1957 case PROP_CONFIG_SHARE_CLIPBOARD:
1958 g_value_set_boolean(value, virt_viewer_app_get_config_share_clipboard(self));
1959 break;
1960
1961 case PROP_SUPPORTS_SHARE_CLIPBOARD:
1962 g_value_set_boolean(value, virt_viewer_app_get_supports_share_clipboard(self));
1963 break;
1964
1965 default:
1966 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1967 }
1968 }
1969
1970 static void
virt_viewer_app_set_property(GObject * object,guint property_id,const GValue * value G_GNUC_UNUSED,GParamSpec * pspec)1971 virt_viewer_app_set_property (GObject *object, guint property_id,
1972 const GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
1973 {
1974 g_return_if_fail(VIRT_VIEWER_IS_APP(object));
1975 VirtViewerApp *self = VIRT_VIEWER_APP(object);
1976 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
1977 GAction *action;
1978
1979 switch (property_id) {
1980 case PROP_VERBOSE:
1981 priv->verbose = g_value_get_boolean(value);
1982 break;
1983
1984 case PROP_GUEST_NAME:
1985 g_free(priv->guest_name);
1986 priv->guest_name = g_value_dup_string(value);
1987 break;
1988
1989 case PROP_GURI:
1990 g_free(priv->guri);
1991 priv->guri = g_value_dup_string(value);
1992 virt_viewer_app_update_pretty_address(self);
1993 break;
1994
1995 case PROP_FULLSCREEN:
1996 virt_viewer_app_set_fullscreen(self, g_value_get_boolean(value));
1997 break;
1998
1999 case PROP_TITLE:
2000 g_free(priv->title);
2001 priv->title = g_value_dup_string(value);
2002 break;
2003
2004 case PROP_RELEASE_CURSOR_DISPLAY_HOTKEY:
2005 virt_viewer_app_set_release_cursor_display_hotkey(self, g_value_dup_string(value));
2006 break;
2007
2008 case PROP_KIOSK:
2009 virt_viewer_app_set_kiosk(self, g_value_get_boolean(value));
2010 break;
2011
2012 case PROP_QUIT_ON_DISCONNECT:
2013 priv->quit_on_disconnect = g_value_get_boolean(value);
2014 break;
2015
2016 case PROP_UUID:
2017 virt_viewer_app_set_uuid_string(self, g_value_get_string(value));
2018 break;
2019
2020 case PROP_VM_UI:
2021 priv->vm_ui = g_value_get_boolean(value);
2022
2023 virt_viewer_app_set_actions_sensitive(self);
2024 break;
2025
2026 case PROP_VM_RUNNING:
2027 priv->vm_running = g_value_get_boolean(value);
2028
2029 action = g_action_map_lookup_action(G_ACTION_MAP(self), "machine-pause");
2030 g_simple_action_set_state(G_SIMPLE_ACTION(action),
2031 g_variant_new_boolean(priv->vm_running));
2032 break;
2033
2034 case PROP_CONFIG_SHARE_CLIPBOARD:
2035 virt_viewer_app_set_config_share_clipboard(self, g_value_get_boolean(value));
2036 break;
2037
2038 case PROP_SUPPORTS_SHARE_CLIPBOARD:
2039 virt_viewer_app_set_supports_share_clipboard(self, g_value_get_boolean(value));
2040 break;
2041
2042 default:
2043 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2044 }
2045 }
2046
2047 static void
virt_viewer_app_dispose(GObject * object)2048 virt_viewer_app_dispose (GObject *object)
2049 {
2050 VirtViewerApp *self = VIRT_VIEWER_APP(object);
2051 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2052
2053 if (priv->preferences)
2054 gtk_widget_destroy(priv->preferences);
2055 priv->preferences = NULL;
2056
2057 if (priv->windows) {
2058 GList *tmp = priv->windows;
2059 /* null-ify before unrefing, because we need
2060 * to prevent callbacks using priv->windows
2061 * while it is being disposed off. */
2062 priv->windows = NULL;
2063 priv->main_window = NULL;
2064 g_list_free_full(tmp, g_object_unref);
2065 }
2066
2067 if (priv->displays) {
2068 GHashTable *tmp = priv->displays;
2069 /* null-ify before unrefing, because we need
2070 * to prevent callbacks using priv->displays
2071 * while it is being disposed of. */
2072 priv->displays = NULL;
2073 g_hash_table_unref(tmp);
2074 }
2075
2076 priv->resource = NULL;
2077 g_clear_object(&priv->session);
2078 g_free(priv->title);
2079 priv->title = NULL;
2080 g_free(priv->guest_name);
2081 priv->guest_name = NULL;
2082 g_free(priv->pretty_address);
2083 priv->pretty_address = NULL;
2084 g_free(priv->guri);
2085 priv->guri = NULL;
2086 g_free(priv->title);
2087 priv->title = NULL;
2088 g_free(priv->uuid);
2089 priv->uuid = NULL;
2090 g_free(priv->config_file);
2091 priv->config_file = NULL;
2092 g_free(priv->release_cursor_display_hotkey);
2093 priv->release_cursor_display_hotkey = NULL;
2094 g_strfreev(priv->insert_smartcard_accel);
2095 priv->insert_smartcard_accel = NULL;
2096 g_strfreev(priv->remove_smartcard_accel);
2097 priv->remove_smartcard_accel = NULL;
2098 g_strfreev(priv->usb_device_reset_accel);
2099 priv->usb_device_reset_accel = NULL;
2100 g_clear_pointer(&priv->config, g_key_file_free);
2101 g_clear_pointer(&priv->initial_display_map, g_hash_table_unref);
2102
2103 virt_viewer_app_free_connect_info(self);
2104
2105 G_OBJECT_CLASS (virt_viewer_app_parent_class)->dispose (object);
2106 }
2107
2108 static gboolean
virt_viewer_app_default_start(VirtViewerApp * self,GError ** error G_GNUC_UNUSED)2109 virt_viewer_app_default_start(VirtViewerApp *self, GError **error G_GNUC_UNUSED)
2110 {
2111 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2112 virt_viewer_window_show(priv->main_window);
2113 return TRUE;
2114 }
2115
virt_viewer_app_start(VirtViewerApp * self,GError ** error)2116 gboolean virt_viewer_app_start(VirtViewerApp *self, GError **error)
2117 {
2118 VirtViewerAppClass *klass;
2119
2120 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
2121 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2122 klass = VIRT_VIEWER_APP_GET_CLASS(self);
2123
2124 g_return_val_if_fail(!priv->started, TRUE);
2125
2126 priv->started = klass->start(self, error);
2127 return priv->started;
2128 }
2129
2130 static int opt_zoom = NORMAL_ZOOM_LEVEL;
2131 static gchar *opt_hotkeys = NULL;
2132 static gchar *opt_keymap = NULL;
2133 static gboolean opt_version = FALSE;
2134 static gboolean opt_verbose = FALSE;
2135 static gboolean opt_debug = FALSE;
2136 static gboolean opt_fullscreen = FALSE;
2137 static gboolean opt_kiosk = FALSE;
2138 static gboolean opt_kiosk_quit = FALSE;
2139 static gchar *opt_cursor = NULL;
2140 static gchar *opt_resize = NULL;
2141
2142 #ifndef G_OS_WIN32
2143 static gboolean
sigint_cb(gpointer data)2144 sigint_cb(gpointer data)
2145 {
2146 VirtViewerApp *self = VIRT_VIEWER_APP(data);
2147 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2148
2149 g_debug("got SIGINT, quitting\n");
2150 if (priv->started)
2151 virt_viewer_app_quit(self);
2152 else
2153 exit(EXIT_SUCCESS);
2154
2155 return priv->quitting ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
2156 }
2157 #endif
2158
2159 static void
title_maybe_changed(VirtViewerApp * self,GParamSpec * pspec G_GNUC_UNUSED,gpointer user_data G_GNUC_UNUSED)2160 title_maybe_changed(VirtViewerApp *self, GParamSpec* pspec G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED)
2161 {
2162 virt_viewer_app_set_all_window_subtitles(self);
2163 }
2164
2165 static void
virt_viewer_update_smartcard_accels(VirtViewerApp * self)2166 virt_viewer_update_smartcard_accels(VirtViewerApp *self)
2167 {
2168 gboolean sw_smartcard;
2169 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2170
2171 if (priv->session != NULL) {
2172 g_object_get(G_OBJECT(priv->session),
2173 "software-smartcard-reader", &sw_smartcard,
2174 NULL);
2175 } else {
2176 sw_smartcard = FALSE;
2177 }
2178 if (sw_smartcard) {
2179 g_debug("enabling smartcard shortcuts");
2180 if (priv->insert_smartcard_accel)
2181 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "app.smartcard-insert",
2182 (const gchar * const *)priv->insert_smartcard_accel);
2183 if (priv->remove_smartcard_accel)
2184 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "app.smartcard-remove",
2185 (const gchar * const *)priv->remove_smartcard_accel);
2186 } else {
2187 g_debug("disabling smartcard shortcuts");
2188 const gchar *no_accels[] = { NULL };
2189 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "app.smartcard-insert", no_accels);
2190 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "app.smartcard-remove", no_accels);
2191 }
2192 }
2193
2194 static void
virt_viewer_update_usbredir_accels(VirtViewerApp * self)2195 virt_viewer_update_usbredir_accels(VirtViewerApp *self)
2196 {
2197 gboolean has_usbredir;
2198 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2199
2200 if (priv->session != NULL) {
2201 g_object_get(G_OBJECT(priv->session),
2202 "has-usbredir", &has_usbredir, NULL);
2203 } else {
2204 has_usbredir = FALSE;
2205 }
2206
2207 if (has_usbredir) {
2208 if (priv->usb_device_reset_accel)
2209 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "win.usb-device-reset",
2210 (const gchar * const *)priv->usb_device_reset_accel);
2211 } else {
2212 const gchar *no_accels[] = { NULL };
2213 gtk_application_set_accels_for_action(GTK_APPLICATION(self), "win.usb-device-reset", no_accels);
2214 }
2215 }
2216
2217 static void
virt_viewer_app_action_machine_reset(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)2218 virt_viewer_app_action_machine_reset(GSimpleAction *act G_GNUC_UNUSED,
2219 GVariant *param G_GNUC_UNUSED,
2220 gpointer opaque)
2221 {
2222 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2223
2224 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2225
2226 virt_viewer_session_vm_action(virt_viewer_app_get_session(self),
2227 VIRT_VIEWER_SESSION_VM_ACTION_RESET);
2228 }
2229
2230 static void
virt_viewer_app_action_machine_powerdown(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)2231 virt_viewer_app_action_machine_powerdown(GSimpleAction *act G_GNUC_UNUSED,
2232 GVariant *param G_GNUC_UNUSED,
2233 gpointer opaque)
2234 {
2235 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2236
2237 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2238
2239 virt_viewer_session_vm_action(virt_viewer_app_get_session(self),
2240 VIRT_VIEWER_SESSION_VM_ACTION_POWER_DOWN);
2241 }
2242
2243 static void
virt_viewer_app_action_machine_pause(GSimpleAction * act,GVariant * state,gpointer opaque)2244 virt_viewer_app_action_machine_pause(GSimpleAction *act,
2245 GVariant *state,
2246 gpointer opaque)
2247 {
2248 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2249
2250 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2251 gboolean paused = g_variant_get_boolean(state);
2252 gint action;
2253
2254 g_simple_action_set_state(act, g_variant_new_boolean(paused));
2255
2256 if (paused)
2257 action = VIRT_VIEWER_SESSION_VM_ACTION_PAUSE;
2258 else
2259 action = VIRT_VIEWER_SESSION_VM_ACTION_CONTINUE;
2260
2261 virt_viewer_session_vm_action(virt_viewer_app_get_session(self), action);
2262 }
2263
2264
2265 static void
virt_viewer_app_action_smartcard_insert(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)2266 virt_viewer_app_action_smartcard_insert(GSimpleAction *act G_GNUC_UNUSED,
2267 GVariant *param G_GNUC_UNUSED,
2268 gpointer opaque)
2269 {
2270 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2271
2272 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2273
2274 virt_viewer_session_smartcard_insert(virt_viewer_app_get_session(self));
2275 }
2276
2277 static void
virt_viewer_app_action_smartcard_remove(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)2278 virt_viewer_app_action_smartcard_remove(GSimpleAction *act G_GNUC_UNUSED,
2279 GVariant *param G_GNUC_UNUSED,
2280 gpointer opaque)
2281 {
2282 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2283
2284 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2285
2286 virt_viewer_session_smartcard_remove(virt_viewer_app_get_session(self));
2287 }
2288
2289
2290 static void
virt_viewer_app_set_display_auto_resize(VirtViewerApp * self,VirtViewerDisplay * display)2291 virt_viewer_app_set_display_auto_resize(VirtViewerApp *self,
2292 VirtViewerDisplay *display)
2293 {
2294 GAction *action = g_action_map_lookup_action(G_ACTION_MAP(self), "auto-resize");
2295 GVariant *state = g_action_get_state(action);
2296 gboolean resize = g_variant_get_boolean(state);
2297 virt_viewer_display_set_auto_resize(display, resize);
2298 }
2299
2300 static void
set_window_autoresize(gpointer value,gpointer user_data)2301 set_window_autoresize(gpointer value, gpointer user_data)
2302 {
2303 VirtViewerApp *self = VIRT_VIEWER_APP(user_data);
2304 VirtViewerDisplay *display = virt_viewer_window_get_display(VIRT_VIEWER_WINDOW(value));
2305 virt_viewer_app_set_display_auto_resize(self, display);
2306 }
2307
2308 static void
virt_viewer_app_action_auto_resize(GSimpleAction * act G_GNUC_UNUSED,GVariant * state,gpointer opaque)2309 virt_viewer_app_action_auto_resize(GSimpleAction *act G_GNUC_UNUSED,
2310 GVariant *state,
2311 gpointer opaque)
2312 {
2313 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2314
2315 VirtViewerApp *self = VIRT_VIEWER_APP(opaque);
2316 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2317 gboolean resize = g_variant_get_boolean(state);
2318
2319 g_simple_action_set_state(act, g_variant_new_boolean(resize));
2320
2321 g_list_foreach(priv->windows, set_window_autoresize, self);
2322 }
2323
2324 static void
virt_viewer_app_action_window(VirtViewerApp * self,VirtViewerWindow * win,GSimpleAction * act,GVariant * state)2325 virt_viewer_app_action_window(VirtViewerApp *self,
2326 VirtViewerWindow *win,
2327 GSimpleAction *act,
2328 GVariant *state)
2329 {
2330 VirtViewerDisplay *display;
2331 VirtViewerAppPrivate *priv;
2332 gboolean visible = g_variant_get_boolean(state);
2333
2334 g_return_if_fail(VIRT_VIEWER_IS_WINDOW(win));
2335
2336 priv = virt_viewer_app_get_instance_private(self);
2337 display = virt_viewer_window_get_display(win);
2338
2339 if (visible) {
2340 virt_viewer_window_show(win);
2341 } else {
2342 if (virt_viewer_app_get_n_windows_visible(self) > 1) {
2343 virt_viewer_window_hide(win);
2344 } else {
2345 virt_viewer_app_maybe_quit(self, win);
2346 if (!priv->quitting) {
2347 /* the last item remains active, doesn't matter if we quit */
2348 visible = TRUE;
2349 g_action_change_state(G_ACTION(act),
2350 g_variant_new_boolean(visible));
2351 }
2352 }
2353 }
2354
2355 if (!priv->quitting)
2356 virt_viewer_session_update_displays_geometry(virt_viewer_display_get_session(display));
2357
2358 g_simple_action_set_state(act, g_variant_new_boolean(visible));
2359 }
2360
2361
2362 static void
virt_viewer_app_action_monitor(GSimpleAction * act,GVariant * state,gpointer opaque)2363 virt_viewer_app_action_monitor(GSimpleAction *act,
2364 GVariant *state,
2365 gpointer opaque)
2366 {
2367 VirtViewerApp *self;
2368 VirtViewerWindow *win;
2369 VirtViewerDisplay *display;
2370 VirtViewerAppPrivate *priv;
2371 int nth;
2372
2373 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2374 self = VIRT_VIEWER_APP(opaque);
2375
2376 priv = virt_viewer_app_get_instance_private(self);
2377 if (priv->quitting)
2378 return;
2379
2380 nth = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(act), "nth"));
2381 display = VIRT_VIEWER_DISPLAY(g_hash_table_lookup(priv->displays, GINT_TO_POINTER(nth)));
2382 win = ensure_window_for_display(self, display);
2383
2384 virt_viewer_app_action_window(self, win, act, state);
2385 }
2386
2387 static void
virt_viewer_app_action_vte(GSimpleAction * act,GVariant * state,gpointer opaque)2388 virt_viewer_app_action_vte(GSimpleAction *act,
2389 GVariant *state,
2390 gpointer opaque)
2391 {
2392 VirtViewerApp *self;
2393 VirtViewerWindow *win;
2394 VirtViewerAppPrivate *priv;
2395 const gchar *name;
2396
2397 g_return_if_fail(VIRT_VIEWER_IS_APP(opaque));
2398 self = VIRT_VIEWER_APP(opaque);
2399
2400 priv = virt_viewer_app_get_instance_private(self);
2401 if (priv->quitting)
2402 return;
2403
2404 name = g_object_get_data(G_OBJECT(act), "vte");
2405 win = virt_viewer_app_get_vte_window(self, name);
2406 virt_viewer_app_action_window(self, win, act, state);
2407 }
2408
2409 static GActionEntry actions[] = {
2410 { .name = "machine-reset",
2411 .activate = virt_viewer_app_action_machine_reset },
2412 { .name = "machine-powerdown",
2413 .activate = virt_viewer_app_action_machine_powerdown },
2414 { .name = "machine-pause",
2415 .state = "false",
2416 .change_state = virt_viewer_app_action_machine_pause },
2417 { .name = "smartcard-insert",
2418 .activate = virt_viewer_app_action_smartcard_insert },
2419 { .name = "smartcard-remove",
2420 .activate = virt_viewer_app_action_smartcard_remove },
2421 { .name = "auto-resize",
2422 .state = "true",
2423 .change_state = virt_viewer_app_action_auto_resize },
2424 };
2425
2426 static const struct {
2427 const char *name;
2428 const char *action;
2429 const gchar *default_accels[3];
2430 } hotkey_defaults[] = {
2431 { "toggle-fullscreen", "win.fullscreen", {"F11", NULL, NULL} },
2432 { "zoom-in", "win.zoom-in", { "<Ctrl>plus", "<Ctrl>KP_Add", NULL } },
2433 { "zoom-out", "win.zoom-out", { "<Ctrl>minus", "<Ctrl>KP_Subtract", NULL } },
2434 { "zoom-reset", "win.zoom-reset", { "<Ctrl>0", "<Ctrl>KP_0", NULL } },
2435 { "release-cursor", "win.release-cursor", {"<Shift>F12", NULL, NULL} },
2436 { "smartcard-insert", "app.smartcard-insert", {"<Shift>F8", NULL, NULL} },
2437 { "smartcard-remove", "app.smartcard-remove", {"<Shift>F9", NULL, NULL} },
2438 { "secure-attention", "win.secure-attention", {"<Ctrl><Alt>End", NULL, NULL} },
2439 { "usb-device-reset", "win.usb-device-reset", {"<Ctrl><Shift>r", NULL, NULL} },
2440 };
2441
2442 static gchar **hotkey_names;
2443
2444 /* g_strdupv() will not take a const gchar** */
2445 static gchar**
g_strdupvc(const gchar * const * str_array)2446 g_strdupvc(const gchar* const *str_array)
2447 {
2448 if (!str_array)
2449 return NULL;
2450 gsize i = 0;
2451 gchar **retval;
2452 while (str_array[i])
2453 ++i;
2454 retval = g_new(gchar*, i + 1);
2455 i = 0;
2456 while (str_array[i]) {
2457 retval[i] = g_strdup(str_array[i]);
2458 ++i;
2459 }
2460 retval[i] = NULL;
2461 return retval;
2462 }
2463
2464 static void
virt_viewer_app_on_application_startup(GApplication * app)2465 virt_viewer_app_on_application_startup(GApplication *app)
2466 {
2467 VirtViewerApp *self = VIRT_VIEWER_APP(app);
2468 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2469 GError *error = NULL;
2470 gint i;
2471 #ifndef G_OS_WIN32
2472 GtkSettings *gtk_settings;
2473 #endif
2474
2475 G_APPLICATION_CLASS(virt_viewer_app_parent_class)->startup(app);
2476
2477 #ifndef G_OS_WIN32
2478 gtk_settings = gtk_settings_get_default();
2479 g_object_set(G_OBJECT(gtk_settings),
2480 "gtk-application-prefer-dark-theme",
2481 TRUE, NULL);
2482 #endif
2483
2484 priv->resource = virt_viewer_get_resource();
2485
2486 virt_viewer_app_set_debug(opt_debug);
2487 virt_viewer_app_set_fullscreen(self, opt_fullscreen);
2488
2489 virt_viewer_app_set_keymap(self, opt_keymap);
2490
2491 priv->verbose = opt_verbose;
2492 priv->quit_on_disconnect = opt_kiosk ? opt_kiosk_quit : TRUE;
2493
2494 priv->main_window = virt_viewer_app_window_new(self,
2495 virt_viewer_app_get_first_monitor(self));
2496 priv->main_notebook = GTK_WIDGET(virt_viewer_window_get_notebook(priv->main_window));
2497 priv->initial_display_map = virt_viewer_app_get_monitor_mapping_for_section(self, "fallback");
2498
2499 virt_viewer_app_set_kiosk(self, opt_kiosk);
2500
2501 priv->release_cursor_display_hotkey = NULL;
2502 hotkey_names = g_new(gchar*, G_N_ELEMENTS(hotkey_defaults) + 1);
2503 for (i = 0 ; i < G_N_ELEMENTS(hotkey_defaults); i++) {
2504 hotkey_names[i] = g_strdup(hotkey_defaults[i].name);
2505 if (g_str_equal(hotkey_defaults[i].name, "smartcard-insert")) {
2506 priv->insert_smartcard_accel = g_strdupvc(hotkey_defaults[i].default_accels);
2507 continue;
2508 }
2509 if (g_str_equal(hotkey_defaults[i].name, "smartcard-remove")) {
2510 priv->remove_smartcard_accel = g_strdupvc(hotkey_defaults[i].default_accels);
2511 continue;
2512 }
2513 if (g_str_equal(hotkey_defaults[i].name, "usb-device-reset")) {
2514 priv->usb_device_reset_accel = g_strdupvc(hotkey_defaults[i].default_accels);
2515 continue;
2516 }
2517 gtk_application_set_accels_for_action(GTK_APPLICATION(app),
2518 hotkey_defaults[i].action,
2519 hotkey_defaults[i].default_accels);
2520 }
2521 hotkey_names[i] = NULL;
2522
2523 virt_viewer_app_set_hotkeys(self, opt_hotkeys);
2524
2525 if (opt_zoom < MIN_ZOOM_LEVEL || opt_zoom > MAX_ZOOM_LEVEL) {
2526 g_printerr(_("Zoom level must be within %d-%d\n"), MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
2527 opt_zoom = NORMAL_ZOOM_LEVEL;
2528 }
2529
2530 virt_viewer_app_set_actions_sensitive(self);
2531 virt_viewer_window_set_zoom_level(priv->main_window, opt_zoom);
2532
2533 // Restore initial state of config-share-clipboard property from config and notify about it
2534 virt_viewer_app_set_config_share_clipboard(self, virt_viewer_app_get_config_share_clipboard(self));
2535
2536 if (!virt_viewer_app_start(self, &error)) {
2537 if (error && !g_error_matches(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_CANCELLED))
2538 virt_viewer_app_simple_message_dialog(self, "%s", error->message);
2539
2540 g_clear_error(&error);
2541 g_application_quit(app);
2542 return;
2543 }
2544 }
2545
2546 static gboolean
virt_viewer_app_local_command_line(GApplication * gapp,gchar *** args,int * status)2547 virt_viewer_app_local_command_line (GApplication *gapp,
2548 gchar ***args,
2549 int *status)
2550 {
2551 VirtViewerApp *self = VIRT_VIEWER_APP(gapp);
2552 gboolean ret = FALSE;
2553 GError *error = NULL;
2554 GOptionContext *context = g_option_context_new(NULL);
2555 GOptionGroup *group = g_option_group_new("virt-viewer", NULL, NULL, gapp, NULL);
2556
2557 *status = 0;
2558 g_option_context_set_main_group(context, group);
2559 VIRT_VIEWER_APP_GET_CLASS(self)->add_option_entries(self, context, group);
2560
2561 g_option_context_add_group(context, gtk_get_option_group(FALSE));
2562
2563 #ifdef HAVE_GTK_VNC
2564 g_option_context_add_group(context, vnc_display_get_option_group());
2565 #endif
2566
2567 #ifdef HAVE_SPICE_GTK
2568 g_option_context_add_group(context, spice_get_option_group());
2569 #endif
2570
2571 if (!g_option_context_parse_strv(context, args, &error)) {
2572 if (error != NULL) {
2573 g_printerr("%s\n", error->message);
2574 g_error_free(error);
2575 }
2576
2577 *status = 1;
2578 ret = TRUE;
2579 goto end;
2580 }
2581
2582 if (opt_version) {
2583 g_print(_("%s version %s"), g_get_prgname(), VERSION BUILDID);
2584 #ifdef REMOTE_VIEWER_OS_ID
2585 g_print(" (OS ID: %s)", REMOTE_VIEWER_OS_ID);
2586 #endif
2587 g_print("\n");
2588 ret = TRUE;
2589 }
2590
2591 if (opt_cursor) {
2592 int cursor = virt_viewer_enum_from_string(VIRT_VIEWER_TYPE_CURSOR,
2593 opt_cursor);
2594 if (cursor < 0) {
2595 g_printerr("unknown value '%s' for --cursor\n", opt_cursor);
2596 *status = 1;
2597 ret = TRUE;
2598 goto end;
2599 }
2600 virt_viewer_app_set_cursor(self, cursor);
2601 }
2602
2603 if (opt_resize) {
2604 GAction *resize = g_action_map_lookup_action(G_ACTION_MAP(self),
2605 "auto-resize");
2606 gboolean enabled = TRUE;
2607 if (g_str_equal(opt_resize, "always")) {
2608 enabled = TRUE;
2609 } else if (g_str_equal(opt_resize, "never")) {
2610 enabled = FALSE;
2611 } else {
2612 g_printerr("--auto-resize expects 'always' or 'never'\n");
2613 *status = 1;
2614 ret = TRUE;
2615 goto end;
2616 }
2617 g_simple_action_set_state(G_SIMPLE_ACTION(resize),
2618 g_variant_new_boolean(enabled));
2619 }
2620
2621 end:
2622 g_option_context_free(context);
2623 return ret;
2624 }
2625
2626 static void
virt_viewer_app_class_init(VirtViewerAppClass * klass)2627 virt_viewer_app_class_init (VirtViewerAppClass *klass)
2628 {
2629 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2630 GApplicationClass *g_app_class = G_APPLICATION_CLASS(klass);
2631
2632 object_class->get_property = virt_viewer_app_get_property;
2633 object_class->set_property = virt_viewer_app_set_property;
2634 object_class->dispose = virt_viewer_app_dispose;
2635
2636 g_app_class->local_command_line = virt_viewer_app_local_command_line;
2637 g_app_class->startup = virt_viewer_app_on_application_startup;
2638 g_app_class->command_line = NULL; /* inhibit GApplication default handler */
2639
2640 klass->start = virt_viewer_app_default_start;
2641 klass->initial_connect = virt_viewer_app_default_initial_connect;
2642 klass->activate = virt_viewer_app_default_activate;
2643 klass->deactivated = virt_viewer_app_default_deactivated;
2644 klass->open_connection = virt_viewer_app_default_open_connection;
2645 klass->add_option_entries = virt_viewer_app_add_option_entries;
2646
2647 g_object_class_install_property(object_class,
2648 PROP_VERBOSE,
2649 g_param_spec_boolean("verbose",
2650 "Verbose",
2651 "Verbose trace",
2652 FALSE,
2653 G_PARAM_READABLE |
2654 G_PARAM_WRITABLE |
2655 G_PARAM_STATIC_STRINGS));
2656
2657 g_object_class_install_property(object_class,
2658 PROP_SESSION,
2659 g_param_spec_object("session",
2660 "Session",
2661 "ViewerSession",
2662 VIRT_VIEWER_TYPE_SESSION,
2663 G_PARAM_READABLE |
2664 G_PARAM_STATIC_STRINGS));
2665
2666 g_object_class_install_property(object_class,
2667 PROP_GUEST_NAME,
2668 g_param_spec_string("guest-name",
2669 "Guest name",
2670 "Guest name",
2671 "",
2672 G_PARAM_READABLE |
2673 G_PARAM_WRITABLE |
2674 G_PARAM_STATIC_STRINGS));
2675
2676 g_object_class_install_property(object_class,
2677 PROP_GURI,
2678 g_param_spec_string("guri",
2679 "guri",
2680 "Remote graphical URI",
2681 "",
2682 G_PARAM_READWRITE |
2683 G_PARAM_STATIC_STRINGS));
2684
2685 g_object_class_install_property(object_class,
2686 PROP_FULLSCREEN,
2687 g_param_spec_boolean("fullscreen",
2688 "Fullscreen",
2689 "Fullscreen",
2690 FALSE,
2691 G_PARAM_READWRITE |
2692 G_PARAM_STATIC_STRINGS));
2693
2694 g_object_class_install_property(object_class,
2695 PROP_TITLE,
2696 g_param_spec_string("title",
2697 "Title",
2698 "Title",
2699 "",
2700 G_PARAM_READABLE |
2701 G_PARAM_WRITABLE |
2702 G_PARAM_STATIC_STRINGS));
2703
2704 g_object_class_install_property(object_class,
2705 PROP_RELEASE_CURSOR_DISPLAY_HOTKEY,
2706 g_param_spec_string("release-cursor-display-hotkey",
2707 "Release Cursor Display Hotkey",
2708 "Display-managed hotkey to ungrab keyboard and mouse",
2709 NULL,
2710 G_PARAM_READWRITE |
2711 G_PARAM_STATIC_STRINGS));
2712
2713 g_object_class_install_property(object_class,
2714 PROP_KIOSK,
2715 g_param_spec_boolean("kiosk",
2716 "Kiosk",
2717 "Kiosk mode",
2718 FALSE,
2719 G_PARAM_CONSTRUCT |
2720 G_PARAM_READWRITE |
2721 G_PARAM_STATIC_STRINGS));
2722
2723 g_object_class_install_property(object_class,
2724 PROP_QUIT_ON_DISCONNECT,
2725 g_param_spec_boolean("quit-on-disconnect",
2726 "Quit on disconnect",
2727 "Quit on disconnect",
2728 TRUE,
2729 G_PARAM_READWRITE |
2730 G_PARAM_STATIC_STRINGS));
2731
2732 g_object_class_install_property(object_class,
2733 PROP_UUID,
2734 g_param_spec_string("uuid",
2735 "uuid",
2736 "uuid",
2737 NULL,
2738 G_PARAM_READABLE |
2739 G_PARAM_WRITABLE |
2740 G_PARAM_STATIC_STRINGS));
2741
2742 g_object_class_install_property(object_class,
2743 PROP_VM_UI,
2744 g_param_spec_boolean("vm-ui",
2745 "VM UI",
2746 "QEMU UI & behaviour",
2747 FALSE,
2748 G_PARAM_READWRITE |
2749 G_PARAM_STATIC_STRINGS));
2750
2751 g_object_class_install_property(object_class,
2752 PROP_VM_RUNNING,
2753 g_param_spec_boolean("vm-running",
2754 "VM running",
2755 "VM running",
2756 FALSE,
2757 G_PARAM_READWRITE |
2758 G_PARAM_STATIC_STRINGS));
2759
2760 g_object_class_install_property(object_class,
2761 PROP_CONFIG_SHARE_CLIPBOARD,
2762 g_param_spec_boolean("config-share-clipboard",
2763 "Share clipboard",
2764 "Indicates whether to share clipboard",
2765 TRUE, /* backwards-compatible default value */
2766 G_PARAM_READWRITE |
2767 G_PARAM_STATIC_STRINGS));
2768
2769 g_object_class_install_property(object_class,
2770 PROP_SUPPORTS_SHARE_CLIPBOARD,
2771 g_param_spec_boolean("supports-share-clipboard",
2772 "Support for share clipboard",
2773 "Indicates whether to support for clipboard sharing is available",
2774 FALSE,
2775 G_PARAM_READWRITE |
2776 G_PARAM_STATIC_STRINGS));
2777 }
2778
2779 static void
virt_viewer_app_init(VirtViewerApp * self)2780 virt_viewer_app_init(VirtViewerApp *self)
2781 {
2782 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2783 GError *error = NULL;
2784 priv = virt_viewer_app_get_instance_private(self);
2785
2786 gtk_window_set_default_icon_name("virt-viewer");
2787
2788 #ifndef G_OS_WIN32
2789 g_unix_signal_add (SIGINT, sigint_cb, self);
2790 #endif
2791
2792 priv->displays = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
2793 priv->config = g_key_file_new();
2794 priv->config_file = g_build_filename(g_get_user_config_dir(),
2795 "virt-viewer", "settings", NULL);
2796 g_key_file_load_from_file(priv->config, priv->config_file,
2797 G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error);
2798
2799 if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
2800 g_debug("No configuration file %s", priv->config_file);
2801 else if (error)
2802 g_warning("Couldn't load configuration: %s", error->message);
2803
2804 g_clear_error(&error);
2805
2806 g_signal_connect(self, "notify::guest-name", G_CALLBACK(title_maybe_changed), NULL);
2807 g_signal_connect(self, "notify::title", G_CALLBACK(title_maybe_changed), NULL);
2808 g_signal_connect(self, "notify::guri", G_CALLBACK(title_maybe_changed), NULL);
2809
2810 g_action_map_add_action_entries(G_ACTION_MAP(self),
2811 actions,
2812 G_N_ELEMENTS(actions),
2813 self);
2814 }
2815
2816 void
virt_viewer_app_set_direct(VirtViewerApp * self,gboolean direct)2817 virt_viewer_app_set_direct(VirtViewerApp *self, gboolean direct)
2818 {
2819 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2820
2821 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2822 priv->direct = direct;
2823 }
2824
virt_viewer_app_get_direct(VirtViewerApp * self)2825 gboolean virt_viewer_app_get_direct(VirtViewerApp *self)
2826 {
2827 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
2828
2829 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2830 return priv->direct;
2831 }
2832
2833 gchar*
virt_viewer_app_get_release_cursor_display_hotkey(VirtViewerApp * self)2834 virt_viewer_app_get_release_cursor_display_hotkey(VirtViewerApp *self)
2835 {
2836 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
2837
2838 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2839 return priv->release_cursor_display_hotkey;
2840 }
2841
2842 void
virt_viewer_app_set_release_cursor_display_hotkey(VirtViewerApp * self,const gchar * hotkey)2843 virt_viewer_app_set_release_cursor_display_hotkey(VirtViewerApp *self, const gchar *hotkey)
2844 {
2845 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2846
2847 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2848 g_free(priv->release_cursor_display_hotkey);
2849 priv->release_cursor_display_hotkey = g_strdup(hotkey);
2850 g_object_notify(G_OBJECT(self), "release-cursor-display-hotkey");
2851 }
2852
2853 gchar**
virt_viewer_app_get_hotkey_names(void)2854 virt_viewer_app_get_hotkey_names(void)
2855 {
2856 return hotkey_names;
2857 }
2858
2859 void
virt_viewer_app_clear_hotkeys(VirtViewerApp * self)2860 virt_viewer_app_clear_hotkeys(VirtViewerApp *self)
2861 {
2862 gint i;
2863 const gchar *no_accels[] = { NULL };
2864 for (i = 0 ; i < G_N_ELEMENTS(hotkey_defaults); i++) {
2865 gtk_application_set_accels_for_action(GTK_APPLICATION(self),
2866 hotkey_defaults[i].action,
2867 no_accels);
2868 }
2869
2870 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2871 virt_viewer_app_set_release_cursor_display_hotkey(self, "Control_L+Alt_L");
2872 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2873 g_strfreev(priv->insert_smartcard_accel);
2874 priv->insert_smartcard_accel = NULL;
2875 g_strfreev(priv->remove_smartcard_accel);
2876 priv->remove_smartcard_accel = NULL;
2877 g_strfreev(priv->usb_device_reset_accel);
2878 priv->usb_device_reset_accel = NULL;
2879 }
2880
2881 void
virt_viewer_app_set_hotkey(VirtViewerApp * self,const gchar * hotkey_name,const gchar * hotkey)2882 virt_viewer_app_set_hotkey(VirtViewerApp *self, const gchar *hotkey_name,
2883 const gchar *hotkey)
2884 {
2885 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2886 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2887
2888 const gchar *action = NULL;
2889 int i;
2890 for (i = 0; i < G_N_ELEMENTS(hotkey_defaults); i++) {
2891 if (g_str_equal(hotkey_name, hotkey_defaults[i].name)) {
2892 action = hotkey_defaults[i].action;
2893 break;
2894 }
2895 }
2896 if (action == NULL) {
2897 g_warning("Unknown hotkey name %s", hotkey_name);
2898 return;
2899 }
2900
2901 gchar *accel = spice_hotkey_to_gtk_accelerator(hotkey);
2902 const gchar *accels[] = { accel, NULL };
2903 guint accel_key;
2904 GdkModifierType accel_mods;
2905 /*
2906 * First try the spice translated accel.
2907 * Works for basic modifiers and single letters/numbers
2908 * where forced uppercasing matches GTK key names
2909 */
2910 gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
2911 if (accel_key == 0 && accel_mods == 0) {
2912 /* Fallback to native GTK accels to cope with
2913 * case sensitive accels
2914 */
2915 accels[0] = hotkey;
2916 gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
2917 }
2918 if (g_str_equal(hotkey_name, "release-cursor")) {
2919 if (accel_key == 0) {
2920 /* GTK does not support using modifiers as hotkeys without any non-modifiers
2921 * (eg. CTRL+ALT), however the displays do support this for the grab sequence.
2922 */
2923 virt_viewer_app_set_release_cursor_display_hotkey(self, hotkey);
2924 g_free(accel);
2925 return;
2926 }
2927 virt_viewer_app_set_release_cursor_display_hotkey(self, NULL);
2928 }
2929 if (accel_key == 0) {
2930 g_warning("Invalid hotkey '%s' for '%s'", hotkey, hotkey_name);
2931 g_free(accel);
2932 return;
2933 }
2934
2935 if (g_str_equal(hotkey_name, "smartcard-insert")) {
2936 g_strfreev(priv->insert_smartcard_accel);
2937 priv->insert_smartcard_accel = g_strdupvc(accels);
2938 g_free(accel);
2939 virt_viewer_update_smartcard_accels(self);
2940 return;
2941 }
2942 if (g_str_equal(hotkey_name, "smartcard-remove")) {
2943 g_strfreev(priv->remove_smartcard_accel);
2944 priv->remove_smartcard_accel = g_strdupvc(accels);
2945 g_free(accel);
2946 virt_viewer_update_smartcard_accels(self);
2947 return;
2948 }
2949 if (g_str_equal(hotkey_name, "usb-device-reset")) {
2950 g_strfreev(priv->usb_device_reset_accel);
2951 priv->usb_device_reset_accel = g_strdupvc(accels);
2952 g_free(accel);
2953 virt_viewer_update_usbredir_accels(self);
2954 return;
2955 }
2956
2957 gtk_application_set_accels_for_action(GTK_APPLICATION(self), action, accels);
2958 g_free(accel);
2959 }
2960
2961 void
virt_viewer_app_set_hotkeys(VirtViewerApp * self,const gchar * hotkeys_str)2962 virt_viewer_app_set_hotkeys(VirtViewerApp *self, const gchar *hotkeys_str)
2963 {
2964 gchar **hotkey, **hotkeys = NULL;
2965
2966 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2967
2968 if (hotkeys_str)
2969 hotkeys = g_strsplit(hotkeys_str, ",", -1);
2970
2971 if (!hotkeys || g_strv_length(hotkeys) == 0) {
2972 g_strfreev(hotkeys);
2973 return;
2974 }
2975
2976 virt_viewer_app_clear_hotkeys(self);
2977
2978 for (hotkey = hotkeys; *hotkey != NULL; hotkey++) {
2979 gchar *eq = strstr(*hotkey, "=");
2980 const gchar *value = (eq == NULL) ? NULL : (*eq = '\0', eq + 1);
2981 if (value == NULL || *value == '\0') {
2982 g_warning("Missing value for hotkey '%s'", *hotkey);
2983 continue;
2984 }
2985
2986 virt_viewer_app_set_hotkey(self, *hotkey, value);
2987 }
2988 g_strfreev(hotkeys);
2989 }
2990
2991 void
virt_viewer_app_set_attach(VirtViewerApp * self,gboolean attach)2992 virt_viewer_app_set_attach(VirtViewerApp *self, gboolean attach)
2993 {
2994 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
2995
2996 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
2997 priv->attach = attach;
2998 }
2999
3000 gboolean
virt_viewer_app_get_attach(VirtViewerApp * self)3001 virt_viewer_app_get_attach(VirtViewerApp *self)
3002 {
3003 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3004
3005 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3006 return priv->attach;
3007 }
3008
3009 void
virt_viewer_app_set_shared(VirtViewerApp * self,gboolean shared)3010 virt_viewer_app_set_shared(VirtViewerApp *self, gboolean shared)
3011 {
3012 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3013
3014 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3015 priv->shared = shared;
3016 }
3017
3018 gboolean
virt_viewer_app_get_shared(VirtViewerApp * self)3019 virt_viewer_app_get_shared(VirtViewerApp *self)
3020 {
3021 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3022
3023 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3024 return priv->shared;
3025 }
3026
virt_viewer_app_set_cursor(VirtViewerApp * self,VirtViewerCursor cursor)3027 void virt_viewer_app_set_cursor(VirtViewerApp *self, VirtViewerCursor cursor)
3028 {
3029 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3030
3031 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3032 priv->cursor = cursor;
3033 }
3034
virt_viewer_app_get_cursor(VirtViewerApp * self)3035 VirtViewerCursor virt_viewer_app_get_cursor(VirtViewerApp *self)
3036 {
3037 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3038
3039 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3040 return priv->cursor;
3041 }
3042
3043 gboolean
virt_viewer_app_is_active(VirtViewerApp * self)3044 virt_viewer_app_is_active(VirtViewerApp *self)
3045 {
3046 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3047
3048 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3049 return priv->active;
3050 }
3051
3052 gboolean
virt_viewer_app_has_session(VirtViewerApp * self)3053 virt_viewer_app_has_session(VirtViewerApp *self)
3054 {
3055 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3056
3057 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3058 return priv->session != NULL;
3059 }
3060
3061 static void
virt_viewer_app_update_pretty_address(VirtViewerApp * self)3062 virt_viewer_app_update_pretty_address(VirtViewerApp *self)
3063 {
3064 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3065 g_free(priv->pretty_address);
3066 priv->pretty_address = NULL;
3067 if (priv->guri)
3068 priv->pretty_address = g_strdup(priv->guri);
3069 else if (priv->gport)
3070 priv->pretty_address = g_strdup_printf("%s:%s", priv->ghost, priv->gport);
3071 else if (priv->host && priv->unixsock)
3072 priv->pretty_address = g_strdup_printf("%s:%s", priv->host, priv->unixsock);
3073 }
3074
3075 typedef struct {
3076 VirtViewerApp *app;
3077 gboolean fullscreen;
3078 } FullscreenOptions;
3079
fullscreen_cb(gpointer value,gpointer user_data)3080 static void fullscreen_cb(gpointer value,
3081 gpointer user_data)
3082 {
3083 FullscreenOptions *options = (FullscreenOptions *)user_data;
3084 gint nth = 0;
3085 VirtViewerWindow *vwin = VIRT_VIEWER_WINDOW(value);
3086 VirtViewerDisplay *display = virt_viewer_window_get_display(vwin);
3087
3088 /* At startup, the main window will not yet have an associated display, so
3089 * assume that it's the first display */
3090 if (display)
3091 nth = virt_viewer_display_get_nth(display);
3092 g_debug("fullscreen display %d: %d", nth, options->fullscreen);
3093
3094 if (options->fullscreen)
3095 app_window_try_fullscreen(options->app, vwin, nth);
3096 else
3097 virt_viewer_window_leave_fullscreen(vwin);
3098 }
3099
3100 gboolean
virt_viewer_app_get_fullscreen(VirtViewerApp * self)3101 virt_viewer_app_get_fullscreen(VirtViewerApp *self)
3102 {
3103 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3104
3105 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3106 return priv->fullscreen;
3107 }
3108
3109 static void
virt_viewer_app_set_fullscreen(VirtViewerApp * self,gboolean fullscreen)3110 virt_viewer_app_set_fullscreen(VirtViewerApp *self, gboolean fullscreen)
3111 {
3112 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3113 FullscreenOptions options = {
3114 .app = self,
3115 .fullscreen = fullscreen,
3116 };
3117
3118 /* we iterate unconditionnaly, even if it was set before to update new windows */
3119 priv->fullscreen = fullscreen;
3120 g_list_foreach(priv->windows, fullscreen_cb, &options);
3121
3122 g_object_notify(G_OBJECT(self), "fullscreen");
3123 }
3124
3125
3126 static gint
update_menu_displays_sort(gconstpointer a,gconstpointer b)3127 update_menu_displays_sort(gconstpointer a, gconstpointer b)
3128 {
3129 const int ai = GPOINTER_TO_INT(a);
3130 const int bi = GPOINTER_TO_INT(b);
3131
3132 if (ai > bi)
3133 return 1;
3134 else if (ai < bi)
3135 return -1;
3136 else
3137 return 0;
3138 }
3139
3140
3141 static void
window_update_menu_displays_cb(gpointer value,gpointer user_data)3142 window_update_menu_displays_cb(gpointer value,
3143 gpointer user_data)
3144 {
3145 VirtViewerApp *self = VIRT_VIEWER_APP(user_data);
3146 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3147 VirtViewerWindow *window = VIRT_VIEWER_WINDOW(value);
3148 GMenuModel *menu;
3149 GList *keys = g_hash_table_get_keys(priv->displays);
3150 GList *tmp;
3151 int nth;
3152
3153 keys = g_list_sort(keys, update_menu_displays_sort);
3154
3155 menu = virt_viewer_window_get_menu_displays(window);
3156 g_menu_remove_all(G_MENU(menu));
3157
3158 tmp = keys;
3159 while (tmp) {
3160 GMenuItem *item;
3161 gchar *label;
3162 gchar *actionname;
3163
3164 nth = GPOINTER_TO_INT(tmp->data);
3165 actionname = g_strdup_printf("app.monitor-%d", nth);
3166 label = g_strdup_printf(_("Display _%d"), nth + 1);
3167 item = g_menu_item_new(label, actionname);
3168
3169 g_menu_append_item(G_MENU(menu), item);
3170
3171 g_free(label);
3172 g_free(actionname);
3173
3174 tmp = tmp->next;
3175 }
3176
3177 for (tmp = priv->windows, nth = 0; tmp; tmp = tmp->next, nth++) {
3178 VirtViewerWindow *win = VIRT_VIEWER_WINDOW(tmp->data);
3179 VirtViewerDisplay *display = virt_viewer_window_get_display(win);
3180
3181 if (VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
3182 gchar *name = NULL;
3183 GMenuItem *item;
3184 gchar *actionname;
3185
3186 g_object_get(display, "name", &name, NULL);
3187 actionname = g_strdup_printf("app.vte-%d", nth);
3188
3189 item = g_menu_item_new(name, actionname);
3190
3191 g_menu_append_item(G_MENU(menu), item);
3192
3193 g_free(actionname);
3194 g_free(name);
3195 }
3196 }
3197
3198 g_list_free(keys);
3199 }
3200
3201 static void
virt_viewer_app_clear_window_actions(VirtViewerApp * self)3202 virt_viewer_app_clear_window_actions(VirtViewerApp *self)
3203 {
3204 gchar **oldactions = g_action_group_list_actions(G_ACTION_GROUP(self));
3205 int i;
3206
3207 for (i = 0; oldactions && oldactions[i] != NULL; i++) {
3208 if (g_str_has_prefix(oldactions[i], "monitor-") ||
3209 g_str_has_prefix(oldactions[i], "vte-")) {
3210 g_action_map_remove_action(G_ACTION_MAP(self), oldactions[i]);
3211 }
3212 }
3213
3214 g_strfreev(oldactions);
3215 }
3216
3217
3218 static void
virt_viewer_app_create_window_actions(VirtViewerApp * self)3219 virt_viewer_app_create_window_actions(VirtViewerApp *self)
3220 {
3221 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3222 GList *keys = g_hash_table_get_keys(priv->displays);
3223 GList *tmp;
3224 gboolean sensitive;
3225 gboolean visible;
3226 GSimpleAction *action;
3227 gchar *actionname;
3228 int nth;
3229
3230 tmp = keys;
3231 while (tmp) {
3232 VirtViewerWindow *vwin;
3233 VirtViewerDisplay *display;
3234
3235 nth = GPOINTER_TO_INT(tmp->data);
3236 actionname = g_strdup_printf("monitor-%d", nth);
3237
3238 vwin = virt_viewer_app_get_nth_window(self, nth);
3239 display = VIRT_VIEWER_DISPLAY(g_hash_table_lookup(priv->displays, tmp->data));
3240 visible = vwin && gtk_widget_get_visible(GTK_WIDGET(virt_viewer_window_get_window(vwin)));
3241
3242 sensitive = visible;
3243 if (display) {
3244 guint hint = virt_viewer_display_get_show_hint(display);
3245
3246 if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY)
3247 sensitive = TRUE;
3248
3249 if (virt_viewer_display_get_selectable(display))
3250 sensitive = TRUE;
3251 }
3252
3253 action = g_simple_action_new_stateful(actionname,
3254 NULL,
3255 g_variant_new_boolean(visible));
3256 g_object_set_data(G_OBJECT(action), "nth", GINT_TO_POINTER(nth));
3257 g_simple_action_set_enabled(action,
3258 sensitive);
3259
3260 g_signal_connect(action, "change-state",
3261 G_CALLBACK(virt_viewer_app_action_monitor),
3262 self);
3263
3264 g_action_map_add_action(G_ACTION_MAP(self), G_ACTION(action));
3265
3266
3267 g_free(actionname);
3268
3269 tmp = tmp->next;
3270 }
3271
3272 for (tmp = priv->windows, nth = 0; tmp; tmp = tmp->next, nth++) {
3273 VirtViewerWindow *win = VIRT_VIEWER_WINDOW(tmp->data);
3274 VirtViewerDisplay *display = virt_viewer_window_get_display(win);
3275 gchar *name;
3276
3277 if (!VIRT_VIEWER_IS_DISPLAY_VTE(display)) {
3278 continue;
3279 }
3280
3281 g_object_get(display, "name", &name, NULL);
3282 actionname = g_strdup_printf("vte-%d", nth);
3283
3284 visible = gtk_widget_get_visible(GTK_WIDGET(virt_viewer_window_get_window(win)));
3285
3286 action = g_simple_action_new_stateful(actionname,
3287 NULL,
3288 g_variant_new_boolean(visible));
3289 g_object_set_data_full(G_OBJECT(action), "vte", g_strdup(name), g_free);
3290 g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
3291 TRUE);
3292
3293 g_signal_connect(action, "change-state",
3294 G_CALLBACK(virt_viewer_app_action_vte),
3295 self);
3296
3297 g_action_map_add_action(G_ACTION_MAP(self), G_ACTION(action));
3298
3299 g_free(actionname);
3300 g_free(name);
3301 }
3302
3303 g_list_free(keys);
3304 }
3305
3306 static void
virt_viewer_app_update_menu_displays(VirtViewerApp * self)3307 virt_viewer_app_update_menu_displays(VirtViewerApp *self)
3308 {
3309 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3310 if (!priv->windows)
3311 return;
3312 virt_viewer_app_clear_window_actions(self);
3313 virt_viewer_app_create_window_actions(self);
3314 g_list_foreach(priv->windows, window_update_menu_displays_cb, self);
3315 }
3316
3317 void
virt_viewer_app_set_connect_info(VirtViewerApp * self,const gchar * host,const gchar * ghost,const gchar * gport,const gchar * gtlsport,const gchar * transport,const gchar * unixsock,const gchar * user,gint port,const gchar * guri)3318 virt_viewer_app_set_connect_info(VirtViewerApp *self,
3319 const gchar *host,
3320 const gchar *ghost,
3321 const gchar *gport,
3322 const gchar *gtlsport,
3323 const gchar *transport,
3324 const gchar *unixsock,
3325 const gchar *user,
3326 gint port,
3327 const gchar *guri)
3328 {
3329 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3330 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3331
3332 g_debug("Set connect info: %s,%s,%s,%s,%s,%s,%s,%d",
3333 host, ghost, gport ? gport : "-1", gtlsport ? gtlsport : "-1", transport, unixsock, user, port);
3334
3335 g_free(priv->host);
3336 g_free(priv->ghost);
3337 g_free(priv->gport);
3338 g_free(priv->gtlsport);
3339 g_free(priv->transport);
3340 g_free(priv->unixsock);
3341 g_free(priv->user);
3342 g_free(priv->guri);
3343
3344 priv->host = g_strdup(host);
3345 priv->ghost = g_strdup(ghost);
3346 priv->gport = g_strdup(gport);
3347 priv->gtlsport = g_strdup(gtlsport);
3348 priv->transport = g_strdup(transport);
3349 priv->unixsock = g_strdup(unixsock);
3350 priv->user = g_strdup(user);
3351 priv->guri = g_strdup(guri);
3352 priv->port = port;
3353
3354 virt_viewer_app_update_pretty_address(self);
3355 }
3356
3357 void
virt_viewer_app_free_connect_info(VirtViewerApp * self)3358 virt_viewer_app_free_connect_info(VirtViewerApp *self)
3359 {
3360 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3361
3362 virt_viewer_app_set_connect_info(self, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL);
3363 }
3364
3365 VirtViewerWindow*
virt_viewer_app_get_main_window(VirtViewerApp * self)3366 virt_viewer_app_get_main_window(VirtViewerApp *self)
3367 {
3368 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
3369
3370 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3371 return priv->main_window;
3372 }
3373
3374 static void
show_status_cb(gpointer value,gpointer user_data)3375 show_status_cb(gpointer value,
3376 gpointer user_data)
3377 {
3378 VirtViewerNotebook *nb = virt_viewer_window_get_notebook(VIRT_VIEWER_WINDOW(value));
3379 gchar *text = (gchar*)user_data;
3380
3381 virt_viewer_notebook_show_status(nb, "%s", text);
3382 }
3383
3384 void
virt_viewer_app_show_status(VirtViewerApp * self,const gchar * fmt,...)3385 virt_viewer_app_show_status(VirtViewerApp *self, const gchar *fmt, ...)
3386 {
3387 va_list args;
3388 gchar *text;
3389
3390 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3391 g_return_if_fail(fmt != NULL);
3392
3393 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3394
3395 va_start(args, fmt);
3396 text = g_strdup_vprintf(fmt, args);
3397 va_end(args);
3398
3399 g_list_foreach(priv->windows, show_status_cb, text);
3400 g_free(text);
3401 }
3402
3403 static void
show_display_cb(gpointer value,gpointer user_data G_GNUC_UNUSED)3404 show_display_cb(gpointer value,
3405 gpointer user_data G_GNUC_UNUSED)
3406 {
3407 VirtViewerNotebook *nb = virt_viewer_window_get_notebook(VIRT_VIEWER_WINDOW(value));
3408
3409 virt_viewer_notebook_show_display(nb);
3410 }
3411
3412 void
virt_viewer_app_show_display(VirtViewerApp * self)3413 virt_viewer_app_show_display(VirtViewerApp *self)
3414 {
3415 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3416 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3417 g_list_foreach(priv->windows, show_display_cb, self);
3418 }
3419
3420 VirtViewerSession*
virt_viewer_app_get_session(VirtViewerApp * self)3421 virt_viewer_app_get_session(VirtViewerApp *self)
3422 {
3423 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3424
3425 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3426 return priv->session;
3427 }
3428
3429 GList*
virt_viewer_app_get_windows(VirtViewerApp * self)3430 virt_viewer_app_get_windows(VirtViewerApp *self)
3431 {
3432 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
3433 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3434 return priv->windows;
3435 }
3436
3437 static void
share_folder_changed(VirtViewerApp * self)3438 share_folder_changed(VirtViewerApp *self)
3439 {
3440 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3441 gchar *folder;
3442
3443 folder = gtk_file_chooser_get_filename(priv->preferences_shared_folder);
3444
3445 g_object_set(virt_viewer_app_get_session(self),
3446 "shared-folder", folder, NULL);
3447
3448 g_free(folder);
3449 }
3450
3451 static GtkWidget *
virt_viewer_app_get_preferences(VirtViewerApp * self)3452 virt_viewer_app_get_preferences(VirtViewerApp *self)
3453 {
3454 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3455 VirtViewerSession *session = virt_viewer_app_get_session(self);
3456 GtkBuilder *builder = virt_viewer_util_load_ui("virt-viewer-preferences.ui");
3457 gboolean can_share_folder = virt_viewer_session_can_share_folder(session);
3458 GtkWidget *preferences = priv->preferences;
3459 gchar *path;
3460
3461 if (preferences)
3462 goto end;
3463
3464 gtk_builder_connect_signals(builder, self);
3465
3466 preferences = GTK_WIDGET(gtk_builder_get_object(builder, "preferences"));
3467 priv->preferences = preferences;
3468
3469 g_object_bind_property(self,
3470 "config-share-clipboard",
3471 gtk_builder_get_object(builder, "cbshareclipboard"),
3472 "active",
3473 G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE);
3474
3475 g_object_set (gtk_builder_get_object(builder, "cbshareclipboard"),
3476 "sensitive", virt_viewer_app_get_supports_share_clipboard(self), NULL);
3477 g_object_set (gtk_builder_get_object(builder, "cbsharefolder"),
3478 "sensitive", can_share_folder, NULL);
3479 g_object_set (gtk_builder_get_object(builder, "cbsharefolderro"),
3480 "sensitive", can_share_folder, NULL);
3481 g_object_set (gtk_builder_get_object(builder, "fcsharefolder"),
3482 "sensitive", can_share_folder, NULL);
3483
3484 if (!can_share_folder)
3485 goto end;
3486
3487 g_object_bind_property(virt_viewer_app_get_session(self),
3488 "share-folder",
3489 gtk_builder_get_object(builder, "cbsharefolder"),
3490 "active",
3491 G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE);
3492
3493 g_object_bind_property(virt_viewer_app_get_session(self),
3494 "share-folder-ro",
3495 gtk_builder_get_object(builder, "cbsharefolderro"),
3496 "active",
3497 G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE);
3498
3499 priv->preferences_shared_folder =
3500 GTK_FILE_CHOOSER(gtk_builder_get_object(builder, "fcsharefolder"));
3501
3502 g_object_get(virt_viewer_app_get_session(self),
3503 "shared-folder", &path, NULL);
3504
3505 gtk_file_chooser_set_filename(priv->preferences_shared_folder, path);
3506 g_free(path);
3507
3508 virt_viewer_signal_connect_object(priv->preferences_shared_folder,
3509 "file-set",
3510 G_CALLBACK(share_folder_changed), self,
3511 G_CONNECT_SWAPPED);
3512
3513 end:
3514 g_object_unref(builder);
3515
3516 return preferences;
3517 }
3518
3519 void
virt_viewer_app_show_preferences(VirtViewerApp * self,GtkWidget * parent)3520 virt_viewer_app_show_preferences(VirtViewerApp *self, GtkWidget *parent)
3521 {
3522 GtkWidget *preferences = virt_viewer_app_get_preferences(self);
3523
3524 gtk_window_set_transient_for(GTK_WINDOW(preferences),
3525 GTK_WINDOW(parent));
3526
3527 gtk_window_present(GTK_WINDOW(preferences));
3528 }
3529
3530 static gboolean
option_kiosk_quit(G_GNUC_UNUSED const gchar * option_name,const gchar * value,G_GNUC_UNUSED gpointer data,GError ** error)3531 option_kiosk_quit(G_GNUC_UNUSED const gchar *option_name,
3532 const gchar *value,
3533 G_GNUC_UNUSED gpointer data, GError **error)
3534 {
3535 if (g_str_equal(value, "never")) {
3536 opt_kiosk_quit = FALSE;
3537 return TRUE;
3538 }
3539 if (g_str_equal(value, "on-disconnect")) {
3540 opt_kiosk_quit = TRUE;
3541 return TRUE;
3542 }
3543
3544 g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Invalid kiosk-quit argument: %s"), value);
3545 return FALSE;
3546 }
3547
3548 static void
virt_viewer_app_add_option_entries(G_GNUC_UNUSED VirtViewerApp * self,G_GNUC_UNUSED GOptionContext * context,GOptionGroup * group)3549 virt_viewer_app_add_option_entries(G_GNUC_UNUSED VirtViewerApp *self,
3550 G_GNUC_UNUSED GOptionContext *context,
3551 GOptionGroup *group)
3552 {
3553 static const GOptionEntry options [] = {
3554 { "version", 'V', 0, G_OPTION_ARG_NONE, &opt_version,
3555 N_("Display version information"), NULL },
3556 { "zoom", 'z', 0, G_OPTION_ARG_INT, &opt_zoom,
3557 N_("Zoom level of window, in percentage"), "ZOOM" },
3558 { "full-screen", 'f', 0, G_OPTION_ARG_NONE, &opt_fullscreen,
3559 N_("Open in full screen mode (adjusts guest resolution to fit the client)"), NULL },
3560 { "hotkeys", 'H', 0, G_OPTION_ARG_STRING, &opt_hotkeys,
3561 N_("Customise hotkeys"), NULL },
3562 { "auto-resize", '\0', 0, G_OPTION_ARG_STRING, &opt_resize,
3563 N_("Automatically resize remote framebuffer"), N_("<never|always>") },
3564 { "keymap", 'K', 0, G_OPTION_ARG_STRING, &opt_keymap,
3565 N_("Remap keys format key=keymod+key e.g. F1=SHIFT+CTRL+F1,1=SHIFT+F1,ALT_L=Void"), NULL },
3566 { "cursor", '\0', 0, G_OPTION_ARG_STRING, &opt_cursor,
3567 N_("Cursor display mode: 'local' or 'auto'"), "MODE" },
3568 { "kiosk", 'k', 0, G_OPTION_ARG_NONE, &opt_kiosk,
3569 N_("Enable kiosk mode"), NULL },
3570 { "kiosk-quit", '\0', 0, G_OPTION_ARG_CALLBACK, option_kiosk_quit,
3571 N_("Quit on given condition in kiosk mode"), N_("<never|on-disconnect>") },
3572 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose,
3573 N_("Display verbose information"), NULL },
3574 { "debug", '\0', 0, G_OPTION_ARG_NONE, &opt_debug,
3575 N_("Display debugging information"), NULL },
3576 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
3577 };
3578
3579 g_option_group_add_entries(group, options);
3580 }
3581
virt_viewer_app_get_session_cancelled(VirtViewerApp * self)3582 gboolean virt_viewer_app_get_session_cancelled(VirtViewerApp *self)
3583 {
3584 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3585 return priv->cancelled;
3586 }
3587
virt_viewer_app_get_config_share_clipboard(VirtViewerApp * self)3588 gboolean virt_viewer_app_get_config_share_clipboard(VirtViewerApp *self)
3589 {
3590 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3591
3592 GError *error = NULL;
3593 gboolean share_clipboard;
3594
3595 share_clipboard = g_key_file_get_boolean(priv->config,
3596 "virt-viewer", "share-clipboard", &error);
3597
3598 if (error) {
3599 share_clipboard = TRUE; /* backwards-compatible default value */
3600 g_clear_error(&error);
3601 }
3602
3603 return share_clipboard;
3604 }
3605
virt_viewer_app_set_config_share_clipboard(VirtViewerApp * self,gboolean enable)3606 void virt_viewer_app_set_config_share_clipboard(VirtViewerApp *self, gboolean enable)
3607 {
3608 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3609
3610 g_key_file_set_boolean(priv->config,
3611 "virt-viewer", "share-clipboard", enable);
3612 g_object_notify(G_OBJECT(self), "config-share-clipboard");
3613 }
3614
virt_viewer_app_get_supports_share_clipboard(VirtViewerApp * self)3615 gboolean virt_viewer_app_get_supports_share_clipboard(VirtViewerApp *self)
3616 {
3617 g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
3618
3619 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3620
3621 return priv->supports_share_clipboard;
3622 }
3623
virt_viewer_app_set_supports_share_clipboard(VirtViewerApp * self,gboolean enable)3624 void virt_viewer_app_set_supports_share_clipboard(VirtViewerApp *self, gboolean enable)
3625 {
3626 g_return_if_fail(VIRT_VIEWER_IS_APP(self));
3627
3628 VirtViewerAppPrivate *priv = virt_viewer_app_get_instance_private(self);
3629 if (priv->supports_share_clipboard == enable)
3630 return;
3631
3632 priv->supports_share_clipboard = enable;
3633 g_object_notify(G_OBJECT(self), "supports-share-clipboard");
3634 }
3635