qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 3/3] gtk: add devices menu to allow changing removab


From: Anthony Liguori
Subject: [Qemu-devel] [PATCH 3/3] gtk: add devices menu to allow changing removable block devices
Date: Fri, 26 Apr 2013 14:43:07 -0500

To generate this menu, we first walk the composition tree to
find any device with a 'drive' property.  We then record these
devices and the BlockDriverState that they are associated with.

Then we use query-block to get the BDS state for each of the
discovered devices.

This code doesn't handle hot-plug yet but it should deal nicely
with someone using the human monitor.

Signed-off-by: Anthony Liguori <address@hidden>
---
 ui/gtk.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 302 insertions(+)

diff --git a/ui/gtk.c b/ui/gtk.c
index e12f228..c420914 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -64,6 +64,7 @@
 #include "x_keymap.h"
 #include "keymaps.h"
 #include "sysemu/char.h"
+#include "qapi/qmp/qevents.h"
 
 //#define DEBUG_GTK
 
@@ -139,6 +140,10 @@ typedef struct GtkDisplayState
     GtkWidget *grab_on_hover_item;
     GtkWidget *vga_item;
 
+    GtkWidget *devices_menu_item;
+    GtkWidget *devices_menu;
+    GHashTable *devices_map;
+
     int nb_vcs;
     VirtualConsole vc[MAX_VCS];
 
@@ -165,6 +170,8 @@ typedef struct GtkDisplayState
     bool external_pause_update;
 
     bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
+
+    Notifier event_notifier;
 } GtkDisplayState;
 
 static GtkDisplayState *global_state;
@@ -1382,6 +1389,296 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState 
*s, GtkAccelGroup *accel_g
     return view_menu;
 }
 
