在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);//清空devicesend: 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用来关闭该线程}新闻热点
疑难解答