<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <span dir="ltr"><<a href="mailto:victortoso@redhat.com" target="_blank">victortoso@redhat.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On Mon, Sep 17, 2018 at 04:23:02PM +0300, Yuri Benditovich wrote:<br>
> New USB widget supports all the functionality related to<br>
> redirection of local USB device (as previous usb widget did).<br>
> Additionally it allows creation, management and redirection of<br>
> emulated USB CD devices for CD sharing.<br>
<br>
</span>Wouldn't be possible to extend the current widget instead of<br>
creating a new one?<br></blockquote><div><br></div><div>Of course it was the first idea before creating new one.</div><div>After several discussions we understood that there is almost nothing</div><div>common between what we'd like to have for CD and what we have in existing widget.</div><div>This also was decided on special meeting of Alexander with spice team (remove</div><div>some controls and split it to two lists, locals and CD). Earlier the screenshot of</div><div>the latest widget (current code) was sent to internal list for review and no objections</div><div>were received.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div><div class="h5"><br>
> Signed-off-by: Alexander Nezhinsky <<a href="mailto:alexander@daynix.com">alexander@daynix.com</a>><br>
> Signed-off-by: Yuri Benditovich <<a href="mailto:yuri.benditovich@daynix.com">yuri.benditovich@daynix.com</a>><br>
> ---<br>
>  src/usb-device-redir-widget.c | 2065 ++++++++++++++++++++++++++++++<wbr>+++++++++++<br>
>  1 file changed, 2065 insertions(+)<br>
>  create mode 100644 src/usb-device-redir-widget.c<br>
> <br>
> diff --git a/src/usb-device-redir-widget.<wbr>c b/src/usb-device-redir-widget.<wbr>c<br>
> new file mode 100644<br>
> index 0000000..59a6043<br>
> --- /dev/null<br>
> +++ b/src/usb-device-redir-widget.<wbr>c<br>
> @@ -0,0 +1,2065 @@<br>
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */<br>
> +/*<br>
> +   Copyright (C) 2012 Red Hat, Inc.<br>
> +<br>
> +   Red Hat Authors:<br>
> +   Alexander Nezhinsky<<a href="mailto:anezhins@redhat.com">anezhins@redhat.com</a>><br>
> +<br>
> +   This library is free software; you can redistribute it and/or<br>
> +   modify it under the terms of the GNU Lesser General Public<br>
> +   License as published by the Free Software Foundation; either<br>
> +   version 2.1 of the License, or (at your option) any later version.<br>
> +<br>
> +   This library is distributed in the hope that it will be useful,<br>
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU<br>
> +   Lesser General Public License for more details.<br>
> +<br>
> +   You should have received a copy of the GNU Lesser General Public<br>
> +   License along with this library; if not, see <<a href="http://www.gnu.org/licenses/" rel="noreferrer" target="_blank">http://www.gnu.org/licenses/</a>><wbr>.<br>
> +*/<br>
> +<br>
> +#include "config.h"<br>
> +#ifndef USB_WIDGET_TEST<br>
> +    #include <glib/gi18n-lib.h><br>
> +    #include "spice-client.h"<br>
> +    #include "spice-marshal.h"<br>
> +#else<br>
> +    #include "spice-client.h"<br>
> +#endif<br>
> +#include "usb-device-widget.h"<br>
> +<br>
> +/*<br>
> +    Debugging note:<br>
> +    Logging from this module is not affected by --spice-debug<br>
> +    command line parameter<br>
> +    Use SPICE_DEBUG=1 environment varible to enable logs<br>
> +*/<br>
> +<br>
> +#ifdef USE_NEW_USB_WIDGET<br>
> +<br>
> +/**<br>
> + * SECTION:usb-device-widget<br>
> + * @short_description: USB device selection widget<br>
> + * @title: Spice USB device selection widget<br>
> + * @section_id:<br>
> + * @see_also:<br>
> + * @stability: Under development<br>
> + * @include: spice-client-gtk.h<br>
> + *<br>
> + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily<br>
> + * add an UI to select USB devices to redirect (or unredirect).<br>
> + */<br>
> +<br>
> +struct _SpiceUsbDeviceWidget<br>
> +{<br>
> +    GtkBox parent;<br>
> +<br>
> +    SpiceUsbDeviceWidgetPrivate *priv;<br>
> +};<br>
> +<br>
> +struct _SpiceUsbDeviceWidgetClass<br>
> +{<br>
> +    GtkBoxClass parent_class;<br>
> +<br>
> +    /* signals */<br>
> +    void (*connect_failed) (SpiceUsbDeviceWidget *widget,<br>
> +                            SpiceUsbDevice *device, GError *error);<br>
> +};<br>
> +<br>
> +/* ------------------------------<wbr>------------------------------<wbr>------ */<br>
> +/* Prototypes for callbacks  */<br>
> +static void device_added_cb(<wbr>SpiceUsbDeviceManager *manager,<br>
> +    SpiceUsbDevice *device, gpointer user_data);<br>
> +static void device_removed_cb(<wbr>SpiceUsbDeviceManager *manager,<br>
> +    SpiceUsbDevice *device, gpointer user_data);<br>
> +static void device_changed_cb(<wbr>SpiceUsbDeviceManager *manager,<br>
> +    SpiceUsbDevice *device, gpointer user_data);<br>
> +static void device_error_cb(<wbr>SpiceUsbDeviceManager *manager,<br>
> +    SpiceUsbDevice *device, GError *err, gpointer user_data);<br>
> +static gboolean spice_usb_device_widget_<wbr>update_status(gpointer user_data);<br>
> +<br>
> +/* ------------------------------<wbr>------------------------------<wbr>------ */<br>
> +/* gobject glue                                                       */<br>
> +<br>
> +#define SPICE_USB_DEVICE_WIDGET_GET_<wbr>PRIVATE(obj) \<br>
> +    (G_TYPE_INSTANCE_GET_PRIVATE((<wbr>obj), SPICE_TYPE_USB_DEVICE_WIDGET, \<br>
> +                                 SpiceUsbDeviceWidgetPrivate))<br>
> +<br>
> +enum {<br>
> +    PROP_0,<br>
> +    PROP_SESSION,<br>
> +    PROP_DEVICE_FORMAT_STRING,<br>
> +};<br>
> +<br>
> +enum {<br>
> +    CONNECT_FAILED,<br>
> +    LAST_SIGNAL,<br>
> +};<br>
> +<br>
> +typedef struct {<br>
> +    GtkTreeView *tree_view;<br>
> +    GtkTreeStore *tree_store;<br>
> +} SpiceUsbDeviceWidgetTree;<br>
> +<br>
> +struct _SpiceUsbDeviceWidgetPrivate {<br>
> +    SpiceSession *session;<br>
> +    gchar *device_format_string;<br>
> +    SpiceUsbDeviceManager *manager;<br>
> +    GtkWidget *info_bar;<br>
> +    GtkWidget *label;<br>
> +    SpiceUsbDeviceWidgetTree cd_tree;<br>
> +    SpiceUsbDeviceWidgetTree usb_tree;<br>
> +    GdkPixbuf *icon_cd;<br>
> +    GdkPixbuf *icon_connected;<br>
> +    GdkPixbuf *icon_disconn;<br>
> +    GdkPixbuf *icon_warning;<br>
> +    GdkPixbuf *icon_info;<br>
> +    gchar *err_msg;<br>
> +    gsize device_count;<br>
> +};<br>
> +<br>
> +static guint signals[LAST_SIGNAL] = { 0, };<br>
> +<br>
> +G_DEFINE_TYPE(<wbr>SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX);<br>
> +<br>
> +/* TREE */<br>
> +<br>
> +enum column_id<br>
> +{<br>
> +    COL_REDIRECT = 0,<br>
> +    COL_ADDRESS,<br>
> +    COL_CONNECT_ICON,<br>
> +    COL_CD_ICON,<br>
> +    COL_VENDOR,<br>
> +    COL_PRODUCT,<br>
> +    COL_FILE,<br>
> +    COL_LOADED,<br>
> +    COL_LOCKED,<br>
> +    COL_IDLE,<br>
> +    /* internal columns */<br>
> +    COL_REVISION,<br>
> +    COL_CD_DEV,<br>
> +    COL_LUN_ITEM,<br>
> +    COL_DEV_ITEM,<br>
> +    COL_ITEM_DATA,<br>
> +    COL_CONNECTED,<br>
> +    COL_CAN_REDIRECT,<br>
> +    COL_ROW_COLOR,<br>
> +    COL_ROW_COLOR_SET,<br>
> +    NUM_COLS,<br>
> +<br>
> +    INVALID_COL<br>
> +};<br>
> +<br>
> +// there is a possibility to use different names<br>
> +// for columns in USB device list and CD device list, if needed<br>
> +// currently they are identical<br>
> +static const char *column_names_cd[NUM_COLS];<br>
> +static const char *column_names_usb[NUM_COLS];<br>
> +<br>
> +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s<br>
> +<br>
> +static void initialize_columns(void)<br>
> +{<br>
> +    SET_COLUMN(COL_REDIRECT, _("Redirect"));<br>
> +    SET_COLUMN(COL_ADDRESS, _("Address"));<br>
> +    SET_COLUMN(COL_CONNECT_ICON, _("Conn"));<br>
> +    SET_COLUMN(COL_CD_ICON, _("CD"));<br>
> +    SET_COLUMN(COL_VENDOR, _("Vendor"));<br>
> +    SET_COLUMN(COL_PRODUCT, _("Product"));<br>
> +    SET_COLUMN(COL_FILE, _("File/Device Path"));<br>
> +    SET_COLUMN(COL_LOADED, _("Loaded"));<br>
> +    SET_COLUMN(COL_LOCKED, _("Locked"));<br>
> +    SET_COLUMN(COL_IDLE, _("Idle"));<br>
> +    SET_COLUMN(COL_REVISION, "?Revision");<br>
> +    SET_COLUMN(COL_CD_DEV, "?CD_DEV");<br>
> +    SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM");<br>
> +    SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM");<br>
> +    SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA");<br>
> +    SET_COLUMN(COL_CONNECTED, "?CONNECTED");<br>
> +    SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT");<br>
> +    SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR");<br>
> +    SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET");<br>
> +};<br>
> +<br>
> +static const char *column_name(enum column_id id, gboolean is_cd)<br>
> +{<br>
> +    const char **col_name = is_cd ? column_names_cd : column_names_usb;<br>
> +    return col_name[id];<br>
> +}<br>
> +<br>
> +typedef struct _UsbWidgetLunItem {<br>
> +    SpiceUsbDeviceManager *manager;<br>
> +    SpiceUsbDevice *device;<br>
> +    guint lun;<br>
> +    SpiceUsbDeviceLunInfo info;<br>
> +} UsbWidgetLunItem;<br>
> +<br>
> +typedef struct _TreeFindUsbDev {<br>
> +    SpiceUsbDevice *usb_dev;<br>
> +    GtkTreeIter dev_iter;<br>
> +} TreeFindUsbDev;<br>
> +<br>
> +typedef void (*tree_item_toggled_cb)(<wbr>GtkCellRendererToggle *, gchar *, gpointer);<br>
> +<br>
> +static void usb_widget_add_device(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                          SpiceUsbDevice *usb_device,<br>
> +                                          GtkTreeIter *old_dev_iter);<br>
> +<br>
> +static gchar *usb_device_description(<wbr>SpiceUsbDeviceManager *manager,<br>
> +                                     SpiceUsbDevice *device,<br>
> +                                     const gchar *format)<br>
> +{<br>
> +    SpiceUsbDeviceDescription desc;<br>
> +    gchar *descriptor;<br>
> +    gchar *res;<br>
> +    spice_usb_device_get_info(<wbr>manager, device, &desc);<br>
> +    descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id);<br>
> +    if (!format) {<br>
> +        format = _("%s %s %s at %d-%d");<br>
> +    }<br>
> +    res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address);<br>
> +    g_free(desc.vendor);<br>
> +    g_free(desc.product);<br>
> +    g_free(descriptor);<br>
> +    return res;<br>
> +}<br>
> +<br>
> +static GtkTreeStore* usb_widget_create_tree_store(<wbr>void)<br>
> +{<br>
> +    GtkTreeStore *tree_store;<br>
> +<br>
> +    tree_store = gtk_tree_store_new(NUM_COLS,<br>
> +                        G_TYPE_BOOLEAN, /* COL_REDIRECT */<br>
> +                        G_TYPE_STRING, /* COL_ADDRESS */<br>
> +                        GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */<br>
> +                        GDK_TYPE_PIXBUF, /* COL_CD_ICON */<br>
> +                        G_TYPE_STRING, /* COL_VENDOR */<br>
> +                        G_TYPE_STRING, /* COL_PRODUCT */<br>
> +                        G_TYPE_STRING, /* COL_FILE */<br>
> +                        G_TYPE_BOOLEAN, /* COL_LOADED */<br>
> +                        G_TYPE_BOOLEAN, /* COL_LOCKED */<br>
> +                        G_TYPE_BOOLEAN, /* COL_IDLE */<br>
> +                        /* internal columns */<br>
> +                        G_TYPE_STRING, /* COL_REVISION */<br>
> +                        G_TYPE_BOOLEAN, /* COL_CD_DEV */<br>
> +                        G_TYPE_BOOLEAN, /* COL_LUN_ITEM */<br>
> +                        G_TYPE_BOOLEAN, /* COL_DEV_ITEM */<br>
> +                        G_TYPE_POINTER, /* COL_ITEM_DATA */<br>
> +                        G_TYPE_BOOLEAN, /* COL_CONNECTED */<br>
> +                        G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */<br>
> +                        G_TYPE_STRING, /* COL_ROW_COLOR */<br>
> +                        G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ );<br>
> +    SPICE_DEBUG("tree store created");<br>
> +<br>
> +    return tree_store;<br>
> +}<br>
> +<br>
> +static GdkPixbuf *get_named_icon(const gchar *name, gint size)<br>
> +{<br>
> +    GtkIconInfo *info = gtk_icon_theme_lookup_icon(<wbr>gtk_icon_theme_get_default(), name, size, 0);<br>
> +    GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL);<br>
> +    g_object_unref (info);<br>
> +    return pixbuf;<br>
> +}<br>
> +<br>
> +static void select_widget_size(GtkWidget *wg)<br>
> +{<br>
> +    GdkDisplay *d = gtk_widget_get_display(wg);<br>
> +    int i, w = 2000, h = 1024;<br>
> +    int n = gdk_display_get_n_monitors(d);<br>
> +    for (i = 0; i < n; ++i)<br>
> +    {<br>
> +        GdkMonitor *m = gdk_display_get_monitor(d, i);<br>
> +        if (m) {<br>
> +            GdkRectangle area;<br>
> +            gdk_monitor_get_workarea(m, &area);<br>
> +            SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)",<br>
> +                i, area.width, area.height, area.x, area.y );<br>
> +            w = MIN(w, area.width);<br>
> +            h = MIN(h, area.height);<br>
> +        }<br>
> +    }<br>
> +<br>
> +    w = (w * 3) / 4;<br>
> +    h = h / 2;<br>
> +<br>
> +    SPICE_DEBUG("sizing widget as %d x %d", w, h);<br>
> +    gtk_widget_set_size_request(<wbr>wg, w, h);<br>
> +}<br>
> +<br>
> +static void usb_widget_add_device(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                          SpiceUsbDevice *usb_device,<br>
> +                                          GtkTreeIter *old_dev_iter)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;<br>
> +    SpiceUsbDeviceWidgetTree *tree;<br>
> +    GtkTreeView *tree_view;<br>
> +    GtkTreeStore *tree_store;<br>
> +    GtkTreeIter new_dev_iter;<br>
> +    SpiceUsbDeviceDescription dev_info;<br>
> +    GtkTreePath *new_dev_path;<br>
> +    gboolean is_dev_redirected, is_dev_connected, is_dev_cd;<br>
> +    gchar *addr_str;<br>
> +    GArray *lun_array;<br>
> +    guint lun_index;<br>
> +    GError *error = NULL;<br>
> +<br>
> +    is_dev_cd = spice_usb_device_manager_is_<wbr>device_cd(usb_dev_mgr, usb_device);<br>
> +    tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree;<br>
> +    tree_view = tree->tree_view;<br>
> +    tree_store = tree->tree_store;<br>
> +<br>
> +    if (old_dev_iter == NULL) {<br>
> +        gtk_tree_store_append(tree_<wbr>store, &new_dev_iter, NULL);<br>
> +    } else {<br>
> +        gtk_tree_store_insert_after(<wbr>tree_store, &new_dev_iter, NULL, old_dev_iter);<br>
> +        gtk_tree_store_remove(tree_<wbr>store, old_dev_iter);<br>
> +    }<br>
> +<br>
> +    spice_usb_device_get_info(usb_<wbr>dev_mgr, usb_device, &dev_info);<br>
> +    addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address);<br>
> +    is_dev_connected = spice_usb_device_manager_is_<wbr>device_connected(usb_dev_mgr, usb_device);<br>
> +    is_dev_redirected = is_dev_connected;<br>
> +    SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d",<br>
> +        addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd);<br>
> +<br>
> +    gtk_tree_store_set(tree_store, &new_dev_iter,<br>
> +        COL_REDIRECT, is_dev_redirected,<br>
> +        COL_ADDRESS, addr_str,<br>
> +        COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn,<br>
> +        COL_CD_ICON, priv->icon_cd,<br>
> +        COL_VENDOR, dev_info.vendor,<br>
> +        COL_PRODUCT, dev_info.product,<br>
> +        COL_CD_DEV, is_dev_cd,<br>
> +        COL_LUN_ITEM, FALSE, /* USB device item */<br>
> +        COL_DEV_ITEM, TRUE, /* USB device item */<br>
> +        COL_ITEM_DATA, (gpointer)usb_device,<br>
> +        COL_CONNECTED, is_dev_connected,<br>
> +        COL_CAN_REDIRECT, spice_usb_device_manager_can_<wbr>redirect_device(usb_dev_mgr, usb_device, &error),<br>
> +        COL_ROW_COLOR, "beige",<br>
> +        COL_ROW_COLOR_SET, TRUE,<br>
> +        -1);<br>
> +    g_clear_error(&error);<br>
> +<br>
> +    priv->device_count++;<br>
> +<br>
> +    /* get all the luns */<br>
> +    lun_array = spice_usb_device_manager_get_<wbr>device_luns(usb_dev_mgr, usb_device);<br>
> +    for (lun_index = 0; lun_index < lun_array->len; lun_index++) {<br>
> +        UsbWidgetLunItem *lun_item;<br>
> +        GtkTreeIter lun_iter;<br>
> +        gchar lun_str[8];<br>
> +<br>
> +        lun_item = g_malloc(sizeof(*lun_item));<br>
> +        lun_item->manager = usb_dev_mgr;<br>
> +        lun_item->device = usb_device;<br>
> +        lun_item->lun = g_array_index(lun_array, guint, lun_index);<br>
> +        spice_usb_device_manager_<wbr>device_lun_get_info(usb_dev_<wbr>mgr, usb_device, lun_item->lun, &lun_item->info);<br>
> +        SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p",<br>
> +                lun_index, lun_item->info.vendor, lun_item->info.product,<br>
> +                lun_item->info.revision, lun_item->info.file_path, lun_item);<br>
> +        g_snprintf(lun_str, 8, "↳%u", lun_item->lun);<br>
> +<br>
> +        /* Append LUN as a child of USB device */<br>
> +        gtk_tree_store_append(tree_<wbr>store, &lun_iter, &new_dev_iter);<br>
> +        gtk_tree_store_set(tree_store, &lun_iter,<br>
> +                COL_ADDRESS, lun_str,<br>
> +                COL_VENDOR, lun_item->info.vendor,<br>
> +                COL_PRODUCT, lun_item->info.product,<br>
> +                COL_REVISION, lun_item->info.revision,<br>
> +                COL_FILE, lun_item->info.file_path,<br>
> +                COL_LOADED, lun_item->info.loaded,<br>
> +                COL_LOCKED, lun_item->info.locked,<br>
> +                COL_IDLE, !lun_item->info.started,<br>
> +                COL_CD_DEV, FALSE,<br>
> +                COL_LUN_ITEM, TRUE, /* LUN item */<br>
> +                COL_DEV_ITEM, FALSE, /* LUN item */<br>
> +                COL_ITEM_DATA, (gpointer)lun_item,<br>
> +                COL_CONNECTED, is_dev_connected,<br>
> +                COL_ROW_COLOR, "azure",<br>
> +                COL_ROW_COLOR_SET, TRUE,<br>
> +                -1);<br>
> +    }<br>
> +    g_array_unref(lun_array);<br>
> +<br>
> +    new_dev_path = gtk_tree_model_get_path(GTK_<wbr>TREE_MODEL(tree_store), &new_dev_iter);<br>
> +    gtk_tree_view_expand_row(tree_<wbr>view, new_dev_path, FALSE);<br>
> +    gtk_tree_path_free(new_dev_<wbr>path);<br>
> +<br>
> +    g_free(dev_info.vendor);<br>
> +    g_free(dev_info.product);<br>
> +    g_free(addr_str);<br>
> +}<br>
> +<br>
> +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter)<br>
> +{<br>
> +    gboolean is_lun;<br>
> +    gtk_tree_model_get(GTK_TREE_<wbr>MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);<br>
> +    return is_lun;<br>
> +}<br>
> +<br>
> +static gboolean usb_widget_tree_store_find_<wbr>usb_dev_foreach_cb(<wbr>GtkTreeModel *tree_model,<br>
> +                                                              GtkTreePath *path, GtkTreeIter *iter,<br>
> +                                                              gpointer user_data)<br>
> +{<br>
> +    TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data;<br>
> +    SpiceUsbDevice *find_usb_device = find_info->usb_dev;<br>
> +    SpiceUsbDevice *usb_device;<br>
> +    gboolean is_lun_item;<br>
> +<br>
> +    gtk_tree_model_get(tree_model, iter,<br>
> +                       COL_LUN_ITEM, &is_lun_item,<br>
> +                       COL_ITEM_DATA, (gpointer *)&usb_device,<br>
> +                       -1);<br>
> +    if (!is_lun_item && usb_device == find_usb_device) {<br>
> +        find_info->dev_iter = *iter;<br>
> +        find_info->usb_dev  = NULL;<br>
> +        SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter);<br>
> +        return TRUE; /* stop iterating */<br>
> +    } else {<br>
> +        return FALSE; /* continue iterating */<br>
> +    }<br>
> +}<br>
> +<br>
> +static GtkTreeIter *usb_widget_tree_store_find_<wbr>usb_device(GtkTreeStore *tree_store,<br>
> +                                                          SpiceUsbDevice *usb_device)<br>
> +{<br>
> +    TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} };<br>
> +    GtkTreeIter *iter = NULL;<br>
> +<br>
> +    gtk_tree_model_foreach(GTK_<wbr>TREE_MODEL(tree_store),<br>
> +                           usb_widget_tree_store_find_<wbr>usb_dev_foreach_cb, (gpointer)&find_info);<br>
> +    // the callback sets 'usb_dev' field to zero if it finds the device<br>
> +    if (!find_info.usb_dev) {<br>
> +        iter = g_malloc(sizeof(*iter));<br>
> +        *iter = find_info.dev_iter;<br>
> +    }<br>
> +    return iter;<br>
> +}<br>
> +<br>
> +static gboolean usb_widget_remove_device(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                         SpiceUsbDevice *usb_device)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeIter *old_dev_iter;<br>
> +    GtkTreeStore *tree_store = priv->usb_tree.tree_store;<br>
> +    // on WIN32 it is possible the backend device is already removed<br>
> +    // from the USB device manager list and we can't know it was<br>
> +    // USB or CD, do we will try both lists<br>
> +    old_dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_device);<br>
> +    if (old_dev_iter != NULL) {<br>
> +        SPICE_DEBUG("USB Device removed");<br>
> +        gtk_tree_store_remove(tree_<wbr>store, old_dev_iter);<br>
> +        priv->device_count--;<br>
> +        g_free(old_dev_iter);<br>
> +        return TRUE;<br>
> +    }<br>
> +    tree_store = priv->cd_tree.tree_store;<br>
> +    if (tree_store) {<br>
> +        old_dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_device);<br>
> +    }<br>
> +    if (old_dev_iter != NULL) {<br>
> +        SPICE_DEBUG("CD Device removed");<br>
> +        gtk_tree_store_remove(tree_<wbr>store, old_dev_iter);<br>
> +        priv->device_count--;<br>
> +        g_free(old_dev_iter);<br>
> +        return TRUE;<br>
> +    }<br>
> +    SPICE_DEBUG("Device %p not found!", usb_device);<br>
> +    return FALSE;<br>
> +}<br>
> +<br>
> +static GtkTreeViewColumn* view_add_toggle_column(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                                 enum column_id toggle_col_id,<br>
> +                                                 enum column_id visible_col_id,<br>
> +                                                 enum column_id sensitive_col_id,<br>
> +                                                 tree_item_toggled_cb toggled_cb,<br>
> +                                                 gboolean is_cd)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkCellRenderer     *renderer;<br>
> +    GtkTreeViewColumn   *view_col;<br>
> +    const char *col_name = column_name(toggle_col_id, is_cd);<br>
> +<br>
> +    renderer = gtk_cell_renderer_toggle_new()<wbr>;<br>
> +<br>
> +    if (sensitive_col_id != INVALID_COL) {<br>
> +        view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                        col_name,<br>
> +                        renderer,<br>
> +                        "active", toggle_col_id,<br>
> +                        "visible", visible_col_id,<br>
> +                        "activatable", sensitive_col_id,<br>
> +                        NULL);<br>
> +    } else {<br>
> +        view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                        col_name,<br>
> +                        renderer,<br>
> +                        "active", toggle_col_id,<br>
> +                        "visible", visible_col_id,<br>
> +                        NULL);<br>
> +    }<br>
> +<br>
> +    gtk_tree_view_column_set_<wbr>sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);<br>
> +    gtk_tree_view_column_set_<wbr>expand(view_col, FALSE);<br>
> +    gtk_tree_view_column_set_<wbr>resizable(view_col, FALSE);<br>
> +    gtk_tree_view_append_column(<wbr>is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);<br>
> +<br>
> +    g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self);<br>
> +<br>
> +    SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]",<br>
> +            toggle_col_id, col_name,<br>
> +            visible_col_id, column_name(visible_col_id, is_cd));<br>
> +    return view_col;<br>
> +}<br>
> +<br>
> +static GtkTreeViewColumn* view_add_read_only_toggle_<wbr>column(SpiceUsbDeviceWidget *self,<br>
> +                                                           enum column_id toggle_col_id,<br>
> +                                                           enum column_id visible_col_id,<br>
> +                                                           gboolean is_cd)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkCellRenderer     *renderer;<br>
> +    GtkTreeViewColumn   *view_col;<br>
> +    const char *col_name = column_name(toggle_col_id, is_cd);<br>
> +<br>
> +    renderer = gtk_cell_renderer_toggle_new()<wbr>;<br>
> +<br>
> +    view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                    col_name,<br>
> +                    renderer,<br>
> +                    "active", toggle_col_id,<br>
> +                    "visible", visible_col_id,<br>
> +                    NULL);<br>
> +<br>
> +    gtk_cell_renderer_toggle_set_<wbr>activatable(GTK_CELL_RENDERER_<wbr>TOGGLE(renderer), FALSE);<br>
> +<br>
> +    gtk_tree_view_column_set_<wbr>sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED);<br>
> +    gtk_tree_view_column_set_<wbr>expand(view_col, FALSE);<br>
> +    gtk_tree_view_column_set_<wbr>resizable(view_col, FALSE);<br>
> +    gtk_tree_view_append_column(<wbr>is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);<br>
> +<br>
> +    SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]",<br>
> +            toggle_col_id, col_name,<br>
> +            visible_col_id, column_name(visible_col_id, is_cd));<br>
> +    return view_col;<br>
> +}<br>
> +<br>
> +static GtkTreeViewColumn* view_add_text_column(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                               enum column_id col_id,<br>
> +                                               gboolean expandable,<br>
> +                                               gboolean is_cd)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkCellRenderer     *renderer;<br>
> +    GtkTreeViewColumn   *view_col;<br>
> +<br>
> +    renderer = gtk_cell_renderer_text_new();<br>
> +<br>
> +    view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                    column_name(col_id, is_cd),<br>
> +                    renderer,<br>
> +                    "text", col_id,<br>
> +                    //"cell-background", COL_ROW_COLOR,<br>
> +                    //"cell-background-set", COL_ROW_COLOR_SET,<br>
> +                    NULL);<br>
> +<br>
> +    gtk_tree_view_column_set_<wbr>sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_<wbr>ONLY);<br>
> +    gtk_tree_view_column_set_<wbr>resizable(view_col, TRUE);<br>
> +    gtk_tree_view_column_set_<wbr>expand(view_col, expandable);<br>
> +<br>
> +    gtk_tree_view_append_column(<wbr>is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);<br>
> +<br>
> +    SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd));<br>
> +    return view_col;<br>
> +}<br>
> +<br>
> +static GtkTreeViewColumn* view_add_pixbuf_column(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                                 enum column_id col_id,<br>
> +                                                 enum column_id visible_col_id,<br>
> +                                                 gboolean is_cd)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkCellRenderer     *renderer;<br>
> +    GtkTreeViewColumn   *view_col;<br>
> +    const char *col_name = column_name(col_id, is_cd);<br>
> +<br>
> +    renderer = gtk_cell_renderer_pixbuf_new()<wbr>;<br>
> +<br>
> +    if (visible_col_id == INVALID_COL) {<br>
> +        view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                        col_name,<br>
> +                        renderer,<br>
> +                        "pixbuf", col_id,<br>
> +                        NULL);<br>
> +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name);<br>
> +    } else {<br>
> +        view_col = gtk_tree_view_column_new_with_<wbr>attributes(<br>
> +                        col_name,<br>
> +                        renderer,<br>
> +                        "pixbuf", col_id,<br>
> +                        "visible", visible_col_id,<br>
> +                        NULL);<br>
> +        SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]",<br>
> +                col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd));<br>
> +    }<br>
> +    gtk_tree_view_append_column(<wbr>is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col);<br>
> +    return view_col;<br>
> +}<br>
> +<br>
> +/* Toggle handlers */<br>
> +<br>
> +static gboolean tree_item_toggle_get_val(<wbr>GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id)<br>
> +{<br>
> +    gboolean toggle_val;<br>
> +<br>
> +    gtk_tree_model_get_iter_from_<wbr>string(GTK_TREE_MODEL(tree_<wbr>store), iter, path_str);<br>
> +    gtk_tree_model_get(GTK_TREE_<wbr>MODEL(tree_store), iter, col_id, &toggle_val, -1);<br>
> +<br>
> +    return toggle_val;<br>
> +}<br>
> +<br>
> +static void tree_item_toggle_set(<wbr>GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val)<br>
> +{<br>
> +    gtk_tree_store_set(tree_store, iter, col_id, new_val, -1);<br>
> +}<br>
> +<br>
> +typedef struct _connect_cb_data {<br>
> +    SpiceUsbDeviceWidget *self;<br>
> +    SpiceUsbDevice *usb_dev;<br>
> +} connect_cb_data;<br>
> +<br>
> +static void connect_cb_data_free(connect_<wbr>cb_data *user_data)<br>
> +{<br>
> +    spice_usb_device_widget_<wbr>update_status(user_data->self)<wbr>;<br>
> +    g_object_unref(user_data-><wbr>self);<br>
> +    g_boxed_free(spice_usb_device_<wbr>get_type(), user_data->usb_dev);<br>
> +    g_free(user_data);<br>
> +}<br>
> +<br>
> +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)<br>
> +{<br>
> +    connect_cb_data *cb_data = user_data;<br>
> +    SpiceUsbDeviceWidget *self = cb_data->self;<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;<br>
> +    GError *err = NULL;<br>
> +    GtkTreeIter *dev_iter;<br>
> +    gchar *desc;<br>
> +    gboolean finished;<br>
> +    gboolean is_cd = spice_usb_device_manager_is_<wbr>device_cd(priv->manager, usb_dev);<br>
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;<br>
> +<br>
> +    dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_dev);<br>
> +    if (!dev_iter) {<br>
> +        return;<br>
> +    }<br>
> +<br>
> +    desc = usb_device_description(priv-><wbr>manager, usb_dev, priv->device_format_string);<br>
> +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);<br>
> +<br>
> +    finished = spice_usb_device_manager_<wbr>connect_device_finish(priv-><wbr>manager, res, &err);<br>
> +    if (finished) {<br>
> +        gtk_tree_store_set(tree_store, dev_iter,<br>
> +                           COL_CONNECT_ICON, priv->icon_connected,<br>
> +                           COL_CONNECTED, TRUE,<br>
> +                           -1);<br>
> +    } else {<br>
> +        gtk_tree_store_set(tree_store, dev_iter,<br>
> +                           COL_REDIRECT, FALSE,<br>
> +                           -1);<br>
> +        g_prefix_error(&err, "Device connect failed %s: ", desc);<br>
> +        if (err) {<br>
> +            SPICE_DEBUG("%s", err->message);<br>
> +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err);<br>
> +            g_error_free(err);<br>
> +        } else {<br>
> +            g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL);<br>
> +        }<br>
> +<br>
> +        /* don't trigger a disconnect if connect failed */<br>
> +        /*<br>
> +        g_signal_handlers_block_by_<wbr>func(GTK_TOGGLE_BUTTON(user_<wbr>data->check),<br>
> +                                        checkbox_clicked_cb, self);<br>
> +        gtk_toggle_button_set_active(<wbr>GTK_TOGGLE_BUTTON(user_data-><wbr>check), FALSE);<br>
> +        g_signal_handlers_unblock_by_<wbr>func(GTK_TOGGLE_BUTTON(user_<wbr>data->check),<br>
> +                                        checkbox_clicked_cb, self);<br>
> +        */<br>
> +    }<br>
> +    g_free(desc);<br>
> +    g_free(dev_iter);<br>
> +    connect_cb_data_free(user_<wbr>data);<br>
> +}<br>
> +<br>
> +static void usb_widget_disconnect_cb(<wbr>GObject *source_object, GAsyncResult *res, gpointer user_data)<br>
> +{<br>
> +    connect_cb_data *cb_data = user_data;<br>
> +    SpiceUsbDeviceWidget *self = cb_data->self;<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    SpiceUsbDevice *usb_dev = cb_data->usb_dev;<br>
> +    GError *err = NULL;<br>
> +    GtkTreeIter *dev_iter;<br>
> +    gchar *desc;<br>
> +    gboolean finished;<br>
> +    gboolean is_cd = spice_usb_device_manager_is_<wbr>device_cd(priv->manager, usb_dev);<br>
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;<br>
> +<br>
> +    dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_dev);<br>
> +    if (!dev_iter) {<br>
> +        return;<br>
> +    }<br>
> +<br>
> +    desc = usb_device_description(priv-><wbr>manager, usb_dev, priv->device_format_string);<br>
> +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);<br>
> +<br>
> +    finished = spice_usb_device_manager_<wbr>disconnect_device_finish(priv-<wbr>>manager, res, &err);<br>
> +    if (finished) {<br>
> +        gtk_tree_store_set(tree_store, dev_iter,<br>
> +                           COL_CONNECT_ICON, priv->icon_disconn,<br>
> +                           COL_CONNECTED, FALSE,<br>
> +                           -1);<br>
> +    } else {<br>
> +        gtk_tree_store_set(tree_store, dev_iter,<br>
> +                           COL_REDIRECT, TRUE,<br>
> +                           -1);<br>
> +        g_prefix_error(&err, "Device disconnect failed %s: ", desc);<br>
> +        if (err) {<br>
> +            SPICE_DEBUG("%s", err->message);<br>
> +            g_error_free(err);<br>
> +        }<br>
> +    }<br>
> +    g_free(desc);<br>
> +    g_free(dev_iter);<br>
> +    connect_cb_data_free(user_<wbr>data);<br>
> +}<br>
> +<br>
> +static void tree_item_toggled_cb_redirect(<wbr>GtkCellRendererToggle *cell,<br>
> +                                          gchar *path_str,<br>
> +                                          SpiceUsbDeviceWidget *self,<br>
> +                                          GtkTreeStore *tree_store)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    connect_cb_data *cb_data = g_new(connect_cb_data, 1);<br>
> +    SpiceUsbDevice *usb_dev;<br>
> +    GtkTreeIter iter;<br>
> +    gboolean new_redirect_val;<br>
> +<br>
> +    new_redirect_val = !tree_item_toggle_get_val(<wbr>tree_store, path_str, &iter, COL_REDIRECT);<br>
> +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");<br>
> +    tree_item_toggle_set(tree_<wbr>store, &iter, COL_REDIRECT, new_redirect_val);<br>
> +<br>
> +    gtk_tree_model_get(GTK_TREE_<wbr>MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1);<br>
> +    cb_data->self = g_object_ref(self);<br>
> +    cb_data->usb_dev = g_boxed_copy(spice_usb_device_<wbr>get_type(), usb_dev);<br>
> +<br>
> +    if (new_redirect_val) {<br>
> +        spice_usb_device_manager_<wbr>connect_device_async(priv-><wbr>manager, usb_dev,<br>
> +                                                      NULL, /* cancellable */<br>
> +                                                      usb_widget_connect_cb, cb_data);<br>
> +    } else {<br>
> +        spice_usb_device_manager_<wbr>disconnect_device_async(priv-><wbr>manager, usb_dev,<br>
> +                                                         NULL, /* cancellable */<br>
> +                                                         usb_widget_disconnect_cb, cb_data);<br>
> +<br>
> +    }<br>
> +    spice_usb_device_widget_<wbr>update_status(self);<br>
> +}<br>
> +<br>
> +static void tree_item_toggled_cb_redirect_<wbr>cd(GtkCellRendererToggle *cell,<br>
> +                                             gchar *path_str,<br>
> +                                             gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    tree_item_toggled_cb_redirect(<wbr>cell, path_str, self, priv->cd_tree.tree_store);<br>
> +}<br>
> +<br>
> +static void tree_item_toggled_cb_redirect_<wbr>usb(GtkCellRendererToggle *cell,<br>
> +                                             gchar *path_str,<br>
> +                                             gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    tree_item_toggled_cb_redirect(<wbr>cell, path_str, self, priv->usb_tree.tree_store);<br>
> +}<br>
> +<br>
> +/* Signal handlers */<br>
> +<br>
> +static void device_added_cb(<wbr>SpiceUsbDeviceManager *usb_dev_mgr,<br>
> +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +<br>
> +    SPICE_DEBUG("Signal: Device Added");<br>
> +<br>
> +    usb_widget_add_device(self, usb_device, NULL);<br>
> +<br>
> +    spice_usb_device_widget_<wbr>update_status(self);<br>
> +}<br>
> +<br>
> +static void device_removed_cb(<wbr>SpiceUsbDeviceManager *usb_dev_mgr,<br>
> +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    gboolean dev_removed;<br>
> +<br>
> +    SPICE_DEBUG("Signal: Device Removed");<br>
> +<br>
> +    dev_removed = usb_widget_remove_device(self, usb_device);<br>
> +    if (dev_removed) {<br>
> +        spice_usb_device_widget_<wbr>update_status(self);<br>
> +    }<br>
> +}<br>
> +<br>
> +static void device_changed_cb(<wbr>SpiceUsbDeviceManager *usb_dev_mgr,<br>
> +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeIter *old_dev_iter;<br>
> +    gboolean is_cd = spice_usb_device_manager_is_<wbr>device_cd(priv->manager, usb_device);<br>
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;<br>
> +<br>
> +    SPICE_DEBUG("Signal: Device Changed");<br>
> +<br>
> +    old_dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_device);<br>
> +    if (old_dev_iter != NULL) {<br>
> +<br>
> +        usb_widget_add_device(self, usb_device, old_dev_iter);<br>
> +<br>
> +        spice_usb_device_widget_<wbr>update_status(self);<br>
> +        g_free(old_dev_iter);<br>
> +    } else {<br>
> +        SPICE_DEBUG("Device not found!");<br>
> +    }<br>
> +}<br>
> +<br>
> +static void device_error_cb(<wbr>SpiceUsbDeviceManager *manager,<br>
> +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeIter *dev_iter;<br>
> +    gboolean is_cd = spice_usb_device_manager_is_<wbr>device_cd(priv->manager, usb_device);<br>
> +    GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store;<br>
> +<br>
> +    SPICE_DEBUG("Signal: Device Error");<br>
> +<br>
> +    dev_iter = usb_widget_tree_store_find_<wbr>usb_device(tree_store, usb_device);<br>
> +    if (dev_iter != NULL) {<br>
> +        tree_item_toggle_set(tree_<wbr>store, dev_iter, COL_REDIRECT, FALSE);<br>
> +        spice_usb_device_widget_<wbr>update_status(self);<br>
> +        g_free(dev_iter);<br>
> +    } else {<br>
> +        SPICE_DEBUG("Device not found!");<br>
> +    }<br>
> +}<br>
> +<br>
> +/* Selection handler */<br>
> +<br>
> +static void tree_selection_changed_cb(<wbr>GtkTreeSelection *select, gpointer user_data)<br>
> +{<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +    GtkTreePath *path;<br>
> +    gboolean is_lun;<br>
> +    UsbWidgetLunItem *lun_item;<br>
> +    gchar *txt[3];<br>
> +<br>
> +    if (gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        gtk_tree_model_get(tree_model, &iter,<br>
> +                COL_VENDOR, &txt[0],<br>
> +                COL_PRODUCT, &txt[1],<br>
> +                COL_REVISION, &txt[2],<br>
> +                COL_LUN_ITEM, &is_lun,<br>
> +                COL_ITEM_DATA, (gpointer *)&lun_item,<br>
> +                -1);<br>
> +        path = gtk_tree_model_get_path(tree_<wbr>model, &iter);<br>
> +<br>
> +        SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]",<br>
> +                txt[0], txt[1],<br>
> +                is_lun ? txt[2] : "--",<br>
> +                is_lun ? "LUN" : "USB-DEV",<br>
> +                is_lun ? lun_item->info.file_path : "--",<br>
> +                gtk_tree_path_to_string(path))<wbr>;<br>
> +<br>
> +        if (txt[0]) {<br>
> +            g_free(txt[0]);<br>
> +        }<br>
> +        if (txt[1]) {<br>
> +            g_free(txt[1]);<br>
> +        }<br>
> +        if (txt[2]) {<br>
> +            g_free(txt[2]);<br>
> +        }<br>
> +        gtk_tree_path_free(path);<br>
> +    }<br>
> +}<br>
> +<br>
> +static GtkTreeSelection* set_selection_handler(<wbr>GtkTreeView *tree_view)<br>
> +{<br>
> +    GtkTreeSelection *select;<br>
> +<br>
> +    select = gtk_tree_view_get_selection(<wbr>tree_view);<br>
> +    gtk_tree_selection_set_mode(<wbr>select, GTK_SELECTION_SINGLE);<br>
> +<br>
> +    g_signal_connect(G_OBJECT(<wbr>select), "changed",<br>
> +                     G_CALLBACK(tree_selection_<wbr>changed_cb),<br>
> +                     NULL);<br>
> +<br>
> +    SPICE_DEBUG("selection handler set");<br>
> +    return select;<br>
> +}<br>
> +<br>
> +static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent)<br>
> +{<br>
> +    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_<wbr>HORIZONTAL, 6);<br>
> +    GtkWidget *icon = gtk_image_new_from_icon_name(<wbr>icon_name, GTK_ICON_SIZE_MENU);<br>
> +    GtkWidget *label = gtk_accel_label_new(label_str)<wbr>;<br>
> +    GtkAccelGroup *accel_group = gtk_accel_group_new();<br>
> +    guint accel_key;<br>
> +<br>
> +    /* add icon */<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(box), icon);<br>
> +<br>
> +    /* add label */<br>
> +    gtk_label_set_xalign(GTK_<wbr>LABEL(label), 0.0);<br>
> +    gtk_label_set_use_underline(<wbr>GTK_LABEL(label), TRUE);<br>
> +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);<br>
> +    gtk_widget_add_accelerator(<wbr>parent, "activate", accel_group, accel_key,<br>
> +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);<br>
> +    gtk_accel_label_set_accel_<wbr>widget(GTK_ACCEL_LABEL(label), parent);<br>
> +    gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0);<br>
> +<br>
> +    /* add the new box to the parent widget */<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(parent), box);<br>
> +<br>
> +    return box;<br>
> +}<br>
> +<br>
> +/* LUN properties dialog */<br>
> +<br>
> +typedef struct _lun_properties_dialog {<br>
> +    GtkWidget *dialog;<br>
> +    GtkWidget *advanced_grid;<br>
> +    gboolean advanced_shown;<br>
> +<br>
> +    GtkWidget *file_entry;<br>
> +    GtkWidget *vendor_entry;<br>
> +    GtkWidget *product_entry;<br>
> +    GtkWidget *revision_entry;<br>
> +    GtkWidget *loaded_switch;<br>
> +    GtkWidget *locked_switch;<br>
> +} lun_properties_dialog;<br>
> +<br>
> +#if 1<br>
> +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)<br>
> +{<br>
> +    GtkWidget *file_entry = (GtkWidget *)user_data;<br>
> +    GtkFileChooserNative *native;<br>
> +    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;<br>
> +    gint res;<br>
> +<br>
> +    native = gtk_file_chooser_native_new("<wbr>Choose File for USB CD",<br>
> +        GTK_WINDOW(gtk_widget_get_<wbr>toplevel(file_entry)),<br>
> +        action,<br>
> +        "_Open",<br>
> +        "_Cancel");<br>
> +<br>
> +    res = gtk_native_dialog_run(GTK_<wbr>NATIVE_DIALOG(native));<br>
> +    if (res == GTK_RESPONSE_ACCEPT) {<br>
> +        char *filename = gtk_file_chooser_get_filename(<wbr>GTK_FILE_CHOOSER(native));<br>
> +        gtk_entry_set_alignment(GTK_<wbr>ENTRY(file_entry), 1);<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>file_entry), filename);<br>
> +        g_free(filename);<br>
> +    }<br>
> +    else {<br>
> +        gtk_widget_grab_focus(button);<br>
> +    }<br>
> +<br>
> +    g_object_unref(native);<br>
> +}<br>
> +#else<br>
> +// to be removed<br>
> +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data)<br>
> +{<br>
> +    GtkWidget *file_entry = (GtkWidget *)user_data;<br>
> +    GtkWidget *dialog;<br>
> +    gint res;<br>
> +<br>
> +    dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD",<br>
> +                                          GTK_WINDOW(gtk_widget_get_<wbr>toplevel(file_entry)),<br>
> +                                          GTK_FILE_CHOOSER_ACTION_OPEN,<br>
> +                                          "_Cancel",<br>
> +                                          GTK_RESPONSE_CANCEL,<br>
> +                                          "_Ok",<br>
> +                                          GTK_RESPONSE_ACCEPT,<br>
> +                                          NULL);<br>
> +    gtk_dialog_set_default_<wbr>response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);<br>
> +<br>
> +    res = gtk_dialog_run(GTK_DIALOG(<wbr>dialog));<br>
> +    if (res == GTK_RESPONSE_ACCEPT) {<br>
> +        char *filename = gtk_file_chooser_get_filename(<wbr>GTK_FILE_CHOOSER(dialog));<br>
> +        gtk_entry_set_alignment(GTK_<wbr>ENTRY(file_entry), 1);<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>file_entry), filename);<br>
> +        g_free(filename);<br>
> +    }<br>
> +    gtk_widget_destroy(dialog);<br>
> +}<br>
> +#endif<br>
> +<br>
> +static gboolean lun_properties_dialog_loaded_<wbr>switch_cb(GtkWidget *widget,<br>
> +                                                       gboolean state, gpointer user_data)<br>
> +{<br>
> +    lun_properties_dialog *lun_dialog = user_data;<br>
> +<br>
> +    gtk_widget_set_sensitive(lun_<wbr>dialog->locked_switch, state);<br>
> +    gtk_widget_set_can_focus(lun_<wbr>dialog->locked_switch, state);<br>
> +<br>
> +    return FALSE; /* call default signal handler */<br>
> +}<br>
> +<br>
> +static void lun_properties_dialog_toggle_<wbr>advanced(GtkWidget *widget, gpointer user_data)<br>
> +{<br>
> +    lun_properties_dialog *lun_dialog = user_data;<br>
> +<br>
> +    if (lun_dialog->advanced_shown) {<br>
> +        gtk_widget_hide(lun_dialog-><wbr>advanced_grid);<br>
> +        lun_dialog->advanced_shown = FALSE;<br>
> +    } else {<br>
> +        gtk_widget_show_all(lun_<wbr>dialog->advanced_grid);<br>
> +        lun_dialog->advanced_shown = TRUE;<br>
> +    }<br>
> +}<br>
> +<br>
> +static void create_lun_properties_dialog(<wbr>SpiceUsbDeviceWidget *self,<br>
> +                                         GtkWidget *parent_window,<br>
> +                                         SpiceUsbDeviceLunInfo *lun_info,<br>
> +                                         lun_properties_dialog *lun_dialog)<br>
> +{<br>
> +    // SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkWidget *dialog, *content_area;<br>
> +    GtkWidget *grid, *advanced_grid;<br>
> +    GtkWidget *file_entry, *choose_button;<br>
> +    GtkWidget *advanced_button, *advanced_icon;<br>
> +    GtkWidget *vendor_entry, *product_entry, *revision_entry;<br>
> +    GtkWidget *loaded_switch, *loaded_label;<br>
> +    GtkWidget *locked_switch, *locked_label;<br>
> +    gint nrow = 0;<br>
> +<br>
> +    dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings",<br>
> +                    GTK_WINDOW(parent_window),<br>
> +                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_<wbr>PARENT, /* flags */<br>
> +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,<br>
> +                    "Cancel", GTK_RESPONSE_REJECT,<br>
> +                    NULL);<br>
> +<br>
> +    gtk_dialog_set_default_<wbr>response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);<br>
> +    gtk_container_set_border_<wbr>width(GTK_CONTAINER(dialog), 12);<br>
> +    gtk_box_set_spacing(GTK_BOX(<wbr>gtk_bin_get_child(GTK_BIN(<wbr>dialog))), 12);<br>
> +<br>
> +    content_area = gtk_dialog_get_content_area(<wbr>GTK_DIALOG(dialog));<br>
> +<br>
> +    /* main grid - always visible */<br>
> +    grid = gtk_grid_new();<br>
> +    gtk_grid_set_row_spacing(GTK_<wbr>GRID(grid), 12);<br>
> +    gtk_grid_set_column_<wbr>homogeneous(GTK_GRID(grid), FALSE);<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(content_area), grid);<br>
> +<br>
> +    /* File path label */<br>
> +    gtk_grid_attach(GTK_GRID(grid)<wbr>,<br>
> +            gtk_label_new("Select file or device"),<br>
> +            0, nrow++, // left top<br>
> +            7, 1); // width height<br>
> +<br>
> +    /* file/device path entry */<br>
> +    file_entry = gtk_entry_new();<br>
> +    gtk_widget_set_hexpand(file_<wbr>entry, TRUE);<br>
> +    if (!lun_info) {<br>
> +        gtk_entry_set_placeholder_<wbr>text(GTK_ENTRY(file_entry), "file-path");<br>
> +    } else {<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>file_entry), lun_info->file_path);<br>
> +        if (lun_info->loaded) {<br>
> +            gtk_editable_set_editable(GTK_<wbr>EDITABLE(file_entry), FALSE);<br>
> +            gtk_widget_set_can_focus(file_<wbr>entry, FALSE);<br>
> +        }<br>
> +    }<br>
> +    gtk_grid_attach(GTK_GRID(grid)<wbr>,<br>
> +            file_entry,<br>
> +            0, nrow, // left top<br>
> +            6, 1); // width height<br>
> +<br>
> +    /* choose button */<br>
> +    choose_button = gtk_button_new_with_mnemonic("<wbr>_Choose File");<br>
> +    gtk_widget_set_hexpand(choose_<wbr>button, FALSE);<br>
> +    g_signal_connect(GTK_BUTTON(<wbr>choose_button),<br>
> +                     "clicked", G_CALLBACK(usb_cd_choose_file)<wbr>, file_entry);<br>
> +    if (lun_info && lun_info->loaded) {<br>
> +        gtk_widget_set_sensitive(<wbr>choose_button, FALSE);<br>
> +        gtk_widget_set_can_focus(<wbr>choose_button, FALSE);<br>
> +    }<br>
> +<br>
> +    gtk_grid_attach(GTK_GRID(grid)<wbr>,<br>
> +            choose_button,<br>
> +            6, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    /* advanced button */<br>
> +    advanced_button = gtk_button_new_with_label("<wbr>Advanced");<br>
> +    gtk_button_set_relief(GTK_<wbr>BUTTON(advanced_button), GTK_RELIEF_NONE);<br>
> +    advanced_icon = gtk_image_new_from_icon_name("<wbr>preferences-system", GTK_ICON_SIZE_BUTTON);<br>
> +    gtk_button_set_image(GTK_<wbr>BUTTON(advanced_button), advanced_icon);<br>
> +    gtk_button_set_always_show_<wbr>image(GTK_BUTTON(advanced_<wbr>button), TRUE);<br>
> +    g_signal_connect(advanced_<wbr>button, "clicked", G_CALLBACK(lun_properties_<wbr>dialog_toggle_advanced), lun_dialog);<br>
> +<br>
> +    gtk_grid_attach(GTK_GRID(grid)<wbr>,<br>
> +            advanced_button,<br>
> +            0, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    /* advanced grid */<br>
> +    advanced_grid = gtk_grid_new();<br>
> +    gtk_grid_set_row_spacing(GTK_<wbr>GRID(advanced_grid), 12);<br>
> +    gtk_grid_set_column_<wbr>homogeneous(GTK_GRID(advanced_<wbr>grid), FALSE);<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(content_area), advanced_grid);<br>
> +<br>
> +    /* horizontal separator */<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(content_area), gtk_separator_new(GTK_<wbr>ORIENTATION_HORIZONTAL));<br>
> +<br>
> +    /* pack advanced grid */<br>
> +    nrow = 0;<br>
> +<br>
> +    /* horizontal separator */<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            gtk_separator_new(GTK_<wbr>ORIENTATION_HORIZONTAL),<br>
> +            0, nrow++, // left top<br>
> +            7, 1); // width height<br>
> +<br>
> +    /* product id labels */<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            gtk_label_new("Vendor"),<br>
> +            0, nrow, // left top<br>
> +            2, 1); // width height<br>
> +<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            gtk_label_new("Product"),<br>
> +            2, nrow, // left top<br>
> +            4, 1); // width height<br>
> +<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            gtk_label_new("Revision"),<br>
> +            6, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    /* vendor entry */<br>
> +    vendor_entry = gtk_entry_new();<br>
> +    gtk_entry_set_max_length(GTK_<wbr>ENTRY(vendor_entry), 8);<br>
> +    if (lun_info) {<br>
> +        gtk_widget_set_sensitive(<wbr>vendor_entry, FALSE);<br>
> +        gtk_widget_set_can_focus(<wbr>vendor_entry, FALSE);<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>vendor_entry), lun_info->vendor);<br>
> +    } else {<br>
> +        gtk_entry_set_placeholder_<wbr>text(GTK_ENTRY(vendor_entry), "auto");<br>
> +    }<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            vendor_entry,<br>
> +            0, nrow, // left top<br>
> +            2, 1); // width height<br>
> +<br>
> +    /* product entry */<br>
> +    product_entry = gtk_entry_new();<br>
> +    gtk_entry_set_max_length(GTK_<wbr>ENTRY(product_entry), 16);<br>
> +    if (lun_info) {<br>
> +        gtk_widget_set_sensitive(<wbr>product_entry, FALSE);<br>
> +        gtk_widget_set_can_focus(<wbr>product_entry, FALSE);<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>product_entry), lun_info->product);<br>
> +    } else {<br>
> +        gtk_entry_set_placeholder_<wbr>text(GTK_ENTRY(product_entry), "auto");<br>
> +    }<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            product_entry,<br>
> +            2, nrow, // left top<br>
> +            4, 1); // width height<br>
> +<br>
> +    /* revision entry */<br>
> +    revision_entry = gtk_entry_new();<br>
> +    gtk_entry_set_max_length(GTK_<wbr>ENTRY(revision_entry), 4);<br>
> +    if (lun_info) {<br>
> +        gtk_widget_set_sensitive(<wbr>revision_entry, FALSE);<br>
> +        gtk_widget_set_can_focus(<wbr>revision_entry, FALSE);<br>
> +        gtk_entry_set_text(GTK_ENTRY(<wbr>revision_entry), lun_info->revision);<br>
> +    } else {<br>
> +        gtk_entry_set_placeholder_<wbr>text(GTK_ENTRY(revision_entry)<wbr>, "auto");<br>
> +    }<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            revision_entry,<br>
> +            6, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    /* horizontal separator */<br>
> +    if (!lun_info) {<br>
> +        gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +                gtk_separator_new(GTK_<wbr>ORIENTATION_HORIZONTAL),<br>
> +                0, nrow++, // left top<br>
> +                7, 1); // width height<br>
> +    }<br>
> +<br>
> +    /* initially loaded switch */<br>
> +    loaded_label = gtk_label_new("Initially loaded:");<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +        loaded_label,<br>
> +        0, nrow, // left top<br>
> +        2, 1); // width height<br>
> +<br>
> +    loaded_switch = gtk_switch_new();<br>
> +    gtk_switch_set_state(GTK_<wbr>SWITCH(loaded_switch), TRUE);<br>
> +    if (lun_info) {<br>
> +        gtk_widget_set_child_visible(<wbr>loaded_switch, FALSE);<br>
> +        gtk_widget_set_child_visible(<wbr>loaded_label, FALSE);<br>
> +    } else {<br>
> +        g_signal_connect(loaded_<wbr>switch, "state-set",<br>
> +                         G_CALLBACK(lun_properties_<wbr>dialog_loaded_switch_cb), lun_dialog);<br>
> +    }<br>
> +    gtk_widget_set_halign(loaded_<wbr>switch, GTK_ALIGN_START);<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            loaded_switch,<br>
> +            2, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    /* initially locked switch */<br>
> +    locked_label = gtk_label_new("Initially locked:");<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +        locked_label,<br>
> +        0, nrow, // left top<br>
> +        2, 1); // width height<br>
> +<br>
> +    locked_switch = gtk_switch_new();<br>
> +    gtk_switch_set_state(GTK_<wbr>SWITCH(locked_switch), FALSE);<br>
> +    gtk_widget_set_hexpand(locked_<wbr>switch, FALSE);<br>
> +    if (lun_info) {<br>
> +        gtk_widget_set_child_visible(<wbr>locked_switch, FALSE);<br>
> +        gtk_widget_set_child_visible(<wbr>locked_label, FALSE);<br>
> +    }<br>
> +    gtk_widget_set_halign(locked_<wbr>switch, GTK_ALIGN_START);<br>
> +    gtk_grid_attach(GTK_GRID(<wbr>advanced_grid),<br>
> +            locked_switch,<br>
> +            2, nrow++, // left top<br>
> +            1, 1); // width height<br>
> +<br>
> +    lun_dialog->dialog = dialog;<br>
> +    lun_dialog->advanced_grid = advanced_grid;<br>
> +    lun_dialog->advanced_shown = FALSE;<br>
> +    lun_dialog->file_entry = file_entry;<br>
> +    lun_dialog->vendor_entry = vendor_entry;<br>
> +    lun_dialog->product_entry = product_entry;<br>
> +    lun_dialog->revision_entry = revision_entry;<br>
> +    lun_dialog->loaded_switch = loaded_switch;<br>
> +    lun_dialog->locked_switch = locked_switch;<br>
> +<br>
> +    gtk_widget_show_all(dialog);<br>
> +    gtk_widget_hide(advanced_grid)<wbr>;<br>
> +}<br>
> +<br>
> +static void lun_properties_dialog_get_<wbr>info(lun_properties_dialog *lun_dialog,<br>
> +                                            SpiceUsbDeviceLunInfo *lun_info)<br>
> +{<br>
> +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(<wbr>lun_dialog->file_entry));<br>
> +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(<wbr>lun_dialog->vendor_entry));<br>
> +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(<wbr>lun_dialog->product_entry));<br>
> +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(<wbr>lun_dialog->revision_entry));<br>
> +    lun_info->loaded = gtk_switch_get_active(GTK_<wbr>SWITCH(lun_dialog->loaded_<wbr>switch));<br>
> +    lun_info->locked = gtk_switch_get_active(GTK_<wbr>SWITCH(lun_dialog->locked_<wbr>switch));<br>
> +}<br>
> +<br>
> +/* Popup menu */<br>
> +static void view_popup_menu_on_eject(<wbr>GtkWidget *menuitem, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(<wbr>priv->cd_tree.tree_view);<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +<br>
> +    if (gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        if (!tree_item_is_lun(priv->cd_<wbr>tree.tree_store, &iter)) {<br>
> +            SpiceUsbDevice *usb_device;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);<br>
> +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);<br>
> +        }<br>
> +        else {<br>
> +            UsbWidgetLunItem *lun_item;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);<br>
> +            spice_usb_device_manager_<wbr>device_lun_load(<br>
> +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded);<br>
> +        }<br>
> +    }<br>
> +    else {<br>
> +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);<br>
> +    }<br>
> +}<br>
> +<br>
> +static void view_popup_menu_on_lock(<wbr>GtkWidget *menuitem, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(<wbr>priv->cd_tree.tree_view);<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +<br>
> +    if (gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        if (!tree_item_is_lun(priv->cd_<wbr>tree.tree_store, &iter)) {<br>
> +            SpiceUsbDevice *usb_device;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);<br>
> +            SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__);<br>
> +        }<br>
> +        else {<br>
> +            UsbWidgetLunItem *lun_item;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);<br>
> +            spice_usb_device_manager_<wbr>device_lun_lock(<br>
> +                lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked);<br>
> +        }<br>
> +    }<br>
> +    else {<br>
> +        SPICE_DEBUG("%s - failed to get selection", __FUNCTION__);<br>
> +    }<br>
> +}<br>
> +<br>
> +static void view_popup_menu_on_remove(<wbr>GtkWidget *menuitem, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(<wbr>priv->cd_tree.tree_view);<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +<br>
> +    if (gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        if (!tree_item_is_lun(priv->cd_<wbr>tree.tree_store, &iter)) {<br>
> +            SpiceUsbDevice *usb_device;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1);<br>
> +            SPICE_DEBUG("Remove USB device");<br>
> +        } else {<br>
> +            UsbWidgetLunItem *lun_item;<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);<br>
> +            gtk_tree_selection_unselect_<wbr>all(select);<br>
> +            spice_usb_device_manager_<wbr>device_lun_remove(lun_item-><wbr>manager, lun_item->device, lun_item->lun);<br>
> +        }<br>
> +    } else {<br>
> +        SPICE_DEBUG("Remove - failed to get selection");<br>
> +    }<br>
> +}<br>
> +<br>
> +static void view_popup_menu_on_settings(<wbr>GtkWidget *menuitem, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeSelection *select = gtk_tree_view_get_selection(<wbr>priv->cd_tree.tree_view);<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +<br>
> +    if (gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        if (!tree_item_is_lun(priv->cd_<wbr>tree.tree_store, &iter)) {<br>
> +            SPICE_DEBUG("No settings for USB device yet");<br>
> +        } else {<br>
> +            lun_properties_dialog lun_dialog;<br>
> +            UsbWidgetLunItem *lun_item;<br>
> +            gint resp;<br>
> +<br>
> +            gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1);<br>
> +            gtk_tree_selection_unselect_<wbr>all(select);<br>
> +            create_lun_properties_dialog(<wbr>self, NULL, &lun_item->info, &lun_dialog);<br>
> +<br>
> +            resp = gtk_dialog_run(GTK_DIALOG(lun_<wbr>dialog.dialog));<br>
> +            if (resp == GTK_RESPONSE_ACCEPT) {<br>
> +                SpiceUsbDeviceLunInfo lun_info;<br>
> +                SPICE_DEBUG("response is ACCEPT");<br>
> +                lun_properties_dialog_get_<wbr>info(&lun_dialog, &lun_info);<br>
> +                spice_usb_device_manager_<wbr>device_lun_change_media(<br>
> +                    priv->manager, lun_item->device, lun_item->lun, &lun_info);<br>
> +            } else {<br>
> +                SPICE_DEBUG("response is REJECT");<br>
> +            }<br>
> +            gtk_widget_destroy(lun_dialog.<wbr>dialog);<br>
> +        }<br>
> +    } else {<br>
> +        SPICE_DEBUG("Remove - failed to get selection");<br>
> +    }<br>
> +}<br>
> +<br>
> +static GtkWidget *view_popup_add_menu_item(<wbr>GtkWidget *menu,<br>
> +    const gchar *label_str,<br>
> +    const gchar *icon_name,<br>
> +    GCallback cb_func, gpointer user_data)<br>
> +{<br>
> +    GtkWidget *menu_item = gtk_menu_item_new();<br>
> +    create_image_button_box(label_<wbr>str, icon_name, menu_item);<br>
> +    g_signal_connect(menu_item, "activate", cb_func, user_data);<br>
> +<br>
> +    gtk_widget_show_all(menu_item)<wbr>;<br>
> +    gtk_menu_shell_append(GTK_<wbr>MENU_SHELL(menu), menu_item);<br>
> +<br>
> +    return menu_item;<br>
> +}<br>
> +<br>
> +static gboolean has_single_lun(<wbr>SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device)<br>
> +{<br>
> +    gboolean result;<br>
> +    SpiceUsbDeviceManager *usb_dev_mgr = priv->manager;<br>
> +    GArray *lun_array;<br>
> +    lun_array = spice_usb_device_manager_get_<wbr>device_luns(usb_dev_mgr, device);<br>
> +    result = lun_array && lun_array->len <= 1;<br>
> +    if (lun_array) {<br>
> +        g_array_unref(lun_array);<br>
> +    }<br>
> +    return result;<br>
> +}<br>
> +<br>
> +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeSelection *select;<br>
> +    GtkTreeModel *tree_model;<br>
> +    GtkTreeIter iter;<br>
> +    UsbWidgetLunItem *lun_item;<br>
> +    gboolean is_loaded, is_locked;<br>
> +    GtkTreeIter *usb_dev_iter;<br>
> +    gboolean is_dev_connected;<br>
> +    GtkWidget *menu;<br>
> +<br>
> +    if (tree_view != priv->cd_tree.tree_view) {<br>
> +        SPICE_DEBUG("Not applicable for USB device");<br>
> +        return;<br>
> +    }<br>
> +<br>
> +    select = gtk_tree_view_get_selection(<wbr>tree_view);<br>
> +<br>
> +    if (!gtk_tree_selection_get_<wbr>selected(select, &tree_model, &iter)) {<br>
> +        SPICE_DEBUG("No tree view row is selected");<br>
> +        return;<br>
> +    }<br>
> +    if (!tree_item_is_lun(priv->cd_<wbr>tree.tree_store, &iter)) {<br>
> +        SPICE_DEBUG("No settings for USB device yet");<br>
> +        return;<br>
> +    }<br>
> +<br>
> +    gtk_tree_model_get(tree_model, &iter,<br>
> +                       COL_ITEM_DATA, (gpointer *)&lun_item,<br>
> +                       COL_LOADED, &is_loaded,<br>
> +                       COL_LOCKED, &is_locked,<br>
> +                       -1);<br>
> +<br>
> +    usb_dev_iter = usb_widget_tree_store_find_<wbr>usb_device(priv->cd_tree.tree_<wbr>store, lun_item->device);<br>
> +    if (usb_dev_iter != NULL) {<br>
> +        gtk_tree_model_get(tree_model, usb_dev_iter,<br>
> +                           COL_CONNECTED, &is_dev_connected,<br>
> +                           -1);<br>
> +        g_free(usb_dev_iter);<br>
> +    } else {<br>
> +        is_dev_connected = FALSE;<br>
> +        SPICE_DEBUG("Failed to find USB device for LUN: %s|%s",<br>
> +                    lun_item->info.vendor, lun_item->info.product);<br>
> +    }<br>
> +    SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d",<br>
> +                lun_item->info.vendor, lun_item->info.product,<br>
> +                is_dev_connected, is_loaded, is_locked);<br>
> +<br>
> +    /* Set up the menu */<br>
> +    menu = gtk_menu_new();<br>
> +<br>
> +    view_popup_add_menu_item(menu, "_Settings", "preferences-system",<br>
> +                             G_CALLBACK(view_popup_menu_on_<wbr>settings), user_data);<br>
> +    if (is_loaded) {<br>
> +        if (!is_locked) {<br>
> +            view_popup_add_menu_item(menu, "_Lock", "system-lock-screen",<br>
> +                                    G_CALLBACK(view_popup_menu_on_<wbr>lock), user_data);<br>
> +            view_popup_add_menu_item(menu, "_Eject", "media-eject",<br>
> +                                     G_CALLBACK(view_popup_menu_on_<wbr>eject), user_data);<br>
> +        } else {<br>
> +            view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen",<br>
> +                                     G_CALLBACK(view_popup_menu_on_<wbr>lock), user_data);<br>
> +        }<br>
> +    } else {<br>
> +        view_popup_add_menu_item(menu, "_Load", "media-eject",<br>
> +                                 G_CALLBACK(view_popup_menu_on_<wbr>eject), user_data);<br>
> +    }<br>
> +<br>
> +    if (!is_dev_connected || has_single_lun(priv, lun_item->device)) {<br>
> +        view_popup_add_menu_item(menu, "_Remove", "edit-delete",<br>
> +                                 G_CALLBACK(view_popup_menu_on_<wbr>remove), user_data);<br>
> +    }<br>
> +<br>
> +    gtk_widget_show_all(menu);<br>
> +    gtk_menu_popup_at_pointer(GTK_<wbr>MENU(menu), NULL);<br>
> +}<br>
> +<br>
> +static void treeview_select_current_row_<wbr>by_pos(GtkTreeView *tree_view, gint x, gint y)<br>
> +{<br>
> +    GtkTreeSelection *selection = gtk_tree_view_get_selection(<wbr>GTK_TREE_VIEW(tree_view));<br>
> +    if (gtk_tree_selection_count_<wbr>selected_rows(selection) <= 1) {<br>
> +        GtkTreePath *path;<br>
> +        /* Get tree path for row that was clicked */<br>
> +        if (gtk_tree_view_get_path_at_<wbr>pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))<br>
> +        {<br>
> +            gtk_tree_selection_unselect_<wbr>all(selection);<br>
> +            gtk_tree_selection_select_<wbr>path(selection, path);<br>
> +            gtk_tree_path_free(path);<br>
> +        }<br>
> +    }<br>
> +}<br>
> +<br>
> +static gboolean treeview_on_right_button_<wbr>pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data)<br>
> +{<br>
> +    GtkTreeView *tree_view = GTK_TREE_VIEW(view);<br>
> +    /* single click with the right mouse button */<br>
> +    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {<br>
> +        /* select the row that was clicked, it will also provide the context */<br>
> +        treeview_select_current_row_<wbr>by_pos(tree_view, (gint)event->x, (gint)event->y);<br>
> +        view_popup_menu(tree_view, event, user_data);<br>
> +        return TRUE; /* we handled this */<br>
> +    } else {<br>
> +        return FALSE; /* we did not handle this */<br>
> +    }<br>
> +}<br>
> +<br>
> +static gboolean treeview_on_popup_key_pressed_<wbr>cb(GtkWidget *view, gpointer user_data)<br>
> +{<br>
> +    view_popup_menu(GTK_TREE_VIEW(<wbr>view), NULL, user_data);<br>
> +    return TRUE; /* we handled this */<br>
> +}<br>
> +<br>
> +/* Add LUN dialog */<br>
> +<br>
> +static void add_cd_lun_button_clicked_cb(<wbr>GtkWidget *add_cd_button, gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_<wbr>cd_button);<br>
> +    lun_properties_dialog lun_dialog;<br>
> +    gint resp;<br>
> +<br>
> +    create_lun_properties_dialog(<wbr>self, parent_window, NULL, &lun_dialog);<br>
> +<br>
> +    resp = gtk_dialog_run(GTK_DIALOG(lun_<wbr>dialog.dialog));<br>
> +    if (resp == GTK_RESPONSE_ACCEPT) {<br>
> +        SpiceUsbDeviceLunInfo lun_info;<br>
> +        SPICE_DEBUG("response is ACCEPT");<br>
> +        lun_properties_dialog_get_<wbr>info(&lun_dialog, &lun_info);<br>
> +        spice_usb_device_manager_add_<wbr>cd_lun(priv->manager, &lun_info);<br>
> +    } else {<br>
> +        SPICE_DEBUG("response is REJECT");<br>
> +    }<br>
> +    gtk_widget_destroy(lun_dialog.<wbr>dialog);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_get_<wbr>property(GObject     *gobject,<br>
> +                                                 guint        prop_id,<br>
> +                                                 GValue      *value,<br>
> +                                                 GParamSpec  *pspec)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(<wbr>gobject);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    switch (prop_id) {<br>
> +    case PROP_SESSION:<br>
> +        g_value_set_object(value, priv->session);<br>
> +        break;<br>
> +    case PROP_DEVICE_FORMAT_STRING:<br>
> +        g_value_set_string(value, priv->device_format_string);<br>
> +        break;<br>
> +    default:<br>
> +        G_OBJECT_WARN_INVALID_<wbr>PROPERTY_ID(gobject, prop_id, pspec);<br>
> +        break;<br>
> +    }<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_set_<wbr>property(GObject       *gobject,<br>
> +                                                 guint          prop_id,<br>
> +                                                 const GValue  *value,<br>
> +                                                 GParamSpec    *pspec)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(<wbr>gobject);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    switch (prop_id) {<br>
> +    case PROP_SESSION:<br>
> +        priv->session = g_value_dup_object(value);<br>
> +        break;<br>
> +    case PROP_DEVICE_FORMAT_STRING:<br>
> +        priv->device_format_string = g_value_dup_string(value);<br>
> +        break;<br>
> +    default:<br>
> +        G_OBJECT_WARN_INVALID_<wbr>PROPERTY_ID(gobject, prop_id, pspec);<br>
> +        break;<br>
> +    }<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_hide_<wbr>info_bar(SpiceUsbDeviceWidget *self)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    if (priv->info_bar) {<br>
> +        gtk_widget_destroy(priv->info_<wbr>bar);<br>
> +        priv->info_bar = NULL;<br>
> +    }<br>
> +}<br>
> +<br>
> +static void<br>
> +spice_usb_device_widget_show_<wbr>info_bar(SpiceUsbDeviceWidget *self,<br>
> +                                      const gchar          *message,<br>
> +                                      GtkMessageType        message_type,<br>
> +                                      GdkPixbuf            *icon_pixbuf)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkWidget *info_bar, *content_area, *hbox, *icon, *label;<br>
> +<br>
> +    spice_usb_device_widget_hide_<wbr>info_bar(self);<br>
> +<br>
> +    info_bar = gtk_info_bar_new();<br>
> +    gtk_info_bar_set_message_type(<wbr>GTK_INFO_BAR(info_bar), message_type);<br>
> +<br>
> +    content_area = gtk_info_bar_get_content_area(<wbr>GTK_INFO_BAR(info_bar));<br>
> +    hbox = gtk_box_new(GTK_ORIENTATION_<wbr>HORIZONTAL, 0);<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(content_area), hbox);<br>
> +<br>
> +    icon = gtk_image_new_from_pixbuf(<wbr>icon_pixbuf);<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>hbox), icon, FALSE, FALSE, 10);<br>
> +<br>
> +    label = gtk_label_new(message);<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>hbox), label, TRUE, TRUE, 0);<br>
> +<br>
> +    priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0);<br>
> +    gtk_alignment_set_padding(GTK_<wbr>ALIGNMENT(priv->info_bar), 0, 0, 0, 0);<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(priv->info_bar), info_bar);<br>
> +<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>self), priv->info_bar, FALSE, FALSE, 0);<br>
> +    gtk_box_reorder_child(GTK_BOX(<wbr>self), priv->info_bar, 1); /* put after the lable */<br>
> +    gtk_widget_show_all(priv-><wbr>info_bar);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_<wbr>create_tree_view(<wbr>SpiceUsbDeviceWidget *self, gboolean is_cd)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    GtkTreeStore *tree_store = usb_widget_create_tree_store()<wbr>;<br>
> +    GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_<wbr>new());<br>
> +<br>
> +    if (is_cd) {<br>
> +        priv->cd_tree.tree_view = tree_view;<br>
> +        priv->cd_tree.tree_store = tree_store;<br>
> +    } else {<br>
> +        priv->usb_tree.tree_view = tree_view;<br>
> +        priv->usb_tree.tree_store = tree_store;<br>
> +    }<br>
> +<br>
> +    gtk_tree_view_set_model(tree_<wbr>view, GTK_TREE_MODEL(tree_store));<br>
> +    g_object_unref(tree_store); /* destroy tree_store automatically with tree_view */<br>
> +<br>
> +    view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM, COL_CAN_REDIRECT,<br>
> +        is_cd ? tree_item_toggled_cb_redirect_<wbr>cd : tree_item_toggled_cb_redirect_<wbr>usb, is_cd);<br>
> +<br>
> +    view_add_text_column(self, COL_ADDRESS, FALSE, is_cd);<br>
> +<br>
> +    view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd);<br>
> +    if (is_cd) {<br>
> +        view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd);<br>
> +    }<br>
> +<br>
> +    view_add_text_column(self, COL_VENDOR, TRUE, is_cd);<br>
> +    view_add_text_column(self, COL_PRODUCT, TRUE, is_cd);<br>
> +<br>
> +    if (is_cd) {<br>
> +        view_add_text_column(self, COL_FILE, TRUE, is_cd);<br>
> +        view_add_read_only_toggle_<wbr>column(self, COL_LOADED, COL_LUN_ITEM, is_cd);<br>
> +        view_add_read_only_toggle_<wbr>column(self, COL_LOCKED, COL_LUN_ITEM, is_cd);<br>
> +        // uncomment to show also 'idle' column for CD<br>
> +        //view_add_read_only_toggle_<wbr>column(self, COL_IDLE, COL_LUN_ITEM, is_cd);<br>
> +    }<br>
> +<br>
> +    gtk_tree_selection_set_mode(<br>
> +            gtk_tree_view_get_selection(<wbr>tree_view),<br>
> +            GTK_SELECTION_NONE);<br>
> +<br>
> +    if (is_cd) {<br>
> +        set_selection_handler(tree_<wbr>view);<br>
> +    }<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_<wbr>signals_connect(<wbr>SpiceUsbDeviceWidget *self)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    g_signal_connect(priv-><wbr>manager, "device-added",<br>
> +                     G_CALLBACK(device_added_cb), self);<br>
> +    g_signal_connect(priv-><wbr>manager, "device-removed",<br>
> +                     G_CALLBACK(device_removed_cb), self);<br>
> +    g_signal_connect(priv-><wbr>manager, "device-changed",<br>
> +                     G_CALLBACK(device_changed_cb), self);<br>
> +    // TODO: connect failed<br>
> +    g_signal_connect(priv-><wbr>manager, "device-error",<br>
> +                     G_CALLBACK(device_error_cb), self);<br>
> +<br>
> +    g_signal_connect(priv->cd_<wbr>tree.tree_view, "button-press-event",<br>
> +                     G_CALLBACK(treeview_on_right_<wbr>button_pressed_cb), self);<br>
> +    g_signal_connect(priv->cd_<wbr>tree.tree_view, "popup-menu",<br>
> +                     G_CALLBACK(treeview_on_popup_<wbr>key_pressed_cb), self);<br>
> +}<br>
> +<br>
> +static void create_tree_window(<wbr>SpiceUsbDeviceWidget *self, GtkTreeView *tree_view)<br>
> +{<br>
> +    GtkWidget *sw;<br>
> +    /* scrolled window */<br>
> +    sw = gtk_scrolled_window_new(NULL, NULL);<br>
> +    gtk_scrolled_window_set_<wbr>shadow_type(GTK_SCROLLED_<wbr>WINDOW(sw),<br>
> +        GTK_SHADOW_ETCHED_IN);<br>
> +    gtk_scrolled_window_set_<wbr>policy(GTK_SCROLLED_WINDOW(sw)<wbr>,<br>
> +        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);<br>
> +    gtk_widget_set_hexpand(sw, TRUE);<br>
> +    gtk_widget_set_halign(sw, GTK_ALIGN_FILL);<br>
> +    gtk_widget_set_vexpand(sw, TRUE);<br>
> +    gtk_widget_set_valign(sw, GTK_ALIGN_FILL);<br>
> +    gtk_container_add(GTK_<wbr>CONTAINER(sw), GTK_WIDGET(tree_view));<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>self), sw, TRUE, TRUE, 0);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_<wbr>constructed(GObject *gobject)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self;<br>
> +    GtkRequisition min_size, natural_size;<br>
> +    SpiceUsbDeviceWidgetPrivate *priv;<br>
> +    GtkWidget *hbox, *dev_label;<br>
> +    GtkWidget *add_cd_button, *add_cd_icon;<br>
> +    GPtrArray *devices = NULL;<br>
> +    GError *err = NULL;<br>
> +    gchar *str;<br>
> +    guint i;<br>
> +    gboolean cd_sharing_enabled = TRUE;<br>
> +    #ifndef USE_CD_SHARING<br>
> +    cd_sharing_enabled = FALSE;<br>
> +    #endif<br>
> +<br>
> +    self = SPICE_USB_DEVICE_WIDGET(<wbr>gobject);<br>
> +    priv = self->priv;<br>
> +    if (!priv->session)<br>
> +        g_error("SpiceUsbDeviceWidget constructed without a session");<br>
> +<br>
> +    min_size.width = 600;<br>
> +    min_size.height = 300;<br>
> +    natural_size.width = 1200;<br>
> +    natural_size.height = 600;<br>
> +    gtk_widget_get_preferred_size(<wbr>GTK_WIDGET(self), &min_size, &natural_size);<br>
> +<br>
> +    priv->label = gtk_label_new(NULL);<br>
> +    str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));<br>
> +    gtk_label_set_markup(GTK_<wbr>LABEL(priv->label), str);<br>
> +    g_free(str);<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>self), priv->label, FALSE, FALSE, 0);<br>
> +<br>
> +    priv->icon_cd = get_named_icon("media-optical"<wbr>, GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
> +    priv->icon_connected = get_named_icon("network-<wbr>transmit-receive", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
> +    priv->icon_disconn = get_named_icon("network-<wbr>offline", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
> +    priv->icon_warning = get_named_icon("dialog-<wbr>warning", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
> +    priv->icon_info = get_named_icon("dialog-<wbr>information", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
> +<br>
> +    priv->manager = spice_usb_device_manager_get(<wbr>priv->session, &err);<br>
> +    if (err) {<br>
> +        spice_usb_device_widget_show_<wbr>info_bar(self, err->message,<br>
> +                                              GTK_MESSAGE_WARNING, priv->icon_warning);<br>
> +        g_clear_error(&err);<br>
> +        return;<br>
> +    }<br>
> +<br>
> +    if (cd_sharing_enabled) {<br>
> +        spice_usb_device_widget_<wbr>create_tree_view(self, TRUE);<br>
> +    } else {<br>
> +        priv->cd_tree.tree_store = NULL;<br>
> +        priv->cd_tree.tree_view = NULL;<br>
> +    }<br>
> +    spice_usb_device_widget_<wbr>create_tree_view(self, FALSE);<br>
> +<br>
> +    spice_usb_device_widget_<wbr>signals_connect(self);<br>
> +<br>
> +    hbox = gtk_box_new(GTK_ORIENTATION_<wbr>HORIZONTAL, 0);<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>self), hbox, FALSE, FALSE, 0);<br>
> +<br>
> +    /* "Available devices" label - in hbox */<br>
> +    dev_label = gtk_label_new(_("Local USB devices"));<br>
> +    gtk_box_pack_start(GTK_BOX(<wbr>hbox), dev_label, TRUE, FALSE, 0);<br>
> +    create_tree_window(self, priv->usb_tree.tree_view);<br>
> +<br>
> +    if (cd_sharing_enabled) {<br>
> +        hbox = gtk_box_new(GTK_ORIENTATION_<wbr>HORIZONTAL, 0);<br>
> +        gtk_box_pack_start(GTK_BOX(<wbr>self), hbox, FALSE, FALSE, 0);<br>
> +        dev_label = gtk_label_new(_("Shared CD devices"));<br>
> +        gtk_box_pack_start(GTK_BOX(<wbr>hbox), dev_label, TRUE, FALSE, 0);<br>
> +        /* "Add CD" button - in hbox */<br>
> +        add_cd_button = gtk_button_new_with_label(_("<wbr>Add CD"));<br>
> +        gtk_button_set_always_show_<wbr>image(GTK_BUTTON(add_cd_<wbr>button), TRUE);<br>
> +        add_cd_icon = gtk_image_new_from_icon_name("<wbr>list-add", GTK_ICON_SIZE_BUTTON);<br>
> +        gtk_button_set_image(GTK_<wbr>BUTTON(add_cd_button), add_cd_icon);<br>
> +<br>
> +        gtk_widget_set_halign(add_cd_<wbr>button, GTK_ALIGN_END);<br>
> +        g_signal_connect(add_cd_<wbr>button, "clicked", G_CALLBACK(add_cd_lun_button_<wbr>clicked_cb), self);<br>
> +        gtk_box_pack_start(GTK_BOX(<wbr>hbox), add_cd_button, FALSE, FALSE, 0);<br>
> +        create_tree_window(self, priv->cd_tree.tree_view);<br>
> +    }<br>
> +    devices = spice_usb_device_manager_get_<wbr>devices(priv->manager);<br>
> +    if (!devices)<br>
> +        goto end;<br>
> +<br>
> +    for (i = 0; i < devices->len; i++) {<br>
> +        SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i);<br>
> +        usb_widget_add_device(self, usb_device, NULL);<br>
> +    }<br>
> +    g_ptr_array_unref(devices);<br>
> +<br>
> +    select_widget_size(GTK_WIDGET(<wbr>self));<br>
> +<br>
> +end:<br>
> +    spice_usb_device_widget_<wbr>update_status(self);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_<wbr>finalize(GObject *object)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(<wbr>object);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    if (priv->manager) {<br>
> +        g_signal_handlers_disconnect_<wbr>by_func(priv->manager,<br>
> +                                             device_added_cb, self);<br>
> +        g_signal_handlers_disconnect_<wbr>by_func(priv->manager,<br>
> +                                             device_removed_cb, self);<br>
> +        g_signal_handlers_disconnect_<wbr>by_func(priv->manager,<br>
> +                                             device_changed_cb, self);<br>
> +        g_signal_handlers_disconnect_<wbr>by_func(priv->manager,<br>
> +                                             device_error_cb, self);<br>
> +    }<br>
> +    g_object_unref(priv->session);<br>
> +    g_free(priv->device_format_<wbr>string);<br>
> +<br>
> +    if (G_OBJECT_CLASS(spice_usb_<wbr>device_widget_parent_class)-><wbr>finalize)<br>
> +        G_OBJECT_CLASS(spice_usb_<wbr>device_widget_parent_class)-><wbr>finalize(object);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_class_<wbr>init(<br>
> +    SpiceUsbDeviceWidgetClass *klass)<br>
> +{<br>
> +    GObjectClass *gobject_class = (GObjectClass *)klass;<br>
> +    GParamSpec *pspec;<br>
> +<br>
> +    g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate))<wbr>;<br>
> +<br>
> +    gobject_class->constructed  = spice_usb_device_widget_<wbr>constructed;<br>
> +    gobject_class->finalize     = spice_usb_device_widget_<wbr>finalize;<br>
> +    gobject_class->get_property = spice_usb_device_widget_get_<wbr>property;<br>
> +    gobject_class->set_property = spice_usb_device_widget_set_<wbr>property;<br>
> +<br>
> +    /**<br>
> +     * SpiceUsbDeviceWidget:session:<br>
> +     *<br>
> +     * #SpiceSession this #SpiceUsbDeviceWidget is associated with<br>
> +     *<br>
> +     **/<br>
> +    pspec = g_param_spec_object("session",<br>
> +                                "Session",<br>
> +                                "SpiceSession",<br>
> +                                SPICE_TYPE_SESSION,<br>
> +                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |<br>
> +                                G_PARAM_STATIC_STRINGS);<br>
> +    g_object_class_install_<wbr>property(gobject_class, PROP_SESSION, pspec);<br>
> +<br>
> +    /**<br>
> +     * SpiceUsbDeviceWidget:device-<wbr>format-string:<br>
> +     *<br>
> +     * Format string to pass to spice_usb_device_get_<wbr>description() for getting<br>
> +     * the device USB descriptions.<br>
> +     */<br>
> +    pspec = g_param_spec_string("device-<wbr>format-string",<br>
> +                                "Device format string",<br>
> +                                "Format string for device description",<br>
> +                                NULL,<br>
> +                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |<br>
> +                                G_PARAM_STATIC_STRINGS);<br>
> +    g_object_class_install_<wbr>property(gobject_class, PROP_DEVICE_FORMAT_STRING,<br>
> +                                    pspec);<br>
> +<br>
> +    /**<br>
> +     * SpiceUsbDeviceWidget::connect-<wbr>failed:<br>
> +     * @widget: The #SpiceUsbDeviceWidget that emitted the signal<br>
> +     * @device: #SpiceUsbDevice boxed object corresponding to the added device<br>
> +     * @error:  #GError describing the reason why the connect failed<br>
> +     *<br>
> +     * The #SpiceUsbDeviceWidget::<wbr>connect-failed signal is emitted whenever<br>
> +     * the user has requested for a device to be redirected and this has<br>
> +     * failed.<br>
> +     **/<br>
> +    signals[CONNECT_FAILED] =<br>
> +        g_signal_new("connect-failed",<br>
> +                    G_OBJECT_CLASS_TYPE(gobject_<wbr>class),<br>
> +                    G_SIGNAL_RUN_FIRST,<br>
> +                    G_STRUCT_OFFSET(<wbr>SpiceUsbDeviceWidgetClass, connect_failed),<br>
> +                    NULL, NULL,<br>
> +                    g_cclosure_user_marshal_VOID__<wbr>BOXED_BOXED,<br>
> +                    G_TYPE_NONE,<br>
> +                    2,<br>
> +                    SPICE_TYPE_USB_DEVICE,<br>
> +                    G_TYPE_ERROR);<br>
> +}<br>
> +<br>
> +static void spice_usb_device_widget_init(<wbr>SpiceUsbDeviceWidget *self)<br>
> +{<br>
> +    self->priv = SPICE_USB_DEVICE_WIDGET_GET_<wbr>PRIVATE(self);<br>
> +}<br>
> +<br>
> +/* ------------------------------<wbr>------------------------------<wbr>------ */<br>
> +/* public api                                                         */<br>
> +<br>
> +/**<br>
> + * spice_usb_device_widget_new:<br>
> + * @session: #SpiceSession for which to widget will control USB redirection<br>
> + * @device_format_string: (allow-none): String passed to<br>
> + * spice_usb_device_get_<wbr>description()<br>
> + *<br>
> + * Creates a new widget to control USB redirection.<br>
> + *<br>
> + * Returns: a new #SpiceUsbDeviceWidget instance<br>
> + */<br>
> +GtkWidget *spice_usb_device_widget_new(<wbr>SpiceSession    *session,<br>
> +                                       const gchar     *device_format_string)<br>
> +{<br>
> +    static gboolean init_columns = TRUE;<br>
> +    spice_util_get_debug();<br>
> +    if (init_columns) {<br>
> +        initialize_columns();<br>
> +        init_columns = FALSE;<br>
> +    }<br>
> +    return g_object_new(SPICE_TYPE_USB_<wbr>DEVICE_WIDGET,<br>
> +                        "orientation", GTK_ORIENTATION_VERTICAL,<br>
> +                        "session", session,<br>
> +                        "device-format-string", device_format_string,<br>
> +                        "spacing", 6,<br>
> +                        NULL);<br>
> +}<br>
> +<br>
> +/* ------------------------------<wbr>------------------------------<wbr>------ */<br>
> +/* callbacks                                                          */<br>
> +<br>
> +static gboolean usb_widget_tree_store_check_<wbr>redirect_foreach_cb(<wbr>GtkTreeModel *tree_model,<br>
> +                                                                GtkTreePath *path,<br>
> +                                                                GtkTreeIter *iter,<br>
> +                                                                SpiceUsbDeviceWidget *self,<br>
> +                                                                GtkTreeStore *tree_store)<br>
> +{<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +<br>
> +    if (!tree_item_is_lun(tree_store, iter)) {<br>
> +        SpiceUsbDevice *usb_device;<br>
> +        gboolean can_redirect;<br>
> +<br>
> +        gtk_tree_model_get(tree_model, iter,<br>
> +                           COL_ITEM_DATA, (gpointer *)&usb_device,<br>
> +                           -1);<br>
> +<br>
> +        if (spice_usb_device_manager_is_<wbr>redirecting(priv->manager)) {<br>
> +            can_redirect = FALSE;<br>
> +        } else {<br>
> +            GError *err = NULL;<br>
> +<br>
> +            can_redirect = spice_usb_device_manager_can_<wbr>redirect_device(priv->manager,<br>
> +                                                                        usb_device, &err);<br>
> +<br>
> +            /* If we cannot redirect this device, append the error message to<br>
> +               err_msg, but only if it is *not* already there! */<br>
> +            if (!can_redirect) {<br>
> +                if (priv->err_msg) {<br>
> +                    if (!strstr(priv->err_msg, err->message)) {<br>
> +                        gchar *old_err_msg = priv->err_msg;<br>
> +                        priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg,<br>
> +                                                        err->message);<br>
> +                        g_free(old_err_msg);<br>
> +                    }<br>
> +                } else {<br>
> +                    priv->err_msg = g_strdup(err->message);<br>
> +                }<br>
> +            }<br>
> +            g_clear_error(&err);<br>
> +        }<br>
> +        gtk_tree_store_set(tree_store, iter,<br>
> +                           COL_CAN_REDIRECT, can_redirect,<br>
> +                           -1);<br>
> +    }<br>
> +    return FALSE; /* continue iterating */<br>
> +}<br>
> +<br>
> +static gboolean usb_widget_tree_store_check_<wbr>redirect_foreach_cb_usb(<wbr>GtkTreeModel *tree_model,<br>
> +                                                                 GtkTreePath *path, GtkTreeIter *iter,<br>
> +                                                                 gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    return usb_widget_tree_store_check_<wbr>redirect_foreach_cb(<br>
> +        tree_model, path, iter, self, priv->usb_tree.tree_store);<br>
> +}<br>
> +<br>
> +#ifdef USE_CD_SHARING<br>
> +static gboolean usb_widget_tree_store_check_<wbr>redirect_foreach_cb_cd(<wbr>GtkTreeModel *tree_model,<br>
> +                                                                 GtkTreePath *path, GtkTreeIter *iter,<br>
> +                                                                 gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    return usb_widget_tree_store_check_<wbr>redirect_foreach_cb(<br>
> +        tree_model, path, iter, self, priv->cd_tree.tree_store);<br>
> +}<br>
> +#endif<br>
> +<br>
> +static gboolean spice_usb_device_widget_<wbr>update_status(gpointer user_data)<br>
> +{<br>
> +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_<wbr>data);<br>
> +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
> +    gchar *str, *markup_str;<br>
> +    const gchar *free_channels_str;<br>
> +    int free_channels;<br>
> +<br>
> +    g_object_get(priv->manager, "free-channels", &free_channels, NULL);<br>
> +    free_channels_str = ngettext(_("Select USB devices to redirect (%d free channel)"),<br>
> +                                 _("Select USB devices to redirect (%d free channels)"),<br>
> +                                 free_channels);<br>
> +    str = g_strdup_printf(free_channels_<wbr>str, free_channels);<br>
> +    markup_str = g_strdup_printf("<b>%s</b>", str);<br>
> +    gtk_label_set_markup(GTK_LABEL (priv->label), markup_str);<br>
> +    g_free(markup_str);<br>
> +    g_free(str);<br>
> +<br>
> +    gtk_tree_model_foreach(GTK_<wbr>TREE_MODEL(priv->usb_tree.<wbr>tree_store),<br>
> +                           usb_widget_tree_store_check_<wbr>redirect_foreach_cb_usb, self);<br>
> +    gtk_widget_show_all(GTK_<wbr>WIDGET(priv->usb_tree.tree_<wbr>view));<br>
> +<br>
> +#ifdef USE_CD_SHARING<br>
> +    gtk_tree_model_foreach(GTK_<wbr>TREE_MODEL(priv->cd_tree.tree_<wbr>store),<br>
> +                           usb_widget_tree_store_check_<wbr>redirect_foreach_cb_cd, self);<br>
> +    gtk_widget_show_all(GTK_<wbr>WIDGET(priv->cd_tree.tree_<wbr>view));<br>
> +#endif<br>
> +<br>
> +    /* Show messages in the info, if necessary */<br>
> +    if (priv->err_msg) {<br>
> +        spice_usb_device_widget_show_<wbr>info_bar(self, priv->err_msg,<br>
> +                                              GTK_MESSAGE_INFO, priv->icon_warning);<br>
> +        g_free(priv->err_msg);<br>
> +        priv->err_msg = NULL;<br>
> +    } else if ( spice_usb_device_manager_is_<wbr>redirecting(priv->manager)) {<br>
> +        spice_usb_device_widget_show_<wbr>info_bar(self, _("Redirecting USB Device..."),<br>
> +                                              GTK_MESSAGE_INFO, priv->icon_info);<br>
> +    } else {<br>
> +        spice_usb_device_widget_hide_<wbr>info_bar(self);<br>
> +    }<br>
> +<br>
> +    if (priv->device_count == 0)<br>
> +        spice_usb_device_widget_show_<wbr>info_bar(self, _("No USB devices detected"),<br>
> +                                              GTK_MESSAGE_INFO, priv->icon_info);<br>
> +<br>
> +    return FALSE;<br>
> +}<br>
> +<br>
> +#endif<br>
> -- <br>
> 2.9.4<br>
> <br>
</div></div>> ______________________________<wbr>_________________<br>
> Spice-devel mailing list<br>
> <a href="mailto:Spice-devel@lists.freedesktop.org">Spice-devel@lists.freedesktop.<wbr>org</a><br>
> <a href="https://lists.freedesktop.org/mailman/listinfo/spice-devel" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>mailman/listinfo/spice-devel</a><br>
</blockquote></div><br></div></div>