+typedef void (DeviceIterFunc)(const char *path, const char *property, void 
*opaque);
+
+static void device_foreach_proptype(const char *path, const char *proptype,
+                                    DeviceIterFunc *fn, void *opaque)
+{
+    ObjectPropertyInfoList *prop_list, *iter;
+
+    prop_list = qmp_qom_list(path, NULL);
+    for (iter = prop_list; iter; iter = iter->next) {
+        ObjectPropertyInfo *prop = iter->value;
+
+        if (strstart(prop->type, "child<", NULL)) {
+            char *subpath;
+            subpath = g_strdup_printf("%s/%s", path, prop->name);
+            device_foreach_proptype(subpath, proptype, fn, opaque);
+            g_free(subpath);
+        } else if (strcmp(prop->type, proptype) == 0) {
+            fn(path, prop->name, opaque);
+        }
+    }
+    qapi_free_ObjectPropertyInfoList(prop_list);
+}
+
+typedef enum DiskType
+{
+    DT_NORMAL,
+    DT_CDROM,
+    DT_FLOPPY,
+} DiskType;
+
+typedef struct BlockDeviceMenu
+{
+    GtkWidget *entry;
+    GtkWidget *submenu;
+    GtkWidget *eject;
+    GtkWidget *change;
+
+    char *name;
+    char *path;
+    char *desc;
+    DiskType disk_type;
+} BlockDeviceMenu;
+
+static void gd_block_device_menu_update(BlockDeviceMenu *bdm, BlockInfo *info)
+{
+    bool value;
+    const char *label = _("<No media>");
+
+    value = info->has_inserted && !info->locked;
+    gtk_widget_set_sensitive(bdm->eject, value);
+
+    value = !info->locked;
+    gtk_widget_set_sensitive(bdm->change, value);
+
+    if (info->has_inserted) {
+        label = info->inserted->file;
+        if (strlen(label) > 32) {
+            char *new_label;
+
+            new_label = strrchr(label, '/');
+            if (new_label) {
+                label = new_label + 1;
+            }
+        }
+    }
+
+    gtk_menu_item_set_label(GTK_MENU_ITEM(bdm->change), label);
+}
+
+static void gd_update_block_menus(GtkDisplayState *s)
+{
+    BlockInfoList *block_list, *iter;
+
+    block_list = qmp_query_block(NULL);
+    for (iter = block_list; iter; iter = iter->next) {
+        BlockInfo *info = iter->value;
+        BlockDeviceMenu *bdm;
+
+        if (!info->removable) {
+            continue;
+        }
+
+        bdm = g_hash_table_lookup(s->devices_map, info->device);
+        if (!bdm) {
+            continue;
+        }
+
+        gd_block_device_menu_update(bdm, info);
+    }
+    qapi_free_BlockInfoList(block_list);
+}
+
+static void gd_enum_disk(const char *path, const char *proptype, void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    Object *obj;
+    char *block_id;
+
+    obj = object_resolve_path(path, NULL);
+    g_assert(obj != NULL);
+
+    block_id = object_property_get_str(obj, proptype, NULL);
+    if (strcmp(block_id, "") != 0) {
+        BlockDeviceMenu *bdm;
+        DiskType disk_type;
+        char *type;
+        char *desc = NULL;
+
+        type = object_property_get_str(obj, "type", NULL);
+
+        if (strcmp(type, "ide-cd") == 0 || strcmp(type, "ide-hd") == 0) {
+            desc = object_property_get_str(obj, "drive-id", NULL);
+        } else {
+            desc = g_strdup(type);
+        }
+
+        if (strcmp(type, "ide-cd") == 0) {
+            disk_type = DT_CDROM;
+        } else if (strcmp(type, "isa-fdc") == 0) {
+            disk_type = DT_FLOPPY;
+        } else {
+            disk_type = DT_NORMAL;
+        }
+
+        bdm = g_malloc0(sizeof(*bdm));
+        bdm->name = g_strdup(block_id);
+        bdm->path = g_strdup(path);
+        bdm->desc = desc;
+        bdm->disk_type = disk_type;
+
+        g_free(type);
+
+        g_hash_table_insert(s->devices_map, bdm->name, bdm);
+    }
+    g_free(block_id);
+}
+
+static void gd_error(GtkDisplayState *s, const char *fmt, ...)
+{
+    GtkWidget *dialog;
+    char *message;
+    va_list ap;
+
+    va_start(ap, fmt);
+    message = g_strdup_vprintf(fmt, ap);
+    va_end(ap);
+
+    dialog = gtk_message_dialog_new(GTK_WINDOW(s->window), 
GTK_DIALOG_DESTROY_WITH_PARENT,
+                                    GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+                                    "%s", message);
+
+    g_free(message);
+
+    gtk_widget_show_all(dialog);
+
+    g_signal_connect_swapped(dialog, "response", 
G_CALLBACK(gtk_widget_destroy), dialog);
+}
+
+static void gd_menu_bdm_change_response(GtkDialog *dialog, gint response_id,
+                                        gpointer opaque)
+{
+    BlockDeviceMenu *bdm = opaque;
+
+    if (response_id == GTK_RESPONSE_ACCEPT) {
+        char *filename;
+        Error *local_err = NULL;
+
+        filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+
+        qmp_change(bdm->name, filename, false, NULL, &local_err);
+        if (local_err) {
+            gd_error(global_state,
+                     _("Block device change failed: %s"),
+                     error_get_pretty(local_err));
+            error_free(local_err);
+        }
+
+        g_free(filename);
+    }
+
+    gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void gd_menu_bdm_change(GtkMenuItem *item, void *opaque)
+{
+    GtkDisplayState *s = global_state;
+    BlockDeviceMenu *bdm = opaque;
+    GtkWidget *chooser;
+
+    chooser = gtk_file_chooser_dialog_new(_("Choose Image File"),
+                                          GTK_WINDOW(s->window),
+                                          GTK_FILE_CHOOSER_ACTION_OPEN,
+                                          GTK_STOCK_CANCEL, 
GTK_RESPONSE_CANCEL,
+                                          GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                                          NULL);
+
+    g_signal_connect(chooser, "response",
+                     G_CALLBACK(gd_menu_bdm_change_response), bdm);
+
+    gtk_widget_show_all(chooser);
+}
+
+static void gd_menu_bdm_eject(GtkMenuItem *item, void *opaque)
+{
+    BlockDeviceMenu *bdm = opaque;
+
+    qmp_eject(bdm->name, false, false, NULL);
+}
+
+static void gd_event_notify(Notifier *notifier, void *data)
+{
+    QDict *event = qobject_to_qdict(data);
+    const char *event_str;
+
+    event_str = qdict_get_str(event, "event");
+    if (strcmp(event_str, "DEVICE_TRAY_MOVED") == 0) {
+        gd_update_block_menus(global_state);
+    }
+}
+
+static GtkWidget *gd_create_menu_devices(GtkDisplayState *s, GtkAccelGroup 
*accel_group)
+{
+    GtkWidget *devices_menu;
+    BlockInfoList *block_list, *iter;
+
+    s->event_notifier.notify = gd_event_notify;
+    qmp_add_event_notifier(&s->event_notifier);
+    s->devices_map = g_hash_table_new(g_str_hash, g_str_equal);
+
+    /* We first search for devices that has a drive property. */
+    device_foreach_proptype("/", "drive", gd_enum_disk, s);
+
+    devices_menu = gtk_menu_new();
+    gtk_menu_set_accel_group(GTK_MENU(devices_menu), accel_group);
+
+    block_list = qmp_query_block(NULL);
+    for (iter = block_list; iter; iter = iter->next) {
+        BlockInfo *info = iter->value;
+        BlockDeviceMenu *bdm;
+        const char *stock_id = NULL;
+
+        /* Only show removable devices */
+        if (!info->removable) {
+            continue;
+        }
+
+        /* Only show devices were we could identify the actual device */
+        bdm = g_hash_table_lookup(s->devices_map, info->device);
+        if (!bdm) {
+            continue;
+        }
+
+        bdm->submenu = gtk_menu_new();
+        bdm->change = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
+        gtk_menu_item_set_label(GTK_MENU_ITEM(bdm->change), _("<No media>"));
+        bdm->eject = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
+        gtk_menu_item_set_label(GTK_MENU_ITEM(bdm->eject), _("Eject"));
+        gtk_menu_shell_append(GTK_MENU_SHELL(bdm->submenu), bdm->change);
+        gtk_menu_shell_append(GTK_MENU_SHELL(bdm->submenu), bdm->eject);
+
+        g_signal_connect(bdm->change, "activate",
+                         G_CALLBACK(gd_menu_bdm_change), bdm);
+        g_signal_connect(bdm->eject, "activate",
+                         G_CALLBACK(gd_menu_bdm_eject), bdm);
+
+        switch (bdm->disk_type) {
+        case DT_NORMAL:
+            stock_id = GTK_STOCK_HARDDISK;
+            break;
+        case DT_CDROM:
+            stock_id = GTK_STOCK_CDROM;
+            break;
+        case DT_FLOPPY:
+            stock_id = GTK_STOCK_FLOPPY;
+            break;
+        }
+
+        bdm->entry = gtk_image_menu_item_new_from_stock(stock_id, NULL);
+        gtk_menu_item_set_label(GTK_MENU_ITEM(bdm->entry), bdm->desc);
+        gtk_menu_item_set_submenu(GTK_MENU_ITEM(bdm->entry), bdm->submenu);
+        gtk_menu_shell_append(GTK_MENU_SHELL(devices_menu), bdm->entry);
+
+        gd_block_device_menu_update(bdm, info);
+    }
+
+    qapi_free_BlockInfoList(block_list);
+
+    return devices_menu;
+}
+
 static void gd_create_menus(GtkDisplayState *s)
 {
     GtkAccelGroup *accel_group;
@@ -1389,6 +1686,7 @@ static void gd_create_menus(GtkDisplayState *s)
     accel_group = gtk_accel_group_new();
     s->machine_menu = gd_create_menu_machine(s, accel_group);
     s->view_menu = gd_create_menu_view(s, accel_group);
+    s->devices_menu = gd_create_menu_devices(s, accel_group);
 
     s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
@@ -1399,6 +1697,10 @@ static void gd_create_menus(GtkDisplayState *s)
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
     gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
 
+    s->devices_menu_item = gtk_menu_item_new_with_mnemonic(_("_Devices"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->devices_menu_item), 
s->devices_menu);
+    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->devices_menu_item);
+
     g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
     gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
     s->accel_group = accel_group;
-- 
1.8.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]