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