virt-viewer/src/virt-viewer-app.c
Christophe Fergeau 2f890cf2f5 Hide all windows on disconnection
When starting remote-viewer without argument, we are showing a
window where the user can enter connection details. We then
go on to try and connect to the URI the user specified, and if
the connection fails, we disconnect from the remote server, and then
we show again the connection window so that the user can correct the
URI if he entered it wrong.
However, when this happens, the window for the previous connection
will still be visible even if connection failed. To avoid this,
this commit makes sure we hide all windows when we get a disconnection
event.

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1024309
2013-11-13 11:29:05 +01:00

2208 lines
68 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 <libxml/xpath.h>
#include <libxml/uri.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include "virt-gtk-compat.h"
#include "virt-viewer-app.h"
#include "virt-viewer-auth.h"
#include "virt-viewer-window.h"
#include "virt-viewer-session.h"
#ifdef HAVE_GTK_VNC
#include "virt-viewer-session-vnc.h"
#endif
#ifdef HAVE_SPICE_GTK
#include "virt-viewer-session-spice.h"
#endif
gboolean doDebug = FALSE;
/* Signal handlers for about dialog */
void virt_viewer_app_about_close(GtkWidget *dialog, VirtViewerApp *self);
void virt_viewer_app_about_delete(GtkWidget *dialog, void *dummy, VirtViewerApp *self);
/* Internal methods */
static void virt_viewer_app_connected(VirtViewerSession *session,
VirtViewerApp *self);
static void virt_viewer_app_initialized(VirtViewerSession *session,
VirtViewerApp *self);
static void virt_viewer_app_disconnected(VirtViewerSession *session,
VirtViewerApp *self);
static void virt_viewer_app_auth_refused(VirtViewerSession *session,
const char *msg,
VirtViewerApp *self);
static void virt_viewer_app_auth_failed(VirtViewerSession *session,
const char *msg,
VirtViewerApp *self);
static void virt_viewer_app_usb_failed(VirtViewerSession *session,
const char *msg,
VirtViewerApp *self);
static void virt_viewer_app_server_cut_text(VirtViewerSession *session,
const gchar *text,
VirtViewerApp *self);
static void virt_viewer_app_bell(VirtViewerSession *session,
VirtViewerApp *self);
static void virt_viewer_app_cancelled(VirtViewerSession *session,
VirtViewerApp *self);
static void virt_viewer_app_channel_open(VirtViewerSession *session,
VirtViewerSessionChannel *channel,
VirtViewerApp *self);
static void virt_viewer_app_update_pretty_address(VirtViewerApp *self);
static void virt_viewer_app_set_fullscreen(VirtViewerApp *self, gboolean fullscreen);
static void virt_viewer_app_update_menu_displays(VirtViewerApp *self);
static void virt_viewer_update_smartcard_accels(VirtViewerApp *self);
struct _VirtViewerAppPrivate {
VirtViewerWindow *main_window;
GtkWidget *main_notebook;
GHashTable *windows;
gchar *clipboard;
gboolean direct;
gboolean verbose;
gboolean enable_accel;
gboolean authretry;
gboolean started;
gboolean fullscreen;
gboolean attach;
gboolean quitting;
gboolean kiosk;
VirtViewerSession *session;
gboolean active;
gboolean connected;
gboolean cancelled;
guint reconnect_poll; /* source id */
char *unixsock;
char *guri; /* prefered over ghost:gport */
char *ghost;
char *gport;
char *gtlsport;
char *host; /* ssh */
int port;/* ssh */
char *user; /* ssh */
char *transport;
char *pretty_address;
gchar *guest_name;
gboolean grabbed;
char *title;
gint focused;
GKeyFile *config;
gchar *config_file;
guint insert_smartcard_accel_key;
GdkModifierType insert_smartcard_accel_mods;
guint remove_smartcard_accel_key;
GdkModifierType remove_smartcard_accel_mods;
gboolean quit_on_disconnect;
};
G_DEFINE_ABSTRACT_TYPE(VirtViewerApp, virt_viewer_app, G_TYPE_OBJECT)
#define GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), VIRT_VIEWER_TYPE_APP, VirtViewerAppPrivate))
enum {
PROP_0,
PROP_VERBOSE,
PROP_SESSION,
PROP_GUEST_NAME,
PROP_GURI,
PROP_FULLSCREEN,
PROP_TITLE,
PROP_ENABLE_ACCEL,
PROP_HAS_FOCUS,
PROP_KIOSK,
PROP_QUIT_ON_DISCONNECT,
};
enum {
SIGNAL_WINDOW_ADDED,
SIGNAL_WINDOW_REMOVED,
SIGNAL_LAST,
};
static guint signals[SIGNAL_LAST];
void
virt_viewer_app_set_debug(gboolean debug)
{
#if GLIB_CHECK_VERSION(2, 31, 0)
if (debug) {
const gchar *doms = g_getenv("G_MESSAGES_DEBUG");
if (!doms) {
g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1);
} else if (!g_str_equal(doms, "all") &&
!strstr(doms, G_LOG_DOMAIN)) {
gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN);
g_setenv("G_MESSAGES_DEBUG", newdoms, 1);
g_free(newdoms);
}
}
#endif
doDebug = debug;
}
void
virt_viewer_app_simple_message_dialog(VirtViewerApp *self,
const char *fmt, ...)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
GtkWindow *window = GTK_WINDOW(virt_viewer_window_get_window(self->priv->main_window));
GtkWidget *dialog;
char *msg;
va_list vargs;
va_start(vargs, fmt);
msg = g_strdup_vprintf(fmt, vargs);
va_end(vargs);
dialog = gtk_message_dialog_new(window,
GTK_DIALOG_MODAL |
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"%s",
msg);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_free(msg);
}
static void
virt_viewer_app_save_config(VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
GError *error = NULL;
gchar *dir, *data;
dir = g_path_get_dirname(priv->config_file);
if (g_mkdir_with_parents(dir, S_IRWXU) == -1)
g_warning("failed to create config directory");
g_free(dir);
if ((data = g_key_file_to_data(priv->config, NULL, &error)) == NULL ||
!g_file_set_contents(priv->config_file, data, -1, &error)) {
g_warning("Couldn't save configuration: %s", error->message);
g_clear_error(&error);
}
g_free(data);
}
static void
virt_viewer_app_quit(VirtViewerApp *self)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
VirtViewerAppPrivate *priv = self->priv;
if (self->priv->kiosk) {
g_warning("The app is in kiosk mode and can't quit");
return;
}
virt_viewer_app_save_config(self);
if (priv->session) {
virt_viewer_session_close(VIRT_VIEWER_SESSION(priv->session));
if (priv->connected) {
priv->quitting = TRUE;
return;
}
}
gtk_main_quit();
}
void
virt_viewer_app_maybe_quit(VirtViewerApp *self, VirtViewerWindow *window)
{
GError *error = NULL;
gboolean ask = g_key_file_get_boolean(self->priv->config,
"virt-viewer", "ask-quit", &error);
if (error) {
ask = TRUE;
g_clear_error(&error);
}
if (ask) {
GtkWidget *dialog =
gtk_message_dialog_new (virt_viewer_window_get_window(window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_OK_CANCEL,
_("Do you want to close the session?"));
GtkWidget *check = gtk_check_button_new_with_label(_("Do not ask me again"));
gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), check);
gtk_widget_show(check);
gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
gboolean dont_ask = FALSE;
g_object_get(check, "active", &dont_ask, NULL);
if (dont_ask)
g_key_file_set_boolean(self->priv->config,
"virt-viewer", "ask-quit", FALSE);
gtk_widget_destroy(dialog);
switch (result) {
case GTK_RESPONSE_OK:
virt_viewer_app_quit(self);
break;
default:
break;
}
} else {
virt_viewer_app_quit(self);
}
}
static void count_window_visible(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
GtkWindow *win = virt_viewer_window_get_window(VIRT_VIEWER_WINDOW(value));
guint *n = (guint*)user_data;
if (gtk_widget_get_visible(GTK_WIDGET(win)))
*n += 1;
}
static guint
virt_viewer_app_get_n_windows_visible(VirtViewerApp *self)
{
guint n = 0;
g_hash_table_foreach(self->priv->windows, count_window_visible, &n);
return n;
}
gboolean
virt_viewer_app_window_set_visible(VirtViewerApp *self,
VirtViewerWindow *window,
gboolean visible)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(window), FALSE);
if (visible) {
virt_viewer_window_show(window);
return TRUE;
} else {
if (virt_viewer_app_get_n_windows_visible(self) > 1) {
virt_viewer_window_hide(window);
return FALSE;
}
virt_viewer_app_maybe_quit(self, window);
}
g_warn_if_reached();
return FALSE;
}
static void hide_one_window(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data G_GNUC_UNUSED)
{
virt_viewer_window_hide(VIRT_VIEWER_WINDOW(value));
}
static void
virt_viewer_app_hide_all_windows(VirtViewerApp *app)
{
g_hash_table_foreach(app->priv->windows, hide_one_window, NULL);
}
G_MODULE_EXPORT void
virt_viewer_app_about_close(GtkWidget *dialog,
VirtViewerApp *self G_GNUC_UNUSED)
{
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog);
}
G_MODULE_EXPORT void
virt_viewer_app_about_delete(GtkWidget *dialog,
void *dummy G_GNUC_UNUSED,
VirtViewerApp *self G_GNUC_UNUSED)
{
gtk_widget_hide(dialog);
gtk_widget_destroy(dialog);
}
#if defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK)
static int
virt_viewer_app_open_tunnel(const char **cmd)
{
int fd[2];
pid_t pid;
if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0)
return -1;
pid = fork();
if (pid == -1) {
close(fd[0]);
close(fd[1]);
return -1;
}
if (pid == 0) { /* child */
close(fd[0]);
close(0);
close(1);
if (dup(fd[1]) < 0)
_exit(1);
if (dup(fd[1]) < 0)
_exit(1);
close(fd[1]);
execvp("ssh", (char *const*)cmd);
_exit(1);
}
close(fd[1]);
return fd[0];
}
static int
virt_viewer_app_open_tunnel_ssh(const char *sshhost,
int sshport,
const char *sshuser,
const char *host,
const char *port,
const char *unixsock)
{
const char *cmd[10];
char portstr[50];
int n = 0;
cmd[n++] = "ssh";
if (sshport) {
cmd[n++] = "-p";
sprintf(portstr, "%d", sshport);
cmd[n++] = portstr;
}
if (sshuser) {
cmd[n++] = "-l";
cmd[n++] = sshuser;
}
cmd[n++] = sshhost;
cmd[n++] = "nc";
if (port) {
cmd[n++] = host;
cmd[n++] = port;
} else {
cmd[n++] = "-U";
cmd[n++] = unixsock;
}
cmd[n++] = NULL;
return virt_viewer_app_open_tunnel(cmd);
}
static int
virt_viewer_app_open_unix_sock(const char *unixsock)
{
struct sockaddr_un addr;
int fd;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, unixsock);
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return -1;
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
close(fd);
return -1;
}
return fd;
}
#endif /* defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK) */
void
virt_viewer_app_trace(VirtViewerApp *self,
const char *fmt, ...)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
va_list ap;
VirtViewerAppPrivate *priv = self->priv;
if (doDebug) {
va_start(ap, fmt);
g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fmt, ap);
va_end(ap);
}
if (priv->verbose) {
va_start(ap, fmt);
g_vprintf(fmt, ap);
va_end(ap);
g_print("\n");
}
}
static void
virt_viewer_app_set_window_subtitle(VirtViewerApp *app,
VirtViewerWindow *window,
int nth)
{
gchar *subtitle = NULL;
if (app->priv->title != NULL) {
gchar *d = strstr(app->priv->title, "%d");
if (d != NULL) {
*d = '\0';
subtitle = g_strdup_printf("%s%d%s", app->priv->title, nth + 1, d + 2);
*d = '%';
} else
subtitle = g_strdup_printf("%s (%d)", app->priv->title, nth + 1);
}
g_object_set(window, "subtitle", subtitle, NULL);
g_free(subtitle);
}
static void
set_title(gpointer key,
gpointer value,
gpointer user_data)
{
gint *nth = key;
VirtViewerApp *app = user_data;
VirtViewerWindow *window = value;
virt_viewer_app_set_window_subtitle(app, window, *nth);
}
static void
virt_viewer_app_set_all_window_subtitles(VirtViewerApp *app)
{
g_hash_table_foreach(app->priv->windows, set_title, app);
}
static void update_title(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data G_GNUC_UNUSED)
{
virt_viewer_window_update_title(VIRT_VIEWER_WINDOW(value));
}
static void
virt_viewer_app_update_title(VirtViewerApp *self)
{
g_hash_table_foreach(self->priv->windows, update_title, NULL);
}
static void set_usb_options_sensitive(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
virt_viewer_window_set_usb_options_sensitive(VIRT_VIEWER_WINDOW(value),
GPOINTER_TO_INT(user_data));
}
static void
virt_viewer_app_set_usb_options_sensitive(VirtViewerApp *self, gboolean sensitive)
{
g_hash_table_foreach(self->priv->windows, set_usb_options_sensitive,
GINT_TO_POINTER(sensitive));
}
static VirtViewerWindow *
virt_viewer_app_get_nth_window(VirtViewerApp *self, gint nth)
{
return g_hash_table_lookup(self->priv->windows, &nth);
}
static gboolean
virt_viewer_app_remove_nth_window(VirtViewerApp *self, gint nth)
{
VirtViewerWindow *win;
gboolean removed;
g_return_val_if_fail(nth != 0, FALSE);
win = virt_viewer_app_get_nth_window(self, nth);
g_return_val_if_fail(win != NULL, FALSE);
DEBUG_LOG("Remove window %d %p", nth, win);
removed = g_hash_table_steal(self->priv->windows, &nth);
g_warn_if_fail(removed);
virt_viewer_app_update_menu_displays(self);
if (removed)
g_signal_emit(self, signals[SIGNAL_WINDOW_REMOVED], 0, win);
g_object_unref(win);
return removed;
}
static void
virt_viewer_app_set_nth_window(VirtViewerApp *self, gint nth, VirtViewerWindow *win)
{
gint *key;
g_return_if_fail(virt_viewer_app_get_nth_window(self, nth) == NULL);
key = g_malloc(sizeof(gint));
*key = nth;
DEBUG_LOG("Insert window %d %p", nth, win);
g_hash_table_insert(self->priv->windows, key, win);
virt_viewer_app_set_window_subtitle(self, win, nth);
virt_viewer_app_update_menu_displays(self);
if (self->priv->session) {
virt_viewer_window_set_usb_options_sensitive(win,
virt_viewer_session_get_has_usbredir(self->priv->session));
}
g_signal_emit(self, signals[SIGNAL_WINDOW_ADDED], 0, win);
}
static void
viewer_window_visible_cb(GtkWidget *widget G_GNUC_UNUSED,
gpointer user_data)
{
virt_viewer_app_update_menu_displays(VIRT_VIEWER_APP(user_data));
}
static gboolean
viewer_window_focus_in_cb(GtkWindow *window G_GNUC_UNUSED,
GdkEvent *event G_GNUC_UNUSED,
VirtViewerApp *self)
{
self->priv->focused += 1;
if (self->priv->focused == 1)
g_object_notify(G_OBJECT(self), "has-focus");
return FALSE;
}
static gboolean
viewer_window_focus_out_cb(GtkWindow *window G_GNUC_UNUSED,
GdkEvent *event G_GNUC_UNUSED,
VirtViewerApp *self)
{
self->priv->focused -= 1;
g_warn_if_fail(self->priv->focused >= 0);
if (self->priv->focused <= 0)
g_object_notify(G_OBJECT(self), "has-focus");
return FALSE;
}
static void
app_window_try_fullscreen(VirtViewerApp *self G_GNUC_UNUSED,
VirtViewerWindow *win, gint nth)
{
GdkScreen *screen = gdk_screen_get_default();
if (nth >= gdk_screen_get_n_monitors(screen)) {
DEBUG_LOG("skipping display %d", nth);
return;
}
virt_viewer_window_enter_fullscreen(win, nth);
}
static VirtViewerWindow*
virt_viewer_app_window_new(VirtViewerApp *self, gint nth)
{
VirtViewerWindow* window;
GtkWindow *w;
window = virt_viewer_app_get_nth_window(self, nth);
if (window)
return window;
window = g_object_new(VIRT_VIEWER_TYPE_WINDOW, "app", self, NULL);
virt_viewer_window_set_kiosk(window, self->priv->kiosk);
if (self->priv->main_window)
virt_viewer_window_set_zoom_level(window, virt_viewer_window_get_zoom_level(self->priv->main_window));
virt_viewer_app_set_nth_window(self, nth, window);
if (self->priv->fullscreen)
app_window_try_fullscreen(self, window, nth);
w = virt_viewer_window_get_window(window);
g_signal_connect(w, "hide", G_CALLBACK(viewer_window_visible_cb), self);
g_signal_connect(w, "show", G_CALLBACK(viewer_window_visible_cb), self);
g_signal_connect(w, "focus-in-event", G_CALLBACK(viewer_window_focus_in_cb), self);
g_signal_connect(w, "focus-out-event", G_CALLBACK(viewer_window_focus_out_cb), self);
return window;
}
static void
display_show_hint(VirtViewerDisplay *display,
GParamSpec *pspec G_GNUC_UNUSED,
VirtViewerWindow *win)
{
VirtViewerApp *self;
VirtViewerNotebook *nb = virt_viewer_window_get_notebook(win);
gint nth;
guint hint;
g_object_get(win,
"app", &self,
NULL);
g_object_get(display,
"nth-display", &nth,
"show-hint", &hint,
NULL);
if (self->priv->fullscreen &&
nth >= gdk_screen_get_n_monitors(gdk_screen_get_default())) {
virt_viewer_window_hide(win);
} else if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_DISABLED) {
virt_viewer_window_hide(win);
} else if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY) {
virt_viewer_notebook_show_display(nb);
virt_viewer_window_show(win);
} else {
if (win != self->priv->main_window &&
g_getenv("VIRT_VIEWER_HIDE"))
virt_viewer_window_hide(win);
if (!self->priv->kiosk)
virt_viewer_notebook_show_status(nb, _("Waiting for display %d..."), nth + 1);
}
virt_viewer_app_update_menu_displays(self);
g_object_unref(self);
}
static void
virt_viewer_app_display_added(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerDisplay *display,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
VirtViewerWindow *window;
gint nth;
g_object_get(display, "nth-display", &nth, NULL);
if (nth == 0) {
window = priv->main_window;
} else {
window = virt_viewer_app_get_nth_window(self, nth);
if (window == NULL) {
if (priv->kiosk) {
/* don't show extra monitors that don't fit on client */
g_debug("kiosk mode: skip extra monitors that don't fit on client");
g_object_unref(display);
return;
}
window = virt_viewer_app_window_new(self, nth);
}
}
virt_viewer_window_set_display(window, display);
virt_viewer_app_update_menu_displays(self);
virt_viewer_signal_connect_object(display, "notify::show-hint",
G_CALLBACK(display_show_hint), window, 0);
g_object_notify(G_OBJECT(display), "show-hint"); /* call display_show_hint */
}
static void
virt_viewer_app_display_removed(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerDisplay *display,
VirtViewerApp *self)
{
VirtViewerWindow *win = NULL;
gint nth;
gtk_widget_hide(GTK_WIDGET(display));
g_object_get(display, "nth-display", &nth, NULL);
win = virt_viewer_app_get_nth_window(self, nth);
virt_viewer_window_set_display(win, NULL);
if (nth != 0)
virt_viewer_app_remove_nth_window(self, nth);
}
static void
virt_viewer_app_display_updated(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerApp *self)
{
virt_viewer_app_update_menu_displays(self);
}
static void
virt_viewer_app_has_usbredir_updated(VirtViewerSession *session,
GParamSpec *pspec G_GNUC_UNUSED,
VirtViewerApp *self)
{
virt_viewer_app_set_usb_options_sensitive(self,
virt_viewer_session_get_has_usbredir(session));
}
static void notify_software_reader_cb(GObject *gobject G_GNUC_UNUSED,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data)
{
virt_viewer_update_smartcard_accels(VIRT_VIEWER_APP(user_data));
}
int
virt_viewer_app_create_session(VirtViewerApp *self, const gchar *type)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), -1);
VirtViewerAppPrivate *priv = self->priv;
g_return_val_if_fail(priv->session == NULL, -1);
g_return_val_if_fail(type != NULL, -1);
#ifdef HAVE_GTK_VNC
if (g_ascii_strcasecmp(type, "vnc") == 0) {
GtkWindow *window = virt_viewer_window_get_window(priv->main_window);
virt_viewer_app_trace(self, "Guest %s has a %s display",
priv->guest_name, type);
priv->session = virt_viewer_session_vnc_new(self, window);
} else
#endif
#ifdef HAVE_SPICE_GTK
if (g_ascii_strcasecmp(type, "spice") == 0) {
GtkWindow *window = virt_viewer_window_get_window(priv->main_window);
virt_viewer_app_trace(self, "Guest %s has a %s display",
priv->guest_name, type);
priv->session = virt_viewer_session_spice_new(self, window);
} else
#endif
{
virt_viewer_app_trace(self, "Guest %s has unsupported %s display type",
priv->guest_name, type);
virt_viewer_app_simple_message_dialog(self, _("Unknown graphic type for the guest %s"),
priv->guest_name);
return -1;
}
g_signal_connect(priv->session, "session-initialized",
G_CALLBACK(virt_viewer_app_initialized), self);
g_signal_connect(priv->session, "session-connected",
G_CALLBACK(virt_viewer_app_connected), self);
g_signal_connect(priv->session, "session-disconnected",
G_CALLBACK(virt_viewer_app_disconnected), self);
g_signal_connect(priv->session, "session-channel-open",
G_CALLBACK(virt_viewer_app_channel_open), self);
g_signal_connect(priv->session, "session-auth-refused",
G_CALLBACK(virt_viewer_app_auth_refused), self);
g_signal_connect(priv->session, "session-auth-failed",
G_CALLBACK(virt_viewer_app_auth_failed), self);
g_signal_connect(priv->session, "session-usb-failed",
G_CALLBACK(virt_viewer_app_usb_failed), self);
g_signal_connect(priv->session, "session-display-added",
G_CALLBACK(virt_viewer_app_display_added), self);
g_signal_connect(priv->session, "session-display-removed",
G_CALLBACK(virt_viewer_app_display_removed), self);
g_signal_connect(priv->session, "session-display-updated",
G_CALLBACK(virt_viewer_app_display_updated), self);
g_signal_connect(priv->session, "notify::has-usbredir",
G_CALLBACK(virt_viewer_app_has_usbredir_updated), self);
g_signal_connect(priv->session, "session-cut-text",
G_CALLBACK(virt_viewer_app_server_cut_text), self);
g_signal_connect(priv->session, "session-bell",
G_CALLBACK(virt_viewer_app_bell), self);
g_signal_connect(priv->session, "session-cancelled",
G_CALLBACK(virt_viewer_app_cancelled), self);
g_signal_connect(priv->session, "notify::software-smartcard-reader",
(GCallback)notify_software_reader_cb, self);
return 0;
}
static gboolean
virt_viewer_app_default_open_connection(VirtViewerApp *self G_GNUC_UNUSED, int *fd)
{
*fd = -1;
return TRUE;
}
static int
virt_viewer_app_open_connection(VirtViewerApp *self, int *fd)
{
VirtViewerAppClass *klass;
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), -1);
klass = VIRT_VIEWER_APP_GET_CLASS(self);
return klass->open_connection(self, fd);
}
#if defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK)
static void
virt_viewer_app_channel_open(VirtViewerSession *session,
VirtViewerSessionChannel *channel,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv;
int fd = -1;
g_return_if_fail(self != NULL);
if (!virt_viewer_app_open_connection(self, &fd))
return;
DEBUG_LOG("After open connection callback fd=%d", fd);
priv = self->priv;
if (priv->transport && g_ascii_strcasecmp(priv->transport, "ssh") == 0 &&
!priv->direct && fd == -1) {
if ((fd = virt_viewer_app_open_tunnel_ssh(priv->host, priv->port, priv->user,
priv->ghost, priv->gport, NULL)) < 0)
virt_viewer_app_simple_message_dialog(self, _("Connect to ssh failed."));
} else if (fd == -1) {
virt_viewer_app_simple_message_dialog(self, _("Can't connect to channel, SSH only supported."));
}
if (fd >= 0)
virt_viewer_session_channel_open_fd(session, channel, fd);
}
#else
static void
virt_viewer_app_channel_open(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerSessionChannel *channel G_GNUC_UNUSED,
VirtViewerApp *self)
{
virt_viewer_app_simple_message_dialog(self, _("Connect to channel unsupported."));
}
#endif
static gboolean
virt_viewer_app_default_activate(VirtViewerApp *self, GError **error)
{
VirtViewerAppPrivate *priv = self->priv;
int fd = -1;
if (!virt_viewer_app_open_connection(self, &fd))
return FALSE;
DEBUG_LOG("After open connection callback fd=%d", fd);
#if defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK)
if (priv->transport &&
g_ascii_strcasecmp(priv->transport, "ssh") == 0 &&
!priv->direct &&
fd == -1) {
gchar *p = NULL;
if (priv->gport) {
virt_viewer_app_trace(self, "Opening indirect TCP connection to display at %s:%s",
priv->ghost, priv->gport);
} else {
virt_viewer_app_trace(self, "Opening indirect UNIX connection to display at %s",
priv->unixsock);
}
if (priv->port)
p = g_strdup_printf(":%d", priv->port);
virt_viewer_app_trace(self, "Setting up SSH tunnel via %s%s%s%s",
priv->user ? priv->user : "",
priv->user ? "@" : "",
priv->host, p ? p : "");
g_free(p);
if ((fd = virt_viewer_app_open_tunnel_ssh(priv->host, priv->port,
priv->user, priv->ghost,
priv->gport, priv->unixsock)) < 0)
return FALSE;
} else if (priv->unixsock && fd == -1) {
virt_viewer_app_trace(self, "Opening direct UNIX connection to display at %s",
priv->unixsock);
if ((fd = virt_viewer_app_open_unix_sock(priv->unixsock)) < 0)
return FALSE;
}
#endif
if (fd >= 0) {
return virt_viewer_session_open_fd(VIRT_VIEWER_SESSION(priv->session), fd);
} else if (priv->guri) {
virt_viewer_app_trace(self, "Opening connection to display at %s", priv->guri);
return virt_viewer_session_open_uri(VIRT_VIEWER_SESSION(priv->session), priv->guri, error);
} else {
virt_viewer_app_trace(self, "Opening direct TCP connection to display at %s:%s:%s",
priv->ghost, priv->gport, priv->gtlsport ? priv->gtlsport : "-1");
return virt_viewer_session_open_host(VIRT_VIEWER_SESSION(priv->session),
priv->ghost, priv->gport, priv->gtlsport);
}
return FALSE;
}
gboolean
virt_viewer_app_activate(VirtViewerApp *self, GError **error)
{
VirtViewerAppPrivate *priv;
gboolean ret;
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
priv = self->priv;
if (priv->active)
return FALSE;
ret = VIRT_VIEWER_APP_GET_CLASS(self)->activate(self, error);
if (ret == FALSE) {
priv->connected = FALSE;
} else {
virt_viewer_app_show_status(self, _("Connecting to graphic server"));
priv->cancelled = FALSE;
priv->active = TRUE;
}
priv->grabbed = FALSE;
virt_viewer_app_update_title(self);
return ret;
}
/* text was actually requested */
static void
virt_viewer_app_clipboard_copy(GtkClipboard *clipboard G_GNUC_UNUSED,
GtkSelectionData *data,
guint info G_GNUC_UNUSED,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
gtk_selection_data_set_text(data, priv->clipboard, -1);
}
static void
virt_viewer_app_server_cut_text(VirtViewerSession *session G_GNUC_UNUSED,
const gchar *text,
VirtViewerApp *self)
{
GtkClipboard *cb;
gsize a, b;
VirtViewerAppPrivate *priv = self->priv;
GtkTargetEntry targets[] = {
{g_strdup("UTF8_STRING"), 0, 0},
{g_strdup("COMPOUND_TEXT"), 0, 0},
{g_strdup("TEXT"), 0, 0},
{g_strdup("STRING"), 0, 0},
};
if (!text)
return;
g_free (priv->clipboard);
priv->clipboard = g_convert (text, -1, "utf-8", "iso8859-1", &a, &b, NULL);
if (priv->clipboard) {
cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_with_owner (cb,
targets,
G_N_ELEMENTS(targets),
(GtkClipboardGetFunc)virt_viewer_app_clipboard_copy,
NULL,
G_OBJECT (self));
}
}
static void virt_viewer_app_bell(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
gdk_window_beep(gtk_widget_get_window(GTK_WIDGET(virt_viewer_window_get_window(priv->main_window))));
}
static gboolean
virt_viewer_app_default_initial_connect(VirtViewerApp *self, GError **error)
{
return virt_viewer_app_activate(self, error);
}
gboolean
virt_viewer_app_initial_connect(VirtViewerApp *self, GError **error)
{
VirtViewerAppClass *klass;
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
klass = VIRT_VIEWER_APP_GET_CLASS(self);
return klass->initial_connect(self, error);
}
static gboolean
virt_viewer_app_retryauth(gpointer opaque)
{
VirtViewerApp *self = opaque;
virt_viewer_app_initial_connect(self, NULL);
return FALSE;
}
static gboolean
virt_viewer_app_connect_timer(void *opaque)
{
VirtViewerApp *self = opaque;
VirtViewerAppPrivate *priv = self->priv;
DEBUG_LOG("Connect timer fired");
if (!priv->active &&
virt_viewer_app_initial_connect(self, NULL) < 0)
gtk_main_quit();
if (priv->active) {
priv->reconnect_poll = 0;
return FALSE;
}
return TRUE;
}
void
virt_viewer_app_start_reconnect_poll(VirtViewerApp *self)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
VirtViewerAppPrivate *priv = self->priv;
DEBUG_LOG("reconnect_poll: %d", priv->reconnect_poll);
if (priv->reconnect_poll != 0)
return;
priv->reconnect_poll = g_timeout_add(500, virt_viewer_app_connect_timer, self);
}
static void
virt_viewer_app_default_deactivated(VirtViewerApp *self, gboolean connect_error)
{
VirtViewerAppPrivate *priv = self->priv;
if (!connect_error) {
virt_viewer_app_show_status(self, _("Guest domain has shutdown"));
virt_viewer_app_trace(self, "Guest %s display has disconnected, shutting down",
priv->guest_name);
}
if (self->priv->quit_on_disconnect)
gtk_main_quit();
}
static void
virt_viewer_app_deactivated(VirtViewerApp *self, gboolean connect_error)
{
VirtViewerAppClass *klass;
klass = VIRT_VIEWER_APP_GET_CLASS(self);
klass->deactivated(self, connect_error);
}
static void
virt_viewer_app_deactivate(VirtViewerApp *self, gboolean connect_error)
{
VirtViewerAppPrivate *priv = self->priv;
if (!priv->active)
return;
if (priv->session) {
virt_viewer_session_close(VIRT_VIEWER_SESSION(priv->session));
g_clear_object(&priv->session);
}
priv->connected = FALSE;
priv->active = FALSE;
priv->started = FALSE;
#if 0
g_free(priv->pretty_address);
priv->pretty_address = NULL;
#endif
priv->grabbed = FALSE;
virt_viewer_app_update_title(self);
if (priv->authretry) {
priv->authretry = FALSE;
g_idle_add(virt_viewer_app_retryauth, self);
} else
virt_viewer_app_deactivated(self, connect_error);
}
static void
virt_viewer_app_connected(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
priv->connected = TRUE;
if (self->priv->kiosk)
virt_viewer_app_show_status(self, "");
else
virt_viewer_app_show_status(self, _("Connected to graphic server"));
}
static void
virt_viewer_app_initialized(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerApp *self)
{
virt_viewer_app_update_title(self);
}
static void
virt_viewer_app_disconnected(VirtViewerSession *session G_GNUC_UNUSED,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
gboolean connect_error = !priv->connected && !priv->cancelled;
virt_viewer_app_hide_all_windows(self);
if (priv->quitting)
gtk_main_quit();
if (connect_error) {
virt_viewer_app_simple_message_dialog(self,
_("Unable to connect to the graphic server %s"),
priv->pretty_address);
}
virt_viewer_app_set_usb_options_sensitive(self, FALSE);
virt_viewer_app_deactivate(self, connect_error);
}
static void virt_viewer_app_cancelled(VirtViewerSession *session,
VirtViewerApp *self)
{
VirtViewerAppPrivate *priv = self->priv;
priv->cancelled = TRUE;
virt_viewer_app_disconnected(session, self);
}
static void virt_viewer_app_auth_refused(VirtViewerSession *session G_GNUC_UNUSED,
const char *msg,
VirtViewerApp *self)
{
GtkWidget *dialog;
int ret;
VirtViewerAppPrivate *priv = self->priv;
dialog = gtk_message_dialog_new(virt_viewer_window_get_window(priv->main_window),
GTK_DIALOG_MODAL |
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_YES_NO,
_("Unable to authenticate with remote desktop server at %s: %s\n"
"Retry connection again?"),
priv->pretty_address, msg);
ret = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
if (ret == GTK_RESPONSE_YES)
priv->authretry = TRUE;
else
priv->authretry = FALSE;
}
static void virt_viewer_app_auth_failed(VirtViewerSession *session G_GNUC_UNUSED,
const char *msg,
VirtViewerApp *self)
{
virt_viewer_app_simple_message_dialog(self,
_("Unable to authenticate with remote desktop server: %s"),
msg);
}
static void virt_viewer_app_usb_failed(VirtViewerSession *session G_GNUC_UNUSED,
const gchar *msg,
VirtViewerApp *self)
{
virt_viewer_app_simple_message_dialog(self, _("USB redirection error: %s"), msg);
}
static void
virt_viewer_app_set_kiosk(VirtViewerApp *self, gboolean enabled)
{
int i;
self->priv->kiosk = enabled;
if (enabled)
virt_viewer_app_set_fullscreen(self, enabled);
for (i = 0; i < gdk_screen_get_n_monitors(gdk_screen_get_default()); i++) {
VirtViewerWindow *win = virt_viewer_app_get_nth_window(self, i);
if (win == NULL)
win = virt_viewer_app_window_new(self, i);
if (enabled)
virt_viewer_window_show(win);
virt_viewer_window_set_kiosk(win, enabled);
}
}
static void
virt_viewer_app_get_property (GObject *object, guint property_id,
GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(object));
VirtViewerApp *self = VIRT_VIEWER_APP(object);
VirtViewerAppPrivate *priv = self->priv;
switch (property_id) {
case PROP_VERBOSE:
g_value_set_boolean(value, priv->verbose);
break;
case PROP_SESSION:
g_value_set_object(value, priv->session);
break;
case PROP_GUEST_NAME:
g_value_set_string(value, priv->guest_name);
break;
case PROP_GURI:
g_value_set_string(value, priv->guri);
break;
case PROP_FULLSCREEN:
g_value_set_boolean(value, priv->fullscreen);
break;
case PROP_TITLE:
g_value_set_string(value, priv->title);
break;
case PROP_ENABLE_ACCEL:
g_value_set_boolean(value, virt_viewer_app_get_enable_accel(self));
break;
case PROP_HAS_FOCUS:
g_value_set_boolean(value, priv->focused > 0);
break;
case PROP_KIOSK:
g_value_set_boolean(value, priv->kiosk);
break;
case PROP_QUIT_ON_DISCONNECT:
g_value_set_boolean(value, priv->quit_on_disconnect);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
virt_viewer_app_set_property (GObject *object, guint property_id,
const GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(object));
VirtViewerApp *self = VIRT_VIEWER_APP(object);
VirtViewerAppPrivate *priv = self->priv;
switch (property_id) {
case PROP_VERBOSE:
priv->verbose = g_value_get_boolean(value);
break;
case PROP_GUEST_NAME:
g_free(priv->guest_name);
priv->guest_name = g_value_dup_string(value);
break;
case PROP_GURI:
g_free(priv->guri);
priv->guri = g_value_dup_string(value);
virt_viewer_app_update_pretty_address(self);
break;
case PROP_FULLSCREEN:
virt_viewer_app_set_fullscreen(self, g_value_get_boolean(value));
break;
case PROP_TITLE:
virt_viewer_app_set_title(self, g_value_get_string(value));
break;
case PROP_ENABLE_ACCEL:
priv->enable_accel = g_value_get_boolean(value);
break;
case PROP_KIOSK:
virt_viewer_app_set_kiosk(self, g_value_get_boolean(value));
break;
case PROP_QUIT_ON_DISCONNECT:
priv->quit_on_disconnect = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
virt_viewer_app_dispose (GObject *object)
{
VirtViewerApp *self = VIRT_VIEWER_APP(object);
VirtViewerAppPrivate *priv = self->priv;
if (priv->windows) {
GHashTable *tmp = priv->windows;
/* null-ify before unrefing, because we need
* to prevent callbacks using priv->windows
* while it is being disposed off. */
priv->windows = NULL;
priv->main_window = NULL;
g_hash_table_unref(tmp);
}
g_clear_object(&priv->session);
g_free(priv->title);
priv->title = NULL;
g_free(priv->guest_name);
priv->guest_name = NULL;
g_free(priv->pretty_address);
priv->pretty_address = NULL;
g_free(priv->guri);
priv->guri = NULL;
g_free(priv->title);
priv->title = NULL;
g_free(priv->config_file);
priv->config_file = NULL;
g_clear_pointer(&priv->config, g_key_file_free);
virt_viewer_app_free_connect_info(self);
G_OBJECT_CLASS (virt_viewer_app_parent_class)->dispose (object);
}
static gboolean
virt_viewer_app_default_start(VirtViewerApp *self)
{
virt_viewer_window_show(self->priv->main_window);
return TRUE;
}
gboolean virt_viewer_app_start(VirtViewerApp *self)
{
VirtViewerAppClass *klass;
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
klass = VIRT_VIEWER_APP_GET_CLASS(self);
g_return_val_if_fail(!self->priv->started, TRUE);
self->priv->started = klass->start(self);
return self->priv->started;
}
static int opt_zoom = 100;
static gchar *opt_hotkeys = NULL;
static gboolean opt_verbose = FALSE;
static gboolean opt_debug = FALSE;
static gboolean opt_fullscreen = FALSE;
static gboolean opt_kiosk = FALSE;
static gboolean opt_kiosk_quit = FALSE;
static void
virt_viewer_app_init (VirtViewerApp *self)
{
GError *error = NULL;
gtk_window_set_default_icon_name("virt-viewer");
virt_viewer_app_set_debug(opt_debug);
self->priv = GET_PRIVATE(self);
self->priv->windows = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_object_unref);
self->priv->config = g_key_file_new();
self->priv->config_file = g_build_filename(g_get_user_config_dir(),
"virt-viewer", "settings", NULL);
g_key_file_load_from_file(self->priv->config, self->priv->config_file,
G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error);
if (error)
g_debug("Couldn't load configuration: %s", error->message);
g_clear_error(&error);
if (opt_zoom < MIN_ZOOM_LEVEL || opt_zoom > MAX_ZOOM_LEVEL) {
g_printerr(_("Zoom level must be within %d-%d\n"), MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
opt_zoom = 100;
}
self->priv->verbose = opt_verbose;
self->priv->quit_on_disconnect = opt_kiosk ? opt_kiosk_quit : TRUE;
}
static void
virt_viewer_set_insert_smartcard_accel(VirtViewerApp *self,
guint accel_key,
GdkModifierType accel_mods)
{
VirtViewerAppPrivate *priv = self->priv;
priv->insert_smartcard_accel_key = accel_key;
priv->insert_smartcard_accel_mods = accel_mods;
}
static void
virt_viewer_set_remove_smartcard_accel(VirtViewerApp *self,
guint accel_key,
GdkModifierType accel_mods)
{
VirtViewerAppPrivate *priv = self->priv;
priv->remove_smartcard_accel_key = accel_key;
priv->remove_smartcard_accel_mods = accel_mods;
}
static void
virt_viewer_update_smartcard_accels(VirtViewerApp *self)
{
gboolean sw_smartcard;
VirtViewerAppPrivate *priv = self->priv;
if (self->priv->session != NULL) {
g_object_get(G_OBJECT(self->priv->session),
"software-smartcard-reader", &sw_smartcard,
NULL);
} else {
sw_smartcard = FALSE;
}
if (sw_smartcard) {
g_warning("enabling smartcard shortcuts");
gtk_accel_map_change_entry("<virt-viewer>/file/smartcard-insert",
priv->insert_smartcard_accel_key,
priv->insert_smartcard_accel_mods,
TRUE);
gtk_accel_map_change_entry("<virt-viewer>/file/smartcard-remove",
priv->remove_smartcard_accel_key,
priv->remove_smartcard_accel_mods,
TRUE);
} else {
g_warning("disabling smartcard shortcuts");
gtk_accel_map_change_entry("<virt-viewer>/file/smartcard-insert", 0, 0, TRUE);
gtk_accel_map_change_entry("<virt-viewer>/file/smartcard-remove", 0, 0, TRUE);
}
}
static GObject *
virt_viewer_app_constructor (GType gtype,
guint n_properties,
GObjectConstructParam *properties)
{
GObject *obj;
VirtViewerApp *self;
VirtViewerAppPrivate *priv;
obj = G_OBJECT_CLASS (virt_viewer_app_parent_class)->constructor (gtype, n_properties, properties);
self = VIRT_VIEWER_APP(obj);
priv = self->priv;
priv->main_window = virt_viewer_app_window_new(self, 0);
priv->main_notebook = GTK_WIDGET(virt_viewer_window_get_notebook(priv->main_window));
virt_viewer_set_insert_smartcard_accel(self, GDK_F8, GDK_SHIFT_MASK);
virt_viewer_set_remove_smartcard_accel(self, GDK_F9, GDK_SHIFT_MASK);
gtk_accel_map_add_entry("<virt-viewer>/view/toggle-fullscreen", GDK_F11, 0);
gtk_accel_map_add_entry("<virt-viewer>/view/release-cursor", GDK_F12, GDK_SHIFT_MASK);
gtk_accel_map_add_entry("<virt-viewer>/view/zoom-reset", GDK_0, GDK_CONTROL_MASK);
gtk_accel_map_add_entry("<virt-viewer>/send/secure-attention", GDK_End, GDK_CONTROL_MASK | GDK_MOD1_MASK);
virt_viewer_window_set_zoom_level(priv->main_window, opt_zoom);
virt_viewer_app_set_fullscreen(self, opt_fullscreen);
virt_viewer_app_set_hotkeys(self, opt_hotkeys);
virt_viewer_app_set_kiosk(self, opt_kiosk);
return obj;
}
static void
virt_viewer_app_class_init (VirtViewerAppClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (VirtViewerAppPrivate));
object_class->constructor = virt_viewer_app_constructor;
object_class->get_property = virt_viewer_app_get_property;
object_class->set_property = virt_viewer_app_set_property;
object_class->dispose = virt_viewer_app_dispose;
klass->start = virt_viewer_app_default_start;
klass->initial_connect = virt_viewer_app_default_initial_connect;
klass->activate = virt_viewer_app_default_activate;
klass->deactivated = virt_viewer_app_default_deactivated;
klass->open_connection = virt_viewer_app_default_open_connection;
g_object_class_install_property(object_class,
PROP_VERBOSE,
g_param_spec_boolean("verbose",
"Verbose",
"Verbose trace",
FALSE,
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_SESSION,
g_param_spec_object("session",
"Session",
"ViewerSession",
VIRT_VIEWER_TYPE_SESSION,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_GUEST_NAME,
g_param_spec_string("guest-name",
"Guest name",
"Guest name",
"",
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_GURI,
g_param_spec_string("guri",
"guri",
"Remote graphical URI",
"",
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_FULLSCREEN,
g_param_spec_boolean("fullscreen",
"Fullscreen",
"Fullscreen",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_TITLE,
g_param_spec_string("title",
"Title",
"Title",
"",
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_ENABLE_ACCEL,
g_param_spec_boolean("enable-accel",
"Enable Accel",
"Enable accelerators",
FALSE,
G_PARAM_CONSTRUCT |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_HAS_FOCUS,
g_param_spec_boolean("has-focus",
"Has Focus",
"Application has focus",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_KIOSK,
g_param_spec_boolean("kiosk",
"Kiosk",
"Kiosk mode",
FALSE,
G_PARAM_CONSTRUCT |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property(object_class,
PROP_QUIT_ON_DISCONNECT,
g_param_spec_boolean("quit-on-disconnect",
"Quit on disconnect",
"Quit on disconnect",
TRUE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
signals[SIGNAL_WINDOW_ADDED] =
g_signal_new("window-added",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(VirtViewerAppClass, window_added),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
signals[SIGNAL_WINDOW_REMOVED] =
g_signal_new("window-removed",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(VirtViewerAppClass, window_removed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
}
const char *virt_viewer_app_get_title(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
return self->priv->title;
}
void virt_viewer_app_set_title(VirtViewerApp *self, const char *title)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
g_free(self->priv->title);
self->priv->title = g_strdup(title);
virt_viewer_app_set_all_window_subtitles(self);
}
void
virt_viewer_app_set_direct(VirtViewerApp *self, gboolean direct)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
self->priv->direct = direct;
}
void
virt_viewer_app_clear_hotkeys(VirtViewerApp *self)
{
/* Disable default bindings and replace them with our own */
gtk_accel_map_change_entry("<virt-viewer>/view/toggle-fullscreen", 0, 0, TRUE);
gtk_accel_map_change_entry("<virt-viewer>/view/release-cursor", 0, 0, TRUE);
gtk_accel_map_change_entry("<virt-viewer>/send/secure-attention", 0, 0, TRUE);
virt_viewer_set_insert_smartcard_accel(self, 0, 0);
virt_viewer_set_remove_smartcard_accel(self, 0, 0);
}
void
virt_viewer_app_set_hotkeys(VirtViewerApp *self, const gchar *hotkeys_str)
{
gchar **hotkey, **hotkeys = NULL;
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
if (hotkeys_str)
hotkeys = g_strsplit(hotkeys_str, ",", -1);
if (!hotkeys || g_strv_length(hotkeys) == 0) {
g_strfreev(hotkeys);
g_object_set(self, "enable-accel", FALSE, NULL);
return;
}
virt_viewer_app_clear_hotkeys(self);
g_object_set(self, "enable-accel", TRUE, NULL);
for (hotkey = hotkeys; *hotkey != NULL; hotkey++) {
gchar *key = strstr(*hotkey, "=");
if (key == NULL) {
g_warn_if_reached();
continue;
}
*key = '\0';
gchar *accel = spice_hotkey_to_gtk_accelerator(key + 1);
guint accel_key;
GdkModifierType accel_mods;
gtk_accelerator_parse(accel, &accel_key, &accel_mods);
g_free(accel);
if (g_str_equal(*hotkey, "toggle-fullscreen")) {
gtk_accel_map_change_entry("<virt-viewer>/view/toggle-fullscreen", accel_key, accel_mods, TRUE);
} else if (g_str_equal(*hotkey, "release-cursor")) {
gtk_accel_map_change_entry("<virt-viewer>/view/release-cursor", accel_key, accel_mods, TRUE);
} else if (g_str_equal(*hotkey, "secure-attention")) {
gtk_accel_map_change_entry("<virt-viewer>/send/secure-attention", accel_key, accel_mods, TRUE);
} else if (g_str_equal(*hotkey, "smartcard-insert")) {
virt_viewer_set_insert_smartcard_accel(self, accel_key, accel_mods);
} else if (g_str_equal(*hotkey, "smartcard-remove")) {
virt_viewer_set_remove_smartcard_accel(self, accel_key, accel_mods);
} else {
g_warning("Unknown hotkey command %s", *hotkey);
}
}
g_strfreev(hotkeys);
virt_viewer_update_smartcard_accels(self);
}
void
virt_viewer_app_set_attach(VirtViewerApp *self, gboolean attach)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
self->priv->attach = attach;
}
gboolean
virt_viewer_app_get_attach(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->attach;
}
gboolean
virt_viewer_app_is_active(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->active;
}
gboolean
virt_viewer_app_has_session(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->session != NULL;
}
static void
virt_viewer_app_update_pretty_address(VirtViewerApp *self)
{
VirtViewerAppPrivate *priv;
priv = self->priv;
g_free(priv->pretty_address);
priv->pretty_address = NULL;
if (priv->guri)
priv->pretty_address = g_strdup(priv->guri);
else if (priv->gport)
priv->pretty_address = g_strdup_printf("%s:%s", priv->ghost, priv->gport);
else if (priv->host && priv->unixsock)
priv->pretty_address = g_strdup_printf("%s:%s", priv->host, priv->unixsock);
}
typedef struct {
VirtViewerApp *app;
gboolean fullscreen;
} FullscreenOptions;
static void fullscreen_cb(gpointer key,
gpointer value,
gpointer user_data)
{
gint nth = *(gint*)key;
FullscreenOptions *options = (FullscreenOptions *)user_data;
VirtViewerWindow *vwin = VIRT_VIEWER_WINDOW(value);
DEBUG_LOG("fullscreen display %d: %d", nth, options->fullscreen);
if (options->fullscreen)
app_window_try_fullscreen(options->app, vwin, nth);
else
virt_viewer_window_leave_fullscreen(vwin);
}
gboolean
virt_viewer_app_get_fullscreen(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->fullscreen;
}
static void
virt_viewer_app_set_fullscreen(VirtViewerApp *self, gboolean fullscreen)
{
VirtViewerAppPrivate *priv = self->priv;
FullscreenOptions options = {
.app = self,
.fullscreen = fullscreen,
};
/* we iterate unconditionnaly, even if it was set before to update new windows */
priv->fullscreen = fullscreen;
g_hash_table_foreach(priv->windows, fullscreen_cb, &options);
g_object_notify(G_OBJECT(self), "fullscreen");
}
static void
menu_display_visible_toggled_cb(GtkCheckMenuItem *checkmenuitem,
VirtViewerWindow *vwin)
{
VirtViewerApp *self;
gboolean visible;
static gboolean reentering = FALSE;
if (reentering) /* do not reenter if I switch you back */
return;
reentering = TRUE;
g_object_get(vwin, "app", &self, NULL);
visible = virt_viewer_app_window_set_visible(self, vwin,
gtk_check_menu_item_get_active(checkmenuitem));
gtk_check_menu_item_set_active(checkmenuitem, /* will be toggled again */ !visible);
g_object_unref(self);
reentering = FALSE;
}
static gint
update_menu_displays_sort(gconstpointer a, gconstpointer b)
{
const int *ai = a;
const int *bi = b;
if (*ai > *bi)
return 1;
else if (*ai < *bi)
return -1;
else
return 0;
}
static GtkMenuShell *
window_empty_display_submenu(VirtViewerWindow *window)
{
/* Because of what apparently is a gtk+2 bug (rhbz#922712), we
* cannot recreate the submenu every time we need to refresh it,
* otherwise the application may get frozen with the keyboard and
* mouse grabbed if gtk_menu_item_set_submenu is called while
* the menu is displayed. Reusing the same menu every time
* works around this issue.
*/
GtkMenuItem *menu = virt_viewer_window_get_menu_displays(window);
GtkMenuShell *submenu;
submenu = GTK_MENU_SHELL(gtk_menu_item_get_submenu(menu));
if (submenu) {
GList *subitems;
GList *it;
subitems = gtk_container_get_children(GTK_CONTAINER(submenu));
for (it = subitems; it != NULL; it = it->next) {
gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(it->data));
}
g_list_free(subitems);
} else {
submenu = GTK_MENU_SHELL(gtk_menu_new());
gtk_menu_item_set_submenu(menu, GTK_WIDGET(submenu));
}
return submenu;
}
static void
window_update_menu_displays_cb(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
VirtViewerApp *self = VIRT_VIEWER_APP(user_data);
GtkMenuShell *submenu;
GList *keys = g_hash_table_get_keys(self->priv->windows);
GList *tmp;
keys = g_list_sort(keys, update_menu_displays_sort);
submenu = window_empty_display_submenu(VIRT_VIEWER_WINDOW(value));
tmp = keys;
while (tmp) {
int *nth = tmp->data;
VirtViewerWindow *vwin = VIRT_VIEWER_WINDOW(g_hash_table_lookup(self->priv->windows, nth));
VirtViewerDisplay *display = virt_viewer_window_get_display(vwin);
GtkWidget *item;
gboolean visible, sensitive;
gchar *label;
label = g_strdup_printf(_("Display %d"), *nth + 1);
item = gtk_check_menu_item_new_with_label(label);
g_free(label);
visible = gtk_widget_get_visible(GTK_WIDGET(virt_viewer_window_get_window(vwin)));
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), visible);
sensitive = visible;
if (display) {
guint hint = virt_viewer_display_get_show_hint(display);
if (hint & VIRT_VIEWER_DISPLAY_SHOW_HINT_READY)
sensitive = TRUE;
if (virt_viewer_display_get_selectable(display))
sensitive = TRUE;
}
gtk_widget_set_sensitive(item, sensitive);
g_signal_connect(G_OBJECT(item),
"toggled", G_CALLBACK(menu_display_visible_toggled_cb), vwin);
gtk_menu_shell_append(submenu, item);
tmp = tmp->next;
}
gtk_widget_show_all(GTK_WIDGET(submenu));
g_list_free(keys);
}
static void
virt_viewer_app_update_menu_displays(VirtViewerApp *self)
{
if (!self->priv->windows)
return;
g_hash_table_foreach(self->priv->windows, window_update_menu_displays_cb, self);
}
void
virt_viewer_app_set_connect_info(VirtViewerApp *self,
const gchar *host,
const gchar *ghost,
const gchar *gport,
const gchar *gtlsport,
const gchar *transport,
const gchar *unixsock,
const gchar *user,
gint port,
const gchar *guri)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
VirtViewerAppPrivate *priv = self->priv;
DEBUG_LOG("Set connect info: %s,%s,%s,%s,%s,%s,%s,%d",
host, ghost, gport, gtlsport ? gtlsport : "-1", transport, unixsock, user, port);
g_free(priv->host);
g_free(priv->ghost);
g_free(priv->gport);
g_free(priv->gtlsport);
g_free(priv->transport);
g_free(priv->unixsock);
g_free(priv->user);
g_free(priv->guri);
priv->host = g_strdup(host);
priv->ghost = g_strdup(ghost);
priv->gport = g_strdup(gport);
priv->gtlsport = g_strdup(gtlsport);
priv->transport = g_strdup(transport);
priv->unixsock = g_strdup(unixsock);
priv->user = g_strdup(user);
priv->guri = g_strdup(guri);
priv->port = port;
virt_viewer_app_update_pretty_address(self);
}
void
virt_viewer_app_free_connect_info(VirtViewerApp *self)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
virt_viewer_app_set_connect_info(self, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL);
}
VirtViewerWindow*
virt_viewer_app_get_main_window(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
return self->priv->main_window;
}
static void
show_status_cb(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
VirtViewerNotebook *nb = virt_viewer_window_get_notebook(VIRT_VIEWER_WINDOW(value));
gchar *text = (gchar*)user_data;
virt_viewer_notebook_show_status(nb, text);
}
void
virt_viewer_app_show_status(VirtViewerApp *self, const gchar *fmt, ...)
{
va_list args;
gchar *text;
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
g_return_if_fail(fmt != NULL);
va_start(args, fmt);
text = g_strdup_vprintf(fmt, args);
va_end(args);
g_hash_table_foreach(self->priv->windows, show_status_cb, text);
g_free(text);
}
static void
show_display_cb(gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data G_GNUC_UNUSED)
{
VirtViewerNotebook *nb = virt_viewer_window_get_notebook(VIRT_VIEWER_WINDOW(value));
virt_viewer_notebook_show_display(nb);
}
void
virt_viewer_app_show_display(VirtViewerApp *self)
{
g_return_if_fail(VIRT_VIEWER_IS_APP(self));
g_hash_table_foreach(self->priv->windows, show_display_cb, self);
}
gboolean
virt_viewer_app_get_enable_accel(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->enable_accel;
}
VirtViewerSession*
virt_viewer_app_get_session(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), FALSE);
return self->priv->session;
}
GHashTable*
virt_viewer_app_get_windows(VirtViewerApp *self)
{
g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
return self->priv->windows;
}
static gboolean
option_kiosk_quit(G_GNUC_UNUSED const gchar *option_name,
const gchar *value,
G_GNUC_UNUSED gpointer data, GError **error)
{
if (g_str_equal(value, "never")) {
opt_kiosk_quit = FALSE;
return TRUE;
}
if (g_str_equal(value, "on-disconnect")) {
opt_kiosk_quit = TRUE;
return TRUE;
}
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Invalid kiosk-quit argument: %s"), value);
return FALSE;
}
const GOptionEntry *
virt_viewer_app_get_options(void)
{
static const GOptionEntry options [] = {
{ "zoom", 'z', 0, G_OPTION_ARG_INT, &opt_zoom,
N_("Zoom level of window, in percentage"), "ZOOM" },
{ "full-screen", 'f', 0, G_OPTION_ARG_NONE, &opt_fullscreen,
N_("Open in full screen mode (adjusts guest resolution to fit the client)"), NULL },
{ "hotkeys", 'H', 0, G_OPTION_ARG_STRING, &opt_hotkeys,
N_("Customise hotkeys"), NULL },
{ "kiosk", 'k', 0, G_OPTION_ARG_NONE, &opt_kiosk,
N_("Enable kiosk mode"), NULL },
{ "kiosk-quit", '\0', 0, G_OPTION_ARG_CALLBACK, option_kiosk_quit,
N_("Quit on given condition in kiosk mode"), N_("<never|on-disconnect>") },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose,
N_("Display verbose information"), NULL },
{ "debug", '\0', 0, G_OPTION_ARG_NONE, &opt_debug,
N_("Display debugging information"), NULL },
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
return options;
}
/*
* Local variables:
* c-indent-level: 4
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/