mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
GtkAboutDialog in GTK+ 3 can be configured with few standard licenses, including GPL2+. Thus, just set that property, so the about dialog will show its own license text, and there is no need for our custom one.
2205 lines
74 KiB
C
2205 lines
74 KiB
C
/* virt-p2v
|
|
* Copyright (C) 2009-2017 Red Hat Inc.
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
/**
|
|
* This file implements almost all of the virt-p2v graphical user
|
|
* interface (GUI).
|
|
*
|
|
* The GUI has three main dialogs:
|
|
*
|
|
* =over 4
|
|
*
|
|
* =item Connection dialog
|
|
*
|
|
* The connection dialog is the one shown initially. It asks the user
|
|
* to type in the login details for the remote conversion server and
|
|
* invites the user to test the ssh connection.
|
|
*
|
|
* =item Conversion dialog
|
|
*
|
|
* The conversion dialog asks for information about the target VM
|
|
* (eg. the number of vCPUs required), and about what to convert
|
|
* (eg. which network interfaces should be copied and which should be
|
|
* ignored).
|
|
*
|
|
* =item Running dialog
|
|
*
|
|
* The running dialog is displayed when the P2V process is underway.
|
|
* It mainly displays the virt-v2v debug messages.
|
|
*
|
|
* =back
|
|
*
|
|
* Note that the other major dialog (C<"Configure network ...">) is
|
|
* handled entirely by NetworkManager's L<nm-connection-editor(1)>
|
|
* program and has nothing to do with this code.
|
|
*
|
|
* This file is written in a kind of "pseudo-Gtk" which is backwards
|
|
* compatible from Gtk 2.10 (RHEL 5) through at least Gtk 3.22. This
|
|
* is done using a few macros to implement old C<gtk_*> functions or
|
|
* map them to newer functions. Supporting ancient Gtk is important
|
|
* because we want to provide a virt-p2v binary that can run on very
|
|
* old kernels, to support 32 bit and proprietary SCSI drivers.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <error.h>
|
|
#include <locale.h>
|
|
#include <assert.h>
|
|
#include <libintl.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
/* errors in <gtk.h> */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
|
|
#if defined(__GNUC__) && __GNUC__ >= 6 /* gcc >= 6 */
|
|
#pragma GCC diagnostic ignored "-Wshift-overflow"
|
|
#endif
|
|
#include <gtk/gtk.h>
|
|
#pragma GCC diagnostic pop
|
|
|
|
#include "ignore-value.h"
|
|
#include "getprogname.h"
|
|
|
|
#include "p2v.h"
|
|
|
|
/* See note about "pseudo-Gtk" above. */
|
|
#include "gui-gtk2-compat.h"
|
|
#include "gui-gtk3-compat.h"
|
|
|
|
/* Maximum vCPUs and guest memory that we will allow users to set.
|
|
* These limits come from
|
|
* https://access.redhat.com/articles/rhel-kvm-limits
|
|
*/
|
|
#define MAX_SUPPORTED_VCPUS 160
|
|
#define MAX_SUPPORTED_MEMORY_MB (UINT64_C (4000 * 1024))
|
|
|
|
static void create_connection_dialog (struct config *);
|
|
static void create_conversion_dialog (struct config *);
|
|
static void create_running_dialog (void);
|
|
static void show_connection_dialog (void);
|
|
static void show_conversion_dialog (void);
|
|
static void show_running_dialog (void);
|
|
|
|
static void set_info_label (void);
|
|
|
|
/* The connection dialog. */
|
|
static GtkWidget *conn_dlg,
|
|
*server_entry, *port_entry,
|
|
*username_entry, *password_entry, *identity_entry, *sudo_button,
|
|
*spinner_hbox,
|
|
#ifdef GTK_SPINNER
|
|
*spinner,
|
|
#endif
|
|
*spinner_message, *next_button;
|
|
|
|
/* The conversion dialog. */
|
|
static GtkWidget *conv_dlg,
|
|
*guestname_entry, *vcpus_entry, *memory_entry,
|
|
*vcpus_warning, *memory_warning, *target_warning_label,
|
|
*o_combo, *oc_entry, *os_entry, *of_entry, *oa_combo,
|
|
*info_label,
|
|
*disks_list, *removable_list, *interfaces_list,
|
|
*start_button;
|
|
|
|
/* The running dialog which is displayed when virt-v2v is running. */
|
|
static GtkWidget *run_dlg,
|
|
*v2v_output_sw, *v2v_output, *log_label, *status_label,
|
|
*cancel_button, *reboot_button;
|
|
|
|
/* Colour tags used in the v2v_output GtkTextBuffer. */
|
|
static GtkTextTag *v2v_output_tags[16];
|
|
|
|
#if !GTK_CHECK_VERSION(3,0,0) /* gtk < 3 */
|
|
/* The license of virt-p2v, for the About dialog. */
|
|
static const char gplv2plus[] =
|
|
"This program is free software; you can redistribute it and/or modify\n"
|
|
"it under the terms of the GNU General Public License as published by\n"
|
|
"the Free Software Foundation; either version 2 of the License, or\n"
|
|
"(at your option) any later version.\n"
|
|
"\n"
|
|
"This program is distributed in the hope that it will be useful,\n"
|
|
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
|
|
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
|
|
"GNU General Public License for more details.\n"
|
|
"\n"
|
|
"You should have received a copy of the GNU General Public License\n"
|
|
"along with this program; if not, write to the Free Software\n"
|
|
"Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n";
|
|
#endif
|
|
|
|
/**
|
|
* The entry point from the main program.
|
|
*
|
|
* Note that C<gtk_init> etc have already been called in C<main>.
|
|
*/
|
|
void
|
|
gui_conversion (struct config *config)
|
|
{
|
|
/* Create the dialogs. */
|
|
create_connection_dialog (config);
|
|
create_conversion_dialog (config);
|
|
create_running_dialog ();
|
|
|
|
/* Start by displaying the connection dialog. */
|
|
show_connection_dialog ();
|
|
|
|
gtk_main ();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Connection dialog. */
|
|
|
|
static void username_changed_callback (GtkWidget *w, gpointer data);
|
|
static void password_or_identity_changed_callback (GtkWidget *w, gpointer data);
|
|
static void test_connection_clicked (GtkWidget *w, gpointer data);
|
|
static void *test_connection_thread (void *data);
|
|
static gboolean start_spinner (gpointer user_data);
|
|
static gboolean stop_spinner (gpointer user_data);
|
|
static gboolean test_connection_error (gpointer user_data);
|
|
static gboolean test_connection_ok (gpointer user_data);
|
|
static void configure_network_button_clicked (GtkWidget *w, gpointer data);
|
|
static void xterm_button_clicked (GtkWidget *w, gpointer data);
|
|
static void about_button_clicked (GtkWidget *w, gpointer data);
|
|
static void connection_next_clicked (GtkWidget *w, gpointer data);
|
|
static void repopulate_output_combo (struct config *config);
|
|
|
|
/**
|
|
* Create the connection dialog.
|
|
*
|
|
* This creates the dialog, but it is not displayed. See
|
|
* C<show_connection_dialog>.
|
|
*/
|
|
static void
|
|
create_connection_dialog (struct config *config)
|
|
{
|
|
GtkWidget *intro, *table;
|
|
GtkWidget *server_label;
|
|
GtkWidget *server_hbox;
|
|
GtkWidget *port_colon_label;
|
|
GtkWidget *username_label;
|
|
GtkWidget *password_label;
|
|
GtkWidget *identity_label;
|
|
GtkWidget *test_hbox, *test;
|
|
GtkWidget *about;
|
|
GtkWidget *configure_network;
|
|
GtkWidget *xterm;
|
|
char port_str[64];
|
|
|
|
conn_dlg = gtk_dialog_new ();
|
|
gtk_window_set_title (GTK_WINDOW (conn_dlg), getprogname ());
|
|
gtk_window_set_resizable (GTK_WINDOW (conn_dlg), FALSE);
|
|
|
|
/* The main dialog area. */
|
|
intro = gtk_label_new (_("Connect to a virt-v2v conversion server over SSH:"));
|
|
gtk_label_set_line_wrap (GTK_LABEL (intro), TRUE);
|
|
set_padding (intro, 10, 10);
|
|
|
|
table_new (table, 5, 2);
|
|
server_label = gtk_label_new_with_mnemonic (_("Conversion _server:"));
|
|
table_attach (table, server_label,
|
|
0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
|
|
set_alignment (server_label, 1., 0.5);
|
|
|
|
hbox_new (server_hbox, FALSE, 4);
|
|
server_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (server_label), server_entry);
|
|
if (config->server != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (server_entry), config->server);
|
|
port_colon_label = gtk_label_new (":");
|
|
port_entry = gtk_entry_new ();
|
|
gtk_entry_set_width_chars (GTK_ENTRY (port_entry), 6);
|
|
snprintf (port_str, sizeof port_str, "%d", config->port);
|
|
gtk_entry_set_text (GTK_ENTRY (port_entry), port_str);
|
|
gtk_box_pack_start (GTK_BOX (server_hbox), server_entry, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (server_hbox), port_colon_label, FALSE, FALSE, 0);
|
|
gtk_box_pack_start (GTK_BOX (server_hbox), port_entry, FALSE, FALSE, 0);
|
|
table_attach (table, server_hbox,
|
|
1, 2, 0, 1, GTK_EXPAND|GTK_FILL, GTK_FILL, 4, 4);
|
|
|
|
username_label = gtk_label_new_with_mnemonic (_("_User name:"));
|
|
table_attach (table, username_label,
|
|
0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
|
|
set_alignment (username_label, 1., 0.5);
|
|
username_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (username_label), username_entry);
|
|
if (config->username != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (username_entry), config->username);
|
|
else
|
|
gtk_entry_set_text (GTK_ENTRY (username_entry), "root");
|
|
table_attach (table, username_entry,
|
|
1, 2, 1, 2, GTK_EXPAND|GTK_FILL, GTK_FILL, 4, 4);
|
|
|
|
password_label = gtk_label_new_with_mnemonic (_("_Password:"));
|
|
table_attach (table, password_label,
|
|
0, 1, 2, 3, GTK_FILL, GTK_FILL, 4, 4);
|
|
set_alignment (password_label, 1., 0.5);
|
|
password_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (password_label), password_entry);
|
|
gtk_entry_set_visibility (GTK_ENTRY (password_entry), FALSE);
|
|
#ifdef GTK_INPUT_PURPOSE_PASSWORD
|
|
gtk_entry_set_input_purpose (GTK_ENTRY (password_entry),
|
|
GTK_INPUT_PURPOSE_PASSWORD);
|
|
#endif
|
|
if (config->password != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (password_entry), config->password);
|
|
table_attach (table, password_entry,
|
|
1, 2, 2, 3, GTK_EXPAND|GTK_FILL, GTK_FILL, 4, 4);
|
|
|
|
identity_label = gtk_label_new_with_mnemonic (_("SSH _Identity URL:"));
|
|
table_attach (table, identity_label,
|
|
0, 1, 3, 4, GTK_FILL, GTK_FILL, 4, 4);
|
|
set_alignment (identity_label, 1., 0.5);
|
|
identity_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (identity_label), identity_entry);
|
|
if (config->identity_url != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (identity_entry), config->identity_url);
|
|
table_attach (table, identity_entry,
|
|
1, 2, 3, 4, GTK_EXPAND|GTK_FILL, GTK_FILL, 4, 4);
|
|
|
|
sudo_button =
|
|
gtk_check_button_new_with_mnemonic (_("Use su_do when running virt-v2v"));
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sudo_button),
|
|
config->sudo);
|
|
table_attach (table, sudo_button,
|
|
1, 2, 4, 5, GTK_FILL, GTK_FILL, 4, 4);
|
|
|
|
hbox_new (test_hbox, FALSE, 0);
|
|
test = gtk_button_new_with_mnemonic (_("_Test connection"));
|
|
gtk_box_pack_start (GTK_BOX (test_hbox), test, TRUE, FALSE, 0);
|
|
|
|
hbox_new (spinner_hbox, FALSE, 10);
|
|
#ifdef GTK_SPINNER
|
|
spinner = gtk_spinner_new ();
|
|
gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner, FALSE, FALSE, 0);
|
|
#endif
|
|
spinner_message = gtk_label_new (NULL);
|
|
gtk_label_set_line_wrap (GTK_LABEL (spinner_message), TRUE);
|
|
set_padding (spinner_message, 10, 10);
|
|
gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner_message, TRUE, TRUE, 0);
|
|
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (conn_dlg))),
|
|
intro, TRUE, TRUE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (conn_dlg))),
|
|
table, TRUE, TRUE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (conn_dlg))),
|
|
test_hbox, FALSE, FALSE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (conn_dlg))),
|
|
spinner_hbox, TRUE, TRUE, 0);
|
|
|
|
/* Buttons. */
|
|
gtk_dialog_add_buttons (GTK_DIALOG (conn_dlg),
|
|
_("_Configure network ..."), 1,
|
|
_("_XTerm ..."), 2,
|
|
_("_About virt-p2v " PACKAGE_VERSION " ..."), 3,
|
|
_("_Next"), 4,
|
|
NULL);
|
|
|
|
next_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 4);
|
|
gtk_widget_set_sensitive (next_button, FALSE);
|
|
|
|
configure_network =
|
|
gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 1);
|
|
xterm = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 2);
|
|
about = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 3);
|
|
|
|
/* Signals. */
|
|
g_signal_connect_swapped (G_OBJECT (conn_dlg), "destroy",
|
|
G_CALLBACK (gtk_main_quit), NULL);
|
|
g_signal_connect (G_OBJECT (test), "clicked",
|
|
G_CALLBACK (test_connection_clicked), config);
|
|
g_signal_connect (G_OBJECT (configure_network), "clicked",
|
|
G_CALLBACK (configure_network_button_clicked), NULL);
|
|
g_signal_connect (G_OBJECT (xterm), "clicked",
|
|
G_CALLBACK (xterm_button_clicked), NULL);
|
|
g_signal_connect (G_OBJECT (about), "clicked",
|
|
G_CALLBACK (about_button_clicked), NULL);
|
|
g_signal_connect (G_OBJECT (next_button), "clicked",
|
|
G_CALLBACK (connection_next_clicked), NULL);
|
|
g_signal_connect (G_OBJECT (username_entry), "changed",
|
|
G_CALLBACK (username_changed_callback), NULL);
|
|
g_signal_connect (G_OBJECT (password_entry), "changed",
|
|
G_CALLBACK (password_or_identity_changed_callback), NULL);
|
|
g_signal_connect (G_OBJECT (identity_entry), "changed",
|
|
G_CALLBACK (password_or_identity_changed_callback), NULL);
|
|
|
|
/* Call this signal to initialize the sensitivity of the sudo
|
|
* button correctly.
|
|
*/
|
|
username_changed_callback (NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* If the username is "root", disable the sudo button.
|
|
*/
|
|
static void
|
|
username_changed_callback (GtkWidget *w, gpointer data)
|
|
{
|
|
const char *str;
|
|
int username_is_root;
|
|
int sudo_is_set;
|
|
|
|
str = gtk_entry_get_text (GTK_ENTRY (username_entry));
|
|
username_is_root = str != NULL && STREQ (str, "root");
|
|
sudo_is_set = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sudo_button));
|
|
|
|
/* The sudo button is sensitive if:
|
|
* - The username is not "root", or
|
|
* - The button is not already checked (to allow the user to uncheck it)
|
|
*/
|
|
gtk_widget_set_sensitive (sudo_button, !username_is_root || sudo_is_set);
|
|
}
|
|
|
|
/**
|
|
* The password or SSH identity URL entries are mutually exclusive, so
|
|
* if one contains text then disable the other. This function is
|
|
* called when the "changed" signal is received on either.
|
|
*/
|
|
static void
|
|
password_or_identity_changed_callback (GtkWidget *w, gpointer data)
|
|
{
|
|
const char *str;
|
|
int password_set;
|
|
int identity_set;
|
|
|
|
str = gtk_entry_get_text (GTK_ENTRY (password_entry));
|
|
password_set = str != NULL && STRNEQ (str, "");
|
|
str = gtk_entry_get_text (GTK_ENTRY (identity_entry));
|
|
identity_set = str != NULL && STRNEQ (str, "");
|
|
|
|
if (!password_set && !identity_set) {
|
|
gtk_widget_set_sensitive (password_entry, TRUE);
|
|
gtk_widget_set_sensitive (identity_entry, TRUE);
|
|
}
|
|
else if (identity_set)
|
|
gtk_widget_set_sensitive (password_entry, FALSE);
|
|
else if (password_set)
|
|
gtk_widget_set_sensitive (identity_entry, FALSE);
|
|
}
|
|
|
|
/**
|
|
* Hide all other dialogs and show the connection dialog.
|
|
*/
|
|
static void
|
|
show_connection_dialog (void)
|
|
{
|
|
/* Hide the other dialogs. */
|
|
gtk_widget_hide (conv_dlg);
|
|
gtk_widget_hide (run_dlg);
|
|
|
|
/* Show everything except the spinner. */
|
|
gtk_widget_show_all (conn_dlg);
|
|
gtk_widget_hide (spinner_hbox);
|
|
}
|
|
|
|
/**
|
|
* Callback from the C<Test connection> button.
|
|
*
|
|
* This initiates a background thread which actually does the ssh to
|
|
* the conversion server and the rest of the testing (see
|
|
* C<test_connection_thread>).
|
|
*/
|
|
static void
|
|
test_connection_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
struct config *config = data;
|
|
const gchar *port_str;
|
|
const gchar *identity_str;
|
|
size_t errors = 0;
|
|
struct config *copy;
|
|
int err;
|
|
pthread_t tid;
|
|
pthread_attr_t attr;
|
|
|
|
gtk_label_set_text (GTK_LABEL (spinner_message), "");
|
|
gtk_widget_show_all (spinner_hbox);
|
|
#ifdef GTK_SPINNER
|
|
gtk_widget_hide (spinner);
|
|
#endif
|
|
|
|
/* Get the fields from the various widgets. */
|
|
free (config->server);
|
|
config->server = strdup (gtk_entry_get_text (GTK_ENTRY (server_entry)));
|
|
if (STREQ (config->server, "")) {
|
|
gtk_label_set_text (GTK_LABEL (spinner_message),
|
|
_("error: No conversion server given."));
|
|
gtk_widget_grab_focus (server_entry);
|
|
errors++;
|
|
}
|
|
port_str = gtk_entry_get_text (GTK_ENTRY (port_entry));
|
|
if (sscanf (port_str, "%d", &config->port) != 1 ||
|
|
config->port <= 0 || config->port >= 65536) {
|
|
gtk_label_set_text (GTK_LABEL (spinner_message),
|
|
_("error: Invalid port number. If in doubt, use \"22\"."));
|
|
gtk_widget_grab_focus (port_entry);
|
|
errors++;
|
|
}
|
|
free (config->username);
|
|
config->username = strdup (gtk_entry_get_text (GTK_ENTRY (username_entry)));
|
|
if (STREQ (config->username, "")) {
|
|
gtk_label_set_text (GTK_LABEL (spinner_message),
|
|
_("error: No user name. If in doubt, use \"root\"."));
|
|
gtk_widget_grab_focus (username_entry);
|
|
errors++;
|
|
}
|
|
free (config->password);
|
|
config->password = strdup (gtk_entry_get_text (GTK_ENTRY (password_entry)));
|
|
|
|
free (config->identity_url);
|
|
identity_str = gtk_entry_get_text (GTK_ENTRY (identity_entry));
|
|
if (identity_str && STRNEQ (identity_str, ""))
|
|
config->identity_url = strdup (identity_str);
|
|
else
|
|
config->identity_url = NULL;
|
|
config->identity_file_needs_update = 1;
|
|
|
|
config->sudo = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sudo_button));
|
|
|
|
if (errors)
|
|
return;
|
|
|
|
/* Give the testing thread its own copy of the config in case we
|
|
* update the config in the main thread.
|
|
*/
|
|
copy = copy_config (config);
|
|
|
|
/* No errors so far, so test the connection in a background thread. */
|
|
pthread_attr_init (&attr);
|
|
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
|
|
err = pthread_create (&tid, &attr, test_connection_thread, copy);
|
|
if (err != 0)
|
|
error (EXIT_FAILURE, err, "pthread_create");
|
|
pthread_attr_destroy (&attr);
|
|
}
|
|
|
|
/**
|
|
* Run C<test_connection> (in a detached background thread). Once it
|
|
* finishes stop the spinner and set the spinner message
|
|
* appropriately. If the test is successful then we enable the
|
|
* C<Next> button. If unsuccessful, an error is shown in the
|
|
* connection dialog.
|
|
*/
|
|
static void *
|
|
test_connection_thread (void *data)
|
|
{
|
|
struct config *copy = data;
|
|
int r;
|
|
|
|
g_idle_add (start_spinner, NULL);
|
|
|
|
wait_network_online (copy);
|
|
r = test_connection (copy);
|
|
free_config (copy);
|
|
|
|
g_idle_add (stop_spinner, NULL);
|
|
|
|
if (r == -1)
|
|
g_idle_add (test_connection_error, NULL);
|
|
else
|
|
g_idle_add (test_connection_ok, NULL);
|
|
|
|
/* Thread is detached anyway, so no one is waiting for the status. */
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<test_connection_thread> (but run on the
|
|
* main thread) to start the spinner in the connection dialog.
|
|
*/
|
|
static gboolean
|
|
start_spinner (gpointer user_data)
|
|
{
|
|
gtk_label_set_text (GTK_LABEL (spinner_message),
|
|
_("Testing the connection to the conversion server ..."));
|
|
#ifdef GTK_SPINNER
|
|
gtk_widget_show (spinner);
|
|
gtk_spinner_start (GTK_SPINNER (spinner));
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<test_connection_thread> (but run on the
|
|
* main thread) to stop the spinner in the connection dialog.
|
|
*/
|
|
static gboolean
|
|
stop_spinner (gpointer user_data)
|
|
{
|
|
#ifdef GTK_SPINNER
|
|
gtk_spinner_stop (GTK_SPINNER (spinner));
|
|
gtk_widget_hide (spinner);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<test_connection_thread> (but run on the
|
|
* main thread) when there is an error. Display the error message and
|
|
* disable the C<Next> button so the user is forced to correct it.
|
|
*/
|
|
static gboolean
|
|
test_connection_error (gpointer user_data)
|
|
{
|
|
const char *err = get_ssh_error ();
|
|
|
|
gtk_label_set_text (GTK_LABEL (spinner_message), err);
|
|
/* Disable the Next button. */
|
|
gtk_widget_set_sensitive (next_button, FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<test_connection_thread> (but run on the
|
|
* main thread) when the connection test was successful.
|
|
*/
|
|
static gboolean
|
|
test_connection_ok (gpointer user_data)
|
|
{
|
|
gtk_label_set_text
|
|
(GTK_LABEL (spinner_message),
|
|
_("Connected to the conversion server.\n"
|
|
"Press the \"Next\" button to configure the conversion process."));
|
|
|
|
/* Enable the Next button. */
|
|
gtk_widget_set_sensitive (next_button, TRUE);
|
|
gtk_widget_grab_focus (next_button);
|
|
|
|
/* Update the information in the conversion dialog. */
|
|
set_info_label ();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Callback from the C<Configure network ...> button. This dialog is
|
|
* handled entirely by an external program which is part of
|
|
* NetworkManager.
|
|
*/
|
|
static void
|
|
configure_network_button_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
if (access ("/sbin/yast2", X_OK) >= 0)
|
|
ignore_value (system ("yast2 lan &"));
|
|
else
|
|
ignore_value (system ("nm-connection-editor &"));
|
|
}
|
|
|
|
/**
|
|
* Callback from the C<XTerm ...> button.
|
|
*/
|
|
static void
|
|
xterm_button_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
ignore_value (system ("xterm &"));
|
|
}
|
|
|
|
/**
|
|
* Callback from the C<About virt-p2v ...> button.
|
|
*
|
|
* See also F<p2v/about-authors.c> and F<p2v/about-license.c>.
|
|
*/
|
|
static void
|
|
about_button_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *parent = conn_dlg;
|
|
|
|
dialog = gtk_about_dialog_new ();
|
|
|
|
g_object_set (G_OBJECT (dialog),
|
|
"program-name", getprogname (),
|
|
"version", PACKAGE_VERSION_FULL " (" host_cpu ")",
|
|
"copyright", "\u00A9 2009-2017 Red Hat Inc.",
|
|
"comments",
|
|
_("Virtualize a physical machine to run on KVM"),
|
|
#if GTK_CHECK_VERSION(3,0,0) /* gtk >= 3 */
|
|
"license-type", GTK_LICENSE_GPL_2_0,
|
|
#else
|
|
"license", gplv2plus,
|
|
#endif
|
|
"website", "http://libguestfs.org/",
|
|
"authors", authors,
|
|
NULL);
|
|
|
|
if (documenters[0] != NULL)
|
|
g_object_set (G_OBJECT (dialog),
|
|
"documenters", documenters,
|
|
NULL);
|
|
|
|
if (qa[0] != NULL)
|
|
gtk_about_dialog_add_credit_section (GTK_ABOUT_DIALOG (dialog),
|
|
"Quality assurance", qa);
|
|
|
|
if (others[0] != NULL)
|
|
gtk_about_dialog_add_credit_section (GTK_ABOUT_DIALOG (dialog),
|
|
"Libguestfs development", others);
|
|
|
|
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
|
|
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent));
|
|
gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
|
|
|
|
gtk_dialog_run (GTK_DIALOG (dialog));
|
|
gtk_widget_destroy (dialog);
|
|
}
|
|
|
|
/**
|
|
* Callback when the connection dialog C<Next> button has been
|
|
* clicked.
|
|
*/
|
|
static void
|
|
connection_next_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
/* Switch to the conversion dialog. */
|
|
show_conversion_dialog ();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Conversion dialog. */
|
|
|
|
static void populate_disks (GtkTreeView *disks_list);
|
|
static void populate_removable (GtkTreeView *removable_list);
|
|
static void populate_interfaces (GtkTreeView *interfaces_list);
|
|
static void toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data);
|
|
static void network_edited_callback (GtkCellRendererToggle *cell, gchar *path_str, gchar *new_text, gpointer data);
|
|
static gboolean maybe_identify_click (GtkWidget *interfaces_list, GdkEventButton *event, gpointer data);
|
|
static void set_disks_from_ui (struct config *);
|
|
static void set_removable_from_ui (struct config *);
|
|
static void set_interfaces_from_ui (struct config *);
|
|
static void conversion_back_clicked (GtkWidget *w, gpointer data);
|
|
static void start_conversion_clicked (GtkWidget *w, gpointer data);
|
|
static void vcpus_or_memory_check_callback (GtkWidget *w, gpointer data);
|
|
static void notify_ui_callback (int type, const char *data);
|
|
static int get_vcpus_from_conv_dlg (void);
|
|
static uint64_t get_memory_from_conv_dlg (void);
|
|
|
|
enum {
|
|
DISKS_COL_CONVERT = 0,
|
|
DISKS_COL_DEVICE,
|
|
NUM_DISKS_COLS,
|
|
};
|
|
|
|
enum {
|
|
REMOVABLE_COL_CONVERT = 0,
|
|
REMOVABLE_COL_DEVICE,
|
|
NUM_REMOVABLE_COLS,
|
|
};
|
|
|
|
enum {
|
|
INTERFACES_COL_CONVERT = 0,
|
|
INTERFACES_COL_DEVICE,
|
|
INTERFACES_COL_NETWORK,
|
|
NUM_INTERFACES_COLS,
|
|
};
|
|
|
|
/**
|
|
* Create the conversion dialog.
|
|
*
|
|
* This creates the dialog, but it is not displayed. See
|
|
* C<show_conversion_dialog>.
|
|
*/
|
|
static void
|
|
create_conversion_dialog (struct config *config)
|
|
{
|
|
GtkWidget *back;
|
|
GtkWidget *hbox, *left_vbox, *right_vbox;
|
|
GtkWidget *target_frame, *target_vbox, *target_tbl;
|
|
GtkWidget *guestname_label, *vcpus_label, *memory_label;
|
|
GtkWidget *output_frame, *output_vbox, *output_tbl;
|
|
GtkWidget *o_label, *oa_label, *oc_label, *of_label, *os_label;
|
|
GtkWidget *info_frame;
|
|
GtkWidget *disks_frame, *disks_sw;
|
|
GtkWidget *removable_frame, *removable_sw;
|
|
GtkWidget *interfaces_frame, *interfaces_sw;
|
|
char vcpus_str[64];
|
|
char memory_str[64];
|
|
|
|
conv_dlg = gtk_dialog_new ();
|
|
gtk_window_set_title (GTK_WINDOW (conv_dlg), getprogname ());
|
|
gtk_window_set_resizable (GTK_WINDOW (conv_dlg), FALSE);
|
|
/* XXX It would be nice not to have to set this explicitly, but
|
|
* if we don't then Gtk chooses a very small window.
|
|
*/
|
|
gtk_widget_set_size_request (conv_dlg, 900, 600);
|
|
|
|
/* The main dialog area. */
|
|
hbox_new (hbox, TRUE, 1);
|
|
vbox_new (left_vbox, FALSE, 1);
|
|
vbox_new (right_vbox, TRUE, 1);
|
|
|
|
/* The left column: target properties and output options. */
|
|
target_frame = gtk_frame_new (_("Target properties"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (target_frame), 4);
|
|
|
|
vbox_new (target_vbox, FALSE, 1);
|
|
|
|
table_new (target_tbl, 3, 3);
|
|
guestname_label = gtk_label_new_with_mnemonic (_("_Name:"));
|
|
table_attach (target_tbl, guestname_label,
|
|
0, 1, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (guestname_label, 1., 0.5);
|
|
guestname_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (guestname_label), guestname_entry);
|
|
if (config->guestname != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (guestname_entry), config->guestname);
|
|
table_attach (target_tbl, guestname_entry,
|
|
1, 2, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
vcpus_label = gtk_label_new_with_mnemonic (_("# _vCPUs:"));
|
|
table_attach (target_tbl, vcpus_label,
|
|
0, 1, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (vcpus_label, 1., 0.5);
|
|
vcpus_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (vcpus_label), vcpus_entry);
|
|
snprintf (vcpus_str, sizeof vcpus_str, "%d", config->vcpus);
|
|
gtk_entry_set_text (GTK_ENTRY (vcpus_entry), vcpus_str);
|
|
table_attach (target_tbl, vcpus_entry,
|
|
1, 2, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
|
|
vcpus_warning = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING,
|
|
GTK_ICON_SIZE_BUTTON);
|
|
table_attach (target_tbl, vcpus_warning,
|
|
2, 3, 1, 2, 0, 0, 1, 1);
|
|
|
|
memory_label = gtk_label_new_with_mnemonic (_("_Memory (MB):"));
|
|
table_attach (target_tbl, memory_label,
|
|
0, 1, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (memory_label, 1., 0.5);
|
|
memory_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (memory_label), memory_entry);
|
|
snprintf (memory_str, sizeof memory_str, "%" PRIu64,
|
|
config->memory / 1024 / 1024);
|
|
gtk_entry_set_text (GTK_ENTRY (memory_entry), memory_str);
|
|
table_attach (target_tbl, memory_entry,
|
|
1, 2, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
|
|
memory_warning = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING,
|
|
GTK_ICON_SIZE_BUTTON);
|
|
table_attach (target_tbl, memory_warning,
|
|
2, 3, 2, 3, 0, 0, 1, 1);
|
|
|
|
gtk_box_pack_start (GTK_BOX (target_vbox), target_tbl, TRUE, TRUE, 0);
|
|
|
|
target_warning_label = gtk_label_new ("");
|
|
gtk_label_set_line_wrap (GTK_LABEL (target_warning_label), TRUE);
|
|
gtk_label_set_line_wrap_mode (GTK_LABEL (target_warning_label),
|
|
PANGO_WRAP_WORD);
|
|
gtk_widget_set_size_request (target_warning_label, -1, 7 * 16);
|
|
gtk_box_pack_end (GTK_BOX (target_vbox), target_warning_label, TRUE, TRUE, 0);
|
|
|
|
gtk_container_add (GTK_CONTAINER (target_frame), target_vbox);
|
|
|
|
output_frame = gtk_frame_new (_("Virt-v2v output options"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (output_frame), 4);
|
|
|
|
vbox_new (output_vbox, FALSE, 1);
|
|
|
|
table_new (output_tbl, 5, 2);
|
|
o_label = gtk_label_new_with_mnemonic (_("Output _to (-o):"));
|
|
table_attach (output_tbl, o_label,
|
|
0, 1, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (o_label, 1., 0.5);
|
|
o_combo = gtk_combo_box_text_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (o_label), o_combo);
|
|
gtk_widget_set_tooltip_markup (o_combo, _("<b>libvirt</b> means send the converted guest to libvirt-managed KVM on the conversion server. <b>local</b> means put it in a directory on the conversion server. <b>rhv</b> means write it to RHV-M/oVirt. <b>glance</b> means write it to OpenStack Glance. See the virt-v2v(1) manual page for more information about output options."));
|
|
repopulate_output_combo (config);
|
|
table_attach (output_tbl, o_combo,
|
|
1, 2, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
oc_label = gtk_label_new_with_mnemonic (_("_Output conn. (-oc):"));
|
|
table_attach (output_tbl, oc_label,
|
|
0, 1, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (oc_label, 1., 0.5);
|
|
oc_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (oc_label), oc_entry);
|
|
gtk_widget_set_tooltip_markup (oc_entry, _("For <b>libvirt</b> only, the libvirt connection URI, or leave blank to add the guest to the default libvirt instance on the conversion server. For others, leave this field blank."));
|
|
if (config->output_connection != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (oc_entry), config->output_connection);
|
|
table_attach (output_tbl, oc_entry,
|
|
1, 2, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
os_label = gtk_label_new_with_mnemonic (_("Output _storage (-os):"));
|
|
table_attach (output_tbl, os_label,
|
|
0, 1, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (os_label, 1., 0.5);
|
|
os_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (os_label), os_entry);
|
|
gtk_widget_set_tooltip_markup (os_entry, _("For <b>local</b>, put the directory name on the conversion server. For <b>rhv</b>, put the Export Storage Domain (server:/mountpoint). For others, leave this field blank."));
|
|
if (config->output_storage != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (os_entry), config->output_storage);
|
|
table_attach (output_tbl, os_entry,
|
|
1, 2, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
of_label = gtk_label_new_with_mnemonic (_("Output _format (-of):"));
|
|
table_attach (output_tbl, of_label,
|
|
0, 1, 3, 4, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (of_label, 1., 0.5);
|
|
of_entry = gtk_entry_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (of_label), of_entry);
|
|
gtk_widget_set_tooltip_markup (of_entry, _("The output disk format, typically <b>raw</b> or <b>qcow2</b>. If blank, defaults to <b>raw</b>."));
|
|
if (config->output_format != NULL)
|
|
gtk_entry_set_text (GTK_ENTRY (of_entry), config->output_format);
|
|
table_attach (output_tbl, of_entry,
|
|
1, 2, 3, 4, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
oa_label = gtk_label_new_with_mnemonic (_("Output _allocation (-oa):"));
|
|
table_attach (output_tbl, oa_label,
|
|
0, 1, 4, 5, GTK_FILL, GTK_FILL, 1, 1);
|
|
set_alignment (oa_label, 1., 0.5);
|
|
oa_combo = gtk_combo_box_text_new ();
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (oa_label), oa_combo);
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (oa_combo),
|
|
"sparse");
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (oa_combo),
|
|
"preallocated");
|
|
switch (config->output_allocation) {
|
|
case OUTPUT_ALLOCATION_PREALLOCATED:
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (oa_combo), 1);
|
|
break;
|
|
default:
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (oa_combo), 0);
|
|
break;
|
|
}
|
|
table_attach (output_tbl, oa_combo,
|
|
1, 2, 4, 5, GTK_FILL, GTK_FILL, 1, 1);
|
|
|
|
gtk_box_pack_start (GTK_BOX (output_vbox), output_tbl, TRUE, TRUE, 0);
|
|
gtk_container_add (GTK_CONTAINER (output_frame), output_vbox);
|
|
|
|
info_frame = gtk_frame_new (_("Information"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (info_frame), 4);
|
|
info_label = gtk_label_new (NULL);
|
|
set_alignment (info_label, 0.1, 0.5);
|
|
set_info_label ();
|
|
gtk_container_add (GTK_CONTAINER (info_frame), info_label);
|
|
|
|
/* The right column: select devices to be converted. */
|
|
disks_frame = gtk_frame_new (_("Fixed hard disks"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (disks_frame), 4);
|
|
disks_sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_container_set_border_width (GTK_CONTAINER (disks_sw), 8);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (disks_sw),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
disks_list = gtk_tree_view_new ();
|
|
populate_disks (GTK_TREE_VIEW (disks_list));
|
|
scrolled_window_add_with_viewport (disks_sw, disks_list);
|
|
gtk_container_add (GTK_CONTAINER (disks_frame), disks_sw);
|
|
|
|
removable_frame = gtk_frame_new (_("Removable media"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (removable_frame), 4);
|
|
removable_sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_container_set_border_width (GTK_CONTAINER (removable_sw), 8);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (removable_sw),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
removable_list = gtk_tree_view_new ();
|
|
populate_removable (GTK_TREE_VIEW (removable_list));
|
|
scrolled_window_add_with_viewport (removable_sw, removable_list);
|
|
gtk_container_add (GTK_CONTAINER (removable_frame), removable_sw);
|
|
|
|
interfaces_frame = gtk_frame_new (_("Network interfaces"));
|
|
gtk_container_set_border_width (GTK_CONTAINER (interfaces_frame), 4);
|
|
interfaces_sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_container_set_border_width (GTK_CONTAINER (interfaces_sw), 8);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (interfaces_sw),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
interfaces_list = gtk_tree_view_new ();
|
|
/* See maybe_identify_click below for what we're doing. */
|
|
g_signal_connect (interfaces_list, "button-press-event",
|
|
G_CALLBACK (maybe_identify_click), NULL);
|
|
gtk_widget_set_tooltip_markup (interfaces_list, _("Left click on an interface name to flash the light on the physical interface."));
|
|
populate_interfaces (GTK_TREE_VIEW (interfaces_list));
|
|
scrolled_window_add_with_viewport (interfaces_sw, interfaces_list);
|
|
gtk_container_add (GTK_CONTAINER (interfaces_frame), interfaces_sw);
|
|
|
|
/* Pack the top level dialog. */
|
|
gtk_box_pack_start (GTK_BOX (left_vbox), target_frame, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (left_vbox), output_frame, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (left_vbox), info_frame, TRUE, TRUE, 0);
|
|
|
|
gtk_box_pack_start (GTK_BOX (right_vbox), disks_frame, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (right_vbox), removable_frame, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (right_vbox), interfaces_frame, TRUE, TRUE, 0);
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), left_vbox, TRUE, TRUE, 0);
|
|
gtk_box_pack_start (GTK_BOX (hbox), right_vbox, TRUE, TRUE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (conv_dlg))),
|
|
hbox, TRUE, TRUE, 0);
|
|
|
|
/* Buttons. */
|
|
gtk_dialog_add_buttons (GTK_DIALOG (conv_dlg),
|
|
_("_Back"), 1,
|
|
_("Start _conversion"), 2,
|
|
NULL);
|
|
back = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 1);
|
|
start_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 2);
|
|
|
|
/* Signals. */
|
|
g_signal_connect_swapped (G_OBJECT (conv_dlg), "destroy",
|
|
G_CALLBACK (gtk_main_quit), NULL);
|
|
g_signal_connect (G_OBJECT (back), "clicked",
|
|
G_CALLBACK (conversion_back_clicked), NULL);
|
|
g_signal_connect (G_OBJECT (start_button), "clicked",
|
|
G_CALLBACK (start_conversion_clicked), config);
|
|
g_signal_connect (G_OBJECT (vcpus_entry), "changed",
|
|
G_CALLBACK (vcpus_or_memory_check_callback), NULL);
|
|
g_signal_connect (G_OBJECT (memory_entry), "changed",
|
|
G_CALLBACK (vcpus_or_memory_check_callback), NULL);
|
|
}
|
|
|
|
/**
|
|
* Hide all other dialogs and show the conversion dialog.
|
|
*/
|
|
static void
|
|
show_conversion_dialog (void)
|
|
{
|
|
/* Hide the other dialogs. */
|
|
gtk_widget_hide (conn_dlg);
|
|
gtk_widget_hide (run_dlg);
|
|
|
|
/* Show the conversion dialog. */
|
|
gtk_widget_show_all (conv_dlg);
|
|
gtk_widget_hide (vcpus_warning);
|
|
gtk_widget_hide (memory_warning);
|
|
|
|
/* output_drivers may have been updated, so repopulate o_combo. */
|
|
repopulate_output_combo (NULL);
|
|
}
|
|
|
|
/**
|
|
* Update the C<Information> section in the conversion dialog.
|
|
*
|
|
* Note that C<v2v_version> (the remote virt-v2v version) is read from
|
|
* the remote virt-v2v in the C<test_connection> function.
|
|
*/
|
|
static void
|
|
set_info_label (void)
|
|
{
|
|
CLEANUP_FREE char *text;
|
|
int r;
|
|
|
|
if (!v2v_version)
|
|
r = asprintf (&text, _("virt-p2v (client):\n%s"), PACKAGE_VERSION);
|
|
else
|
|
r = asprintf (&text,
|
|
_("virt-p2v (client):\n"
|
|
"%s\n"
|
|
"virt-v2v (conversion server):\n"
|
|
"%s"),
|
|
PACKAGE_VERSION_FULL, v2v_version);
|
|
if (r == -1) {
|
|
perror ("asprintf");
|
|
return;
|
|
}
|
|
|
|
gtk_label_set_text (GTK_LABEL (info_label), text);
|
|
}
|
|
|
|
/**
|
|
* Repopulate the list of output drivers in the C<Output to (-o)>
|
|
* combo. The list of drivers is read from the remote virt-v2v
|
|
* instance in C<test_connection>.
|
|
*/
|
|
static void
|
|
repopulate_output_combo (struct config *config)
|
|
{
|
|
GtkTreeModel *model;
|
|
CLEANUP_FREE char *output;
|
|
size_t i;
|
|
|
|
/* Which driver is currently selected? */
|
|
if (config && config->output)
|
|
output = strdup (config->output);
|
|
else
|
|
output = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (o_combo));
|
|
|
|
/* Remove existing rows in o_combo. */
|
|
model = gtk_combo_box_get_model (GTK_COMBO_BOX (o_combo));
|
|
gtk_list_store_clear (GTK_LIST_STORE (model));
|
|
|
|
/* List of output_drivers from virt-v2v not read yet, so present
|
|
* a standard set of drivers.
|
|
*/
|
|
if (output_drivers == NULL) {
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (o_combo), "libvirt");
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (o_combo), "local");
|
|
/* Use rhev instead of rhv here so we can work with old virt-v2v. */
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (o_combo), "rhev");
|
|
if (output == NULL || STREQ (output, "libvirt"))
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (o_combo), 0);
|
|
else if (STREQ (output, "local"))
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (o_combo), 1);
|
|
else if (STREQ (output, "rhev"))
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (o_combo), 2);
|
|
}
|
|
/* List of -o options read from remote virt-v2v --machine-readable. */
|
|
else {
|
|
for (i = 0; output_drivers[i] != NULL; ++i)
|
|
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (o_combo),
|
|
output_drivers[i]);
|
|
if (output) {
|
|
for (i = 0; output_drivers[i] != NULL; ++i)
|
|
if (STREQ (output, output_drivers[i]))
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (o_combo), i);
|
|
}
|
|
else
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (o_combo), 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populate the C<Fixed hard disks> treeview.
|
|
*/
|
|
static void
|
|
populate_disks (GtkTreeView *disks_list)
|
|
{
|
|
GtkListStore *disks_store;
|
|
GtkCellRenderer *disks_col_convert, *disks_col_device;
|
|
GtkTreeIter iter;
|
|
size_t i;
|
|
|
|
disks_store = gtk_list_store_new (NUM_DISKS_COLS,
|
|
G_TYPE_BOOLEAN, G_TYPE_STRING);
|
|
if (all_disks != NULL) {
|
|
for (i = 0; all_disks[i] != NULL; ++i) {
|
|
uint64_t size;
|
|
CLEANUP_FREE char *size_gb = NULL;
|
|
CLEANUP_FREE char *model = NULL;
|
|
CLEANUP_FREE char *serial = NULL;
|
|
CLEANUP_FREE char *device_descr = NULL;
|
|
|
|
if (all_disks[i][0] != '/') { /* not using --test-disk */
|
|
size = get_blockdev_size (all_disks[i]);
|
|
if (asprintf (&size_gb, "%" PRIu64 "G", size) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
model = get_blockdev_model (all_disks[i]);
|
|
serial = get_blockdev_serial (all_disks[i]);
|
|
}
|
|
|
|
if (asprintf (&device_descr,
|
|
"<b>%s</b>\n"
|
|
"<small>"
|
|
"%s %s\n"
|
|
"%s%s"
|
|
"</small>",
|
|
all_disks[i],
|
|
size_gb ? size_gb : "", model ? model : "",
|
|
serial ? "s/n " : "", serial ? serial : "") == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
|
|
gtk_list_store_append (disks_store, &iter);
|
|
gtk_list_store_set (disks_store, &iter,
|
|
DISKS_COL_CONVERT, TRUE,
|
|
DISKS_COL_DEVICE, device_descr,
|
|
-1);
|
|
}
|
|
}
|
|
gtk_tree_view_set_model (disks_list,
|
|
GTK_TREE_MODEL (disks_store));
|
|
gtk_tree_view_set_headers_visible (disks_list, TRUE);
|
|
disks_col_convert = gtk_cell_renderer_toggle_new ();
|
|
gtk_tree_view_insert_column_with_attributes (disks_list,
|
|
-1,
|
|
_("Convert"),
|
|
disks_col_convert,
|
|
"active", DISKS_COL_CONVERT,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (disks_col_convert, 0.5, 0.0);
|
|
disks_col_device = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (disks_list,
|
|
-1,
|
|
_("Device"),
|
|
disks_col_device,
|
|
"markup", DISKS_COL_DEVICE,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (disks_col_device, 0.0, 0.0);
|
|
|
|
g_signal_connect (disks_col_convert, "toggled",
|
|
G_CALLBACK (toggled), disks_store);
|
|
}
|
|
|
|
/**
|
|
* Populate the C<Removable media> treeview.
|
|
*/
|
|
static void
|
|
populate_removable (GtkTreeView *removable_list)
|
|
{
|
|
GtkListStore *removable_store;
|
|
GtkCellRenderer *removable_col_convert, *removable_col_device;
|
|
GtkTreeIter iter;
|
|
size_t i;
|
|
|
|
removable_store = gtk_list_store_new (NUM_REMOVABLE_COLS,
|
|
G_TYPE_BOOLEAN, G_TYPE_STRING);
|
|
if (all_removable != NULL) {
|
|
for (i = 0; all_removable[i] != NULL; ++i) {
|
|
CLEANUP_FREE char *device_descr = NULL;
|
|
|
|
if (asprintf (&device_descr, "<b>%s</b>\n", all_removable[i]) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
|
|
gtk_list_store_append (removable_store, &iter);
|
|
gtk_list_store_set (removable_store, &iter,
|
|
REMOVABLE_COL_CONVERT, TRUE,
|
|
REMOVABLE_COL_DEVICE, device_descr,
|
|
-1);
|
|
}
|
|
}
|
|
gtk_tree_view_set_model (removable_list,
|
|
GTK_TREE_MODEL (removable_store));
|
|
gtk_tree_view_set_headers_visible (removable_list, TRUE);
|
|
removable_col_convert = gtk_cell_renderer_toggle_new ();
|
|
gtk_tree_view_insert_column_with_attributes (removable_list,
|
|
-1,
|
|
_("Convert"),
|
|
removable_col_convert,
|
|
"active", REMOVABLE_COL_CONVERT,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (removable_col_convert, 0.5, 0.0);
|
|
removable_col_device = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (removable_list,
|
|
-1,
|
|
_("Device"),
|
|
removable_col_device,
|
|
"markup", REMOVABLE_COL_DEVICE,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (removable_col_device, 0.0, 0.0);
|
|
|
|
g_signal_connect (removable_col_convert, "toggled",
|
|
G_CALLBACK (toggled), removable_store);
|
|
}
|
|
|
|
/**
|
|
* Populate the C<Network interfaces> treeview.
|
|
*/
|
|
static void
|
|
populate_interfaces (GtkTreeView *interfaces_list)
|
|
{
|
|
GtkListStore *interfaces_store;
|
|
GtkCellRenderer *interfaces_col_convert, *interfaces_col_device,
|
|
*interfaces_col_network;
|
|
GtkTreeIter iter;
|
|
size_t i;
|
|
|
|
interfaces_store = gtk_list_store_new (NUM_INTERFACES_COLS,
|
|
G_TYPE_BOOLEAN, G_TYPE_STRING,
|
|
G_TYPE_STRING);
|
|
if (all_interfaces) {
|
|
for (i = 0; all_interfaces[i] != NULL; ++i) {
|
|
const char *if_name = all_interfaces[i];
|
|
CLEANUP_FREE char *device_descr = NULL;
|
|
CLEANUP_FREE char *if_addr = get_if_addr (if_name);
|
|
CLEANUP_FREE char *if_vendor = get_if_vendor (if_name, 40);
|
|
|
|
if (asprintf (&device_descr,
|
|
"<b>%s</b>\n"
|
|
"<small>"
|
|
"%s\n"
|
|
"%s"
|
|
"</small>\n"
|
|
"<small><u><span foreground=\"blue\">Identify interface</span></u></small>",
|
|
if_name,
|
|
if_addr ? : _("Unknown"),
|
|
if_vendor ? : _("Unknown")) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
|
|
gtk_list_store_append (interfaces_store, &iter);
|
|
gtk_list_store_set (interfaces_store, &iter,
|
|
/* Only convert the first interface. As
|
|
* they are sorted, this is usually the
|
|
* physical interface.
|
|
*/
|
|
INTERFACES_COL_CONVERT, i == 0,
|
|
INTERFACES_COL_DEVICE, device_descr,
|
|
INTERFACES_COL_NETWORK, "default",
|
|
-1);
|
|
}
|
|
}
|
|
gtk_tree_view_set_model (interfaces_list,
|
|
GTK_TREE_MODEL (interfaces_store));
|
|
gtk_tree_view_set_headers_visible (interfaces_list, TRUE);
|
|
interfaces_col_convert = gtk_cell_renderer_toggle_new ();
|
|
gtk_tree_view_insert_column_with_attributes (interfaces_list,
|
|
-1,
|
|
_("Convert"),
|
|
interfaces_col_convert,
|
|
"active", INTERFACES_COL_CONVERT,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (interfaces_col_convert, 0.5, 0.0);
|
|
interfaces_col_device = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (interfaces_list,
|
|
-1,
|
|
_("Device"),
|
|
interfaces_col_device,
|
|
"markup", INTERFACES_COL_DEVICE,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (interfaces_col_device, 0.0, 0.0);
|
|
interfaces_col_network = gtk_cell_renderer_text_new ();
|
|
gtk_tree_view_insert_column_with_attributes (interfaces_list,
|
|
-1,
|
|
_("Connect to virtual network"),
|
|
interfaces_col_network,
|
|
"text", INTERFACES_COL_NETWORK,
|
|
NULL);
|
|
gtk_cell_renderer_set_alignment (interfaces_col_network, 0.0, 0.0);
|
|
|
|
g_signal_connect (interfaces_col_convert, "toggled",
|
|
G_CALLBACK (toggled), interfaces_store);
|
|
|
|
g_object_set (interfaces_col_network, "editable", TRUE, NULL);
|
|
g_signal_connect (interfaces_col_network, "edited",
|
|
G_CALLBACK (network_edited_callback), interfaces_store);
|
|
}
|
|
|
|
static void
|
|
toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
|
|
{
|
|
GtkTreeModel *model = data;
|
|
GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
|
|
GtkTreeIter iter;
|
|
gboolean v;
|
|
|
|
gtk_tree_model_get_iter (model, &iter, path);
|
|
gtk_tree_model_get (model, &iter, 0 /* CONVERT */, &v, -1);
|
|
v ^= 1;
|
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0 /* CONVERT */, v, -1);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
static void
|
|
network_edited_callback (GtkCellRendererToggle *cell, gchar *path_str,
|
|
gchar *new_text, gpointer data)
|
|
{
|
|
GtkTreeModel *model = data;
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
|
|
if (new_text == NULL || STREQ (new_text, ""))
|
|
return;
|
|
|
|
path = gtk_tree_path_new_from_string (path_str);
|
|
|
|
gtk_tree_model_get_iter (model, &iter, path);
|
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
|
INTERFACES_COL_NETWORK, new_text, -1);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
/**
|
|
* When the user clicks on the interface name on the list of
|
|
* interfaces, we want to run C<ethtool --identify>, which usually
|
|
* makes some lights flash on the physical interface.
|
|
*
|
|
* We cannot catch clicks on the cell itself, so we have to go via a
|
|
* more obscure route. See L<http://stackoverflow.com/a/27207433> and
|
|
* L<https://en.wikibooks.org/wiki/GTK%2B_By_Example/Tree_View/Events>
|
|
*/
|
|
static gboolean
|
|
maybe_identify_click (GtkWidget *interfaces_list, GdkEventButton *event,
|
|
gpointer data)
|
|
{
|
|
gboolean ret = FALSE; /* Did we handle this event? */
|
|
|
|
/* Single left click only. */
|
|
if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
|
|
GtkTreePath *path;
|
|
GtkTreeViewColumn *column;
|
|
|
|
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (interfaces_list),
|
|
event->x, event->y,
|
|
&path, &column, NULL, NULL)) {
|
|
GList *cols;
|
|
gint column_index;
|
|
|
|
/* Get column index. */
|
|
cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (interfaces_list));
|
|
column_index = g_list_index (cols, (gpointer) column);
|
|
g_list_free (cols);
|
|
|
|
if (column_index == INTERFACES_COL_DEVICE) {
|
|
const gint *indices;
|
|
gint row_index;
|
|
const char *if_name;
|
|
char *cmd;
|
|
|
|
/* Get the row index. */
|
|
indices = gtk_tree_path_get_indices (path);
|
|
row_index = indices[0];
|
|
|
|
/* And the interface name. */
|
|
if_name = all_interfaces[row_index];
|
|
|
|
/* Issue the ethtool command in the background. */
|
|
if (asprintf (&cmd, "ethtool --identify '%s' 10 &", if_name) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
printf ("%s\n", cmd);
|
|
ignore_value (system (cmd));
|
|
|
|
free (cmd);
|
|
|
|
ret = TRUE; /* We handled this event. */
|
|
}
|
|
|
|
gtk_tree_path_free (path);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
set_from_ui_generic (char **all, char ***ret, GtkTreeView *list)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gboolean b, v;
|
|
size_t i, j;
|
|
|
|
if (all == NULL) {
|
|
guestfs_int_free_string_list (*ret);
|
|
*ret = NULL;
|
|
return;
|
|
}
|
|
|
|
model = gtk_tree_view_get_model (list);
|
|
|
|
guestfs_int_free_string_list (*ret);
|
|
*ret = malloc ((1 + guestfs_int_count_strings (all)) * sizeof (char *));
|
|
if (*ret == NULL)
|
|
error (EXIT_FAILURE, errno, "malloc");
|
|
i = j = 0;
|
|
|
|
b = gtk_tree_model_get_iter_first (model, &iter);
|
|
while (b) {
|
|
gtk_tree_model_get (model, &iter, 0 /* CONVERT */, &v, -1);
|
|
if (v) {
|
|
assert (all[i] != NULL);
|
|
(*ret)[j++] = strdup (all[i]);
|
|
}
|
|
b = gtk_tree_model_iter_next (model, &iter);
|
|
++i;
|
|
}
|
|
|
|
(*ret)[j] = NULL;
|
|
}
|
|
|
|
static void
|
|
set_disks_from_ui (struct config *config)
|
|
{
|
|
set_from_ui_generic (all_disks, &config->disks,
|
|
GTK_TREE_VIEW (disks_list));
|
|
}
|
|
|
|
static void
|
|
set_removable_from_ui (struct config *config)
|
|
{
|
|
set_from_ui_generic (all_removable, &config->removable,
|
|
GTK_TREE_VIEW (removable_list));
|
|
}
|
|
|
|
static void
|
|
set_interfaces_from_ui (struct config *config)
|
|
{
|
|
set_from_ui_generic (all_interfaces, &config->interfaces,
|
|
GTK_TREE_VIEW (interfaces_list));
|
|
}
|
|
|
|
static void
|
|
set_network_map_from_ui (struct config *config)
|
|
{
|
|
GtkTreeView *list;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gboolean b;
|
|
const char *s;
|
|
size_t i, j;
|
|
|
|
if (all_interfaces == NULL) {
|
|
guestfs_int_free_string_list (config->network_map);
|
|
config->network_map = NULL;
|
|
return;
|
|
}
|
|
|
|
list = GTK_TREE_VIEW (interfaces_list);
|
|
model = gtk_tree_view_get_model (list);
|
|
|
|
guestfs_int_free_string_list (config->network_map);
|
|
config->network_map =
|
|
malloc ((1 + guestfs_int_count_strings (all_interfaces))
|
|
* sizeof (char *));
|
|
if (config->network_map == NULL)
|
|
error (EXIT_FAILURE, errno, "malloc");
|
|
i = j = 0;
|
|
|
|
b = gtk_tree_model_get_iter_first (model, &iter);
|
|
while (b) {
|
|
gtk_tree_model_get (model, &iter, INTERFACES_COL_NETWORK, &s, -1);
|
|
if (s) {
|
|
assert (all_interfaces[i] != NULL);
|
|
if (asprintf (&config->network_map[j], "%s:%s",
|
|
all_interfaces[i], s) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
++j;
|
|
}
|
|
b = gtk_tree_model_iter_next (model, &iter);
|
|
++i;
|
|
}
|
|
|
|
config->network_map[j] = NULL;
|
|
}
|
|
|
|
/**
|
|
* The conversion dialog C<Back> button has been clicked.
|
|
*/
|
|
static void
|
|
conversion_back_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
/* Switch to the connection dialog. */
|
|
show_connection_dialog ();
|
|
|
|
/* Better disable the Next button so the user is forced to
|
|
* do "Test connection" again.
|
|
*/
|
|
gtk_widget_set_sensitive (next_button, FALSE);
|
|
}
|
|
|
|
static char *concat_warning (char *warning, const char *fs, ...)
|
|
__attribute__((format (printf,2,3)));
|
|
|
|
static char *
|
|
concat_warning (char *warning, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
char *msg;
|
|
size_t len, len2;
|
|
int r;
|
|
|
|
if (warning == NULL) {
|
|
warning = strdup ("");
|
|
if (warning == NULL)
|
|
malloc_fail:
|
|
error (EXIT_FAILURE, errno, "malloc");
|
|
}
|
|
|
|
len = strlen (warning);
|
|
if (len > 0 && warning[len-1] != '\n' && fs[0] != '\n') {
|
|
warning = concat_warning (warning, "\n");
|
|
len = strlen (warning);
|
|
}
|
|
|
|
va_start (args, fs);
|
|
r = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
if (r == -1) goto malloc_fail;
|
|
|
|
len2 = strlen (msg);
|
|
warning = realloc (warning, len + len2 + 1);
|
|
if (warning == NULL) goto malloc_fail;
|
|
memcpy (&warning[len], msg, len2 + 1);
|
|
free (msg);
|
|
|
|
return warning;
|
|
}
|
|
|
|
/**
|
|
* Display a warning if the vCPUs or memory is outside the supported
|
|
* range (L<https://bugzilla.redhat.com/823758>).
|
|
*/
|
|
static void
|
|
vcpus_or_memory_check_callback (GtkWidget *w, gpointer data)
|
|
{
|
|
int vcpus;
|
|
uint64_t memory;
|
|
CLEANUP_FREE char *warning = NULL;
|
|
|
|
vcpus = get_vcpus_from_conv_dlg ();
|
|
memory = get_memory_from_conv_dlg ();
|
|
|
|
if (vcpus > MAX_SUPPORTED_VCPUS) {
|
|
gtk_widget_show (vcpus_warning);
|
|
|
|
warning = concat_warning (warning,
|
|
_("Number of virtual CPUs is larger than what is supported for KVM (max: %d)."),
|
|
MAX_SUPPORTED_VCPUS);
|
|
}
|
|
else
|
|
gtk_widget_hide (vcpus_warning);
|
|
|
|
if (memory > MAX_SUPPORTED_MEMORY_MB * 1024 * 1024) {
|
|
gtk_widget_show (memory_warning);
|
|
|
|
warning = concat_warning (warning,
|
|
_("Memory size is larger than what is supported for KVM (max: %" PRIu64 ")."),
|
|
MAX_SUPPORTED_MEMORY_MB);
|
|
}
|
|
else
|
|
gtk_widget_hide (memory_warning);
|
|
|
|
if (warning != NULL) {
|
|
warning = concat_warning (warning,
|
|
_("If you ignore this warning, conversion can still succeed, but the guest may not work or may not be supported on the target."));
|
|
gtk_label_set_text (GTK_LABEL (target_warning_label), warning);
|
|
}
|
|
else
|
|
gtk_label_set_text (GTK_LABEL (target_warning_label), "");
|
|
}
|
|
|
|
static int
|
|
get_vcpus_from_conv_dlg (void)
|
|
{
|
|
const char *str;
|
|
int i;
|
|
|
|
str = gtk_entry_get_text (GTK_ENTRY (vcpus_entry));
|
|
if (sscanf (str, "%d", &i) == 1 && i > 0)
|
|
return i;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static uint64_t
|
|
get_memory_from_conv_dlg (void)
|
|
{
|
|
const char *str;
|
|
uint64_t i;
|
|
|
|
str = gtk_entry_get_text (GTK_ENTRY (memory_entry));
|
|
if (sscanf (str, "%" SCNu64, &i) == 1 && i >= 256)
|
|
return i * 1024 * 1024;
|
|
else
|
|
return UINT64_C (1024) * 1024 * 1024;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Running dialog. */
|
|
|
|
static gboolean set_log_dir (gpointer remote_dir);
|
|
static gboolean set_status (gpointer msg);
|
|
static gboolean add_v2v_output (gpointer msg);
|
|
static void *start_conversion_thread (void *data);
|
|
static gboolean conversion_error (gpointer user_data);
|
|
static gboolean conversion_finished (gpointer user_data);
|
|
static void cancel_conversion_dialog (GtkWidget *w, gpointer data);
|
|
static void reboot_clicked (GtkWidget *w, gpointer data);
|
|
static gboolean close_running_dialog (GtkWidget *w, GdkEvent *event, gpointer data);
|
|
|
|
/**
|
|
* Create the running dialog.
|
|
*
|
|
* This creates the dialog, but it is not displayed. See
|
|
* C<show_running_dialog>.
|
|
*/
|
|
static void
|
|
create_running_dialog (void)
|
|
{
|
|
size_t i;
|
|
static const char *tags[16] =
|
|
{ "black", "maroon", "green", "olive", "navy", "purple", "teal", "silver",
|
|
"gray", "red", "lime", "yellow", "blue", "fuchsia", "cyan", "white" };
|
|
GtkTextBuffer *buf;
|
|
|
|
run_dlg = gtk_dialog_new ();
|
|
gtk_window_set_title (GTK_WINDOW (run_dlg), getprogname ());
|
|
gtk_window_set_resizable (GTK_WINDOW (run_dlg), FALSE);
|
|
|
|
/* The main dialog area. */
|
|
v2v_output_sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (v2v_output_sw),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
gtk_widget_set_size_request (v2v_output_sw, 700, 400);
|
|
|
|
v2v_output = gtk_text_view_new ();
|
|
gtk_text_view_set_editable (GTK_TEXT_VIEW (v2v_output), FALSE);
|
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (v2v_output), GTK_WRAP_CHAR);
|
|
|
|
buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (v2v_output));
|
|
for (i = 0; i < 16; ++i) {
|
|
CLEANUP_FREE char *tag_name;
|
|
|
|
if (asprintf (&tag_name, "tag_%s", tags[i]) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
v2v_output_tags[i] =
|
|
gtk_text_buffer_create_tag (buf, tag_name, "foreground", tags[i], NULL);
|
|
}
|
|
|
|
#if GTK_CHECK_VERSION(3,16,0) /* gtk >= 3.16 */
|
|
/* XXX This only sets the "CSS" style. It's not clear how to set
|
|
* the particular font. However (by accident) this does at least
|
|
* set the widget to use a monospace font.
|
|
*/
|
|
GtkStyleContext *context = gtk_widget_get_style_context (v2v_output);
|
|
gtk_style_context_add_class (context, "monospace");
|
|
#else
|
|
PangoFontDescription *font;
|
|
font = pango_font_description_from_string ("Monospace 11");
|
|
#if GTK_CHECK_VERSION(3,0,0) /* gtk >= 3 */
|
|
gtk_widget_override_font (v2v_output, font);
|
|
#else
|
|
gtk_widget_modify_font (v2v_output, font);
|
|
#endif
|
|
pango_font_description_free (font);
|
|
#endif
|
|
|
|
log_label = gtk_label_new (NULL);
|
|
set_alignment (log_label, 0., 0.5);
|
|
set_padding (log_label, 10, 10);
|
|
set_log_dir (NULL);
|
|
status_label = gtk_label_new (NULL);
|
|
set_alignment (status_label, 0., 0.5);
|
|
set_padding (status_label, 10, 10);
|
|
|
|
gtk_container_add (GTK_CONTAINER (v2v_output_sw), v2v_output);
|
|
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (run_dlg))),
|
|
v2v_output_sw, TRUE, TRUE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (run_dlg))),
|
|
log_label, TRUE, TRUE, 0);
|
|
gtk_box_pack_start
|
|
(GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (run_dlg))),
|
|
status_label, TRUE, TRUE, 0);
|
|
|
|
/* Buttons. */
|
|
gtk_dialog_add_buttons (GTK_DIALOG (run_dlg),
|
|
_("_Cancel conversion ..."), 1,
|
|
_("_Reboot"), 2,
|
|
NULL);
|
|
cancel_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (run_dlg), 1);
|
|
gtk_widget_set_sensitive (cancel_button, FALSE);
|
|
reboot_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (run_dlg), 2);
|
|
gtk_widget_set_sensitive (reboot_button, FALSE);
|
|
|
|
/* Signals. */
|
|
g_signal_connect_swapped (G_OBJECT (run_dlg), "delete_event",
|
|
G_CALLBACK (close_running_dialog), NULL);
|
|
g_signal_connect_swapped (G_OBJECT (run_dlg), "destroy",
|
|
G_CALLBACK (gtk_main_quit), NULL);
|
|
g_signal_connect (G_OBJECT (cancel_button), "clicked",
|
|
G_CALLBACK (cancel_conversion_dialog), NULL);
|
|
g_signal_connect (G_OBJECT (reboot_button), "clicked",
|
|
G_CALLBACK (reboot_clicked), NULL);
|
|
}
|
|
|
|
/**
|
|
* Hide all other dialogs and show the running dialog.
|
|
*/
|
|
static void
|
|
show_running_dialog (void)
|
|
{
|
|
/* Hide the other dialogs. */
|
|
gtk_widget_hide (conn_dlg);
|
|
gtk_widget_hide (conv_dlg);
|
|
|
|
/* Show the running dialog. */
|
|
gtk_widget_show_all (run_dlg);
|
|
gtk_widget_set_sensitive (cancel_button, TRUE);
|
|
if (is_iso_environment)
|
|
gtk_widget_set_sensitive (reboot_button, FALSE);
|
|
}
|
|
|
|
/**
|
|
* Display the remote log directory in the running dialog.
|
|
*
|
|
* If this isn't called from the main thread, then you must only
|
|
* call it via an idle task (C<g_idle_add>).
|
|
*
|
|
* B<NB:> This frees the remote_dir (C<user_data> pointer) which was
|
|
* strdup'd in C<notify_ui_callback>.
|
|
*/
|
|
static gboolean
|
|
set_log_dir (gpointer user_data)
|
|
{
|
|
CLEANUP_FREE const char *remote_dir = user_data;
|
|
CLEANUP_FREE char *msg;
|
|
|
|
if (asprintf (&msg,
|
|
_("Debug information and log files "
|
|
"are saved to this directory "
|
|
"on the conversion server:\n"
|
|
"%s"),
|
|
remote_dir ? remote_dir : "") == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
|
|
gtk_label_set_text (GTK_LABEL (log_label), msg);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Display the conversion status in the running dialog.
|
|
*
|
|
* If this isn't called from the main thread, then you must only
|
|
* call it via an idle task (C<g_idle_add>).
|
|
*
|
|
* B<NB:> This frees the message (C<user_data> pointer) which was
|
|
* strdup'd in C<notify_ui_callback>.
|
|
*/
|
|
static gboolean
|
|
set_status (gpointer user_data)
|
|
{
|
|
CLEANUP_FREE const char *msg = user_data;
|
|
|
|
gtk_label_set_text (GTK_LABEL (status_label), msg);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Append output from the virt-v2v process to the buffer, and scroll
|
|
* to ensure it is visible.
|
|
*
|
|
* This function is able to parse ANSI colour sequences and more.
|
|
*
|
|
* If this isn't called from the main thread, then you must only
|
|
* call it via an idle task (C<g_idle_add>).
|
|
*
|
|
* B<NB:> This frees the message (C<user_data> pointer) which was
|
|
* strdup'd in C<notify_ui_callback>.
|
|
*/
|
|
static gboolean
|
|
add_v2v_output (gpointer user_data)
|
|
{
|
|
CLEANUP_FREE const char *msg = user_data;
|
|
const char *p;
|
|
static size_t linelen = 0;
|
|
static enum {
|
|
state_normal,
|
|
state_escape1, /* seen ESC, expecting [ */
|
|
state_escape2, /* seen ESC [, expecting 0 or 1 */
|
|
state_escape3, /* seen ESC [ 0/1, expecting ; or m */
|
|
state_escape4, /* seen ESC [ 0/1 ;, expecting 3 */
|
|
state_escape5, /* seen ESC [ 0/1 ; 3, expecting 1/2/4/5 */
|
|
state_escape6, /* seen ESC [ 0/1 ; 3 1/2/5/5, expecting m */
|
|
state_cr, /* seen CR */
|
|
state_truncating, /* truncating line until next \n */
|
|
} state = state_normal;
|
|
static int colour = 0;
|
|
static GtkTextTag *tag = NULL;
|
|
GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (v2v_output));
|
|
GtkTextIter iter, iter2;
|
|
const char *dots = " [...]";
|
|
|
|
for (p = msg; *p != '\0'; ++p) {
|
|
char c = *p;
|
|
|
|
switch (state) {
|
|
case state_normal:
|
|
if (c == '\r') /* Start of possible CRLF sequence. */
|
|
state = state_cr;
|
|
else if (c == '\x1b') { /* Start of an escape sequence. */
|
|
state = state_escape1;
|
|
colour = 0;
|
|
}
|
|
else if (c != '\n' && linelen >= 256) {
|
|
/* Gtk2 (in ~ Fedora 23) has a regression where it takes much
|
|
* longer to display long lines, to the point where the
|
|
* virt-p2v UI would still be slowly displaying kernel modules
|
|
* while the conversion had finished. For this reason,
|
|
* arbitrarily truncate very long lines.
|
|
*/
|
|
gtk_text_buffer_get_end_iter (buf, &iter);
|
|
gtk_text_buffer_insert_with_tags (buf, &iter,
|
|
dots, strlen (dots), tag, NULL);
|
|
state = state_truncating;
|
|
colour = 0;
|
|
tag = NULL;
|
|
}
|
|
else { /* Treat everything else as a normal char. */
|
|
if (c != '\n') linelen++; else linelen = 0;
|
|
gtk_text_buffer_get_end_iter (buf, &iter);
|
|
gtk_text_buffer_insert_with_tags (buf, &iter, &c, 1, tag, NULL);
|
|
}
|
|
break;
|
|
|
|
case state_escape1:
|
|
if (c == '[')
|
|
state = state_escape2;
|
|
else
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_escape2:
|
|
if (c == '0')
|
|
state = state_escape3;
|
|
else if (c == '1') {
|
|
state = state_escape3;
|
|
colour += 8;
|
|
}
|
|
else
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_escape3:
|
|
if (c == ';')
|
|
state = state_escape4;
|
|
else if (c == 'm') {
|
|
tag = NULL; /* restore text colour */
|
|
state = state_normal;
|
|
}
|
|
else
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_escape4:
|
|
if (c == '3')
|
|
state = state_escape5;
|
|
else
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_escape5:
|
|
if (c >= '0' && c <= '7') {
|
|
state = state_escape6;
|
|
colour += c - '0';
|
|
}
|
|
else
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_escape6:
|
|
if (c == 'm') {
|
|
assert (colour >= 0 && colour <= 15);
|
|
tag = v2v_output_tags[colour]; /* set colour tag */
|
|
}
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_cr:
|
|
if (c == '\n')
|
|
/* Process CRLF as single a newline character. */
|
|
p--;
|
|
else { /* Delete current (== last) line. */
|
|
linelen = 0;
|
|
gtk_text_buffer_get_end_iter (buf, &iter);
|
|
iter2 = iter;
|
|
gtk_text_iter_set_line_offset (&iter, 0);
|
|
/* Delete from iter..iter2 */
|
|
gtk_text_buffer_delete (buf, &iter, &iter2);
|
|
}
|
|
state = state_normal;
|
|
break;
|
|
|
|
case state_truncating:
|
|
if (c == '\n') {
|
|
p--;
|
|
state = state_normal;
|
|
}
|
|
break;
|
|
} /* switch (state) */
|
|
} /* for */
|
|
|
|
/* Scroll to the end of the buffer. */
|
|
gtk_text_buffer_get_end_iter (buf, &iter);
|
|
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (v2v_output), &iter,
|
|
0, FALSE, 0., 1.);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Callback when the C<Start conversion> button is clicked.
|
|
*/
|
|
static void
|
|
start_conversion_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
struct config *config = data;
|
|
const char *str;
|
|
char *str2;
|
|
GtkWidget *dlg;
|
|
struct config *copy;
|
|
int err;
|
|
pthread_t tid;
|
|
pthread_attr_t attr;
|
|
|
|
/* Unpack dialog fields and check them. */
|
|
free (config->guestname);
|
|
config->guestname = strdup (gtk_entry_get_text (GTK_ENTRY (guestname_entry)));
|
|
|
|
if (STREQ (config->guestname, "")) {
|
|
dlg = gtk_message_dialog_new (GTK_WINDOW (conv_dlg),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_OK,
|
|
_("The guest \"Name\" field is empty."));
|
|
gtk_window_set_title (GTK_WINDOW (dlg), _("Error"));
|
|
gtk_dialog_run (GTK_DIALOG (dlg));
|
|
gtk_widget_destroy (dlg);
|
|
gtk_widget_grab_focus (guestname_entry);
|
|
return;
|
|
}
|
|
|
|
config->vcpus = get_vcpus_from_conv_dlg ();
|
|
config->memory = get_memory_from_conv_dlg ();
|
|
|
|
/* Get the list of disks to be converted. */
|
|
set_disks_from_ui (config);
|
|
|
|
/* The list of disks must be non-empty. */
|
|
if (config->disks == NULL || guestfs_int_count_strings (config->disks) == 0) {
|
|
dlg = gtk_message_dialog_new (GTK_WINDOW (conv_dlg),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_OK,
|
|
_("No disks were selected for conversion.\n"
|
|
"At least one fixed hard disk must be selected.\n"));
|
|
gtk_window_set_title (GTK_WINDOW (dlg), _("Error"));
|
|
gtk_dialog_run (GTK_DIALOG (dlg));
|
|
gtk_widget_destroy (dlg);
|
|
return;
|
|
}
|
|
|
|
/* List of removable media and network interfaces. */
|
|
set_removable_from_ui (config);
|
|
set_interfaces_from_ui (config);
|
|
set_network_map_from_ui (config);
|
|
|
|
/* Output selection. */
|
|
free (config->output);
|
|
config->output =
|
|
gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (o_combo));
|
|
|
|
config->output_allocation = OUTPUT_ALLOCATION_NONE;
|
|
str2 = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (oa_combo));
|
|
if (str2) {
|
|
if (STREQ (str2, "sparse"))
|
|
config->output_allocation = OUTPUT_ALLOCATION_SPARSE;
|
|
else if (STREQ (str2, "preallocated"))
|
|
config->output_allocation = OUTPUT_ALLOCATION_PREALLOCATED;
|
|
free (str2);
|
|
}
|
|
|
|
free (config->output_connection);
|
|
str = gtk_entry_get_text (GTK_ENTRY (oc_entry));
|
|
if (str && STRNEQ (str, ""))
|
|
config->output_connection = strdup (str);
|
|
else
|
|
config->output_connection = NULL;
|
|
|
|
free (config->output_format);
|
|
str = gtk_entry_get_text (GTK_ENTRY (of_entry));
|
|
if (str && STRNEQ (str, ""))
|
|
config->output_format = strdup (str);
|
|
else
|
|
config->output_format = NULL;
|
|
|
|
free (config->output_storage);
|
|
str = gtk_entry_get_text (GTK_ENTRY (os_entry));
|
|
if (str && STRNEQ (str, ""))
|
|
config->output_storage = strdup (str);
|
|
else
|
|
config->output_storage = NULL;
|
|
|
|
/* Display the UI for conversion. */
|
|
show_running_dialog ();
|
|
|
|
/* Do the conversion, in a background thread. */
|
|
|
|
/* Give the conversion (background) thread its own copy of the
|
|
* config in case we update the config in the main thread.
|
|
*/
|
|
copy = copy_config (config);
|
|
|
|
pthread_attr_init (&attr);
|
|
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
|
|
err = pthread_create (&tid, &attr, start_conversion_thread, copy);
|
|
if (err != 0)
|
|
error (EXIT_FAILURE, err, "pthread_create");
|
|
pthread_attr_destroy (&attr);
|
|
}
|
|
|
|
/**
|
|
* This is the background thread which performs the conversion.
|
|
*/
|
|
static void *
|
|
start_conversion_thread (void *data)
|
|
{
|
|
struct config *copy = data;
|
|
int r;
|
|
|
|
r = start_conversion (copy, notify_ui_callback);
|
|
free_config (copy);
|
|
|
|
if (r == -1)
|
|
g_idle_add (conversion_error, NULL);
|
|
else
|
|
g_idle_add (conversion_finished, NULL);
|
|
|
|
/* Thread is detached anyway, so no one is waiting for the status. */
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<start_conversion_thread> (but run on the
|
|
* main thread) when there was an error during the conversion.
|
|
*/
|
|
static gboolean
|
|
conversion_error (gpointer user_data)
|
|
{
|
|
const char *err = get_conversion_error ();
|
|
GtkWidget *dlg;
|
|
|
|
dlg = gtk_message_dialog_new (GTK_WINDOW (run_dlg),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_OK,
|
|
_("Conversion failed: %s"), err);
|
|
gtk_window_set_title (GTK_WINDOW (dlg), _("Conversion failed"));
|
|
gtk_dialog_run (GTK_DIALOG (dlg));
|
|
gtk_widget_destroy (dlg);
|
|
|
|
/* Disable the cancel button. */
|
|
gtk_widget_set_sensitive (cancel_button, FALSE);
|
|
|
|
/* Enable the reboot button. */
|
|
if (is_iso_environment)
|
|
gtk_widget_set_sensitive (reboot_button, TRUE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Idle task called from C<start_conversion_thread> (but run on the
|
|
* main thread) when the conversion completed without errors.
|
|
*/
|
|
static gboolean
|
|
conversion_finished (gpointer user_data)
|
|
{
|
|
GtkWidget *dlg;
|
|
|
|
dlg = gtk_message_dialog_new (GTK_WINDOW (run_dlg),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_INFO,
|
|
GTK_BUTTONS_OK,
|
|
_("The conversion was successful."));
|
|
gtk_window_set_title (GTK_WINDOW (dlg), _("Conversion was successful"));
|
|
gtk_dialog_run (GTK_DIALOG (dlg));
|
|
gtk_widget_destroy (dlg);
|
|
|
|
/* Disable the cancel button. */
|
|
gtk_widget_set_sensitive (cancel_button, FALSE);
|
|
|
|
/* Enable the reboot button. */
|
|
if (is_iso_environment)
|
|
gtk_widget_set_sensitive (reboot_button, TRUE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* This is called from F<conversion.c>:C<start_conversion>
|
|
* when there is a status change or a log message.
|
|
*/
|
|
static void
|
|
notify_ui_callback (int type, const char *data)
|
|
{
|
|
/* Because we call the functions as idle callbacks which run
|
|
* in the main thread some time later, we must duplicate the
|
|
* 'data' parameter (which is always a \0-terminated string).
|
|
*
|
|
* This is freed by the idle task function.
|
|
*/
|
|
char *copy = strdup (data);
|
|
|
|
switch (type) {
|
|
case NOTIFY_LOG_DIR:
|
|
g_idle_add (set_log_dir, (gpointer) copy);
|
|
break;
|
|
|
|
case NOTIFY_REMOTE_MESSAGE:
|
|
g_idle_add (add_v2v_output, (gpointer) copy);
|
|
break;
|
|
|
|
case NOTIFY_STATUS:
|
|
g_idle_add (set_status, (gpointer) copy);
|
|
break;
|
|
|
|
default:
|
|
fprintf (stderr,
|
|
"%s: unknown message during conversion: type=%d data=%s\n",
|
|
getprogname (), type, data);
|
|
free (copy);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
close_running_dialog (GtkWidget *w, GdkEvent *event, gpointer data)
|
|
{
|
|
/* This function is called if the user tries to close the running
|
|
* dialog. This is the same as cancelling the conversion.
|
|
*/
|
|
if (conversion_is_running ()) {
|
|
cancel_conversion ();
|
|
return TRUE;
|
|
}
|
|
else
|
|
/* Conversion is not running, so this will delete the dialog. */
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* This is called when the user clicks on the "Cancel conversion"
|
|
* button. Since conversions can run for a long time, and cancelling
|
|
* the conversion is non-recoverable, this function displays a
|
|
* confirmation dialog before cancelling the conversion.
|
|
*/
|
|
static void
|
|
cancel_conversion_dialog (GtkWidget *w, gpointer data)
|
|
{
|
|
GtkWidget *dlg;
|
|
|
|
if (!conversion_is_running ())
|
|
return;
|
|
|
|
dlg = gtk_message_dialog_new (GTK_WINDOW (run_dlg),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_YES_NO,
|
|
_("Really cancel the conversion? "
|
|
"To convert this machine you will need to "
|
|
"re-run the conversion from the beginning."));
|
|
gtk_window_set_title (GTK_WINDOW (dlg), _("Cancel the conversion"));
|
|
if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_YES)
|
|
/* This makes start_conversion return an error (eventually). */
|
|
cancel_conversion ();
|
|
|
|
gtk_widget_destroy (dlg);
|
|
}
|
|
|
|
static void
|
|
reboot_clicked (GtkWidget *w, gpointer data)
|
|
{
|
|
if (!is_iso_environment)
|
|
return;
|
|
|
|
sync ();
|
|
sleep (2);
|
|
ignore_value (system ("/sbin/reboot"));
|
|
}
|