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

Victor Toso victortoso at redhat.com
Tue Sep 18 06:10:48 UTC 2018


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 at daynix.com>
> Signed-off-by: Yuri Benditovich <yuri.benditovich at 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 at 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 at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/spice-devel/attachments/20180918/80cfdf97/attachment-0001.sig>


More information about the Spice-devel mailing list