在spice-gtk-0.30内部有一个spicy命令用于链接远程的虚拟机,
例如spicy -h 172.16.3.4 -p 6700 这样就可以链接到虚拟桌面
其客户端有个USB 设备重定向管理功能,在spicy内有个Input选项卡,其子项有个“Select USB Devices for redirection”。选择重定向USB 设备,点开后就会出现列有USB设备的对话矿对话框,就可以选择USB 设备进行重定向。其如下图所示:
弹出的对话况如下所示:
可以看到所有的USB设备都一GTK CheckButton的形式列出来了,那么当选中里面的USB设备时,本地的USB设备就会被重定向到虚拟桌面Win7下面:
其实现的流程如下:
相关源文件:spicy.c、usb-device-manager.c、channel-usbredir.c、usb-device-widget.c
在usb-device-widget.c中的代码如下:
static GObject *spice_usb_device_widget_constructor(
GType gtype, guint n_properties, GObjectConstructParam *properties)
{
GObject *obj;
SpiceUsbDeviceWidget *self;
SpiceUsbDeviceWidgetPrivate *priv;
GPtrArray *devices = NULL;
GError *err = NULL;
GtkWidget *label;
gchar *str;
int i;
printf("spice_usb_device_widget_constructor-------------usb-device-widget.c\n");
{
/* Always chain up to the parent constructor */
GObjectClass *parent_class;
parent_class = G_OBJECT_CLASS(spice_usb_device_widget_parent_class);
obj = parent_class->constructor(gtype, n_properties, properties);
}
self = SPICE_USB_DEVICE_WIDGET(obj);
priv = self->priv;
if (!priv->session)
g_error("SpiceUsbDeviceWidget constructed without a session");
label = gtk_label_new(NULL);//创建lable控件
str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect"));//控件的内容
gtk_label_set_markup(GTK_LABEL (label), str);//设置lable字体属性
g_free(str);
gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);//设置对齐
gtk_box_pack_start(GTK_BOX(self), label, FALSE, FALSE, 0);//启动容器
priv->manager = spice_usb_device_manager_get(priv->session, &err);//从session中获取manager
if (err) {
spice_usb_device_widget_show_info_bar(self, err->message,
GTK_MESSAGE_WARNING,
GTK_STOCK_DIALOG_WARNING);
g_clear_error(&err);
return obj;
}
g_signal_connect(priv->manager, "device-added",
G_CALLBACK(device_added_cb), self);//设置回调函数用于处理USB热插拔
g_signal_connect(priv->manager, "device-removed",
G_CALLBACK(device_removed_cb), self);
g_signal_connect(priv->manager, "device-error",
G_CALLBACK(device_error_cb), self);
//从manager内获取device列表是根据filter去获取过滤USB设备规则,但是此处是将所有的USB设备都呈现,因此filter为空
devices = spice_usb_device_manager_get_devices(priv->manager);
if (!devices)
goto end;
for (i = 0; i < devices->len; i++)//以gtk内checkbutton控件的模式列出所有设备
{ device_added_cb(NULL, g_ptr_array_index(devices, i), self);
printf("xxxxxxxxxxxxxxx--usb-device-widget.c\n");
}
g_ptr_array_unref(devices);//清空devices
end:
spice_usb_device_widget_update_status(self);//更新控件状态
return obj;
}
在对话框构建constructor内会为每个USB设备建立CheckButton控件,如下:
static void device_added_cb(SpiceUsbDeviceManager *manager,
SpiceUsbDevice *device, gpointer user_data)
{
SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
SpiceUsbDeviceWidgetPrivate *priv = self->priv;
GtkWidget *align, *check;
gchar *desc;
printf("device_added_cb-----usb-device-widget.c\n");
desc = spice_usb_device_get_description(device,//获取usb设备描述符
priv->device_format_string);
check = gtk_check_button_new_with_label(desc);//创建checkbutton
g_free(desc);//创建完毕后就释放掉
if (spice_usb_device_manager_is_device_connected(priv->manager,//检测usb设备是否链接状态如果是连接的就设置checkbutton为真
device))
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);//设置checkbutton为Ture
g_object_set_data_full(//设置数据和回调函数
G_OBJECT(check), "usb-device",
g_boxed_copy(spice_usb_device_get_type(), device),
checkbox_usb_device_destroy_notify);
g_signal_connect(G_OBJECT(check), "clicked",//注册点击checkbutton事件
G_CALLBACK(checkbox_clicked_cb), self);//当点击事件触发时调用回调函数checkbox_clicked_cb
align = gtk_alignment_new(0, 0, 0, 0);//创建对齐控件
gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
gtk_container_add(GTK_CONTAINER(align), check);//在checkbutton中添加对齐方式
gtk_box_pack_end(GTK_BOX(self), align, FALSE, FALSE, 0);
spice_usb_device_widget_update_status(self);//更新usb控件状态
gtk_widget_show_all(align);
}
在建立成功后注册CheckButton的选中点击事件:
static void checkbox_clicked_cb(GtkWidget *check, gpointer user_data)
{
SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data);
SpiceUsbDeviceWidgetPrivate *priv = self->priv;
SpiceUsbDevice *device;
device = g_object_get_data(G_OBJECT(check), "usb-device");//获取数据
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check))) {//获取checkbutton的状态,为Ture
connect_cb_data *data = g_new(connect_cb_data, 1);
data->check = g_object_ref(check);
data->self = g_object_ref(self);
printf("checkbox_clicked_cb---true-------usb-device-widget.c\n");
spice_usb_device_manager_connect_device_async(priv->manager,//重定向设备
device,
NULL,
connect_cb,
data);
} else {//如果状态为False则断开链接
printf("checkbox_clicked_cb---false-------usb-device-widget.c\n");
spice_usb_device_manager_disconnect_device(priv->manager,//断开channel与device
device);
}
spice_usb_device_widget_update_status(self);
}
到此两个事件,一个选中事件则调用spice_usb_device_manager_connect_device_async对该USB 设备俄进行重定向
另一个取消选中,就会掉用spice_usb_device_manager_disconnect_device,断开channel和device的链接,取消重定向,
一先看看简单的取消的过程:这个函数在usb-device-manager.c文件内定义如下:
void spice_usb_device_manager_disconnect_device(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device)
{
g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
g_return_if_fail(device != NULL);//device为空退出
SPICE_DEBUG("disconnecting device %p", device);
printf("spice_usb_device_manager_disconnect_device,disconnecting device %p ------usb-device-manager.c\n",device);
#ifdef USE_USBREDIR
SpiceUsbredirChannel *channel;
channel = spice_usb_device_manager_get_channel_for_dev(self, device);//根据device获取channel
if (channel)
spice_usbredir_channel_disconnect_device(channel);//清空channel内的相关device并设置状态
#ifdef G_OS_WIN32
//没有在windows下使用,因此代码省略注销
#endif
#endif
}
在这个函数内部首先根据device获取相匹配的Channel,在对Channel内的device进行清空:
static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
SpiceUsbDeviceManager *manager, SpiceUsbDevice *device)
{
#ifdef USE_USBREDIR
SpiceUsbDeviceManagerPrivate *priv = manager->priv;
guint i;
for (i = 0; i < priv->channels->len; i++) {//获取device相对应的channel
SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);//获取channel
libusb_device *libdev = spice_usbredir_channel_get_device(channel);
if (spice_usb_device_equal_libdev(device, libdev))//如果两者匹配就返回channel
return channel;//返回device对应的channel
}
#endif
return NULL;
}
清空函数spice_usbredir_channel_disconnect_device在channel-usbredir.c内部
void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel)
{
SpiceUsbredirChannelPrivate *priv = channel->priv;
CHANNEL_DEBUG(channel, "disconnecting device from usb channel %p", channel);
printf("spice_usbredir_channel_disconnect_device, priv->state=%d---------channel-usbredir.c\n",priv->state);
switch (priv->state) {
case STATE_DISCONNECTED:
case STATE_DISCONNECTING:
break;
#if USE_POLKIT
case STATE_WAITING_FOR_ACL_HELPER:
priv->state = STATE_DISCONNECTING;
/* We're still waiting for the acl helper -> cancel it */
spice_usb_acl_helper_close_acl(priv->acl_helper);
break;
#endif
case STATE_CONNECTED://设备处于链接状态
/*
* This sets the usb event thread run condition to FALSE, therefor
* it must be done before usbredirhost_set_device NULL, as
* usbredirhost_set_device NULL will interrupt the
* libusb_handle_events call in the thread.
*/
printf("STATE_CONNECTED --------channel-usbredir.c\n");
{
SpiceSession *session = spice_channel_get_session(SPICE_CHANNEL(channel));//从SpiceChannel中获取session
if (session != NULL)
spice_usb_device_manager_stop_event_listening(//停止监听
spice_usb_device_manager_get(session, NULL));//获取USBmanager即从SpiceSession获取SpiceUsbDeviceManager
}
/* This also closes the libusb handle we passed from open_device */
usbredirhost_set_device(priv->host, NULL);//清空usbhost
libusb_unref_device(priv->device);//去掉引用计数
priv->device = NULL;//清空设备
g_boxed_free(spice_usb_device_get_type(), priv->spice_device);//清空
priv->spice_device = NULL;
priv->state = STATE_DISCONNECTED;//设置状态为断开状态
break;
}
}
可以看到在该channel内的device被清空了,首先就是要告知manager停止对该device的事件监听,退出这个device的事件线程,
spice_usb_device_manager_stop_event_listening如下:
void spice_usb_device_manager_stop_event_listening(
SpiceUsbDeviceManager *self)
{
SpiceUsbDeviceManagerPrivate *priv = self->priv;
g_return_if_fail(priv->event_listeners > 0);
priv->event_listeners--;//运行监听计数减1
printf("priv->event_listeners--= %d-----usb-device-manager.c\n",priv->event_listeners);
if (priv->event_listeners == 0)
priv->event_thread_run = FALSE;//设置线程运行开关为FALSE用来关闭该线程
}