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 <glib/gprintf.h>
36 #include <glib/gi18n.h>
37 #include <math.h>
38 
39 #include "virt-viewer-window.h"
40 #include "virt-viewer-display.h"
41 #include "virt-viewer-session.h"
42 #include "virt-viewer-app.h"
43 #include "virt-viewer-util.h"
44 #include "virt-viewer-timed-revealer.h"
45 #include "virt-viewer-display-vte.h"
46 
47 #include "remote-viewer-iso-list-dialog.h"
48 
49 #define ZOOM_STEP 10
50 
51 /* Signal handlers for main window (move in a VirtViewerMainWindow?) */
52 gboolean virt_viewer_window_delete(GtkWidget *src, void *dummy, VirtViewerWindow *self);
53 void virt_viewer_window_guest_details_response(GtkDialog *dialog, gint response_id, gpointer user_data);
54 
55 /* Internal methods */
56 static void virt_viewer_window_enable_modifiers(VirtViewerWindow *self);
57 static void virt_viewer_window_disable_modifiers(VirtViewerWindow *self);
58 static void virt_viewer_window_queue_resize(VirtViewerWindow *self);
59 static GMenu* virt_viewer_window_get_keycombo_menu(VirtViewerWindow *self);
60 static void virt_viewer_window_get_minimal_dimensions(VirtViewerWindow *self, guint *width, guint *height);
61 static gint virt_viewer_window_get_minimal_zoom_level(VirtViewerWindow *self);
62 static void virt_viewer_window_set_fullscreen(VirtViewerWindow *self,
63                                               gboolean fullscreen);
64 
65 enum {
66     PROP_0,
67     PROP_WINDOW,
68     PROP_DISPLAY,
69     PROP_SUBTITLE,
70     PROP_APP,
71     PROP_KEYMAP,
72 };
73 
74 struct _VirtViewerWindow {
75     GObject parent;
76     VirtViewerApp *app;
77 
78     GtkBuilder *builder;
79     GtkWidget *window;
80     GtkAccelGroup *accel_group;
81     VirtViewerNotebook *notebook;
82     VirtViewerDisplay *display;
83     VirtViewerTimedRevealer *revealer;
84 
85     gboolean accel_enabled;
86     GValue accel_setting;
87     GSList *accel_list;
88     gboolean enable_mnemonics_save;
89     gboolean grabbed;
90     gint fullscreen_monitor;
91     gboolean desktop_resize_pending;
92     gboolean kiosk;
93 
94     gint zoomlevel;
95     gboolean fullscreen;
96     gchar *subtitle;
97     gboolean initial_zoom_set;
98     VirtViewerKeyMapping *keyMappings;
99 };
100 
G_DEFINE_TYPE(VirtViewerWindow,virt_viewer_window,G_TYPE_OBJECT)101 G_DEFINE_TYPE(VirtViewerWindow, virt_viewer_window, G_TYPE_OBJECT)
102 
103 static void
104 virt_viewer_window_get_property (GObject *object, guint property_id,
105                                  GValue *value, GParamSpec *pspec)
106 {
107     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(object);
108 
109     switch (property_id) {
110     case PROP_SUBTITLE:
111         g_value_set_string(value, self->subtitle);
112         break;
113 
114     case PROP_WINDOW:
115         g_value_set_object(value, self->window);
116         break;
117 
118     case PROP_DISPLAY:
119         g_value_set_object(value, virt_viewer_window_get_display(self));
120         break;
121 
122     case PROP_APP:
123         g_value_set_object(value, self->app);
124         break;
125 
126     default:
127         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
128     }
129 }
130 
131 static void
virt_viewer_window_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)132 virt_viewer_window_set_property (GObject *object, guint property_id,
133                                  const GValue *value, GParamSpec *pspec)
134 {
135     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(object);
136 
137     switch (property_id) {
138     case PROP_SUBTITLE:
139         g_free(self->subtitle);
140         self->subtitle = g_value_dup_string(value);
141         virt_viewer_window_update_title(VIRT_VIEWER_WINDOW(object));
142         break;
143 
144     case PROP_APP:
145         g_return_if_fail(self->app == NULL);
146         self->app = g_value_get_object(value);
147         break;
148 
149     case PROP_KEYMAP:
150         g_free(self->keyMappings);
151         self->keyMappings = (VirtViewerKeyMapping *)g_value_get_pointer(value);
152         break;
153 
154     default:
155         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
156     }
157 }
158 
159 static void
virt_viewer_window_dispose(GObject * object)160 virt_viewer_window_dispose (GObject *object)
161 {
162     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(object);
163     GSList *it;
164 
165     if (self->display) {
166         g_object_unref(self->display);
167         self->display = NULL;
168     }
169 
170     g_debug("Disposing window %p\n", object);
171 
172     if (self->window) {
173         gtk_widget_destroy(self->window);
174         self->window = NULL;
175     }
176     if (self->builder) {
177         g_object_unref(self->builder);
178         self->builder = NULL;
179     }
180 
181     self->revealer = NULL;
182 
183     for (it = self->accel_list ; it != NULL ; it = it->next) {
184         g_object_unref(G_OBJECT(it->data));
185     }
186     g_slist_free(self->accel_list);
187     self->accel_list = NULL;
188 
189     g_free(self->subtitle);
190     self->subtitle = NULL;
191 
192     g_value_unset(&self->accel_setting);
193 
194     G_OBJECT_CLASS (virt_viewer_window_parent_class)->dispose (object);
195 }
196 
197 static void
rebuild_combo_menu(GObject * gobject G_GNUC_UNUSED,GParamSpec * pspec G_GNUC_UNUSED,gpointer user_data)198 rebuild_combo_menu(GObject    *gobject G_GNUC_UNUSED,
199                    GParamSpec *pspec G_GNUC_UNUSED,
200                    gpointer    user_data)
201 {
202     VirtViewerWindow *self = user_data;
203     GObject *button;
204     GMenu *menu;
205 
206     menu = virt_viewer_window_get_keycombo_menu(self);
207 
208     button = gtk_builder_get_object(self->builder, "header-send-key");
209     gtk_menu_button_set_menu_model(
210         GTK_MENU_BUTTON(button),
211         G_MENU_MODEL(menu));
212 
213     button = gtk_builder_get_object(self->builder, "toolbar-send-key");
214     gtk_menu_button_set_menu_model(
215         GTK_MENU_BUTTON(button),
216         G_MENU_MODEL(menu));
217 }
218 
219 static void
virt_viewer_window_constructed(GObject * object)220 virt_viewer_window_constructed(GObject *object)
221 {
222     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(object);
223 
224     if (G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed)
225         G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed(object);
226 
227     g_signal_connect(self->app, "notify::release-cursor-display-hotkey",
228                      G_CALLBACK(rebuild_combo_menu), object);
229     rebuild_combo_menu(NULL, NULL, object);
230 }
231 
232 static void
virt_viewer_window_class_init(VirtViewerWindowClass * klass)233 virt_viewer_window_class_init (VirtViewerWindowClass *klass)
234 {
235     GObjectClass *object_class = G_OBJECT_CLASS (klass);
236 
237     object_class->get_property = virt_viewer_window_get_property;
238     object_class->set_property = virt_viewer_window_set_property;
239     object_class->dispose = virt_viewer_window_dispose;
240     object_class->constructed = virt_viewer_window_constructed;
241 
242     g_object_class_install_property(object_class,
243                                     PROP_SUBTITLE,
244                                     g_param_spec_string("subtitle",
245                                                         "Subtitle",
246                                                         "Window subtitle",
247                                                         "",
248                                                         G_PARAM_READABLE |
249                                                         G_PARAM_WRITABLE |
250                                                         G_PARAM_STATIC_STRINGS));
251 
252     g_object_class_install_property(object_class,
253                                     PROP_WINDOW,
254                                     g_param_spec_object("window",
255                                                         "Window",
256                                                         "GtkWindow",
257                                                         GTK_TYPE_WIDGET,
258                                                         G_PARAM_READABLE |
259                                                         G_PARAM_STATIC_STRINGS));
260 
261     g_object_class_install_property(object_class,
262                                     PROP_DISPLAY,
263                                     g_param_spec_object("display",
264                                                         "Display",
265                                                         "VirtDisplay",
266                                                         VIRT_VIEWER_TYPE_DISPLAY,
267                                                         G_PARAM_READABLE |
268                                                         G_PARAM_STATIC_STRINGS));
269 
270     g_object_class_install_property(object_class,
271                                     PROP_APP,
272                                     g_param_spec_object("app",
273                                                         "App",
274                                                         "VirtViewerApp",
275                                                         VIRT_VIEWER_TYPE_APP,
276                                                         G_PARAM_READABLE |
277                                                         G_PARAM_WRITABLE |
278                                                         G_PARAM_CONSTRUCT_ONLY |
279                                                         G_PARAM_STATIC_STRINGS));
280     g_object_class_install_property(object_class,
281                                     PROP_KEYMAP,
282                                     g_param_spec_pointer("keymap",
283                                                         "keymap",
284                                                         "Remapped keys",
285                                                         G_PARAM_WRITABLE |
286                                                         G_PARAM_STATIC_STRINGS));
287 
288 }
289 
290 static void
virt_viewer_window_action_zoom_out(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)291 virt_viewer_window_action_zoom_out(GSimpleAction *act G_GNUC_UNUSED,
292                                    GVariant *param G_GNUC_UNUSED,
293                                    gpointer opaque)
294 {
295     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
296 
297     virt_viewer_window_zoom_out(VIRT_VIEWER_WINDOW(opaque));
298 }
299 
300 static void
virt_viewer_window_action_zoom_in(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)301 virt_viewer_window_action_zoom_in(GSimpleAction *act G_GNUC_UNUSED,
302                                   GVariant *param G_GNUC_UNUSED,
303                                   gpointer opaque)
304 {
305     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
306 
307     virt_viewer_window_zoom_in(VIRT_VIEWER_WINDOW(opaque));
308 }
309 
310 static void
virt_viewer_window_action_zoom_reset(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)311 virt_viewer_window_action_zoom_reset(GSimpleAction *act G_GNUC_UNUSED,
312                                      GVariant *param G_GNUC_UNUSED,
313                                      gpointer opaque)
314 {
315     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
316 
317     virt_viewer_window_zoom_reset(VIRT_VIEWER_WINDOW(opaque));
318 }
319 
320 static void
virt_viewer_window_action_quit(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)321 virt_viewer_window_action_quit(GSimpleAction *act G_GNUC_UNUSED,
322                                GVariant *param G_GNUC_UNUSED,
323                                gpointer opaque)
324 {
325     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
326 
327     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
328 
329     virt_viewer_app_maybe_quit(self->app, self);
330 }
331 
332 static void
virt_viewer_window_action_minimize(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)333 virt_viewer_window_action_minimize(GSimpleAction *act G_GNUC_UNUSED,
334                                    GVariant *param G_GNUC_UNUSED,
335                                    gpointer opaque)
336 {
337     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
338 
339     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
340 
341     gtk_window_iconify(GTK_WINDOW(self->window));
342 }
343 
344 static void
virt_viewer_window_action_about(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)345 virt_viewer_window_action_about(GSimpleAction *act G_GNUC_UNUSED,
346                                 GVariant *param G_GNUC_UNUSED,
347                                 gpointer opaque)
348 {
349     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
350 
351     virt_viewer_window_show_about(VIRT_VIEWER_WINDOW(opaque));
352 }
353 
354 static void
virt_viewer_window_action_guest_details(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)355 virt_viewer_window_action_guest_details(GSimpleAction *act G_GNUC_UNUSED,
356                                         GVariant *param G_GNUC_UNUSED,
357                                         gpointer opaque)
358 {
359     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
360 
361     virt_viewer_window_show_guest_details(VIRT_VIEWER_WINDOW(opaque));
362 }
363 
364 static void
virt_viewer_window_action_fullscreen(GSimpleAction * act,GVariant * state,gpointer opaque)365 virt_viewer_window_action_fullscreen(GSimpleAction *act,
366                                      GVariant *state,
367                                      gpointer opaque)
368 {
369     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
370 
371     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
372     gboolean fullscreen = g_variant_get_boolean(state);
373 
374     g_simple_action_set_state(act, g_variant_new_boolean(fullscreen));
375 
376     virt_viewer_window_set_fullscreen(self, fullscreen);
377 }
378 
379 static void
virt_viewer_window_action_send_key(GSimpleAction * act G_GNUC_UNUSED,GVariant * param,gpointer opaque)380 virt_viewer_window_action_send_key(GSimpleAction *act G_GNUC_UNUSED,
381                                    GVariant *param,
382                                    gpointer opaque)
383 {
384     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
385 
386     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
387 
388     g_return_if_fail(self->display != NULL);
389     gsize nkeys = 0;
390     const guint *keys = g_variant_get_fixed_array(param,
391                                                   &nkeys,
392                                                   sizeof(guint32));
393     g_return_if_fail(keys != NULL);
394 
395     virt_viewer_display_send_keys(VIRT_VIEWER_DISPLAY(self->display),
396                                   keys, nkeys);
397 }
398 
399 static void
virt_viewer_window_action_screenshot(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)400 virt_viewer_window_action_screenshot(GSimpleAction *act G_GNUC_UNUSED,
401                                      GVariant *param G_GNUC_UNUSED,
402                                      gpointer opaque)
403 {
404     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
405 
406     virt_viewer_window_screenshot(VIRT_VIEWER_WINDOW(opaque));
407 }
408 
409 static void
virt_viewer_window_action_usb_device_select(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)410 virt_viewer_window_action_usb_device_select(GSimpleAction *act G_GNUC_UNUSED,
411                                             GVariant *param G_GNUC_UNUSED,
412                                             gpointer opaque)
413 {
414     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
415 
416     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
417 
418     virt_viewer_session_usb_device_selection(virt_viewer_app_get_session(self->app),
419                                              GTK_WINDOW(self->window));
420 }
421 
422 static void
virt_viewer_window_action_usb_device_reset(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)423 virt_viewer_window_action_usb_device_reset(GSimpleAction *act G_GNUC_UNUSED,
424                                            GVariant *param G_GNUC_UNUSED,
425                                            gpointer opaque)
426 {
427     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
428 
429     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
430 
431     virt_viewer_session_usb_device_reset(virt_viewer_app_get_session(self->app));
432 }
433 
434 
435 static void
virt_viewer_window_action_release_cursor(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)436 virt_viewer_window_action_release_cursor(GSimpleAction *act G_GNUC_UNUSED,
437                                          GVariant *param G_GNUC_UNUSED,
438                                          gpointer opaque)
439 {
440     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
441 
442     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
443 
444     g_return_if_fail(self->display != NULL);
445     virt_viewer_display_release_cursor(VIRT_VIEWER_DISPLAY(self->display));
446 }
447 
448 static void
virt_viewer_window_action_preferences(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)449 virt_viewer_window_action_preferences(GSimpleAction *act G_GNUC_UNUSED,
450                                       GVariant *param G_GNUC_UNUSED,
451                                       gpointer opaque)
452 {
453     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
454 
455     VirtViewerWindow *self = VIRT_VIEWER_WINDOW(opaque);
456 
457     virt_viewer_app_show_preferences(self->app, self->window);
458 }
459 
460 static void
virt_viewer_window_action_change_cd(GSimpleAction * act G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)461 virt_viewer_window_action_change_cd(GSimpleAction *act G_GNUC_UNUSED,
462                                     GVariant *param G_GNUC_UNUSED,
463                                     gpointer opaque)
464 {
465     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
466 
467     virt_viewer_window_change_cd(VIRT_VIEWER_WINDOW(opaque));
468 }
469 
470 static void
virt_viewer_window_action_secure_attention(GSimpleAction * action G_GNUC_UNUSED,GVariant * param G_GNUC_UNUSED,gpointer opaque)471 virt_viewer_window_action_secure_attention(GSimpleAction *action G_GNUC_UNUSED,
472                                            GVariant *param G_GNUC_UNUSED,
473                                            gpointer opaque)
474 {
475     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(opaque));
476 
477     VirtViewerWindow *self =  VIRT_VIEWER_WINDOW(opaque);
478     guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
479 
480     virt_viewer_display_send_keys(VIRT_VIEWER_DISPLAY(self->display),
481                                   keys, G_N_ELEMENTS(keys));
482 }
483 
484 static GActionEntry actions[] = {
485     { .name = "zoom-out",
486       .activate = virt_viewer_window_action_zoom_out },
487     { .name = "zoom-in",
488       .activate = virt_viewer_window_action_zoom_in },
489     { .name = "zoom-reset",
490       .activate = virt_viewer_window_action_zoom_reset },
491     { .name = "quit",
492       .activate = virt_viewer_window_action_quit },
493     { .name = "minimize",
494       .activate = virt_viewer_window_action_minimize },
495     { .name = "about",
496       .activate = virt_viewer_window_action_about },
497     { .name = "guest-details",
498       .activate = virt_viewer_window_action_guest_details },
499     { .name = "fullscreen",
500       .state = "false",
501       .change_state = virt_viewer_window_action_fullscreen },
502     { .name = "send-key",
503       .parameter_type = "au",
504       .activate = virt_viewer_window_action_send_key },
505     { .name = "screenshot",
506       .activate = virt_viewer_window_action_screenshot },
507     { .name = "usb-device-select",
508       .activate = virt_viewer_window_action_usb_device_select },
509     { .name = "usb-device-reset",
510       .activate = virt_viewer_window_action_usb_device_reset },
511     { .name = "release-cursor",
512       .activate = virt_viewer_window_action_release_cursor },
513     { .name = "preferences",
514       .activate = virt_viewer_window_action_preferences },
515     { .name = "change-cd",
516       .activate = virt_viewer_window_action_change_cd },
517     { .name = "secure-attention",
518       .activate = virt_viewer_window_action_secure_attention },
519 };
520 
521 static void
virt_viewer_window_init(VirtViewerWindow * self)522 virt_viewer_window_init (VirtViewerWindow *self)
523 {
524     GtkWidget *overlay;
525     GtkWidget *toolbar;
526     GSList *accels;
527     GObject *menu;
528     GtkBuilder *menuBuilder;
529 
530     self->fullscreen_monitor = -1;
531     g_value_init(&self->accel_setting, G_TYPE_STRING);
532 
533     self->notebook = virt_viewer_notebook_new();
534     gtk_widget_show(GTK_WIDGET(self->notebook));
535 
536     self->builder = virt_viewer_util_load_ui("virt-viewer.ui");
537 
538     gtk_builder_connect_signals(self->builder, self);
539 
540     self->accel_group = GTK_ACCEL_GROUP(gtk_builder_get_object(self->builder, "accelgroup"));
541 
542     overlay = GTK_WIDGET(gtk_builder_get_object(self->builder, "viewer-overlay"));
543     toolbar = GTK_WIDGET(gtk_builder_get_object(self->builder, "toolbar"));
544 
545     gtk_container_remove(GTK_CONTAINER(overlay), toolbar);
546     gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(self->notebook));
547     self->revealer = virt_viewer_timed_revealer_new(toolbar);
548     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(self->revealer));
549 
550     self->window = GTK_WIDGET(gtk_builder_get_object(self->builder, "viewer"));
551 
552     g_action_map_add_action_entries(G_ACTION_MAP(self->window), actions,
553                                     G_N_ELEMENTS(actions), self);
554 
555     gtk_window_add_accel_group(GTK_WINDOW(self->window), self->accel_group);
556 
557     menuBuilder =
558         gtk_builder_new_from_resource(VIRT_VIEWER_RESOURCE_PREFIX "/ui/virt-viewer-menus.ui");
559 
560     menu = gtk_builder_get_object(self->builder, "header-action");
561     gtk_menu_button_set_menu_model(
562         GTK_MENU_BUTTON(menu),
563         G_MENU_MODEL(gtk_builder_get_object(menuBuilder, "action-menu")));
564 
565     menu = gtk_builder_get_object(self->builder, "header-machine");
566     gtk_menu_button_set_menu_model(
567         GTK_MENU_BUTTON(menu),
568         G_MENU_MODEL(gtk_builder_get_object(menuBuilder, "machine-menu")));
569 
570     menu = gtk_builder_get_object(self->builder, "toolbar-action");
571     gtk_menu_button_set_menu_model(
572         GTK_MENU_BUTTON(menu),
573         G_MENU_MODEL(gtk_builder_get_object(menuBuilder, "action-menu")));
574 
575     menu = gtk_builder_get_object(self->builder, "toolbar-machine");
576     gtk_menu_button_set_menu_model(
577         GTK_MENU_BUTTON(menu),
578         G_MENU_MODEL(gtk_builder_get_object(menuBuilder, "machine-menu")));
579 
580     virt_viewer_window_update_title(self);
581     gtk_window_set_resizable(GTK_WINDOW(self->window), TRUE);
582     self->accel_enabled = TRUE;
583 
584     accels = gtk_accel_groups_from_object(G_OBJECT(self->window));
585     for ( ; accels ; accels = accels->next) {
586         self->accel_list = g_slist_append(self->accel_list, accels->data);
587         g_object_ref(G_OBJECT(accels->data));
588     }
589 
590     self->zoomlevel = NORMAL_ZOOM_LEVEL;
591 }
592 
593 static void
virt_viewer_window_desktop_resize(VirtViewerDisplay * display G_GNUC_UNUSED,VirtViewerWindow * self)594 virt_viewer_window_desktop_resize(VirtViewerDisplay *display G_GNUC_UNUSED,
595                                   VirtViewerWindow *self)
596 {
597     if (!gtk_widget_get_visible(self->window)) {
598         self->desktop_resize_pending = TRUE;
599         return;
600     }
601     virt_viewer_window_queue_resize(self);
602 }
603 
604 static gint
virt_viewer_window_get_real_zoom_level(VirtViewerWindow * self)605 virt_viewer_window_get_real_zoom_level(VirtViewerWindow *self)
606 {
607     GtkAllocation allocation;
608     guint width, height;
609 
610     g_return_val_if_fail(self->display != NULL, NORMAL_ZOOM_LEVEL);
611 
612     gtk_widget_get_allocation(GTK_WIDGET(self->display), &allocation);
613     virt_viewer_display_get_desktop_size(self->display, &width, &height);
614 
615     return round((double) NORMAL_ZOOM_LEVEL * allocation.width / width);
616 }
617 
618 void
virt_viewer_window_zoom_out(VirtViewerWindow * self)619 virt_viewer_window_zoom_out(VirtViewerWindow *self)
620 {
621     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
622 
623     if (VIRT_VIEWER_IS_DISPLAY_VTE(self->display)) {
624         virt_viewer_display_vte_zoom_out(VIRT_VIEWER_DISPLAY_VTE(self->display));
625     } else {
626         virt_viewer_window_set_zoom_level(self,
627                                           virt_viewer_window_get_real_zoom_level(self) - ZOOM_STEP);
628     }
629 }
630 
631 void
virt_viewer_window_zoom_in(VirtViewerWindow * self)632 virt_viewer_window_zoom_in(VirtViewerWindow *self)
633 {
634     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
635 
636     if (VIRT_VIEWER_IS_DISPLAY_VTE(self->display)) {
637         virt_viewer_display_vte_zoom_in(VIRT_VIEWER_DISPLAY_VTE(self->display));
638     } else {
639         virt_viewer_window_set_zoom_level(self,
640                                           virt_viewer_window_get_real_zoom_level(self) + ZOOM_STEP);
641     }
642 }
643 
644 void
virt_viewer_window_zoom_reset(VirtViewerWindow * self)645 virt_viewer_window_zoom_reset(VirtViewerWindow *self)
646 {
647     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
648 
649     if (VIRT_VIEWER_IS_DISPLAY_VTE(self->display)) {
650         virt_viewer_display_vte_zoom_reset(VIRT_VIEWER_DISPLAY_VTE(self->display));
651     } else {
652         virt_viewer_window_set_zoom_level(self, NORMAL_ZOOM_LEVEL);
653     }
654 }
655 
656 /* Kick GtkWindow to tell it to adjust to our new widget sizes */
657 static void
virt_viewer_window_queue_resize(VirtViewerWindow * self)658 virt_viewer_window_queue_resize(VirtViewerWindow *self)
659 {
660     GtkRequisition nat;
661     GtkWidget *child;
662     guint border;
663 
664     border = gtk_container_get_border_width(GTK_CONTAINER(self->window));
665     child = gtk_bin_get_child(GTK_BIN(self->window));
666     gtk_window_set_default_size(GTK_WINDOW(self->window), -1, -1);
667     gtk_widget_get_preferred_size(child, NULL, &nat);
668     gtk_window_resize(GTK_WINDOW(self->window), nat.width + border, nat.height + border);
669 }
670 
671 static void
virt_viewer_window_move_to_monitor(VirtViewerWindow * self)672 virt_viewer_window_move_to_monitor(VirtViewerWindow *self)
673 {
674     GdkRectangle mon;
675     gint n = self->fullscreen_monitor;
676 
677     if (n == -1)
678         return;
679 
680     gdk_screen_get_monitor_geometry(gdk_screen_get_default(), n, &mon);
681     gtk_window_move(GTK_WINDOW(self->window), mon.x, mon.y);
682 
683     gtk_widget_set_size_request(self->window,
684                                 mon.width,
685                                 mon.height);
686 }
687 
688 static gboolean
mapped(GtkWidget * widget,GdkEvent * event G_GNUC_UNUSED,VirtViewerWindow * self)689 mapped(GtkWidget *widget, GdkEvent *event G_GNUC_UNUSED,
690        VirtViewerWindow *self)
691 {
692     g_signal_handlers_disconnect_by_func(widget, mapped, self);
693     self->fullscreen = FALSE;
694     virt_viewer_window_enter_fullscreen(self, self->fullscreen_monitor);
695     return FALSE;
696 }
697 
698 void
virt_viewer_window_leave_fullscreen(VirtViewerWindow * self)699 virt_viewer_window_leave_fullscreen(VirtViewerWindow *self)
700 {
701     /* if we enter and leave fullscreen mode before being shown, make sure to
702      * disconnect the mapped signal handler */
703     g_signal_handlers_disconnect_by_func(self->window, mapped, self);
704 
705     if (!self->fullscreen)
706         return;
707 
708     self->fullscreen = FALSE;
709     self->fullscreen_monitor = -1;
710     if (self->display) {
711         virt_viewer_display_set_monitor(self->display, -1);
712         virt_viewer_display_set_fullscreen(self->display, FALSE);
713     }
714     virt_viewer_timed_revealer_force_reveal(self->revealer, FALSE);
715     gtk_widget_set_size_request(self->window, -1, -1);
716     gtk_window_unfullscreen(GTK_WINDOW(self->window));
717 
718 }
719 
720 void
virt_viewer_window_enter_fullscreen(VirtViewerWindow * self,gint monitor)721 virt_viewer_window_enter_fullscreen(VirtViewerWindow *self, gint monitor)
722 {
723     if (self->fullscreen && self->fullscreen_monitor != monitor)
724         virt_viewer_window_leave_fullscreen(self);
725 
726     if (self->fullscreen)
727         return;
728 
729     self->fullscreen_monitor = monitor;
730     self->fullscreen = TRUE;
731 
732     if (!gtk_widget_get_mapped(self->window)) {
733         /*
734          * To avoid some races with metacity, the window should be placed
735          * as early as possible, before it is (re)allocated & mapped
736          * Position & size should not be queried yet. (rhbz#809546).
737          */
738         virt_viewer_window_move_to_monitor(self);
739         g_signal_connect(self->window, "map-event", G_CALLBACK(mapped), self);
740         return;
741     }
742 
743     if (!self->kiosk) {
744         virt_viewer_timed_revealer_force_reveal(self->revealer, TRUE);
745     }
746 
747     if (self->display) {
748         virt_viewer_display_set_monitor(self->display, monitor);
749         virt_viewer_display_set_fullscreen(self->display, TRUE);
750     }
751     virt_viewer_window_move_to_monitor(self);
752 
753     if (monitor == -1) {
754         // just go fullscreen on the current monitor
755         gtk_window_fullscreen(GTK_WINDOW(self->window));
756     } else {
757         gtk_window_fullscreen_on_monitor(GTK_WINDOW(self->window),
758                                          gdk_screen_get_default(), monitor);
759     }
760 }
761 
762 #define MAX_KEY_COMBO 4
763 struct keyComboDef {
764     guint32 keys[MAX_KEY_COMBO];
765     const char *accel_label;
766 };
767 
768 static const struct keyComboDef keyCombos[] = {
769     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete, GDK_KEY_VoidSymbol }, "<Control><Alt>Delete" },
770     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_BackSpace, GDK_KEY_VoidSymbol }, "<Control><Alt>BackSpace" },
771     { { GDK_KEY_VoidSymbol }, "" },
772     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F1, GDK_KEY_VoidSymbol }, "<Control><Alt>F1" },
773     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F2, GDK_KEY_VoidSymbol }, "<Control><Alt>F2" },
774     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F3, GDK_KEY_VoidSymbol }, "<Control><Alt>F3" },
775     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F4, GDK_KEY_VoidSymbol }, "<Control><Alt>F4" },
776     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F5, GDK_KEY_VoidSymbol }, "<Control><Alt>F5" },
777     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F6, GDK_KEY_VoidSymbol }, "<Control><Alt>F6" },
778     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F7, GDK_KEY_VoidSymbol }, "<Control><Alt>F7" },
779     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F8, GDK_KEY_VoidSymbol }, "<Control><Alt>F8" },
780     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F9, GDK_KEY_VoidSymbol }, "<Control><Alt>F9" },
781     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F10, GDK_KEY_VoidSymbol }, "<Control><Alt>F10" },
782     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F11, GDK_KEY_VoidSymbol }, "<Control><Alt>F11" },
783     { { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_F12, GDK_KEY_VoidSymbol }, "<Control><Alt>F12" },
784     { { GDK_KEY_VoidSymbol }, "" },
785     { { GDK_KEY_Print, GDK_KEY_VoidSymbol }, "Print" },
786 };
787 
788 static guint
get_nkeys(const guint32 * keys)789 get_nkeys(const guint32 *keys)
790 {
791     guint i;
792 
793     for (i = 0; keys[i] != GDK_KEY_VoidSymbol; )
794         i++;
795 
796     return i;
797 }
798 
799 static void
virt_viewer_menu_add_combo(VirtViewerWindow * self G_GNUC_UNUSED,GMenu * menu,const guint * keys,const gchar * label)800 virt_viewer_menu_add_combo(VirtViewerWindow *self G_GNUC_UNUSED, GMenu *menu,
801                            const guint *keys, const gchar *label)
802 {
803     GMenuItem *item = g_menu_item_new(label, NULL);
804 
805     g_menu_item_set_action_and_target_value(item,
806                                             "win.send-key",
807                                             g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
808                                                                       keys,
809                                                                       get_nkeys(keys),
810                                                                       sizeof(guint32)));
811 
812     g_menu_append_item(menu, item);
813 }
814 
815 static guint*
accel_key_to_keys(guint accel_key,GdkModifierType accel_mods)816 accel_key_to_keys(guint accel_key,
817                   GdkModifierType accel_mods)
818 {
819     guint i;
820     guint *val, *keys;
821     static const struct {
822         const guint mask;
823         const guint key;
824     } modifiers[] = {
825         {GDK_SHIFT_MASK, GDK_KEY_Shift_L},
826         {GDK_CONTROL_MASK, GDK_KEY_Control_L},
827         {GDK_MOD1_MASK, GDK_KEY_Alt_L},
828     };
829 
830     g_warn_if_fail((accel_mods &
831                     ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0);
832 
833     keys = val = g_new(guint, G_N_ELEMENTS(modifiers) + 2); /* up to 3 modifiers, key and the stop symbol */
834     /* first, send the modifiers */
835     for (i = 0; i < G_N_ELEMENTS(modifiers); i++) {
836         if (accel_mods & modifiers[i].mask)
837             *val++ = modifiers[i].key;
838     }
839 
840     /* only after, the non-modifier key (ctrl-t, not t-ctrl) */
841     *val++ = accel_key;
842     /* stop symbol */
843     *val = GDK_KEY_VoidSymbol;
844 
845     return keys;
846 }
847 
848 static GMenu *
virt_viewer_window_get_keycombo_menu(VirtViewerWindow * self)849 virt_viewer_window_get_keycombo_menu(VirtViewerWindow *self)
850 {
851     gint i, j;
852     GMenu *menu = g_menu_new();
853     GMenu *sectionitems = g_menu_new();
854     GMenuItem *section = g_menu_item_new_section(NULL, G_MENU_MODEL(sectionitems));
855 
856     g_menu_append_item(menu, section);
857 
858     for (i = 0 ; i < G_N_ELEMENTS(keyCombos); i++) {
859         if (keyCombos[i].keys[0] == GDK_KEY_VoidSymbol) {
860             sectionitems = g_menu_new();
861             section = g_menu_item_new_section(NULL, G_MENU_MODEL(sectionitems));
862             g_menu_append_item(menu, section);
863         } else {
864             gchar *label = NULL;
865             guint key;
866             GdkModifierType mods;
867             gtk_accelerator_parse(keyCombos[i].accel_label, &key, &mods);
868             label = gtk_accelerator_get_label(key, mods);
869 
870             virt_viewer_menu_add_combo(self, sectionitems, keyCombos[i].keys, label);
871             g_free(label);
872         }
873     }
874 
875     gchar **accelactions = gtk_application_list_action_descriptions(GTK_APPLICATION(self->app));
876 
877     sectionitems = g_menu_new();
878     section = g_menu_item_new_section(NULL, G_MENU_MODEL(sectionitems));
879     g_menu_append_item(menu, section);
880 
881     for (i = 0; accelactions[i] != NULL; i++) {
882         if (g_str_equal(accelactions[i], "win.release-cursor")) {
883             gchar *display_hotkey = virt_viewer_app_get_release_cursor_display_hotkey(self->app);
884             if (display_hotkey) {
885                 gchar *accel = spice_hotkey_to_gtk_accelerator(display_hotkey);
886                 guint accel_key;
887                 GdkModifierType accel_mods;
888                 gtk_accelerator_parse(accel, &accel_key, &accel_mods);
889 
890                 guint *keys = accel_key_to_keys(accel_key, accel_mods);
891                 gchar *label = spice_hotkey_to_display_hotkey(display_hotkey);
892                 virt_viewer_menu_add_combo(self, sectionitems, keys, label);
893                 g_free(label);
894                 g_free(keys);
895             }
896         }
897 
898         gchar **accels = gtk_application_get_accels_for_action(GTK_APPLICATION(self->app),
899                                                                accelactions[i]);
900 
901         for (j = 0; accels[j] != NULL; j++) {
902             guint accel_key;
903             GdkModifierType accel_mods;
904             gtk_accelerator_parse(accels[j], &accel_key, &accel_mods);
905 
906             guint *keys = accel_key_to_keys(accel_key, accel_mods);
907             gchar *label = gtk_accelerator_get_label(accel_key, accel_mods);
908             virt_viewer_menu_add_combo(self, sectionitems, keys, label);
909             g_free(label);
910             g_free(keys);
911         }
912         g_strfreev(accels);
913     }
914 
915     g_strfreev(accelactions);
916 
917     return menu;
918 }
919 
920 void
virt_viewer_window_disable_modifiers(VirtViewerWindow * self)921 virt_viewer_window_disable_modifiers(VirtViewerWindow *self)
922 {
923     GtkSettings *settings = gtk_settings_get_default();
924     GValue empty;
925     GSList *accels;
926 
927     if (!self->accel_enabled)
928         return;
929 
930     /* This stops F10 activating menu bar */
931     memset(&empty, 0, sizeof empty);
932     g_value_init(&empty, G_TYPE_STRING);
933     g_object_get_property(G_OBJECT(settings), "gtk-menu-bar-accel", &self->accel_setting);
934     g_object_set_property(G_OBJECT(settings), "gtk-menu-bar-accel", &empty);
935 
936     /* This stops global accelerators like Ctrl+Q == Quit */
937     for (accels = self->accel_list ; accels ; accels = accels->next) {
938         if (self->accel_group == accels->data && !self->kiosk)
939             continue;
940         gtk_window_remove_accel_group(GTK_WINDOW(self->window), accels->data);
941     }
942 
943     /* This stops menu bar shortcuts like Alt+F == File */
944     g_object_get(settings,
945                  "gtk-enable-mnemonics", &self->enable_mnemonics_save,
946                  NULL);
947     g_object_set(settings,
948                  "gtk-enable-mnemonics", FALSE,
949                  NULL);
950 
951     self->accel_enabled = FALSE;
952 }
953 
954 void
virt_viewer_window_enable_modifiers(VirtViewerWindow * self)955 virt_viewer_window_enable_modifiers(VirtViewerWindow *self)
956 {
957     GtkSettings *settings = gtk_settings_get_default();
958     GSList *accels;
959     GSList *attached_accels;
960 
961     if (self->accel_enabled)
962         return;
963 
964     /* This allows F10 activating menu bar */
965     g_object_set_property(G_OBJECT(settings), "gtk-menu-bar-accel", &self->accel_setting);
966     attached_accels = gtk_accel_groups_from_object(G_OBJECT(self->window));
967 
968     /* This allows global accelerators like Ctrl+Q == Quit */
969     for (accels = self->accel_list ; accels ; accels = accels->next) {
970         /* Do not attach accels that are already attached. */
971         if (attached_accels && g_slist_find(attached_accels, accels->data))
972             continue;
973 
974         gtk_window_add_accel_group(GTK_WINDOW(self->window), accels->data);
975     }
976 
977     /* This allows menu bar shortcuts like Alt+F == File */
978     g_object_set(settings,
979                  "gtk-enable-mnemonics", self->enable_mnemonics_save,
980                  NULL);
981 
982     self->accel_enabled = TRUE;
983 }
984 
985 
986 G_MODULE_EXPORT gboolean
virt_viewer_window_delete(GtkWidget * src G_GNUC_UNUSED,void * dummy G_GNUC_UNUSED,VirtViewerWindow * self)987 virt_viewer_window_delete(GtkWidget *src G_GNUC_UNUSED,
988                           void *dummy G_GNUC_UNUSED,
989                           VirtViewerWindow *self)
990 {
991     g_debug("Window closed");
992     virt_viewer_app_maybe_quit(self->app, self);
993     return TRUE;
994 }
995 
996 
997 static void
virt_viewer_window_set_fullscreen(VirtViewerWindow * self,gboolean fullscreen)998 virt_viewer_window_set_fullscreen(VirtViewerWindow *self,
999                                   gboolean fullscreen)
1000 {
1001     if (fullscreen) {
1002         virt_viewer_window_enter_fullscreen(self, -1);
1003     } else {
1004         /* leave all windows fullscreen state */
1005         if (virt_viewer_app_get_fullscreen(self->app))
1006             g_object_set(self->app, "fullscreen", FALSE, NULL);
1007         /* or just this window */
1008         else
1009             virt_viewer_window_leave_fullscreen(self);
1010     }
1011 }
1012 
1013 
add_if_writable(GdkPixbufFormat * data,GHashTable * formats)1014 static void add_if_writable (GdkPixbufFormat *data, GHashTable *formats)
1015 {
1016     if (gdk_pixbuf_format_is_writable(data)) {
1017         gchar **extensions;
1018         gchar **it;
1019         extensions = gdk_pixbuf_format_get_extensions(data);
1020         for (it = extensions; *it != NULL; it++) {
1021             g_hash_table_insert(formats, g_strdup(*it), data);
1022         }
1023         g_strfreev(extensions);
1024     }
1025 }
1026 
init_image_formats(G_GNUC_UNUSED gpointer user_data)1027 static GHashTable *init_image_formats(G_GNUC_UNUSED gpointer user_data)
1028 {
1029     GHashTable *format_map;
1030     GSList *formats = gdk_pixbuf_get_formats();
1031 
1032     format_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1033     g_slist_foreach(formats, (GFunc)add_if_writable, format_map);
1034     g_slist_free (formats);
1035 
1036     return format_map;
1037 }
1038 
get_image_format(const char * filename)1039 static GdkPixbufFormat *get_image_format(const char *filename)
1040 {
1041     static GOnce image_formats_once = G_ONCE_INIT;
1042     const char *ext;
1043 
1044     g_once(&image_formats_once, (GThreadFunc)init_image_formats, NULL);
1045 
1046     ext = strrchr(filename, '.');
1047     if (ext == NULL)
1048         return NULL;
1049 
1050     ext++; /* skip '.' */
1051 
1052     return g_hash_table_lookup(image_formats_once.retval, ext);
1053 }
1054 
1055 static gboolean
virt_viewer_window_save_screenshot(VirtViewerWindow * self,const char * file,GError ** error)1056 virt_viewer_window_save_screenshot(VirtViewerWindow *self,
1057                                    const char *file,
1058                                    GError **error)
1059 {
1060     GdkPixbuf *pix = virt_viewer_display_get_pixbuf(VIRT_VIEWER_DISPLAY(self->display));
1061     GdkPixbufFormat *format = get_image_format(file);
1062     gboolean result;
1063 
1064     if (format == NULL) {
1065         g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
1066                     _("Unable to determine image format for file '%s'"), file);
1067         result = FALSE;
1068     } else {
1069         char *type = gdk_pixbuf_format_get_name(format);
1070         g_debug("saving to %s", type);
1071         result = gdk_pixbuf_save(pix, file, type, error, NULL);
1072         g_free(type);
1073     }
1074 
1075     g_object_unref(pix);
1076     return result;
1077 }
1078 
1079 void
virt_viewer_window_screenshot(VirtViewerWindow * self)1080 virt_viewer_window_screenshot(VirtViewerWindow *self)
1081 {
1082     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1083 
1084     GtkWidget *dialog;
1085     const char *image_dir;
1086 
1087     g_return_if_fail(self->display != NULL);
1088 
1089     dialog = gtk_file_chooser_dialog_new(_("Save screenshot"),
1090                                          NULL,
1091                                          GTK_FILE_CHOOSER_ACTION_SAVE,
1092                                          _("_Cancel"), GTK_RESPONSE_CANCEL,
1093                                          _("_Save"), GTK_RESPONSE_ACCEPT,
1094                                          NULL);
1095     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER (dialog), TRUE);
1096     gtk_window_set_transient_for(GTK_WINDOW(dialog),
1097                                  GTK_WINDOW(self->window));
1098     image_dir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
1099     if (image_dir != NULL)
1100         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (dialog), image_dir);
1101     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER (dialog), _("Screenshot.png"));
1102 
1103 retry_dialog:
1104     if (gtk_dialog_run(GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1105         char *filename;
1106         GError *error = NULL;
1107 
1108         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog));
1109         if (g_strrstr(filename, ".") == NULL) {
1110             // no extension provided
1111             GtkWidget *msg_dialog ;
1112             g_free(filename);
1113             msg_dialog = gtk_message_dialog_new (GTK_WINDOW(dialog), GTK_DIALOG_MODAL,
1114                                                  GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
1115                                                  _("Please add an extension to the file name"));
1116             gtk_dialog_run(GTK_DIALOG(msg_dialog));
1117             gtk_widget_destroy(msg_dialog);
1118             goto retry_dialog;
1119         }
1120 
1121         if (!virt_viewer_window_save_screenshot(self, filename, &error)) {
1122             virt_viewer_app_simple_message_dialog(self->app,
1123                                                   "%s", error->message);
1124             g_error_free(error);
1125         }
1126         g_free(filename);
1127     }
1128 
1129     gtk_widget_destroy(dialog);
1130 }
1131 
1132 
1133 void
virt_viewer_window_show_guest_details(VirtViewerWindow * self)1134 virt_viewer_window_show_guest_details(VirtViewerWindow *self)
1135 {
1136     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1137 
1138     GtkBuilder *ui = virt_viewer_util_load_ui("virt-viewer-guest-details.ui");
1139     char *name = NULL;
1140     char *uuid = NULL;
1141 
1142     g_return_if_fail(ui != NULL);
1143 
1144     GtkWidget *dialog = GTK_WIDGET(gtk_builder_get_object(ui, "guestdetailsdialog"));
1145     GtkWidget *namelabel = GTK_WIDGET(gtk_builder_get_object(ui, "namevaluelabel"));
1146     GtkWidget *guidlabel = GTK_WIDGET(gtk_builder_get_object(ui, "guidvaluelabel"));
1147 
1148     g_return_if_fail(dialog && namelabel && guidlabel);
1149 
1150     g_object_get(self->app, "guest-name", &name, "uuid", &uuid, NULL);
1151 
1152     if (!name || *name == '\0')
1153         name = g_strdup(C_("Unknown name", "Unknown"));
1154     if (!uuid || *uuid == '\0')
1155         uuid = g_strdup(C_("Unknown UUID", "Unknown"));
1156     gtk_label_set_text(GTK_LABEL(namelabel), name);
1157     gtk_label_set_text(GTK_LABEL(guidlabel), uuid);
1158     g_free(name);
1159     g_free(uuid);
1160 
1161     gtk_window_set_transient_for(GTK_WINDOW(dialog),
1162                                  GTK_WINDOW(self->window));
1163 
1164     gtk_builder_connect_signals(ui, self);
1165 
1166     gtk_widget_show_all(dialog);
1167 
1168     g_object_unref(G_OBJECT(ui));
1169 }
1170 
1171 G_MODULE_EXPORT void
virt_viewer_window_guest_details_response(GtkDialog * dialog,gint response_id,gpointer user_data G_GNUC_UNUSED)1172 virt_viewer_window_guest_details_response(GtkDialog *dialog,
1173                                           gint response_id,
1174                                           gpointer user_data G_GNUC_UNUSED)
1175 {
1176     if (response_id == GTK_RESPONSE_CLOSE)
1177         gtk_widget_hide(GTK_WIDGET(dialog));
1178 }
1179 
1180 void
virt_viewer_window_show_about(VirtViewerWindow * self)1181 virt_viewer_window_show_about(VirtViewerWindow *self)
1182 {
1183     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1184 
1185     GtkBuilder *about;
1186     GtkWidget *dialog;
1187     GdkPixbuf *icon;
1188 
1189     about = virt_viewer_util_load_ui("virt-viewer-about.ui");
1190 
1191     dialog = GTK_WIDGET(gtk_builder_get_object(about, "about"));
1192 
1193     gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), VERSION BUILDID);
1194 
1195     icon = gdk_pixbuf_new_from_resource(VIRT_VIEWER_RESOURCE_PREFIX"/icons/48x48/virt-viewer.png", NULL);
1196     if (icon != NULL) {
1197         gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), icon);
1198         g_object_unref(icon);
1199     } else {
1200         gtk_about_dialog_set_logo_icon_name(GTK_ABOUT_DIALOG(dialog), "virt-viewer");
1201     }
1202 
1203     gtk_window_set_transient_for(GTK_WINDOW(dialog),
1204                                  GTK_WINDOW(self->window));
1205 
1206     gtk_builder_connect_signals(about, self);
1207 
1208     gtk_widget_show_all(dialog);
1209 
1210     g_object_unref(G_OBJECT(about));
1211 }
1212 
1213 
1214 #if HAVE_OVIRT
1215 static void
iso_dialog_response(GtkDialog * dialog,gint response_id,gpointer user_data G_GNUC_UNUSED)1216 iso_dialog_response(GtkDialog *dialog,
1217                     gint response_id,
1218                     gpointer user_data G_GNUC_UNUSED)
1219 {
1220     if (response_id == GTK_RESPONSE_NONE)
1221         return;
1222 
1223     gtk_widget_destroy(GTK_WIDGET(dialog));
1224 }
1225 #endif
1226 
1227 void
virt_viewer_window_change_cd(VirtViewerWindow * self G_GNUC_UNUSED)1228 virt_viewer_window_change_cd(VirtViewerWindow *self G_GNUC_UNUSED)
1229 {
1230 #if HAVE_OVIRT
1231     GtkWidget *dialog;
1232     GObject *foreign_menu;
1233 
1234     g_object_get(G_OBJECT(self->app), "ovirt-foreign-menu", &foreign_menu, NULL);
1235     dialog = remote_viewer_iso_list_dialog_new(GTK_WINDOW(self->window), foreign_menu);
1236     g_object_unref(foreign_menu);
1237 
1238     if (!dialog)
1239         dialog = gtk_message_dialog_new(GTK_WINDOW(self->window),
1240                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1241                                         GTK_MESSAGE_ERROR,
1242                                         GTK_BUTTONS_CLOSE,
1243                                         _("Unable to connnect to oVirt"));
1244 
1245     g_signal_connect(dialog, "response", G_CALLBACK(iso_dialog_response), NULL);
1246     gtk_widget_show_all(dialog);
1247     gtk_dialog_run(GTK_DIALOG(dialog));
1248 #endif
1249 }
1250 
1251 
1252 VirtViewerNotebook*
virt_viewer_window_get_notebook(VirtViewerWindow * self)1253 virt_viewer_window_get_notebook (VirtViewerWindow *self)
1254 {
1255     return VIRT_VIEWER_NOTEBOOK(self->notebook);
1256 }
1257 
1258 GtkWindow*
virt_viewer_window_get_window(VirtViewerWindow * self)1259 virt_viewer_window_get_window (VirtViewerWindow *self)
1260 {
1261     return GTK_WINDOW(self->window);
1262 }
1263 
1264 static void
virt_viewer_window_pointer_grab(VirtViewerDisplay * display G_GNUC_UNUSED,VirtViewerWindow * self)1265 virt_viewer_window_pointer_grab(VirtViewerDisplay *display G_GNUC_UNUSED,
1266                                 VirtViewerWindow *self)
1267 {
1268     self->grabbed = TRUE;
1269     virt_viewer_window_update_title(self);
1270 }
1271 
1272 static void
virt_viewer_window_pointer_ungrab(VirtViewerDisplay * display G_GNUC_UNUSED,VirtViewerWindow * self)1273 virt_viewer_window_pointer_ungrab(VirtViewerDisplay *display G_GNUC_UNUSED,
1274                                   VirtViewerWindow *self)
1275 {
1276     self->grabbed = FALSE;
1277     virt_viewer_window_update_title(self);
1278 }
1279 
1280 static void
virt_viewer_window_keyboard_grab(VirtViewerDisplay * display G_GNUC_UNUSED,VirtViewerWindow * self)1281 virt_viewer_window_keyboard_grab(VirtViewerDisplay *display G_GNUC_UNUSED,
1282                                  VirtViewerWindow *self)
1283 {
1284     virt_viewer_window_disable_modifiers(self);
1285 }
1286 
1287 static void
virt_viewer_window_keyboard_ungrab(VirtViewerDisplay * display G_GNUC_UNUSED,VirtViewerWindow * self)1288 virt_viewer_window_keyboard_ungrab(VirtViewerDisplay *display G_GNUC_UNUSED,
1289                                    VirtViewerWindow *self)
1290 {
1291     virt_viewer_window_enable_modifiers(self);
1292 }
1293 
1294 void
virt_viewer_window_update_title(VirtViewerWindow * self)1295 virt_viewer_window_update_title(VirtViewerWindow *self)
1296 {
1297     char *title;
1298     char *grabhint = NULL;
1299     GtkWidget *header;
1300     GtkWidget *toolbar;
1301 
1302     header = GTK_WIDGET(gtk_builder_get_object(self->builder, "header"));
1303     toolbar = GTK_WIDGET(gtk_builder_get_object(self->builder, "toolbar"));
1304 
1305     if (self->grabbed) {
1306         gchar *label;
1307         gchar *display_hotkey;
1308         guint accel_key = 0;
1309         GdkModifierType accel_mods = 0;
1310         gchar **accels;
1311 
1312         display_hotkey = virt_viewer_app_get_release_cursor_display_hotkey(self->app);
1313         if (display_hotkey) {
1314             label = spice_hotkey_to_display_hotkey(display_hotkey);
1315         } else {
1316             accels = gtk_application_get_accels_for_action(GTK_APPLICATION(self->app), "win.release-cursor");
1317             if (accels[0])
1318                 gtk_accelerator_parse(accels[0], &accel_key, &accel_mods);
1319             g_strfreev(accels);
1320             g_debug("release-cursor accel key: key=%u, mods=%x", accel_key, accel_mods);
1321             label = gtk_accelerator_get_label(accel_key, accel_mods);
1322         }
1323 
1324         grabhint = g_strdup_printf(_("(Press %s to release pointer)"), label);
1325         g_free(label);
1326 
1327         if (self->subtitle) {
1328             /* translators:
1329              * This is "<ungrab accelerator> <subtitle> - <appname>"
1330              * Such as: "(Press Ctrl+Alt to release pointer) BigCorpTycoon MOTD - Virt Viewer"
1331              */
1332             title = g_strdup_printf(_("%s %s - %s"),
1333                                     grabhint,
1334                                     self->subtitle,
1335                                     g_get_application_name());
1336         } else {
1337             /* translators:
1338              * This is "<ungrab accelerator> - <appname>"
1339              * Such as: "(Press Ctrl+Alt to release pointer) - Virt Viewer"
1340              */
1341             title = g_strdup_printf(_("%s - %s"),
1342                                     grabhint,
1343                                     g_get_application_name());
1344         }
1345     } else if (self->subtitle) {
1346         /* translators:
1347          * This is "<subtitle> - <appname>"
1348          * Such as: "BigCorpTycoon MOTD - Virt Viewer"
1349          */
1350         title = g_strdup_printf(_("%s - %s"),
1351                                 self->subtitle,
1352                                 g_get_application_name());
1353     } else {
1354         title = g_strdup(g_get_application_name());
1355     }
1356 
1357     gtk_window_set_title(GTK_WINDOW(self->window), title);
1358     if (self->subtitle) {
1359         gtk_header_bar_set_title(GTK_HEADER_BAR(header), self->subtitle);
1360         gtk_header_bar_set_title(GTK_HEADER_BAR(toolbar), self->subtitle);
1361     } else {
1362         gtk_header_bar_set_title(GTK_HEADER_BAR(header), g_get_application_name());
1363         gtk_header_bar_set_title(GTK_HEADER_BAR(toolbar), g_get_application_name());
1364     }
1365     if (grabhint) {
1366         gtk_header_bar_set_subtitle(GTK_HEADER_BAR(header), grabhint);
1367         gtk_header_bar_set_subtitle(GTK_HEADER_BAR(toolbar), grabhint);
1368     } else {
1369         gtk_header_bar_set_subtitle(GTK_HEADER_BAR(header), "");
1370         gtk_header_bar_set_subtitle(GTK_HEADER_BAR(toolbar), "");
1371     }
1372 
1373     g_free(title);
1374     g_free(grabhint);
1375 }
1376 
1377 void
virt_viewer_window_set_usb_options_sensitive(VirtViewerWindow * self,gboolean sensitive)1378 virt_viewer_window_set_usb_options_sensitive(VirtViewerWindow *self, gboolean sensitive)
1379 {
1380     GAction *action;
1381     GActionMap *map;
1382 
1383     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1384 
1385     map = G_ACTION_MAP(self->window);
1386     action = g_action_map_lookup_action(map, "usb-device-select");
1387     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1388 }
1389 
1390 void
virt_viewer_window_set_usb_reset_sensitive(VirtViewerWindow * self,gboolean sensitive)1391 virt_viewer_window_set_usb_reset_sensitive(VirtViewerWindow *self, gboolean sensitive)
1392 {
1393     GAction *action;
1394     GActionMap *map;
1395 
1396     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1397 
1398     map = G_ACTION_MAP(self->window);
1399     action = g_action_map_lookup_action(map, "usb-device-reset");
1400     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1401 }
1402 
1403 void
virt_viewer_window_set_actions_sensitive(VirtViewerWindow * self,gboolean sensitive)1404 virt_viewer_window_set_actions_sensitive(VirtViewerWindow *self, gboolean sensitive)
1405 {
1406     GAction *action;
1407     GActionMap *map;
1408 
1409     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1410 
1411     map = G_ACTION_MAP(self->window);
1412     action = g_action_map_lookup_action(map, "preferences");
1413     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1414 
1415     action = g_action_map_lookup_action(map, "screenshot");
1416     g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1417                                 sensitive &&
1418                                 VIRT_VIEWER_DISPLAY_CAN_SCREENSHOT(self->display));
1419 
1420     action = g_action_map_lookup_action(map, "zoom-in");
1421     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1422 
1423     action = g_action_map_lookup_action(map, "zoom-out");
1424     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1425 
1426     action = g_action_map_lookup_action(map, "zoom-reset");
1427     g_simple_action_set_enabled(G_SIMPLE_ACTION(action), sensitive);
1428 
1429     action = g_action_map_lookup_action(map, "send-key");
1430     g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1431                                 sensitive &&
1432                                 VIRT_VIEWER_DISPLAY_CAN_SEND_KEYS(self->display));
1433 }
1434 
1435 static void
display_show_hint(VirtViewerDisplay * display,GParamSpec * pspec G_GNUC_UNUSED,VirtViewerWindow * self)1436 display_show_hint(VirtViewerDisplay *display,
1437                   GParamSpec *pspec G_GNUC_UNUSED,
1438                   VirtViewerWindow *self)
1439 {
1440     GAction *action;
1441     GActionMap *map;
1442     guint hint;
1443 
1444     g_object_get(display, "show-hint", &hint, NULL);
1445 
1446     hint = (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY);
1447 
1448     if (!self->initial_zoom_set && hint && virt_viewer_display_get_enabled(display)) {
1449         self->initial_zoom_set = TRUE;
1450         virt_viewer_window_set_zoom_level(self, self->zoomlevel);
1451     }
1452 
1453     map = G_ACTION_MAP(self->window);
1454     action = g_action_map_lookup_action(map, "screenshot");
1455     g_simple_action_set_enabled(G_SIMPLE_ACTION(action),
1456                                 hint);
1457 }
1458 
1459 
1460 static gboolean
window_key_pressed(GtkWidget * widget G_GNUC_UNUSED,GdkEvent * ev,VirtViewerWindow * self)1461 window_key_pressed (GtkWidget *widget G_GNUC_UNUSED,
1462                     GdkEvent  *ev,
1463                    VirtViewerWindow *self)
1464 {
1465     GdkEventKey  *event;
1466     VirtViewerDisplay *display;
1467     display = self->display;
1468     event = (GdkEventKey *)ev;
1469 
1470     gtk_widget_grab_focus(GTK_WIDGET(display));
1471 
1472     // Look through keymaps - if set for mappings and intercept
1473     if (self->keyMappings) {
1474         VirtViewerKeyMapping *ptr, *matched;
1475         ptr = self->keyMappings;
1476         matched = NULL;
1477         do {
1478                 if (event->keyval == ptr->sourceKey) {
1479                         matched = ptr;
1480                 }
1481                 if (ptr->isLast) {
1482                         break;
1483                 }
1484                 ptr++;
1485         } while (matched == NULL);
1486 
1487         if (matched) {
1488                 if (matched->mappedKeys == NULL) {
1489                         // Key to be ignored and not pass through to VM
1490                         g_debug("Blocking keypress '%s'", gdk_keyval_name(matched->sourceKey));
1491                 } else {
1492                         g_debug("Sending through mapped keys");
1493                         virt_viewer_display_send_keys(display,
1494                                 matched->mappedKeys, matched->numMappedKeys);
1495                 }
1496                 return TRUE;
1497         }
1498 
1499     }
1500     g_debug("Key pressed was keycode='0x%x', gdk_keyname='%s'", event->keyval, gdk_keyval_name(event->keyval));
1501     return gtk_widget_event(GTK_WIDGET(display), ev);
1502 }
1503 
1504 
1505 void
virt_viewer_window_set_display(VirtViewerWindow * self,VirtViewerDisplay * display)1506 virt_viewer_window_set_display(VirtViewerWindow *self, VirtViewerDisplay *display)
1507 {
1508     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1509     g_return_if_fail(display == NULL || VIRT_VIEWER_IS_DISPLAY(display));
1510 
1511     if (self->display) {
1512         gtk_notebook_remove_page(GTK_NOTEBOOK(self->notebook), 1);
1513         g_object_unref(self->display);
1514         self->display = NULL;
1515     }
1516 
1517     if (display != NULL) {
1518         self->display = g_object_ref(display);
1519 
1520         virt_viewer_display_set_monitor(VIRT_VIEWER_DISPLAY(self->display), self->fullscreen_monitor);
1521         virt_viewer_display_set_fullscreen(VIRT_VIEWER_DISPLAY(self->display), self->fullscreen);
1522 
1523         gtk_widget_show_all(GTK_WIDGET(display));
1524         gtk_notebook_append_page(GTK_NOTEBOOK(self->notebook), GTK_WIDGET(display), NULL);
1525         gtk_widget_realize(GTK_WIDGET(display));
1526 
1527         virt_viewer_signal_connect_object(self->window, "key-press-event",
1528                                           G_CALLBACK(window_key_pressed), self, 0);
1529 
1530         /* switch back to non-display if not ready */
1531         if (!(virt_viewer_display_get_show_hint(display) &
1532               VIRT_VIEWER_DISPLAY_SHOW_HINT_READY))
1533             gtk_notebook_set_current_page(GTK_NOTEBOOK(self->notebook), 0);
1534 
1535         virt_viewer_signal_connect_object(display, "display-pointer-grab",
1536                                           G_CALLBACK(virt_viewer_window_pointer_grab), self, 0);
1537         virt_viewer_signal_connect_object(display, "display-pointer-ungrab",
1538                                           G_CALLBACK(virt_viewer_window_pointer_ungrab), self, 0);
1539         virt_viewer_signal_connect_object(display, "display-keyboard-grab",
1540                                           G_CALLBACK(virt_viewer_window_keyboard_grab), self, 0);
1541         virt_viewer_signal_connect_object(display, "display-keyboard-ungrab",
1542                                           G_CALLBACK(virt_viewer_window_keyboard_ungrab), self, 0);
1543         virt_viewer_signal_connect_object(display, "display-desktop-resize",
1544                                           G_CALLBACK(virt_viewer_window_desktop_resize), self, 0);
1545         virt_viewer_signal_connect_object(display, "notify::show-hint",
1546                                           G_CALLBACK(display_show_hint), self, 0);
1547 
1548         display_show_hint(display, NULL, self);
1549 
1550         if (virt_viewer_display_get_enabled(display))
1551             virt_viewer_window_desktop_resize(display, self);
1552     }
1553 }
1554 
1555 static void
virt_viewer_window_enable_kiosk(VirtViewerWindow * self)1556 virt_viewer_window_enable_kiosk(VirtViewerWindow *self)
1557 {
1558     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1559 
1560     virt_viewer_timed_revealer_force_reveal(self->revealer, FALSE);
1561 
1562     /* You probably also want X11 Option "DontVTSwitch" "true" */
1563     /* and perhaps more distro/desktop-specific options */
1564     virt_viewer_window_disable_modifiers(self);
1565 }
1566 
1567 void
virt_viewer_window_show(VirtViewerWindow * self)1568 virt_viewer_window_show(VirtViewerWindow *self)
1569 {
1570     if (self->display && !virt_viewer_display_get_enabled(self->display))
1571         virt_viewer_display_enable(self->display);
1572 
1573     if (self->desktop_resize_pending) {
1574         virt_viewer_window_queue_resize(self);
1575         self->desktop_resize_pending = FALSE;
1576     }
1577 
1578     gtk_widget_show(self->window);
1579 
1580     if (self->fullscreen)
1581         virt_viewer_window_move_to_monitor(self);
1582 }
1583 
1584 void
virt_viewer_window_hide(VirtViewerWindow * self)1585 virt_viewer_window_hide(VirtViewerWindow *self)
1586 {
1587     if (self->kiosk) {
1588         g_warning("Can't hide windows in kiosk mode");
1589         return;
1590     }
1591 
1592     gtk_widget_hide(self->window);
1593 
1594     if (self->display) {
1595         VirtViewerDisplay *display = self->display;
1596         virt_viewer_display_disable(display);
1597     }
1598 }
1599 
1600 void
virt_viewer_window_set_zoom_level(VirtViewerWindow * self,gint zoom_level)1601 virt_viewer_window_set_zoom_level(VirtViewerWindow *self, gint zoom_level)
1602 {
1603     gint min_zoom;
1604 
1605     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1606 
1607     if (zoom_level < MIN_ZOOM_LEVEL)
1608         zoom_level = MIN_ZOOM_LEVEL;
1609     if (zoom_level > MAX_ZOOM_LEVEL)
1610         zoom_level = MAX_ZOOM_LEVEL;
1611     self->zoomlevel = zoom_level;
1612 
1613     if (!self->display)
1614         return;
1615 
1616     min_zoom = virt_viewer_window_get_minimal_zoom_level(self);
1617     if (min_zoom > self->zoomlevel) {
1618         g_debug("Cannot set zoom level %d, using %d", self->zoomlevel, min_zoom);
1619         self->zoomlevel = min_zoom;
1620     }
1621 
1622     if (self->zoomlevel == virt_viewer_display_get_zoom_level(self->display) &&
1623         self->zoomlevel == virt_viewer_window_get_real_zoom_level(self)) {
1624         g_debug("Zoom level not changed, using: %d", self->zoomlevel);
1625         return;
1626     }
1627 
1628     virt_viewer_display_set_zoom_level(VIRT_VIEWER_DISPLAY(self->display), self->zoomlevel);
1629 
1630     if (!VIRT_VIEWER_IS_DISPLAY_VTE(self->display)) {
1631         virt_viewer_window_queue_resize(self);
1632     }
1633 }
1634 
virt_viewer_window_get_zoom_level(VirtViewerWindow * self)1635 gint virt_viewer_window_get_zoom_level(VirtViewerWindow *self)
1636 {
1637     g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NORMAL_ZOOM_LEVEL);
1638     return self->zoomlevel;
1639 }
1640 
1641 GMenuModel *
virt_viewer_window_get_menu_displays(VirtViewerWindow * self)1642 virt_viewer_window_get_menu_displays(VirtViewerWindow *self)
1643 {
1644     GObject *menu;
1645     GMenuModel *model;
1646     g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
1647 
1648     menu = gtk_builder_get_object(self->builder, "header-machine");
1649     model = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(menu));
1650 
1651     return g_menu_model_get_item_link(model, 0, G_MENU_LINK_SECTION);
1652 }
1653 
1654 GtkBuilder*
virt_viewer_window_get_builder(VirtViewerWindow * self)1655 virt_viewer_window_get_builder(VirtViewerWindow *self)
1656 {
1657     g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
1658 
1659     return self->builder;
1660 }
1661 
1662 VirtViewerDisplay*
virt_viewer_window_get_display(VirtViewerWindow * self)1663 virt_viewer_window_get_display(VirtViewerWindow *self)
1664 {
1665     g_return_val_if_fail(VIRT_VIEWER_WINDOW(self), NULL);
1666 
1667     return self->display;
1668 }
1669 
1670 void
virt_viewer_window_set_kiosk(VirtViewerWindow * self,gboolean enabled)1671 virt_viewer_window_set_kiosk(VirtViewerWindow *self, gboolean enabled)
1672 {
1673     g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
1674     g_return_if_fail(enabled == !!enabled);
1675 
1676     if (self->kiosk == enabled)
1677         return;
1678 
1679     self->kiosk = enabled;
1680 
1681     if (enabled)
1682         virt_viewer_window_enable_kiosk(self);
1683     else
1684         g_debug("disabling kiosk not implemented yet");
1685 }
1686 
1687 static void
virt_viewer_window_get_minimal_dimensions(VirtViewerWindow * self G_GNUC_UNUSED,guint * width,guint * height)1688 virt_viewer_window_get_minimal_dimensions(VirtViewerWindow *self G_GNUC_UNUSED,
1689                                           guint *width,
1690                                           guint *height)
1691 {
1692     *height = MIN_DISPLAY_HEIGHT;
1693     *width = MIN_DISPLAY_WIDTH;
1694 }
1695 
1696 /**
1697  * virt_viewer_window_get_minimal_zoom_level:
1698  * @self: a #VirtViewerWindow
1699  *
1700  * Calculates the zoom level with respect to the desktop dimensions
1701  *
1702  * Returns: minimal possible zoom level (multiple of ZOOM_STEP)
1703  */
1704 static gint
virt_viewer_window_get_minimal_zoom_level(VirtViewerWindow * self)1705 virt_viewer_window_get_minimal_zoom_level(VirtViewerWindow *self)
1706 {
1707     guint min_width, min_height;
1708     guint width, height; /* desktop dimensions */
1709     gint zoom;
1710     double width_ratio, height_ratio;
1711 
1712     g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self) &&
1713                          self->display != NULL, MIN_ZOOM_LEVEL);
1714 
1715     virt_viewer_window_get_minimal_dimensions(self, &min_width, &min_height);
1716     virt_viewer_display_get_desktop_size(virt_viewer_window_get_display(self), &width, &height);
1717 
1718     /* e.g. minimal width = 200, desktop width = 550 => width ratio = 0.36
1719      * which means that the minimal zoom level is 40 (4 * ZOOM_STEP)
1720      */
1721     width_ratio = (double) min_width / width;
1722     height_ratio = (double) min_height / height;
1723     zoom = ceil(10 * MAX(width_ratio, height_ratio));
1724 
1725     /* make sure that the returned zoom level is in the range from MIN_ZOOM_LEVEL to NORMAL_ZOOM_LEVEL */
1726     return CLAMP(zoom * ZOOM_STEP, MIN_ZOOM_LEVEL, NORMAL_ZOOM_LEVEL);
1727 }
1728