<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Sep 18, 2018 at 12:15 PM, Marc-André Lureau <span dir="ltr"><<a href="mailto:marcandre.lureau@gmail.com" target="_blank">marcandre.lureau@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi<br>
<span><br>
On Tue, Sep 18, 2018 at 12:04 PM Yuri Benditovich<br>
<<a href="mailto:yuri.benditovich@daynix.com" target="_blank">yuri.benditovich@daynix.com</a>> wrote:<br>
><br>
><br>
><br>
> On Tue, Sep 18, 2018 at 9:10 AM, Victor Toso <<a href="mailto:victortoso@redhat.com" target="_blank">victortoso@redhat.com</a>> wrote:<br>
>><br>
>> 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>
>> Wouldn't be possible to extend the current widget instead of<br>
>> creating a new one?<br>
><br>
><br>
> Of course it was the first idea before creating new one.<br>
> After several discussions we understood that there is almost nothing<br>
> common between what we'd like to have for CD and what we have in existing widget.<br>
> This also was decided on special meeting of Alexander with spice team (remove<br>
> some controls and split it to two lists, locals and CD). Earlier the screenshot of<br>
> the latest widget (current code) was sent to internal list for review and no objections<br>
> were received.<br>
<br>
</span>Ultimately the discussion and decision should take place on public mailing list.<br>
<br>
And many of us (including me), may not be directly involved in the RH<br>
spice team.<br>
<br>
Could you detail the goals you had and why you had to write a new widget?<br></blockquote><div><br></div><div>Regarding new USB widget:</div><div><br></div><div>User experience when USB CD drive is presented to user is wider than just</div><div>plug/unplug the USB device.</div><div><br></div><div>The CD may include several units, each of them can be managed separately.</div><div>If more than one unit supported on CD, single CD with number of units requires</div><div>just one redirection channel. [At the moment due to time restrictions, we define</div><div>CD as single unit, but I think in the future we will want to enable multiple units on CD]</div><div>So in common case the GUI for CD drives tends to be something like tree view.</div><div>This easily serves also local USB devices as in GTK tree view == list view.</div><div>Initially the widget was developed with single list containing all the devices</div><div>when shared CD devices have leaves and local USB devices do not.</div><div>IMHO, this kind of presentation is better than current one which presents local devices</div><div>and CD devices separately.</div><div><br></div><div>When CD drive is presented to the user on guest machine, it can be ejected by</div><div>user action (from Gnome or Explorer). When the media is ejected, it can be changed</div><div>on client side. So the status (ejected/loaded) should be visible and the user should be</div><div>able to control this also on client side exactly as you can open/close the drive door manually.</div><div><br></div><div>

