[spice-gtk,v4,12/13] cd-sharing: implementation of new USB widget

Submitted by Yuri Benditovich on Sept. 17, 2018, 1:23 p.m.

Details

Message ID 1537190583-4576-13-git-send-email-yuri.benditovich@daynix.com
State New
Headers show
Series "CD sharing feature" ( rev: 4 ) in Spice

Not browsing as part of any series.

Commit Message

Yuri Benditovich Sept. 17, 2018, 1:23 p.m.
New USB widget supports all the functionality related to
redirection of local USB device (as previous usb widget did).
Additionally it allows creation, management and redirection of
emulated USB CD devices for CD sharing.

Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
---
 src/usb-device-redir-widget.c | 2065 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 2065 insertions(+)
 create mode 100644 src/usb-device-redir-widget.c

Patch hide | download patch | download mbox

diff --git a/src/usb-device-redir-widget.c b/src/usb-device-redir-widget.c
new file mode 100644
index 0000000..59a6043
--- /dev/null
+++ b/src/usb-device-redir-widget.c
@@ -0,0 +1,2065 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2012 Red Hat, Inc.
+
+   Red Hat Authors:
+   Alexander Nezhinsky<anezhins@redhat.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#ifndef USB_WIDGET_TEST
+    #include <glib/gi18n-lib.h>
+    #include "spice-client.h"
+    #include "spice-marshal.h"
+#else
+    #include "spice-client.h"
+#endif
+#include "usb-device-widget.h"
+
+/*
+    Debugging note:
+    Logging from this module is not affected by --spice-debug
+    command line parameter
+    Use SPICE_DEBUG=1 environment varible to enable logs
+*/
+
+#ifdef USE_NEW_USB_WIDGET
+
+/**
+ * SECTION:usb-device-widget
+ * @short_description: USB device selection widget
+ * @title: Spice USB device selection widget
+ * @section_id:
+ * @see_also:
+ * @stability: Under development
+ * @include: spice-client-gtk.h
+ *
+ * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
+ * add an UI to select USB devices to redirect (or unredirect).
+ */
+
+struct _SpiceUsbDeviceWidget
+{
+    GtkBox parent;
+
+    SpiceUsbDeviceWidgetPrivate *priv;
+};
+
+struct _SpiceUsbDeviceWidgetClass
+{
+    GtkBoxClass parent_class;
+
+    /* signals */
+    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
+                            SpiceUsbDevice *device, GError *error);
+};
+
+/* ------------------------------------------------------------------ */
+/* Prototypes for callbacks  */
+static void device_added_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_removed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_changed_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, gpointer user_data);
+static void device_error_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *device, GError *err, gpointer user_data);
+static gboolean spice_usb_device_widget_update_status(gpointer user_data);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
+                                 SpiceUsbDeviceWidgetPrivate))
+
+enum {
+    PROP_0,
+    PROP_SESSION,
+    PROP_DEVICE_FORMAT_STRING,
+};
+
+enum {
+    CONNECT_FAILED,
+    LAST_SIGNAL,
+};
+
+typedef struct {
+    GtkTreeView *tree_view;
+    GtkTreeStore *tree_store;
+} SpiceUsbDeviceWidgetTree;
+
+struct _SpiceUsbDeviceWidgetPrivate {
+    SpiceSession *session;
+    gchar *device_format_string;
+    SpiceUsbDeviceManager *manager;
+    GtkWidget *info_bar;
+    GtkWidget *label;
+    SpiceUsbDeviceWidgetTree cd_tree;
+    SpiceUsbDeviceWidgetTree usb_tree;
+    GdkPixbuf *icon_cd;
+    GdkPixbuf *icon_connected;
+    GdkPixbuf *icon_disconn;
+    GdkPixbuf *icon_warning;
+    GdkPixbuf *icon_info;
+    gchar *err_msg;
+    gsize device_count;
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
+
+/* TREE */
+
+enum column_id
+{
+    COL_REDIRECT = 0,
+    COL_ADDRESS,
+    COL_CONNECT_ICON,
+    COL_CD_ICON,
+    COL_VENDOR,
+    COL_PRODUCT,
+    COL_FILE,
+    COL_LOADED,
+    COL_LOCKED,
+    COL_IDLE,
+    /* internal columns */
+    COL_REVISION,
+    COL_CD_DEV,
+    COL_LUN_ITEM,
+    COL_DEV_ITEM,
+    COL_ITEM_DATA,
+    COL_CONNECTED,
+    COL_CAN_REDIRECT,
+    COL_ROW_COLOR,
+    COL_ROW_COLOR_SET,
+    NUM_COLS,
+
+    INVALID_COL
+};
+
+// there is a possibility to use different names
+// for columns in USB device list and CD device list, if needed
+// currently they are identical
+static const char *column_names_cd[NUM_COLS];
+static const char *column_names_usb[NUM_COLS];
+
+#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
+
+static void initialize_columns(void)
+{
+    SET_COLUMN(COL_REDIRECT, _("Redirect"));
+    SET_COLUMN(COL_ADDRESS, _("Address"));
+    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
+    SET_COLUMN(COL_CD_ICON, _("CD"));
+    SET_COLUMN(COL_VENDOR, _("Vendor"));
+    SET_COLUMN(COL_PRODUCT, _("Product"));
+    SET_COLUMN(COL_FILE, _("File/Device Path"));
+    SET_COLUMN(COL_LOADED, _("Loaded"));
+    SET_COLUMN(COL_LOCKED, _("Locked"));
+    SET_COLUMN(COL_IDLE, _("Idle"));
+    SET_COLUMN(COL_REVISION, "?Revision");
+    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
+    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
+    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
+    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
+    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
+    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
+    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
+    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
+};
+
+static const char *column_name(enum column_id id, gboolean is_cd)
+{
+    const char **col_name = is_cd ? column_names_cd : column_names_usb;
+    return col_name[id];
+}
+
+typedef struct _UsbWidgetLunItem {
+    SpiceUsbDeviceManager *manager;
+    SpiceUsbDevice *device;
+    guint lun;
+    SpiceUsbDeviceLunInfo info;
+} UsbWidgetLunItem;
+
+typedef struct _TreeFindUsbDev {
+    SpiceUsbDevice *usb_dev;
+    GtkTreeIter dev_iter;
+} TreeFindUsbDev;
+
+typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *, gpointer);
+
+static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
+                                          SpiceUsbDevice *usb_device,
+                                          GtkTreeIter *old_dev_iter);
+
+static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
+                                     SpiceUsbDevice *device,
+                                     const gchar *format)
+{
+    SpiceUsbDeviceDescription desc;
+    gchar *descriptor;
+    gchar *res;
+    spice_usb_device_get_info(manager, device, &desc);
+    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id);
+    if (!format) {
+        format = _("%s %s %s at %d-%d");
+    }
+    res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address);
+    g_free(desc.vendor);
+    g_free(desc.product);
+    g_free(descriptor);
+    return res;
+}
+
+static GtkTreeStore* usb_widget_create_tree_store(void)
+{
+    GtkTreeStore *tree_store;
+
+    tree_store = gtk_tree_store_new(NUM_COLS,
+                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
+                        G_TYPE_STRING, /* COL_ADDRESS */
+                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
+                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
+                        G_TYPE_STRING, /* COL_VENDOR */
+                        G_TYPE_STRING, /* COL_PRODUCT */
+                        G_TYPE_STRING, /* COL_FILE */
+                        G_TYPE_BOOLEAN, /* COL_LOADED */
+                        G_TYPE_BOOLEAN, /* COL_LOCKED */
+                        G_TYPE_BOOLEAN, /* COL_IDLE */
+                        /* internal columns */
+                        G_TYPE_STRING, /* COL_REVISION */
+                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
+                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
+                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
+                        G_TYPE_POINTER, /* COL_ITEM_DATA */
+                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
+                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
+                        G_TYPE_STRING, /* COL_ROW_COLOR */
+                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
+    SPICE_DEBUG("tree store created");
+
+    return tree_store;
+}
+
+static GdkPixbuf *get_named_icon(const gchar *name, gint size)
+{
+    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), name, size, 0);
+    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
+    g_object_unref (info);
+    return pixbuf;
+}
+
+static void select_widget_size(GtkWidget *wg)
+{
+    GdkDisplay *d = gtk_widget_get_display(wg);
+    int i, w = 2000, h = 1024;
+    int n = gdk_display_get_n_monitors(d);
+    for (i = 0; i < n; ++i)
+    {
+        GdkMonitor *m = gdk_display_get_monitor(d, i);
+        if (m) {
+            GdkRectangle area;
+            gdk_monitor_get_workarea(m, &area);
+            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
+                i, area.width, area.height, area.x, area.y );
+            w = MIN(w, area.width);
+            h = MIN(h, area.height);
+        }
+    }
+
+    w = (w * 3) / 4;
+    h = h / 2;
+
+    SPICE_DEBUG("sizing widget as %d x %d", w, h);
+    gtk_widget_set_size_request(wg, w, h);
+}
+
+static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
+                                          SpiceUsbDevice *usb_device,
+                                          GtkTreeIter *old_dev_iter)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
+    SpiceUsbDeviceWidgetTree *tree;
+    GtkTreeView *tree_view;
+    GtkTreeStore *tree_store;
+    GtkTreeIter new_dev_iter;
+    SpiceUsbDeviceDescription dev_info;
+    GtkTreePath *new_dev_path;
+    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
+    gchar *addr_str;
+    GArray *lun_array;
+    guint lun_index;
+    GError *error = NULL;
+
+    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr, usb_device);
+    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
+    tree_view = tree->tree_view;
+    tree_store = tree->tree_store;
+
+    if (old_dev_iter == NULL) {
+        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
+    } else {
+        gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL, old_dev_iter);
+        gtk_tree_store_remove(tree_store, old_dev_iter);
+    }
+
+    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
+    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address);
+    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr, usb_device);
+    is_dev_redirected = is_dev_connected;
+    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
+        addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd);
+
+    gtk_tree_store_set(tree_store, &new_dev_iter,
+        COL_REDIRECT, is_dev_redirected,
+        COL_ADDRESS, addr_str,
+        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn,
+        COL_CD_ICON, priv->icon_cd,
+        COL_VENDOR, dev_info.vendor,
+        COL_PRODUCT, dev_info.product,
+        COL_CD_DEV, is_dev_cd,
+        COL_LUN_ITEM, FALSE, /* USB device item */
+        COL_DEV_ITEM, TRUE, /* USB device item */
+        COL_ITEM_DATA, (gpointer)usb_device,
+        COL_CONNECTED, is_dev_connected,
+        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr, usb_device, &error),
+        COL_ROW_COLOR, "beige",
+        COL_ROW_COLOR_SET, TRUE,
+        -1);
+    g_clear_error(&error);
+
+    priv->device_count++;
+
+    /* get all the luns */
+    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, usb_device);
+    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
+        UsbWidgetLunItem *lun_item;
+        GtkTreeIter lun_iter;
+        gchar lun_str[8];
+
+        lun_item = g_malloc(sizeof(*lun_item));
+        lun_item->manager = usb_dev_mgr;
+        lun_item->device = usb_device;
+        lun_item->lun = g_array_index(lun_array, guint, lun_index);
+        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr, usb_device, lun_item->lun, &lun_item->info);
+        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",
+                lun_index, lun_item->info.vendor, lun_item->info.product,
+                lun_item->info.revision, lun_item->info.file_path, lun_item);
+        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
+
+        /* Append LUN as a child of USB device */
+        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
+        gtk_tree_store_set(tree_store, &lun_iter,
+                COL_ADDRESS, lun_str,
+                COL_VENDOR, lun_item->info.vendor,
+                COL_PRODUCT, lun_item->info.product,
+                COL_REVISION, lun_item->info.revision,
+                COL_FILE, lun_item->info.file_path,
+                COL_LOADED, lun_item->info.loaded,
+                COL_LOCKED, lun_item->info.locked,
+                COL_IDLE, !lun_item->info.started,
+                COL_CD_DEV, FALSE,
+                COL_LUN_ITEM, TRUE, /* LUN item */
+                COL_DEV_ITEM, FALSE, /* LUN item */
+                COL_ITEM_DATA, (gpointer)lun_item,
+                COL_CONNECTED, is_dev_connected,
+                COL_ROW_COLOR, "azure",
+                COL_ROW_COLOR_SET, TRUE,
+                -1);
+    }
+    g_array_unref(lun_array);
+
+    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &new_dev_iter);
+    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
+    gtk_tree_path_free(new_dev_path);
+
+    g_free(dev_info.vendor);
+    g_free(dev_info.product);
+    g_free(addr_str);
+}
+
+static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter)
+{
+    gboolean is_lun;
+    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);
+    return is_lun;
+}
+
+static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel *tree_model,
+                                                              GtkTreePath *path, GtkTreeIter *iter,
+                                                              gpointer user_data)
+{
+    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
+    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
+    SpiceUsbDevice *usb_device;
+    gboolean is_lun_item;
+
+    gtk_tree_model_get(tree_model, iter,
+                       COL_LUN_ITEM, &is_lun_item,
+                       COL_ITEM_DATA, (gpointer *)&usb_device,
+                       -1);
+    if (!is_lun_item && usb_device == find_usb_device) {
+        find_info->dev_iter = *iter;
+        find_info->usb_dev  = NULL;
+        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
+        return TRUE; /* stop iterating */
+    } else {
+        return FALSE; /* continue iterating */
+    }
+}
+
+static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore *tree_store,
+                                                          SpiceUsbDevice *usb_device)
+{
+    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };
+    GtkTreeIter *iter = NULL;
+
+    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
+                           usb_widget_tree_store_find_usb_dev_foreach_cb, (gpointer)&find_info);
+    // the callback sets 'usb_dev' field to zero if it finds the device
+    if (!find_info.usb_dev) {
+        iter = g_malloc(sizeof(*iter));
+        *iter = find_info.dev_iter;
+    }
+    return iter;
+}
+
+static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
+                                         SpiceUsbDevice *usb_device)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeIter *old_dev_iter;
+    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
+    // on WIN32 it is possible the backend device is already removed
+    // from the USB device manager list and we can't know it was
+    // USB or CD, do we will try both lists
+    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
+    if (old_dev_iter != NULL) {
+        SPICE_DEBUG("USB Device removed");
+        gtk_tree_store_remove(tree_store, old_dev_iter);
+        priv->device_count--;
+        g_free(old_dev_iter);
+        return TRUE;
+    }
+    tree_store = priv->cd_tree.tree_store;
+    if (tree_store) {
+        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
+    }
+    if (old_dev_iter != NULL) {
+        SPICE_DEBUG("CD Device removed");
+        gtk_tree_store_remove(tree_store, old_dev_iter);
+        priv->device_count--;
+        g_free(old_dev_iter);
+        return TRUE;
+    }
+    SPICE_DEBUG("Device %p not found!", usb_device);
+    return FALSE;
+}
+
+static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget *self,
+                                                 enum column_id toggle_col_id,
+                                                 enum column_id visible_col_id,
+                                                 enum column_id sensitive_col_id,
+                                                 tree_item_toggled_cb toggled_cb,
+                                                 gboolean is_cd)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkCellRenderer     *renderer;
+    GtkTreeViewColumn   *view_col;
+    const char *col_name = column_name(toggle_col_id, is_cd);
+
+    renderer = gtk_cell_renderer_toggle_new();
+
+    if (sensitive_col_id != INVALID_COL) {
+        view_col = gtk_tree_view_column_new_with_attributes(
+                        col_name,
+                        renderer,
+                        "active", toggle_col_id,
+                        "visible", visible_col_id,
+                        "activatable", sensitive_col_id,
+                        NULL);
+    } else {
+        view_col = gtk_tree_view_column_new_with_attributes(
+                        col_name,
+                        renderer,
+                        "active", toggle_col_id,
+                        "visible", visible_col_id,
+                        NULL);
+    }
+
+    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
+    gtk_tree_view_column_set_expand(view_col, FALSE);
+    gtk_tree_view_column_set_resizable(view_col, FALSE);
+    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
+
+    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);
+
+    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]",
+            toggle_col_id, col_name,
+            visible_col_id, column_name(visible_col_id, is_cd));
+    return view_col;
+}
+
+static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget *self,
+                                                           enum column_id toggle_col_id,
+                                                           enum column_id visible_col_id,
+                                                           gboolean is_cd)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkCellRenderer     *renderer;
+    GtkTreeViewColumn   *view_col;
+    const char *col_name = column_name(toggle_col_id, is_cd);
+
+    renderer = gtk_cell_renderer_toggle_new();
+
+    view_col = gtk_tree_view_column_new_with_attributes(
+                    col_name,
+                    renderer,
+                    "active", toggle_col_id,
+                    "visible", visible_col_id,
+                    NULL);
+
+    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
+
+    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
+    gtk_tree_view_column_set_expand(view_col, FALSE);
+    gtk_tree_view_column_set_resizable(view_col, FALSE);
+    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
+
+    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]",
+            toggle_col_id, col_name,
+            visible_col_id, column_name(visible_col_id, is_cd));
+    return view_col;
+}
+
+static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget *self,
+                                               enum column_id col_id,
+                                               gboolean expandable,
+                                               gboolean is_cd)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkCellRenderer     *renderer;
+    GtkTreeViewColumn   *view_col;
+
+    renderer = gtk_cell_renderer_text_new();
+
+    view_col = gtk_tree_view_column_new_with_attributes(
+                    column_name(col_id, is_cd),
+                    renderer,
+                    "text", col_id,
+                    //"cell-background", COL_ROW_COLOR,
+                    //"cell-background-set", COL_ROW_COLOR_SET,
+                    NULL);
+
+    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+    gtk_tree_view_column_set_resizable(view_col, TRUE);
+    gtk_tree_view_column_set_expand(view_col, expandable);
+
+    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
+
+    SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd));
+    return view_col;
+}
+
+static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget *self,
+                                                 enum column_id col_id,
+                                                 enum column_id visible_col_id,
+                                                 gboolean is_cd)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkCellRenderer     *renderer;
+    GtkTreeViewColumn   *view_col;
+    const char *col_name = column_name(col_id, is_cd);
+
+    renderer = gtk_cell_renderer_pixbuf_new();
+
+    if (visible_col_id == INVALID_COL) {
+        view_col = gtk_tree_view_column_new_with_attributes(
+                        col_name,
+                        renderer,
+                        "pixbuf", col_id,
+                        NULL);
+        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name);
+    } else {
+        view_col = gtk_tree_view_column_new_with_attributes(
+                        col_name,
+                        renderer,
+                        "pixbuf", col_id,
+                        "visible", visible_col_id,
+                        NULL);
+        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]",
+                col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd));
+    }
+    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
+    return view_col;
+}
+
+/* Toggle handlers */
+
+static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
+{
+    gboolean toggle_val;
+
+    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store), iter, path_str);
+    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id, &toggle_val, -1);
+
+    return toggle_val;
+}
+
+static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
+{
+    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
+}
+
+typedef struct _connect_cb_data {
+    SpiceUsbDeviceWidget *self;
+    SpiceUsbDevice *usb_dev;
+} connect_cb_data;
+
+static void connect_cb_data_free(connect_cb_data *user_data)
+{
+    spice_usb_device_widget_update_status(user_data->self);
+    g_object_unref(user_data->self);
+    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
+    g_free(user_data);
+}
+
+static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    connect_cb_data *cb_data = user_data;
+    SpiceUsbDeviceWidget *self = cb_data->self;
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
+    GError *err = NULL;
+    GtkTreeIter *dev_iter;
+    gchar *desc;
+    gboolean finished;
+    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
+    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
+
+    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
+    if (!dev_iter) {
+        return;
+    }
+
+    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
+    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
+
+    finished = spice_usb_device_manager_connect_device_finish(priv->manager, res, &err);
+    if (finished) {
+        gtk_tree_store_set(tree_store, dev_iter,
+                           COL_CONNECT_ICON, priv->icon_connected,
+                           COL_CONNECTED, TRUE,
+                           -1);
+    } else {
+        gtk_tree_store_set(tree_store, dev_iter,
+                           COL_REDIRECT, FALSE,
+                           -1);
+        g_prefix_error(&err, "Device connect failed %s: ", desc);
+        if (err) {
+            SPICE_DEBUG("%s", err->message);
+            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err);
+            g_error_free(err);
+        } else {
+            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL);
+        }
+
+        /* don't trigger a disconnect if connect failed */
+        /*
+        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data->check),
+                                        checkbox_clicked_cb, self);
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check), FALSE);
+        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_data->check),
+                                        checkbox_clicked_cb, self);
+        */
+    }
+    g_free(desc);
+    g_free(dev_iter);
+    connect_cb_data_free(user_data);
+}
+
+static void usb_widget_disconnect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    connect_cb_data *cb_data = user_data;
+    SpiceUsbDeviceWidget *self = cb_data->self;
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
+    GError *err = NULL;
+    GtkTreeIter *dev_iter;
+    gchar *desc;
+    gboolean finished;
+    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
+    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
+
+    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
+    if (!dev_iter) {
+        return;
+    }
+
+    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
+    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
+
+    finished = spice_usb_device_manager_disconnect_device_finish(priv->manager, res, &err);
+    if (finished) {
+        gtk_tree_store_set(tree_store, dev_iter,
+                           COL_CONNECT_ICON, priv->icon_disconn,
+                           COL_CONNECTED, FALSE,
+                           -1);
+    } else {
+        gtk_tree_store_set(tree_store, dev_iter,
+                           COL_REDIRECT, TRUE,
+                           -1);
+        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
+        if (err) {
+            SPICE_DEBUG("%s", err->message);
+            g_error_free(err);
+        }
+    }
+    g_free(desc);
+    g_free(dev_iter);
+    connect_cb_data_free(user_data);
+}
+
+static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell,
+                                          gchar *path_str,
+                                          SpiceUsbDeviceWidget *self,
+                                          GtkTreeStore *tree_store)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
+    SpiceUsbDevice *usb_dev;
+    GtkTreeIter iter;
+    gboolean new_redirect_val;
+
+    new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str, &iter, COL_REDIRECT);
+    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
+    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT, new_redirect_val);
+
+    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
+    cb_data->self = g_object_ref(self);
+    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(), usb_dev);
+
+    if (new_redirect_val) {
+        spice_usb_device_manager_connect_device_async(priv->manager, usb_dev,
+                                                      NULL, /* cancellable */
+                                                      usb_widget_connect_cb, cb_data);
+    } else {
+        spice_usb_device_manager_disconnect_device_async(priv->manager, usb_dev,
+                                                         NULL, /* cancellable */
+                                                         usb_widget_disconnect_cb, cb_data);
+
+    }
+    spice_usb_device_widget_update_status(self);
+}
+
+static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle *cell,
+                                             gchar *path_str,
+                                             gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    tree_item_toggled_cb_redirect(cell, path_str, self, priv->cd_tree.tree_store);
+}
+
+static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle *cell,
+                                             gchar *path_str,
+                                             gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    tree_item_toggled_cb_redirect(cell, path_str, self, priv->usb_tree.tree_store);
+}
+
+/* Signal handlers */
+
+static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
+    SpiceUsbDevice *usb_device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+
+    SPICE_DEBUG("Signal: Device Added");
+
+    usb_widget_add_device(self, usb_device, NULL);
+
+    spice_usb_device_widget_update_status(self);
+}
+
+static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
+    SpiceUsbDevice *usb_device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    gboolean dev_removed;
+
+    SPICE_DEBUG("Signal: Device Removed");
+
+    dev_removed = usb_widget_remove_device(self, usb_device);
+    if (dev_removed) {
+        spice_usb_device_widget_update_status(self);
+    }
+}
+
+static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
+    SpiceUsbDevice *usb_device, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeIter *old_dev_iter;
+    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
+    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
+
+    SPICE_DEBUG("Signal: Device Changed");
+
+    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
+    if (old_dev_iter != NULL) {
+
+        usb_widget_add_device(self, usb_device, old_dev_iter);
+
+        spice_usb_device_widget_update_status(self);
+        g_free(old_dev_iter);
+    } else {
+        SPICE_DEBUG("Device not found!");
+    }
+}
+
+static void device_error_cb(SpiceUsbDeviceManager *manager,
+    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeIter *dev_iter;
+    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
+    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
+
+    SPICE_DEBUG("Signal: Device Error");
+
+    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
+    if (dev_iter != NULL) {
+        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT, FALSE);
+        spice_usb_device_widget_update_status(self);
+        g_free(dev_iter);
+    } else {
+        SPICE_DEBUG("Device not found!");
+    }
+}
+
+/* Selection handler */
+
+static void tree_selection_changed_cb(GtkTreeSelection *select, gpointer user_data)
+{
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+    GtkTreePath *path;
+    gboolean is_lun;
+    UsbWidgetLunItem *lun_item;
+    gchar *txt[3];
+
+    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        gtk_tree_model_get(tree_model, &iter,
+                COL_VENDOR, &txt[0],
+                COL_PRODUCT, &txt[1],
+                COL_REVISION, &txt[2],
+                COL_LUN_ITEM, &is_lun,
+                COL_ITEM_DATA, (gpointer *)&lun_item,
+                -1);
+        path = gtk_tree_model_get_path(tree_model, &iter);
+
+        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
+                txt[0], txt[1],
+                is_lun ? txt[2] : "--",
+                is_lun ? "LUN" : "USB-DEV",
+                is_lun ? lun_item->info.file_path : "--",
+                gtk_tree_path_to_string(path));
+
+        if (txt[0]) {
+            g_free(txt[0]);
+        }
+        if (txt[1]) {
+            g_free(txt[1]);
+        }
+        if (txt[2]) {
+            g_free(txt[2]);
+        }
+        gtk_tree_path_free(path);
+    }
+}
+
+static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view)
+{
+    GtkTreeSelection *select;
+
+    select = gtk_tree_view_get_selection(tree_view);
+    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
+
+    g_signal_connect(G_OBJECT(select), "changed",
+                     G_CALLBACK(tree_selection_changed_cb),
+                     NULL);
+
+    SPICE_DEBUG("selection handler set");
+    return select;
+}
+
+static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent)
+{
+    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
+    GtkWidget *label = gtk_accel_label_new(label_str);
+    GtkAccelGroup *accel_group = gtk_accel_group_new();
+    guint accel_key;
+
+    /* add icon */
+    gtk_container_add(GTK_CONTAINER(box), icon);
+
+    /* add label */
+    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
+    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
+    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);
+    gtk_widget_add_accelerator(parent, "activate", accel_group, accel_key,
+                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
+    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent);
+    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
+
+    /* add the new box to the parent widget */
+    gtk_container_add(GTK_CONTAINER(parent), box);
+
+    return box;
+}
+
+/* LUN properties dialog */
+
+typedef struct _lun_properties_dialog {
+    GtkWidget *dialog;
+    GtkWidget *advanced_grid;
+    gboolean advanced_shown;
+
+    GtkWidget *file_entry;
+    GtkWidget *vendor_entry;
+    GtkWidget *product_entry;
+    GtkWidget *revision_entry;
+    GtkWidget *loaded_switch;
+    GtkWidget *locked_switch;
+} lun_properties_dialog;
+
+#if 1
+static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
+{
+    GtkWidget *file_entry = (GtkWidget *)user_data;
+    GtkFileChooserNative *native;
+    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+    gint res;
+
+    native = gtk_file_chooser_native_new("Choose File for USB CD",
+        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
+        action,
+        "_Open",
+        "_Cancel");
+
+    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
+    if (res == GTK_RESPONSE_ACCEPT) {
+        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native));
+        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
+        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
+        g_free(filename);
+    }
+    else {
+        gtk_widget_grab_focus(button);
+    }
+
+    g_object_unref(native);
+}
+#else
+// to be removed
+static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
+{
+    GtkWidget *file_entry = (GtkWidget *)user_data;
+    GtkWidget *dialog;
+    gint res;
+
+    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
+                                          GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
+                                          GTK_FILE_CHOOSER_ACTION_OPEN,
+                                          "_Cancel",
+                                          GTK_RESPONSE_CANCEL,
+                                          "_Ok",
+                                          GTK_RESPONSE_ACCEPT,
+                                          NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+
+    res = gtk_dialog_run(GTK_DIALOG(dialog));
+    if (res == GTK_RESPONSE_ACCEPT) {
+        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
+        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
+        g_free(filename);
+    }
+    gtk_widget_destroy(dialog);
+}
+#endif
+
+static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget *widget,
+                                                       gboolean state, gpointer user_data)
+{
+    lun_properties_dialog *lun_dialog = user_data;
+
+    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
+    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
+
+    return FALSE; /* call default signal handler */
+}
+
+static void lun_properties_dialog_toggle_advanced(GtkWidget *widget, gpointer user_data)
+{
+    lun_properties_dialog *lun_dialog = user_data;
+
+    if (lun_dialog->advanced_shown) {
+        gtk_widget_hide(lun_dialog->advanced_grid);
+        lun_dialog->advanced_shown = FALSE;
+    } else {
+        gtk_widget_show_all(lun_dialog->advanced_grid);
+        lun_dialog->advanced_shown = TRUE;
+    }
+}
+
+static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
+                                         GtkWidget *parent_window,
+                                         SpiceUsbDeviceLunInfo *lun_info,
+                                         lun_properties_dialog *lun_dialog)
+{
+    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *dialog, *content_area;
+    GtkWidget *grid, *advanced_grid;
+    GtkWidget *file_entry, *choose_button;
+    GtkWidget *advanced_button, *advanced_icon;
+    GtkWidget *vendor_entry, *product_entry, *revision_entry;
+    GtkWidget *loaded_switch, *loaded_label;
+    GtkWidget *locked_switch, *locked_label;
+    gint nrow = 0;
+
+    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings",
+                    GTK_WINDOW(parent_window),
+                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, /* flags */
+                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
+                    "Cancel", GTK_RESPONSE_REJECT,
+                    NULL);
+
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
+    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
+
+    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+    /* main grid - always visible */
+    grid = gtk_grid_new();
+    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
+    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
+    gtk_container_add(GTK_CONTAINER(content_area), grid);
+
+    /* File path label */
+    gtk_grid_attach(GTK_GRID(grid),
+            gtk_label_new("Select file or device"),
+            0, nrow++, // left top
+            7, 1); // width height
+
+    /* file/device path entry */
+    file_entry = gtk_entry_new();
+    gtk_widget_set_hexpand(file_entry, TRUE);
+    if (!lun_info) {
+        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry), "file-path");
+    } else {
+        gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path);
+        if (lun_info->loaded) {
+            gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE);
+            gtk_widget_set_can_focus(file_entry, FALSE);
+        }
+    }
+    gtk_grid_attach(GTK_GRID(grid),
+            file_entry,
+            0, nrow, // left top
+            6, 1); // width height
+
+    /* choose button */
+    choose_button = gtk_button_new_with_mnemonic("_Choose File");
+    gtk_widget_set_hexpand(choose_button, FALSE);
+    g_signal_connect(GTK_BUTTON(choose_button),
+                     "clicked", G_CALLBACK(usb_cd_choose_file), file_entry);
+    if (lun_info && lun_info->loaded) {
+        gtk_widget_set_sensitive(choose_button, FALSE);
+        gtk_widget_set_can_focus(choose_button, FALSE);
+    }
+
+    gtk_grid_attach(GTK_GRID(grid),
+            choose_button,
+            6, nrow++, // left top
+            1, 1); // width height
+
+    /* advanced button */
+    advanced_button = gtk_button_new_with_label("Advanced");
+    gtk_button_set_relief(GTK_BUTTON(advanced_button), GTK_RELIEF_NONE);
+    advanced_icon = gtk_image_new_from_icon_name("preferences-system", GTK_ICON_SIZE_BUTTON);
+    gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon);
+    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button), TRUE);
+    g_signal_connect(advanced_button, "clicked", G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
+
+    gtk_grid_attach(GTK_GRID(grid),
+            advanced_button,
+            0, nrow++, // left top
+            1, 1); // width height
+
+    /* advanced grid */
+    advanced_grid = gtk_grid_new();
+    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
+    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
+    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
+
+    /* horizontal separator */
+    gtk_container_add(GTK_CONTAINER(content_area), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
+
+    /* pack advanced grid */
+    nrow = 0;
+
+    /* horizontal separator */
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
+            0, nrow++, // left top
+            7, 1); // width height
+
+    /* product id labels */
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            gtk_label_new("Vendor"),
+            0, nrow, // left top
+            2, 1); // width height
+
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            gtk_label_new("Product"),
+            2, nrow, // left top
+            4, 1); // width height
+
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            gtk_label_new("Revision"),
+            6, nrow++, // left top
+            1, 1); // width height
+
+    /* vendor entry */
+    vendor_entry = gtk_entry_new();
+    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
+    if (lun_info) {
+        gtk_widget_set_sensitive(vendor_entry, FALSE);
+        gtk_widget_set_can_focus(vendor_entry, FALSE);
+        gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor);
+    } else {
+        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry), "auto");
+    }
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            vendor_entry,
+            0, nrow, // left top
+            2, 1); // width height
+
+    /* product entry */
+    product_entry = gtk_entry_new();
+    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
+    if (lun_info) {
+        gtk_widget_set_sensitive(product_entry, FALSE);
+        gtk_widget_set_can_focus(product_entry, FALSE);
+        gtk_entry_set_text(GTK_ENTRY(product_entry), lun_info->product);
+    } else {
+        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry), "auto");
+    }
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            product_entry,
+            2, nrow, // left top
+            4, 1); // width height
+
+    /* revision entry */
+    revision_entry = gtk_entry_new();
+    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
+    if (lun_info) {
+        gtk_widget_set_sensitive(revision_entry, FALSE);
+        gtk_widget_set_can_focus(revision_entry, FALSE);
+        gtk_entry_set_text(GTK_ENTRY(revision_entry), lun_info->revision);
+    } else {
+        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry), "auto");
+    }
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            revision_entry,
+            6, nrow++, // left top
+            1, 1); // width height
+
+    /* horizontal separator */
+    if (!lun_info) {
+        gtk_grid_attach(GTK_GRID(advanced_grid),
+                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
+                0, nrow++, // left top
+                7, 1); // width height
+    }
+
+    /* initially loaded switch */
+    loaded_label = gtk_label_new("Initially loaded:");
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+        loaded_label,
+        0, nrow, // left top
+        2, 1); // width height
+
+    loaded_switch = gtk_switch_new();
+    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
+    if (lun_info) {
+        gtk_widget_set_child_visible(loaded_switch, FALSE);
+        gtk_widget_set_child_visible(loaded_label, FALSE);
+    } else {
+        g_signal_connect(loaded_switch, "state-set",
+                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb), lun_dialog);
+    }
+    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            loaded_switch,
+            2, nrow++, // left top
+            1, 1); // width height
+
+    /* initially locked switch */
+    locked_label = gtk_label_new("Initially locked:");
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+        locked_label,
+        0, nrow, // left top
+        2, 1); // width height
+
+    locked_switch = gtk_switch_new();
+    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
+    gtk_widget_set_hexpand(locked_switch, FALSE);
+    if (lun_info) {
+        gtk_widget_set_child_visible(locked_switch, FALSE);
+        gtk_widget_set_child_visible(locked_label, FALSE);
+    }
+    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
+    gtk_grid_attach(GTK_GRID(advanced_grid),
+            locked_switch,
+            2, nrow++, // left top
+            1, 1); // width height
+
+    lun_dialog->dialog = dialog;
+    lun_dialog->advanced_grid = advanced_grid;
+    lun_dialog->advanced_shown = FALSE;
+    lun_dialog->file_entry = file_entry;
+    lun_dialog->vendor_entry = vendor_entry;
+    lun_dialog->product_entry = product_entry;
+    lun_dialog->revision_entry = revision_entry;
+    lun_dialog->loaded_switch = loaded_switch;
+    lun_dialog->locked_switch = locked_switch;
+
+    gtk_widget_show_all(dialog);
+    gtk_widget_hide(advanced_grid);
+}
+
+static void lun_properties_dialog_get_info(lun_properties_dialog *lun_dialog,
+                                            SpiceUsbDeviceLunInfo *lun_info)
+{
+    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(lun_dialog->file_entry));
+    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(lun_dialog->vendor_entry));
+    lun_info->product = gtk_entry_get_text(GTK_ENTRY(lun_dialog->product_entry));
+    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(lun_dialog->revision_entry));
+    lun_info->loaded = gtk_switch_get_active(GTK_SWITCH(lun_dialog->loaded_switch));
+    lun_info->locked = gtk_switch_get_active(GTK_SWITCH(lun_dialog->locked_switch));
+}
+
+/* Popup menu */
+static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+
+    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
+            SpiceUsbDevice *usb_device;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
+            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
+        }
+        else {
+            UsbWidgetLunItem *lun_item;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
+            spice_usb_device_manager_device_lun_load(
+                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded);
+        }
+    }
+    else {
+        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
+    }
+}
+
+static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+
+    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
+            SpiceUsbDevice *usb_device;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
+            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
+        }
+        else {
+            UsbWidgetLunItem *lun_item;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
+            spice_usb_device_manager_device_lun_lock(
+                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked);
+        }
+    }
+    else {
+        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
+    }
+}
+
+static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+
+    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
+            SpiceUsbDevice *usb_device;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
+            SPICE_DEBUG("Remove USB device");
+        } else {
+            UsbWidgetLunItem *lun_item;
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
+            gtk_tree_selection_unselect_all(select);
+            spice_usb_device_manager_device_lun_remove(lun_item->manager, lun_item->device, lun_item->lun);
+        }
+    } else {
+        SPICE_DEBUG("Remove - failed to get selection");
+    }
+}
+
+static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+
+    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
+            SPICE_DEBUG("No settings for USB device yet");
+        } else {
+            lun_properties_dialog lun_dialog;
+            UsbWidgetLunItem *lun_item;
+            gint resp;
+
+            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
+            gtk_tree_selection_unselect_all(select);
+            create_lun_properties_dialog(self, NULL, &lun_item->info, &lun_dialog);
+
+            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
+            if (resp == GTK_RESPONSE_ACCEPT) {
+                SpiceUsbDeviceLunInfo lun_info;
+                SPICE_DEBUG("response is ACCEPT");
+                lun_properties_dialog_get_info(&lun_dialog, &lun_info);
+                spice_usb_device_manager_device_lun_change_media(
+                    priv->manager, lun_item->device, lun_item->lun, &lun_info);
+            } else {
+                SPICE_DEBUG("response is REJECT");
+            }
+            gtk_widget_destroy(lun_dialog.dialog);
+        }
+    } else {
+        SPICE_DEBUG("Remove - failed to get selection");
+    }
+}
+
+static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
+    const gchar *label_str,
+    const gchar *icon_name,
+    GCallback cb_func, gpointer user_data)
+{
+    GtkWidget *menu_item = gtk_menu_item_new();
+    create_image_button_box(label_str, icon_name, menu_item);
+    g_signal_connect(menu_item, "activate", cb_func, user_data);
+
+    gtk_widget_show_all(menu_item);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
+
+    return menu_item;
+}
+
+static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device)
+{
+    gboolean result;
+    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
+    GArray *lun_array;
+    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, device);
+    result = lun_array && lun_array->len <= 1;
+    if (lun_array) {
+        g_array_unref(lun_array);
+    }
+    return result;
+}
+
+static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeSelection *select;
+    GtkTreeModel *tree_model;
+    GtkTreeIter iter;
+    UsbWidgetLunItem *lun_item;
+    gboolean is_loaded, is_locked;
+    GtkTreeIter *usb_dev_iter;
+    gboolean is_dev_connected;
+    GtkWidget *menu;
+
+    if (tree_view != priv->cd_tree.tree_view) {
+        SPICE_DEBUG("Not applicable for USB device");
+        return;
+    }
+
+    select = gtk_tree_view_get_selection(tree_view);
+
+    if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
+        SPICE_DEBUG("No tree view row is selected");
+        return;
+    }
+    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
+        SPICE_DEBUG("No settings for USB device yet");
+        return;
+    }
+
+    gtk_tree_model_get(tree_model, &iter,
+                       COL_ITEM_DATA, (gpointer *)&lun_item,
+                       COL_LOADED, &is_loaded,
+                       COL_LOCKED, &is_locked,
+                       -1);
+
+    usb_dev_iter = usb_widget_tree_store_find_usb_device(priv->cd_tree.tree_store, lun_item->device);
+    if (usb_dev_iter != NULL) {
+        gtk_tree_model_get(tree_model, usb_dev_iter,
+                           COL_CONNECTED, &is_dev_connected,
+                           -1);
+        g_free(usb_dev_iter);
+    } else {
+        is_dev_connected = FALSE;
+        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
+                    lun_item->info.vendor, lun_item->info.product);
+    }
+    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d",
+                lun_item->info.vendor, lun_item->info.product,
+                is_dev_connected, is_loaded, is_locked);
+
+    /* Set up the menu */
+    menu = gtk_menu_new();
+
+    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
+                             G_CALLBACK(view_popup_menu_on_settings), user_data);
+    if (is_loaded) {
+        if (!is_locked) {
+            view_popup_add_menu_item(menu, "_Lock", "system-lock-screen",
+                                    G_CALLBACK(view_popup_menu_on_lock), user_data);
+            view_popup_add_menu_item(menu, "_Eject", "media-eject",
+                                     G_CALLBACK(view_popup_menu_on_eject), user_data);
+        } else {
+            view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen",
+                                     G_CALLBACK(view_popup_menu_on_lock), user_data);
+        }
+    } else {
+        view_popup_add_menu_item(menu, "_Load", "media-eject",
+                                 G_CALLBACK(view_popup_menu_on_eject), user_data);
+    }
+
+    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {
+        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
+                                 G_CALLBACK(view_popup_menu_on_remove), user_data);
+    }
+
+    gtk_widget_show_all(menu);
+    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
+}
+
+static void treeview_select_current_row_by_pos(GtkTreeView *tree_view, gint x, gint y)
+{
+    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
+        GtkTreePath *path;
+        /* Get tree path for row that was clicked */
+        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))
+        {
+            gtk_tree_selection_unselect_all(selection);
+            gtk_tree_selection_select_path(selection, path);
+            gtk_tree_path_free(path);
+        }
+    }
+}
+
+static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data)
+{
+    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
+    /* single click with the right mouse button */
+    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
+        /* select the row that was clicked, it will also provide the context */
+        treeview_select_current_row_by_pos(tree_view, (gint)event->x, (gint)event->y);
+        view_popup_menu(tree_view, event, user_data);
+        return TRUE; /* we handled this */
+    } else {
+        return FALSE; /* we did not handle this */
+    }
+}
+
+static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view, gpointer user_data)
+{
+    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
+    return TRUE; /* we handled this */
+}
+
+/* Add LUN dialog */
+
+static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button, gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button);
+    lun_properties_dialog lun_dialog;
+    gint resp;
+
+    create_lun_properties_dialog(self, parent_window, NULL, &lun_dialog);
+
+    resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
+    if (resp == GTK_RESPONSE_ACCEPT) {
+        SpiceUsbDeviceLunInfo lun_info;
+        SPICE_DEBUG("response is ACCEPT");
+        lun_properties_dialog_get_info(&lun_dialog, &lun_info);
+        spice_usb_device_manager_add_cd_lun(priv->manager, &lun_info);
+    } else {
+        SPICE_DEBUG("response is REJECT");
+    }
+    gtk_widget_destroy(lun_dialog.dialog);
+}
+
+static void spice_usb_device_widget_get_property(GObject     *gobject,
+                                                 guint        prop_id,
+                                                 GValue      *value,
+                                                 GParamSpec  *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        g_value_set_string(value, priv->device_format_string);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_widget_set_property(GObject       *gobject,
+                                                 guint          prop_id,
+                                                 const GValue  *value,
+                                                 GParamSpec    *pspec)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_dup_object(value);
+        break;
+    case PROP_DEVICE_FORMAT_STRING:
+        priv->device_format_string = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (priv->info_bar) {
+        gtk_widget_destroy(priv->info_bar);
+        priv->info_bar = NULL;
+    }
+}
+
+static void
+spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
+                                      const gchar          *message,
+                                      GtkMessageType        message_type,
+                                      GdkPixbuf            *icon_pixbuf)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;
+
+    spice_usb_device_widget_hide_info_bar(self);
+
+    info_bar = gtk_info_bar_new();
+    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type);
+
+    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
+    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_container_add(GTK_CONTAINER(content_area), hbox);
+
+    icon = gtk_image_new_from_pixbuf(icon_pixbuf);
+    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10);
+
+    label = gtk_label_new(message);
+    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+
+    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 0, 0);
+    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
+
+    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
+    gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put after the lable */
+    gtk_widget_show_all(priv->info_bar);
+}
+
+static void spice_usb_device_widget_create_tree_view(SpiceUsbDeviceWidget *self, gboolean is_cd)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    GtkTreeStore *tree_store = usb_widget_create_tree_store();
+    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
+
+    if (is_cd) {
+        priv->cd_tree.tree_view = tree_view;
+        priv->cd_tree.tree_store = tree_store;
+    } else {
+        priv->usb_tree.tree_view = tree_view;
+        priv->usb_tree.tree_store = tree_store;
+    }
+
+    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
+    g_object_unref(tree_store); /* destroy tree_store automatically with tree_view */
+
+    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM, COL_CAN_REDIRECT,
+        is_cd ? tree_item_toggled_cb_redirect_cd : tree_item_toggled_cb_redirect_usb, is_cd);
+
+    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);
+
+    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd);
+    if (is_cd) {
+        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);
+    }
+
+    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);
+    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);
+
+    if (is_cd) {
+        view_add_text_column(self, COL_FILE, TRUE, is_cd);
+        view_add_read_only_toggle_column(self, COL_LOADED, COL_LUN_ITEM, is_cd);
+        view_add_read_only_toggle_column(self, COL_LOCKED, COL_LUN_ITEM, is_cd);
+        // uncomment to show also 'idle' column for CD
+        //view_add_read_only_toggle_column(self, COL_IDLE, COL_LUN_ITEM, is_cd);
+    }
+
+    gtk_tree_selection_set_mode(
+            gtk_tree_view_get_selection(tree_view),
+            GTK_SELECTION_NONE);
+
+    if (is_cd) {
+        set_selection_handler(tree_view);
+    }
+}
+
+static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget *self)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    g_signal_connect(priv->manager, "device-added",
+                     G_CALLBACK(device_added_cb), self);
+    g_signal_connect(priv->manager, "device-removed",
+                     G_CALLBACK(device_removed_cb), self);
+    g_signal_connect(priv->manager, "device-changed",
+                     G_CALLBACK(device_changed_cb), self);
+    // TODO: connect failed
+    g_signal_connect(priv->manager, "device-error",
+                     G_CALLBACK(device_error_cb), self);
+
+    g_signal_connect(priv->cd_tree.tree_view, "button-press-event",
+                     G_CALLBACK(treeview_on_right_button_pressed_cb), self);
+    g_signal_connect(priv->cd_tree.tree_view, "popup-menu",
+                     G_CALLBACK(treeview_on_popup_key_pressed_cb), self);
+}
+
+static void create_tree_window(SpiceUsbDeviceWidget *self, GtkTreeView *tree_view)
+{
+    GtkWidget *sw;
+    /* scrolled window */
+    sw = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+        GTK_SHADOW_ETCHED_IN);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+    gtk_widget_set_hexpand(sw, TRUE);
+    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
+    gtk_widget_set_vexpand(sw, TRUE);
+    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
+    gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view));
+    gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0);
+}
+
+static void spice_usb_device_widget_constructed(GObject *gobject)
+{
+    SpiceUsbDeviceWidget *self;
+    GtkRequisition min_size, natural_size;
+    SpiceUsbDeviceWidgetPrivate *priv;
+    GtkWidget *hbox, *dev_label;
+    GtkWidget *add_cd_button, *add_cd_icon;
+    GPtrArray *devices = NULL;
+    GError *err = NULL;
+    gchar *str;
+    guint i;
+    gboolean cd_sharing_enabled = TRUE;
+    #ifndef USE_CD_SHARING
+    cd_sharing_enabled = FALSE;
+    #endif
+
+    self = SPICE_USB_DEVICE_WIDGET(gobject);
+    priv = self->priv;
+    if (!priv->session)
+        g_error("SpiceUsbDeviceWidget constructed without a session");
+
+    min_size.width = 600;
+    min_size.height = 300;
+    natural_size.width = 1200;
+    natural_size.height = 600;
+    gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size, &natural_size);
+
+    priv->label = gtk_label_new(NULL);
+    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));
+    gtk_label_set_markup(GTK_LABEL(priv->label), str);
+    g_free(str);
+    gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0);
+
+    priv->icon_cd = get_named_icon("media-optical", GTK_ICON_SIZE_LARGE_TOOLBAR);
+    priv->icon_connected = get_named_icon("network-transmit-receive", GTK_ICON_SIZE_LARGE_TOOLBAR);
+    priv->icon_disconn = get_named_icon("network-offline", GTK_ICON_SIZE_LARGE_TOOLBAR);
+    priv->icon_warning = get_named_icon("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR);
+    priv->icon_info = get_named_icon("dialog-information", GTK_ICON_SIZE_LARGE_TOOLBAR);
+
+    priv->manager = spice_usb_device_manager_get(priv->session, &err);
+    if (err) {
+        spice_usb_device_widget_show_info_bar(self, err->message,
+                                              GTK_MESSAGE_WARNING, priv->icon_warning);
+        g_clear_error(&err);
+        return;
+    }
+
+    if (cd_sharing_enabled) {
+        spice_usb_device_widget_create_tree_view(self, TRUE);
+    } else {
+        priv->cd_tree.tree_store = NULL;
+        priv->cd_tree.tree_view = NULL;
+    }
+    spice_usb_device_widget_create_tree_view(self, FALSE);
+
+    spice_usb_device_widget_signals_connect(self);
+
+    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
+
+    /* "Available devices" label - in hbox */
+    dev_label = gtk_label_new(_("Local USB devices"));
+    gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
+    create_tree_window(self, priv->usb_tree.tree_view);
+
+    if (cd_sharing_enabled) {
+        hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+        gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
+        dev_label = gtk_label_new(_("Shared CD devices"));
+        gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
+        /* "Add CD" button - in hbox */
+        add_cd_button = gtk_button_new_with_label(_("Add CD"));
+        gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button), TRUE);
+        add_cd_icon = gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(add_cd_button), add_cd_icon);
+
+        gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END);
+        g_signal_connect(add_cd_button, "clicked", G_CALLBACK(add_cd_lun_button_clicked_cb), self);
+        gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE, FALSE, 0);
+        create_tree_window(self, priv->cd_tree.tree_view);
+    }
+    devices = spice_usb_device_manager_get_devices(priv->manager);
+    if (!devices)
+        goto end;
+
+    for (i = 0; i < devices->len; i++) {
+        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);
+        usb_widget_add_device(self, usb_device, NULL);
+    }
+    g_ptr_array_unref(devices);
+
+    select_widget_size(GTK_WIDGET(self));
+
+end:
+    spice_usb_device_widget_update_status(self);
+}
+
+static void spice_usb_device_widget_finalize(GObject *object)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (priv->manager) {
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_added_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_removed_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_changed_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->manager,
+                                             device_error_cb, self);
+    }
+    g_object_unref(priv->session);
+    g_free(priv->device_format_string);
+
+    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object);
+}
+
+static void spice_usb_device_widget_class_init(
+    SpiceUsbDeviceWidgetClass *klass)
+{
+    GObjectClass *gobject_class = (GObjectClass *)klass;
+    GParamSpec *pspec;
+
+    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
+
+    gobject_class->constructed  = spice_usb_device_widget_constructed;
+    gobject_class->finalize     = spice_usb_device_widget_finalize;
+    gobject_class->get_property = spice_usb_device_widget_get_property;
+    gobject_class->set_property = spice_usb_device_widget_set_property;
+
+    /**
+     * SpiceUsbDeviceWidget:session:
+     *
+     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
+     *
+     **/
+    pspec = g_param_spec_object("session",
+                                "Session",
+                                "SpiceSession",
+                                SPICE_TYPE_SESSION,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
+
+    /**
+     * SpiceUsbDeviceWidget:device-format-string:
+     *
+     * Format string to pass to spice_usb_device_get_description() for getting
+     * the device USB descriptions.
+     */
+    pspec = g_param_spec_string("device-format-string",
+                                "Device format string",
+                                "Format string for device description",
+                                NULL,
+                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                                G_PARAM_STATIC_STRINGS);
+    g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING,
+                                    pspec);
+
+    /**
+     * SpiceUsbDeviceWidget::connect-failed:
+     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
+     * @device: #SpiceUsbDevice boxed object corresponding to the added device
+     * @error:  #GError describing the reason why the connect failed
+     *
+     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever
+     * the user has requested for a device to be redirected and this has
+     * failed.
+     **/
+    signals[CONNECT_FAILED] =
+        g_signal_new("connect-failed",
+                    G_OBJECT_CLASS_TYPE(gobject_class),
+                    G_SIGNAL_RUN_FIRST,
+                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed),
+                    NULL, NULL,
+                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
+                    G_TYPE_NONE,
+                    2,
+                    SPICE_TYPE_USB_DEVICE,
+                    G_TYPE_ERROR);
+}
+
+static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
+{
+    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
+}
+
+/* ------------------------------------------------------------------ */
+/* public api                                                         */
+
+/**
+ * spice_usb_device_widget_new:
+ * @session: #SpiceSession for which to widget will control USB redirection
+ * @device_format_string: (allow-none): String passed to
+ * spice_usb_device_get_description()
+ *
+ * Creates a new widget to control USB redirection.
+ *
+ * Returns: a new #SpiceUsbDeviceWidget instance
+ */
+GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
+                                       const gchar     *device_format_string)
+{
+    static gboolean init_columns = TRUE;
+    spice_util_get_debug();
+    if (init_columns) {
+        initialize_columns();
+        init_columns = FALSE;
+    }
+    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
+                        "orientation", GTK_ORIENTATION_VERTICAL,
+                        "session", session,
+                        "device-format-string", device_format_string,
+                        "spacing", 6,
+                        NULL);
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks                                                          */
+
+static gboolean usb_widget_tree_store_check_redirect_foreach_cb(GtkTreeModel *tree_model,
+                                                                GtkTreePath *path,
+                                                                GtkTreeIter *iter,
+                                                                SpiceUsbDeviceWidget *self,
+                                                                GtkTreeStore *tree_store)
+{
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+
+    if (!tree_item_is_lun(tree_store, iter)) {
+        SpiceUsbDevice *usb_device;
+        gboolean can_redirect;
+
+        gtk_tree_model_get(tree_model, iter,
+                           COL_ITEM_DATA, (gpointer *)&usb_device,
+                           -1);
+
+        if (spice_usb_device_manager_is_redirecting(priv->manager)) {
+            can_redirect = FALSE;
+        } else {
+            GError *err = NULL;
+
+            can_redirect = spice_usb_device_manager_can_redirect_device(priv->manager,
+                                                                        usb_device, &err);
+
+            /* If we cannot redirect this device, append the error message to
+               err_msg, but only if it is *not* already there! */
+            if (!can_redirect) {
+                if (priv->err_msg) {
+                    if (!strstr(priv->err_msg, err->message)) {
+                        gchar *old_err_msg = priv->err_msg;
+                        priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg,
+                                                        err->message);
+                        g_free(old_err_msg);
+                    }
+                } else {
+                    priv->err_msg = g_strdup(err->message);
+                }
+            }
+            g_clear_error(&err);
+        }
+        gtk_tree_store_set(tree_store, iter,
+                           COL_CAN_REDIRECT, can_redirect,
+                           -1);
+    }
+    return FALSE; /* continue iterating */
+}
+
+static gboolean usb_widget_tree_store_check_redirect_foreach_cb_usb(GtkTreeModel *tree_model,
+                                                                 GtkTreePath *path, GtkTreeIter *iter,
+                                                                 gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    return usb_widget_tree_store_check_redirect_foreach_cb(
+        tree_model, path, iter, self, priv->usb_tree.tree_store);
+}
+
+#ifdef USE_CD_SHARING
+static gboolean usb_widget_tree_store_check_redirect_foreach_cb_cd(GtkTreeModel *tree_model,
+                                                                 GtkTreePath *path, GtkTreeIter *iter,
+                                                                 gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    return usb_widget_tree_store_check_redirect_foreach_cb(
+        tree_model, path, iter, self, priv->cd_tree.tree_store);
+}
+#endif
+
+static gboolean spice_usb_device_widget_update_status(gpointer user_data)
+{
+    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
+    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
+    gchar *str, *markup_str;
+    const gchar *free_channels_str;
+    int free_channels;
+
+    g_object_get(priv->manager, "free-channels", &free_channels, NULL);
+    free_channels_str = ngettext(_("Select USB devices to redirect (%d free channel)"),
+                                 _("Select USB devices to redirect (%d free channels)"),
+                                 free_channels);
+    str = g_strdup_printf(free_channels_str, free_channels);
+    markup_str = g_strdup_printf("<b>%s</b>", str);
+    gtk_label_set_markup(GTK_LABEL (priv->label), markup_str);
+    g_free(markup_str);
+    g_free(str);
+
+    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->usb_tree.tree_store),
+                           usb_widget_tree_store_check_redirect_foreach_cb_usb, self);
+    gtk_widget_show_all(GTK_WIDGET(priv->usb_tree.tree_view));
+
+#ifdef USE_CD_SHARING
+    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->cd_tree.tree_store),
+                           usb_widget_tree_store_check_redirect_foreach_cb_cd, self);
+    gtk_widget_show_all(GTK_WIDGET(priv->cd_tree.tree_view));
+#endif
+
+    /* Show messages in the info, if necessary */
+    if (priv->err_msg) {
+        spice_usb_device_widget_show_info_bar(self, priv->err_msg,
+                                              GTK_MESSAGE_INFO, priv->icon_warning);
+        g_free(priv->err_msg);
+        priv->err_msg = NULL;
+    } else if ( spice_usb_device_manager_is_redirecting(priv->manager)) {
+        spice_usb_device_widget_show_info_bar(self, _("Redirecting USB Device..."),
+                                              GTK_MESSAGE_INFO, priv->icon_info);
+    } else {
+        spice_usb_device_widget_hide_info_bar(self);
+    }
+
+    if (priv->device_count == 0)
+        spice_usb_device_widget_show_info_bar(self, _("No USB devices detected"),
+                                              GTK_MESSAGE_INFO, priv->icon_info);
+
+    return FALSE;
+}
+
+#endif

Comments

On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
> New USB widget supports all the functionality related to
> redirection of local USB device (as previous usb widget did).
> Additionally it allows creation, management and redirection of
> emulated USB CD devices for CD sharing.

Wouldn't be possible to extend the current widget instead of
creating a new one?

> Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
> Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
> ---
>  src/usb-device-redir-widget.c | 2065 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 2065 insertions(+)
>  create mode 100644 src/usb-device-redir-widget.c
> 
> diff --git a/src/usb-device-redir-widget.c b/src/usb-device-redir-widget.c
> new file mode 100644
> index 0000000..59a6043
> --- /dev/null
> +++ b/src/usb-device-redir-widget.c
> @@ -0,0 +1,2065 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2012 Red Hat, Inc.
> +
> +   Red Hat Authors:
> +   Alexander Nezhinsky<anezhins@redhat.com>
> +
> +   This library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   This library 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
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with this library; if not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#include "config.h"
> +#ifndef USB_WIDGET_TEST
> +    #include <glib/gi18n-lib.h>
> +    #include "spice-client.h"
> +    #include "spice-marshal.h"
> +#else
> +    #include "spice-client.h"
> +#endif
> +#include "usb-device-widget.h"
> +
> +/*
> +    Debugging note:
> +    Logging from this module is not affected by --spice-debug
> +    command line parameter
> +    Use SPICE_DEBUG=1 environment varible to enable logs
> +*/
> +
> +#ifdef USE_NEW_USB_WIDGET
> +
> +/**
> + * SECTION:usb-device-widget
> + * @short_description: USB device selection widget
> + * @title: Spice USB device selection widget
> + * @section_id:
> + * @see_also:
> + * @stability: Under development
> + * @include: spice-client-gtk.h
> + *
> + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
> + * add an UI to select USB devices to redirect (or unredirect).
> + */
> +
> +struct _SpiceUsbDeviceWidget
> +{
> +    GtkBox parent;
> +
> +    SpiceUsbDeviceWidgetPrivate *priv;
> +};
> +
> +struct _SpiceUsbDeviceWidgetClass
> +{
> +    GtkBoxClass parent_class;
> +
> +    /* signals */
> +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
> +                            SpiceUsbDevice *device, GError *error);
> +};
> +
> +/* ------------------------------------------------------------------ */
> +/* Prototypes for callbacks  */
> +static void device_added_cb(SpiceUsbDeviceManager *manager,
> +    SpiceUsbDevice *device, gpointer user_data);
> +static void device_removed_cb(SpiceUsbDeviceManager *manager,
> +    SpiceUsbDevice *device, gpointer user_data);
> +static void device_changed_cb(SpiceUsbDeviceManager *manager,
> +    SpiceUsbDevice *device, gpointer user_data);
> +static void device_error_cb(SpiceUsbDeviceManager *manager,
> +    SpiceUsbDevice *device, GError *err, gpointer user_data);
> +static gboolean spice_usb_device_widget_update_status(gpointer user_data);
> +
> +/* ------------------------------------------------------------------ */
> +/* gobject glue                                                       */
> +
> +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
> +    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
> +                                 SpiceUsbDeviceWidgetPrivate))
> +
> +enum {
> +    PROP_0,
> +    PROP_SESSION,
> +    PROP_DEVICE_FORMAT_STRING,
> +};
> +
> +enum {
> +    CONNECT_FAILED,
> +    LAST_SIGNAL,
> +};
> +
> +typedef struct {
> +    GtkTreeView *tree_view;
> +    GtkTreeStore *tree_store;
> +} SpiceUsbDeviceWidgetTree;
> +
> +struct _SpiceUsbDeviceWidgetPrivate {
> +    SpiceSession *session;
> +    gchar *device_format_string;
> +    SpiceUsbDeviceManager *manager;
> +    GtkWidget *info_bar;
> +    GtkWidget *label;
> +    SpiceUsbDeviceWidgetTree cd_tree;
> +    SpiceUsbDeviceWidgetTree usb_tree;
> +    GdkPixbuf *icon_cd;
> +    GdkPixbuf *icon_connected;
> +    GdkPixbuf *icon_disconn;
> +    GdkPixbuf *icon_warning;
> +    GdkPixbuf *icon_info;
> +    gchar *err_msg;
> +    gsize device_count;
> +};
> +
> +static guint signals[LAST_SIGNAL] = { 0, };
> +
> +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
> +
> +/* TREE */
> +
> +enum column_id
> +{
> +    COL_REDIRECT = 0,
> +    COL_ADDRESS,
> +    COL_CONNECT_ICON,
> +    COL_CD_ICON,
> +    COL_VENDOR,
> +    COL_PRODUCT,
> +    COL_FILE,
> +    COL_LOADED,
> +    COL_LOCKED,
> +    COL_IDLE,
> +    /* internal columns */
> +    COL_REVISION,
> +    COL_CD_DEV,
> +    COL_LUN_ITEM,
> +    COL_DEV_ITEM,
> +    COL_ITEM_DATA,
> +    COL_CONNECTED,
> +    COL_CAN_REDIRECT,
> +    COL_ROW_COLOR,
> +    COL_ROW_COLOR_SET,
> +    NUM_COLS,
> +
> +    INVALID_COL
> +};
> +
> +// there is a possibility to use different names
> +// for columns in USB device list and CD device list, if needed
> +// currently they are identical
> +static const char *column_names_cd[NUM_COLS];
> +static const char *column_names_usb[NUM_COLS];
> +
> +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
> +
> +static void initialize_columns(void)
> +{
> +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
> +    SET_COLUMN(COL_ADDRESS, _("Address"));
> +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
> +    SET_COLUMN(COL_CD_ICON, _("CD"));
> +    SET_COLUMN(COL_VENDOR, _("Vendor"));
> +    SET_COLUMN(COL_PRODUCT, _("Product"));
> +    SET_COLUMN(COL_FILE, _("File/Device Path"));
> +    SET_COLUMN(COL_LOADED, _("Loaded"));
> +    SET_COLUMN(COL_LOCKED, _("Locked"));
> +    SET_COLUMN(COL_IDLE, _("Idle"));
> +    SET_COLUMN(COL_REVISION, "?Revision");
> +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
> +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
> +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
> +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
> +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
> +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
> +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
> +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
> +};
> +
> +static const char *column_name(enum column_id id, gboolean is_cd)
> +{
> +    const char **col_name = is_cd ? column_names_cd : column_names_usb;
> +    return col_name[id];
> +}
> +
> +typedef struct _UsbWidgetLunItem {
> +    SpiceUsbDeviceManager *manager;
> +    SpiceUsbDevice *device;
> +    guint lun;
> +    SpiceUsbDeviceLunInfo info;
> +} UsbWidgetLunItem;
> +
> +typedef struct _TreeFindUsbDev {
> +    SpiceUsbDevice *usb_dev;
> +    GtkTreeIter dev_iter;
> +} TreeFindUsbDev;
> +
> +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *, gpointer);
> +
> +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> +                                          SpiceUsbDevice *usb_device,
> +                                          GtkTreeIter *old_dev_iter);
> +
> +static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
> +                                     SpiceUsbDevice *device,
> +                                     const gchar *format)
> +{
> +    SpiceUsbDeviceDescription desc;
> +    gchar *descriptor;
> +    gchar *res;
> +    spice_usb_device_get_info(manager, device, &desc);
> +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id);
> +    if (!format) {
> +        format = _("%s %s %s at %d-%d");
> +    }
> +    res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address);
> +    g_free(desc.vendor);
> +    g_free(desc.product);
> +    g_free(descriptor);
> +    return res;
> +}
> +
> +static GtkTreeStore* usb_widget_create_tree_store(void)
> +{
> +    GtkTreeStore *tree_store;
> +
> +    tree_store = gtk_tree_store_new(NUM_COLS,
> +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
> +                        G_TYPE_STRING, /* COL_ADDRESS */
> +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
> +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
> +                        G_TYPE_STRING, /* COL_VENDOR */
> +                        G_TYPE_STRING, /* COL_PRODUCT */
> +                        G_TYPE_STRING, /* COL_FILE */
> +                        G_TYPE_BOOLEAN, /* COL_LOADED */
> +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
> +                        G_TYPE_BOOLEAN, /* COL_IDLE */
> +                        /* internal columns */
> +                        G_TYPE_STRING, /* COL_REVISION */
> +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
> +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
> +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
> +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
> +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
> +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
> +                        G_TYPE_STRING, /* COL_ROW_COLOR */
> +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
> +    SPICE_DEBUG("tree store created");
> +
> +    return tree_store;
> +}
> +
> +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
> +{
> +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), name, size, 0);
> +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
> +    g_object_unref (info);
> +    return pixbuf;
> +}
> +
> +static void select_widget_size(GtkWidget *wg)
> +{
> +    GdkDisplay *d = gtk_widget_get_display(wg);
> +    int i, w = 2000, h = 1024;
> +    int n = gdk_display_get_n_monitors(d);
> +    for (i = 0; i < n; ++i)
> +    {
> +        GdkMonitor *m = gdk_display_get_monitor(d, i);
> +        if (m) {
> +            GdkRectangle area;
> +            gdk_monitor_get_workarea(m, &area);
> +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
> +                i, area.width, area.height, area.x, area.y );
> +            w = MIN(w, area.width);
> +            h = MIN(h, area.height);
> +        }
> +    }
> +
> +    w = (w * 3) / 4;
> +    h = h / 2;
> +
> +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
> +    gtk_widget_set_size_request(wg, w, h);
> +}
> +
> +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> +                                          SpiceUsbDevice *usb_device,
> +                                          GtkTreeIter *old_dev_iter)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> +    SpiceUsbDeviceWidgetTree *tree;
> +    GtkTreeView *tree_view;
> +    GtkTreeStore *tree_store;
> +    GtkTreeIter new_dev_iter;
> +    SpiceUsbDeviceDescription dev_info;
> +    GtkTreePath *new_dev_path;
> +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
> +    gchar *addr_str;
> +    GArray *lun_array;
> +    guint lun_index;
> +    GError *error = NULL;
> +
> +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr, usb_device);
> +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
> +    tree_view = tree->tree_view;
> +    tree_store = tree->tree_store;
> +
> +    if (old_dev_iter == NULL) {
> +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
> +    } else {
> +        gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL, old_dev_iter);
> +        gtk_tree_store_remove(tree_store, old_dev_iter);
> +    }
> +
> +    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
> +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address);
> +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr, usb_device);
> +    is_dev_redirected = is_dev_connected;
> +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
> +        addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd);
> +
> +    gtk_tree_store_set(tree_store, &new_dev_iter,
> +        COL_REDIRECT, is_dev_redirected,
> +        COL_ADDRESS, addr_str,
> +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn,
> +        COL_CD_ICON, priv->icon_cd,
> +        COL_VENDOR, dev_info.vendor,
> +        COL_PRODUCT, dev_info.product,
> +        COL_CD_DEV, is_dev_cd,
> +        COL_LUN_ITEM, FALSE, /* USB device item */
> +        COL_DEV_ITEM, TRUE, /* USB device item */
> +        COL_ITEM_DATA, (gpointer)usb_device,
> +        COL_CONNECTED, is_dev_connected,
> +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr, usb_device, &error),
> +        COL_ROW_COLOR, "beige",
> +        COL_ROW_COLOR_SET, TRUE,
> +        -1);
> +    g_clear_error(&error);
> +
> +    priv->device_count++;
> +
> +    /* get all the luns */
> +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, usb_device);
> +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
> +        UsbWidgetLunItem *lun_item;
> +        GtkTreeIter lun_iter;
> +        gchar lun_str[8];
> +
> +        lun_item = g_malloc(sizeof(*lun_item));
> +        lun_item->manager = usb_dev_mgr;
> +        lun_item->device = usb_device;
> +        lun_item->lun = g_array_index(lun_array, guint, lun_index);
> +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr, usb_device, lun_item->lun, &lun_item->info);
> +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",
> +                lun_index, lun_item->info.vendor, lun_item->info.product,
> +                lun_item->info.revision, lun_item->info.file_path, lun_item);
> +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
> +
> +        /* Append LUN as a child of USB device */
> +        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
> +        gtk_tree_store_set(tree_store, &lun_iter,
> +                COL_ADDRESS, lun_str,
> +                COL_VENDOR, lun_item->info.vendor,
> +                COL_PRODUCT, lun_item->info.product,
> +                COL_REVISION, lun_item->info.revision,
> +                COL_FILE, lun_item->info.file_path,
> +                COL_LOADED, lun_item->info.loaded,
> +                COL_LOCKED, lun_item->info.locked,
> +                COL_IDLE, !lun_item->info.started,
> +                COL_CD_DEV, FALSE,
> +                COL_LUN_ITEM, TRUE, /* LUN item */
> +                COL_DEV_ITEM, FALSE, /* LUN item */
> +                COL_ITEM_DATA, (gpointer)lun_item,
> +                COL_CONNECTED, is_dev_connected,
> +                COL_ROW_COLOR, "azure",
> +                COL_ROW_COLOR_SET, TRUE,
> +                -1);
> +    }
> +    g_array_unref(lun_array);
> +
> +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &new_dev_iter);
> +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
> +    gtk_tree_path_free(new_dev_path);
> +
> +    g_free(dev_info.vendor);
> +    g_free(dev_info.product);
> +    g_free(addr_str);
> +}
> +
> +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter)
> +{
> +    gboolean is_lun;
> +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);
> +    return is_lun;
> +}
> +
> +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel *tree_model,
> +                                                              GtkTreePath *path, GtkTreeIter *iter,
> +                                                              gpointer user_data)
> +{
> +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
> +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
> +    SpiceUsbDevice *usb_device;
> +    gboolean is_lun_item;
> +
> +    gtk_tree_model_get(tree_model, iter,
> +                       COL_LUN_ITEM, &is_lun_item,
> +                       COL_ITEM_DATA, (gpointer *)&usb_device,
> +                       -1);
> +    if (!is_lun_item && usb_device == find_usb_device) {
> +        find_info->dev_iter = *iter;
> +        find_info->usb_dev  = NULL;
> +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
> +        return TRUE; /* stop iterating */
> +    } else {
> +        return FALSE; /* continue iterating */
> +    }
> +}
> +
> +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore *tree_store,
> +                                                          SpiceUsbDevice *usb_device)
> +{
> +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };
> +    GtkTreeIter *iter = NULL;
> +
> +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
> +                           usb_widget_tree_store_find_usb_dev_foreach_cb, (gpointer)&find_info);
> +    // the callback sets 'usb_dev' field to zero if it finds the device
> +    if (!find_info.usb_dev) {
> +        iter = g_malloc(sizeof(*iter));
> +        *iter = find_info.dev_iter;
> +    }
> +    return iter;
> +}
> +
> +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
> +                                         SpiceUsbDevice *usb_device)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeIter *old_dev_iter;
> +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
> +    // on WIN32 it is possible the backend device is already removed
> +    // from the USB device manager list and we can't know it was
> +    // USB or CD, do we will try both lists
> +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
> +    if (old_dev_iter != NULL) {
> +        SPICE_DEBUG("USB Device removed");
> +        gtk_tree_store_remove(tree_store, old_dev_iter);
> +        priv->device_count--;
> +        g_free(old_dev_iter);
> +        return TRUE;
> +    }
> +    tree_store = priv->cd_tree.tree_store;
> +    if (tree_store) {
> +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
> +    }
> +    if (old_dev_iter != NULL) {
> +        SPICE_DEBUG("CD Device removed");
> +        gtk_tree_store_remove(tree_store, old_dev_iter);
> +        priv->device_count--;
> +        g_free(old_dev_iter);
> +        return TRUE;
> +    }
> +    SPICE_DEBUG("Device %p not found!", usb_device);
> +    return FALSE;
> +}
> +
> +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget *self,
> +                                                 enum column_id toggle_col_id,
> +                                                 enum column_id visible_col_id,
> +                                                 enum column_id sensitive_col_id,
> +                                                 tree_item_toggled_cb toggled_cb,
> +                                                 gboolean is_cd)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkCellRenderer     *renderer;
> +    GtkTreeViewColumn   *view_col;
> +    const char *col_name = column_name(toggle_col_id, is_cd);
> +
> +    renderer = gtk_cell_renderer_toggle_new();
> +
> +    if (sensitive_col_id != INVALID_COL) {
> +        view_col = gtk_tree_view_column_new_with_attributes(
> +                        col_name,
> +                        renderer,
> +                        "active", toggle_col_id,
> +                        "visible", visible_col_id,
> +                        "activatable", sensitive_col_id,
> +                        NULL);
> +    } else {
> +        view_col = gtk_tree_view_column_new_with_attributes(
> +                        col_name,
> +                        renderer,
> +                        "active", toggle_col_id,
> +                        "visible", visible_col_id,
> +                        NULL);
> +    }
> +
> +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
> +    gtk_tree_view_column_set_expand(view_col, FALSE);
> +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
> +
> +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);
> +
> +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]",
> +            toggle_col_id, col_name,
> +            visible_col_id, column_name(visible_col_id, is_cd));
> +    return view_col;
> +}
> +
> +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget *self,
> +                                                           enum column_id toggle_col_id,
> +                                                           enum column_id visible_col_id,
> +                                                           gboolean is_cd)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkCellRenderer     *renderer;
> +    GtkTreeViewColumn   *view_col;
> +    const char *col_name = column_name(toggle_col_id, is_cd);
> +
> +    renderer = gtk_cell_renderer_toggle_new();
> +
> +    view_col = gtk_tree_view_column_new_with_attributes(
> +                    col_name,
> +                    renderer,
> +                    "active", toggle_col_id,
> +                    "visible", visible_col_id,
> +                    NULL);
> +
> +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
> +
> +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
> +    gtk_tree_view_column_set_expand(view_col, FALSE);
> +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
> +
> +    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]",
> +            toggle_col_id, col_name,
> +            visible_col_id, column_name(visible_col_id, is_cd));
> +    return view_col;
> +}
> +
> +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget *self,
> +                                               enum column_id col_id,
> +                                               gboolean expandable,
> +                                               gboolean is_cd)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkCellRenderer     *renderer;
> +    GtkTreeViewColumn   *view_col;
> +
> +    renderer = gtk_cell_renderer_text_new();
> +
> +    view_col = gtk_tree_view_column_new_with_attributes(
> +                    column_name(col_id, is_cd),
> +                    renderer,
> +                    "text", col_id,
> +                    //"cell-background", COL_ROW_COLOR,
> +                    //"cell-background-set", COL_ROW_COLOR_SET,
> +                    NULL);
> +
> +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
> +    gtk_tree_view_column_set_resizable(view_col, TRUE);
> +    gtk_tree_view_column_set_expand(view_col, expandable);
> +
> +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
> +
> +    SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd));
> +    return view_col;
> +}
> +
> +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget *self,
> +                                                 enum column_id col_id,
> +                                                 enum column_id visible_col_id,
> +                                                 gboolean is_cd)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkCellRenderer     *renderer;
> +    GtkTreeViewColumn   *view_col;
> +    const char *col_name = column_name(col_id, is_cd);
> +
> +    renderer = gtk_cell_renderer_pixbuf_new();
> +
> +    if (visible_col_id == INVALID_COL) {
> +        view_col = gtk_tree_view_column_new_with_attributes(
> +                        col_name,
> +                        renderer,
> +                        "pixbuf", col_id,
> +                        NULL);
> +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name);
> +    } else {
> +        view_col = gtk_tree_view_column_new_with_attributes(
> +                        col_name,
> +                        renderer,
> +                        "pixbuf", col_id,
> +                        "visible", visible_col_id,
> +                        NULL);
> +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]",
> +                col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd));
> +    }
> +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
> +    return view_col;
> +}
> +
> +/* Toggle handlers */
> +
> +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
> +{
> +    gboolean toggle_val;
> +
> +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store), iter, path_str);
> +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id, &toggle_val, -1);
> +
> +    return toggle_val;
> +}
> +
> +static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
> +{
> +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
> +}
> +
> +typedef struct _connect_cb_data {
> +    SpiceUsbDeviceWidget *self;
> +    SpiceUsbDevice *usb_dev;
> +} connect_cb_data;
> +
> +static void connect_cb_data_free(connect_cb_data *user_data)
> +{
> +    spice_usb_device_widget_update_status(user_data->self);
> +    g_object_unref(user_data->self);
> +    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
> +    g_free(user_data);
> +}
> +
> +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
> +{
> +    connect_cb_data *cb_data = user_data;
> +    SpiceUsbDeviceWidget *self = cb_data->self;
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> +    GError *err = NULL;
> +    GtkTreeIter *dev_iter;
> +    gchar *desc;
> +    gboolean finished;
> +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
> +
> +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
> +    if (!dev_iter) {
> +        return;
> +    }
> +
> +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
> +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
> +
> +    finished = spice_usb_device_manager_connect_device_finish(priv->manager, res, &err);
> +    if (finished) {
> +        gtk_tree_store_set(tree_store, dev_iter,
> +                           COL_CONNECT_ICON, priv->icon_connected,
> +                           COL_CONNECTED, TRUE,
> +                           -1);
> +    } else {
> +        gtk_tree_store_set(tree_store, dev_iter,
> +                           COL_REDIRECT, FALSE,
> +                           -1);
> +        g_prefix_error(&err, "Device connect failed %s: ", desc);
> +        if (err) {
> +            SPICE_DEBUG("%s", err->message);
> +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err);
> +            g_error_free(err);
> +        } else {
> +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL);
> +        }
> +
> +        /* don't trigger a disconnect if connect failed */
> +        /*
> +        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data->check),
> +                                        checkbox_clicked_cb, self);
> +        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check), FALSE);
> +        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_data->check),
> +                                        checkbox_clicked_cb, self);
> +        */
> +    }
> +    g_free(desc);
> +    g_free(dev_iter);
> +    connect_cb_data_free(user_data);
> +}
> +
> +static void usb_widget_disconnect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
> +{
> +    connect_cb_data *cb_data = user_data;
> +    SpiceUsbDeviceWidget *self = cb_data->self;
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> +    GError *err = NULL;
> +    GtkTreeIter *dev_iter;
> +    gchar *desc;
> +    gboolean finished;
> +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
> +
> +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
> +    if (!dev_iter) {
> +        return;
> +    }
> +
> +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
> +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
> +
> +    finished = spice_usb_device_manager_disconnect_device_finish(priv->manager, res, &err);
> +    if (finished) {
> +        gtk_tree_store_set(tree_store, dev_iter,
> +                           COL_CONNECT_ICON, priv->icon_disconn,
> +                           COL_CONNECTED, FALSE,
> +                           -1);
> +    } else {
> +        gtk_tree_store_set(tree_store, dev_iter,
> +                           COL_REDIRECT, TRUE,
> +                           -1);
> +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
> +        if (err) {
> +            SPICE_DEBUG("%s", err->message);
> +            g_error_free(err);
> +        }
> +    }
> +    g_free(desc);
> +    g_free(dev_iter);
> +    connect_cb_data_free(user_data);
> +}
> +
> +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell,
> +                                          gchar *path_str,
> +                                          SpiceUsbDeviceWidget *self,
> +                                          GtkTreeStore *tree_store)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
> +    SpiceUsbDevice *usb_dev;
> +    GtkTreeIter iter;
> +    gboolean new_redirect_val;
> +
> +    new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str, &iter, COL_REDIRECT);
> +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
> +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT, new_redirect_val);
> +
> +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
> +    cb_data->self = g_object_ref(self);
> +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(), usb_dev);
> +
> +    if (new_redirect_val) {
> +        spice_usb_device_manager_connect_device_async(priv->manager, usb_dev,
> +                                                      NULL, /* cancellable */
> +                                                      usb_widget_connect_cb, cb_data);
> +    } else {
> +        spice_usb_device_manager_disconnect_device_async(priv->manager, usb_dev,
> +                                                         NULL, /* cancellable */
> +                                                         usb_widget_disconnect_cb, cb_data);
> +
> +    }
> +    spice_usb_device_widget_update_status(self);
> +}
> +
> +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle *cell,
> +                                             gchar *path_str,
> +                                             gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->cd_tree.tree_store);
> +}
> +
> +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle *cell,
> +                                             gchar *path_str,
> +                                             gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->usb_tree.tree_store);
> +}
> +
> +/* Signal handlers */
> +
> +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> +    SpiceUsbDevice *usb_device, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +
> +    SPICE_DEBUG("Signal: Device Added");
> +
> +    usb_widget_add_device(self, usb_device, NULL);
> +
> +    spice_usb_device_widget_update_status(self);
> +}
> +
> +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> +    SpiceUsbDevice *usb_device, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    gboolean dev_removed;
> +
> +    SPICE_DEBUG("Signal: Device Removed");
> +
> +    dev_removed = usb_widget_remove_device(self, usb_device);
> +    if (dev_removed) {
> +        spice_usb_device_widget_update_status(self);
> +    }
> +}
> +
> +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> +    SpiceUsbDevice *usb_device, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeIter *old_dev_iter;
> +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
> +
> +    SPICE_DEBUG("Signal: Device Changed");
> +
> +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
> +    if (old_dev_iter != NULL) {
> +
> +        usb_widget_add_device(self, usb_device, old_dev_iter);
> +
> +        spice_usb_device_widget_update_status(self);
> +        g_free(old_dev_iter);
> +    } else {
> +        SPICE_DEBUG("Device not found!");
> +    }
> +}
> +
> +static void device_error_cb(SpiceUsbDeviceManager *manager,
> +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeIter *dev_iter;
> +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
> +
> +    SPICE_DEBUG("Signal: Device Error");
> +
> +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
> +    if (dev_iter != NULL) {
> +        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT, FALSE);
> +        spice_usb_device_widget_update_status(self);
> +        g_free(dev_iter);
> +    } else {
> +        SPICE_DEBUG("Device not found!");
> +    }
> +}
> +
> +/* Selection handler */
> +
> +static void tree_selection_changed_cb(GtkTreeSelection *select, gpointer user_data)
> +{
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +    GtkTreePath *path;
> +    gboolean is_lun;
> +    UsbWidgetLunItem *lun_item;
> +    gchar *txt[3];
> +
> +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        gtk_tree_model_get(tree_model, &iter,
> +                COL_VENDOR, &txt[0],
> +                COL_PRODUCT, &txt[1],
> +                COL_REVISION, &txt[2],
> +                COL_LUN_ITEM, &is_lun,
> +                COL_ITEM_DATA, (gpointer *)&lun_item,
> +                -1);
> +        path = gtk_tree_model_get_path(tree_model, &iter);
> +
> +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
> +                txt[0], txt[1],
> +                is_lun ? txt[2] : "--",
> +                is_lun ? "LUN" : "USB-DEV",
> +                is_lun ? lun_item->info.file_path : "--",
> +                gtk_tree_path_to_string(path));
> +
> +        if (txt[0]) {
> +            g_free(txt[0]);
> +        }
> +        if (txt[1]) {
> +            g_free(txt[1]);
> +        }
> +        if (txt[2]) {
> +            g_free(txt[2]);
> +        }
> +        gtk_tree_path_free(path);
> +    }
> +}
> +
> +static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view)
> +{
> +    GtkTreeSelection *select;
> +
> +    select = gtk_tree_view_get_selection(tree_view);
> +    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
> +
> +    g_signal_connect(G_OBJECT(select), "changed",
> +                     G_CALLBACK(tree_selection_changed_cb),
> +                     NULL);
> +
> +    SPICE_DEBUG("selection handler set");
> +    return select;
> +}
> +
> +static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent)
> +{
> +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
> +    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
> +    GtkWidget *label = gtk_accel_label_new(label_str);
> +    GtkAccelGroup *accel_group = gtk_accel_group_new();
> +    guint accel_key;
> +
> +    /* add icon */
> +    gtk_container_add(GTK_CONTAINER(box), icon);
> +
> +    /* add label */
> +    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
> +    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
> +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);
> +    gtk_widget_add_accelerator(parent, "activate", accel_group, accel_key,
> +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
> +    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent);
> +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
> +
> +    /* add the new box to the parent widget */
> +    gtk_container_add(GTK_CONTAINER(parent), box);
> +
> +    return box;
> +}
> +
> +/* LUN properties dialog */
> +
> +typedef struct _lun_properties_dialog {
> +    GtkWidget *dialog;
> +    GtkWidget *advanced_grid;
> +    gboolean advanced_shown;
> +
> +    GtkWidget *file_entry;
> +    GtkWidget *vendor_entry;
> +    GtkWidget *product_entry;
> +    GtkWidget *revision_entry;
> +    GtkWidget *loaded_switch;
> +    GtkWidget *locked_switch;
> +} lun_properties_dialog;
> +
> +#if 1
> +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> +{
> +    GtkWidget *file_entry = (GtkWidget *)user_data;
> +    GtkFileChooserNative *native;
> +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
> +    gint res;
> +
> +    native = gtk_file_chooser_native_new("Choose File for USB CD",
> +        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
> +        action,
> +        "_Open",
> +        "_Cancel");
> +
> +    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
> +    if (res == GTK_RESPONSE_ACCEPT) {
> +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native));
> +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> +        g_free(filename);
> +    }
> +    else {
> +        gtk_widget_grab_focus(button);
> +    }
> +
> +    g_object_unref(native);
> +}
> +#else
> +// to be removed
> +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> +{
> +    GtkWidget *file_entry = (GtkWidget *)user_data;
> +    GtkWidget *dialog;
> +    gint res;
> +
> +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
> +                                          GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
> +                                          GTK_FILE_CHOOSER_ACTION_OPEN,
> +                                          "_Cancel",
> +                                          GTK_RESPONSE_CANCEL,
> +                                          "_Ok",
> +                                          GTK_RESPONSE_ACCEPT,
> +                                          NULL);
> +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
> +
> +    res = gtk_dialog_run(GTK_DIALOG(dialog));
> +    if (res == GTK_RESPONSE_ACCEPT) {
> +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
> +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> +        g_free(filename);
> +    }
> +    gtk_widget_destroy(dialog);
> +}
> +#endif
> +
> +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget *widget,
> +                                                       gboolean state, gpointer user_data)
> +{
> +    lun_properties_dialog *lun_dialog = user_data;
> +
> +    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
> +    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
> +
> +    return FALSE; /* call default signal handler */
> +}
> +
> +static void lun_properties_dialog_toggle_advanced(GtkWidget *widget, gpointer user_data)
> +{
> +    lun_properties_dialog *lun_dialog = user_data;
> +
> +    if (lun_dialog->advanced_shown) {
> +        gtk_widget_hide(lun_dialog->advanced_grid);
> +        lun_dialog->advanced_shown = FALSE;
> +    } else {
> +        gtk_widget_show_all(lun_dialog->advanced_grid);
> +        lun_dialog->advanced_shown = TRUE;
> +    }
> +}
> +
> +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
> +                                         GtkWidget *parent_window,
> +                                         SpiceUsbDeviceLunInfo *lun_info,
> +                                         lun_properties_dialog *lun_dialog)
> +{
> +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkWidget *dialog, *content_area;
> +    GtkWidget *grid, *advanced_grid;
> +    GtkWidget *file_entry, *choose_button;
> +    GtkWidget *advanced_button, *advanced_icon;
> +    GtkWidget *vendor_entry, *product_entry, *revision_entry;
> +    GtkWidget *loaded_switch, *loaded_label;
> +    GtkWidget *locked_switch, *locked_label;
> +    gint nrow = 0;
> +
> +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings",
> +                    GTK_WINDOW(parent_window),
> +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, /* flags */
> +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
> +                    "Cancel", GTK_RESPONSE_REJECT,
> +                    NULL);
> +
> +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
> +    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
> +    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
> +
> +    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
> +
> +    /* main grid - always visible */
> +    grid = gtk_grid_new();
> +    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
> +    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
> +    gtk_container_add(GTK_CONTAINER(content_area), grid);
> +
> +    /* File path label */
> +    gtk_grid_attach(GTK_GRID(grid),
> +            gtk_label_new("Select file or device"),
> +            0, nrow++, // left top
> +            7, 1); // width height
> +
> +    /* file/device path entry */
> +    file_entry = gtk_entry_new();
> +    gtk_widget_set_hexpand(file_entry, TRUE);
> +    if (!lun_info) {
> +        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry), "file-path");
> +    } else {
> +        gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path);
> +        if (lun_info->loaded) {
> +            gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE);
> +            gtk_widget_set_can_focus(file_entry, FALSE);
> +        }
> +    }
> +    gtk_grid_attach(GTK_GRID(grid),
> +            file_entry,
> +            0, nrow, // left top
> +            6, 1); // width height
> +
> +    /* choose button */
> +    choose_button = gtk_button_new_with_mnemonic("_Choose File");
> +    gtk_widget_set_hexpand(choose_button, FALSE);
> +    g_signal_connect(GTK_BUTTON(choose_button),
> +                     "clicked", G_CALLBACK(usb_cd_choose_file), file_entry);
> +    if (lun_info && lun_info->loaded) {
> +        gtk_widget_set_sensitive(choose_button, FALSE);
> +        gtk_widget_set_can_focus(choose_button, FALSE);
> +    }
> +
> +    gtk_grid_attach(GTK_GRID(grid),
> +            choose_button,
> +            6, nrow++, // left top
> +            1, 1); // width height
> +
> +    /* advanced button */
> +    advanced_button = gtk_button_new_with_label("Advanced");
> +    gtk_button_set_relief(GTK_BUTTON(advanced_button), GTK_RELIEF_NONE);
> +    advanced_icon = gtk_image_new_from_icon_name("preferences-system", GTK_ICON_SIZE_BUTTON);
> +    gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon);
> +    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button), TRUE);
> +    g_signal_connect(advanced_button, "clicked", G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
> +
> +    gtk_grid_attach(GTK_GRID(grid),
> +            advanced_button,
> +            0, nrow++, // left top
> +            1, 1); // width height
> +
> +    /* advanced grid */
> +    advanced_grid = gtk_grid_new();
> +    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
> +    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
> +    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
> +
> +    /* horizontal separator */
> +    gtk_container_add(GTK_CONTAINER(content_area), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
> +
> +    /* pack advanced grid */
> +    nrow = 0;
> +
> +    /* horizontal separator */
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> +            0, nrow++, // left top
> +            7, 1); // width height
> +
> +    /* product id labels */
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            gtk_label_new("Vendor"),
> +            0, nrow, // left top
> +            2, 1); // width height
> +
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            gtk_label_new("Product"),
> +            2, nrow, // left top
> +            4, 1); // width height
> +
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            gtk_label_new("Revision"),
> +            6, nrow++, // left top
> +            1, 1); // width height
> +
> +    /* vendor entry */
> +    vendor_entry = gtk_entry_new();
> +    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
> +    if (lun_info) {
> +        gtk_widget_set_sensitive(vendor_entry, FALSE);
> +        gtk_widget_set_can_focus(vendor_entry, FALSE);
> +        gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor);
> +    } else {
> +        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry), "auto");
> +    }
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            vendor_entry,
> +            0, nrow, // left top
> +            2, 1); // width height
> +
> +    /* product entry */
> +    product_entry = gtk_entry_new();
> +    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
> +    if (lun_info) {
> +        gtk_widget_set_sensitive(product_entry, FALSE);
> +        gtk_widget_set_can_focus(product_entry, FALSE);
> +        gtk_entry_set_text(GTK_ENTRY(product_entry), lun_info->product);
> +    } else {
> +        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry), "auto");
> +    }
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            product_entry,
> +            2, nrow, // left top
> +            4, 1); // width height
> +
> +    /* revision entry */
> +    revision_entry = gtk_entry_new();
> +    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
> +    if (lun_info) {
> +        gtk_widget_set_sensitive(revision_entry, FALSE);
> +        gtk_widget_set_can_focus(revision_entry, FALSE);
> +        gtk_entry_set_text(GTK_ENTRY(revision_entry), lun_info->revision);
> +    } else {
> +        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry), "auto");
> +    }
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            revision_entry,
> +            6, nrow++, // left top
> +            1, 1); // width height
> +
> +    /* horizontal separator */
> +    if (!lun_info) {
> +        gtk_grid_attach(GTK_GRID(advanced_grid),
> +                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> +                0, nrow++, // left top
> +                7, 1); // width height
> +    }
> +
> +    /* initially loaded switch */
> +    loaded_label = gtk_label_new("Initially loaded:");
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +        loaded_label,
> +        0, nrow, // left top
> +        2, 1); // width height
> +
> +    loaded_switch = gtk_switch_new();
> +    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
> +    if (lun_info) {
> +        gtk_widget_set_child_visible(loaded_switch, FALSE);
> +        gtk_widget_set_child_visible(loaded_label, FALSE);
> +    } else {
> +        g_signal_connect(loaded_switch, "state-set",
> +                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb), lun_dialog);
> +    }
> +    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            loaded_switch,
> +            2, nrow++, // left top
> +            1, 1); // width height
> +
> +    /* initially locked switch */
> +    locked_label = gtk_label_new("Initially locked:");
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +        locked_label,
> +        0, nrow, // left top
> +        2, 1); // width height
> +
> +    locked_switch = gtk_switch_new();
> +    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
> +    gtk_widget_set_hexpand(locked_switch, FALSE);
> +    if (lun_info) {
> +        gtk_widget_set_child_visible(locked_switch, FALSE);
> +        gtk_widget_set_child_visible(locked_label, FALSE);
> +    }
> +    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
> +    gtk_grid_attach(GTK_GRID(advanced_grid),
> +            locked_switch,
> +            2, nrow++, // left top
> +            1, 1); // width height
> +
> +    lun_dialog->dialog = dialog;
> +    lun_dialog->advanced_grid = advanced_grid;
> +    lun_dialog->advanced_shown = FALSE;
> +    lun_dialog->file_entry = file_entry;
> +    lun_dialog->vendor_entry = vendor_entry;
> +    lun_dialog->product_entry = product_entry;
> +    lun_dialog->revision_entry = revision_entry;
> +    lun_dialog->loaded_switch = loaded_switch;
> +    lun_dialog->locked_switch = locked_switch;
> +
> +    gtk_widget_show_all(dialog);
> +    gtk_widget_hide(advanced_grid);
> +}
> +
> +static void lun_properties_dialog_get_info(lun_properties_dialog *lun_dialog,
> +                                            SpiceUsbDeviceLunInfo *lun_info)
> +{
> +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(lun_dialog->file_entry));
> +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(lun_dialog->vendor_entry));
> +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(lun_dialog->product_entry));
> +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(lun_dialog->revision_entry));
> +    lun_info->loaded = gtk_switch_get_active(GTK_SWITCH(lun_dialog->loaded_switch));
> +    lun_info->locked = gtk_switch_get_active(GTK_SWITCH(lun_dialog->locked_switch));
> +}
> +
> +/* Popup menu */
> +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +
> +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> +            SpiceUsbDevice *usb_device;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
> +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
> +        }
> +        else {
> +            UsbWidgetLunItem *lun_item;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
> +            spice_usb_device_manager_device_lun_load(
> +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded);
> +        }
> +    }
> +    else {
> +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> +    }
> +}
> +
> +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +
> +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> +            SpiceUsbDevice *usb_device;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
> +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
> +        }
> +        else {
> +            UsbWidgetLunItem *lun_item;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
> +            spice_usb_device_manager_device_lun_lock(
> +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked);
> +        }
> +    }
> +    else {
> +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> +    }
> +}
> +
> +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +
> +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> +            SpiceUsbDevice *usb_device;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
> +            SPICE_DEBUG("Remove USB device");
> +        } else {
> +            UsbWidgetLunItem *lun_item;
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
> +            gtk_tree_selection_unselect_all(select);
> +            spice_usb_device_manager_device_lun_remove(lun_item->manager, lun_item->device, lun_item->lun);
> +        }
> +    } else {
> +        SPICE_DEBUG("Remove - failed to get selection");
> +    }
> +}
> +
> +static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +
> +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> +            SPICE_DEBUG("No settings for USB device yet");
> +        } else {
> +            lun_properties_dialog lun_dialog;
> +            UsbWidgetLunItem *lun_item;
> +            gint resp;
> +
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
> +            gtk_tree_selection_unselect_all(select);
> +            create_lun_properties_dialog(self, NULL, &lun_item->info, &lun_dialog);
> +
> +            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> +            if (resp == GTK_RESPONSE_ACCEPT) {
> +                SpiceUsbDeviceLunInfo lun_info;
> +                SPICE_DEBUG("response is ACCEPT");
> +                lun_properties_dialog_get_info(&lun_dialog, &lun_info);
> +                spice_usb_device_manager_device_lun_change_media(
> +                    priv->manager, lun_item->device, lun_item->lun, &lun_info);
> +            } else {
> +                SPICE_DEBUG("response is REJECT");
> +            }
> +            gtk_widget_destroy(lun_dialog.dialog);
> +        }
> +    } else {
> +        SPICE_DEBUG("Remove - failed to get selection");
> +    }
> +}
> +
> +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
> +    const gchar *label_str,
> +    const gchar *icon_name,
> +    GCallback cb_func, gpointer user_data)
> +{
> +    GtkWidget *menu_item = gtk_menu_item_new();
> +    create_image_button_box(label_str, icon_name, menu_item);
> +    g_signal_connect(menu_item, "activate", cb_func, user_data);
> +
> +    gtk_widget_show_all(menu_item);
> +    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
> +
> +    return menu_item;
> +}
> +
> +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device)
> +{
> +    gboolean result;
> +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> +    GArray *lun_array;
> +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, device);
> +    result = lun_array && lun_array->len <= 1;
> +    if (lun_array) {
> +        g_array_unref(lun_array);
> +    }
> +    return result;
> +}
> +
> +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeSelection *select;
> +    GtkTreeModel *tree_model;
> +    GtkTreeIter iter;
> +    UsbWidgetLunItem *lun_item;
> +    gboolean is_loaded, is_locked;
> +    GtkTreeIter *usb_dev_iter;
> +    gboolean is_dev_connected;
> +    GtkWidget *menu;
> +
> +    if (tree_view != priv->cd_tree.tree_view) {
> +        SPICE_DEBUG("Not applicable for USB device");
> +        return;
> +    }
> +
> +    select = gtk_tree_view_get_selection(tree_view);
> +
> +    if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> +        SPICE_DEBUG("No tree view row is selected");
> +        return;
> +    }
> +    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> +        SPICE_DEBUG("No settings for USB device yet");
> +        return;
> +    }
> +
> +    gtk_tree_model_get(tree_model, &iter,
> +                       COL_ITEM_DATA, (gpointer *)&lun_item,
> +                       COL_LOADED, &is_loaded,
> +                       COL_LOCKED, &is_locked,
> +                       -1);
> +
> +    usb_dev_iter = usb_widget_tree_store_find_usb_device(priv->cd_tree.tree_store, lun_item->device);
> +    if (usb_dev_iter != NULL) {
> +        gtk_tree_model_get(tree_model, usb_dev_iter,
> +                           COL_CONNECTED, &is_dev_connected,
> +                           -1);
> +        g_free(usb_dev_iter);
> +    } else {
> +        is_dev_connected = FALSE;
> +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
> +                    lun_item->info.vendor, lun_item->info.product);
> +    }
> +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d",
> +                lun_item->info.vendor, lun_item->info.product,
> +                is_dev_connected, is_loaded, is_locked);
> +
> +    /* Set up the menu */
> +    menu = gtk_menu_new();
> +
> +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
> +                             G_CALLBACK(view_popup_menu_on_settings), user_data);
> +    if (is_loaded) {
> +        if (!is_locked) {
> +            view_popup_add_menu_item(menu, "_Lock", "system-lock-screen",
> +                                    G_CALLBACK(view_popup_menu_on_lock), user_data);
> +            view_popup_add_menu_item(menu, "_Eject", "media-eject",
> +                                     G_CALLBACK(view_popup_menu_on_eject), user_data);
> +        } else {
> +            view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen",
> +                                     G_CALLBACK(view_popup_menu_on_lock), user_data);
> +        }
> +    } else {
> +        view_popup_add_menu_item(menu, "_Load", "media-eject",
> +                                 G_CALLBACK(view_popup_menu_on_eject), user_data);
> +    }
> +
> +    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {
> +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
> +                                 G_CALLBACK(view_popup_menu_on_remove), user_data);
> +    }
> +
> +    gtk_widget_show_all(menu);
> +    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
> +}
> +
> +static void treeview_select_current_row_by_pos(GtkTreeView *tree_view, gint x, gint y)
> +{
> +    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
> +    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
> +        GtkTreePath *path;
> +        /* Get tree path for row that was clicked */
> +        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))
> +        {
> +            gtk_tree_selection_unselect_all(selection);
> +            gtk_tree_selection_select_path(selection, path);
> +            gtk_tree_path_free(path);
> +        }
> +    }
> +}
> +
> +static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data)
> +{
> +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
> +    /* single click with the right mouse button */
> +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
> +        /* select the row that was clicked, it will also provide the context */
> +        treeview_select_current_row_by_pos(tree_view, (gint)event->x, (gint)event->y);
> +        view_popup_menu(tree_view, event, user_data);
> +        return TRUE; /* we handled this */
> +    } else {
> +        return FALSE; /* we did not handle this */
> +    }
> +}
> +
> +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view, gpointer user_data)
> +{
> +    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
> +    return TRUE; /* we handled this */
> +}
> +
> +/* Add LUN dialog */
> +
> +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button, gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button);
> +    lun_properties_dialog lun_dialog;
> +    gint resp;
> +
> +    create_lun_properties_dialog(self, parent_window, NULL, &lun_dialog);
> +
> +    resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> +    if (resp == GTK_RESPONSE_ACCEPT) {
> +        SpiceUsbDeviceLunInfo lun_info;
> +        SPICE_DEBUG("response is ACCEPT");
> +        lun_properties_dialog_get_info(&lun_dialog, &lun_info);
> +        spice_usb_device_manager_add_cd_lun(priv->manager, &lun_info);
> +    } else {
> +        SPICE_DEBUG("response is REJECT");
> +    }
> +    gtk_widget_destroy(lun_dialog.dialog);
> +}
> +
> +static void spice_usb_device_widget_get_property(GObject     *gobject,
> +                                                 guint        prop_id,
> +                                                 GValue      *value,
> +                                                 GParamSpec  *pspec)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    switch (prop_id) {
> +    case PROP_SESSION:
> +        g_value_set_object(value, priv->session);
> +        break;
> +    case PROP_DEVICE_FORMAT_STRING:
> +        g_value_set_string(value, priv->device_format_string);
> +        break;
> +    default:
> +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> +        break;
> +    }
> +}
> +
> +static void spice_usb_device_widget_set_property(GObject       *gobject,
> +                                                 guint          prop_id,
> +                                                 const GValue  *value,
> +                                                 GParamSpec    *pspec)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    switch (prop_id) {
> +    case PROP_SESSION:
> +        priv->session = g_value_dup_object(value);
> +        break;
> +    case PROP_DEVICE_FORMAT_STRING:
> +        priv->device_format_string = g_value_dup_string(value);
> +        break;
> +    default:
> +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> +        break;
> +    }
> +}
> +
> +static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    if (priv->info_bar) {
> +        gtk_widget_destroy(priv->info_bar);
> +        priv->info_bar = NULL;
> +    }
> +}
> +
> +static void
> +spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
> +                                      const gchar          *message,
> +                                      GtkMessageType        message_type,
> +                                      GdkPixbuf            *icon_pixbuf)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;
> +
> +    spice_usb_device_widget_hide_info_bar(self);
> +
> +    info_bar = gtk_info_bar_new();
> +    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type);
> +
> +    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
> +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> +    gtk_container_add(GTK_CONTAINER(content_area), hbox);
> +
> +    icon = gtk_image_new_from_pixbuf(icon_pixbuf);
> +    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10);
> +
> +    label = gtk_label_new(message);
> +    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
> +
> +    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
> +    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 0, 0);
> +    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
> +
> +    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
> +    gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put after the lable */
> +    gtk_widget_show_all(priv->info_bar);
> +}
> +
> +static void spice_usb_device_widget_create_tree_view(SpiceUsbDeviceWidget *self, gboolean is_cd)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    GtkTreeStore *tree_store = usb_widget_create_tree_store();
> +    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
> +
> +    if (is_cd) {
> +        priv->cd_tree.tree_view = tree_view;
> +        priv->cd_tree.tree_store = tree_store;
> +    } else {
> +        priv->usb_tree.tree_view = tree_view;
> +        priv->usb_tree.tree_store = tree_store;
> +    }
> +
> +    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
> +    g_object_unref(tree_store); /* destroy tree_store automatically with tree_view */
> +
> +    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM, COL_CAN_REDIRECT,
> +        is_cd ? tree_item_toggled_cb_redirect_cd : tree_item_toggled_cb_redirect_usb, is_cd);
> +
> +    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);
> +
> +    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd);
> +    if (is_cd) {
> +        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);
> +    }
> +
> +    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);
> +    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);
> +
> +    if (is_cd) {
> +        view_add_text_column(self, COL_FILE, TRUE, is_cd);
> +        view_add_read_only_toggle_column(self, COL_LOADED, COL_LUN_ITEM, is_cd);
> +        view_add_read_only_toggle_column(self, COL_LOCKED, COL_LUN_ITEM, is_cd);
> +        // uncomment to show also 'idle' column for CD
> +        //view_add_read_only_toggle_column(self, COL_IDLE, COL_LUN_ITEM, is_cd);
> +    }
> +
> +    gtk_tree_selection_set_mode(
> +            gtk_tree_view_get_selection(tree_view),
> +            GTK_SELECTION_NONE);
> +
> +    if (is_cd) {
> +        set_selection_handler(tree_view);
> +    }
> +}
> +
> +static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget *self)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    g_signal_connect(priv->manager, "device-added",
> +                     G_CALLBACK(device_added_cb), self);
> +    g_signal_connect(priv->manager, "device-removed",
> +                     G_CALLBACK(device_removed_cb), self);
> +    g_signal_connect(priv->manager, "device-changed",
> +                     G_CALLBACK(device_changed_cb), self);
> +    // TODO: connect failed
> +    g_signal_connect(priv->manager, "device-error",
> +                     G_CALLBACK(device_error_cb), self);
> +
> +    g_signal_connect(priv->cd_tree.tree_view, "button-press-event",
> +                     G_CALLBACK(treeview_on_right_button_pressed_cb), self);
> +    g_signal_connect(priv->cd_tree.tree_view, "popup-menu",
> +                     G_CALLBACK(treeview_on_popup_key_pressed_cb), self);
> +}
> +
> +static void create_tree_window(SpiceUsbDeviceWidget *self, GtkTreeView *tree_view)
> +{
> +    GtkWidget *sw;
> +    /* scrolled window */
> +    sw = gtk_scrolled_window_new(NULL, NULL);
> +    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
> +        GTK_SHADOW_ETCHED_IN);
> +    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
> +        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
> +    gtk_widget_set_hexpand(sw, TRUE);
> +    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
> +    gtk_widget_set_vexpand(sw, TRUE);
> +    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
> +    gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view));
> +    gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0);
> +}
> +
> +static void spice_usb_device_widget_constructed(GObject *gobject)
> +{
> +    SpiceUsbDeviceWidget *self;
> +    GtkRequisition min_size, natural_size;
> +    SpiceUsbDeviceWidgetPrivate *priv;
> +    GtkWidget *hbox, *dev_label;
> +    GtkWidget *add_cd_button, *add_cd_icon;
> +    GPtrArray *devices = NULL;
> +    GError *err = NULL;
> +    gchar *str;
> +    guint i;
> +    gboolean cd_sharing_enabled = TRUE;
> +    #ifndef USE_CD_SHARING
> +    cd_sharing_enabled = FALSE;
> +    #endif
> +
> +    self = SPICE_USB_DEVICE_WIDGET(gobject);
> +    priv = self->priv;
> +    if (!priv->session)
> +        g_error("SpiceUsbDeviceWidget constructed without a session");
> +
> +    min_size.width = 600;
> +    min_size.height = 300;
> +    natural_size.width = 1200;
> +    natural_size.height = 600;
> +    gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size, &natural_size);
> +
> +    priv->label = gtk_label_new(NULL);
> +    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));
> +    gtk_label_set_markup(GTK_LABEL(priv->label), str);
> +    g_free(str);
> +    gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0);
> +
> +    priv->icon_cd = get_named_icon("media-optical", GTK_ICON_SIZE_LARGE_TOOLBAR);
> +    priv->icon_connected = get_named_icon("network-transmit-receive", GTK_ICON_SIZE_LARGE_TOOLBAR);
> +    priv->icon_disconn = get_named_icon("network-offline", GTK_ICON_SIZE_LARGE_TOOLBAR);
> +    priv->icon_warning = get_named_icon("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR);
> +    priv->icon_info = get_named_icon("dialog-information", GTK_ICON_SIZE_LARGE_TOOLBAR);
> +
> +    priv->manager = spice_usb_device_manager_get(priv->session, &err);
> +    if (err) {
> +        spice_usb_device_widget_show_info_bar(self, err->message,
> +                                              GTK_MESSAGE_WARNING, priv->icon_warning);
> +        g_clear_error(&err);
> +        return;
> +    }
> +
> +    if (cd_sharing_enabled) {
> +        spice_usb_device_widget_create_tree_view(self, TRUE);
> +    } else {
> +        priv->cd_tree.tree_store = NULL;
> +        priv->cd_tree.tree_view = NULL;
> +    }
> +    spice_usb_device_widget_create_tree_view(self, FALSE);
> +
> +    spice_usb_device_widget_signals_connect(self);
> +
> +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> +    gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> +
> +    /* "Available devices" label - in hbox */
> +    dev_label = gtk_label_new(_("Local USB devices"));
> +    gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
> +    create_tree_window(self, priv->usb_tree.tree_view);
> +
> +    if (cd_sharing_enabled) {
> +        hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> +        gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> +        dev_label = gtk_label_new(_("Shared CD devices"));
> +        gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
> +        /* "Add CD" button - in hbox */
> +        add_cd_button = gtk_button_new_with_label(_("Add CD"));
> +        gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button), TRUE);
> +        add_cd_icon = gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_BUTTON);
> +        gtk_button_set_image(GTK_BUTTON(add_cd_button), add_cd_icon);
> +
> +        gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END);
> +        g_signal_connect(add_cd_button, "clicked", G_CALLBACK(add_cd_lun_button_clicked_cb), self);
> +        gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE, FALSE, 0);
> +        create_tree_window(self, priv->cd_tree.tree_view);
> +    }
> +    devices = spice_usb_device_manager_get_devices(priv->manager);
> +    if (!devices)
> +        goto end;
> +
> +    for (i = 0; i < devices->len; i++) {
> +        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);
> +        usb_widget_add_device(self, usb_device, NULL);
> +    }
> +    g_ptr_array_unref(devices);
> +
> +    select_widget_size(GTK_WIDGET(self));
> +
> +end:
> +    spice_usb_device_widget_update_status(self);
> +}
> +
> +static void spice_usb_device_widget_finalize(GObject *object)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    if (priv->manager) {
> +        g_signal_handlers_disconnect_by_func(priv->manager,
> +                                             device_added_cb, self);
> +        g_signal_handlers_disconnect_by_func(priv->manager,
> +                                             device_removed_cb, self);
> +        g_signal_handlers_disconnect_by_func(priv->manager,
> +                                             device_changed_cb, self);
> +        g_signal_handlers_disconnect_by_func(priv->manager,
> +                                             device_error_cb, self);
> +    }
> +    g_object_unref(priv->session);
> +    g_free(priv->device_format_string);
> +
> +    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
> +        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object);
> +}
> +
> +static void spice_usb_device_widget_class_init(
> +    SpiceUsbDeviceWidgetClass *klass)
> +{
> +    GObjectClass *gobject_class = (GObjectClass *)klass;
> +    GParamSpec *pspec;
> +
> +    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
> +
> +    gobject_class->constructed  = spice_usb_device_widget_constructed;
> +    gobject_class->finalize     = spice_usb_device_widget_finalize;
> +    gobject_class->get_property = spice_usb_device_widget_get_property;
> +    gobject_class->set_property = spice_usb_device_widget_set_property;
> +
> +    /**
> +     * SpiceUsbDeviceWidget:session:
> +     *
> +     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
> +     *
> +     **/
> +    pspec = g_param_spec_object("session",
> +                                "Session",
> +                                "SpiceSession",
> +                                SPICE_TYPE_SESSION,
> +                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
> +                                G_PARAM_STATIC_STRINGS);
> +    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
> +
> +    /**
> +     * SpiceUsbDeviceWidget:device-format-string:
> +     *
> +     * Format string to pass to spice_usb_device_get_description() for getting
> +     * the device USB descriptions.
> +     */
> +    pspec = g_param_spec_string("device-format-string",
> +                                "Device format string",
> +                                "Format string for device description",
> +                                NULL,
> +                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
> +                                G_PARAM_STATIC_STRINGS);
> +    g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING,
> +                                    pspec);
> +
> +    /**
> +     * SpiceUsbDeviceWidget::connect-failed:
> +     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
> +     * @device: #SpiceUsbDevice boxed object corresponding to the added device
> +     * @error:  #GError describing the reason why the connect failed
> +     *
> +     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever
> +     * the user has requested for a device to be redirected and this has
> +     * failed.
> +     **/
> +    signals[CONNECT_FAILED] =
> +        g_signal_new("connect-failed",
> +                    G_OBJECT_CLASS_TYPE(gobject_class),
> +                    G_SIGNAL_RUN_FIRST,
> +                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed),
> +                    NULL, NULL,
> +                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
> +                    G_TYPE_NONE,
> +                    2,
> +                    SPICE_TYPE_USB_DEVICE,
> +                    G_TYPE_ERROR);
> +}
> +
> +static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
> +{
> +    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* public api                                                         */
> +
> +/**
> + * spice_usb_device_widget_new:
> + * @session: #SpiceSession for which to widget will control USB redirection
> + * @device_format_string: (allow-none): String passed to
> + * spice_usb_device_get_description()
> + *
> + * Creates a new widget to control USB redirection.
> + *
> + * Returns: a new #SpiceUsbDeviceWidget instance
> + */
> +GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
> +                                       const gchar     *device_format_string)
> +{
> +    static gboolean init_columns = TRUE;
> +    spice_util_get_debug();
> +    if (init_columns) {
> +        initialize_columns();
> +        init_columns = FALSE;
> +    }
> +    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
> +                        "orientation", GTK_ORIENTATION_VERTICAL,
> +                        "session", session,
> +                        "device-format-string", device_format_string,
> +                        "spacing", 6,
> +                        NULL);
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* callbacks                                                          */
> +
> +static gboolean usb_widget_tree_store_check_redirect_foreach_cb(GtkTreeModel *tree_model,
> +                                                                GtkTreePath *path,
> +                                                                GtkTreeIter *iter,
> +                                                                SpiceUsbDeviceWidget *self,
> +                                                                GtkTreeStore *tree_store)
> +{
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +
> +    if (!tree_item_is_lun(tree_store, iter)) {
> +        SpiceUsbDevice *usb_device;
> +        gboolean can_redirect;
> +
> +        gtk_tree_model_get(tree_model, iter,
> +                           COL_ITEM_DATA, (gpointer *)&usb_device,
> +                           -1);
> +
> +        if (spice_usb_device_manager_is_redirecting(priv->manager)) {
> +            can_redirect = FALSE;
> +        } else {
> +            GError *err = NULL;
> +
> +            can_redirect = spice_usb_device_manager_can_redirect_device(priv->manager,
> +                                                                        usb_device, &err);
> +
> +            /* If we cannot redirect this device, append the error message to
> +               err_msg, but only if it is *not* already there! */
> +            if (!can_redirect) {
> +                if (priv->err_msg) {
> +                    if (!strstr(priv->err_msg, err->message)) {
> +                        gchar *old_err_msg = priv->err_msg;
> +                        priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg,
> +                                                        err->message);
> +                        g_free(old_err_msg);
> +                    }
> +                } else {
> +                    priv->err_msg = g_strdup(err->message);
> +                }
> +            }
> +            g_clear_error(&err);
> +        }
> +        gtk_tree_store_set(tree_store, iter,
> +                           COL_CAN_REDIRECT, can_redirect,
> +                           -1);
> +    }
> +    return FALSE; /* continue iterating */
> +}
> +
> +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_usb(GtkTreeModel *tree_model,
> +                                                                 GtkTreePath *path, GtkTreeIter *iter,
> +                                                                 gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    return usb_widget_tree_store_check_redirect_foreach_cb(
> +        tree_model, path, iter, self, priv->usb_tree.tree_store);
> +}
> +
> +#ifdef USE_CD_SHARING
> +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_cd(GtkTreeModel *tree_model,
> +                                                                 GtkTreePath *path, GtkTreeIter *iter,
> +                                                                 gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    return usb_widget_tree_store_check_redirect_foreach_cb(
> +        tree_model, path, iter, self, priv->cd_tree.tree_store);
> +}
> +#endif
> +
> +static gboolean spice_usb_device_widget_update_status(gpointer user_data)
> +{
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> +    gchar *str, *markup_str;
> +    const gchar *free_channels_str;
> +    int free_channels;
> +
> +    g_object_get(priv->manager, "free-channels", &free_channels, NULL);
> +    free_channels_str = ngettext(_("Select USB devices to redirect (%d free channel)"),
> +                                 _("Select USB devices to redirect (%d free channels)"),
> +                                 free_channels);
> +    str = g_strdup_printf(free_channels_str, free_channels);
> +    markup_str = g_strdup_printf("<b>%s</b>", str);
> +    gtk_label_set_markup(GTK_LABEL (priv->label), markup_str);
> +    g_free(markup_str);
> +    g_free(str);
> +
> +    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->usb_tree.tree_store),
> +                           usb_widget_tree_store_check_redirect_foreach_cb_usb, self);
> +    gtk_widget_show_all(GTK_WIDGET(priv->usb_tree.tree_view));
> +
> +#ifdef USE_CD_SHARING
> +    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->cd_tree.tree_store),
> +                           usb_widget_tree_store_check_redirect_foreach_cb_cd, self);
> +    gtk_widget_show_all(GTK_WIDGET(priv->cd_tree.tree_view));
> +#endif
> +
> +    /* Show messages in the info, if necessary */
> +    if (priv->err_msg) {
> +        spice_usb_device_widget_show_info_bar(self, priv->err_msg,
> +                                              GTK_MESSAGE_INFO, priv->icon_warning);
> +        g_free(priv->err_msg);
> +        priv->err_msg = NULL;
> +    } else if ( spice_usb_device_manager_is_redirecting(priv->manager)) {
> +        spice_usb_device_widget_show_info_bar(self, _("Redirecting USB Device..."),
> +                                              GTK_MESSAGE_INFO, priv->icon_info);
> +    } else {
> +        spice_usb_device_widget_hide_info_bar(self);
> +    }
> +
> +    if (priv->device_count == 0)
> +        spice_usb_device_widget_show_info_bar(self, _("No USB devices detected"),
> +                                              GTK_MESSAGE_INFO, priv->icon_info);
> +
> +    return FALSE;
> +}
> +
> +#endif
> -- 
> 2.9.4
> 
> _______________________________________________
> Spice-devel mailing list
> Spice-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel
On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <victortoso@redhat.com> wrote:

> On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
> > New USB widget supports all the functionality related to
> > redirection of local USB device (as previous usb widget did).
> > Additionally it allows creation, management and redirection of
> > emulated USB CD devices for CD sharing.
>
> Wouldn't be possible to extend the current widget instead of
> creating a new one?
>

Of course it was the first idea before creating new one.
After several discussions we understood that there is almost nothing
common between what we'd like to have for CD and what we have in existing
widget.
This also was decided on special meeting of Alexander with spice team
(remove
some controls and split it to two lists, locals and CD). Earlier the
screenshot of
the latest widget (current code) was sent to internal list for review and
no objections
were received.


>
> > Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
> > Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
> > ---
> >  src/usb-device-redir-widget.c | 2065 ++++++++++++++++++++++++++++++
> +++++++++++
> >  1 file changed, 2065 insertions(+)
> >  create mode 100644 src/usb-device-redir-widget.c
> >
> > diff --git a/src/usb-device-redir-widget.c
> b/src/usb-device-redir-widget.c
> > new file mode 100644
> > index 0000000..59a6043
> > --- /dev/null
> > +++ b/src/usb-device-redir-widget.c
> > @@ -0,0 +1,2065 @@
> > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> > +/*
> > +   Copyright (C) 2012 Red Hat, Inc.
> > +
> > +   Red Hat Authors:
> > +   Alexander Nezhinsky<anezhins@redhat.com>
> > +
> > +   This library is free software; you can redistribute it and/or
> > +   modify it under the terms of the GNU Lesser General Public
> > +   License as published by the Free Software Foundation; either
> > +   version 2.1 of the License, or (at your option) any later version.
> > +
> > +   This library 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
> > +   Lesser General Public License for more details.
> > +
> > +   You should have received a copy of the GNU Lesser General Public
> > +   License along with this library; if not, see <
> http://www.gnu.org/licenses/>.
> > +*/
> > +
> > +#include "config.h"
> > +#ifndef USB_WIDGET_TEST
> > +    #include <glib/gi18n-lib.h>
> > +    #include "spice-client.h"
> > +    #include "spice-marshal.h"
> > +#else
> > +    #include "spice-client.h"
> > +#endif
> > +#include "usb-device-widget.h"
> > +
> > +/*
> > +    Debugging note:
> > +    Logging from this module is not affected by --spice-debug
> > +    command line parameter
> > +    Use SPICE_DEBUG=1 environment varible to enable logs
> > +*/
> > +
> > +#ifdef USE_NEW_USB_WIDGET
> > +
> > +/**
> > + * SECTION:usb-device-widget
> > + * @short_description: USB device selection widget
> > + * @title: Spice USB device selection widget
> > + * @section_id:
> > + * @see_also:
> > + * @stability: Under development
> > + * @include: spice-client-gtk.h
> > + *
> > + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
> > + * add an UI to select USB devices to redirect (or unredirect).
> > + */
> > +
> > +struct _SpiceUsbDeviceWidget
> > +{
> > +    GtkBox parent;
> > +
> > +    SpiceUsbDeviceWidgetPrivate *priv;
> > +};
> > +
> > +struct _SpiceUsbDeviceWidgetClass
> > +{
> > +    GtkBoxClass parent_class;
> > +
> > +    /* signals */
> > +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
> > +                            SpiceUsbDevice *device, GError *error);
> > +};
> > +
> > +/* ------------------------------------------------------------------
> */
> > +/* Prototypes for callbacks  */
> > +static void device_added_cb(SpiceUsbDeviceManager *manager,
> > +    SpiceUsbDevice *device, gpointer user_data);
> > +static void device_removed_cb(SpiceUsbDeviceManager *manager,
> > +    SpiceUsbDevice *device, gpointer user_data);
> > +static void device_changed_cb(SpiceUsbDeviceManager *manager,
> > +    SpiceUsbDevice *device, gpointer user_data);
> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);
> > +static gboolean spice_usb_device_widget_update_status(gpointer
> user_data);
> > +
> > +/* ------------------------------------------------------------------
> */
> > +/* gobject glue                                                       */
> > +
> > +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
> > +    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
> > +                                 SpiceUsbDeviceWidgetPrivate))
> > +
> > +enum {
> > +    PROP_0,
> > +    PROP_SESSION,
> > +    PROP_DEVICE_FORMAT_STRING,
> > +};
> > +
> > +enum {
> > +    CONNECT_FAILED,
> > +    LAST_SIGNAL,
> > +};
> > +
> > +typedef struct {
> > +    GtkTreeView *tree_view;
> > +    GtkTreeStore *tree_store;
> > +} SpiceUsbDeviceWidgetTree;
> > +
> > +struct _SpiceUsbDeviceWidgetPrivate {
> > +    SpiceSession *session;
> > +    gchar *device_format_string;
> > +    SpiceUsbDeviceManager *manager;
> > +    GtkWidget *info_bar;
> > +    GtkWidget *label;
> > +    SpiceUsbDeviceWidgetTree cd_tree;
> > +    SpiceUsbDeviceWidgetTree usb_tree;
> > +    GdkPixbuf *icon_cd;
> > +    GdkPixbuf *icon_connected;
> > +    GdkPixbuf *icon_disconn;
> > +    GdkPixbuf *icon_warning;
> > +    GdkPixbuf *icon_info;
> > +    gchar *err_msg;
> > +    gsize device_count;
> > +};
> > +
> > +static guint signals[LAST_SIGNAL] = { 0, };
> > +
> > +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget,
> GTK_TYPE_BOX);
> > +
> > +/* TREE */
> > +
> > +enum column_id
> > +{
> > +    COL_REDIRECT = 0,
> > +    COL_ADDRESS,
> > +    COL_CONNECT_ICON,
> > +    COL_CD_ICON,
> > +    COL_VENDOR,
> > +    COL_PRODUCT,
> > +    COL_FILE,
> > +    COL_LOADED,
> > +    COL_LOCKED,
> > +    COL_IDLE,
> > +    /* internal columns */
> > +    COL_REVISION,
> > +    COL_CD_DEV,
> > +    COL_LUN_ITEM,
> > +    COL_DEV_ITEM,
> > +    COL_ITEM_DATA,
> > +    COL_CONNECTED,
> > +    COL_CAN_REDIRECT,
> > +    COL_ROW_COLOR,
> > +    COL_ROW_COLOR_SET,
> > +    NUM_COLS,
> > +
> > +    INVALID_COL
> > +};
> > +
> > +// there is a possibility to use different names
> > +// for columns in USB device list and CD device list, if needed
> > +// currently they are identical
> > +static const char *column_names_cd[NUM_COLS];
> > +static const char *column_names_usb[NUM_COLS];
> > +
> > +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
> > +
> > +static void initialize_columns(void)
> > +{
> > +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
> > +    SET_COLUMN(COL_ADDRESS, _("Address"));
> > +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
> > +    SET_COLUMN(COL_CD_ICON, _("CD"));
> > +    SET_COLUMN(COL_VENDOR, _("Vendor"));
> > +    SET_COLUMN(COL_PRODUCT, _("Product"));
> > +    SET_COLUMN(COL_FILE, _("File/Device Path"));
> > +    SET_COLUMN(COL_LOADED, _("Loaded"));
> > +    SET_COLUMN(COL_LOCKED, _("Locked"));
> > +    SET_COLUMN(COL_IDLE, _("Idle"));
> > +    SET_COLUMN(COL_REVISION, "?Revision");
> > +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
> > +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
> > +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
> > +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
> > +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
> > +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
> > +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
> > +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
> > +};
> > +
> > +static const char *column_name(enum column_id id, gboolean is_cd)
> > +{
> > +    const char **col_name = is_cd ? column_names_cd : column_names_usb;
> > +    return col_name[id];
> > +}
> > +
> > +typedef struct _UsbWidgetLunItem {
> > +    SpiceUsbDeviceManager *manager;
> > +    SpiceUsbDevice *device;
> > +    guint lun;
> > +    SpiceUsbDeviceLunInfo info;
> > +} UsbWidgetLunItem;
> > +
> > +typedef struct _TreeFindUsbDev {
> > +    SpiceUsbDevice *usb_dev;
> > +    GtkTreeIter dev_iter;
> > +} TreeFindUsbDev;
> > +
> > +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *,
> gpointer);
> > +
> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> > +                                          SpiceUsbDevice *usb_device,
> > +                                          GtkTreeIter *old_dev_iter);
> > +
> > +static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
> > +                                     SpiceUsbDevice *device,
> > +                                     const gchar *format)
> > +{
> > +    SpiceUsbDeviceDescription desc;
> > +    gchar *descriptor;
> > +    gchar *res;
> > +    spice_usb_device_get_info(manager, device, &desc);
> > +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id,
> desc.product_id);
> > +    if (!format) {
> > +        format = _("%s %s %s at %d-%d");
> > +    }
> > +    res = g_strdup_printf(format, desc.vendor, desc.product,
> descriptor, desc.bus, desc.address);
> > +    g_free(desc.vendor);
> > +    g_free(desc.product);
> > +    g_free(descriptor);
> > +    return res;
> > +}
> > +
> > +static GtkTreeStore* usb_widget_create_tree_store(void)
> > +{
> > +    GtkTreeStore *tree_store;
> > +
> > +    tree_store = gtk_tree_store_new(NUM_COLS,
> > +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
> > +                        G_TYPE_STRING, /* COL_ADDRESS */
> > +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
> > +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
> > +                        G_TYPE_STRING, /* COL_VENDOR */
> > +                        G_TYPE_STRING, /* COL_PRODUCT */
> > +                        G_TYPE_STRING, /* COL_FILE */
> > +                        G_TYPE_BOOLEAN, /* COL_LOADED */
> > +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
> > +                        G_TYPE_BOOLEAN, /* COL_IDLE */
> > +                        /* internal columns */
> > +                        G_TYPE_STRING, /* COL_REVISION */
> > +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
> > +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
> > +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
> > +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
> > +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
> > +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
> > +                        G_TYPE_STRING, /* COL_ROW_COLOR */
> > +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
> > +    SPICE_DEBUG("tree store created");
> > +
> > +    return tree_store;
> > +}
> > +
> > +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
> > +{
> > +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(),
> name, size, 0);
> > +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
> > +    g_object_unref (info);
> > +    return pixbuf;
> > +}
> > +
> > +static void select_widget_size(GtkWidget *wg)
> > +{
> > +    GdkDisplay *d = gtk_widget_get_display(wg);
> > +    int i, w = 2000, h = 1024;
> > +    int n = gdk_display_get_n_monitors(d);
> > +    for (i = 0; i < n; ++i)
> > +    {
> > +        GdkMonitor *m = gdk_display_get_monitor(d, i);
> > +        if (m) {
> > +            GdkRectangle area;
> > +            gdk_monitor_get_workarea(m, &area);
> > +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
> > +                i, area.width, area.height, area.x, area.y );
> > +            w = MIN(w, area.width);
> > +            h = MIN(h, area.height);
> > +        }
> > +    }
> > +
> > +    w = (w * 3) / 4;
> > +    h = h / 2;
> > +
> > +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
> > +    gtk_widget_set_size_request(wg, w, h);
> > +}
> > +
> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> > +                                          SpiceUsbDevice *usb_device,
> > +                                          GtkTreeIter *old_dev_iter)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> > +    SpiceUsbDeviceWidgetTree *tree;
> > +    GtkTreeView *tree_view;
> > +    GtkTreeStore *tree_store;
> > +    GtkTreeIter new_dev_iter;
> > +    SpiceUsbDeviceDescription dev_info;
> > +    GtkTreePath *new_dev_path;
> > +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
> > +    gchar *addr_str;
> > +    GArray *lun_array;
> > +    guint lun_index;
> > +    GError *error = NULL;
> > +
> > +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr,
> usb_device);
> > +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
> > +    tree_view = tree->tree_view;
> > +    tree_store = tree->tree_store;
> > +
> > +    if (old_dev_iter == NULL) {
> > +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
> > +    } else {
> > +        gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL,
> old_dev_iter);
> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> > +    }
> > +
> > +    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
> > +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus,
> (gint)dev_info.address);
> > +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr,
> usb_device);
> > +    is_dev_redirected = is_dev_connected;
> > +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
> > +        addr_str, dev_info.vendor, dev_info.product, is_dev_connected,
> is_dev_cd);
> > +
> > +    gtk_tree_store_set(tree_store, &new_dev_iter,
> > +        COL_REDIRECT, is_dev_redirected,
> > +        COL_ADDRESS, addr_str,
> > +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected :
> priv->icon_disconn,
> > +        COL_CD_ICON, priv->icon_cd,
> > +        COL_VENDOR, dev_info.vendor,
> > +        COL_PRODUCT, dev_info.product,
> > +        COL_CD_DEV, is_dev_cd,
> > +        COL_LUN_ITEM, FALSE, /* USB device item */
> > +        COL_DEV_ITEM, TRUE, /* USB device item */
> > +        COL_ITEM_DATA, (gpointer)usb_device,
> > +        COL_CONNECTED, is_dev_connected,
> > +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr,
> usb_device, &error),
> > +        COL_ROW_COLOR, "beige",
> > +        COL_ROW_COLOR_SET, TRUE,
> > +        -1);
> > +    g_clear_error(&error);
> > +
> > +    priv->device_count++;
> > +
> > +    /* get all the luns */
> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr,
> usb_device);
> > +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
> > +        UsbWidgetLunItem *lun_item;
> > +        GtkTreeIter lun_iter;
> > +        gchar lun_str[8];
> > +
> > +        lun_item = g_malloc(sizeof(*lun_item));
> > +        lun_item->manager = usb_dev_mgr;
> > +        lun_item->device = usb_device;
> > +        lun_item->lun = g_array_index(lun_array, guint, lun_index);
> > +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr,
> usb_device, lun_item->lun, &lun_item->info);
> > +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",
> > +                lun_index, lun_item->info.vendor,
> lun_item->info.product,
> > +                lun_item->info.revision, lun_item->info.file_path,
> lun_item);
> > +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
> > +
> > +        /* Append LUN as a child of USB device */
> > +        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
> > +        gtk_tree_store_set(tree_store, &lun_iter,
> > +                COL_ADDRESS, lun_str,
> > +                COL_VENDOR, lun_item->info.vendor,
> > +                COL_PRODUCT, lun_item->info.product,
> > +                COL_REVISION, lun_item->info.revision,
> > +                COL_FILE, lun_item->info.file_path,
> > +                COL_LOADED, lun_item->info.loaded,
> > +                COL_LOCKED, lun_item->info.locked,
> > +                COL_IDLE, !lun_item->info.started,
> > +                COL_CD_DEV, FALSE,
> > +                COL_LUN_ITEM, TRUE, /* LUN item */
> > +                COL_DEV_ITEM, FALSE, /* LUN item */
> > +                COL_ITEM_DATA, (gpointer)lun_item,
> > +                COL_CONNECTED, is_dev_connected,
> > +                COL_ROW_COLOR, "azure",
> > +                COL_ROW_COLOR_SET, TRUE,
> > +                -1);
> > +    }
> > +    g_array_unref(lun_array);
> > +
> > +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store),
> &new_dev_iter);
> > +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
> > +    gtk_tree_path_free(new_dev_path);
> > +
> > +    g_free(dev_info.vendor);
> > +    g_free(dev_info.product);
> > +    g_free(addr_str);
> > +}
> > +
> > +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter
> *iter)
> > +{
> > +    gboolean is_lun;
> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM,
> &is_lun, -1);
> > +    return is_lun;
> > +}
> > +
> > +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel
> *tree_model,
> > +
> GtkTreePath *path, GtkTreeIter *iter,
> > +                                                              gpointer
> user_data)
> > +{
> > +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
> > +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
> > +    SpiceUsbDevice *usb_device;
> > +    gboolean is_lun_item;
> > +
> > +    gtk_tree_model_get(tree_model, iter,
> > +                       COL_LUN_ITEM, &is_lun_item,
> > +                       COL_ITEM_DATA, (gpointer *)&usb_device,
> > +                       -1);
> > +    if (!is_lun_item && usb_device == find_usb_device) {
> > +        find_info->dev_iter = *iter;
> > +        find_info->usb_dev  = NULL;
> > +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
> > +        return TRUE; /* stop iterating */
> > +    } else {
> > +        return FALSE; /* continue iterating */
> > +    }
> > +}
> > +
> > +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore
> *tree_store,
> > +
> SpiceUsbDevice *usb_device)
> > +{
> > +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };
> > +    GtkTreeIter *iter = NULL;
> > +
> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
> > +                           usb_widget_tree_store_find_usb_dev_foreach_cb,
> (gpointer)&find_info);
> > +    // the callback sets 'usb_dev' field to zero if it finds the device
> > +    if (!find_info.usb_dev) {
> > +        iter = g_malloc(sizeof(*iter));
> > +        *iter = find_info.dev_iter;
> > +    }
> > +    return iter;
> > +}
> > +
> > +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
> > +                                         SpiceUsbDevice *usb_device)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeIter *old_dev_iter;
> > +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
> > +    // on WIN32 it is possible the backend device is already removed
> > +    // from the USB device manager list and we can't know it was
> > +    // USB or CD, do we will try both lists
> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> > +    if (old_dev_iter != NULL) {
> > +        SPICE_DEBUG("USB Device removed");
> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> > +        priv->device_count--;
> > +        g_free(old_dev_iter);
> > +        return TRUE;
> > +    }
> > +    tree_store = priv->cd_tree.tree_store;
> > +    if (tree_store) {
> > +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> > +    }
> > +    if (old_dev_iter != NULL) {
> > +        SPICE_DEBUG("CD Device removed");
> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> > +        priv->device_count--;
> > +        g_free(old_dev_iter);
> > +        return TRUE;
> > +    }
> > +    SPICE_DEBUG("Device %p not found!", usb_device);
> > +    return FALSE;
> > +}
> > +
> > +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget
> *self,
> > +                                                 enum column_id
> toggle_col_id,
> > +                                                 enum column_id
> visible_col_id,
> > +                                                 enum column_id
> sensitive_col_id,
> > +                                                 tree_item_toggled_cb
> toggled_cb,
> > +                                                 gboolean is_cd)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkCellRenderer     *renderer;
> > +    GtkTreeViewColumn   *view_col;
> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> > +
> > +    renderer = gtk_cell_renderer_toggle_new();
> > +
> > +    if (sensitive_col_id != INVALID_COL) {
> > +        view_col = gtk_tree_view_column_new_with_attributes(
> > +                        col_name,
> > +                        renderer,
> > +                        "active", toggle_col_id,
> > +                        "visible", visible_col_id,
> > +                        "activatable", sensitive_col_id,
> > +                        NULL);
> > +    } else {
> > +        view_col = gtk_tree_view_column_new_with_attributes(
> > +                        col_name,
> > +                        renderer,
> > +                        "active", toggle_col_id,
> > +                        "visible", visible_col_id,
> > +                        NULL);
> > +    }
> > +
> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> > +
> > +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);
> > +
> > +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u :
> %s]",
> > +            toggle_col_id, col_name,
> > +            visible_col_id, column_name(visible_col_id, is_cd));
> > +    return view_col;
> > +}
> > +
> > +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget
> *self,
> > +                                                           enum
> column_id toggle_col_id,
> > +                                                           enum
> column_id visible_col_id,
> > +                                                           gboolean
> is_cd)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkCellRenderer     *renderer;
> > +    GtkTreeViewColumn   *view_col;
> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> > +
> > +    renderer = gtk_cell_renderer_toggle_new();
> > +
> > +    view_col = gtk_tree_view_column_new_with_attributes(
> > +                    col_name,
> > +                    renderer,
> > +                    "active", toggle_col_id,
> > +                    "visible", visible_col_id,
> > +                    NULL);
> > +
> > +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer),
> FALSE);
> > +
> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> > +
> > +    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible
> when [%u : %s]",
> > +            toggle_col_id, col_name,
> > +            visible_col_id, column_name(visible_col_id, is_cd));
> > +    return view_col;
> > +}
> > +
> > +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget
> *self,
> > +                                               enum column_id col_id,
> > +                                               gboolean expandable,
> > +                                               gboolean is_cd)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkCellRenderer     *renderer;
> > +    GtkTreeViewColumn   *view_col;
> > +
> > +    renderer = gtk_cell_renderer_text_new();
> > +
> > +    view_col = gtk_tree_view_column_new_with_attributes(
> > +                    column_name(col_id, is_cd),
> > +                    renderer,
> > +                    "text", col_id,
> > +                    //"cell-background", COL_ROW_COLOR,
> > +                    //"cell-background-set", COL_ROW_COLOR_SET,
> > +                    NULL);
> > +
> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_GROW_ONLY);
> > +    gtk_tree_view_column_set_resizable(view_col, TRUE);
> > +    gtk_tree_view_column_set_expand(view_col, expandable);
> > +
> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> > +
> > +    SPICE_DEBUG("view added text column [%u : %s]", col_id,
> column_name(col_id, is_cd));
> > +    return view_col;
> > +}
> > +
> > +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget
> *self,
> > +                                                 enum column_id col_id,
> > +                                                 enum column_id
> visible_col_id,
> > +                                                 gboolean is_cd)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkCellRenderer     *renderer;
> > +    GtkTreeViewColumn   *view_col;
> > +    const char *col_name = column_name(col_id, is_cd);
> > +
> > +    renderer = gtk_cell_renderer_pixbuf_new();
> > +
> > +    if (visible_col_id == INVALID_COL) {
> > +        view_col = gtk_tree_view_column_new_with_attributes(
> > +                        col_name,
> > +                        renderer,
> > +                        "pixbuf", col_id,
> > +                        NULL);
> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always",
> col_id, col_name);
> > +    } else {
> > +        view_col = gtk_tree_view_column_new_with_attributes(
> > +                        col_name,
> > +                        renderer,
> > +                        "pixbuf", col_id,
> > +                        "visible", visible_col_id,
> > +                        NULL);
> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u :
> %s]",
> > +                col_id, col_name, visible_col_id,
> column_name(visible_col_id, is_cd));
> > +    }
> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> > +    return view_col;
> > +}
> > +
> > +/* Toggle handlers */
> > +
> > +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store,
> gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
> > +{
> > +    gboolean toggle_val;
> > +
> > +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store),
> iter, path_str);
> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id,
> &toggle_val, -1);
> > +
> > +    return toggle_val;
> > +}
> > +
> > +static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter
> *iter, enum column_id col_id, gboolean new_val)
> > +{
> > +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
> > +}
> > +
> > +typedef struct _connect_cb_data {
> > +    SpiceUsbDeviceWidget *self;
> > +    SpiceUsbDevice *usb_dev;
> > +} connect_cb_data;
> > +
> > +static void connect_cb_data_free(connect_cb_data *user_data)
> > +{
> > +    spice_usb_device_widget_update_status(user_data->self);
> > +    g_object_unref(user_data->self);
> > +    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
> > +    g_free(user_data);
> > +}
> > +
> > +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult
> *res, gpointer user_data)
> > +{
> > +    connect_cb_data *cb_data = user_data;
> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> > +    GError *err = NULL;
> > +    GtkTreeIter *dev_iter;
> > +    gchar *desc;
> > +    gboolean finished;
> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> > +
> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> > +    if (!dev_iter) {
> > +        return;
> > +    }
> > +
> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
> > +
> > +    finished = spice_usb_device_manager_connect_device_finish(priv->manager,
> res, &err);
> > +    if (finished) {
> > +        gtk_tree_store_set(tree_store, dev_iter,
> > +                           COL_CONNECT_ICON, priv->icon_connected,
> > +                           COL_CONNECTED, TRUE,
> > +                           -1);
> > +    } else {
> > +        gtk_tree_store_set(tree_store, dev_iter,
> > +                           COL_REDIRECT, FALSE,
> > +                           -1);
> > +        g_prefix_error(&err, "Device connect failed %s: ", desc);
> > +        if (err) {
> > +            SPICE_DEBUG("%s", err->message);
> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev,
> err);
> > +            g_error_free(err);
> > +        } else {
> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev,
> NULL);
> > +        }
> > +
> > +        /* don't trigger a disconnect if connect failed */
> > +        /*
> > +        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_
> data->check),
> > +                                        checkbox_clicked_cb, self);
> > +        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check),
> FALSE);
> > +        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_
> data->check),
> > +                                        checkbox_clicked_cb, self);
> > +        */
> > +    }
> > +    g_free(desc);
> > +    g_free(dev_iter);
> > +    connect_cb_data_free(user_data);
> > +}
> > +
> > +static void usb_widget_disconnect_cb(GObject *source_object,
> GAsyncResult *res, gpointer user_data)
> > +{
> > +    connect_cb_data *cb_data = user_data;
> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> > +    GError *err = NULL;
> > +    GtkTreeIter *dev_iter;
> > +    gchar *desc;
> > +    gboolean finished;
> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> > +
> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> > +    if (!dev_iter) {
> > +        return;
> > +    }
> > +
> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
> > +
> > +    finished = spice_usb_device_manager_disconnect_device_finish(priv->manager,
> res, &err);
> > +    if (finished) {
> > +        gtk_tree_store_set(tree_store, dev_iter,
> > +                           COL_CONNECT_ICON, priv->icon_disconn,
> > +                           COL_CONNECTED, FALSE,
> > +                           -1);
> > +    } else {
> > +        gtk_tree_store_set(tree_store, dev_iter,
> > +                           COL_REDIRECT, TRUE,
> > +                           -1);
> > +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
> > +        if (err) {
> > +            SPICE_DEBUG("%s", err->message);
> > +            g_error_free(err);
> > +        }
> > +    }
> > +    g_free(desc);
> > +    g_free(dev_iter);
> > +    connect_cb_data_free(user_data);
> > +}
> > +
> > +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell,
> > +                                          gchar *path_str,
> > +                                          SpiceUsbDeviceWidget *self,
> > +                                          GtkTreeStore *tree_store)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
> > +    SpiceUsbDevice *usb_dev;
> > +    GtkTreeIter iter;
> > +    gboolean new_redirect_val;
> > +
> > +    new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str,
> &iter, COL_REDIRECT);
> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
> > +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT,
> new_redirect_val);
> > +
> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter,
> COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
> > +    cb_data->self = g_object_ref(self);
> > +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(),
> usb_dev);
> > +
> > +    if (new_redirect_val) {
> > +        spice_usb_device_manager_connect_device_async(priv->manager,
> usb_dev,
> > +                                                      NULL, /*
> cancellable */
> > +
> usb_widget_connect_cb, cb_data);
> > +    } else {
> > +        spice_usb_device_manager_disconnect_device_async(priv->manager,
> usb_dev,
> > +                                                         NULL, /*
> cancellable */
> > +
>  usb_widget_disconnect_cb, cb_data);
> > +
> > +    }
> > +    spice_usb_device_widget_update_status(self);
> > +}
> > +
> > +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle
> *cell,
> > +                                             gchar *path_str,
> > +                                             gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->cd_tree.tree_store);
> > +}
> > +
> > +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle
> *cell,
> > +                                             gchar *path_str,
> > +                                             gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->usb_tree.tree_store);
> > +}
> > +
> > +/* Signal handlers */
> > +
> > +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +
> > +    SPICE_DEBUG("Signal: Device Added");
> > +
> > +    usb_widget_add_device(self, usb_device, NULL);
> > +
> > +    spice_usb_device_widget_update_status(self);
> > +}
> > +
> > +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    gboolean dev_removed;
> > +
> > +    SPICE_DEBUG("Signal: Device Removed");
> > +
> > +    dev_removed = usb_widget_remove_device(self, usb_device);
> > +    if (dev_removed) {
> > +        spice_usb_device_widget_update_status(self);
> > +    }
> > +}
> > +
> > +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeIter *old_dev_iter;
> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_device);
> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> > +
> > +    SPICE_DEBUG("Signal: Device Changed");
> > +
> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> > +    if (old_dev_iter != NULL) {
> > +
> > +        usb_widget_add_device(self, usb_device, old_dev_iter);
> > +
> > +        spice_usb_device_widget_update_status(self);
> > +        g_free(old_dev_iter);
> > +    } else {
> > +        SPICE_DEBUG("Device not found!");
> > +    }
> > +}
> > +
> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
> > +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeIter *dev_iter;
> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_device);
> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> > +
> > +    SPICE_DEBUG("Signal: Device Error");
> > +
> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> > +    if (dev_iter != NULL) {
> > +        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT,
> FALSE);
> > +        spice_usb_device_widget_update_status(self);
> > +        g_free(dev_iter);
> > +    } else {
> > +        SPICE_DEBUG("Device not found!");
> > +    }
> > +}
> > +
> > +/* Selection handler */
> > +
> > +static void tree_selection_changed_cb(GtkTreeSelection *select,
> gpointer user_data)
> > +{
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +    GtkTreePath *path;
> > +    gboolean is_lun;
> > +    UsbWidgetLunItem *lun_item;
> > +    gchar *txt[3];
> > +
> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        gtk_tree_model_get(tree_model, &iter,
> > +                COL_VENDOR, &txt[0],
> > +                COL_PRODUCT, &txt[1],
> > +                COL_REVISION, &txt[2],
> > +                COL_LUN_ITEM, &is_lun,
> > +                COL_ITEM_DATA, (gpointer *)&lun_item,
> > +                -1);
> > +        path = gtk_tree_model_get_path(tree_model, &iter);
> > +
> > +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
> > +                txt[0], txt[1],
> > +                is_lun ? txt[2] : "--",
> > +                is_lun ? "LUN" : "USB-DEV",
> > +                is_lun ? lun_item->info.file_path : "--",
> > +                gtk_tree_path_to_string(path));
> > +
> > +        if (txt[0]) {
> > +            g_free(txt[0]);
> > +        }
> > +        if (txt[1]) {
> > +            g_free(txt[1]);
> > +        }
> > +        if (txt[2]) {
> > +            g_free(txt[2]);
> > +        }
> > +        gtk_tree_path_free(path);
> > +    }
> > +}
> > +
> > +static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view)
> > +{
> > +    GtkTreeSelection *select;
> > +
> > +    select = gtk_tree_view_get_selection(tree_view);
> > +    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
> > +
> > +    g_signal_connect(G_OBJECT(select), "changed",
> > +                     G_CALLBACK(tree_selection_changed_cb),
> > +                     NULL);
> > +
> > +    SPICE_DEBUG("selection handler set");
> > +    return select;
> > +}
> > +
> > +static GtkWidget *create_image_button_box(const gchar *label_str, const
> gchar *icon_name, GtkWidget *parent)
> > +{
> > +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
> > +    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name,
> GTK_ICON_SIZE_MENU);
> > +    GtkWidget *label = gtk_accel_label_new(label_str);
> > +    GtkAccelGroup *accel_group = gtk_accel_group_new();
> > +    guint accel_key;
> > +
> > +    /* add icon */
> > +    gtk_container_add(GTK_CONTAINER(box), icon);
> > +
> > +    /* add label */
> > +    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
> > +    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
> > +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);
> > +    gtk_widget_add_accelerator(parent, "activate", accel_group,
> accel_key,
> > +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
> > +    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent);
> > +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
> > +
> > +    /* add the new box to the parent widget */
> > +    gtk_container_add(GTK_CONTAINER(parent), box);
> > +
> > +    return box;
> > +}
> > +
> > +/* LUN properties dialog */
> > +
> > +typedef struct _lun_properties_dialog {
> > +    GtkWidget *dialog;
> > +    GtkWidget *advanced_grid;
> > +    gboolean advanced_shown;
> > +
> > +    GtkWidget *file_entry;
> > +    GtkWidget *vendor_entry;
> > +    GtkWidget *product_entry;
> > +    GtkWidget *revision_entry;
> > +    GtkWidget *loaded_switch;
> > +    GtkWidget *locked_switch;
> > +} lun_properties_dialog;
> > +
> > +#if 1
> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> > +{
> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
> > +    GtkFileChooserNative *native;
> > +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
> > +    gint res;
> > +
> > +    native = gtk_file_chooser_native_new("Choose File for USB CD",
> > +        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
> > +        action,
> > +        "_Open",
> > +        "_Cancel");
> > +
> > +    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
> > +    if (res == GTK_RESPONSE_ACCEPT) {
> > +        char *filename = gtk_file_chooser_get_filename(
> GTK_FILE_CHOOSER(native));
> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> > +        g_free(filename);
> > +    }
> > +    else {
> > +        gtk_widget_grab_focus(button);
> > +    }
> > +
> > +    g_object_unref(native);
> > +}
> > +#else
> > +// to be removed
> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> > +{
> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
> > +    GtkWidget *dialog;
> > +    gint res;
> > +
> > +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
> > +                                          GTK_WINDOW(gtk_widget_get_
> toplevel(file_entry)),
> > +                                          GTK_FILE_CHOOSER_ACTION_OPEN,
> > +                                          "_Cancel",
> > +                                          GTK_RESPONSE_CANCEL,
> > +                                          "_Ok",
> > +                                          GTK_RESPONSE_ACCEPT,
> > +                                          NULL);
> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
> GTK_RESPONSE_ACCEPT);
> > +
> > +    res = gtk_dialog_run(GTK_DIALOG(dialog));
> > +    if (res == GTK_RESPONSE_ACCEPT) {
> > +        char *filename = gtk_file_chooser_get_filename(
> GTK_FILE_CHOOSER(dialog));
> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> > +        g_free(filename);
> > +    }
> > +    gtk_widget_destroy(dialog);
> > +}
> > +#endif
> > +
> > +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget
> *widget,
> > +                                                       gboolean state,
> gpointer user_data)
> > +{
> > +    lun_properties_dialog *lun_dialog = user_data;
> > +
> > +    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
> > +    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
> > +
> > +    return FALSE; /* call default signal handler */
> > +}
> > +
> > +static void lun_properties_dialog_toggle_advanced(GtkWidget *widget,
> gpointer user_data)
> > +{
> > +    lun_properties_dialog *lun_dialog = user_data;
> > +
> > +    if (lun_dialog->advanced_shown) {
> > +        gtk_widget_hide(lun_dialog->advanced_grid);
> > +        lun_dialog->advanced_shown = FALSE;
> > +    } else {
> > +        gtk_widget_show_all(lun_dialog->advanced_grid);
> > +        lun_dialog->advanced_shown = TRUE;
> > +    }
> > +}
> > +
> > +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
> > +                                         GtkWidget *parent_window,
> > +                                         SpiceUsbDeviceLunInfo
> *lun_info,
> > +                                         lun_properties_dialog
> *lun_dialog)
> > +{
> > +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkWidget *dialog, *content_area;
> > +    GtkWidget *grid, *advanced_grid;
> > +    GtkWidget *file_entry, *choose_button;
> > +    GtkWidget *advanced_button, *advanced_icon;
> > +    GtkWidget *vendor_entry, *product_entry, *revision_entry;
> > +    GtkWidget *loaded_switch, *loaded_label;
> > +    GtkWidget *locked_switch, *locked_label;
> > +    gint nrow = 0;
> > +
> > +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" :
> "CD LUN Settings",
> > +                    GTK_WINDOW(parent_window),
> > +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
> /* flags */
> > +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
> > +                    "Cancel", GTK_RESPONSE_REJECT,
> > +                    NULL);
> > +
> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
> GTK_RESPONSE_ACCEPT);
> > +    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
> > +    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))),
> 12);
> > +
> > +    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
> > +
> > +    /* main grid - always visible */
> > +    grid = gtk_grid_new();
> > +    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
> > +    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
> > +    gtk_container_add(GTK_CONTAINER(content_area), grid);
> > +
> > +    /* File path label */
> > +    gtk_grid_attach(GTK_GRID(grid),
> > +            gtk_label_new("Select file or device"),
> > +            0, nrow++, // left top
> > +            7, 1); // width height
> > +
> > +    /* file/device path entry */
> > +    file_entry = gtk_entry_new();
> > +    gtk_widget_set_hexpand(file_entry, TRUE);
> > +    if (!lun_info) {
> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry),
> "file-path");
> > +    } else {
> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path);
> > +        if (lun_info->loaded) {
> > +            gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE);
> > +            gtk_widget_set_can_focus(file_entry, FALSE);
> > +        }
> > +    }
> > +    gtk_grid_attach(GTK_GRID(grid),
> > +            file_entry,
> > +            0, nrow, // left top
> > +            6, 1); // width height
> > +
> > +    /* choose button */
> > +    choose_button = gtk_button_new_with_mnemonic("_Choose File");
> > +    gtk_widget_set_hexpand(choose_button, FALSE);
> > +    g_signal_connect(GTK_BUTTON(choose_button),
> > +                     "clicked", G_CALLBACK(usb_cd_choose_file),
> file_entry);
> > +    if (lun_info && lun_info->loaded) {
> > +        gtk_widget_set_sensitive(choose_button, FALSE);
> > +        gtk_widget_set_can_focus(choose_button, FALSE);
> > +    }
> > +
> > +    gtk_grid_attach(GTK_GRID(grid),
> > +            choose_button,
> > +            6, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    /* advanced button */
> > +    advanced_button = gtk_button_new_with_label("Advanced");
> > +    gtk_button_set_relief(GTK_BUTTON(advanced_button),
> GTK_RELIEF_NONE);
> > +    advanced_icon = gtk_image_new_from_icon_name("preferences-system",
> GTK_ICON_SIZE_BUTTON);
> > +    gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon);
> > +    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button),
> TRUE);
> > +    g_signal_connect(advanced_button, "clicked",
> G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
> > +
> > +    gtk_grid_attach(GTK_GRID(grid),
> > +            advanced_button,
> > +            0, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    /* advanced grid */
> > +    advanced_grid = gtk_grid_new();
> > +    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
> > +    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
> > +    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
> > +
> > +    /* horizontal separator */
> > +    gtk_container_add(GTK_CONTAINER(content_area),
> gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
> > +
> > +    /* pack advanced grid */
> > +    nrow = 0;
> > +
> > +    /* horizontal separator */
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> > +            0, nrow++, // left top
> > +            7, 1); // width height
> > +
> > +    /* product id labels */
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            gtk_label_new("Vendor"),
> > +            0, nrow, // left top
> > +            2, 1); // width height
> > +
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            gtk_label_new("Product"),
> > +            2, nrow, // left top
> > +            4, 1); // width height
> > +
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            gtk_label_new("Revision"),
> > +            6, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    /* vendor entry */
> > +    vendor_entry = gtk_entry_new();
> > +    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
> > +    if (lun_info) {
> > +        gtk_widget_set_sensitive(vendor_entry, FALSE);
> > +        gtk_widget_set_can_focus(vendor_entry, FALSE);
> > +        gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor);
> > +    } else {
> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry),
> "auto");
> > +    }
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            vendor_entry,
> > +            0, nrow, // left top
> > +            2, 1); // width height
> > +
> > +    /* product entry */
> > +    product_entry = gtk_entry_new();
> > +    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
> > +    if (lun_info) {
> > +        gtk_widget_set_sensitive(product_entry, FALSE);
> > +        gtk_widget_set_can_focus(product_entry, FALSE);
> > +        gtk_entry_set_text(GTK_ENTRY(product_entry),
> lun_info->product);
> > +    } else {
> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry),
> "auto");
> > +    }
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            product_entry,
> > +            2, nrow, // left top
> > +            4, 1); // width height
> > +
> > +    /* revision entry */
> > +    revision_entry = gtk_entry_new();
> > +    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
> > +    if (lun_info) {
> > +        gtk_widget_set_sensitive(revision_entry, FALSE);
> > +        gtk_widget_set_can_focus(revision_entry, FALSE);
> > +        gtk_entry_set_text(GTK_ENTRY(revision_entry),
> lun_info->revision);
> > +    } else {
> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry),
> "auto");
> > +    }
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            revision_entry,
> > +            6, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    /* horizontal separator */
> > +    if (!lun_info) {
> > +        gtk_grid_attach(GTK_GRID(advanced_grid),
> > +                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> > +                0, nrow++, // left top
> > +                7, 1); // width height
> > +    }
> > +
> > +    /* initially loaded switch */
> > +    loaded_label = gtk_label_new("Initially loaded:");
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +        loaded_label,
> > +        0, nrow, // left top
> > +        2, 1); // width height
> > +
> > +    loaded_switch = gtk_switch_new();
> > +    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
> > +    if (lun_info) {
> > +        gtk_widget_set_child_visible(loaded_switch, FALSE);
> > +        gtk_widget_set_child_visible(loaded_label, FALSE);
> > +    } else {
> > +        g_signal_connect(loaded_switch, "state-set",
> > +                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb),
> lun_dialog);
> > +    }
> > +    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            loaded_switch,
> > +            2, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    /* initially locked switch */
> > +    locked_label = gtk_label_new("Initially locked:");
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +        locked_label,
> > +        0, nrow, // left top
> > +        2, 1); // width height
> > +
> > +    locked_switch = gtk_switch_new();
> > +    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
> > +    gtk_widget_set_hexpand(locked_switch, FALSE);
> > +    if (lun_info) {
> > +        gtk_widget_set_child_visible(locked_switch, FALSE);
> > +        gtk_widget_set_child_visible(locked_label, FALSE);
> > +    }
> > +    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> > +            locked_switch,
> > +            2, nrow++, // left top
> > +            1, 1); // width height
> > +
> > +    lun_dialog->dialog = dialog;
> > +    lun_dialog->advanced_grid = advanced_grid;
> > +    lun_dialog->advanced_shown = FALSE;
> > +    lun_dialog->file_entry = file_entry;
> > +    lun_dialog->vendor_entry = vendor_entry;
> > +    lun_dialog->product_entry = product_entry;
> > +    lun_dialog->revision_entry = revision_entry;
> > +    lun_dialog->loaded_switch = loaded_switch;
> > +    lun_dialog->locked_switch = locked_switch;
> > +
> > +    gtk_widget_show_all(dialog);
> > +    gtk_widget_hide(advanced_grid);
> > +}
> > +
> > +static void lun_properties_dialog_get_info(lun_properties_dialog
> *lun_dialog,
> > +                                            SpiceUsbDeviceLunInfo
> *lun_info)
> > +{
> > +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(
> lun_dialog->file_entry));
> > +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(
> lun_dialog->vendor_entry));
> > +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(
> lun_dialog->product_entry));
> > +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(
> lun_dialog->revision_entry));
> > +    lun_info->loaded = gtk_switch_get_active(GTK_
> SWITCH(lun_dialog->loaded_switch));
> > +    lun_info->locked = gtk_switch_get_active(GTK_
> SWITCH(lun_dialog->locked_switch));
> > +}
> > +
> > +/* Popup menu */
> > +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer
> user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(
> priv->cd_tree.tree_view);
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +
> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> > +            SpiceUsbDevice *usb_device;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> > +            SPICE_DEBUG("%s - not applicable for USB device",
> __FUNCTION__);
> > +        }
> > +        else {
> > +            UsbWidgetLunItem *lun_item;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> > +            spice_usb_device_manager_device_lun_load(
> > +                lun_item->manager, lun_item->device, lun_item->lun,
> !lun_item->info.loaded);
> > +        }
> > +    }
> > +    else {
> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> > +    }
> > +}
> > +
> > +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer
> user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(
> priv->cd_tree.tree_view);
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +
> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> > +            SpiceUsbDevice *usb_device;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> > +            SPICE_DEBUG("%s - not applicable for USB device",
> __FUNCTION__);
> > +        }
> > +        else {
> > +            UsbWidgetLunItem *lun_item;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> > +            spice_usb_device_manager_device_lun_lock(
> > +                lun_item->manager, lun_item->device, lun_item->lun,
> !lun_item->info.locked);
> > +        }
> > +    }
> > +    else {
> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> > +    }
> > +}
> > +
> > +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer
> user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(
> priv->cd_tree.tree_view);
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +
> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> > +            SpiceUsbDevice *usb_device;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> > +            SPICE_DEBUG("Remove USB device");
> > +        } else {
> > +            UsbWidgetLunItem *lun_item;
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> > +            gtk_tree_selection_unselect_all(select);
> > +            spice_usb_device_manager_device_lun_remove(lun_item->manager,
> lun_item->device, lun_item->lun);
> > +        }
> > +    } else {
> > +        SPICE_DEBUG("Remove - failed to get selection");
> > +    }
> > +}
> > +
> > +static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer
> user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(
> priv->cd_tree.tree_view);
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +
> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> > +            SPICE_DEBUG("No settings for USB device yet");
> > +        } else {
> > +            lun_properties_dialog lun_dialog;
> > +            UsbWidgetLunItem *lun_item;
> > +            gint resp;
> > +
> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> > +            gtk_tree_selection_unselect_all(select);
> > +            create_lun_properties_dialog(self, NULL, &lun_item->info,
> &lun_dialog);
> > +
> > +            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> > +            if (resp == GTK_RESPONSE_ACCEPT) {
> > +                SpiceUsbDeviceLunInfo lun_info;
> > +                SPICE_DEBUG("response is ACCEPT");
> > +                lun_properties_dialog_get_info(&lun_dialog, &lun_info);
> > +                spice_usb_device_manager_device_lun_change_media(
> > +                    priv->manager, lun_item->device, lun_item->lun,
> &lun_info);
> > +            } else {
> > +                SPICE_DEBUG("response is REJECT");
> > +            }
> > +            gtk_widget_destroy(lun_dialog.dialog);
> > +        }
> > +    } else {
> > +        SPICE_DEBUG("Remove - failed to get selection");
> > +    }
> > +}
> > +
> > +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
> > +    const gchar *label_str,
> > +    const gchar *icon_name,
> > +    GCallback cb_func, gpointer user_data)
> > +{
> > +    GtkWidget *menu_item = gtk_menu_item_new();
> > +    create_image_button_box(label_str, icon_name, menu_item);
> > +    g_signal_connect(menu_item, "activate", cb_func, user_data);
> > +
> > +    gtk_widget_show_all(menu_item);
> > +    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
> > +
> > +    return menu_item;
> > +}
> > +
> > +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv,
> SpiceUsbDevice *device)
> > +{
> > +    gboolean result;
> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> > +    GArray *lun_array;
> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr,
> device);
> > +    result = lun_array && lun_array->len <= 1;
> > +    if (lun_array) {
> > +        g_array_unref(lun_array);
> > +    }
> > +    return result;
> > +}
> > +
> > +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton
> *event, gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeSelection *select;
> > +    GtkTreeModel *tree_model;
> > +    GtkTreeIter iter;
> > +    UsbWidgetLunItem *lun_item;
> > +    gboolean is_loaded, is_locked;
> > +    GtkTreeIter *usb_dev_iter;
> > +    gboolean is_dev_connected;
> > +    GtkWidget *menu;
> > +
> > +    if (tree_view != priv->cd_tree.tree_view) {
> > +        SPICE_DEBUG("Not applicable for USB device");
> > +        return;
> > +    }
> > +
> > +    select = gtk_tree_view_get_selection(tree_view);
> > +
> > +    if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
> > +        SPICE_DEBUG("No tree view row is selected");
> > +        return;
> > +    }
> > +    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> > +        SPICE_DEBUG("No settings for USB device yet");
> > +        return;
> > +    }
> > +
> > +    gtk_tree_model_get(tree_model, &iter,
> > +                       COL_ITEM_DATA, (gpointer *)&lun_item,
> > +                       COL_LOADED, &is_loaded,
> > +                       COL_LOCKED, &is_locked,
> > +                       -1);
> > +
> > +    usb_dev_iter = usb_widget_tree_store_find_
> usb_device(priv->cd_tree.tree_store, lun_item->device);
> > +    if (usb_dev_iter != NULL) {
> > +        gtk_tree_model_get(tree_model, usb_dev_iter,
> > +                           COL_CONNECTED, &is_dev_connected,
> > +                           -1);
> > +        g_free(usb_dev_iter);
> > +    } else {
> > +        is_dev_connected = FALSE;
> > +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
> > +                    lun_item->info.vendor, lun_item->info.product);
> > +    }
> > +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun
> loaded:%d locked:%d",
> > +                lun_item->info.vendor, lun_item->info.product,
> > +                is_dev_connected, is_loaded, is_locked);
> > +
> > +    /* Set up the menu */
> > +    menu = gtk_menu_new();
> > +
> > +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
> > +                             G_CALLBACK(view_popup_menu_on_settings),
> user_data);
> > +    if (is_loaded) {
> > +        if (!is_locked) {
> > +            view_popup_add_menu_item(menu, "_Lock",
> "system-lock-screen",
> > +                                    G_CALLBACK(view_popup_menu_on_lock),
> user_data);
> > +            view_popup_add_menu_item(menu, "_Eject", "media-eject",
> > +                                     G_CALLBACK(view_popup_menu_on_eject),
> user_data);
> > +        } else {
> > +            view_popup_add_menu_item(menu, "_Unlock",
> "system-lock-screen",
> > +                                     G_CALLBACK(view_popup_menu_on_lock),
> user_data);
> > +        }
> > +    } else {
> > +        view_popup_add_menu_item(menu, "_Load", "media-eject",
> > +                                 G_CALLBACK(view_popup_menu_on_eject),
> user_data);
> > +    }
> > +
> > +    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {
> > +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
> > +                                 G_CALLBACK(view_popup_menu_on_remove),
> user_data);
> > +    }
> > +
> > +    gtk_widget_show_all(menu);
> > +    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
> > +}
> > +
> > +static void treeview_select_current_row_by_pos(GtkTreeView *tree_view,
> gint x, gint y)
> > +{
> > +    GtkTreeSelection *selection = gtk_tree_view_get_selection(
> GTK_TREE_VIEW(tree_view));
> > +    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
> > +        GtkTreePath *path;
> > +        /* Get tree path for row that was clicked */
> > +        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x,
> y, &path, NULL, NULL, NULL))
> > +        {
> > +            gtk_tree_selection_unselect_all(selection);
> > +            gtk_tree_selection_select_path(selection, path);
> > +            gtk_tree_path_free(path);
> > +        }
> > +    }
> > +}
> > +
> > +static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view,
> GdkEventButton *event, gpointer user_data)
> > +{
> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
> > +    /* single click with the right mouse button */
> > +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
> > +        /* select the row that was clicked, it will also provide the
> context */
> > +        treeview_select_current_row_by_pos(tree_view, (gint)event->x,
> (gint)event->y);
> > +        view_popup_menu(tree_view, event, user_data);
> > +        return TRUE; /* we handled this */
> > +    } else {
> > +        return FALSE; /* we did not handle this */
> > +    }
> > +}
> > +
> > +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view,
> gpointer user_data)
> > +{
> > +    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
> > +    return TRUE; /* we handled this */
> > +}
> > +
> > +/* Add LUN dialog */
> > +
> > +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button,
> gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button);
> > +    lun_properties_dialog lun_dialog;
> > +    gint resp;
> > +
> > +    create_lun_properties_dialog(self, parent_window, NULL,
> &lun_dialog);
> > +
> > +    resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> > +    if (resp == GTK_RESPONSE_ACCEPT) {
> > +        SpiceUsbDeviceLunInfo lun_info;
> > +        SPICE_DEBUG("response is ACCEPT");
> > +        lun_properties_dialog_get_info(&lun_dialog, &lun_info);
> > +        spice_usb_device_manager_add_cd_lun(priv->manager, &lun_info);
> > +    } else {
> > +        SPICE_DEBUG("response is REJECT");
> > +    }
> > +    gtk_widget_destroy(lun_dialog.dialog);
> > +}
> > +
> > +static void spice_usb_device_widget_get_property(GObject     *gobject,
> > +                                                 guint        prop_id,
> > +                                                 GValue      *value,
> > +                                                 GParamSpec  *pspec)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    switch (prop_id) {
> > +    case PROP_SESSION:
> > +        g_value_set_object(value, priv->session);
> > +        break;
> > +    case PROP_DEVICE_FORMAT_STRING:
> > +        g_value_set_string(value, priv->device_format_string);
> > +        break;
> > +    default:
> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> > +        break;
> > +    }
> > +}
> > +
> > +static void spice_usb_device_widget_set_property(GObject
>  *gobject,
> > +                                                 guint          prop_id,
> > +                                                 const GValue  *value,
> > +                                                 GParamSpec    *pspec)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    switch (prop_id) {
> > +    case PROP_SESSION:
> > +        priv->session = g_value_dup_object(value);
> > +        break;
> > +    case PROP_DEVICE_FORMAT_STRING:
> > +        priv->device_format_string = g_value_dup_string(value);
> > +        break;
> > +    default:
> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> > +        break;
> > +    }
> > +}
> > +
> > +static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget
> *self)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    if (priv->info_bar) {
> > +        gtk_widget_destroy(priv->info_bar);
> > +        priv->info_bar = NULL;
> > +    }
> > +}
> > +
> > +static void
> > +spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
> > +                                      const gchar          *message,
> > +                                      GtkMessageType
> message_type,
> > +                                      GdkPixbuf            *icon_pixbuf)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;
> > +
> > +    spice_usb_device_widget_hide_info_bar(self);
> > +
> > +    info_bar = gtk_info_bar_new();
> > +    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar),
> message_type);
> > +
> > +    content_area = gtk_info_bar_get_content_area(
> GTK_INFO_BAR(info_bar));
> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> > +    gtk_container_add(GTK_CONTAINER(content_area), hbox);
> > +
> > +    icon = gtk_image_new_from_pixbuf(icon_pixbuf);
> > +    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10);
> > +
> > +    label = gtk_label_new(message);
> > +    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
> > +
> > +    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
> > +    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 0,
> 0);
> > +    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
> > +
> > +    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
> > +    gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put
> after the lable */
> > +    gtk_widget_show_all(priv->info_bar);
> > +}
> > +
> > +static void spice_usb_device_widget_create_tree_view(SpiceUsbDeviceWidget
> *self, gboolean is_cd)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    GtkTreeStore *tree_store = usb_widget_create_tree_store();
> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
> > +
> > +    if (is_cd) {
> > +        priv->cd_tree.tree_view = tree_view;
> > +        priv->cd_tree.tree_store = tree_store;
> > +    } else {
> > +        priv->usb_tree.tree_view = tree_view;
> > +        priv->usb_tree.tree_store = tree_store;
> > +    }
> > +
> > +    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
> > +    g_object_unref(tree_store); /* destroy tree_store automatically
> with tree_view */
> > +
> > +    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM,
> COL_CAN_REDIRECT,
> > +        is_cd ? tree_item_toggled_cb_redirect_cd :
> tree_item_toggled_cb_redirect_usb, is_cd);
> > +
> > +    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);
> > +
> > +    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd);
> > +    if (is_cd) {
> > +        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);
> > +    }
> > +
> > +    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);
> > +    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);
> > +
> > +    if (is_cd) {
> > +        view_add_text_column(self, COL_FILE, TRUE, is_cd);
> > +        view_add_read_only_toggle_column(self, COL_LOADED,
> COL_LUN_ITEM, is_cd);
> > +        view_add_read_only_toggle_column(self, COL_LOCKED,
> COL_LUN_ITEM, is_cd);
> > +        // uncomment to show also 'idle' column for CD
> > +        //view_add_read_only_toggle_column(self, COL_IDLE,
> COL_LUN_ITEM, is_cd);
> > +    }
> > +
> > +    gtk_tree_selection_set_mode(
> > +            gtk_tree_view_get_selection(tree_view),
> > +            GTK_SELECTION_NONE);
> > +
> > +    if (is_cd) {
> > +        set_selection_handler(tree_view);
> > +    }
> > +}
> > +
> > +static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget
> *self)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    g_signal_connect(priv->manager, "device-added",
> > +                     G_CALLBACK(device_added_cb), self);
> > +    g_signal_connect(priv->manager, "device-removed",
> > +                     G_CALLBACK(device_removed_cb), self);
> > +    g_signal_connect(priv->manager, "device-changed",
> > +                     G_CALLBACK(device_changed_cb), self);
> > +    // TODO: connect failed
> > +    g_signal_connect(priv->manager, "device-error",
> > +                     G_CALLBACK(device_error_cb), self);
> > +
> > +    g_signal_connect(priv->cd_tree.tree_view, "button-press-event",
> > +                     G_CALLBACK(treeview_on_right_button_pressed_cb),
> self);
> > +    g_signal_connect(priv->cd_tree.tree_view, "popup-menu",
> > +                     G_CALLBACK(treeview_on_popup_key_pressed_cb),
> self);
> > +}
> > +
> > +static void create_tree_window(SpiceUsbDeviceWidget *self, GtkTreeView
> *tree_view)
> > +{
> > +    GtkWidget *sw;
> > +    /* scrolled window */
> > +    sw = gtk_scrolled_window_new(NULL, NULL);
> > +    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
> > +        GTK_SHADOW_ETCHED_IN);
> > +    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
> > +        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
> > +    gtk_widget_set_hexpand(sw, TRUE);
> > +    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
> > +    gtk_widget_set_vexpand(sw, TRUE);
> > +    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
> > +    gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view));
> > +    gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0);
> > +}
> > +
> > +static void spice_usb_device_widget_constructed(GObject *gobject)
> > +{
> > +    SpiceUsbDeviceWidget *self;
> > +    GtkRequisition min_size, natural_size;
> > +    SpiceUsbDeviceWidgetPrivate *priv;
> > +    GtkWidget *hbox, *dev_label;
> > +    GtkWidget *add_cd_button, *add_cd_icon;
> > +    GPtrArray *devices = NULL;
> > +    GError *err = NULL;
> > +    gchar *str;
> > +    guint i;
> > +    gboolean cd_sharing_enabled = TRUE;
> > +    #ifndef USE_CD_SHARING
> > +    cd_sharing_enabled = FALSE;
> > +    #endif
> > +
> > +    self = SPICE_USB_DEVICE_WIDGET(gobject);
> > +    priv = self->priv;
> > +    if (!priv->session)
> > +        g_error("SpiceUsbDeviceWidget constructed without a session");
> > +
> > +    min_size.width = 600;
> > +    min_size.height = 300;
> > +    natural_size.width = 1200;
> > +    natural_size.height = 600;
> > +    gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size,
> &natural_size);
> > +
> > +    priv->label = gtk_label_new(NULL);
> > +    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to
> redirect"));
> > +    gtk_label_set_markup(GTK_LABEL(priv->label), str);
> > +    g_free(str);
> > +    gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0);
> > +
> > +    priv->icon_cd = get_named_icon("media-optical",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> > +    priv->icon_connected = get_named_icon("network-transmit-receive",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> > +    priv->icon_disconn = get_named_icon("network-offline",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> > +    priv->icon_warning = get_named_icon("dialog-warning",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> > +    priv->icon_info = get_named_icon("dialog-information",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> > +
> > +    priv->manager = spice_usb_device_manager_get(priv->session, &err);
> > +    if (err) {
> > +        spice_usb_device_widget_show_info_bar(self, err->message,
> > +                                              GTK_MESSAGE_WARNING,
> priv->icon_warning);
> > +        g_clear_error(&err);
> > +        return;
> > +    }
> > +
> > +    if (cd_sharing_enabled) {
> > +        spice_usb_device_widget_create_tree_view(self, TRUE);
> > +    } else {
> > +        priv->cd_tree.tree_store = NULL;
> > +        priv->cd_tree.tree_view = NULL;
> > +    }
> > +    spice_usb_device_widget_create_tree_view(self, FALSE);
> > +
> > +    spice_usb_device_widget_signals_connect(self);
> > +
> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> > +    gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> > +
> > +    /* "Available devices" label - in hbox */
> > +    dev_label = gtk_label_new(_("Local USB devices"));
> > +    gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
> > +    create_tree_window(self, priv->usb_tree.tree_view);
> > +
> > +    if (cd_sharing_enabled) {
> > +        hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> > +        gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> > +        dev_label = gtk_label_new(_("Shared CD devices"));
> > +        gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
> > +        /* "Add CD" button - in hbox */
> > +        add_cd_button = gtk_button_new_with_label(_("Add CD"));
> > +        gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button),
> TRUE);
> > +        add_cd_icon = gtk_image_new_from_icon_name("list-add",
> GTK_ICON_SIZE_BUTTON);
> > +        gtk_button_set_image(GTK_BUTTON(add_cd_button), add_cd_icon);
> > +
> > +        gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END);
> > +        g_signal_connect(add_cd_button, "clicked",
> G_CALLBACK(add_cd_lun_button_clicked_cb), self);
> > +        gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE, FALSE,
> 0);
> > +        create_tree_window(self, priv->cd_tree.tree_view);
> > +    }
> > +    devices = spice_usb_device_manager_get_devices(priv->manager);
> > +    if (!devices)
> > +        goto end;
> > +
> > +    for (i = 0; i < devices->len; i++) {
> > +        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);
> > +        usb_widget_add_device(self, usb_device, NULL);
> > +    }
> > +    g_ptr_array_unref(devices);
> > +
> > +    select_widget_size(GTK_WIDGET(self));
> > +
> > +end:
> > +    spice_usb_device_widget_update_status(self);
> > +}
> > +
> > +static void spice_usb_device_widget_finalize(GObject *object)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    if (priv->manager) {
> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> > +                                             device_added_cb, self);
> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> > +                                             device_removed_cb, self);
> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> > +                                             device_changed_cb, self);
> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> > +                                             device_error_cb, self);
> > +    }
> > +    g_object_unref(priv->session);
> > +    g_free(priv->device_format_string);
> > +
> > +    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
> > +        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->
> finalize(object);
> > +}
> > +
> > +static void spice_usb_device_widget_class_init(
> > +    SpiceUsbDeviceWidgetClass *klass)
> > +{
> > +    GObjectClass *gobject_class = (GObjectClass *)klass;
> > +    GParamSpec *pspec;
> > +
> > +    g_type_class_add_private (klass, sizeof
> (SpiceUsbDeviceWidgetPrivate));
> > +
> > +    gobject_class->constructed  = spice_usb_device_widget_constructed;
> > +    gobject_class->finalize     = spice_usb_device_widget_finalize;
> > +    gobject_class->get_property = spice_usb_device_widget_get_property;
> > +    gobject_class->set_property = spice_usb_device_widget_set_property;
> > +
> > +    /**
> > +     * SpiceUsbDeviceWidget:session:
> > +     *
> > +     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
> > +     *
> > +     **/
> > +    pspec = g_param_spec_object("session",
> > +                                "Session",
> > +                                "SpiceSession",
> > +                                SPICE_TYPE_SESSION,
> > +                                G_PARAM_CONSTRUCT_ONLY |
> G_PARAM_READWRITE |
> > +                                G_PARAM_STATIC_STRINGS);
> > +    g_object_class_install_property(gobject_class, PROP_SESSION,
> pspec);
> > +
> > +    /**
> > +     * SpiceUsbDeviceWidget:device-format-string:
> > +     *
> > +     * Format string to pass to spice_usb_device_get_description() for
> getting
> > +     * the device USB descriptions.
> > +     */
> > +    pspec = g_param_spec_string("device-format-string",
> > +                                "Device format string",
> > +                                "Format string for device description",
> > +                                NULL,
> > +                                G_PARAM_CONSTRUCT_ONLY |
> G_PARAM_READWRITE |
> > +                                G_PARAM_STATIC_STRINGS);
> > +    g_object_class_install_property(gobject_class,
> PROP_DEVICE_FORMAT_STRING,
> > +                                    pspec);
> > +
> > +    /**
> > +     * SpiceUsbDeviceWidget::connect-failed:
> > +     * @widget: The #SpiceUsbDeviceWidget that emitted the signal
> > +     * @device: #SpiceUsbDevice boxed object corresponding to the added
> device
> > +     * @error:  #GError describing the reason why the connect failed
> > +     *
> > +     * The #SpiceUsbDeviceWidget::connect-failed signal is emitted
> whenever
> > +     * the user has requested for a device to be redirected and this has
> > +     * failed.
> > +     **/
> > +    signals[CONNECT_FAILED] =
> > +        g_signal_new("connect-failed",
> > +                    G_OBJECT_CLASS_TYPE(gobject_class),
> > +                    G_SIGNAL_RUN_FIRST,
> > +                    G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass,
> connect_failed),
> > +                    NULL, NULL,
> > +                    g_cclosure_user_marshal_VOID__BOXED_BOXED,
> > +                    G_TYPE_NONE,
> > +                    2,
> > +                    SPICE_TYPE_USB_DEVICE,
> > +                    G_TYPE_ERROR);
> > +}
> > +
> > +static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self)
> > +{
> > +    self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self);
> > +}
> > +
> > +/* ------------------------------------------------------------------
> */
> > +/* public api                                                         */
> > +
> > +/**
> > + * spice_usb_device_widget_new:
> > + * @session: #SpiceSession for which to widget will control USB
> redirection
> > + * @device_format_string: (allow-none): String passed to
> > + * spice_usb_device_get_description()
> > + *
> > + * Creates a new widget to control USB redirection.
> > + *
> > + * Returns: a new #SpiceUsbDeviceWidget instance
> > + */
> > +GtkWidget *spice_usb_device_widget_new(SpiceSession    *session,
> > +                                       const gchar
>  *device_format_string)
> > +{
> > +    static gboolean init_columns = TRUE;
> > +    spice_util_get_debug();
> > +    if (init_columns) {
> > +        initialize_columns();
> > +        init_columns = FALSE;
> > +    }
> > +    return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET,
> > +                        "orientation", GTK_ORIENTATION_VERTICAL,
> > +                        "session", session,
> > +                        "device-format-string", device_format_string,
> > +                        "spacing", 6,
> > +                        NULL);
> > +}
> > +
> > +/* ------------------------------------------------------------------
> */
> > +/* callbacks                                                          */
> > +
> > +static gboolean usb_widget_tree_store_check_redirect_foreach_cb(GtkTreeModel
> *tree_model,
> > +
> GtkTreePath *path,
> > +
> GtkTreeIter *iter,
> > +
> SpiceUsbDeviceWidget *self,
> > +
> GtkTreeStore *tree_store)
> > +{
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +
> > +    if (!tree_item_is_lun(tree_store, iter)) {
> > +        SpiceUsbDevice *usb_device;
> > +        gboolean can_redirect;
> > +
> > +        gtk_tree_model_get(tree_model, iter,
> > +                           COL_ITEM_DATA, (gpointer *)&usb_device,
> > +                           -1);
> > +
> > +        if (spice_usb_device_manager_is_redirecting(priv->manager)) {
> > +            can_redirect = FALSE;
> > +        } else {
> > +            GError *err = NULL;
> > +
> > +            can_redirect = spice_usb_device_manager_can_
> redirect_device(priv->manager,
> > +
> usb_device, &err);
> > +
> > +            /* If we cannot redirect this device, append the error
> message to
> > +               err_msg, but only if it is *not* already there! */
> > +            if (!can_redirect) {
> > +                if (priv->err_msg) {
> > +                    if (!strstr(priv->err_msg, err->message)) {
> > +                        gchar *old_err_msg = priv->err_msg;
> > +                        priv->err_msg = g_strdup_printf("%s\n%s",
> priv->err_msg,
> > +                                                        err->message);
> > +                        g_free(old_err_msg);
> > +                    }
> > +                } else {
> > +                    priv->err_msg = g_strdup(err->message);
> > +                }
> > +            }
> > +            g_clear_error(&err);
> > +        }
> > +        gtk_tree_store_set(tree_store, iter,
> > +                           COL_CAN_REDIRECT, can_redirect,
> > +                           -1);
> > +    }
> > +    return FALSE; /* continue iterating */
> > +}
> > +
> > +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_usb(GtkTreeModel
> *tree_model,
> > +
>  GtkTreePath *path, GtkTreeIter *iter,
> > +
>  gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    return usb_widget_tree_store_check_redirect_foreach_cb(
> > +        tree_model, path, iter, self, priv->usb_tree.tree_store);
> > +}
> > +
> > +#ifdef USE_CD_SHARING
> > +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_cd(GtkTreeModel
> *tree_model,
> > +
>  GtkTreePath *path, GtkTreeIter *iter,
> > +
>  gpointer user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    return usb_widget_tree_store_check_redirect_foreach_cb(
> > +        tree_model, path, iter, self, priv->cd_tree.tree_store);
> > +}
> > +#endif
> > +
> > +static gboolean spice_usb_device_widget_update_status(gpointer
> user_data)
> > +{
> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> > +    gchar *str, *markup_str;
> > +    const gchar *free_channels_str;
> > +    int free_channels;
> > +
> > +    g_object_get(priv->manager, "free-channels", &free_channels, NULL);
> > +    free_channels_str = ngettext(_("Select USB devices to redirect (%d
> free channel)"),
> > +                                 _("Select USB devices to redirect (%d
> free channels)"),
> > +                                 free_channels);
> > +    str = g_strdup_printf(free_channels_str, free_channels);
> > +    markup_str = g_strdup_printf("<b>%s</b>", str);
> > +    gtk_label_set_markup(GTK_LABEL (priv->label), markup_str);
> > +    g_free(markup_str);
> > +    g_free(str);
> > +
> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->usb_tree.tree_store),
> > +                           usb_widget_tree_store_check_redirect_foreach_cb_usb,
> self);
> > +    gtk_widget_show_all(GTK_WIDGET(priv->usb_tree.tree_view));
> > +
> > +#ifdef USE_CD_SHARING
> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(priv->cd_tree.tree_store),
> > +                           usb_widget_tree_store_check_redirect_foreach_cb_cd,
> self);
> > +    gtk_widget_show_all(GTK_WIDGET(priv->cd_tree.tree_view));
> > +#endif
> > +
> > +    /* Show messages in the info, if necessary */
> > +    if (priv->err_msg) {
> > +        spice_usb_device_widget_show_info_bar(self, priv->err_msg,
> > +                                              GTK_MESSAGE_INFO,
> priv->icon_warning);
> > +        g_free(priv->err_msg);
> > +        priv->err_msg = NULL;
> > +    } else if ( spice_usb_device_manager_is_redirecting(priv->manager))
> {
> > +        spice_usb_device_widget_show_info_bar(self, _("Redirecting USB
> Device..."),
> > +                                              GTK_MESSAGE_INFO,
> priv->icon_info);
> > +    } else {
> > +        spice_usb_device_widget_hide_info_bar(self);
> > +    }
> > +
> > +    if (priv->device_count == 0)
> > +        spice_usb_device_widget_show_info_bar(self, _("No USB devices
> detected"),
> > +                                              GTK_MESSAGE_INFO,
> priv->icon_info);
> > +
> > +    return FALSE;
> > +}
> > +
> > +#endif
> > --
> > 2.9.4
> >
> > _______________________________________________
> > Spice-devel mailing list
> > Spice-devel@lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/spice-devel
>
Hi

On Tue, Sep 18, 2018 at 12:04 PM Yuri Benditovich
<yuri.benditovich@daynix.com> wrote:
>
>
>
> On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <victortoso@redhat.com> wrote:
>>
>> On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
>> > New USB widget supports all the functionality related to
>> > redirection of local USB device (as previous usb widget did).
>> > Additionally it allows creation, management and redirection of
>> > emulated USB CD devices for CD sharing.
>>
>> Wouldn't be possible to extend the current widget instead of
>> creating a new one?
>
>
> Of course it was the first idea before creating new one.
> After several discussions we understood that there is almost nothing
> common between what we'd like to have for CD and what we have in existing widget.
> This also was decided on special meeting of Alexander with spice team (remove
> some controls and split it to two lists, locals and CD). Earlier the screenshot of
> the latest widget (current code) was sent to internal list for review and no objections
> were received.

Ultimately the discussion and decision should take place on public mailing list.

And many of us (including me), may not be directly involved in the RH
spice team.

Could you detail the goals you had and why you had to write a new widget?

For UI, it's best to focus on the supported use case & the user
story/experience, and avoid feature creep (usually the less UI, the
better).

>
>>
>>
>> > Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
>> > Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
>> > ---
>> >  src/usb-device-redir-widget.c | 2065 +++++++++++++++++++++++++++++++++++++++++
>> >  1 file changed, 2065 insertions(+)
>> >  create mode 100644 src/usb-device-redir-widget.c
>> >
>> > diff --git a/src/usb-device-redir-widget.c b/src/usb-device-redir-widget.c
>> > new file mode 100644
>> > index 0000000..59a6043
>> > --- /dev/null
>> > +++ b/src/usb-device-redir-widget.c
>> > @@ -0,0 +1,2065 @@
>> > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
>> > +/*
>> > +   Copyright (C) 2012 Red Hat, Inc.
>> > +
>> > +   Red Hat Authors:
>> > +   Alexander Nezhinsky<anezhins@redhat.com>
>> > +
>> > +   This library is free software; you can redistribute it and/or
>> > +   modify it under the terms of the GNU Lesser General Public
>> > +   License as published by the Free Software Foundation; either
>> > +   version 2.1 of the License, or (at your option) any later version.
>> > +
>> > +   This library 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
>> > +   Lesser General Public License for more details.
>> > +
>> > +   You should have received a copy of the GNU Lesser General Public
>> > +   License along with this library; if not, see <http://www.gnu.org/licenses/>.
>> > +*/
>> > +
>> > +#include "config.h"
>> > +#ifndef USB_WIDGET_TEST
>> > +    #include <glib/gi18n-lib.h>
>> > +    #include "spice-client.h"
>> > +    #include "spice-marshal.h"
>> > +#else
>> > +    #include "spice-client.h"
>> > +#endif
>> > +#include "usb-device-widget.h"
>> > +
>> > +/*
>> > +    Debugging note:
>> > +    Logging from this module is not affected by --spice-debug
>> > +    command line parameter
>> > +    Use SPICE_DEBUG=1 environment varible to enable logs
>> > +*/
>> > +
>> > +#ifdef USE_NEW_USB_WIDGET
>> > +
>> > +/**
>> > + * SECTION:usb-device-widget
>> > + * @short_description: USB device selection widget
>> > + * @title: Spice USB device selection widget
>> > + * @section_id:
>> > + * @see_also:
>> > + * @stability: Under development
>> > + * @include: spice-client-gtk.h
>> > + *
>> > + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
>> > + * add an UI to select USB devices to redirect (or unredirect).
>> > + */
>> > +
>> > +struct _SpiceUsbDeviceWidget
>> > +{
>> > +    GtkBox parent;
>> > +
>> > +    SpiceUsbDeviceWidgetPrivate *priv;
>> > +};
>> > +
>> > +struct _SpiceUsbDeviceWidgetClass
>> > +{
>> > +    GtkBoxClass parent_class;
>> > +
>> > +    /* signals */
>> > +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
>> > +                            SpiceUsbDevice *device, GError *error);
>> > +};
>> > +
>> > +/* ------------------------------------------------------------------ */
>> > +/* Prototypes for callbacks  */
>> > +static void device_added_cb(SpiceUsbDeviceManager *manager,
>> > +    SpiceUsbDevice *device, gpointer user_data);
>> > +static void device_removed_cb(SpiceUsbDeviceManager *manager,
>> > +    SpiceUsbDevice *device, gpointer user_data);
>> > +static void device_changed_cb(SpiceUsbDeviceManager *manager,
>> > +    SpiceUsbDevice *device, gpointer user_data);
>> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
>> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);
>> > +static gboolean spice_usb_device_widget_update_status(gpointer user_data);
>> > +
>> > +/* ------------------------------------------------------------------ */
>> > +/* gobject glue                                                       */
>> > +
>> > +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
>> > +    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
>> > +                                 SpiceUsbDeviceWidgetPrivate))
>> > +
>> > +enum {
>> > +    PROP_0,
>> > +    PROP_SESSION,
>> > +    PROP_DEVICE_FORMAT_STRING,
>> > +};
>> > +
>> > +enum {
>> > +    CONNECT_FAILED,
>> > +    LAST_SIGNAL,
>> > +};
>> > +
>> > +typedef struct {
>> > +    GtkTreeView *tree_view;
>> > +    GtkTreeStore *tree_store;
>> > +} SpiceUsbDeviceWidgetTree;
>> > +
>> > +struct _SpiceUsbDeviceWidgetPrivate {
>> > +    SpiceSession *session;
>> > +    gchar *device_format_string;
>> > +    SpiceUsbDeviceManager *manager;
>> > +    GtkWidget *info_bar;
>> > +    GtkWidget *label;
>> > +    SpiceUsbDeviceWidgetTree cd_tree;
>> > +    SpiceUsbDeviceWidgetTree usb_tree;
>> > +    GdkPixbuf *icon_cd;
>> > +    GdkPixbuf *icon_connected;
>> > +    GdkPixbuf *icon_disconn;
>> > +    GdkPixbuf *icon_warning;
>> > +    GdkPixbuf *icon_info;
>> > +    gchar *err_msg;
>> > +    gsize device_count;
>> > +};
>> > +
>> > +static guint signals[LAST_SIGNAL] = { 0, };
>> > +
>> > +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
>> > +
>> > +/* TREE */
>> > +
>> > +enum column_id
>> > +{
>> > +    COL_REDIRECT = 0,
>> > +    COL_ADDRESS,
>> > +    COL_CONNECT_ICON,
>> > +    COL_CD_ICON,
>> > +    COL_VENDOR,
>> > +    COL_PRODUCT,
>> > +    COL_FILE,
>> > +    COL_LOADED,
>> > +    COL_LOCKED,
>> > +    COL_IDLE,
>> > +    /* internal columns */
>> > +    COL_REVISION,
>> > +    COL_CD_DEV,
>> > +    COL_LUN_ITEM,
>> > +    COL_DEV_ITEM,
>> > +    COL_ITEM_DATA,
>> > +    COL_CONNECTED,
>> > +    COL_CAN_REDIRECT,
>> > +    COL_ROW_COLOR,
>> > +    COL_ROW_COLOR_SET,
>> > +    NUM_COLS,
>> > +
>> > +    INVALID_COL
>> > +};
>> > +
>> > +// there is a possibility to use different names
>> > +// for columns in USB device list and CD device list, if needed
>> > +// currently they are identical
>> > +static const char *column_names_cd[NUM_COLS];
>> > +static const char *column_names_usb[NUM_COLS];
>> > +
>> > +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
>> > +
>> > +static void initialize_columns(void)
>> > +{
>> > +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
>> > +    SET_COLUMN(COL_ADDRESS, _("Address"));
>> > +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
>> > +    SET_COLUMN(COL_CD_ICON, _("CD"));
>> > +    SET_COLUMN(COL_VENDOR, _("Vendor"));
>> > +    SET_COLUMN(COL_PRODUCT, _("Product"));
>> > +    SET_COLUMN(COL_FILE, _("File/Device Path"));
>> > +    SET_COLUMN(COL_LOADED, _("Loaded"));
>> > +    SET_COLUMN(COL_LOCKED, _("Locked"));
>> > +    SET_COLUMN(COL_IDLE, _("Idle"));
>> > +    SET_COLUMN(COL_REVISION, "?Revision");
>> > +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
>> > +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
>> > +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
>> > +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
>> > +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
>> > +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
>> > +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
>> > +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
>> > +};
>> > +
>> > +static const char *column_name(enum column_id id, gboolean is_cd)
>> > +{
>> > +    const char **col_name = is_cd ? column_names_cd : column_names_usb;
>> > +    return col_name[id];
>> > +}
>> > +
>> > +typedef struct _UsbWidgetLunItem {
>> > +    SpiceUsbDeviceManager *manager;
>> > +    SpiceUsbDevice *device;
>> > +    guint lun;
>> > +    SpiceUsbDeviceLunInfo info;
>> > +} UsbWidgetLunItem;
>> > +
>> > +typedef struct _TreeFindUsbDev {
>> > +    SpiceUsbDevice *usb_dev;
>> > +    GtkTreeIter dev_iter;
>> > +} TreeFindUsbDev;
>> > +
>> > +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *, gpointer);
>> > +
>> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
>> > +                                          SpiceUsbDevice *usb_device,
>> > +                                          GtkTreeIter *old_dev_iter);
>> > +
>> > +static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
>> > +                                     SpiceUsbDevice *device,
>> > +                                     const gchar *format)
>> > +{
>> > +    SpiceUsbDeviceDescription desc;
>> > +    gchar *descriptor;
>> > +    gchar *res;
>> > +    spice_usb_device_get_info(manager, device, &desc);
>> > +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id);
>> > +    if (!format) {
>> > +        format = _("%s %s %s at %d-%d");
>> > +    }
>> > +    res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address);
>> > +    g_free(desc.vendor);
>> > +    g_free(desc.product);
>> > +    g_free(descriptor);
>> > +    return res;
>> > +}
>> > +
>> > +static GtkTreeStore* usb_widget_create_tree_store(void)
>> > +{
>> > +    GtkTreeStore *tree_store;
>> > +
>> > +    tree_store = gtk_tree_store_new(NUM_COLS,
>> > +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
>> > +                        G_TYPE_STRING, /* COL_ADDRESS */
>> > +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
>> > +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
>> > +                        G_TYPE_STRING, /* COL_VENDOR */
>> > +                        G_TYPE_STRING, /* COL_PRODUCT */
>> > +                        G_TYPE_STRING, /* COL_FILE */
>> > +                        G_TYPE_BOOLEAN, /* COL_LOADED */
>> > +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
>> > +                        G_TYPE_BOOLEAN, /* COL_IDLE */
>> > +                        /* internal columns */
>> > +                        G_TYPE_STRING, /* COL_REVISION */
>> > +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
>> > +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
>> > +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
>> > +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
>> > +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
>> > +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
>> > +                        G_TYPE_STRING, /* COL_ROW_COLOR */
>> > +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
>> > +    SPICE_DEBUG("tree store created");
>> > +
>> > +    return tree_store;
>> > +}
>> > +
>> > +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
>> > +{
>> > +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), name, size, 0);
>> > +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
>> > +    g_object_unref (info);
>> > +    return pixbuf;
>> > +}
>> > +
>> > +static void select_widget_size(GtkWidget *wg)
>> > +{
>> > +    GdkDisplay *d = gtk_widget_get_display(wg);
>> > +    int i, w = 2000, h = 1024;
>> > +    int n = gdk_display_get_n_monitors(d);
>> > +    for (i = 0; i < n; ++i)
>> > +    {
>> > +        GdkMonitor *m = gdk_display_get_monitor(d, i);
>> > +        if (m) {
>> > +            GdkRectangle area;
>> > +            gdk_monitor_get_workarea(m, &area);
>> > +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
>> > +                i, area.width, area.height, area.x, area.y );
>> > +            w = MIN(w, area.width);
>> > +            h = MIN(h, area.height);
>> > +        }
>> > +    }
>> > +
>> > +    w = (w * 3) / 4;
>> > +    h = h / 2;
>> > +
>> > +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
>> > +    gtk_widget_set_size_request(wg, w, h);
>> > +}
>> > +
>> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
>> > +                                          SpiceUsbDevice *usb_device,
>> > +                                          GtkTreeIter *old_dev_iter)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
>> > +    SpiceUsbDeviceWidgetTree *tree;
>> > +    GtkTreeView *tree_view;
>> > +    GtkTreeStore *tree_store;
>> > +    GtkTreeIter new_dev_iter;
>> > +    SpiceUsbDeviceDescription dev_info;
>> > +    GtkTreePath *new_dev_path;
>> > +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
>> > +    gchar *addr_str;
>> > +    GArray *lun_array;
>> > +    guint lun_index;
>> > +    GError *error = NULL;
>> > +
>> > +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr, usb_device);
>> > +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
>> > +    tree_view = tree->tree_view;
>> > +    tree_store = tree->tree_store;
>> > +
>> > +    if (old_dev_iter == NULL) {
>> > +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
>> > +    } else {
>> > +        gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL, old_dev_iter);
>> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> > +    }
>> > +
>> > +    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
>> > +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address);
>> > +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr, usb_device);
>> > +    is_dev_redirected = is_dev_connected;
>> > +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
>> > +        addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd);
>> > +
>> > +    gtk_tree_store_set(tree_store, &new_dev_iter,
>> > +        COL_REDIRECT, is_dev_redirected,
>> > +        COL_ADDRESS, addr_str,
>> > +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn,
>> > +        COL_CD_ICON, priv->icon_cd,
>> > +        COL_VENDOR, dev_info.vendor,
>> > +        COL_PRODUCT, dev_info.product,
>> > +        COL_CD_DEV, is_dev_cd,
>> > +        COL_LUN_ITEM, FALSE, /* USB device item */
>> > +        COL_DEV_ITEM, TRUE, /* USB device item */
>> > +        COL_ITEM_DATA, (gpointer)usb_device,
>> > +        COL_CONNECTED, is_dev_connected,
>> > +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr, usb_device, &error),
>> > +        COL_ROW_COLOR, "beige",
>> > +        COL_ROW_COLOR_SET, TRUE,
>> > +        -1);
>> > +    g_clear_error(&error);
>> > +
>> > +    priv->device_count++;
>> > +
>> > +    /* get all the luns */
>> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, usb_device);
>> > +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
>> > +        UsbWidgetLunItem *lun_item;
>> > +        GtkTreeIter lun_iter;
>> > +        gchar lun_str[8];
>> > +
>> > +        lun_item = g_malloc(sizeof(*lun_item));
>> > +        lun_item->manager = usb_dev_mgr;
>> > +        lun_item->device = usb_device;
>> > +        lun_item->lun = g_array_index(lun_array, guint, lun_index);
>> > +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr, usb_device, lun_item->lun, &lun_item->info);
>> > +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",
>> > +                lun_index, lun_item->info.vendor, lun_item->info.product,
>> > +                lun_item->info.revision, lun_item->info.file_path, lun_item);
>> > +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
>> > +
>> > +        /* Append LUN as a child of USB device */
>> > +        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
>> > +        gtk_tree_store_set(tree_store, &lun_iter,
>> > +                COL_ADDRESS, lun_str,
>> > +                COL_VENDOR, lun_item->info.vendor,
>> > +                COL_PRODUCT, lun_item->info.product,
>> > +                COL_REVISION, lun_item->info.revision,
>> > +                COL_FILE, lun_item->info.file_path,
>> > +                COL_LOADED, lun_item->info.loaded,
>> > +                COL_LOCKED, lun_item->info.locked,
>> > +                COL_IDLE, !lun_item->info.started,
>> > +                COL_CD_DEV, FALSE,
>> > +                COL_LUN_ITEM, TRUE, /* LUN item */
>> > +                COL_DEV_ITEM, FALSE, /* LUN item */
>> > +                COL_ITEM_DATA, (gpointer)lun_item,
>> > +                COL_CONNECTED, is_dev_connected,
>> > +                COL_ROW_COLOR, "azure",
>> > +                COL_ROW_COLOR_SET, TRUE,
>> > +                -1);
>> > +    }
>> > +    g_array_unref(lun_array);
>> > +
>> > +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &new_dev_iter);
>> > +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
>> > +    gtk_tree_path_free(new_dev_path);
>> > +
>> > +    g_free(dev_info.vendor);
>> > +    g_free(dev_info.product);
>> > +    g_free(addr_str);
>> > +}
>> > +
>> > +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter)
>> > +{
>> > +    gboolean is_lun;
>> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);
>> > +    return is_lun;
>> > +}
>> > +
>> > +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel *tree_model,
>> > +                                                              GtkTreePath *path, GtkTreeIter *iter,
>> > +                                                              gpointer user_data)
>> > +{
>> > +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
>> > +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
>> > +    SpiceUsbDevice *usb_device;
>> > +    gboolean is_lun_item;
>> > +
>> > +    gtk_tree_model_get(tree_model, iter,
>> > +                       COL_LUN_ITEM, &is_lun_item,
>> > +                       COL_ITEM_DATA, (gpointer *)&usb_device,
>> > +                       -1);
>> > +    if (!is_lun_item && usb_device == find_usb_device) {
>> > +        find_info->dev_iter = *iter;
>> > +        find_info->usb_dev  = NULL;
>> > +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
>> > +        return TRUE; /* stop iterating */
>> > +    } else {
>> > +        return FALSE; /* continue iterating */
>> > +    }
>> > +}
>> > +
>> > +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore *tree_store,
>> > +                                                          SpiceUsbDevice *usb_device)
>> > +{
>> > +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };
>> > +    GtkTreeIter *iter = NULL;
>> > +
>> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
>> > +                           usb_widget_tree_store_find_usb_dev_foreach_cb, (gpointer)&find_info);
>> > +    // the callback sets 'usb_dev' field to zero if it finds the device
>> > +    if (!find_info.usb_dev) {
>> > +        iter = g_malloc(sizeof(*iter));
>> > +        *iter = find_info.dev_iter;
>> > +    }
>> > +    return iter;
>> > +}
>> > +
>> > +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
>> > +                                         SpiceUsbDevice *usb_device)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeIter *old_dev_iter;
>> > +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
>> > +    // on WIN32 it is possible the backend device is already removed
>> > +    // from the USB device manager list and we can't know it was
>> > +    // USB or CD, do we will try both lists
>> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> > +    if (old_dev_iter != NULL) {
>> > +        SPICE_DEBUG("USB Device removed");
>> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> > +        priv->device_count--;
>> > +        g_free(old_dev_iter);
>> > +        return TRUE;
>> > +    }
>> > +    tree_store = priv->cd_tree.tree_store;
>> > +    if (tree_store) {
>> > +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> > +    }
>> > +    if (old_dev_iter != NULL) {
>> > +        SPICE_DEBUG("CD Device removed");
>> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> > +        priv->device_count--;
>> > +        g_free(old_dev_iter);
>> > +        return TRUE;
>> > +    }
>> > +    SPICE_DEBUG("Device %p not found!", usb_device);
>> > +    return FALSE;
>> > +}
>> > +
>> > +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget *self,
>> > +                                                 enum column_id toggle_col_id,
>> > +                                                 enum column_id visible_col_id,
>> > +                                                 enum column_id sensitive_col_id,
>> > +                                                 tree_item_toggled_cb toggled_cb,
>> > +                                                 gboolean is_cd)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkCellRenderer     *renderer;
>> > +    GtkTreeViewColumn   *view_col;
>> > +    const char *col_name = column_name(toggle_col_id, is_cd);
>> > +
>> > +    renderer = gtk_cell_renderer_toggle_new();
>> > +
>> > +    if (sensitive_col_id != INVALID_COL) {
>> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> > +                        col_name,
>> > +                        renderer,
>> > +                        "active", toggle_col_id,
>> > +                        "visible", visible_col_id,
>> > +                        "activatable", sensitive_col_id,
>> > +                        NULL);
>> > +    } else {
>> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> > +                        col_name,
>> > +                        renderer,
>> > +                        "active", toggle_col_id,
>> > +                        "visible", visible_col_id,
>> > +                        NULL);
>> > +    }
>> > +
>> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
>> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
>> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
>> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> > +
>> > +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);
>> > +
>> > +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]",
>> > +            toggle_col_id, col_name,
>> > +            visible_col_id, column_name(visible_col_id, is_cd));
>> > +    return view_col;
>> > +}
>> > +
>> > +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget *self,
>> > +                                                           enum column_id toggle_col_id,
>> > +                                                           enum column_id visible_col_id,
>> > +                                                           gboolean is_cd)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkCellRenderer     *renderer;
>> > +    GtkTreeViewColumn   *view_col;
>> > +    const char *col_name = column_name(toggle_col_id, is_cd);
>> > +
>> > +    renderer = gtk_cell_renderer_toggle_new();
>> > +
>> > +    view_col = gtk_tree_view_column_new_with_attributes(
>> > +                    col_name,
>> > +                    renderer,
>> > +                    "active", toggle_col_id,
>> > +                    "visible", visible_col_id,
>> > +                    NULL);
>> > +
>> > +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
>> > +
>> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
>> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
>> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
>> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> > +
>> > +    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]",
>> > +            toggle_col_id, col_name,
>> > +            visible_col_id, column_name(visible_col_id, is_cd));
>> > +    return view_col;
>> > +}
>> > +
>> > +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget *self,
>> > +                                               enum column_id col_id,
>> > +                                               gboolean expandable,
>> > +                                               gboolean is_cd)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkCellRenderer     *renderer;
>> > +    GtkTreeViewColumn   *view_col;
>> > +
>> > +    renderer = gtk_cell_renderer_text_new();
>> > +
>> > +    view_col = gtk_tree_view_column_new_with_attributes(
>> > +                    column_name(col_id, is_cd),
>> > +                    renderer,
>> > +                    "text", col_id,
>> > +                    //"cell-background", COL_ROW_COLOR,
>> > +                    //"cell-background-set", COL_ROW_COLOR_SET,
>> > +                    NULL);
>> > +
>> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
>> > +    gtk_tree_view_column_set_resizable(view_col, TRUE);
>> > +    gtk_tree_view_column_set_expand(view_col, expandable);
>> > +
>> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> > +
>> > +    SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd));
>> > +    return view_col;
>> > +}
>> > +
>> > +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget *self,
>> > +                                                 enum column_id col_id,
>> > +                                                 enum column_id visible_col_id,
>> > +                                                 gboolean is_cd)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkCellRenderer     *renderer;
>> > +    GtkTreeViewColumn   *view_col;
>> > +    const char *col_name = column_name(col_id, is_cd);
>> > +
>> > +    renderer = gtk_cell_renderer_pixbuf_new();
>> > +
>> > +    if (visible_col_id == INVALID_COL) {
>> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> > +                        col_name,
>> > +                        renderer,
>> > +                        "pixbuf", col_id,
>> > +                        NULL);
>> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name);
>> > +    } else {
>> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> > +                        col_name,
>> > +                        renderer,
>> > +                        "pixbuf", col_id,
>> > +                        "visible", visible_col_id,
>> > +                        NULL);
>> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]",
>> > +                col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd));
>> > +    }
>> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> > +    return view_col;
>> > +}
>> > +
>> > +/* Toggle handlers */
>> > +
>> > +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
>> > +{
>> > +    gboolean toggle_val;
>> > +
>> > +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store), iter, path_str);
>> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id, &toggle_val, -1);
>> > +
>> > +    return toggle_val;
>> > +}
>> > +
>> > +static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
>> > +{
>> > +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
>> > +}
>> > +
>> > +typedef struct _connect_cb_data {
>> > +    SpiceUsbDeviceWidget *self;
>> > +    SpiceUsbDevice *usb_dev;
>> > +} connect_cb_data;
>> > +
>> > +static void connect_cb_data_free(connect_cb_data *user_data)
>> > +{
>> > +    spice_usb_device_widget_update_status(user_data->self);
>> > +    g_object_unref(user_data->self);
>> > +    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
>> > +    g_free(user_data);
>> > +}
>> > +
>> > +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
>> > +{
>> > +    connect_cb_data *cb_data = user_data;
>> > +    SpiceUsbDeviceWidget *self = cb_data->self;
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
>> > +    GError *err = NULL;
>> > +    GtkTreeIter *dev_iter;
>> > +    gchar *desc;
>> > +    gboolean finished;
>> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
>> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> > +
>> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
>> > +    if (!dev_iter) {
>> > +        return;
>> > +    }
>> > +
>> > +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
>> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
>> > +
>> > +    finished = spice_usb_device_manager_connect_device_finish(priv->manager, res, &err);
>> > +    if (finished) {
>> > +        gtk_tree_store_set(tree_store, dev_iter,
>> > +                           COL_CONNECT_ICON, priv->icon_connected,
>> > +                           COL_CONNECTED, TRUE,
>> > +                           -1);
>> > +    } else {
>> > +        gtk_tree_store_set(tree_store, dev_iter,
>> > +                           COL_REDIRECT, FALSE,
>> > +                           -1);
>> > +        g_prefix_error(&err, "Device connect failed %s: ", desc);
>> > +        if (err) {
>> > +            SPICE_DEBUG("%s", err->message);
>> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err);
>> > +            g_error_free(err);
>> > +        } else {
>> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL);
>> > +        }
>> > +
>> > +        /* don't trigger a disconnect if connect failed */
>> > +        /*
>> > +        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data->check),
>> > +                                        checkbox_clicked_cb, self);
>> > +        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check), FALSE);
>> > +        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_data->check),
>> > +                                        checkbox_clicked_cb, self);
>> > +        */
>> > +    }
>> > +    g_free(desc);
>> > +    g_free(dev_iter);
>> > +    connect_cb_data_free(user_data);
>> > +}
>> > +
>> > +static void usb_widget_disconnect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
>> > +{
>> > +    connect_cb_data *cb_data = user_data;
>> > +    SpiceUsbDeviceWidget *self = cb_data->self;
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
>> > +    GError *err = NULL;
>> > +    GtkTreeIter *dev_iter;
>> > +    gchar *desc;
>> > +    gboolean finished;
>> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
>> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> > +
>> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
>> > +    if (!dev_iter) {
>> > +        return;
>> > +    }
>> > +
>> > +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
>> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
>> > +
>> > +    finished = spice_usb_device_manager_disconnect_device_finish(priv->manager, res, &err);
>> > +    if (finished) {
>> > +        gtk_tree_store_set(tree_store, dev_iter,
>> > +                           COL_CONNECT_ICON, priv->icon_disconn,
>> > +                           COL_CONNECTED, FALSE,
>> > +                           -1);
>> > +    } else {
>> > +        gtk_tree_store_set(tree_store, dev_iter,
>> > +                           COL_REDIRECT, TRUE,
>> > +                           -1);
>> > +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
>> > +        if (err) {
>> > +            SPICE_DEBUG("%s", err->message);
>> > +            g_error_free(err);
>> > +        }
>> > +    }
>> > +    g_free(desc);
>> > +    g_free(dev_iter);
>> > +    connect_cb_data_free(user_data);
>> > +}
>> > +
>> > +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell,
>> > +                                          gchar *path_str,
>> > +                                          SpiceUsbDeviceWidget *self,
>> > +                                          GtkTreeStore *tree_store)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
>> > +    SpiceUsbDevice *usb_dev;
>> > +    GtkTreeIter iter;
>> > +    gboolean new_redirect_val;
>> > +
>> > +    new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str, &iter, COL_REDIRECT);
>> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
>> > +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT, new_redirect_val);
>> > +
>> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
>> > +    cb_data->self = g_object_ref(self);
>> > +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(), usb_dev);
>> > +
>> > +    if (new_redirect_val) {
>> > +        spice_usb_device_manager_connect_device_async(priv->manager, usb_dev,
>> > +                                                      NULL, /* cancellable */
>> > +                                                      usb_widget_connect_cb, cb_data);
>> > +    } else {
>> > +        spice_usb_device_manager_disconnect_device_async(priv->manager, usb_dev,
>> > +                                                         NULL, /* cancellable */
>> > +                                                         usb_widget_disconnect_cb, cb_data);
>> > +
>> > +    }
>> > +    spice_usb_device_widget_update_status(self);
>> > +}
>> > +
>> > +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle *cell,
>> > +                                             gchar *path_str,
>> > +                                             gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->cd_tree.tree_store);
>> > +}
>> > +
>> > +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle *cell,
>> > +                                             gchar *path_str,
>> > +                                             gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->usb_tree.tree_store);
>> > +}
>> > +
>> > +/* Signal handlers */
>> > +
>> > +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +
>> > +    SPICE_DEBUG("Signal: Device Added");
>> > +
>> > +    usb_widget_add_device(self, usb_device, NULL);
>> > +
>> > +    spice_usb_device_widget_update_status(self);
>> > +}
>> > +
>> > +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    gboolean dev_removed;
>> > +
>> > +    SPICE_DEBUG("Signal: Device Removed");
>> > +
>> > +    dev_removed = usb_widget_remove_device(self, usb_device);
>> > +    if (dev_removed) {
>> > +        spice_usb_device_widget_update_status(self);
>> > +    }
>> > +}
>> > +
>> > +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeIter *old_dev_iter;
>> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
>> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> > +
>> > +    SPICE_DEBUG("Signal: Device Changed");
>> > +
>> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> > +    if (old_dev_iter != NULL) {
>> > +
>> > +        usb_widget_add_device(self, usb_device, old_dev_iter);
>> > +
>> > +        spice_usb_device_widget_update_status(self);
>> > +        g_free(old_dev_iter);
>> > +    } else {
>> > +        SPICE_DEBUG("Device not found!");
>> > +    }
>> > +}
>> > +
>> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
>> > +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeIter *dev_iter;
>> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
>> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> > +
>> > +    SPICE_DEBUG("Signal: Device Error");
>> > +
>> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> > +    if (dev_iter != NULL) {
>> > +        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT, FALSE);
>> > +        spice_usb_device_widget_update_status(self);
>> > +        g_free(dev_iter);
>> > +    } else {
>> > +        SPICE_DEBUG("Device not found!");
>> > +    }
>> > +}
>> > +
>> > +/* Selection handler */
>> > +
>> > +static void tree_selection_changed_cb(GtkTreeSelection *select, gpointer user_data)
>> > +{
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +    GtkTreePath *path;
>> > +    gboolean is_lun;
>> > +    UsbWidgetLunItem *lun_item;
>> > +    gchar *txt[3];
>> > +
>> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        gtk_tree_model_get(tree_model, &iter,
>> > +                COL_VENDOR, &txt[0],
>> > +                COL_PRODUCT, &txt[1],
>> > +                COL_REVISION, &txt[2],
>> > +                COL_LUN_ITEM, &is_lun,
>> > +                COL_ITEM_DATA, (gpointer *)&lun_item,
>> > +                -1);
>> > +        path = gtk_tree_model_get_path(tree_model, &iter);
>> > +
>> > +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
>> > +                txt[0], txt[1],
>> > +                is_lun ? txt[2] : "--",
>> > +                is_lun ? "LUN" : "USB-DEV",
>> > +                is_lun ? lun_item->info.file_path : "--",
>> > +                gtk_tree_path_to_string(path));
>> > +
>> > +        if (txt[0]) {
>> > +            g_free(txt[0]);
>> > +        }
>> > +        if (txt[1]) {
>> > +            g_free(txt[1]);
>> > +        }
>> > +        if (txt[2]) {
>> > +            g_free(txt[2]);
>> > +        }
>> > +        gtk_tree_path_free(path);
>> > +    }
>> > +}
>> > +
>> > +static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view)
>> > +{
>> > +    GtkTreeSelection *select;
>> > +
>> > +    select = gtk_tree_view_get_selection(tree_view);
>> > +    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
>> > +
>> > +    g_signal_connect(G_OBJECT(select), "changed",
>> > +                     G_CALLBACK(tree_selection_changed_cb),
>> > +                     NULL);
>> > +
>> > +    SPICE_DEBUG("selection handler set");
>> > +    return select;
>> > +}
>> > +
>> > +static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent)
>> > +{
>> > +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
>> > +    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
>> > +    GtkWidget *label = gtk_accel_label_new(label_str);
>> > +    GtkAccelGroup *accel_group = gtk_accel_group_new();
>> > +    guint accel_key;
>> > +
>> > +    /* add icon */
>> > +    gtk_container_add(GTK_CONTAINER(box), icon);
>> > +
>> > +    /* add label */
>> > +    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
>> > +    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
>> > +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);
>> > +    gtk_widget_add_accelerator(parent, "activate", accel_group, accel_key,
>> > +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
>> > +    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent);
>> > +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
>> > +
>> > +    /* add the new box to the parent widget */
>> > +    gtk_container_add(GTK_CONTAINER(parent), box);
>> > +
>> > +    return box;
>> > +}
>> > +
>> > +/* LUN properties dialog */
>> > +
>> > +typedef struct _lun_properties_dialog {
>> > +    GtkWidget *dialog;
>> > +    GtkWidget *advanced_grid;
>> > +    gboolean advanced_shown;
>> > +
>> > +    GtkWidget *file_entry;
>> > +    GtkWidget *vendor_entry;
>> > +    GtkWidget *product_entry;
>> > +    GtkWidget *revision_entry;
>> > +    GtkWidget *loaded_switch;
>> > +    GtkWidget *locked_switch;
>> > +} lun_properties_dialog;
>> > +
>> > +#if 1
>> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
>> > +{
>> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
>> > +    GtkFileChooserNative *native;
>> > +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
>> > +    gint res;
>> > +
>> > +    native = gtk_file_chooser_native_new("Choose File for USB CD",
>> > +        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
>> > +        action,
>> > +        "_Open",
>> > +        "_Cancel");
>> > +
>> > +    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
>> > +    if (res == GTK_RESPONSE_ACCEPT) {
>> > +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native));
>> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
>> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
>> > +        g_free(filename);
>> > +    }
>> > +    else {
>> > +        gtk_widget_grab_focus(button);
>> > +    }
>> > +
>> > +    g_object_unref(native);
>> > +}
>> > +#else
>> > +// to be removed
>> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
>> > +{
>> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
>> > +    GtkWidget *dialog;
>> > +    gint res;
>> > +
>> > +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
>> > +                                          GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
>> > +                                          GTK_FILE_CHOOSER_ACTION_OPEN,
>> > +                                          "_Cancel",
>> > +                                          GTK_RESPONSE_CANCEL,
>> > +                                          "_Ok",
>> > +                                          GTK_RESPONSE_ACCEPT,
>> > +                                          NULL);
>> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
>> > +
>> > +    res = gtk_dialog_run(GTK_DIALOG(dialog));
>> > +    if (res == GTK_RESPONSE_ACCEPT) {
>> > +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
>> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
>> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
>> > +        g_free(filename);
>> > +    }
>> > +    gtk_widget_destroy(dialog);
>> > +}
>> > +#endif
>> > +
>> > +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget *widget,
>> > +                                                       gboolean state, gpointer user_data)
>> > +{
>> > +    lun_properties_dialog *lun_dialog = user_data;
>> > +
>> > +    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
>> > +    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
>> > +
>> > +    return FALSE; /* call default signal handler */
>> > +}
>> > +
>> > +static void lun_properties_dialog_toggle_advanced(GtkWidget *widget, gpointer user_data)
>> > +{
>> > +    lun_properties_dialog *lun_dialog = user_data;
>> > +
>> > +    if (lun_dialog->advanced_shown) {
>> > +        gtk_widget_hide(lun_dialog->advanced_grid);
>> > +        lun_dialog->advanced_shown = FALSE;
>> > +    } else {
>> > +        gtk_widget_show_all(lun_dialog->advanced_grid);
>> > +        lun_dialog->advanced_shown = TRUE;
>> > +    }
>> > +}
>> > +
>> > +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
>> > +                                         GtkWidget *parent_window,
>> > +                                         SpiceUsbDeviceLunInfo *lun_info,
>> > +                                         lun_properties_dialog *lun_dialog)
>> > +{
>> > +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkWidget *dialog, *content_area;
>> > +    GtkWidget *grid, *advanced_grid;
>> > +    GtkWidget *file_entry, *choose_button;
>> > +    GtkWidget *advanced_button, *advanced_icon;
>> > +    GtkWidget *vendor_entry, *product_entry, *revision_entry;
>> > +    GtkWidget *loaded_switch, *loaded_label;
>> > +    GtkWidget *locked_switch, *locked_label;
>> > +    gint nrow = 0;
>> > +
>> > +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings",
>> > +                    GTK_WINDOW(parent_window),
>> > +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, /* flags */
>> > +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
>> > +                    "Cancel", GTK_RESPONSE_REJECT,
>> > +                    NULL);
>> > +
>> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
>> > +    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
>> > +    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
>> > +
>> > +    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
>> > +
>> > +    /* main grid - always visible */
>> > +    grid = gtk_grid_new();
>> > +    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
>> > +    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
>> > +    gtk_container_add(GTK_CONTAINER(content_area), grid);
>> > +
>> > +    /* File path label */
>> > +    gtk_grid_attach(GTK_GRID(grid),
>> > +            gtk_label_new("Select file or device"),
>> > +            0, nrow++, // left top
>> > +            7, 1); // width height
>> > +
>> > +    /* file/device path entry */
>> > +    file_entry = gtk_entry_new();
>> > +    gtk_widget_set_hexpand(file_entry, TRUE);
>> > +    if (!lun_info) {
>> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry), "file-path");
>> > +    } else {
>> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path);
>> > +        if (lun_info->loaded) {
>> > +            gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE);
>> > +            gtk_widget_set_can_focus(file_entry, FALSE);
>> > +        }
>> > +    }
>> > +    gtk_grid_attach(GTK_GRID(grid),
>> > +            file_entry,
>> > +            0, nrow, // left top
>> > +            6, 1); // width height
>> > +
>> > +    /* choose button */
>> > +    choose_button = gtk_button_new_with_mnemonic("_Choose File");
>> > +    gtk_widget_set_hexpand(choose_button, FALSE);
>> > +    g_signal_connect(GTK_BUTTON(choose_button),
>> > +                     "clicked", G_CALLBACK(usb_cd_choose_file), file_entry);
>> > +    if (lun_info && lun_info->loaded) {
>> > +        gtk_widget_set_sensitive(choose_button, FALSE);
>> > +        gtk_widget_set_can_focus(choose_button, FALSE);
>> > +    }
>> > +
>> > +    gtk_grid_attach(GTK_GRID(grid),
>> > +            choose_button,
>> > +            6, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    /* advanced button */
>> > +    advanced_button = gtk_button_new_with_label("Advanced");
>> > +    gtk_button_set_relief(GTK_BUTTON(advanced_button), GTK_RELIEF_NONE);
>> > +    advanced_icon = gtk_image_new_from_icon_name("preferences-system", GTK_ICON_SIZE_BUTTON);
>> > +    gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon);
>> > +    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button), TRUE);
>> > +    g_signal_connect(advanced_button, "clicked", G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
>> > +
>> > +    gtk_grid_attach(GTK_GRID(grid),
>> > +            advanced_button,
>> > +            0, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    /* advanced grid */
>> > +    advanced_grid = gtk_grid_new();
>> > +    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
>> > +    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
>> > +    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
>> > +
>> > +    /* horizontal separator */
>> > +    gtk_container_add(GTK_CONTAINER(content_area), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
>> > +
>> > +    /* pack advanced grid */
>> > +    nrow = 0;
>> > +
>> > +    /* horizontal separator */
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
>> > +            0, nrow++, // left top
>> > +            7, 1); // width height
>> > +
>> > +    /* product id labels */
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            gtk_label_new("Vendor"),
>> > +            0, nrow, // left top
>> > +            2, 1); // width height
>> > +
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            gtk_label_new("Product"),
>> > +            2, nrow, // left top
>> > +            4, 1); // width height
>> > +
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            gtk_label_new("Revision"),
>> > +            6, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    /* vendor entry */
>> > +    vendor_entry = gtk_entry_new();
>> > +    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
>> > +    if (lun_info) {
>> > +        gtk_widget_set_sensitive(vendor_entry, FALSE);
>> > +        gtk_widget_set_can_focus(vendor_entry, FALSE);
>> > +        gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor);
>> > +    } else {
>> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry), "auto");
>> > +    }
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            vendor_entry,
>> > +            0, nrow, // left top
>> > +            2, 1); // width height
>> > +
>> > +    /* product entry */
>> > +    product_entry = gtk_entry_new();
>> > +    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
>> > +    if (lun_info) {
>> > +        gtk_widget_set_sensitive(product_entry, FALSE);
>> > +        gtk_widget_set_can_focus(product_entry, FALSE);
>> > +        gtk_entry_set_text(GTK_ENTRY(product_entry), lun_info->product);
>> > +    } else {
>> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry), "auto");
>> > +    }
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            product_entry,
>> > +            2, nrow, // left top
>> > +            4, 1); // width height
>> > +
>> > +    /* revision entry */
>> > +    revision_entry = gtk_entry_new();
>> > +    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
>> > +    if (lun_info) {
>> > +        gtk_widget_set_sensitive(revision_entry, FALSE);
>> > +        gtk_widget_set_can_focus(revision_entry, FALSE);
>> > +        gtk_entry_set_text(GTK_ENTRY(revision_entry), lun_info->revision);
>> > +    } else {
>> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry), "auto");
>> > +    }
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            revision_entry,
>> > +            6, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    /* horizontal separator */
>> > +    if (!lun_info) {
>> > +        gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
>> > +                0, nrow++, // left top
>> > +                7, 1); // width height
>> > +    }
>> > +
>> > +    /* initially loaded switch */
>> > +    loaded_label = gtk_label_new("Initially loaded:");
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +        loaded_label,
>> > +        0, nrow, // left top
>> > +        2, 1); // width height
>> > +
>> > +    loaded_switch = gtk_switch_new();
>> > +    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
>> > +    if (lun_info) {
>> > +        gtk_widget_set_child_visible(loaded_switch, FALSE);
>> > +        gtk_widget_set_child_visible(loaded_label, FALSE);
>> > +    } else {
>> > +        g_signal_connect(loaded_switch, "state-set",
>> > +                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb), lun_dialog);
>> > +    }
>> > +    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            loaded_switch,
>> > +            2, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    /* initially locked switch */
>> > +    locked_label = gtk_label_new("Initially locked:");
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +        locked_label,
>> > +        0, nrow, // left top
>> > +        2, 1); // width height
>> > +
>> > +    locked_switch = gtk_switch_new();
>> > +    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
>> > +    gtk_widget_set_hexpand(locked_switch, FALSE);
>> > +    if (lun_info) {
>> > +        gtk_widget_set_child_visible(locked_switch, FALSE);
>> > +        gtk_widget_set_child_visible(locked_label, FALSE);
>> > +    }
>> > +    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
>> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> > +            locked_switch,
>> > +            2, nrow++, // left top
>> > +            1, 1); // width height
>> > +
>> > +    lun_dialog->dialog = dialog;
>> > +    lun_dialog->advanced_grid = advanced_grid;
>> > +    lun_dialog->advanced_shown = FALSE;
>> > +    lun_dialog->file_entry = file_entry;
>> > +    lun_dialog->vendor_entry = vendor_entry;
>> > +    lun_dialog->product_entry = product_entry;
>> > +    lun_dialog->revision_entry = revision_entry;
>> > +    lun_dialog->loaded_switch = loaded_switch;
>> > +    lun_dialog->locked_switch = locked_switch;
>> > +
>> > +    gtk_widget_show_all(dialog);
>> > +    gtk_widget_hide(advanced_grid);
>> > +}
>> > +
>> > +static void lun_properties_dialog_get_info(lun_properties_dialog *lun_dialog,
>> > +                                            SpiceUsbDeviceLunInfo *lun_info)
>> > +{
>> > +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(lun_dialog->file_entry));
>> > +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(lun_dialog->vendor_entry));
>> > +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(lun_dialog->product_entry));
>> > +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(lun_dialog->revision_entry));
>> > +    lun_info->loaded = gtk_switch_get_active(GTK_SWITCH(lun_dialog->loaded_switch));
>> > +    lun_info->locked = gtk_switch_get_active(GTK_SWITCH(lun_dialog->locked_switch));
>> > +}
>> > +
>> > +/* Popup menu */
>> > +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +
>> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> > +            SpiceUsbDevice *usb_device;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> > +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
>> > +        }
>> > +        else {
>> > +            UsbWidgetLunItem *lun_item;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> > +            spice_usb_device_manager_device_lun_load(
>> > +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded);
>> > +        }
>> > +    }
>> > +    else {
>> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
>> > +    }
>> > +}
>> > +
>> > +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +
>> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> > +            SpiceUsbDevice *usb_device;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> > +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
>> > +        }
>> > +        else {
>> > +            UsbWidgetLunItem *lun_item;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> > +            spice_usb_device_manager_device_lun_lock(
>> > +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked);
>> > +        }
>> > +    }
>> > +    else {
>> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
>> > +    }
>> > +}
>> > +
>> > +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +
>> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> > +            SpiceUsbDevice *usb_device;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> > +            SPICE_DEBUG("Remove USB device");
>> > +        } else {
>> > +            UsbWidgetLunItem *lun_item;
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> > +            gtk_tree_selection_unselect_all(select);
>> > +            spice_usb_device_manager_device_lun_remove(lun_item->manager, lun_item->device, lun_item->lun);
>> > +        }
>> > +    } else {
>> > +        SPICE_DEBUG("Remove - failed to get selection");
>> > +    }
>> > +}
>> > +
>> > +static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +
>> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> > +            SPICE_DEBUG("No settings for USB device yet");
>> > +        } else {
>> > +            lun_properties_dialog lun_dialog;
>> > +            UsbWidgetLunItem *lun_item;
>> > +            gint resp;
>> > +
>> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> > +            gtk_tree_selection_unselect_all(select);
>> > +            create_lun_properties_dialog(self, NULL, &lun_item->info, &lun_dialog);
>> > +
>> > +            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
>> > +            if (resp == GTK_RESPONSE_ACCEPT) {
>> > +                SpiceUsbDeviceLunInfo lun_info;
>> > +                SPICE_DEBUG("response is ACCEPT");
>> > +                lun_properties_dialog_get_info(&lun_dialog, &lun_info);
>> > +                spice_usb_device_manager_device_lun_change_media(
>> > +                    priv->manager, lun_item->device, lun_item->lun, &lun_info);
>> > +            } else {
>> > +                SPICE_DEBUG("response is REJECT");
>> > +            }
>> > +            gtk_widget_destroy(lun_dialog.dialog);
>> > +        }
>> > +    } else {
>> > +        SPICE_DEBUG("Remove - failed to get selection");
>> > +    }
>> > +}
>> > +
>> > +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
>> > +    const gchar *label_str,
>> > +    const gchar *icon_name,
>> > +    GCallback cb_func, gpointer user_data)
>> > +{
>> > +    GtkWidget *menu_item = gtk_menu_item_new();
>> > +    create_image_button_box(label_str, icon_name, menu_item);
>> > +    g_signal_connect(menu_item, "activate", cb_func, user_data);
>> > +
>> > +    gtk_widget_show_all(menu_item);
>> > +    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
>> > +
>> > +    return menu_item;
>> > +}
>> > +
>> > +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device)
>> > +{
>> > +    gboolean result;
>> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
>> > +    GArray *lun_array;
>> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, device);
>> > +    result = lun_array && lun_array->len <= 1;
>> > +    if (lun_array) {
>> > +        g_array_unref(lun_array);
>> > +    }
>> > +    return result;
>> > +}
>> > +
>> > +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeSelection *select;
>> > +    GtkTreeModel *tree_model;
>> > +    GtkTreeIter iter;
>> > +    UsbWidgetLunItem *lun_item;
>> > +    gboolean is_loaded, is_locked;
>> > +    GtkTreeIter *usb_dev_iter;
>> > +    gboolean is_dev_connected;
>> > +    GtkWidget *menu;
>> > +
>> > +    if (tree_view != priv->cd_tree.tree_view) {
>> > +        SPICE_DEBUG("Not applicable for USB device");
>> > +        return;
>> > +    }
>> > +
>> > +    select = gtk_tree_view_get_selection(tree_view);
>> > +
>> > +    if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> > +        SPICE_DEBUG("No tree view row is selected");
>> > +        return;
>> > +    }
>> > +    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> > +        SPICE_DEBUG("No settings for USB device yet");
>> > +        return;
>> > +    }
>> > +
>> > +    gtk_tree_model_get(tree_model, &iter,
>> > +                       COL_ITEM_DATA, (gpointer *)&lun_item,
>> > +                       COL_LOADED, &is_loaded,
>> > +                       COL_LOCKED, &is_locked,
>> > +                       -1);
>> > +
>> > +    usb_dev_iter = usb_widget_tree_store_find_usb_device(priv->cd_tree.tree_store, lun_item->device);
>> > +    if (usb_dev_iter != NULL) {
>> > +        gtk_tree_model_get(tree_model, usb_dev_iter,
>> > +                           COL_CONNECTED, &is_dev_connected,
>> > +                           -1);
>> > +        g_free(usb_dev_iter);
>> > +    } else {
>> > +        is_dev_connected = FALSE;
>> > +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
>> > +                    lun_item->info.vendor, lun_item->info.product);
>> > +    }
>> > +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d",
>> > +                lun_item->info.vendor, lun_item->info.product,
>> > +                is_dev_connected, is_loaded, is_locked);
>> > +
>> > +    /* Set up the menu */
>> > +    menu = gtk_menu_new();
>> > +
>> > +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
>> > +                             G_CALLBACK(view_popup_menu_on_settings), user_data);
>> > +    if (is_loaded) {
>> > +        if (!is_locked) {
>> > +            view_popup_add_menu_item(menu, "_Lock", "system-lock-screen",
>> > +                                    G_CALLBACK(view_popup_menu_on_lock), user_data);
>> > +            view_popup_add_menu_item(menu, "_Eject", "media-eject",
>> > +                                     G_CALLBACK(view_popup_menu_on_eject), user_data);
>> > +        } else {
>> > +            view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen",
>> > +                                     G_CALLBACK(view_popup_menu_on_lock), user_data);
>> > +        }
>> > +    } else {
>> > +        view_popup_add_menu_item(menu, "_Load", "media-eject",
>> > +                                 G_CALLBACK(view_popup_menu_on_eject), user_data);
>> > +    }
>> > +
>> > +    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {
>> > +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
>> > +                                 G_CALLBACK(view_popup_menu_on_remove), user_data);
>> > +    }
>> > +
>> > +    gtk_widget_show_all(menu);
>> > +    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
>> > +}
>> > +
>> > +static void treeview_select_current_row_by_pos(GtkTreeView *tree_view, gint x, gint y)
>> > +{
>> > +    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
>> > +    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
>> > +        GtkTreePath *path;
>> > +        /* Get tree path for row that was clicked */
>> > +        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))
>> > +        {
>> > +            gtk_tree_selection_unselect_all(selection);
>> > +            gtk_tree_selection_select_path(selection, path);
>> > +            gtk_tree_path_free(path);
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data)
>> > +{
>> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
>> > +    /* single click with the right mouse button */
>> > +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
>> > +        /* select the row that was clicked, it will also provide the context */
>> > +        treeview_select_current_row_by_pos(tree_view, (gint)event->x, (gint)event->y);
>> > +        view_popup_menu(tree_view, event, user_data);
>> > +        return TRUE; /* we handled this */
>> > +    } else {
>> > +        return FALSE; /* we did not handle this */
>> > +    }
>> > +}
>> > +
>> > +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view, gpointer user_data)
>> > +{
>> > +    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
>> > +    return TRUE; /* we handled this */
>> > +}
>> > +
>> > +/* Add LUN dialog */
>> > +
>> > +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button, gpointer user_data)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button);
>> > +    lun_properties_dialog lun_dialog;
>> > +    gint resp;
>> > +
>> > +    create_lun_properties_dialog(self, parent_window, NULL, &lun_dialog);
>> > +
>> > +    resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
>> > +    if (resp == GTK_RESPONSE_ACCEPT) {
>> > +        SpiceUsbDeviceLunInfo lun_info;
>> > +        SPICE_DEBUG("response is ACCEPT");
>> > +        lun_properties_dialog_get_info(&lun_dialog, &lun_info);
>> > +        spice_usb_device_manager_add_cd_lun(priv->manager, &lun_info);
>> > +    } else {
>> > +        SPICE_DEBUG("response is REJECT");
>> > +    }
>> > +    gtk_widget_destroy(lun_dialog.dialog);
>> > +}
>> > +
>> > +static void spice_usb_device_widget_get_property(GObject     *gobject,
>> > +                                                 guint        prop_id,
>> > +                                                 GValue      *value,
>> > +                                                 GParamSpec  *pspec)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +
>> > +    switch (prop_id) {
>> > +    case PROP_SESSION:
>> > +        g_value_set_object(value, priv->session);
>> > +        break;
>> > +    case PROP_DEVICE_FORMAT_STRING:
>> > +        g_value_set_string(value, priv->device_format_string);
>> > +        break;
>> > +    default:
>> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
>> > +        break;
>> > +    }
>> > +}
>> > +
>> > +static void spice_usb_device_widget_set_property(GObject       *gobject,
>> > +                                                 guint          prop_id,
>> > +                                                 const GValue  *value,
>> > +                                                 GParamSpec    *pspec)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +
>> > +    switch (prop_id) {
>> > +    case PROP_SESSION:
>> > +        priv->session = g_value_dup_object(value);
>> > +        break;
>> > +    case PROP_DEVICE_FORMAT_STRING:
>> > +        priv->device_format_string = g_value_dup_string(value);
>> > +        break;
>> > +    default:
>> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
>> > +        break;
>> > +    }
>> > +}
>> > +
>> > +static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +
>> > +    if (priv->info_bar) {
>> > +        gtk_widget_destroy(priv->info_bar);
>> > +        priv->info_bar = NULL;
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
>> > +                                      const gchar          *message,
>> > +                                      GtkMessageType        message_type,
>> > +                                      GdkPixbuf            *icon_pixbuf)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;
>> > +
>> > +    spice_usb_device_widget_hide_info_bar(self);
>> > +
>> > +    info_bar = gtk_info_bar_new();
>> > +    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type);
>> > +
>> > +    content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
>> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
>> > +    gtk_container_add(GTK_CONTAINER(content_area), hbox);
>> > +
>> > +    icon = gtk_image_new_from_pixbuf(icon_pixbuf);
>> > +    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10);
>> > +
>> > +    label = gtk_label_new(message);
>> > +    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
>> > +
>> > +    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
>> > +    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 0, 0);
>> > +    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
>> > +
>> > +    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0);
>> > +    gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put after the lable */
>> > +    gtk_widget_show_all(priv->info_bar);
>> > +}
>> > +
>> > +static void spice_usb_device_widget_create_tree_view(SpiceUsbDeviceWidget *self, gboolean is_cd)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +    GtkTreeStore *tree_store = usb_widget_create_tree_store();
>> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
>> > +
>> > +    if (is_cd) {
>> > +        priv->cd_tree.tree_view = tree_view;
>> > +        priv->cd_tree.tree_store = tree_store;
>> > +    } else {
>> > +        priv->usb_tree.tree_view = tree_view;
>> > +        priv->usb_tree.tree_store = tree_store;
>> > +    }
>> > +
>> > +    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
>> > +    g_object_unref(tree_store); /* destroy tree_store automatically with tree_view */
>> > +
>> > +    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM, COL_CAN_REDIRECT,
>> > +        is_cd ? tree_item_toggled_cb_redirect_cd : tree_item_toggled_cb_redirect_usb, is_cd);
>> > +
>> > +    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);
>> > +
>> > +    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd);
>> > +    if (is_cd) {
>> > +        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);
>> > +    }
>> > +
>> > +    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);
>> > +    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);
>> > +
>> > +    if (is_cd) {
>> > +        view_add_text_column(self, COL_FILE, TRUE, is_cd);
>> > +        view_add_read_only_toggle_column(self, COL_LOADED, COL_LUN_ITEM, is_cd);
>> > +        view_add_read_only_toggle_column(self, COL_LOCKED, COL_LUN_ITEM, is_cd);
>> > +        // uncomment to show also 'idle' column for CD
>> > +        //view_add_read_only_toggle_column(self, COL_IDLE, COL_LUN_ITEM, is_cd);
>> > +    }
>> > +
>> > +    gtk_tree_selection_set_mode(
>> > +            gtk_tree_view_get_selection(tree_view),
>> > +            GTK_SELECTION_NONE);
>> > +
>> > +    if (is_cd) {
>> > +        set_selection_handler(tree_view);
>> > +    }
>> > +}
>> > +
>> > +static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget *self)
>> > +{
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +
>> > +    g_signal_connect(priv->manager, "device-added",
>> > +                     G_CALLBACK(device_added_cb), self);
>> > +    g_signal_connect(priv->manager, "device-removed",
>> > +                     G_CALLBACK(device_removed_cb), self);
>> > +    g_signal_connect(priv->manager, "device-changed",
>> > +                     G_CALLBACK(device_changed_cb), self);
>> > +    // TODO: connect failed
>> > +    g_signal_connect(priv->manager, "device-error",
>> > +                     G_CALLBACK(device_error_cb), self);
>> > +
>> > +    g_signal_connect(priv->cd_tree.tree_view, "button-press-event",
>> > +                     G_CALLBACK(treeview_on_right_button_pressed_cb), self);
>> > +    g_signal_connect(priv->cd_tree.tree_view, "popup-menu",
>> > +                     G_CALLBACK(treeview_on_popup_key_pressed_cb), self);
>> > +}
>> > +
>> > +static void create_tree_window(SpiceUsbDeviceWidget *self, GtkTreeView *tree_view)
>> > +{
>> > +    GtkWidget *sw;
>> > +    /* scrolled window */
>> > +    sw = gtk_scrolled_window_new(NULL, NULL);
>> > +    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
>> > +        GTK_SHADOW_ETCHED_IN);
>> > +    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
>> > +        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
>> > +    gtk_widget_set_hexpand(sw, TRUE);
>> > +    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
>> > +    gtk_widget_set_vexpand(sw, TRUE);
>> > +    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
>> > +    gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view));
>> > +    gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0);
>> > +}
>> > +
>> > +static void spice_usb_device_widget_constructed(GObject *gobject)
>> > +{
>> > +    SpiceUsbDeviceWidget *self;
>> > +    GtkRequisition min_size, natural_size;
>> > +    SpiceUsbDeviceWidgetPrivate *priv;
>> > +    GtkWidget *hbox, *dev_label;
>> > +    GtkWidget *add_cd_button, *add_cd_icon;
>> > +    GPtrArray *devices = NULL;
>> > +    GError *err = NULL;
>> > +    gchar *str;
>> > +    guint i;
>> > +    gboolean cd_sharing_enabled = TRUE;
>> > +    #ifndef USE_CD_SHARING
>> > +    cd_sharing_enabled = FALSE;
>> > +    #endif
>> > +
>> > +    self = SPICE_USB_DEVICE_WIDGET(gobject);
>> > +    priv = self->priv;
>> > +    if (!priv->session)
>> > +        g_error("SpiceUsbDeviceWidget constructed without a session");
>> > +
>> > +    min_size.width = 600;
>> > +    min_size.height = 300;
>> > +    natural_size.width = 1200;
>> > +    natural_size.height = 600;
>> > +    gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size, &natural_size);
>> > +
>> > +    priv->label = gtk_label_new(NULL);
>> > +    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));
>> > +    gtk_label_set_markup(GTK_LABEL(priv->label), str);
>> > +    g_free(str);
>> > +    gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0);
>> > +
>> > +    priv->icon_cd = get_named_icon("media-optical", GTK_ICON_SIZE_LARGE_TOOLBAR);
>> > +    priv->icon_connected = get_named_icon("network-transmit-receive", GTK_ICON_SIZE_LARGE_TOOLBAR);
>> > +    priv->icon_disconn = get_named_icon("network-offline", GTK_ICON_SIZE_LARGE_TOOLBAR);
>> > +    priv->icon_warning = get_named_icon("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR);
>> > +    priv->icon_info = get_named_icon("dialog-information", GTK_ICON_SIZE_LARGE_TOOLBAR);
>> > +
>> > +    priv->manager = spice_usb_device_manager_get(priv->session, &err);
>> > +    if (err) {
>> > +        spice_usb_device_widget_show_info_bar(self, err->message,
>> > +                                              GTK_MESSAGE_WARNING, priv->icon_warning);
>> > +        g_clear_error(&err);
>> > +        return;
>> > +    }
>> > +
>> > +    if (cd_sharing_enabled) {
>> > +        spice_usb_device_widget_create_tree_view(self, TRUE);
>> > +    } else {
>> > +        priv->cd_tree.tree_store = NULL;
>> > +        priv->cd_tree.tree_view = NULL;
>> > +    }
>> > +    spice_usb_device_widget_create_tree_view(self, FALSE);
>> > +
>> > +    spice_usb_device_widget_signals_connect(self);
>> > +
>> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
>> > +    gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
>> > +
>> > +    /* "Available devices" label - in hbox */
>> > +    dev_label = gtk_label_new(_("Local USB devices"));
>> > +    gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
>> > +    create_tree_window(self, priv->usb_tree.tree_view);
>> > +
>> > +    if (cd_sharing_enabled) {
>> > +        hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
>> > +        gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
>> > +        dev_label = gtk_label_new(_("Shared CD devices"));
>> > +        gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
>> > +        /* "Add CD" button - in hbox */
>> > +        add_cd_button = gtk_button_new_with_label(_("Add CD"));
>> > +        gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button), TRUE);
>> > +        add_cd_icon = gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_BUTTON);
>> > +        gtk_button_set_image(GTK_BUTTON(add_cd_button), add_cd_icon);
>> > +
>> > +        gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END);
>> > +        g_signal_connect(add_cd_button, "clicked", G_CALLBACK(add_cd_lun_button_clicked_cb), self);
>> > +        gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE, FALSE, 0);
>> > +        create_tree_window(self, priv->cd_tree.tree_view);
>> > +    }
>> > +    devices = spice_usb_device_manager_get_devices(priv->manager);
>> > +    if (!devices)
>> > +        goto end;
>> > +
>> > +    for (i = 0; i < devices->len; i++) {
>> > +        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);
>> > +        usb_widget_add_device(self, usb_device, NULL);
>> > +    }
>> > +    g_ptr_array_unref(devices);
>> > +
>> > +    select_widget_size(GTK_WIDGET(self));
>> > +
>> > +end:
>> > +    spice_usb_device_widget_update_status(self);
>> > +}
>> > +
>> > +static void spice_usb_device_widget_finalize(GObject *object)
>> > +{
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> > +
>> > +    if (priv->manager) {
>> > +        g_signal_handlers_disconnect_by_func(priv->manager,
>> > +                                             device_added_cb, self);
>> > +        g_signal_handlers_disconnect_by_func(priv->manager,
>> > +                                             device_removed_cb, self);
>> > +        g_signal_handlers_disconnect_by_func(priv->manager,
>> > +                                             device_changed_cb, self);
>> > +        g_signal_handlers_disconnect_by_func(priv->manager,
>> > +                                             device_error_cb, self);
>> > +    }
>> > +    g_object_unref(priv->session);
>> > +    g_free(priv->device_format_string);
>> > +
>> > +    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize)
>> > +        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object);
>> > +}
>> > +
>> > +static void spice_usb_device_widget_class_init(
>> > +    SpiceUsbDeviceWidgetClass *klass)
>> > +{
>> > +    GObjectClass *gobject_class = (GObjectClass *)klass;
>> > +    GParamSpec *pspec;
>> > +
>> > +    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate));
>> > +
>> > +    gobject_class->constructed  = spice_usb_device_widget_constructed;
>> > +    gobject_class->finalize     = spice_usb_device_widget_finalize;
>> > +    gobject_class->get_property = spice_usb_device_widget_get_property;
>> > +    gobject_class->set_property = spice_usb_device_widget_set_property;
>> > +
>> > +    /**
>> > +     * SpiceUsbDeviceWidget:session:
>> > +     *
>> > +     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
>> > +     *
>> > +     **/
>> > +    pspec = g_param_spec_object("session",
>> > +                                "Session",
>> > +                                "SpiceSession",
>> > +                                SPICE_TYPE_SESSION,
>> > +                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
>> > +                                G_PARAM_STATIC_STRINGS);
>> > +    g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
>> > +
>> > +    /**
>> > +     * SpiceUsbDeviceWidget:device-format-string:
>> > +     *
>> > +     * Format string to pass to spice_usb_device_get_descript_______________________________________________
>> Spice-devel mailing list
>> Spice-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/spice-devel
On Tue, Sep 18, 2018 at 12:15 PM, Marc-André Lureau <
marcandre.lureau@gmail.com> wrote:

> Hi
>
> On Tue, Sep 18, 2018 at 12:04 PM Yuri Benditovich
> <yuri.benditovich@daynix.com> wrote:
> >
> >
> >
> > On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <victortoso@redhat.com>
> wrote:
> >>
> >> On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
> >> > New USB widget supports all the functionality related to
> >> > redirection of local USB device (as previous usb widget did).
> >> > Additionally it allows creation, management and redirection of
> >> > emulated USB CD devices for CD sharing.
> >>
> >> Wouldn't be possible to extend the current widget instead of
> >> creating a new one?
> >
> >
> > Of course it was the first idea before creating new one.
> > After several discussions we understood that there is almost nothing
> > common between what we'd like to have for CD and what we have in
> existing widget.
> > This also was decided on special meeting of Alexander with spice team
> (remove
> > some controls and split it to two lists, locals and CD). Earlier the
> screenshot of
> > the latest widget (current code) was sent to internal list for review
> and no objections
> > were received.
>
> Ultimately the discussion and decision should take place on public mailing
> list.
>
> And many of us (including me), may not be directly involved in the RH
> spice team.
>
> Could you detail the goals you had and why you had to write a new widget?
>

Regarding new USB widget:

User experience when USB CD drive is presented to user is wider than just
plug/unplug the USB device.

The CD may include several units, each of them can be managed separately.
If more than one unit supported on CD, single CD with number of units
requires
just one redirection channel. [At the moment due to time restrictions, we
define
CD as single unit, but I think in the future we will want to enable
multiple units on CD]
So in common case the GUI for CD drives tends to be something like tree
view.
This easily serves also local USB devices as in GTK tree view == list view.
Initially the widget was developed with single list containing all the
devices
when shared CD devices have leaves and local USB devices do not.
IMHO, this kind of presentation is better than current one which presents
local devices
and CD devices separately.

When CD drive is presented to the user on guest machine, it can be ejected
by
user action (from Gnome or Explorer). When the media is ejected, it can be
changed
on client side. So the status (ejected/loaded) should be visible and the
user should be
able to control this also on client side exactly as you can open/close the
drive door manually.

Drive lock. When CD is reqired for SW operation, the SW often locks
the drive preventing user-initiated eject. In such case, if needed, the CD
still can be
manually ejected using clip. Another option related to 'lock' is that user
may want
to prevent removal of CD by guest software and lock it on client side.

The user shall select the properties for logical CD unit and it is good to
show them
in the GUI. They include (at least):
* file name (or device name) used as media
* device attributes (vendor/product) which are used to report properties of
SCSI device
(these properties are visible to user in seabios boot screen, so in case of
2 CDs the user
can recognize the needed one). Note that 2 CDs is typical case for Windows
installation
on VM with virtio.
* Whether the CD media is currently loaded

Visible status of the device is (IMHO) good for self support. When
something does not
work as expected and you can't see anything in the GUI that can explain
this, the next step
is opening issues, sending logs etc., i.e. spend own time and time of other
people.
I disagree with the statement 'less GUI is better'. Definitely, less GUI is
simpler,
but this simplicity often increases cost of ownership.

Our solid opinion is that even current CD additions are too heavy for
existing widget.

Thanks,
Yuri


>
> For UI, it's best to focus on the supported use case & the user
> story/experience, and avoid feature creep (usually the less UI, the
> better).
>
> >
> >>
> >>
> >> > Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
> >> > Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
> >> > ---
> >> >  src/usb-device-redir-widget.c | 2065 ++++++++++++++++++++++++++++++
> +++++++++++
> >> >  1 file changed, 2065 insertions(+)
> >> >  create mode 100644 src/usb-device-redir-widget.c
> >> >
> >> > diff --git a/src/usb-device-redir-widget.c
> b/src/usb-device-redir-widget.c
> >> > new file mode 100644
> >> > index 0000000..59a6043
> >> > --- /dev/null
> >> > +++ b/src/usb-device-redir-widget.c
> >> > @@ -0,0 +1,2065 @@
> >> > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> >> > +/*
> >> > +   Copyright (C) 2012 Red Hat, Inc.
> >> > +
> >> > +   Red Hat Authors:
> >> > +   Alexander Nezhinsky<anezhins@redhat.com>
> >> > +
> >> > +   This library is free software; you can redistribute it and/or
> >> > +   modify it under the terms of the GNU Lesser General Public
> >> > +   License as published by the Free Software Foundation; either
> >> > +   version 2.1 of the License, or (at your option) any later version.
> >> > +
> >> > +   This library 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
> >> > +   Lesser General Public License for more details.
> >> > +
> >> > +   You should have received a copy of the GNU Lesser General Public
> >> > +   License along with this library; if not, see <
> http://www.gnu.org/licenses/>.
> >> > +*/
> >> > +
> >> > +#include "config.h"
> >> > +#ifndef USB_WIDGET_TEST
> >> > +    #include <glib/gi18n-lib.h>
> >> > +    #include "spice-client.h"
> >> > +    #include "spice-marshal.h"
> >> > +#else
> >> > +    #include "spice-client.h"
> >> > +#endif
> >> > +#include "usb-device-widget.h"
> >> > +
> >> > +/*
> >> > +    Debugging note:
> >> > +    Logging from this module is not affected by --spice-debug
> >> > +    command line parameter
> >> > +    Use SPICE_DEBUG=1 environment varible to enable logs
> >> > +*/
> >> > +
> >> > +#ifdef USE_NEW_USB_WIDGET
> >> > +
> >> > +/**
> >> > + * SECTION:usb-device-widget
> >> > + * @short_description: USB device selection widget
> >> > + * @title: Spice USB device selection widget
> >> > + * @section_id:
> >> > + * @see_also:
> >> > + * @stability: Under development
> >> > + * @include: spice-client-gtk.h
> >> > + *
> >> > + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
> >> > + * add an UI to select USB devices to redirect (or unredirect).
> >> > + */
> >> > +
> >> > +struct _SpiceUsbDeviceWidget
> >> > +{
> >> > +    GtkBox parent;
> >> > +
> >> > +    SpiceUsbDeviceWidgetPrivate *priv;
> >> > +};
> >> > +
> >> > +struct _SpiceUsbDeviceWidgetClass
> >> > +{
> >> > +    GtkBoxClass parent_class;
> >> > +
> >> > +    /* signals */
> >> > +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
> >> > +                            SpiceUsbDevice *device, GError *error);
> >> > +};
> >> > +
> >> > +/* ------------------------------------------------------------------
> */
> >> > +/* Prototypes for callbacks  */
> >> > +static void device_added_cb(SpiceUsbDeviceManager *manager,
> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> > +static void device_removed_cb(SpiceUsbDeviceManager *manager,
> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> > +static void device_changed_cb(SpiceUsbDeviceManager *manager,
> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
> >> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);
> >> > +static gboolean spice_usb_device_widget_update_status(gpointer
> user_data);
> >> > +
> >> > +/* ------------------------------------------------------------------
> */
> >> > +/* gobject glue
>  */
> >> > +
> >> > +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
> >> > +    (G_TYPE_INSTANCE_GET_PRIVATE((obj),
> SPICE_TYPE_USB_DEVICE_WIDGET, \
> >> > +                                 SpiceUsbDeviceWidgetPrivate))
> >> > +
> >> > +enum {
> >> > +    PROP_0,
> >> > +    PROP_SESSION,
> >> > +    PROP_DEVICE_FORMAT_STRING,
> >> > +};
> >> > +
> >> > +enum {
> >> > +    CONNECT_FAILED,
> >> > +    LAST_SIGNAL,
> >> > +};
> >> > +
> >> > +typedef struct {
> >> > +    GtkTreeView *tree_view;
> >> > +    GtkTreeStore *tree_store;
> >> > +} SpiceUsbDeviceWidgetTree;
> >> > +
> >> > +struct _SpiceUsbDeviceWidgetPrivate {
> >> > +    SpiceSession *session;
> >> > +    gchar *device_format_string;
> >> > +    SpiceUsbDeviceManager *manager;
> >> > +    GtkWidget *info_bar;
> >> > +    GtkWidget *label;
> >> > +    SpiceUsbDeviceWidgetTree cd_tree;
> >> > +    SpiceUsbDeviceWidgetTree usb_tree;
> >> > +    GdkPixbuf *icon_cd;
> >> > +    GdkPixbuf *icon_connected;
> >> > +    GdkPixbuf *icon_disconn;
> >> > +    GdkPixbuf *icon_warning;
> >> > +    GdkPixbuf *icon_info;
> >> > +    gchar *err_msg;
> >> > +    gsize device_count;
> >> > +};
> >> > +
> >> > +static guint signals[LAST_SIGNAL] = { 0, };
> >> > +
> >> > +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget,
> GTK_TYPE_BOX);
> >> > +
> >> > +/* TREE */
> >> > +
> >> > +enum column_id
> >> > +{
> >> > +    COL_REDIRECT = 0,
> >> > +    COL_ADDRESS,
> >> > +    COL_CONNECT_ICON,
> >> > +    COL_CD_ICON,
> >> > +    COL_VENDOR,
> >> > +    COL_PRODUCT,
> >> > +    COL_FILE,
> >> > +    COL_LOADED,
> >> > +    COL_LOCKED,
> >> > +    COL_IDLE,
> >> > +    /* internal columns */
> >> > +    COL_REVISION,
> >> > +    COL_CD_DEV,
> >> > +    COL_LUN_ITEM,
> >> > +    COL_DEV_ITEM,
> >> > +    COL_ITEM_DATA,
> >> > +    COL_CONNECTED,
> >> > +    COL_CAN_REDIRECT,
> >> > +    COL_ROW_COLOR,
> >> > +    COL_ROW_COLOR_SET,
> >> > +    NUM_COLS,
> >> > +
> >> > +    INVALID_COL
> >> > +};
> >> > +
> >> > +// there is a possibility to use different names
> >> > +// for columns in USB device list and CD device list, if needed
> >> > +// currently they are identical
> >> > +static const char *column_names_cd[NUM_COLS];
> >> > +static const char *column_names_usb[NUM_COLS];
> >> > +
> >> > +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
> >> > +
> >> > +static void initialize_columns(void)
> >> > +{
> >> > +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
> >> > +    SET_COLUMN(COL_ADDRESS, _("Address"));
> >> > +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
> >> > +    SET_COLUMN(COL_CD_ICON, _("CD"));
> >> > +    SET_COLUMN(COL_VENDOR, _("Vendor"));
> >> > +    SET_COLUMN(COL_PRODUCT, _("Product"));
> >> > +    SET_COLUMN(COL_FILE, _("File/Device Path"));
> >> > +    SET_COLUMN(COL_LOADED, _("Loaded"));
> >> > +    SET_COLUMN(COL_LOCKED, _("Locked"));
> >> > +    SET_COLUMN(COL_IDLE, _("Idle"));
> >> > +    SET_COLUMN(COL_REVISION, "?Revision");
> >> > +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
> >> > +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
> >> > +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
> >> > +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
> >> > +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
> >> > +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
> >> > +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
> >> > +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
> >> > +};
> >> > +
> >> > +static const char *column_name(enum column_id id, gboolean is_cd)
> >> > +{
> >> > +    const char **col_name = is_cd ? column_names_cd :
> column_names_usb;
> >> > +    return col_name[id];
> >> > +}
> >> > +
> >> > +typedef struct _UsbWidgetLunItem {
> >> > +    SpiceUsbDeviceManager *manager;
> >> > +    SpiceUsbDevice *device;
> >> > +    guint lun;
> >> > +    SpiceUsbDeviceLunInfo info;
> >> > +} UsbWidgetLunItem;
> >> > +
> >> > +typedef struct _TreeFindUsbDev {
> >> > +    SpiceUsbDevice *usb_dev;
> >> > +    GtkTreeIter dev_iter;
> >> > +} TreeFindUsbDev;
> >> > +
> >> > +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar
> *, gpointer);
> >> > +
> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> >> > +                                          SpiceUsbDevice *usb_device,
> >> > +                                          GtkTreeIter *old_dev_iter);
> >> > +
> >> > +static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
> >> > +                                     SpiceUsbDevice *device,
> >> > +                                     const gchar *format)
> >> > +{
> >> > +    SpiceUsbDeviceDescription desc;
> >> > +    gchar *descriptor;
> >> > +    gchar *res;
> >> > +    spice_usb_device_get_info(manager, device, &desc);
> >> > +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id,
> desc.product_id);
> >> > +    if (!format) {
> >> > +        format = _("%s %s %s at %d-%d");
> >> > +    }
> >> > +    res = g_strdup_printf(format, desc.vendor, desc.product,
> descriptor, desc.bus, desc.address);
> >> > +    g_free(desc.vendor);
> >> > +    g_free(desc.product);
> >> > +    g_free(descriptor);
> >> > +    return res;
> >> > +}
> >> > +
> >> > +static GtkTreeStore* usb_widget_create_tree_store(void)
> >> > +{
> >> > +    GtkTreeStore *tree_store;
> >> > +
> >> > +    tree_store = gtk_tree_store_new(NUM_COLS,
> >> > +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
> >> > +                        G_TYPE_STRING, /* COL_ADDRESS */
> >> > +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
> >> > +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
> >> > +                        G_TYPE_STRING, /* COL_VENDOR */
> >> > +                        G_TYPE_STRING, /* COL_PRODUCT */
> >> > +                        G_TYPE_STRING, /* COL_FILE */
> >> > +                        G_TYPE_BOOLEAN, /* COL_LOADED */
> >> > +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
> >> > +                        G_TYPE_BOOLEAN, /* COL_IDLE */
> >> > +                        /* internal columns */
> >> > +                        G_TYPE_STRING, /* COL_REVISION */
> >> > +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
> >> > +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
> >> > +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
> >> > +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
> >> > +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
> >> > +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
> >> > +                        G_TYPE_STRING, /* COL_ROW_COLOR */
> >> > +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
> >> > +    SPICE_DEBUG("tree store created");
> >> > +
> >> > +    return tree_store;
> >> > +}
> >> > +
> >> > +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
> >> > +{
> >> > +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(),
> name, size, 0);
> >> > +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
> >> > +    g_object_unref (info);
> >> > +    return pixbuf;
> >> > +}
> >> > +
> >> > +static void select_widget_size(GtkWidget *wg)
> >> > +{
> >> > +    GdkDisplay *d = gtk_widget_get_display(wg);
> >> > +    int i, w = 2000, h = 1024;
> >> > +    int n = gdk_display_get_n_monitors(d);
> >> > +    for (i = 0; i < n; ++i)
> >> > +    {
> >> > +        GdkMonitor *m = gdk_display_get_monitor(d, i);
> >> > +        if (m) {
> >> > +            GdkRectangle area;
> >> > +            gdk_monitor_get_workarea(m, &area);
> >> > +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
> >> > +                i, area.width, area.height, area.x, area.y );
> >> > +            w = MIN(w, area.width);
> >> > +            h = MIN(h, area.height);
> >> > +        }
> >> > +    }
> >> > +
> >> > +    w = (w * 3) / 4;
> >> > +    h = h / 2;
> >> > +
> >> > +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
> >> > +    gtk_widget_set_size_request(wg, w, h);
> >> > +}
> >> > +
> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> >> > +                                          SpiceUsbDevice *usb_device,
> >> > +                                          GtkTreeIter *old_dev_iter)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> >> > +    SpiceUsbDeviceWidgetTree *tree;
> >> > +    GtkTreeView *tree_view;
> >> > +    GtkTreeStore *tree_store;
> >> > +    GtkTreeIter new_dev_iter;
> >> > +    SpiceUsbDeviceDescription dev_info;
> >> > +    GtkTreePath *new_dev_path;
> >> > +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
> >> > +    gchar *addr_str;
> >> > +    GArray *lun_array;
> >> > +    guint lun_index;
> >> > +    GError *error = NULL;
> >> > +
> >> > +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr,
> usb_device);
> >> > +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
> >> > +    tree_view = tree->tree_view;
> >> > +    tree_store = tree->tree_store;
> >> > +
> >> > +    if (old_dev_iter == NULL) {
> >> > +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
> >> > +    } else {
> >> > +        gtk_tree_store_insert_after(tree_store, &new_dev_iter,
> NULL, old_dev_iter);
> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> > +    }
> >> > +
> >> > +    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
> >> > +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus,
> (gint)dev_info.address);
> >> > +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr,
> usb_device);
> >> > +    is_dev_redirected = is_dev_connected;
> >> > +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
> >> > +        addr_str, dev_info.vendor, dev_info.product,
> is_dev_connected, is_dev_cd);
> >> > +
> >> > +    gtk_tree_store_set(tree_store, &new_dev_iter,
> >> > +        COL_REDIRECT, is_dev_redirected,
> >> > +        COL_ADDRESS, addr_str,
> >> > +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected :
> priv->icon_disconn,
> >> > +        COL_CD_ICON, priv->icon_cd,
> >> > +        COL_VENDOR, dev_info.vendor,
> >> > +        COL_PRODUCT, dev_info.product,
> >> > +        COL_CD_DEV, is_dev_cd,
> >> > +        COL_LUN_ITEM, FALSE, /* USB device item */
> >> > +        COL_DEV_ITEM, TRUE, /* USB device item */
> >> > +        COL_ITEM_DATA, (gpointer)usb_device,
> >> > +        COL_CONNECTED, is_dev_connected,
> >> > +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr,
> usb_device, &error),
> >> > +        COL_ROW_COLOR, "beige",
> >> > +        COL_ROW_COLOR_SET, TRUE,
> >> > +        -1);
> >> > +    g_clear_error(&error);
> >> > +
> >> > +    priv->device_count++;
> >> > +
> >> > +    /* get all the luns */
> >> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr,
> usb_device);
> >> > +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
> >> > +        UsbWidgetLunItem *lun_item;
> >> > +        GtkTreeIter lun_iter;
> >> > +        gchar lun_str[8];
> >> > +
> >> > +        lun_item = g_malloc(sizeof(*lun_item));
> >> > +        lun_item->manager = usb_dev_mgr;
> >> > +        lun_item->device = usb_device;
> >> > +        lun_item->lun = g_array_index(lun_array, guint, lun_index);
> >> > +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr,
> usb_device, lun_item->lun, &lun_item->info);
> >> > +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s]
> lun_item:%p",
> >> > +                lun_index, lun_item->info.vendor,
> lun_item->info.product,
> >> > +                lun_item->info.revision, lun_item->info.file_path,
> lun_item);
> >> > +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
> >> > +
> >> > +        /* Append LUN as a child of USB device */
> >> > +        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
> >> > +        gtk_tree_store_set(tree_store, &lun_iter,
> >> > +                COL_ADDRESS, lun_str,
> >> > +                COL_VENDOR, lun_item->info.vendor,
> >> > +                COL_PRODUCT, lun_item->info.product,
> >> > +                COL_REVISION, lun_item->info.revision,
> >> > +                COL_FILE, lun_item->info.file_path,
> >> > +                COL_LOADED, lun_item->info.loaded,
> >> > +                COL_LOCKED, lun_item->info.locked,
> >> > +                COL_IDLE, !lun_item->info.started,
> >> > +                COL_CD_DEV, FALSE,
> >> > +                COL_LUN_ITEM, TRUE, /* LUN item */
> >> > +                COL_DEV_ITEM, FALSE, /* LUN item */
> >> > +                COL_ITEM_DATA, (gpointer)lun_item,
> >> > +                COL_CONNECTED, is_dev_connected,
> >> > +                COL_ROW_COLOR, "azure",
> >> > +                COL_ROW_COLOR_SET, TRUE,
> >> > +                -1);
> >> > +    }
> >> > +    g_array_unref(lun_array);
> >> > +
> >> > +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store),
> &new_dev_iter);
> >> > +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
> >> > +    gtk_tree_path_free(new_dev_path);
> >> > +
> >> > +    g_free(dev_info.vendor);
> >> > +    g_free(dev_info.product);
> >> > +    g_free(addr_str);
> >> > +}
> >> > +
> >> > +static gboolean tree_item_is_lun(GtkTreeStore *tree_store,
> GtkTreeIter *iter)
> >> > +{
> >> > +    gboolean is_lun;
> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter,
> COL_LUN_ITEM, &is_lun, -1);
> >> > +    return is_lun;
> >> > +}
> >> > +
> >> > +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel
> *tree_model,
> >> > +
> GtkTreePath *path, GtkTreeIter *iter,
> >> > +
> gpointer user_data)
> >> > +{
> >> > +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
> >> > +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
> >> > +    SpiceUsbDevice *usb_device;
> >> > +    gboolean is_lun_item;
> >> > +
> >> > +    gtk_tree_model_get(tree_model, iter,
> >> > +                       COL_LUN_ITEM, &is_lun_item,
> >> > +                       COL_ITEM_DATA, (gpointer *)&usb_device,
> >> > +                       -1);
> >> > +    if (!is_lun_item && usb_device == find_usb_device) {
> >> > +        find_info->dev_iter = *iter;
> >> > +        find_info->usb_dev  = NULL;
> >> > +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
> >> > +        return TRUE; /* stop iterating */
> >> > +    } else {
> >> > +        return FALSE; /* continue iterating */
> >> > +    }
> >> > +}
> >> > +
> >> > +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore
> *tree_store,
> >> > +
> SpiceUsbDevice *usb_device)
> >> > +{
> >> > +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter =
> {} };
> >> > +    GtkTreeIter *iter = NULL;
> >> > +
> >> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
> >> > +                           usb_widget_tree_store_find_usb_dev_foreach_cb,
> (gpointer)&find_info);
> >> > +    // the callback sets 'usb_dev' field to zero if it finds the
> device
> >> > +    if (!find_info.usb_dev) {
> >> > +        iter = g_malloc(sizeof(*iter));
> >> > +        *iter = find_info.dev_iter;
> >> > +    }
> >> > +    return iter;
> >> > +}
> >> > +
> >> > +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
> >> > +                                         SpiceUsbDevice *usb_device)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeIter *old_dev_iter;
> >> > +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
> >> > +    // on WIN32 it is possible the backend device is already removed
> >> > +    // from the USB device manager list and we can't know it was
> >> > +    // USB or CD, do we will try both lists
> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> > +    if (old_dev_iter != NULL) {
> >> > +        SPICE_DEBUG("USB Device removed");
> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> > +        priv->device_count--;
> >> > +        g_free(old_dev_iter);
> >> > +        return TRUE;
> >> > +    }
> >> > +    tree_store = priv->cd_tree.tree_store;
> >> > +    if (tree_store) {
> >> > +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> > +    }
> >> > +    if (old_dev_iter != NULL) {
> >> > +        SPICE_DEBUG("CD Device removed");
> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> > +        priv->device_count--;
> >> > +        g_free(old_dev_iter);
> >> > +        return TRUE;
> >> > +    }
> >> > +    SPICE_DEBUG("Device %p not found!", usb_device);
> >> > +    return FALSE;
> >> > +}
> >> > +
> >> > +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget
> *self,
> >> > +                                                 enum column_id
> toggle_col_id,
> >> > +                                                 enum column_id
> visible_col_id,
> >> > +                                                 enum column_id
> sensitive_col_id,
> >> > +
>  tree_item_toggled_cb toggled_cb,
> >> > +                                                 gboolean is_cd)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkCellRenderer     *renderer;
> >> > +    GtkTreeViewColumn   *view_col;
> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> >> > +
> >> > +    renderer = gtk_cell_renderer_toggle_new();
> >> > +
> >> > +    if (sensitive_col_id != INVALID_COL) {
> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                        col_name,
> >> > +                        renderer,
> >> > +                        "active", toggle_col_id,
> >> > +                        "visible", visible_col_id,
> >> > +                        "activatable", sensitive_col_id,
> >> > +                        NULL);
> >> > +    } else {
> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                        col_name,
> >> > +                        renderer,
> >> > +                        "active", toggle_col_id,
> >> > +                        "visible", visible_col_id,
> >> > +                        NULL);
> >> > +    }
> >> > +
> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> >> > +
> >> > +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb),
> self);
> >> > +
> >> > +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u
> : %s]",
> >> > +            toggle_col_id, col_name,
> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
> >> > +    return view_col;
> >> > +}
> >> > +
> >> > +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget
> *self,
> >> > +                                                           enum
> column_id toggle_col_id,
> >> > +                                                           enum
> column_id visible_col_id,
> >> > +                                                           gboolean
> is_cd)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkCellRenderer     *renderer;
> >> > +    GtkTreeViewColumn   *view_col;
> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> >> > +
> >> > +    renderer = gtk_cell_renderer_toggle_new();
> >> > +
> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                    col_name,
> >> > +                    renderer,
> >> > +                    "active", toggle_col_id,
> >> > +                    "visible", visible_col_id,
> >> > +                    NULL);
> >> > +
> >> > +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer),
> FALSE);
> >> > +
> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> >> > +
> >> > +    SPICE_DEBUG("view added read-only toggle column [%u : %s]
> visible when [%u : %s]",
> >> > +            toggle_col_id, col_name,
> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
> >> > +    return view_col;
> >> > +}
> >> > +
> >> > +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget
> *self,
> >> > +                                               enum column_id col_id,
> >> > +                                               gboolean expandable,
> >> > +                                               gboolean is_cd)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkCellRenderer     *renderer;
> >> > +    GtkTreeViewColumn   *view_col;
> >> > +
> >> > +    renderer = gtk_cell_renderer_text_new();
> >> > +
> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                    column_name(col_id, is_cd),
> >> > +                    renderer,
> >> > +                    "text", col_id,
> >> > +                    //"cell-background", COL_ROW_COLOR,
> >> > +                    //"cell-background-set", COL_ROW_COLOR_SET,
> >> > +                    NULL);
> >> > +
> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_GROW_ONLY);
> >> > +    gtk_tree_view_column_set_resizable(view_col, TRUE);
> >> > +    gtk_tree_view_column_set_expand(view_col, expandable);
> >> > +
> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> >> > +
> >> > +    SPICE_DEBUG("view added text column [%u : %s]", col_id,
> column_name(col_id, is_cd));
> >> > +    return view_col;
> >> > +}
> >> > +
> >> > +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget
> *self,
> >> > +                                                 enum column_id
> col_id,
> >> > +                                                 enum column_id
> visible_col_id,
> >> > +                                                 gboolean is_cd)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkCellRenderer     *renderer;
> >> > +    GtkTreeViewColumn   *view_col;
> >> > +    const char *col_name = column_name(col_id, is_cd);
> >> > +
> >> > +    renderer = gtk_cell_renderer_pixbuf_new();
> >> > +
> >> > +    if (visible_col_id == INVALID_COL) {
> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                        col_name,
> >> > +                        renderer,
> >> > +                        "pixbuf", col_id,
> >> > +                        NULL);
> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always",
> col_id, col_name);
> >> > +    } else {
> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> > +                        col_name,
> >> > +                        renderer,
> >> > +                        "pixbuf", col_id,
> >> > +                        "visible", visible_col_id,
> >> > +                        NULL);
> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u
> : %s]",
> >> > +                col_id, col_name, visible_col_id,
> column_name(visible_col_id, is_cd));
> >> > +    }
> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view :
> priv->usb_tree.tree_view, view_col);
> >> > +    return view_col;
> >> > +}
> >> > +
> >> > +/* Toggle handlers */
> >> > +
> >> > +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store,
> gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
> >> > +{
> >> > +    gboolean toggle_val;
> >> > +
> >> > +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store),
> iter, path_str);
> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id,
> &toggle_val, -1);
> >> > +
> >> > +    return toggle_val;
> >> > +}
> >> > +
> >> > +static void tree_item_toggle_set(GtkTreeStore *tree_store,
> GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
> >> > +{
> >> > +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
> >> > +}
> >> > +
> >> > +typedef struct _connect_cb_data {
> >> > +    SpiceUsbDeviceWidget *self;
> >> > +    SpiceUsbDevice *usb_dev;
> >> > +} connect_cb_data;
> >> > +
> >> > +static void connect_cb_data_free(connect_cb_data *user_data)
> >> > +{
> >> > +    spice_usb_device_widget_update_status(user_data->self);
> >> > +    g_object_unref(user_data->self);
> >> > +    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
> >> > +    g_free(user_data);
> >> > +}
> >> > +
> >> > +static void usb_widget_connect_cb(GObject *source_object,
> GAsyncResult *res, gpointer user_data)
> >> > +{
> >> > +    connect_cb_data *cb_data = user_data;
> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> >> > +    GError *err = NULL;
> >> > +    GtkTreeIter *dev_iter;
> >> > +    gchar *desc;
> >> > +    gboolean finished;
> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> > +
> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> >> > +    if (!dev_iter) {
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> >> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
> >> > +
> >> > +    finished = spice_usb_device_manager_conne
> ct_device_finish(priv->manager, res, &err);
> >> > +    if (finished) {
> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> > +                           COL_CONNECT_ICON, priv->icon_connected,
> >> > +                           COL_CONNECTED, TRUE,
> >> > +                           -1);
> >> > +    } else {
> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> > +                           COL_REDIRECT, FALSE,
> >> > +                           -1);
> >> > +        g_prefix_error(&err, "Device connect failed %s: ", desc);
> >> > +        if (err) {
> >> > +            SPICE_DEBUG("%s", err->message);
> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev,
> err);
> >> > +            g_error_free(err);
> >> > +        } else {
> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev,
> NULL);
> >> > +        }
> >> > +
> >> > +        /* don't trigger a disconnect if connect failed */
> >> > +        /*
> >> > +        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data-
> >check),
> >> > +                                        checkbox_clicked_cb, self);
> >> > +        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check),
> FALSE);
> >> > +        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_dat
> a->check),
> >> > +                                        checkbox_clicked_cb, self);
> >> > +        */
> >> > +    }
> >> > +    g_free(desc);
> >> > +    g_free(dev_iter);
> >> > +    connect_cb_data_free(user_data);
> >> > +}
> >> > +
> >> > +static void usb_widget_disconnect_cb(GObject *source_object,
> GAsyncResult *res, gpointer user_data)
> >> > +{
> >> > +    connect_cb_data *cb_data = user_data;
> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> >> > +    GError *err = NULL;
> >> > +    GtkTreeIter *dev_iter;
> >> > +    gchar *desc;
> >> > +    gboolean finished;
> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> > +
> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> >> > +    if (!dev_iter) {
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> >> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
> >> > +
> >> > +    finished = spice_usb_device_manager_disco
> nnect_device_finish(priv->manager, res, &err);
> >> > +    if (finished) {
> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> > +                           COL_CONNECT_ICON, priv->icon_disconn,
> >> > +                           COL_CONNECTED, FALSE,
> >> > +                           -1);
> >> > +    } else {
> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> > +                           COL_REDIRECT, TRUE,
> >> > +                           -1);
> >> > +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
> >> > +        if (err) {
> >> > +            SPICE_DEBUG("%s", err->message);
> >> > +            g_error_free(err);
> >> > +        }
> >> > +    }
> >> > +    g_free(desc);
> >> > +    g_free(dev_iter);
> >> > +    connect_cb_data_free(user_data);
> >> > +}
> >> > +
> >> > +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle
> *cell,
> >> > +                                          gchar *path_str,
> >> > +                                          SpiceUsbDeviceWidget *self,
> >> > +                                          GtkTreeStore *tree_store)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
> >> > +    SpiceUsbDevice *usb_dev;
> >> > +    GtkTreeIter iter;
> >> > +    gboolean new_redirect_val;
> >> > +
> >> > +    new_redirect_val = !tree_item_toggle_get_val(tree_store,
> path_str, &iter, COL_REDIRECT);
> >> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
> >> > +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT,
> new_redirect_val);
> >> > +
> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter,
> COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
> >> > +    cb_data->self = g_object_ref(self);
> >> > +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(),
> usb_dev);
> >> > +
> >> > +    if (new_redirect_val) {
> >> > +        spice_usb_device_manager_connect_device_async(priv->manager,
> usb_dev,
> >> > +                                                      NULL, /*
> cancellable */
> >> > +
> usb_widget_connect_cb, cb_data);
> >> > +    } else {
> >> > +        spice_usb_device_manager_disconnect_device_async(priv->manager,
> usb_dev,
> >> > +                                                         NULL, /*
> cancellable */
> >> > +
>  usb_widget_disconnect_cb, cb_data);
> >> > +
> >> > +    }
> >> > +    spice_usb_device_widget_update_status(self);
> >> > +}
> >> > +
> >> > +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle
> *cell,
> >> > +                                             gchar *path_str,
> >> > +                                             gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->cd_tree.tree_store);
> >> > +}
> >> > +
> >> > +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle
> *cell,
> >> > +                                             gchar *path_str,
> >> > +                                             gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->usb_tree.tree_store);
> >> > +}
> >> > +
> >> > +/* Signal handlers */
> >> > +
> >> > +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +
> >> > +    SPICE_DEBUG("Signal: Device Added");
> >> > +
> >> > +    usb_widget_add_device(self, usb_device, NULL);
> >> > +
> >> > +    spice_usb_device_widget_update_status(self);
> >> > +}
> >> > +
> >> > +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    gboolean dev_removed;
> >> > +
> >> > +    SPICE_DEBUG("Signal: Device Removed");
> >> > +
> >> > +    dev_removed = usb_widget_remove_device(self, usb_device);
> >> > +    if (dev_removed) {
> >> > +        spice_usb_device_widget_update_status(self);
> >> > +    }
> >> > +}
> >> > +
> >> > +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeIter *old_dev_iter;
> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_device);
> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> > +
> >> > +    SPICE_DEBUG("Signal: Device Changed");
> >> > +
> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> > +    if (old_dev_iter != NULL) {
> >> > +
> >> > +        usb_widget_add_device(self, usb_device, old_dev_iter);
> >> > +
> >> > +        spice_usb_device_widget_update_status(self);
> >> > +        g_free(old_dev_iter);
> >> > +    } else {
> >> > +        SPICE_DEBUG("Device not found!");
> >> > +    }
> >> > +}
> >> > +
> >> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
> >> > +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeIter *dev_iter;
> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_device);
> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> > +
> >> > +    SPICE_DEBUG("Signal: Device Error");
> >> > +
> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> > +    if (dev_iter != NULL) {
> >> > +        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT,
> FALSE);
> >> > +        spice_usb_device_widget_update_status(self);
> >> > +        g_free(dev_iter);
> >> > +    } else {
> >> > +        SPICE_DEBUG("Device not found!");
> >> > +    }
> >> > +}
> >> > +
> >> > +/* Selection handler */
> >> > +
> >> > +static void tree_selection_changed_cb(GtkTreeSelection *select,
> gpointer user_data)
> >> > +{
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +    GtkTreePath *path;
> >> > +    gboolean is_lun;
> >> > +    UsbWidgetLunItem *lun_item;
> >> > +    gchar *txt[3];
> >> > +
> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        gtk_tree_model_get(tree_model, &iter,
> >> > +                COL_VENDOR, &txt[0],
> >> > +                COL_PRODUCT, &txt[1],
> >> > +                COL_REVISION, &txt[2],
> >> > +                COL_LUN_ITEM, &is_lun,
> >> > +                COL_ITEM_DATA, (gpointer *)&lun_item,
> >> > +                -1);
> >> > +        path = gtk_tree_model_get_path(tree_model, &iter);
> >> > +
> >> > +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
> >> > +                txt[0], txt[1],
> >> > +                is_lun ? txt[2] : "--",
> >> > +                is_lun ? "LUN" : "USB-DEV",
> >> > +                is_lun ? lun_item->info.file_path : "--",
> >> > +                gtk_tree_path_to_string(path));
> >> > +
> >> > +        if (txt[0]) {
> >> > +            g_free(txt[0]);
> >> > +        }
> >> > +        if (txt[1]) {
> >> > +            g_free(txt[1]);
> >> > +        }
> >> > +        if (txt[2]) {
> >> > +            g_free(txt[2]);
> >> > +        }
> >> > +        gtk_tree_path_free(path);
> >> > +    }
> >> > +}
> >> > +
> >> > +static GtkTreeSelection* set_selection_handler(GtkTreeView
> *tree_view)
> >> > +{
> >> > +    GtkTreeSelection *select;
> >> > +
> >> > +    select = gtk_tree_view_get_selection(tree_view);
> >> > +    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
> >> > +
> >> > +    g_signal_connect(G_OBJECT(select), "changed",
> >> > +                     G_CALLBACK(tree_selection_changed_cb),
> >> > +                     NULL);
> >> > +
> >> > +    SPICE_DEBUG("selection handler set");
> >> > +    return select;
> >> > +}
> >> > +
> >> > +static GtkWidget *create_image_button_box(const gchar *label_str,
> const gchar *icon_name, GtkWidget *parent)
> >> > +{
> >> > +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
> >> > +    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name,
> GTK_ICON_SIZE_MENU);
> >> > +    GtkWidget *label = gtk_accel_label_new(label_str);
> >> > +    GtkAccelGroup *accel_group = gtk_accel_group_new();
> >> > +    guint accel_key;
> >> > +
> >> > +    /* add icon */
> >> > +    gtk_container_add(GTK_CONTAINER(box), icon);
> >> > +
> >> > +    /* add label */
> >> > +    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
> >> > +    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
> >> > +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key,
> NULL);
> >> > +    gtk_widget_add_accelerator(parent, "activate", accel_group,
> accel_key,
> >> > +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
> >> > +    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label),
> parent);
> >> > +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
> >> > +
> >> > +    /* add the new box to the parent widget */
> >> > +    gtk_container_add(GTK_CONTAINER(parent), box);
> >> > +
> >> > +    return box;
> >> > +}
> >> > +
> >> > +/* LUN properties dialog */
> >> > +
> >> > +typedef struct _lun_properties_dialog {
> >> > +    GtkWidget *dialog;
> >> > +    GtkWidget *advanced_grid;
> >> > +    gboolean advanced_shown;
> >> > +
> >> > +    GtkWidget *file_entry;
> >> > +    GtkWidget *vendor_entry;
> >> > +    GtkWidget *product_entry;
> >> > +    GtkWidget *revision_entry;
> >> > +    GtkWidget *loaded_switch;
> >> > +    GtkWidget *locked_switch;
> >> > +} lun_properties_dialog;
> >> > +
> >> > +#if 1
> >> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> >> > +{
> >> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
> >> > +    GtkFileChooserNative *native;
> >> > +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
> >> > +    gint res;
> >> > +
> >> > +    native = gtk_file_chooser_native_new("Choose File for USB CD",
> >> > +        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
> >> > +        action,
> >> > +        "_Open",
> >> > +        "_Cancel");
> >> > +
> >> > +    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
> >> > +    if (res == GTK_RESPONSE_ACCEPT) {
> >> > +        char *filename = gtk_file_chooser_get_filename(
> GTK_FILE_CHOOSER(native));
> >> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> >> > +        g_free(filename);
> >> > +    }
> >> > +    else {
> >> > +        gtk_widget_grab_focus(button);
> >> > +    }
> >> > +
> >> > +    g_object_unref(native);
> >> > +}
> >> > +#else
> >> > +// to be removed
> >> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
> >> > +{
> >> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
> >> > +    GtkWidget *dialog;
> >> > +    gint res;
> >> > +
> >> > +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
> >> > +
> GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
> >> > +
> GTK_FILE_CHOOSER_ACTION_OPEN,
> >> > +                                          "_Cancel",
> >> > +                                          GTK_RESPONSE_CANCEL,
> >> > +                                          "_Ok",
> >> > +                                          GTK_RESPONSE_ACCEPT,
> >> > +                                          NULL);
> >> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
> GTK_RESPONSE_ACCEPT);
> >> > +
> >> > +    res = gtk_dialog_run(GTK_DIALOG(dialog));
> >> > +    if (res == GTK_RESPONSE_ACCEPT) {
> >> > +        char *filename = gtk_file_chooser_get_filename(
> GTK_FILE_CHOOSER(dialog));
> >> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
> >> > +        g_free(filename);
> >> > +    }
> >> > +    gtk_widget_destroy(dialog);
> >> > +}
> >> > +#endif
> >> > +
> >> > +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget
> *widget,
> >> > +                                                       gboolean
> state, gpointer user_data)
> >> > +{
> >> > +    lun_properties_dialog *lun_dialog = user_data;
> >> > +
> >> > +    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
> >> > +    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
> >> > +
> >> > +    return FALSE; /* call default signal handler */
> >> > +}
> >> > +
> >> > +static void lun_properties_dialog_toggle_advanced(GtkWidget
> *widget, gpointer user_data)
> >> > +{
> >> > +    lun_properties_dialog *lun_dialog = user_data;
> >> > +
> >> > +    if (lun_dialog->advanced_shown) {
> >> > +        gtk_widget_hide(lun_dialog->advanced_grid);
> >> > +        lun_dialog->advanced_shown = FALSE;
> >> > +    } else {
> >> > +        gtk_widget_show_all(lun_dialog->advanced_grid);
> >> > +        lun_dialog->advanced_shown = TRUE;
> >> > +    }
> >> > +}
> >> > +
> >> > +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
> >> > +                                         GtkWidget *parent_window,
> >> > +                                         SpiceUsbDeviceLunInfo
> *lun_info,
> >> > +                                         lun_properties_dialog
> *lun_dialog)
> >> > +{
> >> > +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkWidget *dialog, *content_area;
> >> > +    GtkWidget *grid, *advanced_grid;
> >> > +    GtkWidget *file_entry, *choose_button;
> >> > +    GtkWidget *advanced_button, *advanced_icon;
> >> > +    GtkWidget *vendor_entry, *product_entry, *revision_entry;
> >> > +    GtkWidget *loaded_switch, *loaded_label;
> >> > +    GtkWidget *locked_switch, *locked_label;
> >> > +    gint nrow = 0;
> >> > +
> >> > +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" :
> "CD LUN Settings",
> >> > +                    GTK_WINDOW(parent_window),
> >> > +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
> /* flags */
> >> > +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
> >> > +                    "Cancel", GTK_RESPONSE_REJECT,
> >> > +                    NULL);
> >> > +
> >> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog),
> GTK_RESPONSE_ACCEPT);
> >> > +    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
> >> > +    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))),
> 12);
> >> > +
> >> > +    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
> >> > +
> >> > +    /* main grid - always visible */
> >> > +    grid = gtk_grid_new();
> >> > +    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
> >> > +    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
> >> > +    gtk_container_add(GTK_CONTAINER(content_area), grid);
> >> > +
> >> > +    /* File path label */
> >> > +    gtk_grid_attach(GTK_GRID(grid),
> >> > +            gtk_label_new("Select file or device"),
> >> > +            0, nrow++, // left top
> >> > +            7, 1); // width height
> >> > +
> >> > +    /* file/device path entry */
> >> > +    file_entry = gtk_entry_new();
> >> > +    gtk_widget_set_hexpand(file_entry, TRUE);
> >> > +    if (!lun_info) {
> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry),
> "file-path");
> >> > +    } else {
> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry),
> lun_info->file_path);
> >> > +        if (lun_info->loaded) {
> >> > +            gtk_editable_set_editable(GTK_EDITABLE(file_entry),
> FALSE);
> >> > +            gtk_widget_set_can_focus(file_entry, FALSE);
> >> > +        }
> >> > +    }
> >> > +    gtk_grid_attach(GTK_GRID(grid),
> >> > +            file_entry,
> >> > +            0, nrow, // left top
> >> > +            6, 1); // width height
> >> > +
> >> > +    /* choose button */
> >> > +    choose_button = gtk_button_new_with_mnemonic("_Choose File");
> >> > +    gtk_widget_set_hexpand(choose_button, FALSE);
> >> > +    g_signal_connect(GTK_BUTTON(choose_button),
> >> > +                     "clicked", G_CALLBACK(usb_cd_choose_file),
> file_entry);
> >> > +    if (lun_info && lun_info->loaded) {
> >> > +        gtk_widget_set_sensitive(choose_button, FALSE);
> >> > +        gtk_widget_set_can_focus(choose_button, FALSE);
> >> > +    }
> >> > +
> >> > +    gtk_grid_attach(GTK_GRID(grid),
> >> > +            choose_button,
> >> > +            6, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    /* advanced button */
> >> > +    advanced_button = gtk_button_new_with_label("Advanced");
> >> > +    gtk_button_set_relief(GTK_BUTTON(advanced_button),
> GTK_RELIEF_NONE);
> >> > +    advanced_icon = gtk_image_new_from_icon_name("preferences-system",
> GTK_ICON_SIZE_BUTTON);
> >> > +    gtk_button_set_image(GTK_BUTTON(advanced_button),
> advanced_icon);
> >> > +    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button),
> TRUE);
> >> > +    g_signal_connect(advanced_button, "clicked",
> G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
> >> > +
> >> > +    gtk_grid_attach(GTK_GRID(grid),
> >> > +            advanced_button,
> >> > +            0, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    /* advanced grid */
> >> > +    advanced_grid = gtk_grid_new();
> >> > +    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
> >> > +    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
> >> > +    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
> >> > +
> >> > +    /* horizontal separator */
> >> > +    gtk_container_add(GTK_CONTAINER(content_area),
> gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
> >> > +
> >> > +    /* pack advanced grid */
> >> > +    nrow = 0;
> >> > +
> >> > +    /* horizontal separator */
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> >> > +            0, nrow++, // left top
> >> > +            7, 1); // width height
> >> > +
> >> > +    /* product id labels */
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            gtk_label_new("Vendor"),
> >> > +            0, nrow, // left top
> >> > +            2, 1); // width height
> >> > +
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            gtk_label_new("Product"),
> >> > +            2, nrow, // left top
> >> > +            4, 1); // width height
> >> > +
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            gtk_label_new("Revision"),
> >> > +            6, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    /* vendor entry */
> >> > +    vendor_entry = gtk_entry_new();
> >> > +    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
> >> > +    if (lun_info) {
> >> > +        gtk_widget_set_sensitive(vendor_entry, FALSE);
> >> > +        gtk_widget_set_can_focus(vendor_entry, FALSE);
> >> > +        gtk_entry_set_text(GTK_ENTRY(vendor_entry),
> lun_info->vendor);
> >> > +    } else {
> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry),
> "auto");
> >> > +    }
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            vendor_entry,
> >> > +            0, nrow, // left top
> >> > +            2, 1); // width height
> >> > +
> >> > +    /* product entry */
> >> > +    product_entry = gtk_entry_new();
> >> > +    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
> >> > +    if (lun_info) {
> >> > +        gtk_widget_set_sensitive(product_entry, FALSE);
> >> > +        gtk_widget_set_can_focus(product_entry, FALSE);
> >> > +        gtk_entry_set_text(GTK_ENTRY(product_entry),
> lun_info->product);
> >> > +    } else {
> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry),
> "auto");
> >> > +    }
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            product_entry,
> >> > +            2, nrow, // left top
> >> > +            4, 1); // width height
> >> > +
> >> > +    /* revision entry */
> >> > +    revision_entry = gtk_entry_new();
> >> > +    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
> >> > +    if (lun_info) {
> >> > +        gtk_widget_set_sensitive(revision_entry, FALSE);
> >> > +        gtk_widget_set_can_focus(revision_entry, FALSE);
> >> > +        gtk_entry_set_text(GTK_ENTRY(revision_entry),
> lun_info->revision);
> >> > +    } else {
> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry),
> "auto");
> >> > +    }
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            revision_entry,
> >> > +            6, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    /* horizontal separator */
> >> > +    if (!lun_info) {
> >> > +        gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
> >> > +                0, nrow++, // left top
> >> > +                7, 1); // width height
> >> > +    }
> >> > +
> >> > +    /* initially loaded switch */
> >> > +    loaded_label = gtk_label_new("Initially loaded:");
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +        loaded_label,
> >> > +        0, nrow, // left top
> >> > +        2, 1); // width height
> >> > +
> >> > +    loaded_switch = gtk_switch_new();
> >> > +    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
> >> > +    if (lun_info) {
> >> > +        gtk_widget_set_child_visible(loaded_switch, FALSE);
> >> > +        gtk_widget_set_child_visible(loaded_label, FALSE);
> >> > +    } else {
> >> > +        g_signal_connect(loaded_switch, "state-set",
> >> > +                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb),
> lun_dialog);
> >> > +    }
> >> > +    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            loaded_switch,
> >> > +            2, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    /* initially locked switch */
> >> > +    locked_label = gtk_label_new("Initially locked:");
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +        locked_label,
> >> > +        0, nrow, // left top
> >> > +        2, 1); // width height
> >> > +
> >> > +    locked_switch = gtk_switch_new();
> >> > +    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
> >> > +    gtk_widget_set_hexpand(locked_switch, FALSE);
> >> > +    if (lun_info) {
> >> > +        gtk_widget_set_child_visible(locked_switch, FALSE);
> >> > +        gtk_widget_set_child_visible(locked_label, FALSE);
> >> > +    }
> >> > +    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
> >> > +            locked_switch,
> >> > +            2, nrow++, // left top
> >> > +            1, 1); // width height
> >> > +
> >> > +    lun_dialog->dialog = dialog;
> >> > +    lun_dialog->advanced_grid = advanced_grid;
> >> > +    lun_dialog->advanced_shown = FALSE;
> >> > +    lun_dialog->file_entry = file_entry;
> >> > +    lun_dialog->vendor_entry = vendor_entry;
> >> > +    lun_dialog->product_entry = product_entry;
> >> > +    lun_dialog->revision_entry = revision_entry;
> >> > +    lun_dialog->loaded_switch = loaded_switch;
> >> > +    lun_dialog->locked_switch = locked_switch;
> >> > +
> >> > +    gtk_widget_show_all(dialog);
> >> > +    gtk_widget_hide(advanced_grid);
> >> > +}
> >> > +
> >> > +static void lun_properties_dialog_get_info(lun_properties_dialog
> *lun_dialog,
> >> > +                                            SpiceUsbDeviceLunInfo
> *lun_info)
> >> > +{
> >> > +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(l
> un_dialog->file_entry));
> >> > +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(l
> un_dialog->vendor_entry));
> >> > +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(l
> un_dialog->product_entry));
> >> > +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(l
> un_dialog->revision_entry));
> >> > +    lun_info->loaded = gtk_switch_get_active(GTK_SWIT
> CH(lun_dialog->loaded_switch));
> >> > +    lun_info->locked = gtk_switch_get_active(GTK_SWIT
> CH(lun_dialog->locked_switch));
> >> > +}
> >> > +
> >> > +/* Popup menu */
> >> > +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer
> user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr
> iv->cd_tree.tree_view);
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +
> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> >> > +            SpiceUsbDevice *usb_device;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> >> > +            SPICE_DEBUG("%s - not applicable for USB device",
> __FUNCTION__);
> >> > +        }
> >> > +        else {
> >> > +            UsbWidgetLunItem *lun_item;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> >> > +            spice_usb_device_manager_device_lun_load(
> >> > +                lun_item->manager, lun_item->device, lun_item->lun,
> !lun_item->info.loaded);
> >> > +        }
> >> > +    }
> >> > +    else {
> >> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> >> > +    }
> >> > +}
> >> > +
> >> > +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer
> user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr
> iv->cd_tree.tree_view);
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +
> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> >> > +            SpiceUsbDevice *usb_device;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> >> > +            SPICE_DEBUG("%s - not applicable for USB device",
> __FUNCTION__);
> >> > +        }
> >> > +        else {
> >> > +            UsbWidgetLunItem *lun_item;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> >> > +            spice_usb_device_manager_device_lun_lock(
> >> > +                lun_item->manager, lun_item->device, lun_item->lun,
> !lun_item->info.locked);
> >> > +        }
> >> > +    }
> >> > +    else {
> >> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
> >> > +    }
> >> > +}
> >> > +
> >> > +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer
> user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr
> iv->cd_tree.tree_view);
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +
> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> >> > +            SpiceUsbDevice *usb_device;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&usb_device, -1);
> >> > +            SPICE_DEBUG("Remove USB device");
> >> > +        } else {
> >> > +            UsbWidgetLunItem *lun_item;
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> >> > +            gtk_tree_selection_unselect_all(select);
> >> > +            spice_usb_device_manager_devic
> e_lun_remove(lun_item->manager, lun_item->device, lun_item->lun);
> >> > +        }
> >> > +    } else {
> >> > +        SPICE_DEBUG("Remove - failed to get selection");
> >> > +    }
> >> > +}
> >> > +
> >> > +static void view_popup_menu_on_settings(GtkWidget *menuitem,
> gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr
> iv->cd_tree.tree_view);
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +
> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> >> > +            SPICE_DEBUG("No settings for USB device yet");
> >> > +        } else {
> >> > +            lun_properties_dialog lun_dialog;
> >> > +            UsbWidgetLunItem *lun_item;
> >> > +            gint resp;
> >> > +
> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA,
> (gpointer *)&lun_item, -1);
> >> > +            gtk_tree_selection_unselect_all(select);
> >> > +            create_lun_properties_dialog(self, NULL,
> &lun_item->info, &lun_dialog);
> >> > +
> >> > +            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> >> > +            if (resp == GTK_RESPONSE_ACCEPT) {
> >> > +                SpiceUsbDeviceLunInfo lun_info;
> >> > +                SPICE_DEBUG("response is ACCEPT");
> >> > +                lun_properties_dialog_get_info(&lun_dialog,
> &lun_info);
> >> > +                spice_usb_device_manager_device_lun_change_media(
> >> > +                    priv->manager, lun_item->device, lun_item->lun,
> &lun_info);
> >> > +            } else {
> >> > +                SPICE_DEBUG("response is REJECT");
> >> > +            }
> >> > +            gtk_widget_destroy(lun_dialog.dialog);
> >> > +        }
> >> > +    } else {
> >> > +        SPICE_DEBUG("Remove - failed to get selection");
> >> > +    }
> >> > +}
> >> > +
> >> > +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
> >> > +    const gchar *label_str,
> >> > +    const gchar *icon_name,
> >> > +    GCallback cb_func, gpointer user_data)
> >> > +{
> >> > +    GtkWidget *menu_item = gtk_menu_item_new();
> >> > +    create_image_button_box(label_str, icon_name, menu_item);
> >> > +    g_signal_connect(menu_item, "activate", cb_func, user_data);
> >> > +
> >> > +    gtk_widget_show_all(menu_item);
> >> > +    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
> >> > +
> >> > +    return menu_item;
> >> > +}
> >> > +
> >> > +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv,
> SpiceUsbDevice *device)
> >> > +{
> >> > +    gboolean result;
> >> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> >> > +    GArray *lun_array;
> >> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr,
> device);
> >> > +    result = lun_array && lun_array->len <= 1;
> >> > +    if (lun_array) {
> >> > +        g_array_unref(lun_array);
> >> > +    }
> >> > +    return result;
> >> > +}
> >> > +
> >> > +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton
> *event, gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeSelection *select;
> >> > +    GtkTreeModel *tree_model;
> >> > +    GtkTreeIter iter;
> >> > +    UsbWidgetLunItem *lun_item;
> >> > +    gboolean is_loaded, is_locked;
> >> > +    GtkTreeIter *usb_dev_iter;
> >> > +    gboolean is_dev_connected;
> >> > +    GtkWidget *menu;
> >> > +
> >> > +    if (tree_view != priv->cd_tree.tree_view) {
> >> > +        SPICE_DEBUG("Not applicable for USB device");
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    select = gtk_tree_view_get_selection(tree_view);
> >> > +
> >> > +    if (!gtk_tree_selection_get_selected(select, &tree_model,
> &iter)) {
> >> > +        SPICE_DEBUG("No tree view row is selected");
> >> > +        return;
> >> > +    }
> >> > +    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
> >> > +        SPICE_DEBUG("No settings for USB device yet");
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    gtk_tree_model_get(tree_model, &iter,
> >> > +                       COL_ITEM_DATA, (gpointer *)&lun_item,
> >> > +                       COL_LOADED, &is_loaded,
> >> > +                       COL_LOCKED, &is_locked,
> >> > +                       -1);
> >> > +
> >> > +    usb_dev_iter = usb_widget_tree_store_find_usb
> _device(priv->cd_tree.tree_store, lun_item->device);
> >> > +    if (usb_dev_iter != NULL) {
> >> > +        gtk_tree_model_get(tree_model, usb_dev_iter,
> >> > +                           COL_CONNECTED, &is_dev_connected,
> >> > +                           -1);
> >> > +        g_free(usb_dev_iter);
> >> > +    } else {
> >> > +        is_dev_connected = FALSE;
> >> > +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
> >> > +                    lun_item->info.vendor, lun_item->info.product);
> >> > +    }
> >> > +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun
> loaded:%d locked:%d",
> >> > +                lun_item->info.vendor, lun_item->info.product,
> >> > +                is_dev_connected, is_loaded, is_locked);
> >> > +
> >> > +    /* Set up the menu */
> >> > +    menu = gtk_menu_new();
> >> > +
> >> > +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
> >> > +                             G_CALLBACK(view_popup_menu_on_settings),
> user_data);
> >> > +    if (is_loaded) {
> >> > +        if (!is_locked) {
> >> > +            view_popup_add_menu_item(menu, "_Lock",
> "system-lock-screen",
> >> > +                                    G_CALLBACK(view_popup_menu_on_lock),
> user_data);
> >> > +            view_popup_add_menu_item(menu, "_Eject", "media-eject",
> >> > +                                     G_CALLBACK(view_popup_menu_on_eject),
> user_data);
> >> > +        } else {
> >> > +            view_popup_add_menu_item(menu, "_Unlock",
> "system-lock-screen",
> >> > +                                     G_CALLBACK(view_popup_menu_on_lock),
> user_data);
> >> > +        }
> >> > +    } else {
> >> > +        view_popup_add_menu_item(menu, "_Load", "media-eject",
> >> > +                                 G_CALLBACK(view_popup_menu_on_eject),
> user_data);
> >> > +    }
> >> > +
> >> > +    if (!is_dev_connected || has_single_lun(priv, lun_item->device))
> {
> >> > +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
> >> > +                                 G_CALLBACK(view_popup_menu_on_remove),
> user_data);
> >> > +    }
> >> > +
> >> > +    gtk_widget_show_all(menu);
> >> > +    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
> >> > +}
> >> > +
> >> > +static void treeview_select_current_row_by_pos(GtkTreeView
> *tree_view, gint x, gint y)
> >> > +{
> >> > +    GtkTreeSelection *selection = gtk_tree_view_get_selection(GT
> K_TREE_VIEW(tree_view));
> >> > +    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
> >> > +        GtkTreePath *path;
> >> > +        /* Get tree path for row that was clicked */
> >> > +        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view),
> x, y, &path, NULL, NULL, NULL))
> >> > +        {
> >> > +            gtk_tree_selection_unselect_all(selection);
> >> > +            gtk_tree_selection_select_path(selection, path);
> >> > +            gtk_tree_path_free(path);
> >> > +        }
> >> > +    }
> >> > +}
> >> > +
> >> > +static gboolean treeview_on_right_button_pressed_cb(GtkWidget
> *view, GdkEventButton *event, gpointer user_data)
> >> > +{
> >> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
> >> > +    /* single click with the right mouse button */
> >> > +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
> >> > +        /* select the row that was clicked, it will also provide the
> context */
> >> > +        treeview_select_current_row_by_pos(tree_view,
> (gint)event->x, (gint)event->y);
> >> > +        view_popup_menu(tree_view, event, user_data);
> >> > +        return TRUE; /* we handled this */
> >> > +    } else {
> >> > +        return FALSE; /* we did not handle this */
> >> > +    }
> >> > +}
> >> > +
> >> > +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view,
> gpointer user_data)
> >> > +{
> >> > +    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
> >> > +    return TRUE; /* we handled this */
> >> > +}
> >> > +
> >> > +/* Add LUN dialog */
> >> > +
> >> > +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button,
> gpointer user_data)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd
> _button);
> >> > +    lun_properties_dialog lun_dialog;
> >> > +    gint resp;
> >> > +
> >> > +    create_lun_properties_dialog(self, parent_window, NULL,
> &lun_dialog);
> >> > +
> >> > +    resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
> >> > +    if (resp == GTK_RESPONSE_ACCEPT) {
> >> > +        SpiceUsbDeviceLunInfo lun_info;
> >> > +        SPICE_DEBUG("response is ACCEPT");
> >> > +        lun_properties_dialog_get_info(&lun_dialog, &lun_info);
> >> > +        spice_usb_device_manager_add_cd_lun(priv->manager,
> &lun_info);
> >> > +    } else {
> >> > +        SPICE_DEBUG("response is REJECT");
> >> > +    }
> >> > +    gtk_widget_destroy(lun_dialog.dialog);
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_get_property(GObject
>  *gobject,
> >> > +                                                 guint
> prop_id,
> >> > +                                                 GValue      *value,
> >> > +                                                 GParamSpec  *pspec)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +
> >> > +    switch (prop_id) {
> >> > +    case PROP_SESSION:
> >> > +        g_value_set_object(value, priv->session);
> >> > +        break;
> >> > +    case PROP_DEVICE_FORMAT_STRING:
> >> > +        g_value_set_string(value, priv->device_format_string);
> >> > +        break;
> >> > +    default:
> >> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> >> > +        break;
> >> > +    }
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_set_property(GObject
>  *gobject,
> >> > +                                                 guint
> prop_id,
> >> > +                                                 const GValue
> *value,
> >> > +                                                 GParamSpec
> *pspec)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +
> >> > +    switch (prop_id) {
> >> > +    case PROP_SESSION:
> >> > +        priv->session = g_value_dup_object(value);
> >> > +        break;
> >> > +    case PROP_DEVICE_FORMAT_STRING:
> >> > +        priv->device_format_string = g_value_dup_string(value);
> >> > +        break;
> >> > +    default:
> >> > +        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
> >> > +        break;
> >> > +    }
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget
> *self)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +
> >> > +    if (priv->info_bar) {
> >> > +        gtk_widget_destroy(priv->info_bar);
> >> > +        priv->info_bar = NULL;
> >> > +    }
> >> > +}
> >> > +
> >> > +static void
> >> > +spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self,
> >> > +                                      const gchar          *message,
> >> > +                                      GtkMessageType
> message_type,
> >> > +                                      GdkPixbuf
> *icon_pixbuf)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;
> >> > +
> >> > +    spice_usb_device_widget_hide_info_bar(self);
> >> > +
> >> > +    info_bar = gtk_info_bar_new();
> >> > +    gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar),
> message_type);
> >> > +
> >> > +    content_area = gtk_info_bar_get_content_area(
> GTK_INFO_BAR(info_bar));
> >> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> >> > +    gtk_container_add(GTK_CONTAINER(content_area), hbox);
> >> > +
> >> > +    icon = gtk_image_new_from_pixbuf(icon_pixbuf);
> >> > +    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10);
> >> > +
> >> > +    label = gtk_label_new(message);
> >> > +    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
> >> > +
> >> > +    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);
> >> > +    gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0,
> 0, 0);
> >> > +    gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar);
> >> > +
> >> > +    gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE,
> 0);
> >> > +    gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put
> after the lable */
> >> > +    gtk_widget_show_all(priv->info_bar);
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_create
> _tree_view(SpiceUsbDeviceWidget *self, gboolean is_cd)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +    GtkTreeStore *tree_store = usb_widget_create_tree_store();
> >> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
> >> > +
> >> > +    if (is_cd) {
> >> > +        priv->cd_tree.tree_view = tree_view;
> >> > +        priv->cd_tree.tree_store = tree_store;
> >> > +    } else {
> >> > +        priv->usb_tree.tree_view = tree_view;
> >> > +        priv->usb_tree.tree_store = tree_store;
> >> > +    }
> >> > +
> >> > +    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store));
> >> > +    g_object_unref(tree_store); /* destroy tree_store automatically
> with tree_view */
> >> > +
> >> > +    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM,
> COL_CAN_REDIRECT,
> >> > +        is_cd ? tree_item_toggled_cb_redirect_cd :
> tree_item_toggled_cb_redirect_usb, is_cd);
> >> > +
> >> > +    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);
> >> > +
> >> > +    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT,
> is_cd);
> >> > +    if (is_cd) {
> >> > +        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);
> >> > +    }
> >> > +
> >> > +    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);
> >> > +    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);
> >> > +
> >> > +    if (is_cd) {
> >> > +        view_add_text_column(self, COL_FILE, TRUE, is_cd);
> >> > +        view_add_read_only_toggle_column(self, COL_LOADED,
> COL_LUN_ITEM, is_cd);
> >> > +        view_add_read_only_toggle_column(self, COL_LOCKED,
> COL_LUN_ITEM, is_cd);
> >> > +        // uncomment to show also 'idle' column for CD
> >> > +        //view_add_read_only_toggle_column(self, COL_IDLE,
> COL_LUN_ITEM, is_cd);
> >> > +    }
> >> > +
> >> > +    gtk_tree_selection_set_mode(
> >> > +            gtk_tree_view_get_selection(tree_view),
> >> > +            GTK_SELECTION_NONE);
> >> > +
> >> > +    if (is_cd) {
> >> > +        set_selection_handler(tree_view);
> >> > +    }
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget
> *self)
> >> > +{
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +
> >> > +    g_signal_connect(priv->manager, "device-added",
> >> > +                     G_CALLBACK(device_added_cb), self);
> >> > +    g_signal_connect(priv->manager, "device-removed",
> >> > +                     G_CALLBACK(device_removed_cb), self);
> >> > +    g_signal_connect(priv->manager, "device-changed",
> >> > +                     G_CALLBACK(device_changed_cb), self);
> >> > +    // TODO: connect failed
> >> > +    g_signal_connect(priv->manager, "device-error",
> >> > +                     G_CALLBACK(device_error_cb), self);
> >> > +
> >> > +    g_signal_connect(priv->cd_tree.tree_view, "button-press-event",
> >> > +                     G_CALLBACK(treeview_on_right_button_pressed_cb),
> self);
> >> > +    g_signal_connect(priv->cd_tree.tree_view, "popup-menu",
> >> > +                     G_CALLBACK(treeview_on_popup_key_pressed_cb),
> self);
> >> > +}
> >> > +
> >> > +static void create_tree_window(SpiceUsbDeviceWidget *self,
> GtkTreeView *tree_view)
> >> > +{
> >> > +    GtkWidget *sw;
> >> > +    /* scrolled window */
> >> > +    sw = gtk_scrolled_window_new(NULL, NULL);
> >> > +    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
> >> > +        GTK_SHADOW_ETCHED_IN);
> >> > +    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
> >> > +        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
> >> > +    gtk_widget_set_hexpand(sw, TRUE);
> >> > +    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
> >> > +    gtk_widget_set_vexpand(sw, TRUE);
> >> > +    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
> >> > +    gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view));
> >> > +    gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0);
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_constructed(GObject *gobject)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self;
> >> > +    GtkRequisition min_size, natural_size;
> >> > +    SpiceUsbDeviceWidgetPrivate *priv;
> >> > +    GtkWidget *hbox, *dev_label;
> >> > +    GtkWidget *add_cd_button, *add_cd_icon;
> >> > +    GPtrArray *devices = NULL;
> >> > +    GError *err = NULL;
> >> > +    gchar *str;
> >> > +    guint i;
> >> > +    gboolean cd_sharing_enabled = TRUE;
> >> > +    #ifndef USE_CD_SHARING
> >> > +    cd_sharing_enabled = FALSE;
> >> > +    #endif
> >> > +
> >> > +    self = SPICE_USB_DEVICE_WIDGET(gobject);
> >> > +    priv = self->priv;
> >> > +    if (!priv->session)
> >> > +        g_error("SpiceUsbDeviceWidget constructed without a
> session");
> >> > +
> >> > +    min_size.width = 600;
> >> > +    min_size.height = 300;
> >> > +    natural_size.width = 1200;
> >> > +    natural_size.height = 600;
> >> > +    gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size,
> &natural_size);
> >> > +
> >> > +    priv->label = gtk_label_new(NULL);
> >> > +    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to
> redirect"));
> >> > +    gtk_label_set_markup(GTK_LABEL(priv->label), str);
> >> > +    g_free(str);
> >> > +    gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0);
> >> > +
> >> > +    priv->icon_cd = get_named_icon("media-optical",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> >> > +    priv->icon_connected = get_named_icon("network-transmit-receive",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> >> > +    priv->icon_disconn = get_named_icon("network-offline",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> >> > +    priv->icon_warning = get_named_icon("dialog-warning",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> >> > +    priv->icon_info = get_named_icon("dialog-information",
> GTK_ICON_SIZE_LARGE_TOOLBAR);
> >> > +
> >> > +    priv->manager = spice_usb_device_manager_get(priv->session,
> &err);
> >> > +    if (err) {
> >> > +        spice_usb_device_widget_show_info_bar(self, err->message,
> >> > +                                              GTK_MESSAGE_WARNING,
> priv->icon_warning);
> >> > +        g_clear_error(&err);
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    if (cd_sharing_enabled) {
> >> > +        spice_usb_device_widget_create_tree_view(self, TRUE);
> >> > +    } else {
> >> > +        priv->cd_tree.tree_store = NULL;
> >> > +        priv->cd_tree.tree_view = NULL;
> >> > +    }
> >> > +    spice_usb_device_widget_create_tree_view(self, FALSE);
> >> > +
> >> > +    spice_usb_device_widget_signals_connect(self);
> >> > +
> >> > +    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> >> > +    gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> >> > +
> >> > +    /* "Available devices" label - in hbox */
> >> > +    dev_label = gtk_label_new(_("Local USB devices"));
> >> > +    gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0);
> >> > +    create_tree_window(self, priv->usb_tree.tree_view);
> >> > +
> >> > +    if (cd_sharing_enabled) {
> >> > +        hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
> >> > +        gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0);
> >> > +        dev_label = gtk_label_new(_("Shared CD devices"));
> >> > +        gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE,
> 0);
> >> > +        /* "Add CD" button - in hbox */
> >> > +        add_cd_button = gtk_button_new_with_label(_("Add CD"));
> >> > +        gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button),
> TRUE);
> >> > +        add_cd_icon = gtk_image_new_from_icon_name("list-add",
> GTK_ICON_SIZE_BUTTON);
> >> > +        gtk_button_set_image(GTK_BUTTON(add_cd_button),
> add_cd_icon);
> >> > +
> >> > +        gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END);
> >> > +        g_signal_connect(add_cd_button, "clicked",
> G_CALLBACK(add_cd_lun_button_clicked_cb), self);
> >> > +        gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE,
> FALSE, 0);
> >> > +        create_tree_window(self, priv->cd_tree.tree_view);
> >> > +    }
> >> > +    devices = spice_usb_device_manager_get_devices(priv->manager);
> >> > +    if (!devices)
> >> > +        goto end;
> >> > +
> >> > +    for (i = 0; i < devices->len; i++) {
> >> > +        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);
> >> > +        usb_widget_add_device(self, usb_device, NULL);
> >> > +    }
> >> > +    g_ptr_array_unref(devices);
> >> > +
> >> > +    select_widget_size(GTK_WIDGET(self));
> >> > +
> >> > +end:
> >> > +    spice_usb_device_widget_update_status(self);
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_finalize(GObject *object)
> >> > +{
> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object);
> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> > +
> >> > +    if (priv->manager) {
> >> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> >> > +                                             device_added_cb, self);
> >> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> >> > +                                             device_removed_cb,
> self);
> >> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> >> > +                                             device_changed_cb,
> self);
> >> > +        g_signal_handlers_disconnect_by_func(priv->manager,
> >> > +                                             device_error_cb, self);
> >> > +    }
> >> > +    g_object_unref(priv->session);
> >> > +    g_free(priv->device_format_string);
> >> > +
> >> > +    if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->final
> ize)
> >> > +        G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finali
> ze(object);
> >> > +}
> >> > +
> >> > +static void spice_usb_device_widget_class_init(
> >> > +    SpiceUsbDeviceWidgetClass *klass)
> >> > +{
> >> > +    GObjectClass *gobject_class = (GObjectClass *)klass;
> >> > +    GParamSpec *pspec;
> >> > +
> >> > +    g_type_class_add_private (klass, sizeof
> (SpiceUsbDeviceWidgetPrivate));
> >> > +
> >> > +    gobject_class->constructed  = spice_usb_device_widget_constr
> ucted;
> >> > +    gobject_class->finalize     = spice_usb_device_widget_finalize;
> >> > +    gobject_class->get_property = spice_usb_device_widget_get_pr
> operty;
> >> > +    gobject_class->set_property = spice_usb_device_widget_set_pr
> operty;
> >> > +
> >> > +    /**
> >> > +     * SpiceUsbDeviceWidget:session:
> >> > +     *
> >> > +     * #SpiceSession this #SpiceUsbDeviceWidget is associated with
> >> > +     *
> >> > +     **/
> >> > +    pspec = g_param_spec_object("session",
> >> > +                                "Session",
> >> > +                                "SpiceSession",
> >> > +                                SPICE_TYPE_SESSION,
> >> > +                                G_PARAM_CONSTRUCT_ONLY |
> G_PARAM_READWRITE |
> >> > +                                G_PARAM_STATIC_STRINGS);
> >> > +    g_object_class_install_property(gobject_class, PROP_SESSION,
> pspec);
> >> > +
> >> > +    /**
> >> > +     * SpiceUsbDeviceWidget:device-format-string:
> >> > +     *
> >> > +     * Format string to pass to spice_usb_device_get_descript_
> ______________________________________________
> >> Spice-devel mailing list
> >> Spice-devel@lists.freedesktop.org
> >> https://lists.freedesktop.org/mailman/listinfo/spice-devel
>
>
>
> --
> Marc-André Lureau
>
On Tue, Sep 18, 2018 at 11:03:56AM +0300, Yuri Benditovich wrote:
> Earlier the screenshot of the latest widget (current code) was sent to
> internal list for review and no objections were received.

For the record, I do have some objections, but I received the screenshot
at about the same time the patches were sent to the mailing list, so I
figured the discussion could happen there..

Christophe
Hi

On Tue, Sep 18, 2018 at 8:05 PM Yuri Benditovich
<yuri.benditovich@daynix.com> wrote:
>
>
>
> On Tue, Sep 18, 2018 at 12:15 PM, Marc-André Lureau <marcandre.lureau@gmail.com> wrote:
>>
>> Hi
>>
>> On Tue, Sep 18, 2018 at 12:04 PM Yuri Benditovich
>> <yuri.benditovich@daynix.com> wrote:
>> >
>> >
>> >
>> > On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <victortoso@redhat.com> wrote:
>> >>
>> >> On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
>> >> > New USB widget supports all the functionality related to
>> >> > redirection of local USB device (as previous usb widget did).
>> >> > Additionally it allows creation, management and redirection of
>> >> > emulated USB CD devices for CD sharing.
>> >>
>> >> Wouldn't be possible to extend the current widget instead of
>> >> creating a new one?
>> >
>> >
>> > Of course it was the first idea before creating new one.
>> > After several discussions we understood that there is almost nothing
>> > common between what we'd like to have for CD and what we have in existing widget.
>> > This also was decided on special meeting of Alexander with spice team (remove
>> > some controls and split it to two lists, locals and CD). Earlier the screenshot of
>> > the latest widget (current code) was sent to internal list for review and no objections
>> > were received.
>>
>> Ultimately the discussion and decision should take place on public mailing list.
>>
>> And many of us (including me), may not be directly involved in the RH
>> spice team.
>>
>> Could you detail the goals you had and why you had to write a new widget?
>
>
> Regarding new USB widget:
>
> User experience when USB CD drive is presented to user is wider than just
> plug/unplug the USB device.
>
> The CD may include several units, each of them can be managed separately.
> If more than one unit supported on CD, single CD with number of units requires
> just one redirection channel. [At the moment due to time restrictions, we define
> CD as single unit, but I think in the future we will want to enable multiple units on CD]
> So in common case the GUI for CD drives tends to be something like tree view.
> This easily serves also local USB devices as in GTK tree view == list view.
> Initially the widget was developed with single list containing all the devices
> when shared CD devices have leaves and local USB devices do not.
> IMHO, this kind of presentation is better than current one which presents local devices
> and CD devices separately.

Ok, let say I am biased, and I have opinions because I would use it
and this is stuff I am also maintaining:

- I have never used a pluggable CD drive.

- I have never used mulitple CD drive simultaneously.

> When CD drive is presented to the user on guest machine, it can be ejected by
> user action (from Gnome or Explorer). When the media is ejected, it can be changed
> on client side. So the status (ejected/loaded) should be visible and the user should be
> able to control this also on client side exactly as you can open/close the drive door manually.
>
> Drive lock. When CD is reqired for SW operation, the SW often locks
> the drive preventing user-initiated eject. In such case, if needed, the CD still can be
> manually ejected using clip. Another option related to 'lock' is that user may want
> to prevent removal of CD by guest software and lock it on client side.

- I have never used locking either.

>
> The user shall select the properties for logical CD unit and it is good to show them
> in the GUI. They include (at least):
> * file name (or device name) used as media

That's useful indeed

> * device attributes (vendor/product) which are used to report properties of SCSI device
> (these properties are visible to user in seabios boot screen, so in case of 2 CDs the user
> can recognize the needed one). Note that 2 CDs is typical case for Windows installation
> on VM with virtio.

You can switch the mounted CD for this case, no need for multiple
simultaneously mounted CD. I don't know a case where this is
necessary, except perhaps for direct CD copy, that used to be very
risky anyway..

> * Whether the CD media is currently loaded
>
> Visible status of the device is (IMHO) good for self support. When something does not
> work as expected and you can't see anything in the GUI that can explain this, the next step
> is opening issues, sending logs etc., i.e. spend own time and time of other people.
> I disagree with the statement 'less GUI is better'. Definitely, less GUI is simpler,
> but this simplicity often increases cost of ownership.

The UI in Boxes is doing the job quite fine for average user. See
attached screenshot.

>
> Our solid opinion is that even current CD additions are too heavy for existing widget.

In my opinion, we can make it much more user friendly. But we need to
understand what we want to support first. Why spice-gtk cd redirection
would need to be more complex than what Boxes or virt-manager provide?

>
> Thanks,
> Yuri
>
>>
>>
>> For UI, it's best to focus on the supported use case & the user
>> story/experience, and avoid feature creep (usually the less UI, the
>> better).
>>
>> >
>> >>
>> >>
>> >> > Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
>> >> > Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
>> >> > ---
>> >> >  src/usb-device-redir-widget.c | 2065 +++++++++++++++++++++++++++++++++++++++++
>> >> >  1 file changed, 2065 insertions(+)
>> >> >  create mode 100644 src/usb-device-redir-widget.c
>> >> >
>> >> > diff --git a/src/usb-device-redir-widget.c b/src/usb-device-redir-widget.c
>> >> > new file mode 100644
>> >> > index 0000000..59a6043
>> >> > --- /dev/null
>> >> > +++ b/src/usb-device-redir-widget.c
>> >> > @@ -0,0 +1,2065 @@
>> >> > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
>> >> > +/*
>> >> > +   Copyright (C) 2012 Red Hat, Inc.
>> >> > +
>> >> > +   Red Hat Authors:
>> >> > +   Alexander Nezhinsky<anezhins@redhat.com>
>> >> > +
>> >> > +   This library is free software; you can redistribute it and/or
>> >> > +   modify it under the terms of the GNU Lesser General Public
>> >> > +   License as published by the Free Software Foundation; either
>> >> > +   version 2.1 of the License, or (at your option) any later version.
>> >> > +
>> >> > +   This library 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
>> >> > +   Lesser General Public License for more details.
>> >> > +
>> >> > +   You should have received a copy of the GNU Lesser General Public
>> >> > +   License along with this library; if not, see <http://www.gnu.org/licenses/>.
>> >> > +*/
>> >> > +
>> >> > +#include "config.h"
>> >> > +#ifndef USB_WIDGET_TEST
>> >> > +    #include <glib/gi18n-lib.h>
>> >> > +    #include "spice-client.h"
>> >> > +    #include "spice-marshal.h"
>> >> > +#else
>> >> > +    #include "spice-client.h"
>> >> > +#endif
>> >> > +#include "usb-device-widget.h"
>> >> > +
>> >> > +/*
>> >> > +    Debugging note:
>> >> > +    Logging from this module is not affected by --spice-debug
>> >> > +    command line parameter
>> >> > +    Use SPICE_DEBUG=1 environment varible to enable logs
>> >> > +*/
>> >> > +
>> >> > +#ifdef USE_NEW_USB_WIDGET
>> >> > +
>> >> > +/**
>> >> > + * SECTION:usb-device-widget
>> >> > + * @short_description: USB device selection widget
>> >> > + * @title: Spice USB device selection widget
>> >> > + * @section_id:
>> >> > + * @see_also:
>> >> > + * @stability: Under development
>> >> > + * @include: spice-client-gtk.h
>> >> > + *
>> >> > + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily
>> >> > + * add an UI to select USB devices to redirect (or unredirect).
>> >> > + */
>> >> > +
>> >> > +struct _SpiceUsbDeviceWidget
>> >> > +{
>> >> > +    GtkBox parent;
>> >> > +
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv;
>> >> > +};
>> >> > +
>> >> > +struct _SpiceUsbDeviceWidgetClass
>> >> > +{
>> >> > +    GtkBoxClass parent_class;
>> >> > +
>> >> > +    /* signals */
>> >> > +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
>> >> > +                            SpiceUsbDevice *device, GError *error);
>> >> > +};
>> >> > +
>> >> > +/* ------------------------------------------------------------------ */
>> >> > +/* Prototypes for callbacks  */
>> >> > +static void device_added_cb(SpiceUsbDeviceManager *manager,
>> >> > +    SpiceUsbDevice *device, gpointer user_data);
>> >> > +static void device_removed_cb(SpiceUsbDeviceManager *manager,
>> >> > +    SpiceUsbDevice *device, gpointer user_data);
>> >> > +static void device_changed_cb(SpiceUsbDeviceManager *manager,
>> >> > +    SpiceUsbDevice *device, gpointer user_data);
>> >> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
>> >> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);
>> >> > +static gboolean spice_usb_device_widget_update_status(gpointer user_data);
>> >> > +
>> >> > +/* ------------------------------------------------------------------ */
>> >> > +/* gobject glue                                                       */
>> >> > +
>> >> > +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
>> >> > +    (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \
>> >> > +                                 SpiceUsbDeviceWidgetPrivate))
>> >> > +
>> >> > +enum {
>> >> > +    PROP_0,
>> >> > +    PROP_SESSION,
>> >> > +    PROP_DEVICE_FORMAT_STRING,
>> >> > +};
>> >> > +
>> >> > +enum {
>> >> > +    CONNECT_FAILED,
>> >> > +    LAST_SIGNAL,
>> >> > +};
>> >> > +
>> >> > +typedef struct {
>> >> > +    GtkTreeView *tree_view;
>> >> > +    GtkTreeStore *tree_store;
>> >> > +} SpiceUsbDeviceWidgetTree;
>> >> > +
>> >> > +struct _SpiceUsbDeviceWidgetPrivate {
>> >> > +    SpiceSession *session;
>> >> > +    gchar *device_format_string;
>> >> > +    SpiceUsbDeviceManager *manager;
>> >> > +    GtkWidget *info_bar;
>> >> > +    GtkWidget *label;
>> >> > +    SpiceUsbDeviceWidgetTree cd_tree;
>> >> > +    SpiceUsbDeviceWidgetTree usb_tree;
>> >> > +    GdkPixbuf *icon_cd;
>> >> > +    GdkPixbuf *icon_connected;
>> >> > +    GdkPixbuf *icon_disconn;
>> >> > +    GdkPixbuf *icon_warning;
>> >> > +    GdkPixbuf *icon_info;
>> >> > +    gchar *err_msg;
>> >> > +    gsize device_count;
>> >> > +};
>> >> > +
>> >> > +static guint signals[LAST_SIGNAL] = { 0, };
>> >> > +
>> >> > +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);
>> >> > +
>> >> > +/* TREE */
>> >> > +
>> >> > +enum column_id
>> >> > +{
>> >> > +    COL_REDIRECT = 0,
>> >> > +    COL_ADDRESS,
>> >> > +    COL_CONNECT_ICON,
>> >> > +    COL_CD_ICON,
>> >> > +    COL_VENDOR,
>> >> > +    COL_PRODUCT,
>> >> > +    COL_FILE,
>> >> > +    COL_LOADED,
>> >> > +    COL_LOCKED,
>> >> > +    COL_IDLE,
>> >> > +    /* internal columns */
>> >> > +    COL_REVISION,
>> >> > +    COL_CD_DEV,
>> >> > +    COL_LUN_ITEM,
>> >> > +    COL_DEV_ITEM,
>> >> > +    COL_ITEM_DATA,
>> >> > +    COL_CONNECTED,
>> >> > +    COL_CAN_REDIRECT,
>> >> > +    COL_ROW_COLOR,
>> >> > +    COL_ROW_COLOR_SET,
>> >> > +    NUM_COLS,
>> >> > +
>> >> > +    INVALID_COL
>> >> > +};
>> >> > +
>> >> > +// there is a possibility to use different names
>> >> > +// for columns in USB device list and CD device list, if needed
>> >> > +// currently they are identical
>> >> > +static const char *column_names_cd[NUM_COLS];
>> >> > +static const char *column_names_usb[NUM_COLS];
>> >> > +
>> >> > +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s
>> >> > +
>> >> > +static void initialize_columns(void)
>> >> > +{
>> >> > +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
>> >> > +    SET_COLUMN(COL_ADDRESS, _("Address"));
>> >> > +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
>> >> > +    SET_COLUMN(COL_CD_ICON, _("CD"));
>> >> > +    SET_COLUMN(COL_VENDOR, _("Vendor"));
>> >> > +    SET_COLUMN(COL_PRODUCT, _("Product"));
>> >> > +    SET_COLUMN(COL_FILE, _("File/Device Path"));
>> >> > +    SET_COLUMN(COL_LOADED, _("Loaded"));
>> >> > +    SET_COLUMN(COL_LOCKED, _("Locked"));
>> >> > +    SET_COLUMN(COL_IDLE, _("Idle"));
>> >> > +    SET_COLUMN(COL_REVISION, "?Revision");
>> >> > +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
>> >> > +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
>> >> > +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
>> >> > +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
>> >> > +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
>> >> > +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
>> >> > +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
>> >> > +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
>> >> > +};
>> >> > +
>> >> > +static const char *column_name(enum column_id id, gboolean is_cd)
>> >> > +{
>> >> > +    const char **col_name = is_cd ? column_names_cd : column_names_usb;
>> >> > +    return col_name[id];
>> >> > +}
>> >> > +
>> >> > +typedef struct _UsbWidgetLunItem {
>> >> > +    SpiceUsbDeviceManager *manager;
>> >> > +    SpiceUsbDevice *device;
>> >> > +    guint lun;
>> >> > +    SpiceUsbDeviceLunInfo info;
>> >> > +} UsbWidgetLunItem;
>> >> > +
>> >> > +typedef struct _TreeFindUsbDev {
>> >> > +    SpiceUsbDevice *usb_dev;
>> >> > +    GtkTreeIter dev_iter;
>> >> > +} TreeFindUsbDev;
>> >> > +
>> >> > +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *, gpointer);
>> >> > +
>> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
>> >> > +                                          SpiceUsbDevice *usb_device,
>> >> > +                                          GtkTreeIter *old_dev_iter);
>> >> > +
>> >> > +static gchar *usb_device_description(SpiceUsbDeviceManager *manager,
>> >> > +                                     SpiceUsbDevice *device,
>> >> > +                                     const gchar *format)
>> >> > +{
>> >> > +    SpiceUsbDeviceDescription desc;
>> >> > +    gchar *descriptor;
>> >> > +    gchar *res;
>> >> > +    spice_usb_device_get_info(manager, device, &desc);
>> >> > +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id);
>> >> > +    if (!format) {
>> >> > +        format = _("%s %s %s at %d-%d");
>> >> > +    }
>> >> > +    res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address);
>> >> > +    g_free(desc.vendor);
>> >> > +    g_free(desc.product);
>> >> > +    g_free(descriptor);
>> >> > +    return res;
>> >> > +}
>> >> > +
>> >> > +static GtkTreeStore* usb_widget_create_tree_store(void)
>> >> > +{
>> >> > +    GtkTreeStore *tree_store;
>> >> > +
>> >> > +    tree_store = gtk_tree_store_new(NUM_COLS,
>> >> > +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
>> >> > +                        G_TYPE_STRING, /* COL_ADDRESS */
>> >> > +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
>> >> > +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
>> >> > +                        G_TYPE_STRING, /* COL_VENDOR */
>> >> > +                        G_TYPE_STRING, /* COL_PRODUCT */
>> >> > +                        G_TYPE_STRING, /* COL_FILE */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_LOADED */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_IDLE */
>> >> > +                        /* internal columns */
>> >> > +                        G_TYPE_STRING, /* COL_REVISION */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
>> >> > +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
>> >> > +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
>> >> > +                        G_TYPE_STRING, /* COL_ROW_COLOR */
>> >> > +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
>> >> > +    SPICE_DEBUG("tree store created");
>> >> > +
>> >> > +    return tree_store;
>> >> > +}
>> >> > +
>> >> > +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
>> >> > +{
>> >> > +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), name, size, 0);
>> >> > +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
>> >> > +    g_object_unref (info);
>> >> > +    return pixbuf;
>> >> > +}
>> >> > +
>> >> > +static void select_widget_size(GtkWidget *wg)
>> >> > +{
>> >> > +    GdkDisplay *d = gtk_widget_get_display(wg);
>> >> > +    int i, w = 2000, h = 1024;
>> >> > +    int n = gdk_display_get_n_monitors(d);
>> >> > +    for (i = 0; i < n; ++i)
>> >> > +    {
>> >> > +        GdkMonitor *m = gdk_display_get_monitor(d, i);
>> >> > +        if (m) {
>> >> > +            GdkRectangle area;
>> >> > +            gdk_monitor_get_workarea(m, &area);
>> >> > +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
>> >> > +                i, area.width, area.height, area.x, area.y );
>> >> > +            w = MIN(w, area.width);
>> >> > +            h = MIN(h, area.height);
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    w = (w * 3) / 4;
>> >> > +    h = h / 2;
>> >> > +
>> >> > +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
>> >> > +    gtk_widget_set_size_request(wg, w, h);
>> >> > +}
>> >> > +
>> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
>> >> > +                                          SpiceUsbDevice *usb_device,
>> >> > +                                          GtkTreeIter *old_dev_iter)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
>> >> > +    SpiceUsbDeviceWidgetTree *tree;
>> >> > +    GtkTreeView *tree_view;
>> >> > +    GtkTreeStore *tree_store;
>> >> > +    GtkTreeIter new_dev_iter;
>> >> > +    SpiceUsbDeviceDescription dev_info;
>> >> > +    GtkTreePath *new_dev_path;
>> >> > +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
>> >> > +    gchar *addr_str;
>> >> > +    GArray *lun_array;
>> >> > +    guint lun_index;
>> >> > +    GError *error = NULL;
>> >> > +
>> >> > +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr, usb_device);
>> >> > +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
>> >> > +    tree_view = tree->tree_view;
>> >> > +    tree_store = tree->tree_store;
>> >> > +
>> >> > +    if (old_dev_iter == NULL) {
>> >> > +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
>> >> > +    } else {
>> >> > +        gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL, old_dev_iter);
>> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> >> > +    }
>> >> > +
>> >> > +    spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info);
>> >> > +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address);
>> >> > +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr, usb_device);
>> >> > +    is_dev_redirected = is_dev_connected;
>> >> > +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
>> >> > +        addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd);
>> >> > +
>> >> > +    gtk_tree_store_set(tree_store, &new_dev_iter,
>> >> > +        COL_REDIRECT, is_dev_redirected,
>> >> > +        COL_ADDRESS, addr_str,
>> >> > +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn,
>> >> > +        COL_CD_ICON, priv->icon_cd,
>> >> > +        COL_VENDOR, dev_info.vendor,
>> >> > +        COL_PRODUCT, dev_info.product,
>> >> > +        COL_CD_DEV, is_dev_cd,
>> >> > +        COL_LUN_ITEM, FALSE, /* USB device item */
>> >> > +        COL_DEV_ITEM, TRUE, /* USB device item */
>> >> > +        COL_ITEM_DATA, (gpointer)usb_device,
>> >> > +        COL_CONNECTED, is_dev_connected,
>> >> > +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr, usb_device, &error),
>> >> > +        COL_ROW_COLOR, "beige",
>> >> > +        COL_ROW_COLOR_SET, TRUE,
>> >> > +        -1);
>> >> > +    g_clear_error(&error);
>> >> > +
>> >> > +    priv->device_count++;
>> >> > +
>> >> > +    /* get all the luns */
>> >> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, usb_device);
>> >> > +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
>> >> > +        UsbWidgetLunItem *lun_item;
>> >> > +        GtkTreeIter lun_iter;
>> >> > +        gchar lun_str[8];
>> >> > +
>> >> > +        lun_item = g_malloc(sizeof(*lun_item));
>> >> > +        lun_item->manager = usb_dev_mgr;
>> >> > +        lun_item->device = usb_device;
>> >> > +        lun_item->lun = g_array_index(lun_array, guint, lun_index);
>> >> > +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr, usb_device, lun_item->lun, &lun_item->info);
>> >> > +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",
>> >> > +                lun_index, lun_item->info.vendor, lun_item->info.product,
>> >> > +                lun_item->info.revision, lun_item->info.file_path, lun_item);
>> >> > +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
>> >> > +
>> >> > +        /* Append LUN as a child of USB device */
>> >> > +        gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter);
>> >> > +        gtk_tree_store_set(tree_store, &lun_iter,
>> >> > +                COL_ADDRESS, lun_str,
>> >> > +                COL_VENDOR, lun_item->info.vendor,
>> >> > +                COL_PRODUCT, lun_item->info.product,
>> >> > +                COL_REVISION, lun_item->info.revision,
>> >> > +                COL_FILE, lun_item->info.file_path,
>> >> > +                COL_LOADED, lun_item->info.loaded,
>> >> > +                COL_LOCKED, lun_item->info.locked,
>> >> > +                COL_IDLE, !lun_item->info.started,
>> >> > +                COL_CD_DEV, FALSE,
>> >> > +                COL_LUN_ITEM, TRUE, /* LUN item */
>> >> > +                COL_DEV_ITEM, FALSE, /* LUN item */
>> >> > +                COL_ITEM_DATA, (gpointer)lun_item,
>> >> > +                COL_CONNECTED, is_dev_connected,
>> >> > +                COL_ROW_COLOR, "azure",
>> >> > +                COL_ROW_COLOR_SET, TRUE,
>> >> > +                -1);
>> >> > +    }
>> >> > +    g_array_unref(lun_array);
>> >> > +
>> >> > +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &new_dev_iter);
>> >> > +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
>> >> > +    gtk_tree_path_free(new_dev_path);
>> >> > +
>> >> > +    g_free(dev_info.vendor);
>> >> > +    g_free(dev_info.product);
>> >> > +    g_free(addr_str);
>> >> > +}
>> >> > +
>> >> > +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter)
>> >> > +{
>> >> > +    gboolean is_lun;
>> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);
>> >> > +    return is_lun;
>> >> > +}
>> >> > +
>> >> > +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel *tree_model,
>> >> > +                                                              GtkTreePath *path, GtkTreeIter *iter,
>> >> > +                                                              gpointer user_data)
>> >> > +{
>> >> > +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
>> >> > +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
>> >> > +    SpiceUsbDevice *usb_device;
>> >> > +    gboolean is_lun_item;
>> >> > +
>> >> > +    gtk_tree_model_get(tree_model, iter,
>> >> > +                       COL_LUN_ITEM, &is_lun_item,
>> >> > +                       COL_ITEM_DATA, (gpointer *)&usb_device,
>> >> > +                       -1);
>> >> > +    if (!is_lun_item && usb_device == find_usb_device) {
>> >> > +        find_info->dev_iter = *iter;
>> >> > +        find_info->usb_dev  = NULL;
>> >> > +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
>> >> > +        return TRUE; /* stop iterating */
>> >> > +    } else {
>> >> > +        return FALSE; /* continue iterating */
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore *tree_store,
>> >> > +                                                          SpiceUsbDevice *usb_device)
>> >> > +{
>> >> > +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };
>> >> > +    GtkTreeIter *iter = NULL;
>> >> > +
>> >> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
>> >> > +                           usb_widget_tree_store_find_usb_dev_foreach_cb, (gpointer)&find_info);
>> >> > +    // the callback sets 'usb_dev' field to zero if it finds the device
>> >> > +    if (!find_info.usb_dev) {
>> >> > +        iter = g_malloc(sizeof(*iter));
>> >> > +        *iter = find_info.dev_iter;
>> >> > +    }
>> >> > +    return iter;
>> >> > +}
>> >> > +
>> >> > +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self,
>> >> > +                                         SpiceUsbDevice *usb_device)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeIter *old_dev_iter;
>> >> > +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
>> >> > +    // on WIN32 it is possible the backend device is already removed
>> >> > +    // from the USB device manager list and we can't know it was
>> >> > +    // USB or CD, do we will try both lists
>> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> >> > +    if (old_dev_iter != NULL) {
>> >> > +        SPICE_DEBUG("USB Device removed");
>> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> >> > +        priv->device_count--;
>> >> > +        g_free(old_dev_iter);
>> >> > +        return TRUE;
>> >> > +    }
>> >> > +    tree_store = priv->cd_tree.tree_store;
>> >> > +    if (tree_store) {
>> >> > +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> >> > +    }
>> >> > +    if (old_dev_iter != NULL) {
>> >> > +        SPICE_DEBUG("CD Device removed");
>> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
>> >> > +        priv->device_count--;
>> >> > +        g_free(old_dev_iter);
>> >> > +        return TRUE;
>> >> > +    }
>> >> > +    SPICE_DEBUG("Device %p not found!", usb_device);
>> >> > +    return FALSE;
>> >> > +}
>> >> > +
>> >> > +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget *self,
>> >> > +                                                 enum column_id toggle_col_id,
>> >> > +                                                 enum column_id visible_col_id,
>> >> > +                                                 enum column_id sensitive_col_id,
>> >> > +                                                 tree_item_toggled_cb toggled_cb,
>> >> > +                                                 gboolean is_cd)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkCellRenderer     *renderer;
>> >> > +    GtkTreeViewColumn   *view_col;
>> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
>> >> > +
>> >> > +    renderer = gtk_cell_renderer_toggle_new();
>> >> > +
>> >> > +    if (sensitive_col_id != INVALID_COL) {
>> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                        col_name,
>> >> > +                        renderer,
>> >> > +                        "active", toggle_col_id,
>> >> > +                        "visible", visible_col_id,
>> >> > +                        "activatable", sensitive_col_id,
>> >> > +                        NULL);
>> >> > +    } else {
>> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                        col_name,
>> >> > +                        renderer,
>> >> > +                        "active", toggle_col_id,
>> >> > +                        "visible", visible_col_id,
>> >> > +                        NULL);
>> >> > +    }
>> >> > +
>> >> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
>> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
>> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
>> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> >> > +
>> >> > +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);
>> >> > +
>> >> > +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]",
>> >> > +            toggle_col_id, col_name,
>> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
>> >> > +    return view_col;
>> >> > +}
>> >> > +
>> >> > +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget *self,
>> >> > +                                                           enum column_id toggle_col_id,
>> >> > +                                                           enum column_id visible_col_id,
>> >> > +                                                           gboolean is_cd)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkCellRenderer     *renderer;
>> >> > +    GtkTreeViewColumn   *view_col;
>> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
>> >> > +
>> >> > +    renderer = gtk_cell_renderer_toggle_new();
>> >> > +
>> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                    col_name,
>> >> > +                    renderer,
>> >> > +                    "active", toggle_col_id,
>> >> > +                    "visible", visible_col_id,
>> >> > +                    NULL);
>> >> > +
>> >> > +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
>> >> > +
>> >> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);
>> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
>> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
>> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> >> > +
>> >> > +    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]",
>> >> > +            toggle_col_id, col_name,
>> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
>> >> > +    return view_col;
>> >> > +}
>> >> > +
>> >> > +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget *self,
>> >> > +                                               enum column_id col_id,
>> >> > +                                               gboolean expandable,
>> >> > +                                               gboolean is_cd)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkCellRenderer     *renderer;
>> >> > +    GtkTreeViewColumn   *view_col;
>> >> > +
>> >> > +    renderer = gtk_cell_renderer_text_new();
>> >> > +
>> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                    column_name(col_id, is_cd),
>> >> > +                    renderer,
>> >> > +                    "text", col_id,
>> >> > +                    //"cell-background", COL_ROW_COLOR,
>> >> > +                    //"cell-background-set", COL_ROW_COLOR_SET,
>> >> > +                    NULL);
>> >> > +
>> >> > +    gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
>> >> > +    gtk_tree_view_column_set_resizable(view_col, TRUE);
>> >> > +    gtk_tree_view_column_set_expand(view_col, expandable);
>> >> > +
>> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> >> > +
>> >> > +    SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd));
>> >> > +    return view_col;
>> >> > +}
>> >> > +
>> >> > +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget *self,
>> >> > +                                                 enum column_id col_id,
>> >> > +                                                 enum column_id visible_col_id,
>> >> > +                                                 gboolean is_cd)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkCellRenderer     *renderer;
>> >> > +    GtkTreeViewColumn   *view_col;
>> >> > +    const char *col_name = column_name(col_id, is_cd);
>> >> > +
>> >> > +    renderer = gtk_cell_renderer_pixbuf_new();
>> >> > +
>> >> > +    if (visible_col_id == INVALID_COL) {
>> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                        col_name,
>> >> > +                        renderer,
>> >> > +                        "pixbuf", col_id,
>> >> > +                        NULL);
>> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name);
>> >> > +    } else {
>> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
>> >> > +                        col_name,
>> >> > +                        renderer,
>> >> > +                        "pixbuf", col_id,
>> >> > +                        "visible", visible_col_id,
>> >> > +                        NULL);
>> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]",
>> >> > +                col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd));
>> >> > +    }
>> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);
>> >> > +    return view_col;
>> >> > +}
>> >> > +
>> >> > +/* Toggle handlers */
>> >> > +
>> >> > +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
>> >> > +{
>> >> > +    gboolean toggle_val;
>> >> > +
>> >> > +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store), iter, path_str);
>> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id, &toggle_val, -1);
>> >> > +
>> >> > +    return toggle_val;
>> >> > +}
>> >> > +
>> >> > +static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
>> >> > +{
>> >> > +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
>> >> > +}
>> >> > +
>> >> > +typedef struct _connect_cb_data {
>> >> > +    SpiceUsbDeviceWidget *self;
>> >> > +    SpiceUsbDevice *usb_dev;
>> >> > +} connect_cb_data;
>> >> > +
>> >> > +static void connect_cb_data_free(connect_cb_data *user_data)
>> >> > +{
>> >> > +    spice_usb_device_widget_update_status(user_data->self);
>> >> > +    g_object_unref(user_data->self);
>> >> > +    g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev);
>> >> > +    g_free(user_data);
>> >> > +}
>> >> > +
>> >> > +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
>> >> > +{
>> >> > +    connect_cb_data *cb_data = user_data;
>> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
>> >> > +    GError *err = NULL;
>> >> > +    GtkTreeIter *dev_iter;
>> >> > +    gchar *desc;
>> >> > +    gboolean finished;
>> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
>> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> >> > +
>> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
>> >> > +    if (!dev_iter) {
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
>> >> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
>> >> > +
>> >> > +    finished = spice_usb_device_manager_connect_device_finish(priv->manager, res, &err);
>> >> > +    if (finished) {
>> >> > +        gtk_tree_store_set(tree_store, dev_iter,
>> >> > +                           COL_CONNECT_ICON, priv->icon_connected,
>> >> > +                           COL_CONNECTED, TRUE,
>> >> > +                           -1);
>> >> > +    } else {
>> >> > +        gtk_tree_store_set(tree_store, dev_iter,
>> >> > +                           COL_REDIRECT, FALSE,
>> >> > +                           -1);
>> >> > +        g_prefix_error(&err, "Device connect failed %s: ", desc);
>> >> > +        if (err) {
>> >> > +            SPICE_DEBUG("%s", err->message);
>> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err);
>> >> > +            g_error_free(err);
>> >> > +        } else {
>> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL);
>> >> > +        }
>> >> > +
>> >> > +        /* don't trigger a disconnect if connect failed */
>> >> > +        /*
>> >> > +        g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data->check),
>> >> > +                                        checkbox_clicked_cb, self);
>> >> > +        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check), FALSE);
>> >> > +        g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_data->check),
>> >> > +                                        checkbox_clicked_cb, self);
>> >> > +        */
>> >> > +    }
>> >> > +    g_free(desc);
>> >> > +    g_free(dev_iter);
>> >> > +    connect_cb_data_free(user_data);
>> >> > +}
>> >> > +
>> >> > +static void usb_widget_disconnect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
>> >> > +{
>> >> > +    connect_cb_data *cb_data = user_data;
>> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
>> >> > +    GError *err = NULL;
>> >> > +    GtkTreeIter *dev_iter;
>> >> > +    gchar *desc;
>> >> > +    gboolean finished;
>> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev);
>> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> >> > +
>> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev);
>> >> > +    if (!dev_iter) {
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string);
>> >> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
>> >> > +
>> >> > +    finished = spice_usb_device_manager_disconnect_device_finish(priv->manager, res, &err);
>> >> > +    if (finished) {
>> >> > +        gtk_tree_store_set(tree_store, dev_iter,
>> >> > +                           COL_CONNECT_ICON, priv->icon_disconn,
>> >> > +                           COL_CONNECTED, FALSE,
>> >> > +                           -1);
>> >> > +    } else {
>> >> > +        gtk_tree_store_set(tree_store, dev_iter,
>> >> > +                           COL_REDIRECT, TRUE,
>> >> > +                           -1);
>> >> > +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);
>> >> > +        if (err) {
>> >> > +            SPICE_DEBUG("%s", err->message);
>> >> > +            g_error_free(err);
>> >> > +        }
>> >> > +    }
>> >> > +    g_free(desc);
>> >> > +    g_free(dev_iter);
>> >> > +    connect_cb_data_free(user_data);
>> >> > +}
>> >> > +
>> >> > +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell,
>> >> > +                                          gchar *path_str,
>> >> > +                                          SpiceUsbDeviceWidget *self,
>> >> > +                                          GtkTreeStore *tree_store)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
>> >> > +    SpiceUsbDevice *usb_dev;
>> >> > +    GtkTreeIter iter;
>> >> > +    gboolean new_redirect_val;
>> >> > +
>> >> > +    new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str, &iter, COL_REDIRECT);
>> >> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
>> >> > +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT, new_redirect_val);
>> >> > +
>> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
>> >> > +    cb_data->self = g_object_ref(self);
>> >> > +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(), usb_dev);
>> >> > +
>> >> > +    if (new_redirect_val) {
>> >> > +        spice_usb_device_manager_connect_device_async(priv->manager, usb_dev,
>> >> > +                                                      NULL, /* cancellable */
>> >> > +                                                      usb_widget_connect_cb, cb_data);
>> >> > +    } else {
>> >> > +        spice_usb_device_manager_disconnect_device_async(priv->manager, usb_dev,
>> >> > +                                                         NULL, /* cancellable */
>> >> > +                                                         usb_widget_disconnect_cb, cb_data);
>> >> > +
>> >> > +    }
>> >> > +    spice_usb_device_widget_update_status(self);
>> >> > +}
>> >> > +
>> >> > +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle *cell,
>> >> > +                                             gchar *path_str,
>> >> > +                                             gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->cd_tree.tree_store);
>> >> > +}
>> >> > +
>> >> > +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle *cell,
>> >> > +                                             gchar *path_str,
>> >> > +                                             gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self, priv->usb_tree.tree_store);
>> >> > +}
>> >> > +
>> >> > +/* Signal handlers */
>> >> > +
>> >> > +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +
>> >> > +    SPICE_DEBUG("Signal: Device Added");
>> >> > +
>> >> > +    usb_widget_add_device(self, usb_device, NULL);
>> >> > +
>> >> > +    spice_usb_device_widget_update_status(self);
>> >> > +}
>> >> > +
>> >> > +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    gboolean dev_removed;
>> >> > +
>> >> > +    SPICE_DEBUG("Signal: Device Removed");
>> >> > +
>> >> > +    dev_removed = usb_widget_remove_device(self, usb_device);
>> >> > +    if (dev_removed) {
>> >> > +        spice_usb_device_widget_update_status(self);
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
>> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeIter *old_dev_iter;
>> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
>> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> >> > +
>> >> > +    SPICE_DEBUG("Signal: Device Changed");
>> >> > +
>> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> >> > +    if (old_dev_iter != NULL) {
>> >> > +
>> >> > +        usb_widget_add_device(self, usb_device, old_dev_iter);
>> >> > +
>> >> > +        spice_usb_device_widget_update_status(self);
>> >> > +        g_free(old_dev_iter);
>> >> > +    } else {
>> >> > +        SPICE_DEBUG("Device not found!");
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
>> >> > +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeIter *dev_iter;
>> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device);
>> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;
>> >> > +
>> >> > +    SPICE_DEBUG("Signal: Device Error");
>> >> > +
>> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device);
>> >> > +    if (dev_iter != NULL) {
>> >> > +        tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT, FALSE);
>> >> > +        spice_usb_device_widget_update_status(self);
>> >> > +        g_free(dev_iter);
>> >> > +    } else {
>> >> > +        SPICE_DEBUG("Device not found!");
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +/* Selection handler */
>> >> > +
>> >> > +static void tree_selection_changed_cb(GtkTreeSelection *select, gpointer user_data)
>> >> > +{
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +    GtkTreePath *path;
>> >> > +    gboolean is_lun;
>> >> > +    UsbWidgetLunItem *lun_item;
>> >> > +    gchar *txt[3];
>> >> > +
>> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        gtk_tree_model_get(tree_model, &iter,
>> >> > +                COL_VENDOR, &txt[0],
>> >> > +                COL_PRODUCT, &txt[1],
>> >> > +                COL_REVISION, &txt[2],
>> >> > +                COL_LUN_ITEM, &is_lun,
>> >> > +                COL_ITEM_DATA, (gpointer *)&lun_item,
>> >> > +                -1);
>> >> > +        path = gtk_tree_model_get_path(tree_model, &iter);
>> >> > +
>> >> > +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",
>> >> > +                txt[0], txt[1],
>> >> > +                is_lun ? txt[2] : "--",
>> >> > +                is_lun ? "LUN" : "USB-DEV",
>> >> > +                is_lun ? lun_item->info.file_path : "--",
>> >> > +                gtk_tree_path_to_string(path));
>> >> > +
>> >> > +        if (txt[0]) {
>> >> > +            g_free(txt[0]);
>> >> > +        }
>> >> > +        if (txt[1]) {
>> >> > +            g_free(txt[1]);
>> >> > +        }
>> >> > +        if (txt[2]) {
>> >> > +            g_free(txt[2]);
>> >> > +        }
>> >> > +        gtk_tree_path_free(path);
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view)
>> >> > +{
>> >> > +    GtkTreeSelection *select;
>> >> > +
>> >> > +    select = gtk_tree_view_get_selection(tree_view);
>> >> > +    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
>> >> > +
>> >> > +    g_signal_connect(G_OBJECT(select), "changed",
>> >> > +                     G_CALLBACK(tree_selection_changed_cb),
>> >> > +                     NULL);
>> >> > +
>> >> > +    SPICE_DEBUG("selection handler set");
>> >> > +    return select;
>> >> > +}
>> >> > +
>> >> > +static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent)
>> >> > +{
>> >> > +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
>> >> > +    GtkWidget *icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
>> >> > +    GtkWidget *label = gtk_accel_label_new(label_str);
>> >> > +    GtkAccelGroup *accel_group = gtk_accel_group_new();
>> >> > +    guint accel_key;
>> >> > +
>> >> > +    /* add icon */
>> >> > +    gtk_container_add(GTK_CONTAINER(box), icon);
>> >> > +
>> >> > +    /* add label */
>> >> > +    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
>> >> > +    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
>> >> > +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);
>> >> > +    gtk_widget_add_accelerator(parent, "activate", accel_group, accel_key,
>> >> > +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
>> >> > +    gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent);
>> >> > +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);
>> >> > +
>> >> > +    /* add the new box to the parent widget */
>> >> > +    gtk_container_add(GTK_CONTAINER(parent), box);
>> >> > +
>> >> > +    return box;
>> >> > +}
>> >> > +
>> >> > +/* LUN properties dialog */
>> >> > +
>> >> > +typedef struct _lun_properties_dialog {
>> >> > +    GtkWidget *dialog;
>> >> > +    GtkWidget *advanced_grid;
>> >> > +    gboolean advanced_shown;
>> >> > +
>> >> > +    GtkWidget *file_entry;
>> >> > +    GtkWidget *vendor_entry;
>> >> > +    GtkWidget *product_entry;
>> >> > +    GtkWidget *revision_entry;
>> >> > +    GtkWidget *loaded_switch;
>> >> > +    GtkWidget *locked_switch;
>> >> > +} lun_properties_dialog;
>> >> > +
>> >> > +#if 1
>> >> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
>> >> > +{
>> >> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
>> >> > +    GtkFileChooserNative *native;
>> >> > +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
>> >> > +    gint res;
>> >> > +
>> >> > +    native = gtk_file_chooser_native_new("Choose File for USB CD",
>> >> > +        GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
>> >> > +        action,
>> >> > +        "_Open",
>> >> > +        "_Cancel");
>> >> > +
>> >> > +    res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
>> >> > +    if (res == GTK_RESPONSE_ACCEPT) {
>> >> > +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native));
>> >> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
>> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
>> >> > +        g_free(filename);
>> >> > +    }
>> >> > +    else {
>> >> > +        gtk_widget_grab_focus(button);
>> >> > +    }
>> >> > +
>> >> > +    g_object_unref(native);
>> >> > +}
>> >> > +#else
>> >> > +// to be removed
>> >> > +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)
>> >> > +{
>> >> > +    GtkWidget *file_entry = (GtkWidget *)user_data;
>> >> > +    GtkWidget *dialog;
>> >> > +    gint res;
>> >> > +
>> >> > +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",
>> >> > +                                          GTK_WINDOW(gtk_widget_get_toplevel(file_entry)),
>> >> > +                                          GTK_FILE_CHOOSER_ACTION_OPEN,
>> >> > +                                          "_Cancel",
>> >> > +                                          GTK_RESPONSE_CANCEL,
>> >> > +                                          "_Ok",
>> >> > +                                          GTK_RESPONSE_ACCEPT,
>> >> > +                                          NULL);
>> >> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
>> >> > +
>> >> > +    res = gtk_dialog_run(GTK_DIALOG(dialog));
>> >> > +    if (res == GTK_RESPONSE_ACCEPT) {
>> >> > +        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
>> >> > +        gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1);
>> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
>> >> > +        g_free(filename);
>> >> > +    }
>> >> > +    gtk_widget_destroy(dialog);
>> >> > +}
>> >> > +#endif
>> >> > +
>> >> > +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget *widget,
>> >> > +                                                       gboolean state, gpointer user_data)
>> >> > +{
>> >> > +    lun_properties_dialog *lun_dialog = user_data;
>> >> > +
>> >> > +    gtk_widget_set_sensitive(lun_dialog->locked_switch, state);
>> >> > +    gtk_widget_set_can_focus(lun_dialog->locked_switch, state);
>> >> > +
>> >> > +    return FALSE; /* call default signal handler */
>> >> > +}
>> >> > +
>> >> > +static void lun_properties_dialog_toggle_advanced(GtkWidget *widget, gpointer user_data)
>> >> > +{
>> >> > +    lun_properties_dialog *lun_dialog = user_data;
>> >> > +
>> >> > +    if (lun_dialog->advanced_shown) {
>> >> > +        gtk_widget_hide(lun_dialog->advanced_grid);
>> >> > +        lun_dialog->advanced_shown = FALSE;
>> >> > +    } else {
>> >> > +        gtk_widget_show_all(lun_dialog->advanced_grid);
>> >> > +        lun_dialog->advanced_shown = TRUE;
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self,
>> >> > +                                         GtkWidget *parent_window,
>> >> > +                                         SpiceUsbDeviceLunInfo *lun_info,
>> >> > +                                         lun_properties_dialog *lun_dialog)
>> >> > +{
>> >> > +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkWidget *dialog, *content_area;
>> >> > +    GtkWidget *grid, *advanced_grid;
>> >> > +    GtkWidget *file_entry, *choose_button;
>> >> > +    GtkWidget *advanced_button, *advanced_icon;
>> >> > +    GtkWidget *vendor_entry, *product_entry, *revision_entry;
>> >> > +    GtkWidget *loaded_switch, *loaded_label;
>> >> > +    GtkWidget *locked_switch, *locked_label;
>> >> > +    gint nrow = 0;
>> >> > +
>> >> > +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings",
>> >> > +                    GTK_WINDOW(parent_window),
>> >> > +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, /* flags */
>> >> > +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,
>> >> > +                    "Cancel", GTK_RESPONSE_REJECT,
>> >> > +                    NULL);
>> >> > +
>> >> > +    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
>> >> > +    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
>> >> > +    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
>> >> > +
>> >> > +    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
>> >> > +
>> >> > +    /* main grid - always visible */
>> >> > +    grid = gtk_grid_new();
>> >> > +    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
>> >> > +    gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
>> >> > +    gtk_container_add(GTK_CONTAINER(content_area), grid);
>> >> > +
>> >> > +    /* File path label */
>> >> > +    gtk_grid_attach(GTK_GRID(grid),
>> >> > +            gtk_label_new("Select file or device"),
>> >> > +            0, nrow++, // left top
>> >> > +            7, 1); // width height
>> >> > +
>> >> > +    /* file/device path entry */
>> >> > +    file_entry = gtk_entry_new();
>> >> > +    gtk_widget_set_hexpand(file_entry, TRUE);
>> >> > +    if (!lun_info) {
>> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry), "file-path");
>> >> > +    } else {
>> >> > +        gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path);
>> >> > +        if (lun_info->loaded) {
>> >> > +            gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE);
>> >> > +            gtk_widget_set_can_focus(file_entry, FALSE);
>> >> > +        }
>> >> > +    }
>> >> > +    gtk_grid_attach(GTK_GRID(grid),
>> >> > +            file_entry,
>> >> > +            0, nrow, // left top
>> >> > +            6, 1); // width height
>> >> > +
>> >> > +    /* choose button */
>> >> > +    choose_button = gtk_button_new_with_mnemonic("_Choose File");
>> >> > +    gtk_widget_set_hexpand(choose_button, FALSE);
>> >> > +    g_signal_connect(GTK_BUTTON(choose_button),
>> >> > +                     "clicked", G_CALLBACK(usb_cd_choose_file), file_entry);
>> >> > +    if (lun_info && lun_info->loaded) {
>> >> > +        gtk_widget_set_sensitive(choose_button, FALSE);
>> >> > +        gtk_widget_set_can_focus(choose_button, FALSE);
>> >> > +    }
>> >> > +
>> >> > +    gtk_grid_attach(GTK_GRID(grid),
>> >> > +            choose_button,
>> >> > +            6, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    /* advanced button */
>> >> > +    advanced_button = gtk_button_new_with_label("Advanced");
>> >> > +    gtk_button_set_relief(GTK_BUTTON(advanced_button), GTK_RELIEF_NONE);
>> >> > +    advanced_icon = gtk_image_new_from_icon_name("preferences-system", GTK_ICON_SIZE_BUTTON);
>> >> > +    gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon);
>> >> > +    gtk_button_set_always_show_image(GTK_BUTTON(advanced_button), TRUE);
>> >> > +    g_signal_connect(advanced_button, "clicked", G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog);
>> >> > +
>> >> > +    gtk_grid_attach(GTK_GRID(grid),
>> >> > +            advanced_button,
>> >> > +            0, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    /* advanced grid */
>> >> > +    advanced_grid = gtk_grid_new();
>> >> > +    gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12);
>> >> > +    gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE);
>> >> > +    gtk_container_add(GTK_CONTAINER(content_area), advanced_grid);
>> >> > +
>> >> > +    /* horizontal separator */
>> >> > +    gtk_container_add(GTK_CONTAINER(content_area), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
>> >> > +
>> >> > +    /* pack advanced grid */
>> >> > +    nrow = 0;
>> >> > +
>> >> > +    /* horizontal separator */
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
>> >> > +            0, nrow++, // left top
>> >> > +            7, 1); // width height
>> >> > +
>> >> > +    /* product id labels */
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            gtk_label_new("Vendor"),
>> >> > +            0, nrow, // left top
>> >> > +            2, 1); // width height
>> >> > +
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            gtk_label_new("Product"),
>> >> > +            2, nrow, // left top
>> >> > +            4, 1); // width height
>> >> > +
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            gtk_label_new("Revision"),
>> >> > +            6, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    /* vendor entry */
>> >> > +    vendor_entry = gtk_entry_new();
>> >> > +    gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8);
>> >> > +    if (lun_info) {
>> >> > +        gtk_widget_set_sensitive(vendor_entry, FALSE);
>> >> > +        gtk_widget_set_can_focus(vendor_entry, FALSE);
>> >> > +        gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor);
>> >> > +    } else {
>> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry), "auto");
>> >> > +    }
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            vendor_entry,
>> >> > +            0, nrow, // left top
>> >> > +            2, 1); // width height
>> >> > +
>> >> > +    /* product entry */
>> >> > +    product_entry = gtk_entry_new();
>> >> > +    gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16);
>> >> > +    if (lun_info) {
>> >> > +        gtk_widget_set_sensitive(product_entry, FALSE);
>> >> > +        gtk_widget_set_can_focus(product_entry, FALSE);
>> >> > +        gtk_entry_set_text(GTK_ENTRY(product_entry), lun_info->product);
>> >> > +    } else {
>> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry), "auto");
>> >> > +    }
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            product_entry,
>> >> > +            2, nrow, // left top
>> >> > +            4, 1); // width height
>> >> > +
>> >> > +    /* revision entry */
>> >> > +    revision_entry = gtk_entry_new();
>> >> > +    gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4);
>> >> > +    if (lun_info) {
>> >> > +        gtk_widget_set_sensitive(revision_entry, FALSE);
>> >> > +        gtk_widget_set_can_focus(revision_entry, FALSE);
>> >> > +        gtk_entry_set_text(GTK_ENTRY(revision_entry), lun_info->revision);
>> >> > +    } else {
>> >> > +        gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry), "auto");
>> >> > +    }
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            revision_entry,
>> >> > +            6, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    /* horizontal separator */
>> >> > +    if (!lun_info) {
>> >> > +        gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +                gtk_separator_new(GTK_ORIENTATION_HORIZONTAL),
>> >> > +                0, nrow++, // left top
>> >> > +                7, 1); // width height
>> >> > +    }
>> >> > +
>> >> > +    /* initially loaded switch */
>> >> > +    loaded_label = gtk_label_new("Initially loaded:");
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +        loaded_label,
>> >> > +        0, nrow, // left top
>> >> > +        2, 1); // width height
>> >> > +
>> >> > +    loaded_switch = gtk_switch_new();
>> >> > +    gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE);
>> >> > +    if (lun_info) {
>> >> > +        gtk_widget_set_child_visible(loaded_switch, FALSE);
>> >> > +        gtk_widget_set_child_visible(loaded_label, FALSE);
>> >> > +    } else {
>> >> > +        g_signal_connect(loaded_switch, "state-set",
>> >> > +                         G_CALLBACK(lun_properties_dialog_loaded_switch_cb), lun_dialog);
>> >> > +    }
>> >> > +    gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START);
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            loaded_switch,
>> >> > +            2, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    /* initially locked switch */
>> >> > +    locked_label = gtk_label_new("Initially locked:");
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +        locked_label,
>> >> > +        0, nrow, // left top
>> >> > +        2, 1); // width height
>> >> > +
>> >> > +    locked_switch = gtk_switch_new();
>> >> > +    gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE);
>> >> > +    gtk_widget_set_hexpand(locked_switch, FALSE);
>> >> > +    if (lun_info) {
>> >> > +        gtk_widget_set_child_visible(locked_switch, FALSE);
>> >> > +        gtk_widget_set_child_visible(locked_label, FALSE);
>> >> > +    }
>> >> > +    gtk_widget_set_halign(locked_switch, GTK_ALIGN_START);
>> >> > +    gtk_grid_attach(GTK_GRID(advanced_grid),
>> >> > +            locked_switch,
>> >> > +            2, nrow++, // left top
>> >> > +            1, 1); // width height
>> >> > +
>> >> > +    lun_dialog->dialog = dialog;
>> >> > +    lun_dialog->advanced_grid = advanced_grid;
>> >> > +    lun_dialog->advanced_shown = FALSE;
>> >> > +    lun_dialog->file_entry = file_entry;
>> >> > +    lun_dialog->vendor_entry = vendor_entry;
>> >> > +    lun_dialog->product_entry = product_entry;
>> >> > +    lun_dialog->revision_entry = revision_entry;
>> >> > +    lun_dialog->loaded_switch = loaded_switch;
>> >> > +    lun_dialog->locked_switch = locked_switch;
>> >> > +
>> >> > +    gtk_widget_show_all(dialog);
>> >> > +    gtk_widget_hide(advanced_grid);
>> >> > +}
>> >> > +
>> >> > +static void lun_properties_dialog_get_info(lun_properties_dialog *lun_dialog,
>> >> > +                                            SpiceUsbDeviceLunInfo *lun_info)
>> >> > +{
>> >> > +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(lun_dialog->file_entry));
>> >> > +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(lun_dialog->vendor_entry));
>> >> > +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(lun_dialog->product_entry));
>> >> > +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(lun_dialog->revision_entry));
>> >> > +    lun_info->loaded = gtk_switch_get_active(GTK_SWITCH(lun_dialog->loaded_switch));
>> >> > +    lun_info->locked = gtk_switch_get_active(GTK_SWITCH(lun_dialog->locked_switch));
>> >> > +}
>> >> > +
>> >> > +/* Popup menu */
>> >> > +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +
>> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> >> > +            SpiceUsbDevice *usb_device;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> >> > +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
>> >> > +        }
>> >> > +        else {
>> >> > +            UsbWidgetLunItem *lun_item;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> >> > +            spice_usb_device_manager_device_lun_load(
>> >> > +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded);
>> >> > +        }
>> >> > +    }
>> >> > +    else {
>> >> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +
>> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> >> > +            SpiceUsbDevice *usb_device;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> >> > +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);
>> >> > +        }
>> >> > +        else {
>> >> > +            UsbWidgetLunItem *lun_item;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> >> > +            spice_usb_device_manager_device_lun_lock(
>> >> > +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked);
>> >> > +        }
>> >> > +    }
>> >> > +    else {
>> >> > +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +
>> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> >> > +            SpiceUsbDevice *usb_device;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);
>> >> > +            SPICE_DEBUG("Remove USB device");
>> >> > +        } else {
>> >> > +            UsbWidgetLunItem *lun_item;
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> >> > +            gtk_tree_selection_unselect_all(select);
>> >> > +            spice_usb_device_manager_device_lun_remove(lun_item->manager, lun_item->device, lun_item->lun);
>> >> > +        }
>> >> > +    } else {
>> >> > +        SPICE_DEBUG("Remove - failed to get selection");
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view);
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +
>> >> > +    if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> >> > +            SPICE_DEBUG("No settings for USB device yet");
>> >> > +        } else {
>> >> > +            lun_properties_dialog lun_dialog;
>> >> > +            UsbWidgetLunItem *lun_item;
>> >> > +            gint resp;
>> >> > +
>> >> > +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);
>> >> > +            gtk_tree_selection_unselect_all(select);
>> >> > +            create_lun_properties_dialog(self, NULL, &lun_item->info, &lun_dialog);
>> >> > +
>> >> > +            resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog));
>> >> > +            if (resp == GTK_RESPONSE_ACCEPT) {
>> >> > +                SpiceUsbDeviceLunInfo lun_info;
>> >> > +                SPICE_DEBUG("response is ACCEPT");
>> >> > +                lun_properties_dialog_get_info(&lun_dialog, &lun_info);
>> >> > +                spice_usb_device_manager_device_lun_change_media(
>> >> > +                    priv->manager, lun_item->device, lun_item->lun, &lun_info);
>> >> > +            } else {
>> >> > +                SPICE_DEBUG("response is REJECT");
>> >> > +            }
>> >> > +            gtk_widget_destroy(lun_dialog.dialog);
>> >> > +        }
>> >> > +    } else {
>> >> > +        SPICE_DEBUG("Remove - failed to get selection");
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu,
>> >> > +    const gchar *label_str,
>> >> > +    const gchar *icon_name,
>> >> > +    GCallback cb_func, gpointer user_data)
>> >> > +{
>> >> > +    GtkWidget *menu_item = gtk_menu_item_new();
>> >> > +    create_image_button_box(label_str, icon_name, menu_item);
>> >> > +    g_signal_connect(menu_item, "activate", cb_func, user_data);
>> >> > +
>> >> > +    gtk_widget_show_all(menu_item);
>> >> > +    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
>> >> > +
>> >> > +    return menu_item;
>> >> > +}
>> >> > +
>> >> > +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device)
>> >> > +{
>> >> > +    gboolean result;
>> >> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
>> >> > +    GArray *lun_array;
>> >> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, device);
>> >> > +    result = lun_array && lun_array->len <= 1;
>> >> > +    if (lun_array) {
>> >> > +        g_array_unref(lun_array);
>> >> > +    }
>> >> > +    return result;
>> >> > +}
>> >> > +
>> >> > +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkTreeSelection *select;
>> >> > +    GtkTreeModel *tree_model;
>> >> > +    GtkTreeIter iter;
>> >> > +    UsbWidgetLunItem *lun_item;
>> >> > +    gboolean is_loaded, is_locked;
>> >> > +    GtkTreeIter *usb_dev_iter;
>> >> > +    gboolean is_dev_connected;
>> >> > +    GtkWidget *menu;
>> >> > +
>> >> > +    if (tree_view != priv->cd_tree.tree_view) {
>> >> > +        SPICE_DEBUG("Not applicable for USB device");
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    select = gtk_tree_view_get_selection(tree_view);
>> >> > +
>> >> > +    if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) {
>> >> > +        SPICE_DEBUG("No tree view row is selected");
>> >> > +        return;
>> >> > +    }
>> >> > +    if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) {
>> >> > +        SPICE_DEBUG("No settings for USB device yet");
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    gtk_tree_model_get(tree_model, &iter,
>> >> > +                       COL_ITEM_DATA, (gpointer *)&lun_item,
>> >> > +                       COL_LOADED, &is_loaded,
>> >> > +                       COL_LOCKED, &is_locked,
>> >> > +                       -1);
>> >> > +
>> >> > +    usb_dev_iter = usb_widget_tree_store_find_usb_device(priv->cd_tree.tree_store, lun_item->device);
>> >> > +    if (usb_dev_iter != NULL) {
>> >> > +        gtk_tree_model_get(tree_model, usb_dev_iter,
>> >> > +                           COL_CONNECTED, &is_dev_connected,
>> >> > +                           -1);
>> >> > +        g_free(usb_dev_iter);
>> >> > +    } else {
>> >> > +        is_dev_connected = FALSE;
>> >> > +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",
>> >> > +                    lun_item->info.vendor, lun_item->info.product);
>> >> > +    }
>> >> > +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d",
>> >> > +                lun_item->info.vendor, lun_item->info.product,
>> >> > +                is_dev_connected, is_loaded, is_locked);
>> >> > +
>> >> > +    /* Set up the menu */
>> >> > +    menu = gtk_menu_new();
>> >> > +
>> >> > +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",
>> >> > +                             G_CALLBACK(view_popup_menu_on_settings), user_data);
>> >> > +    if (is_loaded) {
>> >> > +        if (!is_locked) {
>> >> > +            view_popup_add_menu_item(menu, "_Lock", "system-lock-screen",
>> >> > +                                    G_CALLBACK(view_popup_menu_on_lock), user_data);
>> >> > +            view_popup_add_menu_item(menu, "_Eject", "media-eject",
>> >> > +                                     G_CALLBACK(view_popup_menu_on_eject), user_data);
>> >> > +        } else {
>> >> > +            view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen",
>> >> > +                                     G_CALLBACK(view_popup_menu_on_lock), user_data);
>> >> > +        }
>> >> > +    } else {
>> >> > +        view_popup_add_menu_item(menu, "_Load", "media-eject",
>> >> > +                                 G_CALLBACK(view_popup_menu_on_eject), user_data);
>> >> > +    }
>> >> > +
>> >> > +    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {
>> >> > +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",
>> >> > +                                 G_CALLBACK(view_popup_menu_on_remove), user_data);
>> >> > +    }
>> >> > +
>> >> > +    gtk_widget_show_all(menu);
>> >> > +    gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
>> >> > +}
>> >> > +
>> >> > +static void treeview_select_current_row_by_pos(GtkTreeView *tree_view, gint x, gint y)
>> >> > +{
>> >> > +    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
>> >> > +    if (gtk_tree_selection_count_selected_rows(selection) <= 1) {
>> >> > +        GtkTreePath *path;
>> >> > +        /* Get tree path for row that was clicked */
>> >> > +        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))
>> >> > +        {
>> >> > +            gtk_tree_selection_unselect_all(selection);
>> >> > +            gtk_tree_selection_select_path(selection, path);
>> >> > +            gtk_tree_path_free(path);
>> >> > +        }
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data)
>> >> > +{
>> >> > +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);
>> >> > +    /* single click with the right mouse button */
>> >> > +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
>> >> > +        /* select the row that was clicked, it will also provide the context */
>> >> > +        treeview_select_current_row_by_pos(tree_view, (gint)event->x, (gint)event->y);
>> >> > +        view_popup_menu(tree_view, event, user_data);
>> >> > +        return TRUE; /* we handled this */
>> >> > +    } else {
>> >> > +        return FALSE; /* we did not handle this */
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view, gpointer user_data)
>> >> > +{
>> >> > +    view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data);
>> >> > +    return TRUE; /* we handled this */
>> >> > +}
>> >> > +
>> >> > +/* Add LUN dialog */
>> >> > +
>> >> > +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button, gpointer user_data)
>> >> > +{
>> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
>> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
>> >> > +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button);
>> >> > +    lun_properties_dialog lun_dialog;
>> >> > +    gint resp;
>> >&g
Hi

On Wed, Sep 19, 2018 at 6:43 PM, Marc-André Lureau <
marcandre.lureau@gmail.com> wrote:

> Hi
>
> On Tue, Sep 18, 2018 at 8:05 PM Yuri Benditovich
> <yuri.benditovich@daynix.com> wrote:
> >
> >
> >
> > On Tue, Sep 18, 2018 at 12:15 PM, Marc-André Lureau <
> marcandre.lureau@gmail.com> wrote:
> >>
> >> Hi
> >>
> >> On Tue, Sep 18, 2018 at 12:04 PM Yuri Benditovich
> >> <yuri.benditovich@daynix.com> wrote:
> >> >
> >> >
> >> >
> >> > On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <victortoso@redhat.com>
> wrote:
> >> >>
> >> >> On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:
> >> >> > New USB widget supports all the functionality related to
> >> >> > redirection of local USB device (as previous usb widget did).
> >> >> > Additionally it allows creation, management and redirection of
> >> >> > emulated USB CD devices for CD sharing.
> >> >>
> >> >> Wouldn't be possible to extend the current widget instead of
> >> >> creating a new one?
> >> >
> >> >
> >> > Of course it was the first idea before creating new one.
> >> > After several discussions we understood that there is almost nothing
> >> > common between what we'd like to have for CD and what we have in
> existing widget.
> >> > This also was decided on special meeting of Alexander with spice team
> (remove
> >> > some controls and split it to two lists, locals and CD). Earlier the
> screenshot of
> >> > the latest widget (current code) was sent to internal list for review
> and no objections
> >> > were received.
> >>
> >> Ultimately the discussion and decision should take place on public
> mailing list.
> >>
> >> And many of us (including me), may not be directly involved in the RH
> >> spice team.
> >>
> >> Could you detail the goals you had and why you had to write a new
> widget?
> >
> >
> > Regarding new USB widget:
> >
> > User experience when USB CD drive is presented to user is wider than just
> > plug/unplug the USB device.
> >
> > The CD may include several units, each of them can be managed separately.
> > If more than one unit supported on CD, single CD with number of units
> requires
> > just one redirection channel. [At the moment due to time restrictions,
> we define
> > CD as single unit, but I think in the future we will want to enable
> multiple units on CD]
> > So in common case the GUI for CD drives tends to be something like tree
> view.
> > This easily serves also local USB devices as in GTK tree view == list
> view.
> > Initially the widget was developed with single list containing all the
> devices
> > when shared CD devices have leaves and local USB devices do not.
> > IMHO, this kind of presentation is better than current one which
> presents local devices
> > and CD devices separately.
>
> Ok, let say I am biased, and I have opinions because I would use it
> and this is stuff I am also maintaining:
>
> - I have never used a pluggable CD drive.
>
> - I have never used mulitple CD drive simultaneously.
>

This is common case when you install Windows VM with virtio kind of hard
drive
Typically you load 2 ISOs (OS and guest tools)


>
> > When CD drive is presented to the user on guest machine, it can be
> ejected by
> > user action (from Gnome or Explorer). When the media is ejected, it can
> be changed
> > on client side. So the status (ejected/loaded) should be visible and the
> user should be
> > able to control this also on client side exactly as you can open/close
> the drive door manually.
> >
> > Drive lock. When CD is reqired for SW operation, the SW often locks
> > the drive preventing user-initiated eject. In such case, if needed, the
> CD still can be
> > manually ejected using clip. Another option related to 'lock' is that
> user may want
> > to prevent removal of CD by guest software and lock it on client side.
>
> - I have never used locking either.
>

You did not, of couse. This is software-driven operation, SW does it by
sending ioctls.


>
> >
> > The user shall select the properties for logical CD unit and it is good
> to show them
> > in the GUI. They include (at least):
> > * file name (or device name) used as media
>
> That's useful indeed
>
> > * device attributes (vendor/product) which are used to report properties
> of SCSI device
> > (these properties are visible to user in seabios boot screen, so in case
> of 2 CDs the user
> > can recognize the needed one). Note that 2 CDs is typical case for
> Windows installation
> > on VM with virtio.
>
> You can switch the mounted CD for this case, no need for multiple
> simultaneously mounted CD. I don't know a case where this is
> necessary, except perhaps for direct CD copy, that used to be very
> risky anyway..
>

Use case: installing Windows 7/10 VM ( or Win 7 ) with virtio hard drive.
Need to share 2 ISOs (this is what users do, this allows install all the
drivers just
after OS installation). The user needs to distinguish between them in boot
screen.
See attached bitmap of boot with 2 qemu ISO images, where
the minimalistic gui causes the user question himself which one to select;
if he selects wrong one, the boot fails.


> > * Whether the CD media is currently loaded
> >
> > Visible status of the device is (IMHO) good for self support. When
> something does not
> > work as expected and you can't see anything in the GUI that can explain
> this, the next step
> > is opening issues, sending logs etc., i.e. spend own time and time of
> other people.
> > I disagree with the statement 'less GUI is better'. Definitely, less GUI
> is simpler,
> > but this simplicity often increases cost of ownership.
>
> The UI in Boxes is doing the job quite fine for average user. See
> attached screenshot.
>
>
Honestly, I'm not impressed by this gui.
There are 2 approaches: "do as less as possible" and
"do a little more". I am on the second one, because when
'something more' required it is typically already done.


> >
> > Our solid opinion is that even current CD additions are too heavy for
> existing widget.
>
> In my opinion, we can make it much more user friendly. But we need to
> understand what we want to support first. Why spice-gtk cd redirection
> would need to be more complex than what Boxes or virt-manager provide?
>
>
This is exactly the problem in this conversation.
I do not think this gui is more complicated, I think it is more reach,
clear and informative.
Indeed, 'Address' field can be removed, it does not help too much.
'Vendor' and 'Product' fields are exactly device properties visible
to the user on guest side (for example, by lsusb and lsscsi) or in Windows
device manager.
'Connected' shows exact status of the device, when the check box
'Redirect' shows the intention. With local device this can take time until
the device is reset, reenumerated and redirected.
'Loaded' shows exact status of the device and gives a hint when the CD
is not visible in guests explorer.
I still think we will want to enable multiple units per CD in future, so we
avoid
binding 'eject' to device removal (this can be done for single-unit CD).
'Lock' allows also to create non-ejectable device (attempt to eject
on guest side will fail). So, IMO we provide just more features and
appropriately richer GUI.


> >
> > Thanks,
> > Yuri
> >
> >>
> >>
> >> For UI, it's best to focus on the supported use case & the user
> >> story/experience, and avoid feature creep (usually the less UI, the
> >> better).
> >>
> >> >
> >> >>
> >> >>
> >> >> > Signed-off-by: Alexander Nezhinsky <alexander@daynix.com>
> >> >> > Signed-off-by: Yuri Benditovich <yuri.benditovich@daynix.com>
> >> >> > ---
> >> >> >  src/usb-device-redir-widget.c | 2065
> +++++++++++++++++++++++++++++++++++++++++
> >> >> >  1 file changed, 2065 insertions(+)
> >> >> >  create mode 100644 src/usb-device-redir-widget.c
> >> >> >
> >> >> > diff --git a/src/usb-device-redir-widget.c
> b/src/usb-device-redir-widget.c
> >> >> > new file mode 100644
> >> >> > index 0000000..59a6043
> >> >> > --- /dev/null
> >> >> > +++ b/src/usb-device-redir-widget.c
> >> >> > @@ -0,0 +1,2065 @@
> >> >> > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> >> >> > +/*
> >> >> > +   Copyright (C) 2012 Red Hat, Inc.
> >> >> > +
> >> >> > +   Red Hat Authors:
> >> >> > +   Alexander Nezhinsky<anezhins@redhat.com>
> >> >> > +
> >> >> > +   This library is free software; you can redistribute it and/or
> >> >> > +   modify it under the terms of the GNU Lesser General Public
> >> >> > +   License as published by the Free Software Foundation; either
> >> >> > +   version 2.1 of the License, or (at your option) any later
> version.
> >> >> > +
> >> >> > +   This library 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
> >> >> > +   Lesser General Public License for more details.
> >> >> > +
> >> >> > +   You should have received a copy of the GNU Lesser General
> Public
> >> >> > +   License along with this library; if not, see <
> http://www.gnu.org/licenses/>.
> >> >> > +*/
> >> >> > +
> >> >> > +#include "config.h"
> >> >> > +#ifndef USB_WIDGET_TEST
> >> >> > +    #include <glib/gi18n-lib.h>
> >> >> > +    #include "spice-client.h"
> >> >> > +    #include "spice-marshal.h"
> >> >> > +#else
> >> >> > +    #include "spice-client.h"
> >> >> > +#endif
> >> >> > +#include "usb-device-widget.h"
> >> >> > +
> >> >> > +/*
> >> >> > +    Debugging note:
> >> >> > +    Logging from this module is not affected by --spice-debug
> >> >> > +    command line parameter
> >> >> > +    Use SPICE_DEBUG=1 environment varible to enable logs
> >> >> > +*/
> >> >> > +
> >> >> > +#ifdef USE_NEW_USB_WIDGET
> >> >> > +
> >> >> > +/**
> >> >> > + * SECTION:usb-device-widget
> >> >> > + * @short_description: USB device selection widget
> >> >> > + * @title: Spice USB device selection widget
> >> >> > + * @section_id:
> >> >> > + * @see_also:
> >> >> > + * @stability: Under development
> >> >> > + * @include: spice-client-gtk.h
> >> >> > + *
> >> >> > + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to
> easily
> >> >> > + * add an UI to select USB devices to redirect (or unredirect).
> >> >> > + */
> >> >> > +
> >> >> > +struct _SpiceUsbDeviceWidget
> >> >> > +{
> >> >> > +    GtkBox parent;
> >> >> > +
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv;
> >> >> > +};
> >> >> > +
> >> >> > +struct _SpiceUsbDeviceWidgetClass
> >> >> > +{
> >> >> > +    GtkBoxClass parent_class;
> >> >> > +
> >> >> > +    /* signals */
> >> >> > +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,
> >> >> > +                            SpiceUsbDevice *device, GError
> *error);
> >> >> > +};
> >> >> > +
> >> >> > +/* ------------------------------------------------------------------
> */
> >> >> > +/* Prototypes for callbacks  */
> >> >> > +static void device_added_cb(SpiceUsbDeviceManager *manager,
> >> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> >> > +static void device_removed_cb(SpiceUsbDeviceManager *manager,
> >> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> >> > +static void device_changed_cb(SpiceUsbDeviceManager *manager,
> >> >> > +    SpiceUsbDevice *device, gpointer user_data);
> >> >> > +static void device_error_cb(SpiceUsbDeviceManager *manager,
> >> >> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);
> >> >> > +static gboolean spice_usb_device_widget_update_status(gpointer
> user_data);
> >> >> > +
> >> >> > +/* ------------------------------------------------------------------
> */
> >> >> > +/* gobject glue
>      */
> >> >> > +
> >> >> > +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \
> >> >> > +    (G_TYPE_INSTANCE_GET_PRIVATE((obj),
> SPICE_TYPE_USB_DEVICE_WIDGET, \
> >> >> > +                                 SpiceUsbDeviceWidgetPrivate))
> >> >> > +
> >> >> > +enum {
> >> >> > +    PROP_0,
> >> >> > +    PROP_SESSION,
> >> >> > +    PROP_DEVICE_FORMAT_STRING,
> >> >> > +};
> >> >> > +
> >> >> > +enum {
> >> >> > +    CONNECT_FAILED,
> >> >> > +    LAST_SIGNAL,
> >> >> > +};
> >> >> > +
> >> >> > +typedef struct {
> >> >> > +    GtkTreeView *tree_view;
> >> >> > +    GtkTreeStore *tree_store;
> >> >> > +} SpiceUsbDeviceWidgetTree;
> >> >> > +
> >> >> > +struct _SpiceUsbDeviceWidgetPrivate {
> >> >> > +    SpiceSession *session;
> >> >> > +    gchar *device_format_string;
> >> >> > +    SpiceUsbDeviceManager *manager;
> >> >> > +    GtkWidget *info_bar;
> >> >> > +    GtkWidget *label;
> >> >> > +    SpiceUsbDeviceWidgetTree cd_tree;
> >> >> > +    SpiceUsbDeviceWidgetTree usb_tree;
> >> >> > +    GdkPixbuf *icon_cd;
> >> >> > +    GdkPixbuf *icon_connected;
> >> >> > +    GdkPixbuf *icon_disconn;
> >> >> > +    GdkPixbuf *icon_warning;
> >> >> > +    GdkPixbuf *icon_info;
> >> >> > +    gchar *err_msg;
> >> >> > +    gsize device_count;
> >> >> > +};
> >> >> > +
> >> >> > +static guint signals[LAST_SIGNAL] = { 0, };
> >> >> > +
> >> >> > +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget,
> GTK_TYPE_BOX);
> >> >> > +
> >> >> > +/* TREE */
> >> >> > +
> >> >> > +enum column_id
> >> >> > +{
> >> >> > +    COL_REDIRECT = 0,
> >> >> > +    COL_ADDRESS,
> >> >> > +    COL_CONNECT_ICON,
> >> >> > +    COL_CD_ICON,
> >> >> > +    COL_VENDOR,
> >> >> > +    COL_PRODUCT,
> >> >> > +    COL_FILE,
> >> >> > +    COL_LOADED,
> >> >> > +    COL_LOCKED,
> >> >> > +    COL_IDLE,
> >> >> > +    /* internal columns */
> >> >> > +    COL_REVISION,
> >> >> > +    COL_CD_DEV,
> >> >> > +    COL_LUN_ITEM,
> >> >> > +    COL_DEV_ITEM,
> >> >> > +    COL_ITEM_DATA,
> >> >> > +    COL_CONNECTED,
> >> >> > +    COL_CAN_REDIRECT,
> >> >> > +    COL_ROW_COLOR,
> >> >> > +    COL_ROW_COLOR_SET,
> >> >> > +    NUM_COLS,
> >> >> > +
> >> >> > +    INVALID_COL
> >> >> > +};
> >> >> > +
> >> >> > +// there is a possibility to use different names
> >> >> > +// for columns in USB device list and CD device list, if needed
> >> >> > +// currently they are identical
> >> >> > +static const char *column_names_cd[NUM_COLS];
> >> >> > +static const char *column_names_usb[NUM_COLS];
> >> >> > +
> >> >> > +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n]
> = s
> >> >> > +
> >> >> > +static void initialize_columns(void)
> >> >> > +{
> >> >> > +    SET_COLUMN(COL_REDIRECT, _("Redirect"));
> >> >> > +    SET_COLUMN(COL_ADDRESS, _("Address"));
> >> >> > +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));
> >> >> > +    SET_COLUMN(COL_CD_ICON, _("CD"));
> >> >> > +    SET_COLUMN(COL_VENDOR, _("Vendor"));
> >> >> > +    SET_COLUMN(COL_PRODUCT, _("Product"));
> >> >> > +    SET_COLUMN(COL_FILE, _("File/Device Path"));
> >> >> > +    SET_COLUMN(COL_LOADED, _("Loaded"));
> >> >> > +    SET_COLUMN(COL_LOCKED, _("Locked"));
> >> >> > +    SET_COLUMN(COL_IDLE, _("Idle"));
> >> >> > +    SET_COLUMN(COL_REVISION, "?Revision");
> >> >> > +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");
> >> >> > +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");
> >> >> > +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");
> >> >> > +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");
> >> >> > +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");
> >> >> > +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");
> >> >> > +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");
> >> >> > +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");
> >> >> > +};
> >> >> > +
> >> >> > +static const char *column_name(enum column_id id, gboolean is_cd)
> >> >> > +{
> >> >> > +    const char **col_name = is_cd ? column_names_cd :
> column_names_usb;
> >> >> > +    return col_name[id];
> >> >> > +}
> >> >> > +
> >> >> > +typedef struct _UsbWidgetLunItem {
> >> >> > +    SpiceUsbDeviceManager *manager;
> >> >> > +    SpiceUsbDevice *device;
> >> >> > +    guint lun;
> >> >> > +    SpiceUsbDeviceLunInfo info;
> >> >> > +} UsbWidgetLunItem;
> >> >> > +
> >> >> > +typedef struct _TreeFindUsbDev {
> >> >> > +    SpiceUsbDevice *usb_dev;
> >> >> > +    GtkTreeIter dev_iter;
> >> >> > +} TreeFindUsbDev;
> >> >> > +
> >> >> > +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *,
> gchar *, gpointer);
> >> >> > +
> >> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> >> >> > +                                          SpiceUsbDevice
> *usb_device,
> >> >> > +                                          GtkTreeIter
> *old_dev_iter);
> >> >> > +
> >> >> > +static gchar *usb_device_description(SpiceUsbDeviceManager
> *manager,
> >> >> > +                                     SpiceUsbDevice *device,
> >> >> > +                                     const gchar *format)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceDescription desc;
> >> >> > +    gchar *descriptor;
> >> >> > +    gchar *res;
> >> >> > +    spice_usb_device_get_info(manager, device, &desc);
> >> >> > +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id,
> desc.product_id);
> >> >> > +    if (!format) {
> >> >> > +        format = _("%s %s %s at %d-%d");
> >> >> > +    }
> >> >> > +    res = g_strdup_printf(format, desc.vendor, desc.product,
> descriptor, desc.bus, desc.address);
> >> >> > +    g_free(desc.vendor);
> >> >> > +    g_free(desc.product);
> >> >> > +    g_free(descriptor);
> >> >> > +    return res;
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeStore* usb_widget_create_tree_store(void)
> >> >> > +{
> >> >> > +    GtkTreeStore *tree_store;
> >> >> > +
> >> >> > +    tree_store = gtk_tree_store_new(NUM_COLS,
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */
> >> >> > +                        G_TYPE_STRING, /* COL_ADDRESS */
> >> >> > +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */
> >> >> > +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */
> >> >> > +                        G_TYPE_STRING, /* COL_VENDOR */
> >> >> > +                        G_TYPE_STRING, /* COL_PRODUCT */
> >> >> > +                        G_TYPE_STRING, /* COL_FILE */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_LOADED */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_LOCKED */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_IDLE */
> >> >> > +                        /* internal columns */
> >> >> > +                        G_TYPE_STRING, /* COL_REVISION */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */
> >> >> > +                        G_TYPE_POINTER, /* COL_ITEM_DATA */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */
> >> >> > +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */
> >> >> > +                        G_TYPE_STRING, /* COL_ROW_COLOR */
> >> >> > +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );
> >> >> > +    SPICE_DEBUG("tree store created");
> >> >> > +
> >> >> > +    return tree_store;
> >> >> > +}
> >> >> > +
> >> >> > +static GdkPixbuf *get_named_icon(const gchar *name, gint size)
> >> >> > +{
> >> >> > +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(),
> name, size, 0);
> >> >> > +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);
> >> >> > +    g_object_unref (info);
> >> >> > +    return pixbuf;
> >> >> > +}
> >> >> > +
> >> >> > +static void select_widget_size(GtkWidget *wg)
> >> >> > +{
> >> >> > +    GdkDisplay *d = gtk_widget_get_display(wg);
> >> >> > +    int i, w = 2000, h = 1024;
> >> >> > +    int n = gdk_display_get_n_monitors(d);
> >> >> > +    for (i = 0; i < n; ++i)
> >> >> > +    {
> >> >> > +        GdkMonitor *m = gdk_display_get_monitor(d, i);
> >> >> > +        if (m) {
> >> >> > +            GdkRectangle area;
> >> >> > +            gdk_monitor_get_workarea(m, &area);
> >> >> > +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",
> >> >> > +                i, area.width, area.height, area.x, area.y );
> >> >> > +            w = MIN(w, area.width);
> >> >> > +            h = MIN(h, area.height);
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    w = (w * 3) / 4;
> >> >> > +    h = h / 2;
> >> >> > +
> >> >> > +    SPICE_DEBUG("sizing widget as %d x %d", w, h);
> >> >> > +    gtk_widget_set_size_request(wg, w, h);
> >> >> > +}
> >> >> > +
> >> >> > +static void usb_widget_add_device(SpiceUsbDeviceWidget *self,
> >> >> > +                                          SpiceUsbDevice
> *usb_device,
> >> >> > +                                          GtkTreeIter
> *old_dev_iter)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;
> >> >> > +    SpiceUsbDeviceWidgetTree *tree;
> >> >> > +    GtkTreeView *tree_view;
> >> >> > +    GtkTreeStore *tree_store;
> >> >> > +    GtkTreeIter new_dev_iter;
> >> >> > +    SpiceUsbDeviceDescription dev_info;
> >> >> > +    GtkTreePath *new_dev_path;
> >> >> > +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;
> >> >> > +    gchar *addr_str;
> >> >> > +    GArray *lun_array;
> >> >> > +    guint lun_index;
> >> >> > +    GError *error = NULL;
> >> >> > +
> >> >> > +    is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr,
> usb_device);
> >> >> > +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;
> >> >> > +    tree_view = tree->tree_view;
> >> >> > +    tree_store = tree->tree_store;
> >> >> > +
> >> >> > +    if (old_dev_iter == NULL) {
> >> >> > +        gtk_tree_store_append(tree_store, &new_dev_iter, NULL);
> >> >> > +    } else {
> >> >> > +        gtk_tree_store_insert_after(tree_store, &new_dev_iter,
> NULL, old_dev_iter);
> >> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> >> > +    }
> >> >> > +
> >> >> > +    spice_usb_device_get_info(usb_dev_mgr, usb_device,
> &dev_info);
> >> >> > +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus,
> (gint)dev_info.address);
> >> >> > +    is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr,
> usb_device);
> >> >> > +    is_dev_redirected = is_dev_connected;
> >> >> > +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",
> >> >> > +        addr_str, dev_info.vendor, dev_info.product,
> is_dev_connected, is_dev_cd);
> >> >> > +
> >> >> > +    gtk_tree_store_set(tree_store, &new_dev_iter,
> >> >> > +        COL_REDIRECT, is_dev_redirected,
> >> >> > +        COL_ADDRESS, addr_str,
> >> >> > +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected
> : priv->icon_disconn,
> >> >> > +        COL_CD_ICON, priv->icon_cd,
> >> >> > +        COL_VENDOR, dev_info.vendor,
> >> >> > +        COL_PRODUCT, dev_info.product,
> >> >> > +        COL_CD_DEV, is_dev_cd,
> >> >> > +        COL_LUN_ITEM, FALSE, /* USB device item */
> >> >> > +        COL_DEV_ITEM, TRUE, /* USB device item */
> >> >> > +        COL_ITEM_DATA, (gpointer)usb_device,
> >> >> > +        COL_CONNECTED, is_dev_connected,
> >> >> > +        COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr,
> usb_device, &error),
> >> >> > +        COL_ROW_COLOR, "beige",
> >> >> > +        COL_ROW_COLOR_SET, TRUE,
> >> >> > +        -1);
> >> >> > +    g_clear_error(&error);
> >> >> > +
> >> >> > +    priv->device_count++;
> >> >> > +
> >> >> > +    /* get all the luns */
> >> >> > +    lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr,
> usb_device);
> >> >> > +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {
> >> >> > +        UsbWidgetLunItem *lun_item;
> >> >> > +        GtkTreeIter lun_iter;
> >> >> > +        gchar lun_str[8];
> >> >> > +
> >> >> > +        lun_item = g_malloc(sizeof(*lun_item));
> >> >> > +        lun_item->manager = usb_dev_mgr;
> >> >> > +        lun_item->device = usb_device;
> >> >> > +        lun_item->lun = g_array_index(lun_array, guint,
> lun_index);
> >> >> > +        spice_usb_device_manager_device_lun_get_info(usb_dev_mgr,
> usb_device, lun_item->lun, &lun_item->info);
> >> >> > +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s]
> lun_item:%p",
> >> >> > +                lun_index, lun_item->info.vendor,
> lun_item->info.product,
> >> >> > +                lun_item->info.revision,
> lun_item->info.file_path, lun_item);
> >> >> > +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);
> >> >> > +
> >> >> > +        /* Append LUN as a child of USB device */
> >> >> > +        gtk_tree_store_append(tree_store, &lun_iter,
> &new_dev_iter);
> >> >> > +        gtk_tree_store_set(tree_store, &lun_iter,
> >> >> > +                COL_ADDRESS, lun_str,
> >> >> > +                COL_VENDOR, lun_item->info.vendor,
> >> >> > +                COL_PRODUCT, lun_item->info.product,
> >> >> > +                COL_REVISION, lun_item->info.revision,
> >> >> > +                COL_FILE, lun_item->info.file_path,
> >> >> > +                COL_LOADED, lun_item->info.loaded,
> >> >> > +                COL_LOCKED, lun_item->info.locked,
> >> >> > +                COL_IDLE, !lun_item->info.started,
> >> >> > +                COL_CD_DEV, FALSE,
> >> >> > +                COL_LUN_ITEM, TRUE, /* LUN item */
> >> >> > +                COL_DEV_ITEM, FALSE, /* LUN item */
> >> >> > +                COL_ITEM_DATA, (gpointer)lun_item,
> >> >> > +                COL_CONNECTED, is_dev_connected,
> >> >> > +                COL_ROW_COLOR, "azure",
> >> >> > +                COL_ROW_COLOR_SET, TRUE,
> >> >> > +                -1);
> >> >> > +    }
> >> >> > +    g_array_unref(lun_array);
> >> >> > +
> >> >> > +    new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store),
> &new_dev_iter);
> >> >> > +    gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE);
> >> >> > +    gtk_tree_path_free(new_dev_path);
> >> >> > +
> >> >> > +    g_free(dev_info.vendor);
> >> >> > +    g_free(dev_info.product);
> >> >> > +    g_free(addr_str);
> >> >> > +}
> >> >> > +
> >> >> > +static gboolean tree_item_is_lun(GtkTreeStore *tree_store,
> GtkTreeIter *iter)
> >> >> > +{
> >> >> > +    gboolean is_lun;
> >> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter,
> COL_LUN_ITEM, &is_lun, -1);
> >> >> > +    return is_lun;
> >> >> > +}
> >> >> > +
> >> >> > +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel
> *tree_model,
> >> >> > +
> GtkTreePath *path, GtkTreeIter *iter,
> >> >> > +
> gpointer user_data)
> >> >> > +{
> >> >> > +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;
> >> >> > +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;
> >> >> > +    SpiceUsbDevice *usb_device;
> >> >> > +    gboolean is_lun_item;
> >> >> > +
> >> >> > +    gtk_tree_model_get(tree_model, iter,
> >> >> > +                       COL_LUN_ITEM, &is_lun_item,
> >> >> > +                       COL_ITEM_DATA, (gpointer *)&usb_device,
> >> >> > +                       -1);
> >> >> > +    if (!is_lun_item && usb_device == find_usb_device) {
> >> >> > +        find_info->dev_iter = *iter;
> >> >> > +        find_info->usb_dev  = NULL;
> >> >> > +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);
> >> >> > +        return TRUE; /* stop iterating */
> >> >> > +    } else {
> >> >> > +        return FALSE; /* continue iterating */
> >> >> > +    }
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore
> *tree_store,
> >> >> > +
> SpiceUsbDevice *usb_device)
> >> >> > +{
> >> >> > +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter
> = {} };
> >> >> > +    GtkTreeIter *iter = NULL;
> >> >> > +
> >> >> > +    gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store),
> >> >> > +                           usb_widget_tree_store_find_usb_dev_foreach_cb,
> (gpointer)&find_info);
> >> >> > +    // the callback sets 'usb_dev' field to zero if it finds the
> device
> >> >> > +    if (!find_info.usb_dev) {
> >> >> > +        iter = g_malloc(sizeof(*iter));
> >> >> > +        *iter = find_info.dev_iter;
> >> >> > +    }
> >> >> > +    return iter;
> >> >> > +}
> >> >> > +
> >> >> > +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget
> *self,
> >> >> > +                                         SpiceUsbDevice
> *usb_device)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkTreeIter *old_dev_iter;
> >> >> > +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;
> >> >> > +    // on WIN32 it is possible the backend device is already
> removed
> >> >> > +    // from the USB device manager list and we can't know it was
> >> >> > +    // USB or CD, do we will try both lists
> >> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> >> > +    if (old_dev_iter != NULL) {
> >> >> > +        SPICE_DEBUG("USB Device removed");
> >> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> >> > +        priv->device_count--;
> >> >> > +        g_free(old_dev_iter);
> >> >> > +        return TRUE;
> >> >> > +    }
> >> >> > +    tree_store = priv->cd_tree.tree_store;
> >> >> > +    if (tree_store) {
> >> >> > +        old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> >> > +    }
> >> >> > +    if (old_dev_iter != NULL) {
> >> >> > +        SPICE_DEBUG("CD Device removed");
> >> >> > +        gtk_tree_store_remove(tree_store, old_dev_iter);
> >> >> > +        priv->device_count--;
> >> >> > +        g_free(old_dev_iter);
> >> >> > +        return TRUE;
> >> >> > +    }
> >> >> > +    SPICE_DEBUG("Device %p not found!", usb_device);
> >> >> > +    return FALSE;
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget
> *self,
> >> >> > +                                                 enum column_id
> toggle_col_id,
> >> >> > +                                                 enum column_id
> visible_col_id,
> >> >> > +                                                 enum column_id
> sensitive_col_id,
> >> >> > +
>  tree_item_toggled_cb toggled_cb,
> >> >> > +                                                 gboolean is_cd)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkCellRenderer     *renderer;
> >> >> > +    GtkTreeViewColumn   *view_col;
> >> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> >> >> > +
> >> >> > +    renderer = gtk_cell_renderer_toggle_new();
> >> >> > +
> >> >> > +    if (sensitive_col_id != INVALID_COL) {
> >> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                        col_name,
> >> >> > +                        renderer,
> >> >> > +                        "active", toggle_col_id,
> >> >> > +                        "visible", visible_col_id,
> >> >> > +                        "activatable", sensitive_col_id,
> >> >> > +                        NULL);
> >> >> > +    } else {
> >> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                        col_name,
> >> >> > +                        renderer,
> >> >> > +                        "active", toggle_col_id,
> >> >> > +                        "visible", visible_col_id,
> >> >> > +                        NULL);
> >> >> > +    }
> >> >> > +
> >> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> >> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> >> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> >> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view
> : priv->usb_tree.tree_view, view_col);
> >> >> > +
> >> >> > +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb),
> self);
> >> >> > +
> >> >> > +    SPICE_DEBUG("view added toggle column [%u : %s] visible when
> [%u : %s]",
> >> >> > +            toggle_col_id, col_name,
> >> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
> >> >> > +    return view_col;
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget
> *self,
> >> >> > +                                                           enum
> column_id toggle_col_id,
> >> >> > +                                                           enum
> column_id visible_col_id,
> >> >> > +
>  gboolean is_cd)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkCellRenderer     *renderer;
> >> >> > +    GtkTreeViewColumn   *view_col;
> >> >> > +    const char *col_name = column_name(toggle_col_id, is_cd);
> >> >> > +
> >> >> > +    renderer = gtk_cell_renderer_toggle_new();
> >> >> > +
> >> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                    col_name,
> >> >> > +                    renderer,
> >> >> > +                    "active", toggle_col_id,
> >> >> > +                    "visible", visible_col_id,
> >> >> > +                    NULL);
> >> >> > +
> >> >> > +    gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer),
> FALSE);
> >> >> > +
> >> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_FIXED);
> >> >> > +    gtk_tree_view_column_set_expand(view_col, FALSE);
> >> >> > +    gtk_tree_view_column_set_resizable(view_col, FALSE);
> >> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view
> : priv->usb_tree.tree_view, view_col);
> >> >> > +
> >> >> > +    SPICE_DEBUG("view added read-only toggle column [%u : %s]
> visible when [%u : %s]",
> >> >> > +            toggle_col_id, col_name,
> >> >> > +            visible_col_id, column_name(visible_col_id, is_cd));
> >> >> > +    return view_col;
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget
> *self,
> >> >> > +                                               enum column_id
> col_id,
> >> >> > +                                               gboolean
> expandable,
> >> >> > +                                               gboolean is_cd)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkCellRenderer     *renderer;
> >> >> > +    GtkTreeViewColumn   *view_col;
> >> >> > +
> >> >> > +    renderer = gtk_cell_renderer_text_new();
> >> >> > +
> >> >> > +    view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                    column_name(col_id, is_cd),
> >> >> > +                    renderer,
> >> >> > +                    "text", col_id,
> >> >> > +                    //"cell-background", COL_ROW_COLOR,
> >> >> > +                    //"cell-background-set", COL_ROW_COLOR_SET,
> >> >> > +                    NULL);
> >> >> > +
> >> >> > +    gtk_tree_view_column_set_sizing(view_col,
> GTK_TREE_VIEW_COLUMN_GROW_ONLY);
> >> >> > +    gtk_tree_view_column_set_resizable(view_col, TRUE);
> >> >> > +    gtk_tree_view_column_set_expand(view_col, expandable);
> >> >> > +
> >> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view
> : priv->usb_tree.tree_view, view_col);
> >> >> > +
> >> >> > +    SPICE_DEBUG("view added text column [%u : %s]", col_id,
> column_name(col_id, is_cd));
> >> >> > +    return view_col;
> >> >> > +}
> >> >> > +
> >> >> > +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget
> *self,
> >> >> > +                                                 enum column_id
> col_id,
> >> >> > +                                                 enum column_id
> visible_col_id,
> >> >> > +                                                 gboolean is_cd)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkCellRenderer     *renderer;
> >> >> > +    GtkTreeViewColumn   *view_col;
> >> >> > +    const char *col_name = column_name(col_id, is_cd);
> >> >> > +
> >> >> > +    renderer = gtk_cell_renderer_pixbuf_new();
> >> >> > +
> >> >> > +    if (visible_col_id == INVALID_COL) {
> >> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                        col_name,
> >> >> > +                        renderer,
> >> >> > +                        "pixbuf", col_id,
> >> >> > +                        NULL);
> >> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible
> always", col_id, col_name);
> >> >> > +    } else {
> >> >> > +        view_col = gtk_tree_view_column_new_with_attributes(
> >> >> > +                        col_name,
> >> >> > +                        renderer,
> >> >> > +                        "pixbuf", col_id,
> >> >> > +                        "visible", visible_col_id,
> >> >> > +                        NULL);
> >> >> > +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible
> when[%u : %s]",
> >> >> > +                col_id, col_name, visible_col_id,
> column_name(visible_col_id, is_cd));
> >> >> > +    }
> >> >> > +    gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view
> : priv->usb_tree.tree_view, view_col);
> >> >> > +    return view_col;
> >> >> > +}
> >> >> > +
> >> >> > +/* Toggle handlers */
> >> >> > +
> >> >> > +static gboolean tree_item_toggle_get_val(GtkTreeStore
> *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)
> >> >> > +{
> >> >> > +    gboolean toggle_val;
> >> >> > +
> >> >> > +    gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store),
> iter, path_str);
> >> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id,
> &toggle_val, -1);
> >> >> > +
> >> >> > +    return toggle_val;
> >> >> > +}
> >> >> > +
> >> >> > +static void tree_item_toggle_set(GtkTreeStore *tree_store,
> GtkTreeIter *iter, enum column_id col_id, gboolean new_val)
> >> >> > +{
> >> >> > +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);
> >> >> > +}
> >> >> > +
> >> >> > +typedef struct _connect_cb_data {
> >> >> > +    SpiceUsbDeviceWidget *self;
> >> >> > +    SpiceUsbDevice *usb_dev;
> >> >> > +} connect_cb_data;
> >> >> > +
> >> >> > +static void connect_cb_data_free(connect_cb_data *user_data)
> >> >> > +{
> >> >> > +    spice_usb_device_widget_update_status(user_data->self);
> >> >> > +    g_object_unref(user_data->self);
> >> >> > +    g_boxed_free(spice_usb_device_get_type(),
> user_data->usb_dev);
> >> >> > +    g_free(user_data);
> >> >> > +}
> >> >> > +
> >> >> > +static void usb_widget_connect_cb(GObject *source_object,
> GAsyncResult *res, gpointer user_data)
> >> >> > +{
> >> >> > +    connect_cb_data *cb_data = user_data;
> >> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> >> >> > +    GError *err = NULL;
> >> >> > +    GtkTreeIter *dev_iter;
> >> >> > +    gchar *desc;
> >> >> > +    gboolean finished;
> >> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> >> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> >> > +
> >> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> >> >> > +    if (!dev_iter) {
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> >> >> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);
> >> >> > +
> >> >> > +    finished = spice_usb_device_manager_conne
> ct_device_finish(priv->manager, res, &err);
> >> >> > +    if (finished) {
> >> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> >> > +                           COL_CONNECT_ICON, priv->icon_connected,
> >> >> > +                           COL_CONNECTED, TRUE,
> >> >> > +                           -1);
> >> >> > +    } else {
> >> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> >> > +                           COL_REDIRECT, FALSE,
> >> >> > +                           -1);
> >> >> > +        g_prefix_error(&err, "Device connect failed %s: ", desc);
> >> >> > +        if (err) {
> >> >> > +            SPICE_DEBUG("%s", err->message);
> >> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0,
> usb_dev, err);
> >> >> > +            g_error_free(err);
> >> >> > +        } else {
> >> >> > +            g_signal_emit(self, signals[CONNECT_FAILED], 0,
> usb_dev, NULL);
> >> >> > +        }
> >> >> > +
> >> >> > +        /* don't trigger a disconnect if connect failed */
> >> >> > +        /*
> >> >> > +        g_signal_handlers_block_by_fun
> c(GTK_TOGGLE_BUTTON(user_data->check),
> >> >> > +                                        checkbox_clicked_cb,
> self);
> >> >> > +        gtk_toggle_button_set_active(G
> TK_TOGGLE_BUTTON(user_data->check), FALSE);
> >> >> > +        g_signal_handlers_unblock_by_f
> unc(GTK_TOGGLE_BUTTON(user_data->check),
> >> >> > +                                        checkbox_clicked_cb,
> self);
> >> >> > +        */
> >> >> > +    }
> >> >> > +    g_free(desc);
> >> >> > +    g_free(dev_iter);
> >> >> > +    connect_cb_data_free(user_data);
> >> >> > +}
> >> >> > +
> >> >> > +static void usb_widget_disconnect_cb(GObject *source_object,
> GAsyncResult *res, gpointer user_data)
> >> >> > +{
> >> >> > +    connect_cb_data *cb_data = user_data;
> >> >> > +    SpiceUsbDeviceWidget *self = cb_data->self;
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;
> >> >> > +    GError *err = NULL;
> >> >> > +    GtkTreeIter *dev_iter;
> >> >> > +    gchar *desc;
> >> >> > +    gboolean finished;
> >> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_dev);
> >> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> >> > +
> >> >> > +    dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_dev);
> >> >> > +    if (!dev_iter) {
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    desc = usb_device_description(priv->manager, usb_dev,
> priv->device_format_string);
> >> >> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);
> >> >> > +
> >> >> > +    finished = spice_usb_device_manager_disco
> nnect_device_finish(priv->manager, res, &err);
> >> >> > +    if (finished) {
> >> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> >> > +                           COL_CONNECT_ICON, priv->icon_disconn,
> >> >> > +                           COL_CONNECTED, FALSE,
> >> >> > +                           -1);
> >> >> > +    } else {
> >> >> > +        gtk_tree_store_set(tree_store, dev_iter,
> >> >> > +                           COL_REDIRECT, TRUE,
> >> >> > +                           -1);
> >> >> > +        g_prefix_error(&err, "Device disconnect failed %s: ",
> desc);
> >> >> > +        if (err) {
> >> >> > +            SPICE_DEBUG("%s", err->message);
> >> >> > +            g_error_free(err);
> >> >> > +        }
> >> >> > +    }
> >> >> > +    g_free(desc);
> >> >> > +    g_free(dev_iter);
> >> >> > +    connect_cb_data_free(user_data);
> >> >> > +}
> >> >> > +
> >> >> > +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle
> *cell,
> >> >> > +                                          gchar *path_str,
> >> >> > +                                          SpiceUsbDeviceWidget
> *self,
> >> >> > +                                          GtkTreeStore
> *tree_store)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);
> >> >> > +    SpiceUsbDevice *usb_dev;
> >> >> > +    GtkTreeIter iter;
> >> >> > +    gboolean new_redirect_val;
> >> >> > +
> >> >> > +    new_redirect_val = !tree_item_toggle_get_val(tree_store,
> path_str, &iter, COL_REDIRECT);
> >> >> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");
> >> >> > +    tree_item_toggle_set(tree_store, &iter, COL_REDIRECT,
> new_redirect_val);
> >> >> > +
> >> >> > +    gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter,
> COL_ITEM_DATA, (gpointer *)&usb_dev, -1);
> >> >> > +    cb_data->self = g_object_ref(self);
> >> >> > +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(),
> usb_dev);
> >> >> > +
> >> >> > +    if (new_redirect_val) {
> >> >> > +        spice_usb_device_manager_connect_device_async(priv->manager,
> usb_dev,
> >> >> > +                                                      NULL, /*
> cancellable */
> >> >> > +
> usb_widget_connect_cb, cb_data);
> >> >> > +    } else {
> >> >> > +        spice_usb_device_manager_disco
> nnect_device_async(priv->manager, usb_dev,
> >> >> > +                                                         NULL, /*
> cancellable */
> >> >> > +
>  usb_widget_disconnect_cb, cb_data);
> >> >> > +
> >> >> > +    }
> >> >> > +    spice_usb_device_widget_update_status(self);
> >> >> > +}
> >> >> > +
> >> >> > +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle
> *cell,
> >> >> > +                                             gchar *path_str,
> >> >> > +                                             gpointer user_data)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d
> ata);
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->cd_tree.tree_store);
> >> >> > +}
> >> >> > +
> >> >> > +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle
> *cell,
> >> >> > +                                             gchar *path_str,
> >> >> > +                                             gpointer user_data)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d
> ata);
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    tree_item_toggled_cb_redirect(cell, path_str, self,
> priv->usb_tree.tree_store);
> >> >> > +}
> >> >> > +
> >> >> > +/* Signal handlers */
> >> >> > +
> >> >> > +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d
> ata);
> >> >> > +
> >> >> > +    SPICE_DEBUG("Signal: Device Added");
> >> >> > +
> >> >> > +    usb_widget_add_device(self, usb_device, NULL);
> >> >> > +
> >> >> > +    spice_usb_device_widget_update_status(self);
> >> >> > +}
> >> >> > +
> >> >> > +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d
> ata);
> >> >> > +    gboolean dev_removed;
> >> >> > +
> >> >> > +    SPICE_DEBUG("Signal: Device Removed");
> >> >> > +
> >> >> > +    dev_removed = usb_widget_remove_device(self, usb_device);
> >> >> > +    if (dev_removed) {
> >> >> > +        spice_usb_device_widget_update_status(self);
> >> >> > +    }
> >> >> > +}
> >> >> > +
> >> >> > +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr,
> >> >> > +    SpiceUsbDevice *usb_device, gpointer user_data)
> >> >> > +{
> >> >> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d
> ata);
> >> >> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;
> >> >> > +    GtkTreeIter *old_dev_iter;
> >> >> > +    gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager,
> usb_device);
> >> >> > +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store :
> priv->usb_tree.tree_store;
> >> >> > +
> >> >> > +    SPICE_DEBUG("Signal: Device Changed");
> >> >> > +
> >> >> > +    old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store,
> usb_device);
> >> >> > +    if (old_dev_iter != NULL) {
> >> >> > +
> >> >> > +        usb_widget_add_device(self, usb_device, old_dev_iter);
> >> >&g