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