Hi

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

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

- I have never used a pluggable CD drive.

- I have never used mulitple CD drive simultaneously.

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

- I have never used locking either.

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

That's useful indeed

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

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

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

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

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

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

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



-- 
Marc-André Lureau
_______________________________________________
Spice-devel mailing list
Spice-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/spice-devel

Reply via email to