virt-viewer/src/virt-viewer-window.c
Jonathon Jongsma 9c77a78af2 Add new functions to enable/disable a display
Previously, there was a single function for controlling the enabled
state of a display: virt_viewer_display_set_enabled(). Unfortunately,
this function is used for two slightly different things:

 A. It informs the local display widget that the display has become
    disabled or enabled on the server. In other words, it tries to
    synchronize the 'enabled' state of the local widget with the actual
    state of the remote display.

OR

 B. It tries to actively enable a currently-disabled display (or vice
    versa) due to some action by the user in the client application.
    This causes the client to send a new configuration down to the
    server. In other words, it tries to change the state of the remote
    display.

There is some conflict between these two scenarios. If the change is due
to a notification from the server, there is no need to send a new
configuration back down to the server, so this results in unnecessary
monitor configuration messages and can in fact cause issues that are a
little bit hard to track down. Because of this, I decided that it was
really necessary to have two separate functions for these two different
scenarios. so the existing _set_enabled() function will be used for
scenario A mentioned above. I added two new
functions (_enable() and _disable()) that are used to send new
configurations down to the server.
2015-09-10 16:02:13 -05:00

1532 lines
53 KiB
C

/*
* Virt Viewer: A virtual machine console viewer
*
* Copyright (C) 2007-2012 Red Hat, Inc.
* Copyright (C) 2009-2012 Daniel P. Berrange
* Copyright (C) 2010 Marc-André Lureau
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <glib/gprintf.h>
#include <glib/gi18n.h>
#include <math.h>
#include "virt-gtk-compat.h"
#include "virt-viewer-window.h"
#include "virt-viewer-display.h"
#include "virt-viewer-session.h"
#include "virt-viewer-app.h"
#include "virt-viewer-util.h"
#include "view/autoDrawer.h"
/* Signal handlers for main window (move in a VirtViewerMainWindow?) */
void virt_viewer_window_menu_view_zoom_out(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_view_zoom_in(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_view_zoom_reset(GtkWidget *menu, VirtViewerWindow *self);
gboolean virt_viewer_window_delete(GtkWidget *src, void *dummy, VirtViewerWindow *self);
void virt_viewer_window_menu_file_quit(GtkWidget *src, VirtViewerWindow *self);
void virt_viewer_window_guest_details_response(GtkDialog *dialog, gint response_id, gpointer user_data);
void virt_viewer_window_menu_help_about(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_help_guest_details(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_view_fullscreen(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_send(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_file_screenshot(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_file_usb_device_selection(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_file_smartcard_insert(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_file_smartcard_remove(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_view_release_cursor(GtkWidget *menu, VirtViewerWindow *self);
void virt_viewer_window_menu_preferences_cb(GtkWidget *menu, VirtViewerWindow *self);
/* Internal methods */
static void virt_viewer_window_enable_modifiers(VirtViewerWindow *self);
static void virt_viewer_window_disable_modifiers(VirtViewerWindow *self);
static void virt_viewer_window_queue_resize(VirtViewerWindow *self);
static void virt_viewer_window_toolbar_setup(VirtViewerWindow *self);
static GtkMenu* virt_viewer_window_get_keycombo_menu(VirtViewerWindow *self);
static void virt_viewer_window_get_minimal_dimensions(VirtViewerWindow *self, guint *width, guint *height);
static gint virt_viewer_window_get_minimal_zoom_level(VirtViewerWindow *self);
G_DEFINE_TYPE (VirtViewerWindow, virt_viewer_window, G_TYPE_OBJECT)
#define GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), VIRT_VIEWER_TYPE_WINDOW, VirtViewerWindowPrivate))
enum {
PROP_0,
PROP_WINDOW,
PROP_DISPLAY,
PROP_SUBTITLE,
PROP_APP,
};
struct _VirtViewerWindowPrivate {
VirtViewerApp *app;
GtkBuilder *builder;
GtkWidget *window;
GtkWidget *layout;
GtkWidget *toolbar;
GtkWidget *toolbar_usb_device_selection;
GtkWidget *toolbar_send_key;
GtkAccelGroup *accel_group;
VirtViewerNotebook *notebook;
VirtViewerDisplay *display;
gboolean accel_enabled;
GValue accel_setting;
GSList *accel_list;
gboolean enable_mnemonics_save;
gboolean grabbed;
gint fullscreen_monitor;
gboolean desktop_resize_pending;
gboolean kiosk;
gint zoomlevel;
gboolean fullscreen;
gchar *subtitle;
gboolean initial_zoom_set;
};
static void
virt_viewer_window_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec)
{
VirtViewerWindow *self = VIRT_VIEWER_WINDOW(object);
VirtViewerWindowPrivate *priv = self->priv;
switch (property_id) {
case PROP_SUBTITLE:
g_value_set_string(value, priv->subtitle);
break;
case PROP_WINDOW:
g_value_set_object(value, priv->window);
break;
case PROP_DISPLAY:
g_value_set_object(value, virt_viewer_window_get_display(self));
break;
case PROP_APP:
g_value_set_object(value, priv->app);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
virt_viewer_window_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec)
{
VirtViewerWindowPrivate *priv = VIRT_VIEWER_WINDOW(object)->priv;
switch (property_id) {
case PROP_SUBTITLE:
g_free(priv->subtitle);
priv->subtitle = g_value_dup_string(value);
virt_viewer_window_update_title(VIRT_VIEWER_WINDOW(object));
break;
case PROP_APP:
g_return_if_fail(priv->app == NULL);
priv->app = g_value_get_object(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
virt_viewer_window_dispose (GObject *object)
{
VirtViewerWindowPrivate *priv = VIRT_VIEWER_WINDOW(object)->priv;
GSList *it;
if (priv->display) {
g_object_unref(priv->display);
priv->display = NULL;
}
g_debug("Disposing window %p\n", object);
if (priv->window) {
gtk_widget_destroy(priv->window);
priv->window = NULL;
}
if (priv->builder) {
g_object_unref(priv->builder);
priv->builder = NULL;
}
for (it = priv->accel_list ; it != NULL ; it = it->next) {
g_object_unref(G_OBJECT(it->data));
}
g_slist_free(priv->accel_list);
priv->accel_list = NULL;
g_free(priv->subtitle);
priv->subtitle = NULL;
g_value_unset(&priv->accel_setting);
g_clear_object(&priv->toolbar);
G_OBJECT_CLASS (virt_viewer_window_parent_class)->dispose (object);
}
static void
rebuild_combo_menu(GObject *gobject G_GNUC_UNUSED,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data)
{
VirtViewerWindow *self = user_data;
GtkWidget *menu;
menu = GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-send"));
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu),
GTK_WIDGET(virt_viewer_window_get_keycombo_menu(self)));
gtk_widget_set_sensitive(menu, (self->priv->display != NULL));
}
static void
virt_viewer_window_constructed(GObject *object)
{
VirtViewerWindowPrivate *priv = VIRT_VIEWER_WINDOW(object)->priv;
if (G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed)
G_OBJECT_CLASS(virt_viewer_window_parent_class)->constructed(object);
g_signal_connect(priv->app, "notify::enable-accel",
G_CALLBACK(rebuild_combo_menu), object);
rebuild_combo_menu(NULL, NULL, object);
}
static void
virt_viewer_window_class_init (VirtViewerWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (VirtViewerWindowPrivate));
object_class->get_property = virt_viewer_window_get_property;
object_class->set_property = virt_viewer_window_set_property;
object_class->dispose = virt_viewer_window_dispose;
object_class->constructed = virt_viewer_window_constructed;
g_object_class_install_property(object_class,
PROP_SUBTITLE,
g_param_spec_string("subtitle",
"Subtitle",
"Window subtitle",
"",
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_WINDOW,
g_param_spec_object("window",
"Window",
"GtkWindow",
GTK_TYPE_WIDGET,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_DISPLAY,
g_param_spec_object("display",
"Display",
"VirtDisplay",
VIRT_VIEWER_TYPE_DISPLAY,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_APP,
g_param_spec_object("app",
"App",
"VirtViewerApp",
VIRT_VIEWER_TYPE_APP,
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static gboolean
can_activate_cb (GtkWidget *widget G_GNUC_UNUSED,
guint signal_id G_GNUC_UNUSED,
VirtViewerWindow *self G_GNUC_UNUSED)
{
return TRUE;
}
static void
virt_viewer_window_init (VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv;
GtkWidget *vbox;
GdkColor color;
GSList *accels;
self->priv = GET_PRIVATE(self);
priv = self->priv;
priv->fullscreen_monitor = -1;
g_value_init(&priv->accel_setting, G_TYPE_STRING);
priv->notebook = virt_viewer_notebook_new();
priv->builder = virt_viewer_util_load_ui("virt-viewer.xml");
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-send")), FALSE);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-view-zoom")), FALSE);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-file-screenshot")), FALSE);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-preferences")), FALSE);
gtk_builder_connect_signals(priv->builder, self);
priv->accel_group = GTK_ACCEL_GROUP(gtk_builder_get_object(priv->builder, "accelgroup"));
/* make sure they can be activated even if the menu item is not visible */
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-view-fullscreen"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-file-smartcard-insert"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-file-smartcard-remove"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-view-release-cursor"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-view-zoom-reset"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-view-zoom-in"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
g_signal_connect(gtk_builder_get_object(priv->builder, "menu-view-zoom-out"),
"can-activate-accel", G_CALLBACK(can_activate_cb), self);
vbox = GTK_WIDGET(gtk_builder_get_object(priv->builder, "viewer-box"));
virt_viewer_window_toolbar_setup(self);
gtk_box_pack_end(GTK_BOX(vbox), priv->layout, TRUE, TRUE, 0);
gdk_color_parse("black", &color);
gtk_widget_modify_bg(priv->layout, GTK_STATE_NORMAL, &color);
priv->window = GTK_WIDGET(gtk_builder_get_object(priv->builder, "viewer"));
gtk_window_add_accel_group(GTK_WINDOW(priv->window), priv->accel_group);
virt_viewer_window_update_title(self);
gtk_window_set_resizable(GTK_WINDOW(priv->window), TRUE);
#if GTK_CHECK_VERSION(3, 0, 0)
gtk_window_set_has_resize_grip(GTK_WINDOW(priv->window), FALSE);
#endif
priv->accel_enabled = TRUE;
accels = gtk_accel_groups_from_object(G_OBJECT(priv->window));
for ( ; accels ; accels = accels->next) {
priv->accel_list = g_slist_append(priv->accel_list, accels->data);
g_object_ref(G_OBJECT(accels->data));
}
priv->zoomlevel = NORMAL_ZOOM_LEVEL;
}
static void
virt_viewer_window_desktop_resize(VirtViewerDisplay *display G_GNUC_UNUSED,
VirtViewerWindow *self)
{
if (!gtk_widget_get_visible(self->priv->window)) {
self->priv->desktop_resize_pending = TRUE;
return;
}
virt_viewer_window_queue_resize(self);
}
static gint
virt_viewer_window_get_real_zoom_level(VirtViewerWindow *self)
{
GtkAllocation allocation;
guint width, height;
gtk_widget_get_allocation(GTK_WIDGET(self->priv->display), &allocation);
virt_viewer_display_get_desktop_size(self->priv->display, &width, &height);
return round((double) NORMAL_ZOOM_LEVEL * allocation.width / width);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_view_zoom_out(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_set_zoom_level(self,
virt_viewer_window_get_real_zoom_level(self) - ZOOM_STEP);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_view_zoom_in(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_set_zoom_level(self,
virt_viewer_window_get_real_zoom_level(self) + ZOOM_STEP);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_view_zoom_reset(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_set_zoom_level(self, NORMAL_ZOOM_LEVEL);
}
/* Kick GtkWindow to tell it to adjust to our new widget sizes */
static void
virt_viewer_window_queue_resize(VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
#if GTK_CHECK_VERSION(3, 0, 0)
GtkRequisition nat;
gtk_window_set_default_size(GTK_WINDOW(priv->window), -1, -1);
gtk_widget_get_preferred_size(GTK_WIDGET(priv->window), NULL, &nat);
gtk_window_resize(GTK_WINDOW(priv->window), nat.width, nat.height);
#else
gtk_window_resize(GTK_WINDOW(priv->window), 1, 1);
#endif
}
static void
virt_viewer_window_move_to_monitor(VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
GdkRectangle mon;
gint n = priv->fullscreen_monitor;
if (n == -1)
return;
gdk_screen_get_monitor_geometry(gdk_screen_get_default(), n, &mon);
gtk_window_move(GTK_WINDOW(priv->window), mon.x, mon.y);
gtk_widget_set_size_request(GTK_WIDGET(priv->window),
mon.width,
mon.height);
}
static gboolean
mapped(GtkWidget *widget, GdkEvent *event G_GNUC_UNUSED,
VirtViewerWindow *self)
{
g_signal_handlers_disconnect_by_func(widget, mapped, self);
self->priv->fullscreen = FALSE;
virt_viewer_window_enter_fullscreen(self, self->priv->fullscreen_monitor);
return FALSE;
}
static void
virt_viewer_window_menu_fullscreen_set_active(VirtViewerWindow *self, gboolean active)
{
GtkCheckMenuItem *check = GTK_CHECK_MENU_ITEM(gtk_builder_get_object(self->priv->builder, "menu-view-fullscreen"));
g_signal_handlers_block_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
gtk_check_menu_item_set_active(check, active);
g_signal_handlers_unblock_by_func(check, virt_viewer_window_menu_view_fullscreen, self);
}
void
virt_viewer_window_leave_fullscreen(VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
GtkWidget *menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "top-menu"));
/* if we enter and leave fullscreen mode before being shown, make sure to
* disconnect the mapped signal handler */
g_signal_handlers_disconnect_by_func(priv->window, mapped, self);
if (!priv->fullscreen)
return;
virt_viewer_window_menu_fullscreen_set_active(self, FALSE);
priv->fullscreen = FALSE;
priv->fullscreen_monitor = -1;
if (priv->display) {
virt_viewer_display_set_monitor(priv->display, -1);
virt_viewer_display_set_fullscreen(priv->display, FALSE);
}
ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(priv->layout), FALSE);
gtk_widget_show(menu);
gtk_widget_hide(priv->toolbar);
gtk_widget_set_size_request(GTK_WIDGET(priv->window), -1, -1);
gtk_window_unfullscreen(GTK_WINDOW(priv->window));
}
void
virt_viewer_window_enter_fullscreen(VirtViewerWindow *self, gint monitor)
{
VirtViewerWindowPrivate *priv = self->priv;
GtkWidget *menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "top-menu"));
if (priv->fullscreen && priv->fullscreen_monitor != monitor)
virt_viewer_window_leave_fullscreen(self);
if (priv->fullscreen)
return;
priv->fullscreen_monitor = monitor;
priv->fullscreen = TRUE;
if (!gtk_widget_get_mapped(priv->window)) {
/*
* To avoid some races with metacity, the window should be placed
* as early as possible, before it is (re)allocated & mapped
* Position & size should not be queried yet. (rhbz#809546).
*/
virt_viewer_window_move_to_monitor(self);
g_signal_connect(priv->window, "map-event", G_CALLBACK(mapped), self);
return;
}
virt_viewer_window_menu_fullscreen_set_active(self, TRUE);
gtk_widget_hide(menu);
gtk_widget_show(priv->toolbar);
ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(priv->layout), TRUE);
ViewAutoDrawer_Close(VIEW_AUTODRAWER(priv->layout));
if (priv->display) {
virt_viewer_display_set_monitor(priv->display, monitor);
virt_viewer_display_set_fullscreen(priv->display, TRUE);
}
virt_viewer_window_move_to_monitor(self);
gtk_window_fullscreen(GTK_WINDOW(priv->window));
}
#define MAX_KEY_COMBO 4
struct keyComboDef {
guint keys[MAX_KEY_COMBO];
const char *label;
const gchar* accel_path;
};
static const struct keyComboDef keyCombos[] = {
{ { GDK_Control_L, GDK_Alt_L, GDK_Delete, GDK_VoidSymbol }, N_("Ctrl+Alt+_Del"), "<virt-viewer>/send/secure-attention"},
{ { GDK_Control_L, GDK_Alt_L, GDK_BackSpace, GDK_VoidSymbol }, N_("Ctrl+Alt+_Backspace"), NULL},
{ { GDK_VoidSymbol }, "" , NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F1, GDK_VoidSymbol }, N_("Ctrl+Alt+F_1"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F2, GDK_VoidSymbol }, N_("Ctrl+Alt+F_2"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F3, GDK_VoidSymbol }, N_("Ctrl+Alt+F_3"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F4, GDK_VoidSymbol }, N_("Ctrl+Alt+F_4"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F5, GDK_VoidSymbol }, N_("Ctrl+Alt+F_5"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F6, GDK_VoidSymbol }, N_("Ctrl+Alt+F_6"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F7, GDK_VoidSymbol }, N_("Ctrl+Alt+F_7"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F8, GDK_VoidSymbol }, N_("Ctrl+Alt+F_8"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F9, GDK_VoidSymbol }, N_("Ctrl+Alt+F_9"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F10, GDK_VoidSymbol }, N_("Ctrl+Alt+F1_0"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F11, GDK_VoidSymbol }, N_("Ctrl+Alt+F11"), NULL},
{ { GDK_Control_L, GDK_Alt_L, GDK_F12, GDK_VoidSymbol }, N_("Ctrl+Alt+F12"), NULL},
{ { GDK_VoidSymbol }, "" , NULL},
{ { GDK_Print, GDK_VoidSymbol }, "_PrintScreen", NULL},
};
static guint
get_nkeys(const guint *keys)
{
guint i;
for (i = 0; keys[i] != GDK_VoidSymbol; )
i++;
return i;
}
G_MODULE_EXPORT void
virt_viewer_window_menu_send(GtkWidget *menu,
VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
g_return_if_fail(priv->display != NULL);
guint *keys = g_object_get_data(G_OBJECT(menu), "vv-keys");
g_return_if_fail(keys != NULL);
virt_viewer_display_send_keys(VIRT_VIEWER_DISPLAY(priv->display),
keys, get_nkeys(keys));
}
static void
virt_viewer_menu_add_combo(VirtViewerWindow *self, GtkMenu *menu,
const guint *keys, const gchar *label, const gchar* accel_path)
{
GtkWidget *item;
if (keys == NULL || keys[0] == GDK_VoidSymbol) {
item = gtk_separator_menu_item_new();
} else {
item = gtk_menu_item_new_with_mnemonic(label);
if (accel_path) {
gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
/* make accel work in fullscreen */
g_signal_connect(item, "can-activate-accel", G_CALLBACK(can_activate_cb), self);
}
guint *ckeys = g_memdup(keys, (get_nkeys(keys) + 1) * sizeof(guint));
g_object_set_data_full(G_OBJECT(item), "vv-keys", ckeys, g_free);
g_signal_connect(item, "activate", G_CALLBACK(virt_viewer_window_menu_send), self);
}
gtk_container_add(GTK_CONTAINER(menu), item);
}
static guint*
accel_key_to_keys(const GtkAccelKey *key)
{
guint val;
GArray *a = g_array_new(FALSE, FALSE, sizeof(guint));
g_warn_if_fail((key->accel_mods &
~(GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0);
/* first, send the modifiers */
if (key->accel_mods & GDK_SHIFT_MASK) {
val = GDK_Shift_L;
g_array_append_val(a, val);
}
if (key->accel_mods & GDK_CONTROL_MASK) {
val = GDK_Control_L;
g_array_append_val(a, val);
}
if (key->accel_mods & GDK_MOD1_MASK) {
val = GDK_Alt_L;
g_array_append_val(a, val);
}
/* only after, the non-modifier key (ctrl-t, not t-ctrl) */
val = key->accel_key;
g_array_append_val(a, val);
val = GDK_VoidSymbol;
g_array_append_val(a, val);
return (guint*)g_array_free(a, FALSE);
}
struct accelCbData
{
VirtViewerWindow *self;
GtkMenu *menu;
};
static void
accel_map_item_cb(gpointer data,
const gchar *accel_path,
guint accel_key,
GdkModifierType accel_mods,
gboolean changed G_GNUC_UNUSED)
{
struct accelCbData *d = data;
GtkAccelKey key = {
.accel_key = accel_key,
.accel_mods = accel_mods
};
if (!g_str_has_prefix(accel_path, "<virt-viewer>"))
return;
if (accel_key == GDK_VoidSymbol || accel_key == 0)
return;
guint *keys = accel_key_to_keys(&key);
gchar *label = gtk_accelerator_get_label(accel_key, accel_mods);
virt_viewer_menu_add_combo(d->self, d->menu, keys, label, NULL);
g_free(label);
g_free(keys);
}
static GtkMenu*
virt_viewer_window_get_keycombo_menu(VirtViewerWindow *self)
{
gint i;
VirtViewerWindowPrivate *priv = self->priv;
GtkMenu *menu = GTK_MENU(gtk_menu_new());
gtk_menu_set_accel_group(menu, priv->accel_group);
for (i = 0 ; i < G_N_ELEMENTS(keyCombos); i++) {
virt_viewer_menu_add_combo(self, menu, keyCombos[i].keys, keyCombos[i].label, keyCombos[i].accel_path);
}
if (virt_viewer_app_get_enable_accel(priv->app)) {
struct accelCbData d = {
.self = self,
.menu = menu
};
gtk_accel_map_foreach(&d, accel_map_item_cb);
}
gtk_widget_show_all(GTK_WIDGET(menu));
return menu;
}
void
virt_viewer_window_disable_modifiers(VirtViewerWindow *self)
{
GtkSettings *settings = gtk_settings_get_default();
VirtViewerWindowPrivate *priv = self->priv;
GValue empty;
GSList *accels;
if (!priv->accel_enabled)
return;
/* This stops F10 activating menu bar */
memset(&empty, 0, sizeof empty);
g_value_init(&empty, G_TYPE_STRING);
g_object_get_property(G_OBJECT(settings), "gtk-menu-bar-accel", &priv->accel_setting);
g_object_set_property(G_OBJECT(settings), "gtk-menu-bar-accel", &empty);
/* This stops global accelerators like Ctrl+Q == Quit */
for (accels = priv->accel_list ; accels ; accels = accels->next) {
if (virt_viewer_app_get_enable_accel(priv->app) &&
priv->accel_group == accels->data)
continue;
gtk_window_remove_accel_group(GTK_WINDOW(priv->window), accels->data);
}
/* This stops menu bar shortcuts like Alt+F == File */
g_object_get(settings,
"gtk-enable-mnemonics", &priv->enable_mnemonics_save,
NULL);
g_object_set(settings,
"gtk-enable-mnemonics", FALSE,
NULL);
priv->accel_enabled = FALSE;
}
void
virt_viewer_window_enable_modifiers(VirtViewerWindow *self)
{
GtkSettings *settings = gtk_settings_get_default();
VirtViewerWindowPrivate *priv = self->priv;
GSList *accels;
if (priv->accel_enabled)
return;
/* This allows F10 activating menu bar */
g_object_set_property(G_OBJECT(settings), "gtk-menu-bar-accel", &priv->accel_setting);
/* This allows global accelerators like Ctrl+Q == Quit */
for (accels = priv->accel_list ; accels ; accels = accels->next) {
if (virt_viewer_app_get_enable_accel(priv->app) &&
priv->accel_group == accels->data)
continue;
gtk_window_add_accel_group(GTK_WINDOW(priv->window), accels->data);
}
/* This allows menu bar shortcuts like Alt+F == File */
g_object_set(settings,
"gtk-enable-mnemonics", priv->enable_mnemonics_save,
NULL);
priv->accel_enabled = TRUE;
}
G_MODULE_EXPORT gboolean
virt_viewer_window_delete(GtkWidget *src G_GNUC_UNUSED,
void *dummy G_GNUC_UNUSED,
VirtViewerWindow *self)
{
g_debug("Window closed");
virt_viewer_app_maybe_quit(self->priv->app, self);
return TRUE;
}
G_MODULE_EXPORT void
virt_viewer_window_menu_file_quit(GtkWidget *src G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_app_maybe_quit(self->priv->app, self);
}
static void
virt_viewer_window_set_fullscreen(VirtViewerWindow *self,
gboolean fullscreen)
{
if (fullscreen) {
virt_viewer_window_enter_fullscreen(self, -1);
} else {
/* leave all windows fullscreen state */
if (virt_viewer_app_get_fullscreen(self->priv->app))
g_object_set(self->priv->app, "fullscreen", FALSE, NULL);
/* or just this window */
else
virt_viewer_window_leave_fullscreen(self);
}
}
static void
virt_viewer_window_toolbar_leave_fullscreen(GtkWidget *button G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_set_fullscreen(self, FALSE);
}
static void keycombo_menu_location(GtkMenu *menu G_GNUC_UNUSED, gint *x, gint *y,
gboolean *push_in, gpointer user_data)
{
VirtViewerWindow *self = user_data;
GtkAllocation allocation;
GtkWidget *toplevel = gtk_widget_get_toplevel(self->priv->toolbar_send_key);
*push_in = TRUE;
gdk_window_get_origin(gtk_widget_get_window(toplevel), x, y);
gtk_widget_translate_coordinates(self->priv->toolbar_send_key, toplevel,
*x, *y, x, y);
gtk_widget_get_allocation(self->priv->toolbar_send_key, &allocation);
*y += allocation.height;
}
static void
virt_viewer_window_toolbar_send_key(GtkWidget *button G_GNUC_UNUSED,
VirtViewerWindow *self)
{
GtkMenu *menu = virt_viewer_window_get_keycombo_menu(self);
gtk_menu_attach_to_widget(menu, GTK_WIDGET(self->priv->window), NULL);
g_object_ref_sink(menu);
gtk_menu_popup(menu, NULL, NULL, keycombo_menu_location, self,
0, gtk_get_current_event_time());
g_object_unref(menu);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_view_fullscreen(GtkWidget *menu,
VirtViewerWindow *self)
{
gboolean fullscreen = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu));
virt_viewer_window_set_fullscreen(self, fullscreen);
}
static void add_if_writable (GdkPixbufFormat *data, GHashTable *formats)
{
if (gdk_pixbuf_format_is_writable(data)) {
gchar **extensions;
gchar **it;
extensions = gdk_pixbuf_format_get_extensions(data);
for (it = extensions; *it != NULL; it++) {
g_hash_table_insert(formats, g_strdup(*it), data);
}
g_strfreev(extensions);
}
}
static GHashTable *init_image_formats(void)
{
GHashTable *format_map;
GSList *formats = gdk_pixbuf_get_formats();
format_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_slist_foreach(formats, (GFunc)add_if_writable, format_map);
g_slist_free (formats);
return format_map;
}
static GdkPixbufFormat *get_image_format(const char *filename)
{
static GOnce image_formats_once = G_ONCE_INIT;
const char *ext;
g_once(&image_formats_once, (GThreadFunc)init_image_formats, NULL);
ext = strrchr(filename, '.');
if (ext == NULL)
return NULL;
ext++; /* skip '.' */
return g_hash_table_lookup(image_formats_once.retval, ext);
}
static void
virt_viewer_window_save_screenshot(VirtViewerWindow *self,
const char *file)
{
VirtViewerWindowPrivate *priv = self->priv;
GdkPixbuf *pix = virt_viewer_display_get_pixbuf(VIRT_VIEWER_DISPLAY(priv->display));
GdkPixbufFormat *format = get_image_format(file);
if (format == NULL) {
g_debug("unknown file extension, falling back to png");
if (!g_str_has_suffix(file, ".png")) {
char *png_filename;
png_filename = g_strconcat(file, ".png", NULL);
gdk_pixbuf_save(pix, png_filename, "png", NULL,
"tEXt::Generator App", PACKAGE, NULL);
g_free(png_filename);
} else {
gdk_pixbuf_save(pix, file, "png", NULL,
"tEXt::Generator App", PACKAGE, NULL);
}
} else {
char *type = gdk_pixbuf_format_get_name(format);
g_debug("saving to %s", type);
gdk_pixbuf_save(pix, file, type, NULL, NULL);
g_free(type);
}
g_object_unref(pix);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_file_screenshot(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
GtkWidget *dialog;
VirtViewerWindowPrivate *priv = self->priv;
const char *image_dir;
g_return_if_fail(priv->display != NULL);
dialog = gtk_file_chooser_dialog_new("Save screenshot",
NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER (dialog), TRUE);
gtk_window_set_transient_for(GTK_WINDOW(dialog),
GTK_WINDOW(self->priv->window));
image_dir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
if (image_dir != NULL)
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (dialog), image_dir);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER (dialog), _("Screenshot"));
if (gtk_dialog_run(GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
char *filename;
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog));
virt_viewer_window_save_screenshot(self, filename);
g_free(filename);
}
gtk_widget_destroy(dialog);
}
G_MODULE_EXPORT void
virt_viewer_window_menu_file_usb_device_selection(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_session_usb_device_selection(virt_viewer_app_get_session(self->priv->app),
GTK_WINDOW(self->priv->window));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_file_smartcard_insert(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_session_smartcard_insert(virt_viewer_app_get_session(self->priv->app));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_file_smartcard_remove(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_session_smartcard_remove(virt_viewer_app_get_session(self->priv->app));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_preferences_cb(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_app_show_preferences(self->priv->app, GTK_WIDGET(self->priv->window));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_view_release_cursor(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
g_return_if_fail(self->priv->display != NULL);
virt_viewer_display_release_cursor(VIRT_VIEWER_DISPLAY(self->priv->display));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_help_guest_details(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
GtkBuilder *ui = virt_viewer_util_load_ui("virt-viewer-guest-details.xml");
char *name = NULL;
char *uuid = NULL;
g_return_if_fail(ui != NULL);
GtkWidget *dialog = GTK_WIDGET(gtk_builder_get_object(ui, "guestdetailsdialog"));
GtkWidget *namelabel = GTK_WIDGET(gtk_builder_get_object(ui, "namevaluelabel"));
GtkWidget *guidlabel = GTK_WIDGET(gtk_builder_get_object(ui, "guidvaluelabel"));
g_return_if_fail(dialog && namelabel && guidlabel);
g_object_get(self->priv->app, "guest-name", &name, "uuid", &uuid, NULL);
if (!name || *name == '\0')
name = g_strdup(_("Unknown"));
if (!uuid || *uuid == '\0')
uuid = g_strdup(_("Unknown"));
gtk_label_set_text(GTK_LABEL(namelabel), name);
gtk_label_set_text(GTK_LABEL(guidlabel), uuid);
g_free(name);
g_free(uuid);
gtk_window_set_transient_for(GTK_WINDOW(dialog),
GTK_WINDOW(self->priv->window));
gtk_builder_connect_signals(ui, self);
gtk_widget_show_all(dialog);
g_object_unref(G_OBJECT(ui));
}
G_MODULE_EXPORT void
virt_viewer_window_guest_details_response(GtkDialog *dialog,
gint response_id,
gpointer user_data G_GNUC_UNUSED)
{
if (response_id == GTK_RESPONSE_CLOSE)
gtk_widget_hide(GTK_WIDGET(dialog));
}
G_MODULE_EXPORT void
virt_viewer_window_menu_help_about(GtkWidget *menu G_GNUC_UNUSED,
VirtViewerWindow *self)
{
GtkBuilder *about = virt_viewer_util_load_ui("virt-viewer-about.xml");
GtkWidget *dialog = GTK_WIDGET(gtk_builder_get_object(about, "about"));
gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), VERSION BUILDID);
gtk_window_set_transient_for(GTK_WINDOW(dialog),
GTK_WINDOW(self->priv->window));
gtk_builder_connect_signals(about, self);
gtk_widget_show_all(dialog);
g_object_unref(G_OBJECT(about));
}
static void
virt_viewer_window_toolbar_setup(VirtViewerWindow *self)
{
GtkWidget *button;
VirtViewerWindowPrivate *priv = self->priv;
priv->toolbar = g_object_ref(gtk_toolbar_new());
gtk_toolbar_set_show_arrow(GTK_TOOLBAR(priv->toolbar), FALSE);
gtk_widget_set_no_show_all(priv->toolbar, TRUE);
gtk_toolbar_set_style(GTK_TOOLBAR(priv->toolbar), GTK_TOOLBAR_BOTH_HORIZ);
/* Close connection */
button = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_CLOSE));
gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), _("Disconnect"));
gtk_widget_show(GTK_WIDGET(button));
gtk_toolbar_insert(GTK_TOOLBAR(priv->toolbar), GTK_TOOL_ITEM (button), 0);
g_signal_connect(button, "clicked", G_CALLBACK(virt_viewer_window_menu_file_quit), self);
/* USB Device selection */
button = gtk_image_new_from_icon_name("virt-viewer-usb",
GTK_ICON_SIZE_INVALID);
button = GTK_WIDGET(gtk_tool_button_new(button, NULL));
gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), _("USB device selection"));
gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), _("USB device selection"));
gtk_toolbar_insert(GTK_TOOLBAR(priv->toolbar), GTK_TOOL_ITEM(button), 0);
g_signal_connect(button, "clicked", G_CALLBACK(virt_viewer_window_menu_file_usb_device_selection), self);
priv->toolbar_usb_device_selection = button;
gtk_widget_show_all(button);
/* Send key */
button = GTK_WIDGET(gtk_tool_button_new(NULL, NULL));
gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(button), "preferences-desktop-keyboard-shortcuts");
gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), _("Send key combination"));
gtk_widget_show(GTK_WIDGET(button));
gtk_toolbar_insert(GTK_TOOLBAR(priv->toolbar), GTK_TOOL_ITEM(button), 0);
g_signal_connect(button, "clicked", G_CALLBACK(virt_viewer_window_toolbar_send_key), self);
gtk_widget_set_sensitive(button, FALSE);
priv->toolbar_send_key = button;
/* Leave fullscreen */
button = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_LEAVE_FULLSCREEN));
gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), _("Leave fullscreen"));
gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), _("Leave fullscreen"));
gtk_tool_item_set_is_important(GTK_TOOL_ITEM(button), TRUE);
gtk_widget_show(GTK_WIDGET(button));
gtk_toolbar_insert(GTK_TOOLBAR(priv->toolbar), GTK_TOOL_ITEM(button), 0);
g_signal_connect(button, "clicked", G_CALLBACK(virt_viewer_window_toolbar_leave_fullscreen), self);
priv->layout = ViewAutoDrawer_New();
ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(priv->layout), FALSE);
ViewOvBox_SetOver(VIEW_OV_BOX(priv->layout), priv->toolbar);
ViewOvBox_SetUnder(VIEW_OV_BOX(priv->layout), GTK_WIDGET(priv->notebook));
ViewAutoDrawer_SetOffset(VIEW_AUTODRAWER(priv->layout), -1);
ViewAutoDrawer_SetFill(VIEW_AUTODRAWER(priv->layout), FALSE);
ViewAutoDrawer_SetOverlapPixels(VIEW_AUTODRAWER(priv->layout), 1);
ViewAutoDrawer_SetNoOverlapPixels(VIEW_AUTODRAWER(priv->layout), 0);
gtk_widget_show(priv->layout);
}
VirtViewerNotebook*
virt_viewer_window_get_notebook (VirtViewerWindow *self)
{
return VIRT_VIEWER_NOTEBOOK(self->priv->notebook);
}
GtkWindow*
virt_viewer_window_get_window (VirtViewerWindow *self)
{
return GTK_WINDOW(self->priv->window);
}
static void
virt_viewer_window_pointer_grab(VirtViewerDisplay *display G_GNUC_UNUSED,
VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
priv->grabbed = TRUE;
virt_viewer_window_update_title(self);
}
static void
virt_viewer_window_pointer_ungrab(VirtViewerDisplay *display G_GNUC_UNUSED,
VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
priv->grabbed = FALSE;
virt_viewer_window_update_title(self);
}
static void
virt_viewer_window_keyboard_grab(VirtViewerDisplay *display G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_disable_modifiers(self);
}
static void
virt_viewer_window_keyboard_ungrab(VirtViewerDisplay *display G_GNUC_UNUSED,
VirtViewerWindow *self)
{
virt_viewer_window_enable_modifiers(self);
}
void
virt_viewer_window_update_title(VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv = self->priv;
char *title;
gchar *ungrab = NULL;
if (priv->grabbed) {
gchar *label;
GtkAccelKey key = { 0 };
if (virt_viewer_app_get_enable_accel(priv->app))
gtk_accel_map_lookup_entry("<virt-viewer>/view/release-cursor", &key);
if (key.accel_key || key.accel_mods) {
g_debug("release-cursor accel key: key=%u, mods=%x, flags=%u", key.accel_key, key.accel_mods, key.accel_flags);
label = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
} else {
label = g_strdup(_("Ctrl+Alt"));
}
ungrab = g_strdup_printf(_("(Press %s to release pointer)"), label);
g_free(label);
}
if (!ungrab && !priv->subtitle)
title = g_strdup(g_get_application_name());
else
/* translators:
* This is "<ungrab (or empty)><space (or empty)><subtitle (or empty)> - <appname>"
* Such as: "(Press Ctrl+Alt to release pointer) BigCorpTycoon MOTD - Virt Viewer"
*/
title = g_strdup_printf(_("%s%s%s - %s"),
/* translators: <ungrab empty> */
ungrab ? ungrab : "",
/* translators: <space> */
ungrab && priv->subtitle ? _(" ") : "",
priv->subtitle,
g_get_application_name());
gtk_window_set_title(GTK_WINDOW(priv->window), title);
g_free(title);
g_free(ungrab);
}
void
virt_viewer_window_set_menu_displays_sensitive(VirtViewerWindow *self, gboolean sensitive)
{
VirtViewerWindowPrivate *priv;
GtkWidget *menu;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
priv = self->priv;
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-displays"));
gtk_widget_set_sensitive(menu, sensitive);
}
void
virt_viewer_window_set_usb_options_sensitive(VirtViewerWindow *self, gboolean sensitive)
{
VirtViewerWindowPrivate *priv;
GtkWidget *menu;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
priv = self->priv;
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-file-usb-device-selection"));
gtk_widget_set_sensitive(menu, sensitive);
gtk_widget_set_visible(priv->toolbar_usb_device_selection, sensitive);
}
void
virt_viewer_window_set_menus_sensitive(VirtViewerWindow *self, gboolean sensitive)
{
VirtViewerWindowPrivate *priv;
GtkWidget *menu;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
priv = self->priv;
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-preferences"));
gtk_widget_set_sensitive(menu, sensitive);
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-file-screenshot"));
gtk_widget_set_sensitive(menu, sensitive);
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-view-zoom"));
gtk_widget_set_sensitive(menu, sensitive);
menu = GTK_WIDGET(gtk_builder_get_object(priv->builder, "menu-send"));
gtk_widget_set_sensitive(menu, sensitive);
}
static void
display_show_hint(VirtViewerDisplay *display,
GParamSpec *pspec G_GNUC_UNUSED,
VirtViewerWindow *self)
{
guint hint;
g_object_get(display, "show-hint", &hint, NULL);
hint = (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY);
if (!self->priv->initial_zoom_set && hint && virt_viewer_display_get_enabled(display)) {
self->priv->initial_zoom_set = TRUE;
virt_viewer_window_set_zoom_level(self, self->priv->zoomlevel);
}
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-file-screenshot")), hint);
}
static gboolean
window_key_pressed (GtkWidget *widget G_GNUC_UNUSED,
GdkEvent *event,
GtkWidget *display)
{
gtk_widget_grab_focus(display);
return gtk_widget_event(display, event);
}
void
virt_viewer_window_set_display(VirtViewerWindow *self, VirtViewerDisplay *display)
{
VirtViewerWindowPrivate *priv;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
g_return_if_fail(display == NULL || VIRT_VIEWER_IS_DISPLAY(display));
priv = self->priv;
if (priv->display) {
gtk_notebook_remove_page(GTK_NOTEBOOK(priv->notebook), 1);
g_object_unref(priv->display);
priv->display = NULL;
}
if (display != NULL) {
priv->display = g_object_ref(display);
virt_viewer_display_set_monitor(VIRT_VIEWER_DISPLAY(priv->display), priv->fullscreen_monitor);
virt_viewer_display_set_fullscreen(VIRT_VIEWER_DISPLAY(priv->display), priv->fullscreen);
gtk_widget_show_all(GTK_WIDGET(display));
gtk_notebook_append_page(GTK_NOTEBOOK(priv->notebook), GTK_WIDGET(display), NULL);
gtk_widget_realize(GTK_WIDGET(display));
virt_viewer_signal_connect_object(priv->window, "key-press-event",
G_CALLBACK(window_key_pressed), display, 0);
/* switch back to non-display if not ready */
if (!(virt_viewer_display_get_show_hint(display) &
VIRT_VIEWER_DISPLAY_SHOW_HINT_READY))
gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), 0);
virt_viewer_signal_connect_object(display, "display-pointer-grab",
G_CALLBACK(virt_viewer_window_pointer_grab), self, 0);
virt_viewer_signal_connect_object(display, "display-pointer-ungrab",
G_CALLBACK(virt_viewer_window_pointer_ungrab), self, 0);
virt_viewer_signal_connect_object(display, "display-keyboard-grab",
G_CALLBACK(virt_viewer_window_keyboard_grab), self, 0);
virt_viewer_signal_connect_object(display, "display-keyboard-ungrab",
G_CALLBACK(virt_viewer_window_keyboard_ungrab), self, 0);
virt_viewer_signal_connect_object(display, "display-desktop-resize",
G_CALLBACK(virt_viewer_window_desktop_resize), self, 0);
virt_viewer_signal_connect_object(display, "notify::show-hint",
G_CALLBACK(display_show_hint), self, 0);
display_show_hint(display, NULL, self);
if (virt_viewer_display_get_enabled(display))
virt_viewer_window_desktop_resize(display, self);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-preferences")), TRUE);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-view-zoom")), TRUE);
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(self->priv->builder, "menu-send")), TRUE);
gtk_widget_set_sensitive(self->priv->toolbar_send_key, TRUE);
}
}
static void
virt_viewer_window_enable_kiosk(VirtViewerWindow *self)
{
VirtViewerWindowPrivate *priv;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
priv = self->priv;
ViewOvBox_SetOver(VIEW_OV_BOX(priv->layout), gtk_drawing_area_new());
ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(priv->layout), FALSE);
ViewAutoDrawer_SetOverlapPixels(VIEW_AUTODRAWER(priv->layout), 0);
/* You probably also want X11 Option "DontVTSwitch" "true" */
/* and perhaps more distro/desktop-specific options */
virt_viewer_window_disable_modifiers(self);
}
void
virt_viewer_window_show(VirtViewerWindow *self)
{
if (self->priv->display && !virt_viewer_display_get_enabled(self->priv->display))
virt_viewer_display_enable(self->priv->display);
if (self->priv->desktop_resize_pending) {
virt_viewer_window_queue_resize(self);
self->priv->desktop_resize_pending = FALSE;
}
gtk_widget_show(self->priv->window);
if (self->priv->kiosk)
virt_viewer_window_enable_kiosk(self);
if (self->priv->fullscreen)
virt_viewer_window_move_to_monitor(self);
}
void
virt_viewer_window_hide(VirtViewerWindow *self)
{
if (self->priv->kiosk) {
g_warning("Can't hide windows in kiosk mode");
return;
}
gtk_widget_hide(self->priv->window);
if (self->priv->display) {
VirtViewerDisplay *display = self->priv->display;
virt_viewer_display_disable(display);
}
}
void
virt_viewer_window_set_zoom_level(VirtViewerWindow *self, gint zoom_level)
{
VirtViewerWindowPrivate *priv;
gint min_zoom;
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
priv = self->priv;
if (zoom_level < MIN_ZOOM_LEVEL)
zoom_level = MIN_ZOOM_LEVEL;
if (zoom_level > MAX_ZOOM_LEVEL)
zoom_level = MAX_ZOOM_LEVEL;
priv->zoomlevel = zoom_level;
if (!priv->display)
return;
min_zoom = virt_viewer_window_get_minimal_zoom_level(self);
if (min_zoom > priv->zoomlevel) {
g_debug("Cannot set zoom level %d, using %d", priv->zoomlevel, min_zoom);
priv->zoomlevel = min_zoom;
}
if (priv->zoomlevel == virt_viewer_display_get_zoom_level(priv->display) &&
priv->zoomlevel == virt_viewer_window_get_real_zoom_level(self)) {
g_debug("Zoom level not changed, using: %d", priv->zoomlevel);
return;
}
virt_viewer_display_set_zoom_level(VIRT_VIEWER_DISPLAY(priv->display), priv->zoomlevel);
virt_viewer_window_queue_resize(self);
}
gint virt_viewer_window_get_zoom_level(VirtViewerWindow *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NORMAL_ZOOM_LEVEL);
return self->priv->zoomlevel;
}
GtkMenuItem*
virt_viewer_window_get_menu_displays(VirtViewerWindow *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
return GTK_MENU_ITEM(gtk_builder_get_object(self->priv->builder, "menu-displays"));
}
GtkBuilder*
virt_viewer_window_get_builder(VirtViewerWindow *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
return self->priv->builder;
}
VirtViewerDisplay*
virt_viewer_window_get_display(VirtViewerWindow *self)
{
g_return_val_if_fail(VIRT_VIEWER_WINDOW(self), NULL);
return self->priv->display;
}
void
virt_viewer_window_set_kiosk(VirtViewerWindow *self, gboolean enabled)
{
g_return_if_fail(VIRT_VIEWER_IS_WINDOW(self));
g_return_if_fail(enabled == !!enabled);
if (self->priv->kiosk == enabled)
return;
self->priv->kiosk = enabled;
if (enabled)
virt_viewer_window_enable_kiosk(self);
else
g_debug("disabling kiosk not implemented yet");
}
static void
virt_viewer_window_get_minimal_dimensions(VirtViewerWindow *self,
guint *width,
guint *height)
{
GtkRequisition req;
GtkWidget *top_menu;
top_menu = GTK_WIDGET(gtk_builder_get_object(virt_viewer_window_get_builder(self), "top-menu"));
#if !GTK_CHECK_VERSION(3, 0, 0)
gtk_widget_get_child_requisition(top_menu, &req);
#else
gtk_widget_get_preferred_size(top_menu, &req, NULL);
#endif
/* minimal dimensions of the window are the maximum of dimensions of the top-menu
* and minimal dimension of the display
*/
*height = MIN_DISPLAY_HEIGHT;
*width = MAX(MIN_DISPLAY_WIDTH, req.width);
}
/**
* virt_viewer_window_get_minimal_zoom_level:
* @self: a #VirtViewerWindow
*
* Calculates the zoom level with respect to the desktop dimensions
*
* Returns: minimal possible zoom level (multiple of ZOOM_STEP)
*/
static gint
virt_viewer_window_get_minimal_zoom_level(VirtViewerWindow *self)
{
guint min_width, min_height;
guint width, height; /* desktop dimensions */
gint zoom;
double width_ratio, height_ratio;
g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self) &&
self->priv->display != NULL, MIN_ZOOM_LEVEL);
virt_viewer_window_get_minimal_dimensions(self, &min_width, &min_height);
virt_viewer_display_get_desktop_size(virt_viewer_window_get_display(self), &width, &height);
/* e.g. minimal width = 200, desktop width = 550 => width ratio = 0.36
* which means that the minimal zoom level is 40 (4 * ZOOM_STEP)
*/
width_ratio = (double) min_width / width;
height_ratio = (double) min_height / height;
zoom = ceil(10 * MAX(width_ratio, height_ratio));
/* make sure that the returned zoom level is in the range from MIN_ZOOM_LEVEL to NORMAL_ZOOM_LEVEL */
return CLAMP(zoom * ZOOM_STEP, MIN_ZOOM_LEVEL, NORMAL_ZOOM_LEVEL);
}
/*
* Local variables:
* c-indent-level: 4
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/