<div style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">Drive lock. When CD is reqired for SW operation, the SW often locks</div><div style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">the drive preventing user-initiated eject. In such case, if needed, the CD still can be</div><div style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">manually ejected using clip. Another option related to 'lock' is that user may want</div><div style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">to prevent removal of CD by guest software and lock it on client side.</div><div><br></div>The user shall select the properties for logical CD unit and it is good to show them<br class="m_-3388448688929319339m_9133730452485927979m_2258020384313733246gmail-Apple-interchange-newline"></div><div>in the GUI. They include (at least):</div><div>* file name (or device name) used as media</div><div>* device attributes (vendor/product) which are used to report properties of SCSI device</div><div>(these properties are visible to user in seabios boot screen, so in case of 2 CDs the user</div><div>can recognize the needed one). Note that 2 CDs is typical case for Windows installation</div><div>on VM with virtio.</div><div>* Whether the CD media is currently loaded<br></div><div><br></div><div>Visible status of the device is (IMHO) good for self support. When something does not<br></div><div>work as expected and you can't see anything in the GUI that can explain this, the next step</div><div>is opening issues, sending logs etc., i.e. spend own time and time of other people.</div><div>I disagree with the statement 'less GUI is better'. Definitely, less GUI is simpler,</div><div>but this simplicity often increases cost of ownership.</div><div><br></div><div>Our solid opinion is that even current CD additions are too heavy for existing widget.</div><div><br></div><div>Thanks,</div><div>Yuri</div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
For UI, it's best to focus on the supported use case & the user<br>
story/experience, and avoid feature creep (usually the less UI, the<br>
better).<br>
<div><div class="m_-3388448688929319339m_9133730452485927979m_2258020384313733246h5"><br>
><br>
>><br>
>><br>
>> > Signed-off-by: Alexander Nezhinsky <<a href="mailto:alexander@daynix.com" target="_blank">alexander@daynix.com</a>><br>
>> > Signed-off-by: Yuri Benditovich <<a href="mailto:yuri.benditovich@daynix.com" target="_blank">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" target="_blank">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(SpiceUsbDevice<wbr>Manager *manager,<br>
>> > +    SpiceUsbDevice *device, gpointer user_data);<br>
>> > +static void device_removed_cb(SpiceUsbDevi<wbr>ceManager *manager,<br>
>> > +    SpiceUsbDevice *device, gpointer user_data);<br>
>> > +static void device_changed_cb(SpiceUsbDevi<wbr>ceManager *manager,<br>
>> > +    SpiceUsbDevice *device, gpointer user_data);<br>
>> > +static void device_error_cb(SpiceUsbDevice<wbr>Manager *manager,<br>
>> > +    SpiceUsbDevice *device, GError *err, gpointer user_data);<br>
>> > +static gboolean spice_usb_device_widget_update<wbr>_status(gpointer user_data);<br>
>> > +<br>
>> > +/* ------------------------------<wbr>------------------------------<wbr>------ */<br>
>> > +/* gobject glue                                                       */<br>
>> > +<br>
>> > +#define SPICE_USB_DEVICE_WIDGET_GET_PR<wbr>IVATE(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(SpiceUsbDeviceW<wbr>idget, 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)(GtkCel<wbr>lRendererToggle *, gchar *, gpointer);<br>
>> > +<br>
>> > +static void usb_widget_add_device(SpiceUsb<wbr>DeviceWidget *self,<br>
>> > +                                          SpiceUsbDevice *usb_device,<br>
>> > +                                          GtkTreeIter *old_dev_iter);<br>
>> > +<br>
>> > +static gchar *usb_device_description(SpiceU<wbr>sbDeviceManager *manager,<br>
>> > +                                     SpiceUsbDevice *device,<br>
>> > +                                     const gchar *format)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceDescription desc;<br>
>> > +    gchar *descriptor;<br>
>> > +    gchar *res;<br>
>> > +    spice_usb_device_get_info(mana<wbr>ger, 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(v<wbr>oid)<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(gtk<wbr>_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(wg<wbr>, w, h);<br>
>> > +}<br>
>> > +<br>
>> > +static void usb_widget_add_device(SpiceUsb<wbr>DeviceWidget *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_de<wbr>vice_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_sto<wbr>re, &new_dev_iter, NULL);<br>
>> > +    } else {<br>
>> > +        gtk_tree_store_insert_after(tr<wbr>ee_store, &new_dev_iter, NULL, old_dev_iter);<br>
>> > +        gtk_tree_store_remove(tree_sto<wbr>re, 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_de<wbr>vice_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_r<wbr>edirect_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_d<wbr>evice_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_devic<wbr>e_lun_get_info(usb_dev_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_sto<wbr>re, &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_TR<wbr>EE_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_pat<wbr>h);<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_MO<wbr>DEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1);<br>
>> > +    return is_lun;<br>
>> > +}<br>
>> > +<br>
>> > +static gboolean usb_widget_tree_store_find_usb<wbr>_dev_foreach_cb(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_us<wbr>b_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_TRE<wbr>E_MODEL(tree_store),<br>
>> > +                           usb_widget_tree_store_find_us<wbr>b_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(Spice<wbr>UsbDeviceWidget *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_usb<wbr>_device(tree_store, usb_device);<br>
>> > +    if (old_dev_iter != NULL) {<br>
>> > +        SPICE_DEBUG("USB Device removed");<br>
>> > +        gtk_tree_store_remove(tree_sto<wbr>re, 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_usb<wbr>_device(tree_store, usb_device);<br>
>> > +    }<br>
>> > +    if (old_dev_iter != NULL) {<br>
>> > +        SPICE_DEBUG("CD Device removed");<br>
>> > +        gtk_tree_store_remove(tree_sto<wbr>re, 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(SpiceUs<wbr>bDeviceWidget *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_sizin<wbr>g(view_col, GTK_TREE_VIEW_COLUMN_FIXED);<br>
>> > +    gtk_tree_view_column_set_expan<wbr>d(view_col, FALSE);<br>
>> > +    gtk_tree_view_column_set_resiz<wbr>able(view_col, FALSE);<br>
>> > +    gtk_tree_view_append_column(is<wbr>_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_colu<wbr>mn(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_a<wbr>ctivatable(GTK_CELL_RENDERER_T<wbr>OGGLE(renderer), FALSE);<br>
>> > +<br>
>> > +    gtk_tree_view_column_set_sizin<wbr>g(view_col, GTK_TREE_VIEW_COLUMN_FIXED);<br>
>> > +    gtk_tree_view_column_set_expan<wbr>d(view_col, FALSE);<br>
>> > +    gtk_tree_view_column_set_resiz<wbr>able(view_col, FALSE);<br>
>> > +    gtk_tree_view_append_column(is<wbr>_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(SpiceUsbD<wbr>eviceWidget *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_sizin<wbr>g(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY<wbr>);<br>
>> > +    gtk_tree_view_column_set_resiz<wbr>able(view_col, TRUE);<br>
>> > +    gtk_tree_view_column_set_expan<wbr>d(view_col, expandable);<br>
>> > +<br>
>> > +    gtk_tree_view_append_column(is<wbr>_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(SpiceUs<wbr>bDeviceWidget *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(is<wbr>_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(GtkTr<wbr>eeStore *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_s<wbr>tring(GTK_TREE_MODEL(tree_stor<wbr>e), iter, path_str);<br>
>> > +    gtk_tree_model_get(GTK_TREE_MO<wbr>DEL(tree_store), iter, col_id, &toggle_val, -1);<br>
>> > +<br>
>> > +    return toggle_val;<br>
>> > +}<br>
>> > +<br>
>> > +static void tree_item_toggle_set(GtkTreeSt<wbr>ore *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_c<wbr>b_data *user_data)<br>
>> > +{<br>
>> > +    spice_usb_device_widget_update<wbr>_status(user_data->self);<br>
>> > +    g_object_unref(user_data->self<wbr>);<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_de<wbr>vice_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_usb<wbr>_device(tree_store, usb_dev);<br>
>> > +    if (!dev_iter) {<br>
>> > +        return;<br>
>> > +    }<br>
>> > +<br>
>> > +    desc = usb_device_description(priv->m<wbr>anager, usb_dev, priv->device_format_string);<br>
>> > +    SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc);<br>
>> > +<br>
>> > +    finished = spice_usb_device_manager_conne<wbr>ct_device_finish(priv->manager<wbr>, 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_fun<wbr>c(GTK_TOGGLE_BUTTON(user_data-<wbr>>check),<br>
>> > +                                        checkbox_clicked_cb, self);<br>
>> > +        gtk_toggle_button_set_active(G<wbr>TK_TOGGLE_BUTTON(user_data->ch<wbr>eck), FALSE);<br>
>> > +        g_signal_handlers_unblock_by_f<wbr>unc(GTK_TOGGLE_BUTTON(user_dat<wbr>a->check),<br>
>> > +                                        checkbox_clicked_cb, self);<br>
>> > +        */<br>
>> > +    }<br>
>> > +    g_free(desc);<br>
>> > +    g_free(dev_iter);<br>
>> > +    connect_cb_data_free(user_data<wbr>);<br>
>> > +}<br>
>> > +<br>
>> > +static void usb_widget_disconnect_cb(GObje<wbr>ct *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_de<wbr>vice_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_usb<wbr>_device(tree_store, usb_dev);<br>
>> > +    if (!dev_iter) {<br>
>> > +        return;<br>
>> > +    }<br>
>> > +<br>
>> > +    desc = usb_device_description(priv->m<wbr>anager, usb_dev, priv->device_format_string);<br>
>> > +    SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc);<br>
>> > +<br>
>> > +    finished = spice_usb_device_manager_disco<wbr>nnect_device_finish(priv->mana<wbr>ger, 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_data<wbr>);<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(tree<wbr>_store, path_str, &iter, COL_REDIRECT);<br>
>> > +    SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF");<br>
>> > +    tree_item_toggle_set(tree_stor<wbr>e, &iter, COL_REDIRECT, new_redirect_val);<br>
>> > +<br>
>> > +    gtk_tree_model_get(GTK_TREE_MO<wbr>DEL(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_conne<wbr>ct_device_async(priv->manager, usb_dev,<br>
>> > +                                                      NULL, /* cancellable */<br>
>> > +                                                      usb_widget_connect_cb, cb_data);<br>
>> > +    } else {<br>
>> > +        spice_usb_device_manager_disco<wbr>nnect_device_async(priv->manag<wbr>er, usb_dev,<br>
>> > +                                                         NULL, /* cancellable */<br>
>> > +                                                         usb_widget_disconnect_cb, cb_data);<br>
>> > +<br>
>> > +    }<br>
>> > +    spice_usb_device_widget_update<wbr>_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_d<wbr>ata);<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_d<wbr>ata);<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(SpiceUsbDevice<wbr>Manager *usb_dev_mgr,<br>
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +<br>
>> > +    SPICE_DEBUG("Signal: Device Added");<br>
>> > +<br>
>> > +    usb_widget_add_device(self, usb_device, NULL);<br>
>> > +<br>
>> > +    spice_usb_device_widget_update<wbr>_status(self);<br>
>> > +}<br>
>> > +<br>
>> > +static void device_removed_cb(SpiceUsbDevi<wbr>ceManager *usb_dev_mgr,<br>
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<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_update<wbr>_status(self);<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static void device_changed_cb(SpiceUsbDevi<wbr>ceManager *usb_dev_mgr,<br>
>> > +    SpiceUsbDevice *usb_device, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeIter *old_dev_iter;<br>
>> > +    gboolean is_cd = spice_usb_device_manager_is_de<wbr>vice_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_usb<wbr>_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_update<wbr>_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(SpiceUsbDevice<wbr>Manager *manager,<br>
>> > +    SpiceUsbDevice *usb_device, GError *err, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeIter *dev_iter;<br>
>> > +    gboolean is_cd = spice_usb_device_manager_is_de<wbr>vice_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_usb<wbr>_device(tree_store, usb_device);<br>
>> > +    if (dev_iter != NULL) {<br>
>> > +        tree_item_toggle_set(tree_stor<wbr>e, dev_iter, COL_REDIRECT, FALSE);<br>
>> > +        spice_usb_device_widget_update<wbr>_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(GtkT<wbr>reeSelection *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_select<wbr>ed(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_m<wbr>odel, &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(GtkTreeV<wbr>iew *tree_view)<br>
>> > +{<br>
>> > +    GtkTreeSelection *select;<br>
>> > +<br>
>> > +    select = gtk_tree_view_get_selection(tr<wbr>ee_view);<br>
>> > +    gtk_tree_selection_set_mode(se<wbr>lect, GTK_SELECTION_SINGLE);<br>
>> > +<br>
>> > +    g_signal_connect(G_OBJECT(sele<wbr>ct), "changed",<br>
>> > +                     G_CALLBACK(tree_selection_cha<wbr>nged_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_HO<wbr>RIZONTAL, 6);<br>
>> > +    GtkWidget *icon = gtk_image_new_from_icon_name(i<wbr>con_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_CONTAINE<wbr>R(box), icon);<br>
>> > +<br>
>> > +    /* add label */<br>
>> > +    gtk_label_set_xalign(GTK_LABEL<wbr>(label), 0.0);<br>
>> > +    gtk_label_set_use_underline(GT<wbr>K_LABEL(label), TRUE);<br>
>> > +    g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL);<br>
>> > +    gtk_widget_add_accelerator(par<wbr>ent, "activate", accel_group, accel_key,<br>
>> > +                               GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);<br>
>> > +    gtk_accel_label_set_accel_widg<wbr>et(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_CONTAINE<wbr>R(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("C<wbr>hoose File for USB CD",<br>
>> > +        GTK_WINDOW(gtk_widget_get_topl<wbr>evel(file_entry)),<br>
>> > +        action,<br>
>> > +        "_Open",<br>
>> > +        "_Cancel");<br>
>> > +<br>
>> > +    res = gtk_native_dialog_run(GTK_NATI<wbr>VE_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_EN<wbr>TRY(file_entry), 1);<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(f<wbr>ile_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_topl<wbr>evel(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_respons<wbr>e(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);<br>
>> > +<br>
>> > +    res = gtk_dialog_run(GTK_DIALOG(dial<wbr>og));<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_EN<wbr>TRY(file_entry), 1);<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(f<wbr>ile_entry), filename);<br>
>> > +        g_free(filename);<br>
>> > +    }<br>
>> > +    gtk_widget_destroy(dialog);<br>
>> > +}<br>
>> > +#endif<br>
>> > +<br>
>> > +static gboolean lun_properties_dialog_loaded_s<wbr>witch_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_d<wbr>ialog->locked_switch, state);<br>
>> > +    gtk_widget_set_can_focus(lun_d<wbr>ialog->locked_switch, state);<br>
>> > +<br>
>> > +    return FALSE; /* call default signal handler */<br>
>> > +}<br>
>> > +<br>
>> > +static void lun_properties_dialog_toggle_a<wbr>dvanced(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->ad<wbr>vanced_grid);<br>
>> > +        lun_dialog->advanced_shown = FALSE;<br>
>> > +    } else {<br>
>> > +        gtk_widget_show_all(lun_dialog<wbr>->advanced_grid);<br>
>> > +        lun_dialog->advanced_shown = TRUE;<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static void create_lun_properties_dialog(S<wbr>piceUsbDeviceWidget *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_PARENT<wbr>, /* flags */<br>
>> > +                    !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT,<br>
>> > +                    "Cancel", GTK_RESPONSE_REJECT,<br>
>> > +                    NULL);<br>
>> > +<br>
>> > +    gtk_dialog_set_default_respons<wbr>e(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);<br>
>> > +    gtk_container_set_border_width<wbr>(GTK_CONTAINER(dialog), 12);<br>
>> > +    gtk_box_set_spacing(GTK_BOX(gt<wbr>k_bin_get_child(GTK_BIN(dialog<wbr>))), 12);<br>
>> > +<br>
>> > +    content_area = gtk_dialog_get_content_area(GT<wbr>K_DIALOG(dialog));<br>
>> > +<br>
>> > +    /* main grid - always visible */<br>
>> > +    grid = gtk_grid_new();<br>
>> > +    gtk_grid_set_row_spacing(GTK_G<wbr>RID(grid), 12);<br>
>> > +    gtk_grid_set_column_homogeneou<wbr>s(GTK_GRID(grid), FALSE);<br>
>> > +    gtk_container_add(GTK_CONTAINE<wbr>R(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_en<wbr>try, TRUE);<br>
>> > +    if (!lun_info) {<br>
>> > +        gtk_entry_set_placeholder_text<wbr>(GTK_ENTRY(file_entry), "file-path");<br>
>> > +    } else {<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(f<wbr>ile_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(ch<wbr>oose_button),<br>
>> > +                     "clicked", G_CALLBACK(usb_cd_choose_file)<wbr>, file_entry);<br>
>> > +    if (lun_info && lun_info->loaded) {<br>
>> > +        gtk_widget_set_sensitive(choos<wbr>e_button, FALSE);<br>
>> > +        gtk_widget_set_can_focus(choos<wbr>e_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("Adv<wbr>anced");<br>
>> > +    gtk_button_set_relief(GTK_BUTT<wbr>ON(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_BUTTO<wbr>N(advanced_button), advanced_icon);<br>
>> > +    gtk_button_set_always_show_ima<wbr>ge(GTK_BUTTON(advanced_button)<wbr>, TRUE);<br>
>> > +    g_signal_connect(advanced_butt<wbr>on, "clicked", G_CALLBACK(lun_properties_dial<wbr>og_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_G<wbr>RID(advanced_grid), 12);<br>
>> > +    gtk_grid_set_column_homogeneou<wbr>s(GTK_GRID(advanced_grid), FALSE);<br>
>> > +    gtk_container_add(GTK_CONTAINE<wbr>R(content_area), advanced_grid);<br>
>> > +<br>
>> > +    /* horizontal separator */<br>
>> > +    gtk_container_add(GTK_CONTAINE<wbr>R(content_area), gtk_separator_new(GTK_ORIENTAT<wbr>ION_HORIZONTAL));<br>
>> > +<br>
>> > +    /* pack advanced grid */<br>
>> > +    nrow = 0;<br>
>> > +<br>
>> > +    /* horizontal separator */<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_grid),<br>
>> > +            gtk_separator_new(GTK_ORIENTAT<wbr>ION_HORIZONTAL),<br>
>> > +            0, nrow++, // left top<br>
>> > +            7, 1); // width height<br>
>> > +<br>
>> > +    /* product id labels */<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_grid),<br>
>> > +            gtk_label_new("Vendor"),<br>
>> > +            0, nrow, // left top<br>
>> > +            2, 1); // width height<br>
>> > +<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_grid),<br>
>> > +            gtk_label_new("Product"),<br>
>> > +            2, nrow, // left top<br>
>> > +            4, 1); // width height<br>
>> > +<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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_E<wbr>NTRY(vendor_entry), 8);<br>
>> > +    if (lun_info) {<br>
>> > +        gtk_widget_set_sensitive(vendo<wbr>r_entry, FALSE);<br>
>> > +        gtk_widget_set_can_focus(vendo<wbr>r_entry, FALSE);<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(v<wbr>endor_entry), lun_info->vendor);<br>
>> > +    } else {<br>
>> > +        gtk_entry_set_placeholder_text<wbr>(GTK_ENTRY(vendor_entry), "auto");<br>
>> > +    }<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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_E<wbr>NTRY(product_entry), 16);<br>
>> > +    if (lun_info) {<br>
>> > +        gtk_widget_set_sensitive(produ<wbr>ct_entry, FALSE);<br>
>> > +        gtk_widget_set_can_focus(produ<wbr>ct_entry, FALSE);<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(p<wbr>roduct_entry), lun_info->product);<br>
>> > +    } else {<br>
>> > +        gtk_entry_set_placeholder_text<wbr>(GTK_ENTRY(product_entry), "auto");<br>
>> > +    }<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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_E<wbr>NTRY(revision_entry), 4);<br>
>> > +    if (lun_info) {<br>
>> > +        gtk_widget_set_sensitive(revis<wbr>ion_entry, FALSE);<br>
>> > +        gtk_widget_set_can_focus(revis<wbr>ion_entry, FALSE);<br>
>> > +        gtk_entry_set_text(GTK_ENTRY(r<wbr>evision_entry), lun_info->revision);<br>
>> > +    } else {<br>
>> > +        gtk_entry_set_placeholder_text<wbr>(GTK_ENTRY(revision_entry), "auto");<br>
>> > +    }<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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(advan<wbr>ced_grid),<br>
>> > +                gtk_separator_new(GTK_ORIENTAT<wbr>ION_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(advan<wbr>ced_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_SWITC<wbr>H(loaded_switch), TRUE);<br>
>> > +    if (lun_info) {<br>
>> > +        gtk_widget_set_child_visible(l<wbr>oaded_switch, FALSE);<br>
>> > +        gtk_widget_set_child_visible(l<wbr>oaded_label, FALSE);<br>
>> > +    } else {<br>
>> > +        g_signal_connect(loaded_switch<wbr>, "state-set",<br>
>> > +                         G_CALLBACK(lun_properties_dia<wbr>log_loaded_switch_cb), lun_dialog);<br>
>> > +    }<br>
>> > +    gtk_widget_set_halign(loaded_s<wbr>witch, GTK_ALIGN_START);<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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(advan<wbr>ced_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_SWITC<wbr>H(locked_switch), FALSE);<br>
>> > +    gtk_widget_set_hexpand(locked_<wbr>switch, FALSE);<br>
>> > +    if (lun_info) {<br>
>> > +        gtk_widget_set_child_visible(l<wbr>ocked_switch, FALSE);<br>
>> > +        gtk_widget_set_child_visible(l<wbr>ocked_label, FALSE);<br>
>> > +    }<br>
>> > +    gtk_widget_set_halign(locked_s<wbr>witch, GTK_ALIGN_START);<br>
>> > +    gtk_grid_attach(GTK_GRID(advan<wbr>ced_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_info<wbr>(lun_properties_dialog *lun_dialog,<br>
>> > +                                            SpiceUsbDeviceLunInfo *lun_info)<br>
>> > +{<br>
>> > +    lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(l<wbr>un_dialog->file_entry));<br>
>> > +    lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(l<wbr>un_dialog->vendor_entry));<br>
>> > +    lun_info->product = gtk_entry_get_text(GTK_ENTRY(l<wbr>un_dialog->product_entry));<br>
>> > +    lun_info->revision = gtk_entry_get_text(GTK_ENTRY(l<wbr>un_dialog->revision_entry));<br>
>> > +    lun_info->loaded = gtk_switch_get_active(GTK_SWIT<wbr>CH(lun_dialog->loaded_switch))<wbr>;<br>
>> > +    lun_info->locked = gtk_switch_get_active(GTK_SWIT<wbr>CH(lun_dialog->locked_switch))<wbr>;<br>
>> > +}<br>
>> > +<br>
>> > +/* Popup menu */<br>
>> > +static void view_popup_menu_on_eject(GtkWi<wbr>dget *menuitem, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr<wbr>iv->cd_tree.tree_view);<br>
>> > +    GtkTreeModel *tree_model;<br>
>> > +    GtkTreeIter iter;<br>
>> > +<br>
>> > +    if (gtk_tree_selection_get_select<wbr>ed(select, &tree_model, &iter)) {<br>
>> > +        if (!tree_item_is_lun(priv->cd_tr<wbr>ee.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_devic<wbr>e_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(GtkWid<wbr>get *menuitem, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr<wbr>iv->cd_tree.tree_view);<br>
>> > +    GtkTreeModel *tree_model;<br>
>> > +    GtkTreeIter iter;<br>
>> > +<br>
>> > +    if (gtk_tree_selection_get_select<wbr>ed(select, &tree_model, &iter)) {<br>
>> > +        if (!tree_item_is_lun(priv->cd_tr<wbr>ee.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_devic<wbr>e_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(GtkW<wbr>idget *menuitem, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr<wbr>iv->cd_tree.tree_view);<br>
>> > +    GtkTreeModel *tree_model;<br>
>> > +    GtkTreeIter iter;<br>
>> > +<br>
>> > +    if (gtk_tree_selection_get_select<wbr>ed(select, &tree_model, &iter)) {<br>
>> > +        if (!tree_item_is_lun(priv->cd_tr<wbr>ee.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_al<wbr>l(select);<br>
>> > +            spice_usb_device_manager_devic<wbr>e_lun_remove(lun_item->manager<wbr>, 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(Gt<wbr>kWidget *menuitem, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkTreeSelection *select = gtk_tree_view_get_selection(pr<wbr>iv->cd_tree.tree_view);<br>
>> > +    GtkTreeModel *tree_model;<br>
>> > +    GtkTreeIter iter;<br>
>> > +<br>
>> > +    if (gtk_tree_selection_get_select<wbr>ed(select, &tree_model, &iter)) {<br>
>> > +        if (!tree_item_is_lun(priv->cd_tr<wbr>ee.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_al<wbr>l(select);<br>
>> > +            create_lun_properties_dialog(s<wbr>elf, 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_info<wbr>(&lun_dialog, &lun_info);<br>
>> > +                spice_usb_device_manager_devic<wbr>e_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(GtkW<wbr>idget *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_MENU<wbr>_SHELL(menu), menu_item);<br>
>> > +<br>
>> > +    return menu_item;<br>
>> > +}<br>
>> > +<br>
>> > +static gboolean has_single_lun(SpiceUsbDeviceW<wbr>idgetPrivate *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_d<wbr>evice_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_d<wbr>ata);<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(tr<wbr>ee_view);<br>
>> > +<br>
>> > +    if (!gtk_tree_selection_get_selec<wbr>ted(select, &tree_model, &iter)) {<br>
>> > +        SPICE_DEBUG("No tree view row is selected");<br>
>> > +        return;<br>
>> > +    }<br>
>> > +    if (!tree_item_is_lun(priv->cd_tr<wbr>ee.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_usb<wbr>_device(priv->cd_tree.tree_sto<wbr>re, 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_by<wbr>_pos(GtkTreeView *tree_view, gint x, gint y)<br>
>> > +{<br>
>> > +    GtkTreeSelection *selection = gtk_tree_view_get_selection(GT<wbr>K_TREE_VIEW(tree_view));<br>
>> > +    if (gtk_tree_selection_count_sele<wbr>cted_rows(selection) <= 1) {<br>
>> > +        GtkTreePath *path;<br>
>> > +        /* Get tree path for row that was clicked */<br>
>> > +        if (gtk_tree_view_get_path_at_pos<wbr>(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL))<br>
>> > +        {<br>
>> > +            gtk_tree_selection_unselect_al<wbr>l(selection);<br>
>> > +            gtk_tree_selection_select_path<wbr>(selection, path);<br>
>> > +            gtk_tree_path_free(path);<br>
>> > +        }<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static gboolean treeview_on_right_button_press<wbr>ed_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_by<wbr>_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(G<wbr>tkWidget *add_cd_button, gpointer user_data)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_d<wbr>ata);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +    GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd<wbr>_button);<br>
>> > +    lun_properties_dialog lun_dialog;<br>
>> > +    gint resp;<br>
>> > +<br>
>> > +    create_lun_properties_dialog(s<wbr>elf, 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_info<wbr>(&lun_dialog, &lun_info);<br>
>> > +        spice_usb_device_manager_add_c<wbr>d_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_pr<wbr>operty(GObject     *gobject,<br>
>> > +                                                 guint        prop_id,<br>
>> > +                                                 GValue      *value,<br>
>> > +                                                 GParamSpec  *pspec)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobjec<wbr>t);<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_PROPERTY<wbr>_ID(gobject, prop_id, pspec);<br>
>> > +        break;<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_set_pr<wbr>operty(GObject       *gobject,<br>
>> > +                                                 guint          prop_id,<br>
>> > +                                                 const GValue  *value,<br>
>> > +                                                 GParamSpec    *pspec)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobjec<wbr>t);<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_PROPERTY<wbr>_ID(gobject, prop_id, pspec);<br>
>> > +        break;<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_hide_i<wbr>nfo_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_i<wbr>nfo_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_HO<wbr>RIZONTAL, 0);<br>
>> > +    gtk_container_add(GTK_CONTAINE<wbr>R(content_area), hbox);<br>
>> > +<br>
>> > +    icon = gtk_image_new_from_pixbuf(icon<wbr>_pixbuf);<br>
>> > +    gtk_box_pack_start(GTK_BOX(hbo<wbr>x), icon, FALSE, FALSE, 10);<br>
>> > +<br>
>> > +    label = gtk_label_new(message);<br>
>> > +    gtk_box_pack_start(GTK_BOX(hbo<wbr>x), 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_CONTAINE<wbr>R(priv->info_bar), info_bar);<br>
>> > +<br>
>> > +    gtk_box_pack_start(GTK_BOX(sel<wbr>f), 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->info<wbr>_bar);<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_create<wbr>_tree_view(SpiceUsbDeviceWidge<wbr>t *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_ne<wbr>w());<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_v<wbr>iew, 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_colu<wbr>mn(self, COL_LOADED, COL_LUN_ITEM, is_cd);<br>
>> > +        view_add_read_only_toggle_colu<wbr>mn(self, COL_LOCKED, COL_LUN_ITEM, is_cd);<br>
>> > +        // uncomment to show also 'idle' column for CD<br>
>> > +        //view_add_read_only_toggle_co<wbr>lumn(self, COL_IDLE, COL_LUN_ITEM, is_cd);<br>
>> > +    }<br>
>> > +<br>
>> > +    gtk_tree_selection_set_mode(<br>
>> > +            gtk_tree_view_get_selection(tr<wbr>ee_view),<br>
>> > +            GTK_SELECTION_NONE);<br>
>> > +<br>
>> > +    if (is_cd) {<br>
>> > +        set_selection_handler(tree_vie<wbr>w);<br>
>> > +    }<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_signal<wbr>s_connect(SpiceUsbDeviceWidget *self)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +<br>
>> > +    g_signal_connect(priv->manager<wbr>, "device-added",<br>
>> > +                     G_CALLBACK(device_added_cb), self);<br>
>> > +    g_signal_connect(priv->manager<wbr>, "device-removed",<br>
>> > +                     G_CALLBACK(device_removed_cb)<wbr>, self);<br>
>> > +    g_signal_connect(priv->manager<wbr>, "device-changed",<br>
>> > +                     G_CALLBACK(device_changed_cb)<wbr>, self);<br>
>> > +    // TODO: connect failed<br>
>> > +    g_signal_connect(priv->manager<wbr>, "device-error",<br>
>> > +                     G_CALLBACK(device_error_cb), self);<br>
>> > +<br>
>> > +    g_signal_connect(priv->cd_tree<wbr>.tree_view, "button-press-event",<br>
>> > +                     G_CALLBACK(treeview_on_right_<wbr>button_pressed_cb), self);<br>
>> > +    g_signal_connect(priv->cd_tree<wbr>.tree_view, "popup-menu",<br>
>> > +                     G_CALLBACK(treeview_on_popup_<wbr>key_pressed_cb), self);<br>
>> > +}<br>
>> > +<br>
>> > +static void create_tree_window(SpiceUsbDev<wbr>iceWidget *self, GtkTreeView *tree_view)<br>
>> > +{<br>
>> > +    GtkWidget *sw;<br>
>> > +    /* scrolled window */<br>
>> > +    sw = gtk_scrolled_window_new(NULL, NULL);<br>
>> > +    gtk_scrolled_window_set_shadow<wbr>_type(GTK_SCROLLED_WINDOW(sw),<br>
>> > +        GTK_SHADOW_ETCHED_IN);<br>
>> > +    gtk_scrolled_window_set_policy<wbr>(GTK_SCROLLED_WINDOW(sw),<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_CONTAINE<wbr>R(sw), GTK_WIDGET(tree_view));<br>
>> > +    gtk_box_pack_start(GTK_BOX(sel<wbr>f), sw, TRUE, TRUE, 0);<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_constr<wbr>ucted(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(gobjec<wbr>t);<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_LABEL<wbr>(priv->label), str);<br>
>> > +    g_free(str);<br>
>> > +    gtk_box_pack_start(GTK_BOX(sel<wbr>f), 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-transm<wbr>it-receive", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
>> > +    priv->icon_disconn = get_named_icon("network-offlin<wbr>e", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
>> > +    priv->icon_warning = get_named_icon("dialog-warning<wbr>", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
>> > +    priv->icon_info = get_named_icon("dialog-informa<wbr>tion", GTK_ICON_SIZE_LARGE_TOOLBAR);<br>
>> > +<br>
>> > +    priv->manager = spice_usb_device_manager_get(p<wbr>riv->session, &err);<br>
>> > +    if (err) {<br>
>> > +        spice_usb_device_widget_show_i<wbr>nfo_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_create<wbr>_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_create<wbr>_tree_view(self, FALSE);<br>
>> > +<br>
>> > +    spice_usb_device_widget_signal<wbr>s_connect(self);<br>
>> > +<br>
>> > +    hbox = gtk_box_new(GTK_ORIENTATION_HO<wbr>RIZONTAL, 0);<br>
>> > +    gtk_box_pack_start(GTK_BOX(sel<wbr>f), 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(hbo<wbr>x), 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_HO<wbr>RIZONTAL, 0);<br>
>> > +        gtk_box_pack_start(GTK_BOX(sel<wbr>f), hbox, FALSE, FALSE, 0);<br>
>> > +        dev_label = gtk_label_new(_("Shared CD devices"));<br>
>> > +        gtk_box_pack_start(GTK_BOX(hbo<wbr>x), dev_label, TRUE, FALSE, 0);<br>
>> > +        /* "Add CD" button - in hbox */<br>
>> > +        add_cd_button = gtk_button_new_with_label(_("A<wbr>dd CD"));<br>
>> > +        gtk_button_set_always_show_ima<wbr>ge(GTK_BUTTON(add_cd_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_BUTTO<wbr>N(add_cd_button), add_cd_icon);<br>
>> > +<br>
>> > +        gtk_widget_set_halign(add_cd_b<wbr>utton, GTK_ALIGN_END);<br>
>> > +        g_signal_connect(add_cd_button<wbr>, "clicked", G_CALLBACK(add_cd_lun_button_c<wbr>licked_cb), self);<br>
>> > +        gtk_box_pack_start(GTK_BOX(hbo<wbr>x), add_cd_button, FALSE, FALSE, 0);<br>
>> > +        create_tree_window(self, priv->cd_tree.tree_view);<br>
>> > +    }<br>
>> > +    devices = spice_usb_device_manager_get_d<wbr>evices(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_update<wbr>_status(self);<br>
>> > +}<br>
>> > +<br>
>> > +static void spice_usb_device_widget_finali<wbr>ze(GObject *object)<br>
>> > +{<br>
>> > +    SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object<wbr>);<br>
>> > +    SpiceUsbDeviceWidgetPrivate *priv = self->priv;<br>
>> > +<br>
>> > +    if (priv->manager) {<br>
>> > +        g_signal_handlers_disconnect_b<wbr>y_func(priv->manager,<br>
>> > +                                             device_added_cb, self);<br>
>> > +        g_signal_handlers_disconnect_b<wbr>y_func(priv->manager,<br>
>> > +                                             device_removed_cb, self);<br>
>> > +        g_signal_handlers_disconnect_b<wbr>y_func(priv->manager,<br>
>> > +                                             device_changed_cb, self);<br>
>> > +        g_signal_handlers_disconnect_b<wbr>y_func(priv->manager,<br>
>> > +                                             device_error_cb, self);<br>
>> > +    }<br>
>> > +    g_object_unref(priv->session);<br>
>> > +    g_free(priv->device_format_str<wbr>ing);<br>
>> > +<br>
>> > +    if (G_OBJECT_CLASS(spice_usb_devi<wbr>ce_widget_parent_class)->final<wbr>ize)<br>
>> > +        G_OBJECT_CLASS(spice_usb_devic<wbr>e_widget_parent_class)->finali<wbr>ze(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_constr<wbr>ucted;<br>
>> > +    gobject_class->finalize     = spice_usb_device_widget_finali<wbr>ze;<br>
</div></div><span>>> > +    gobject_class->get_property = spice_usb_device_widget_get_pr<wbr>operty;<br>
>> > +    gobject_class->set_property = spice_usb_device_widget_set_pr<wbr>operty;<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_propert<wbr>y(gobject_class, PROP_SESSION, pspec);<br>
>> > +<br>
>> > +    /**<br>
>> > +     * SpiceUsbDeviceWidget:device-fo<wbr>rmat-string:<br>
>> > +     *<br>
</span>>> > +     * Format string to pass to spice_usb_device_get_descript_<wbr>______________________________<wbr>________________<br>
<div class="m_-3388448688929319339m_9133730452485927979m_2258020384313733246HOEnZb"><div class="m_-3388448688929319339m_9133730452485927979m_2258020384313733246h5">>> Spice-devel mailing list<br>
>> <a href="mailto:Spice-devel@lists.freedesktop.org" target="_blank">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>
<br>
<br>
<br>
</div></div><span class="m_-3388448688929319339m_9133730452485927979m_2258020384313733246HOEnZb"><font color="#888888">-- <br>
Marc-André Lureau<br>
</font></span></blockquote></div><br></div></div>