Spice下命令spicy的USB重定向过程分析

在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用来关闭该线程
}
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值