mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/virt-viewer
synced 2026-01-11 16:28:01 +00:00
remote_viewer_deactivated() can be calling virt_viewer_app_start() without checking whether it returns TRUE or FALSE. It returns FALSE when it was not successful (when it failed to parse the URI to connect to for example, or whe the user presses Cancel in the connection dialog). This means that if the user starts remote-viewer, enters a valid URI in the connection dialog to which it cannot connect to (spice://example.com:999) and then presses Cancel in the connection dialog that appears after the connection failure, then remote-viewer will be sitting there with an empty window doing nothing. This commit ensures we chain to the parent class when virt_viewer_app_start() returns FALSE, which causes remote-viewer to exit.
1057 lines
34 KiB
C
1057 lines
34 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: Marc-André Lureau <marcandre.lureau@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gprintf.h>
|
|
#include <glib/gi18n.h>
|
|
#include <libxml/uri.h>
|
|
|
|
#ifdef HAVE_OVIRT
|
|
#include <govirt/govirt.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
#include <spice-controller.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
#include "virt-viewer-session-spice.h"
|
|
#endif
|
|
#include "virt-viewer-app.h"
|
|
#include "virt-viewer-auth.h"
|
|
#include "virt-viewer-file.h"
|
|
#include "virt-viewer-session.h"
|
|
#include "remote-viewer.h"
|
|
|
|
#ifndef G_VALUE_INIT /* see bug https://bugzilla.gnome.org/show_bug.cgi?id=654793 */
|
|
#define G_VALUE_INIT { 0, { { 0 } } }
|
|
#endif
|
|
|
|
struct _RemoteViewerPrivate {
|
|
#ifdef HAVE_SPICE_GTK
|
|
SpiceCtrlController *controller;
|
|
SpiceCtrlForeignMenu *ctrl_foreign_menu;
|
|
#endif
|
|
GtkWidget *controller_menu;
|
|
GtkWidget *foreign_menu;
|
|
gboolean open_recent_dialog;
|
|
|
|
gboolean default_title; /* Whether the window title was set by the user, or
|
|
is the default one (URI we are connecting to) */
|
|
|
|
};
|
|
|
|
G_DEFINE_TYPE (RemoteViewer, remote_viewer, VIRT_VIEWER_TYPE_APP)
|
|
#define GET_PRIVATE(o) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((o), REMOTE_VIEWER_TYPE, RemoteViewerPrivate))
|
|
|
|
enum {
|
|
PROP_0,
|
|
#ifdef HAVE_SPICE_GTK
|
|
PROP_CONTROLLER,
|
|
PROP_CTRL_FOREIGN_MENU,
|
|
#endif
|
|
PROP_OPEN_RECENT_DIALOG
|
|
};
|
|
|
|
static gboolean remote_viewer_start(VirtViewerApp *self);
|
|
#ifdef HAVE_SPICE_GTK
|
|
static gboolean remote_viewer_activate(VirtViewerApp *self, GError **error);
|
|
static void remote_viewer_window_added(VirtViewerApp *self, VirtViewerWindow *win);
|
|
static void spice_foreign_menu_updated(RemoteViewer *self);
|
|
static gint connect_dialog(gchar **uri);
|
|
|
|
static void
|
|
remote_viewer_dispose (GObject *object)
|
|
{
|
|
RemoteViewer *self = REMOTE_VIEWER(object);
|
|
RemoteViewerPrivate *priv = self->priv;
|
|
|
|
if (priv->controller) {
|
|
g_object_unref(priv->controller);
|
|
priv->controller = NULL;
|
|
}
|
|
|
|
if (priv->ctrl_foreign_menu) {
|
|
g_object_unref(priv->ctrl_foreign_menu);
|
|
priv->ctrl_foreign_menu = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS(remote_viewer_parent_class)->dispose (object);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
remote_viewer_get_property (GObject *object, guint property_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
RemoteViewer *self = REMOTE_VIEWER(object);
|
|
RemoteViewerPrivate *priv = self->priv;
|
|
|
|
switch (property_id) {
|
|
#ifdef HAVE_SPICE_GTK
|
|
case PROP_CONTROLLER:
|
|
g_value_set_object(value, priv->controller);
|
|
break;
|
|
case PROP_CTRL_FOREIGN_MENU:
|
|
g_value_set_object(value, priv->ctrl_foreign_menu);
|
|
break;
|
|
#endif
|
|
case PROP_OPEN_RECENT_DIALOG:
|
|
g_value_set_boolean(value, priv->open_recent_dialog);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remote_viewer_set_property (GObject *object, guint property_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
RemoteViewer *self = REMOTE_VIEWER(object);
|
|
RemoteViewerPrivate *priv = self->priv;
|
|
|
|
switch (property_id) {
|
|
#ifdef HAVE_SPICE_GTK
|
|
case PROP_CONTROLLER:
|
|
g_return_if_fail(priv->controller == NULL);
|
|
priv->controller = g_value_dup_object(value);
|
|
break;
|
|
case PROP_CTRL_FOREIGN_MENU:
|
|
g_return_if_fail(priv->ctrl_foreign_menu == NULL);
|
|
priv->ctrl_foreign_menu = g_value_dup_object(value);
|
|
break;
|
|
#endif
|
|
case PROP_OPEN_RECENT_DIALOG:
|
|
priv->open_recent_dialog = g_value_get_boolean(value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remote_viewer_deactivated(VirtViewerApp *app, gboolean connect_error)
|
|
{
|
|
RemoteViewer *self = REMOTE_VIEWER(app);
|
|
RemoteViewerPrivate *priv = self->priv;
|
|
|
|
if (connect_error && priv->open_recent_dialog) {
|
|
if (virt_viewer_app_start(app)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
VIRT_VIEWER_APP_CLASS(remote_viewer_parent_class)->deactivated(app, connect_error);
|
|
}
|
|
|
|
static void
|
|
remote_viewer_class_init (RemoteViewerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
VirtViewerAppClass *app_class = VIRT_VIEWER_APP_CLASS (klass);
|
|
|
|
g_type_class_add_private (klass, sizeof (RemoteViewerPrivate));
|
|
|
|
object_class->get_property = remote_viewer_get_property;
|
|
object_class->set_property = remote_viewer_set_property;
|
|
|
|
app_class->start = remote_viewer_start;
|
|
app_class->deactivated = remote_viewer_deactivated;
|
|
#ifdef HAVE_SPICE_GTK
|
|
object_class->dispose = remote_viewer_dispose;
|
|
app_class->activate = remote_viewer_activate;
|
|
app_class->window_added = remote_viewer_window_added;
|
|
#endif
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
g_object_class_install_property(object_class,
|
|
PROP_CONTROLLER,
|
|
g_param_spec_object("controller",
|
|
"Controller",
|
|
"Spice controller",
|
|
SPICE_CTRL_TYPE_CONTROLLER,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(object_class,
|
|
PROP_CTRL_FOREIGN_MENU,
|
|
g_param_spec_object("foreign-menu",
|
|
"Foreign Menu",
|
|
"Spice foreign menu",
|
|
SPICE_CTRL_TYPE_FOREIGN_MENU,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
#endif
|
|
g_object_class_install_property(object_class,
|
|
PROP_OPEN_RECENT_DIALOG,
|
|
g_param_spec_boolean("open-recent-dialog",
|
|
"Open recent dialog",
|
|
"Open recent dialog",
|
|
FALSE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
remote_viewer_init(RemoteViewer *self)
|
|
{
|
|
self->priv = GET_PRIVATE(self);
|
|
}
|
|
|
|
RemoteViewer *
|
|
remote_viewer_new(const gchar *uri, const gchar *title)
|
|
{
|
|
return g_object_new(REMOTE_VIEWER_TYPE,
|
|
"guri", uri,
|
|
"title", title,
|
|
"open-recent-dialog", uri == NULL,
|
|
NULL);
|
|
}
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
static void
|
|
foreign_menu_title_changed(SpiceCtrlForeignMenu *menu G_GNUC_UNUSED,
|
|
GParamSpec *pspec G_GNUC_UNUSED,
|
|
RemoteViewer *self)
|
|
{
|
|
gboolean has_focus;
|
|
|
|
g_object_get(G_OBJECT(self), "has-focus", &has_focus, NULL);
|
|
/* FIXME: use a proper "new client connected" event
|
|
** a foreign menu client set the title when connecting,
|
|
** inform of focus state at that time.
|
|
*/
|
|
spice_ctrl_foreign_menu_app_activated_msg(self->priv->ctrl_foreign_menu, has_focus);
|
|
|
|
/* update menu title */
|
|
spice_foreign_menu_updated(self);
|
|
}
|
|
|
|
RemoteViewer *
|
|
remote_viewer_new_with_controller(void)
|
|
{
|
|
RemoteViewer *self;
|
|
SpiceCtrlController *ctrl = spice_ctrl_controller_new();
|
|
SpiceCtrlForeignMenu *menu = spice_ctrl_foreign_menu_new();
|
|
|
|
self = g_object_new(REMOTE_VIEWER_TYPE,
|
|
"controller", ctrl,
|
|
"foreign-menu", menu,
|
|
NULL);
|
|
g_signal_connect(menu, "notify::title",
|
|
G_CALLBACK(foreign_menu_title_changed),
|
|
self);
|
|
g_object_unref(ctrl);
|
|
g_object_unref(menu);
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_do_connect(SpiceCtrlController *ctrl G_GNUC_UNUSED,
|
|
VirtViewerApp *self)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!virt_viewer_app_initial_connect(self, &error)) {
|
|
const gchar *msg = error ? error->message :
|
|
_("Failed to initiate connection");
|
|
virt_viewer_app_simple_message_dialog(self, msg);
|
|
g_clear_error(&error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_show(SpiceCtrlController *ctrl G_GNUC_UNUSED, RemoteViewer *self)
|
|
{
|
|
virt_viewer_app_show_display(VIRT_VIEWER_APP(self));
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_hide(SpiceCtrlController *ctrl G_GNUC_UNUSED, RemoteViewer *self)
|
|
{
|
|
virt_viewer_app_show_status(VIRT_VIEWER_APP(self), _("Display disabled by controller"));
|
|
}
|
|
|
|
static void
|
|
spice_menuitem_activate_cb(GtkMenuItem *mi, GObject *ctrl)
|
|
{
|
|
SpiceCtrlMenuItem *menuitem = g_object_get_data(G_OBJECT(mi), "spice-menuitem");
|
|
|
|
g_return_if_fail(menuitem != NULL);
|
|
if (gtk_menu_item_get_submenu(mi))
|
|
return;
|
|
|
|
if (SPICE_CTRL_IS_CONTROLLER(ctrl))
|
|
spice_ctrl_controller_menu_item_click_msg(SPICE_CTRL_CONTROLLER(ctrl), menuitem->id);
|
|
else if (SPICE_CTRL_IS_FOREIGN_MENU(ctrl))
|
|
spice_ctrl_foreign_menu_menu_item_click_msg(SPICE_CTRL_FOREIGN_MENU(ctrl), menuitem->id);
|
|
}
|
|
|
|
static GtkWidget *
|
|
ctrlmenu_to_gtkmenu (RemoteViewer *self, SpiceCtrlMenu *ctrlmenu, GObject *ctrl)
|
|
{
|
|
GList *l;
|
|
GtkWidget *menu = gtk_menu_new();
|
|
guint n = 0;
|
|
|
|
for (l = ctrlmenu->items; l != NULL; l = l->next) {
|
|
SpiceCtrlMenuItem *menuitem = l->data;
|
|
GtkWidget *item;
|
|
char *s;
|
|
if (menuitem->text == NULL) {
|
|
g_warn_if_reached();
|
|
continue;
|
|
}
|
|
|
|
for (s = menuitem->text; *s; s++)
|
|
if (*s == '&')
|
|
*s = '_';
|
|
|
|
if (g_str_equal(menuitem->text, "-")) {
|
|
item = gtk_separator_menu_item_new();
|
|
} else if (menuitem->flags & CONTROLLER_MENU_FLAGS_CHECKED) {
|
|
item = gtk_check_menu_item_new_with_mnemonic(menuitem->text);
|
|
g_object_set(item, "active", TRUE, NULL);
|
|
} else {
|
|
item = gtk_menu_item_new_with_mnemonic(menuitem->text);
|
|
}
|
|
|
|
if (menuitem->flags & (CONTROLLER_MENU_FLAGS_GRAYED | CONTROLLER_MENU_FLAGS_DISABLED))
|
|
gtk_widget_set_sensitive(item, FALSE);
|
|
|
|
g_object_set_data_full(G_OBJECT(item), "spice-menuitem",
|
|
g_object_ref(menuitem), g_object_unref);
|
|
g_signal_connect(item, "activate", G_CALLBACK(spice_menuitem_activate_cb), ctrl);
|
|
gtk_menu_attach(GTK_MENU (menu), item, 0, 1, n, n + 1);
|
|
n += 1;
|
|
|
|
if (menuitem->submenu) {
|
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
|
|
ctrlmenu_to_gtkmenu(self, menuitem->submenu, ctrl));
|
|
}
|
|
}
|
|
|
|
if (n == 0) {
|
|
g_object_ref_sink(menu);
|
|
g_object_unref(menu);
|
|
menu = NULL;
|
|
}
|
|
|
|
gtk_widget_show_all(menu);
|
|
return menu;
|
|
}
|
|
|
|
static void
|
|
spice_menu_update(RemoteViewer *self, VirtViewerWindow *win)
|
|
{
|
|
GtkWidget *menuitem = g_object_get_data(G_OBJECT(win), "spice-menu");
|
|
SpiceCtrlMenu *menu;
|
|
|
|
if (self->priv->controller == NULL)
|
|
return;
|
|
|
|
if (menuitem != NULL)
|
|
gtk_widget_destroy(menuitem);
|
|
|
|
{
|
|
GtkMenuShell *shell = GTK_MENU_SHELL(gtk_builder_get_object(virt_viewer_window_get_builder(win), "top-menu"));
|
|
menuitem = gtk_menu_item_new_with_label("Spice");
|
|
gtk_menu_shell_append(shell, menuitem);
|
|
g_object_set_data(G_OBJECT(win), "spice-menu", menuitem);
|
|
}
|
|
|
|
g_object_get(self->priv->controller, "menu", &menu, NULL);
|
|
if (menu == NULL || g_list_length(menu->items) == 0) {
|
|
gtk_widget_set_visible(menuitem, FALSE);
|
|
} else {
|
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
|
|
ctrlmenu_to_gtkmenu(self, menu, G_OBJECT(self->priv->controller)));
|
|
gtk_widget_set_visible(menuitem, TRUE);
|
|
}
|
|
|
|
if (menu != NULL)
|
|
g_object_unref(menu);
|
|
}
|
|
|
|
static void
|
|
spice_menu_update_each(gpointer key G_GNUC_UNUSED,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
spice_menu_update(REMOTE_VIEWER(user_data), VIRT_VIEWER_WINDOW(value));
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_menu_updated(RemoteViewer *self)
|
|
{
|
|
GHashTable *windows = virt_viewer_app_get_windows(VIRT_VIEWER_APP(self));
|
|
|
|
DEBUG_LOG("Spice controller menu updated");
|
|
|
|
g_hash_table_foreach(windows, spice_menu_update_each, self);
|
|
}
|
|
|
|
static void
|
|
foreign_menu_update(RemoteViewer *self, VirtViewerWindow *win)
|
|
{
|
|
GtkWidget *menuitem = g_object_get_data(G_OBJECT(win), "foreign-menu");
|
|
SpiceCtrlMenu *menu;
|
|
|
|
if (self->priv->ctrl_foreign_menu == NULL)
|
|
return;
|
|
|
|
if (menuitem != NULL)
|
|
gtk_widget_destroy(menuitem);
|
|
|
|
{
|
|
GtkMenuShell *shell = GTK_MENU_SHELL(gtk_builder_get_object(virt_viewer_window_get_builder(win), "top-menu"));
|
|
const gchar *title = spice_ctrl_foreign_menu_get_title(self->priv->ctrl_foreign_menu);
|
|
menuitem = gtk_menu_item_new_with_label(title);
|
|
gtk_menu_shell_append(shell, menuitem);
|
|
g_object_set_data(G_OBJECT(win), "foreign-menu", menuitem);
|
|
}
|
|
|
|
g_object_get(self->priv->ctrl_foreign_menu, "menu", &menu, NULL);
|
|
if (menu == NULL || g_list_length(menu->items) == 0) {
|
|
gtk_widget_set_visible(menuitem, FALSE);
|
|
} else {
|
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
|
|
ctrlmenu_to_gtkmenu(self, menu, G_OBJECT(self->priv->ctrl_foreign_menu)));
|
|
gtk_widget_set_visible(menuitem, TRUE);
|
|
}
|
|
g_object_unref(menu);
|
|
}
|
|
|
|
static void
|
|
foreign_menu_update_each(gpointer key G_GNUC_UNUSED,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
foreign_menu_update(REMOTE_VIEWER(user_data), VIRT_VIEWER_WINDOW(value));
|
|
}
|
|
|
|
static void
|
|
spice_foreign_menu_updated(RemoteViewer *self)
|
|
{
|
|
GHashTable *windows = virt_viewer_app_get_windows(VIRT_VIEWER_APP(self));
|
|
|
|
DEBUG_LOG("Spice foreign menu updated");
|
|
|
|
g_hash_table_foreach(windows, foreign_menu_update_each, self);
|
|
}
|
|
|
|
static SpiceSession *
|
|
remote_viewer_get_spice_session(RemoteViewer *self)
|
|
{
|
|
VirtViewerSession *vsession = NULL;
|
|
SpiceSession *session = NULL;
|
|
|
|
g_object_get(self, "session", &vsession, NULL);
|
|
g_return_val_if_fail(vsession != NULL, NULL);
|
|
|
|
g_object_get(vsession, "spice-session", &session, NULL);
|
|
|
|
g_object_unref(vsession);
|
|
|
|
return session;
|
|
}
|
|
|
|
static void
|
|
app_notified(VirtViewerApp *app,
|
|
GParamSpec *pspec,
|
|
RemoteViewer *self)
|
|
{
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
g_value_init(&value, pspec->value_type);
|
|
g_object_get_property(G_OBJECT(app), pspec->name, &value);
|
|
|
|
if (g_str_equal(pspec->name, "has-focus")) {
|
|
if (self->priv->ctrl_foreign_menu)
|
|
spice_ctrl_foreign_menu_app_activated_msg(self->priv->ctrl_foreign_menu, g_value_get_boolean(&value));
|
|
}
|
|
|
|
g_value_unset(&value);
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_notified(SpiceCtrlController *ctrl,
|
|
GParamSpec *pspec,
|
|
RemoteViewer *self)
|
|
{
|
|
SpiceSession *session = remote_viewer_get_spice_session(self);
|
|
GValue value = G_VALUE_INIT;
|
|
VirtViewerApp *app = VIRT_VIEWER_APP(self);
|
|
|
|
g_return_if_fail(session != NULL);
|
|
|
|
g_value_init(&value, pspec->value_type);
|
|
g_object_get_property(G_OBJECT(ctrl), pspec->name, &value);
|
|
|
|
if (g_str_equal(pspec->name, "host") ||
|
|
g_str_equal(pspec->name, "port") ||
|
|
g_str_equal(pspec->name, "password") ||
|
|
g_str_equal(pspec->name, "ca-file") ||
|
|
g_str_equal(pspec->name, "enable-smartcard") ||
|
|
g_str_equal(pspec->name, "color-depth") ||
|
|
g_str_equal(pspec->name, "disable-effects") ||
|
|
g_str_equal(pspec->name, "enable-usbredir") ||
|
|
g_str_equal(pspec->name, "secure-channels") ||
|
|
g_str_equal(pspec->name, "proxy")) {
|
|
g_object_set_property(G_OBJECT(session), pspec->name, &value);
|
|
} else if (g_str_equal(pspec->name, "sport")) {
|
|
g_object_set_property(G_OBJECT(session), "tls-port", &value);
|
|
} else if (g_str_equal(pspec->name, "tls-ciphers")) {
|
|
g_object_set_property(G_OBJECT(session), "ciphers", &value);
|
|
} else if (g_str_equal(pspec->name, "host-subject")) {
|
|
g_object_set_property(G_OBJECT(session), "cert-subject", &value);
|
|
} else if (g_str_equal(pspec->name, "enable-usb-autoshare")) {
|
|
VirtViewerSession *vsession = NULL;
|
|
|
|
g_object_get(self, "session", &vsession, NULL);
|
|
g_object_set_property(G_OBJECT(vsession), "auto-usbredir", &value);
|
|
g_object_unref(G_OBJECT(vsession));
|
|
} else if (g_str_equal(pspec->name, "usb-filter")) {
|
|
SpiceUsbDeviceManager *manager;
|
|
manager = spice_usb_device_manager_get(session, NULL);
|
|
if (manager != NULL) {
|
|
g_object_set_property(G_OBJECT(manager),
|
|
"auto-connect-filter",
|
|
&value);
|
|
}
|
|
} else if (g_str_equal(pspec->name, "title")) {
|
|
virt_viewer_app_set_title(app, g_value_get_string(&value));
|
|
} else if (g_str_equal(pspec->name, "display-flags")) {
|
|
guint flags = g_value_get_uint(&value);
|
|
gboolean fullscreen = !!(flags & (CONTROLLER_SET_FULL_SCREEN | CONTROLLER_AUTO_DISPLAY_RES));
|
|
g_object_set(G_OBJECT(self), "fullscreen", fullscreen, NULL);
|
|
} else if (g_str_equal(pspec->name, "menu")) {
|
|
spice_ctrl_menu_updated(self);
|
|
} else if (g_str_equal(pspec->name, "hotkeys")) {
|
|
virt_viewer_app_set_hotkeys(app, g_value_get_string(&value));
|
|
} else {
|
|
gchar *content = g_strdup_value_contents(&value);
|
|
|
|
g_debug("unimplemented property: %s=%s", pspec->name, content);
|
|
g_free(content);
|
|
}
|
|
|
|
g_object_unref(session);
|
|
g_value_unset(&value);
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_foreign_menu_notified(SpiceCtrlForeignMenu *ctrl_foreign_menu G_GNUC_UNUSED,
|
|
GParamSpec *pspec,
|
|
RemoteViewer *self)
|
|
{
|
|
if (g_str_equal(pspec->name, "menu")) {
|
|
spice_foreign_menu_updated(self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
spice_ctrl_listen_async_cb(GObject *object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
VirtViewerApp *app = VIRT_VIEWER_APP(user_data);
|
|
|
|
if (SPICE_CTRL_IS_CONTROLLER(object))
|
|
spice_ctrl_controller_listen_finish(SPICE_CTRL_CONTROLLER(object), res, &error);
|
|
else if (SPICE_CTRL_IS_FOREIGN_MENU(object)) {
|
|
spice_ctrl_foreign_menu_listen_finish(SPICE_CTRL_FOREIGN_MENU(object), res, &error);
|
|
} else
|
|
g_warn_if_reached();
|
|
|
|
if (error != NULL) {
|
|
virt_viewer_app_simple_message_dialog(app,
|
|
_("Controller connection failed: %s"),
|
|
error->message);
|
|
g_clear_error(&error);
|
|
exit(EXIT_FAILURE); /* TODO: make start async? */
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
remote_viewer_activate(VirtViewerApp *app, GError **error)
|
|
{
|
|
g_return_val_if_fail(REMOTE_VIEWER_IS(app), FALSE);
|
|
RemoteViewer *self = REMOTE_VIEWER(app);
|
|
gboolean ret = FALSE;
|
|
|
|
if (self->priv->controller) {
|
|
SpiceSession *session = remote_viewer_get_spice_session(self);
|
|
ret = spice_session_connect(session);
|
|
g_object_unref(session);
|
|
} else {
|
|
ret = VIRT_VIEWER_APP_CLASS(remote_viewer_parent_class)->activate(app, error);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
remote_viewer_window_added(VirtViewerApp *app G_GNUC_UNUSED,
|
|
VirtViewerWindow *win)
|
|
{
|
|
spice_menu_update(REMOTE_VIEWER(app), win);
|
|
foreign_menu_update(REMOTE_VIEWER(app), win);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_OVIRT
|
|
static gboolean
|
|
parse_ovirt_uri(const gchar *uri_str, char **rest_uri, char **name)
|
|
{
|
|
char *vm_name = NULL;
|
|
char *rel_path;
|
|
xmlURIPtr uri;
|
|
gchar **path_elements;
|
|
guint element_count;
|
|
|
|
g_return_val_if_fail(uri_str != NULL, FALSE);
|
|
g_return_val_if_fail(rest_uri != NULL, FALSE);
|
|
g_return_val_if_fail(name != NULL, FALSE);
|
|
|
|
uri = xmlParseURI(uri_str);
|
|
if (uri == NULL)
|
|
return FALSE;
|
|
|
|
if (g_strcmp0(uri->scheme, "ovirt") != 0) {
|
|
xmlFreeURI(uri);
|
|
return FALSE;
|
|
}
|
|
|
|
if (uri->path == NULL) {
|
|
xmlFreeURI(uri);
|
|
return FALSE;
|
|
}
|
|
|
|
/* extract VM name from path */
|
|
path_elements = g_strsplit(uri->path, "/", -1);
|
|
|
|
element_count = g_strv_length(path_elements);
|
|
if (element_count == 0) {
|
|
g_strfreev(path_elements);
|
|
xmlFreeURI(uri);
|
|
return FALSE;
|
|
}
|
|
vm_name = path_elements[element_count-1];
|
|
path_elements[element_count-1] = NULL;
|
|
|
|
/* build final URI */
|
|
rel_path = g_strjoinv("/", path_elements);
|
|
/* FIXME: how to decide between http and https? */
|
|
*rest_uri = g_strdup_printf("https://%s/%s/api/", uri->server, rel_path);
|
|
*name = vm_name;
|
|
g_free(rel_path);
|
|
g_strfreev(path_elements);
|
|
xmlFreeURI(uri);
|
|
|
|
g_debug("oVirt base URI: %s", *rest_uri);
|
|
g_debug("oVirt VM name: %s", *name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
authenticate_cb(RestProxy *proxy, G_GNUC_UNUSED RestProxyAuth *auth,
|
|
G_GNUC_UNUSED gboolean retrying, gpointer user_data)
|
|
{
|
|
gchar *username;
|
|
gchar *password;
|
|
VirtViewerWindow *window;
|
|
|
|
window = virt_viewer_app_get_main_window(VIRT_VIEWER_APP(user_data));
|
|
int ret = virt_viewer_auth_collect_credentials(virt_viewer_window_get_window(window),
|
|
"oVirt",
|
|
NULL,
|
|
&username, &password);
|
|
if (ret < 0) {
|
|
return FALSE;
|
|
} else {
|
|
g_object_set(G_OBJECT(proxy),
|
|
"username", username,
|
|
"password", password,
|
|
NULL);
|
|
g_free(username);
|
|
g_free(password);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
create_ovirt_session(VirtViewerApp *app, const char *uri)
|
|
{
|
|
OvirtProxy *proxy = NULL;
|
|
OvirtApi *api = NULL;
|
|
OvirtCollection *vms;
|
|
OvirtVm *vm = NULL;
|
|
OvirtVmDisplay *display = NULL;
|
|
OvirtVmState state;
|
|
GError *error = NULL;
|
|
char *rest_uri = NULL;
|
|
char *vm_name = NULL;
|
|
gboolean success = FALSE;
|
|
guint port;
|
|
guint secure_port;
|
|
OvirtVmDisplayType type;
|
|
const char *session_type;
|
|
|
|
gchar *gport = NULL;
|
|
gchar *gtlsport = NULL;
|
|
gchar *ghost = NULL;
|
|
gchar *ticket = NULL;
|
|
gchar *host_subject = NULL;
|
|
|
|
g_return_val_if_fail(VIRT_VIEWER_IS_APP(app), FALSE);
|
|
|
|
if (!parse_ovirt_uri(uri, &rest_uri, &vm_name))
|
|
goto error;
|
|
proxy = ovirt_proxy_new(rest_uri);
|
|
if (proxy == NULL)
|
|
goto error;
|
|
ovirt_set_proxy_options(proxy);
|
|
g_signal_connect(G_OBJECT(proxy), "authenticate",
|
|
G_CALLBACK(authenticate_cb), app);
|
|
|
|
api = ovirt_proxy_fetch_api(proxy, &error);
|
|
if (error != NULL) {
|
|
g_debug("failed to get oVirt 'api' collection: %s", error->message);
|
|
goto error;
|
|
}
|
|
vms = ovirt_api_get_vms(api);
|
|
ovirt_collection_fetch(vms, proxy, &error);
|
|
if (error != NULL) {
|
|
g_debug("failed to lookup %s: %s", vm_name, error->message);
|
|
goto error;
|
|
}
|
|
vm = OVIRT_VM(ovirt_collection_lookup_resource(vms, vm_name));
|
|
g_return_val_if_fail(vm != NULL, FALSE);
|
|
g_object_get(G_OBJECT(vm), "state", &state, NULL);
|
|
if (state != OVIRT_VM_STATE_UP) {
|
|
g_debug("oVirt VM %s is not running", vm_name);
|
|
goto error;
|
|
}
|
|
|
|
if (!ovirt_vm_get_ticket(vm, proxy, &error)) {
|
|
g_debug("failed to get ticket for %s: %s", vm_name, error->message);
|
|
goto error;
|
|
}
|
|
|
|
g_object_get(G_OBJECT(vm), "display", &display, NULL);
|
|
if (display == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
g_object_get(G_OBJECT(display),
|
|
"type", &type,
|
|
"address", &ghost,
|
|
"port", &port,
|
|
"secure-port", &secure_port,
|
|
"ticket", &ticket,
|
|
"host-subject", &host_subject,
|
|
NULL);
|
|
gport = g_strdup_printf("%d", port);
|
|
gtlsport = g_strdup_printf("%d", secure_port);
|
|
|
|
if (type == OVIRT_VM_DISPLAY_SPICE) {
|
|
session_type = "spice";
|
|
} else if (type == OVIRT_VM_DISPLAY_VNC) {
|
|
session_type = "vnc";
|
|
} else {
|
|
g_debug("Unknown display type: %d", type);
|
|
goto error;
|
|
}
|
|
|
|
virt_viewer_app_set_connect_info(app, NULL, ghost, gport, gtlsport,
|
|
session_type, NULL, NULL, 0, NULL);
|
|
|
|
if (virt_viewer_app_create_session(app, session_type) < 0)
|
|
goto error;
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
if (type == OVIRT_VM_DISPLAY_SPICE) {
|
|
SpiceSession *session;
|
|
GByteArray *ca_cert;
|
|
|
|
session = remote_viewer_get_spice_session(REMOTE_VIEWER(app));
|
|
g_object_set(G_OBJECT(session),
|
|
"password", ticket,
|
|
"cert-subject", host_subject,
|
|
NULL);
|
|
g_object_get(G_OBJECT(proxy), "ca-cert", &ca_cert, NULL);
|
|
if (ca_cert != NULL) {
|
|
g_object_set(G_OBJECT(session),
|
|
"ca", ca_cert,
|
|
NULL);
|
|
g_byte_array_unref(ca_cert);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
success = TRUE;
|
|
|
|
error:
|
|
g_free(rest_uri);
|
|
g_free(vm_name);
|
|
g_free(ticket);
|
|
g_free(gport);
|
|
g_free(gtlsport);
|
|
g_free(ghost);
|
|
g_free(host_subject);
|
|
|
|
if (error != NULL)
|
|
g_error_free(error);
|
|
if (display != NULL)
|
|
g_object_unref(display);
|
|
if (vm != NULL)
|
|
g_object_unref(vm);
|
|
if (api != NULL)
|
|
g_object_unref(api);
|
|
if (proxy != NULL)
|
|
g_object_unref(proxy);
|
|
|
|
return success;
|
|
}
|
|
|
|
#endif
|
|
|
|
static void
|
|
recent_selection_changed_dialog_cb(GtkRecentChooser *chooser, gpointer data)
|
|
{
|
|
GtkRecentInfo *info;
|
|
GtkWidget *entry = data;
|
|
const gchar *uri;
|
|
|
|
info = gtk_recent_chooser_get_current_item(chooser);
|
|
if (info == NULL)
|
|
return;
|
|
|
|
uri = gtk_recent_info_get_uri(info);
|
|
g_return_if_fail(uri != NULL);
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(entry), uri);
|
|
|
|
gtk_recent_info_unref(info);
|
|
}
|
|
|
|
static void
|
|
recent_item_activated_dialog_cb(GtkRecentChooser *chooser G_GNUC_UNUSED, gpointer data)
|
|
{
|
|
gtk_dialog_response(GTK_DIALOG (data), GTK_RESPONSE_ACCEPT);
|
|
}
|
|
|
|
static gint
|
|
connect_dialog(gchar **uri)
|
|
{
|
|
GtkWidget *dialog, *area, *label, *entry, *recent;
|
|
GtkRecentFilter *rfilter;
|
|
GtkTable *table;
|
|
gint retval;
|
|
|
|
/* Create the widgets */
|
|
dialog = gtk_dialog_new_with_buttons(_("Connection details"),
|
|
NULL,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_STOCK_CANCEL,
|
|
GTK_RESPONSE_REJECT,
|
|
GTK_STOCK_CONNECT,
|
|
GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
|
|
area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
|
table = GTK_TABLE(gtk_table_new(1, 2, 0));
|
|
gtk_box_pack_start(GTK_BOX(area), GTK_WIDGET(table), TRUE, TRUE, 0);
|
|
gtk_table_set_row_spacings(table, 5);
|
|
gtk_table_set_col_spacings(table, 5);
|
|
|
|
label = gtk_label_new(_("URL:"));
|
|
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
|
|
gtk_table_attach_defaults(table, label, 0, 1, 0, 1);
|
|
entry = GTK_WIDGET(gtk_entry_new());
|
|
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
|
|
g_object_set(entry, "width-request", 200, NULL);
|
|
gtk_table_attach_defaults(table, entry, 1, 2, 0, 1);
|
|
|
|
label = gtk_label_new(_("Recent connections:"));
|
|
gtk_box_pack_start(GTK_BOX(area), label, TRUE, TRUE, 0);
|
|
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
|
|
|
|
recent = GTK_WIDGET(gtk_recent_chooser_widget_new());
|
|
gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(recent), FALSE);
|
|
gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent), GTK_RECENT_SORT_MRU);
|
|
gtk_box_pack_start(GTK_BOX(area), recent, TRUE, TRUE, 0);
|
|
|
|
rfilter = gtk_recent_filter_new();
|
|
gtk_recent_filter_add_mime_type(rfilter, "application/x-spice");
|
|
gtk_recent_filter_add_mime_type(rfilter, "application/x-vnc");
|
|
gtk_recent_filter_add_mime_type(rfilter, "application/x-virt-viewer");
|
|
gtk_recent_chooser_set_filter(GTK_RECENT_CHOOSER(recent), rfilter);
|
|
gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(recent), FALSE);
|
|
g_signal_connect(recent, "selection-changed",
|
|
G_CALLBACK(recent_selection_changed_dialog_cb), entry);
|
|
g_signal_connect(recent, "item-activated",
|
|
G_CALLBACK(recent_item_activated_dialog_cb), dialog);
|
|
|
|
/* show and wait for response */
|
|
gtk_widget_show_all(dialog);
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
|
*uri = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
|
|
g_strstrip(*uri);
|
|
retval = 0;
|
|
} else {
|
|
*uri = NULL;
|
|
retval = -1;
|
|
}
|
|
gtk_widget_destroy(dialog);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
remote_viewer_start(VirtViewerApp *app)
|
|
{
|
|
g_return_val_if_fail(REMOTE_VIEWER_IS(app), FALSE);
|
|
|
|
RemoteViewer *self = REMOTE_VIEWER(app);
|
|
RemoteViewerPrivate *priv = self->priv;
|
|
GFile *file = NULL;
|
|
VirtViewerFile *vvfile = NULL;
|
|
gboolean ret = FALSE;
|
|
gchar *guri = NULL;
|
|
gchar *type = NULL;
|
|
GError *error = NULL;
|
|
|
|
#ifdef HAVE_SPICE_GTK
|
|
g_signal_connect(app, "notify", G_CALLBACK(app_notified), self);
|
|
|
|
if (priv->controller) {
|
|
if (virt_viewer_app_create_session(app, "spice") < 0) {
|
|
virt_viewer_app_simple_message_dialog(app, _("Couldn't create a Spice session"));
|
|
goto cleanup;
|
|
}
|
|
|
|
g_signal_connect(priv->controller, "notify", G_CALLBACK(spice_ctrl_notified), self);
|
|
g_signal_connect(priv->controller, "do_connect", G_CALLBACK(spice_ctrl_do_connect), self);
|
|
g_signal_connect(priv->controller, "show", G_CALLBACK(spice_ctrl_show), self);
|
|
g_signal_connect(priv->controller, "hide", G_CALLBACK(spice_ctrl_hide), self);
|
|
|
|
spice_ctrl_controller_listen(priv->controller, NULL, spice_ctrl_listen_async_cb, self);
|
|
|
|
g_signal_connect(priv->ctrl_foreign_menu, "notify", G_CALLBACK(spice_ctrl_foreign_menu_notified), self);
|
|
spice_ctrl_foreign_menu_listen(priv->ctrl_foreign_menu, NULL, spice_ctrl_listen_async_cb, self);
|
|
|
|
virt_viewer_app_show_status(VIRT_VIEWER_APP(self), _("Setting up Spice session..."));
|
|
} else {
|
|
#endif
|
|
if (priv->open_recent_dialog) {
|
|
if (connect_dialog(&guri) != 0)
|
|
return FALSE;
|
|
g_object_set(app, "guri", guri, NULL);
|
|
} else
|
|
g_object_get(app, "guri", &guri, NULL);
|
|
|
|
g_return_val_if_fail(guri != NULL, FALSE);
|
|
|
|
DEBUG_LOG("Opening display to %s", guri);
|
|
if ((virt_viewer_app_get_title(app) == NULL) || priv->default_title) {
|
|
priv->default_title = TRUE;
|
|
virt_viewer_app_set_title(app, guri);
|
|
}
|
|
|
|
file = g_file_new_for_commandline_arg(guri);
|
|
if (g_file_query_exists(file, NULL)) {
|
|
gchar *path = g_file_get_path(file);
|
|
vvfile = virt_viewer_file_new(path, &error);
|
|
g_free(path);
|
|
if (error) {
|
|
virt_viewer_app_simple_message_dialog(app, _("Invalid file %s"), guri);
|
|
g_warning("%s", error->message);
|
|
g_clear_error(&error);
|
|
goto cleanup;
|
|
}
|
|
g_object_get(G_OBJECT(vvfile), "type", &type, NULL);
|
|
} else if (virt_viewer_util_extract_host(guri, &type, NULL, NULL, NULL, NULL) < 0 || type == NULL) {
|
|
virt_viewer_app_simple_message_dialog(app, _("Cannot determine the connection type from URI"));
|
|
goto cleanup;
|
|
}
|
|
#ifdef HAVE_OVIRT
|
|
if (g_strcmp0(type, "ovirt") == 0) {
|
|
if (!create_ovirt_session(app, guri)) {
|
|
virt_viewer_app_simple_message_dialog(app, _("Couldn't open oVirt session"));
|
|
goto cleanup;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
if (virt_viewer_app_create_session(app, type) < 0) {
|
|
virt_viewer_app_simple_message_dialog(app, _("Couldn't create a session for this type: %s"), type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
virt_viewer_session_set_file(virt_viewer_app_get_session(app), vvfile);
|
|
|
|
if (!virt_viewer_app_initial_connect(app, &error)) {
|
|
const gchar *msg = error ? error->message :
|
|
_("Failed to initiate connection");
|
|
|
|
virt_viewer_app_simple_message_dialog(app, msg);
|
|
g_clear_error(&error);
|
|
goto cleanup;
|
|
}
|
|
#ifdef HAVE_SPICE_GTK
|
|
}
|
|
#endif
|
|
|
|
ret = VIRT_VIEWER_APP_CLASS(remote_viewer_parent_class)->start(app);
|
|
|
|
cleanup:
|
|
g_clear_object(&file);
|
|
g_clear_object(&vvfile);
|
|
g_free(guri);
|
|
g_free(type);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 4
|
|
* c-basic-offset: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|