分享

Spice 分析(4) – spice 客户端实现

 梦醉千秋 2015-05-19

目前上游社区支持的客户端是 spice-gtk, 它其实是一个库的项目, 编译后能得到两个库:

  • libspice-client-glib: spice client 的协议处理部分

  • libspice-client-gtk: 整合了 gtk 的更完善的实现

virt-manager 通过 spice-gtk 实现了 spice client 的功能, 可执行的程序称是 remote-view.

下面对现在市面上存在的各种客户端做一下分类和探讨, 其中对 spice-gtk 重点做 一下探讨, 毕竟这是东宫太子.

使用 spice-gtk

如果要实现 spice 客户端, 最省事最简单的办法莫过于在 libspice-client-gtk 这个库的基础上进行开发了. 比如我曾经介绍过的 编写简单的 Spice Client

但是由于以下种种原因, 基于 spice-gtk 的客户端可能不会是最佳的选择:

gtk3 不支持 X11 的共享内存

如果使用 gtk3, 那么渲染的后端只能是 cairo, 无法选择 X11, 同时意味者 X11 和应用程序的共享内存无法使用, 即每次做 bitmap 渲染的时候, 会在 spice client 和 X11 server 之间多做一次内存拷贝, 损失不必要的性能.

某些平台没有 gtk

在其它平台并没有 gtk 实现, 比如 android, IOS 等, 要移植 gtk 到这些 平台, 不亚于重写 spice client.

性能的考虑

同上面, 如果渲染后端使用 cairo, 在某些平台达不到最好的效果, 实际上, 在 Linux 下, 应该使用 X11 的共享内存技术, 在 windows 或者其它平台也应该使用 相应的技术.

以下几个客户端即是基于 libspice-client-gtk 实现的.

  • remote-view

  • spice-osx

  • spicy(just to be used to test spice-gtk)

使用 spice-glib

libspice-client-glib 完整的实现了 spice 协议的客户端, 只不过用的 C 库是 glib 而已, 它完全和 gtk 无关. 理论上, 每个平台都应该调用 libspice-client-glib, 而图形的渲染 部分, 则由平台相关的 API 来实现, 也即实现 spice-widget.c 部分.

PS: 在 Bitmap 的处理部分, 应该使用平台相关的 API, 这样能达到最佳的性能(最大化的 使用显卡的加速功能), 比如在 linux(X86/ARM) 平台, 使用硬件加速(opengl?) 来处理 Bitmap, 在 windows 平台, 使用 GDI 来处理, 在 macos 平台(?). 遗憾的是, 虽然开发者留下了这些平台独立的接口和文件, 但是并没有完全实现, 这些接口都没有 用到, 目前的实现都是用统一的 pixman 库(纯 CPU 运算, 虽然对各个平台也会做一些优化) 操作, 具体的实现在 common/sw_canvas.c.

言归正传, 下面描述怎么使用 libspice-client-glib, 大部分代码参照以前我在 android 上的 实现的 spice-client.

首先需要实现一个 display 的类(python/c++/c)用来做渲染, 在这个类的构造函数里面, 注册相应的信号处理函数(这些信号由 SpiceSession 在有事件到来的时候通知你), 例如:

    AndroidDisplay *display;AndroidDisplayPrivate *d; // ...... if (!d->session) {g_error("AndroidDisplay constructed without a session");} 
    spice_g_signal_connect_object(d->session, "channel-new",  G_CALLBACK(channel_new), display, 0);spice_g_signal_connect_object(d->session, "channel-destroy",  G_CALLBACK(channel_destroy), display, 0);

其中 channel-new 这个信号, 表示 SpiceSession 监听到有新的 channel(display, input) 在的时候, 主动通知我们的 AndroidDisplay 对象, 我们使用 channel_new() 来处理这个 信号:

static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data){AndroidDisplay *display = data;AndroidDisplayPrivate *d = ANDROID_DISPLAY_GET_PRIVATE(display);int id; 
    g_object_get(channel, "channel-id", &id, NULL);if (SPICE_IS_MAIN_CHANNEL(channel)) {// ......} if (SPICE_IS_DISPLAY_CHANNEL(channel)) {SpiceDisplayPrimary primary;if (id != d->channel_id)return;d->display = channel;// 处理 primary 信号, 下面会讲到spice_g_signal_connect_object(channel, "display-primary-create",  G_CALLBACK(primary_create), display, 0);spice_g_signal_connect_object(channel, "display-primary-destroy",  G_CALLBACK(primary_destroy), display, 0);// 这个信号比较重要, 每次 guest 做任何 UI 的操作, blit, copy 等, 必然会// 重绘某些区域, 等到 SpiceDisplay 处理完 bitmap 的变换之后, 会给注册// 了这个信号的组件发送 display-invalidate 信号, 告诉这些组件哪些区域需要// 重绘.spice_g_signal_connect_object(channel, "display-invalidate",  G_CALLBACK(invalidate), display, 0);spice_g_signal_connect_object(channel, "display-mark",  G_CALLBACK(mark), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED);if (spice_display_get_primary(channel, 0, &primary)) {primary_create(channel, primary.format, primary.width, primary.height,   primary.stride, primary.shmid, primary.data, display);mark(display, primary.marked);}spice_channel_connect(channel);spice_main_set_display_enabled(d->main, get_display_id(display), TRUE);return;} // input 通道, 处理鼠标键盘事件if (SPICE_IS_INPUTS_CHANNEL(channel)) {d->inputs = SPICE_INPUTS_CHANNEL(channel);spice_channel_connect(channel);return;} // handle other channels, e.g. curse, usb-redirreturn;}

下面是 invalidate 的实现.

static void invalidate(SpiceChannel *channel,gint x, gint y, gint w, gint h, gpointer data){AndroidDisplay *display = data;AndroidDisplayPrivate *d = ANDROID_DISPLAY_GET_PRIVATE(display);android_pd = d;/* 正如上面讲到的, 这个是重绘的处理函数, 不同的平台这里可能会不同.
       如果是 gtk: 应该调用相应的 gtk_widget_queue_draw_area() 等函数通知上层
       UI 更新相应的显示区域. 因为这里 android 没有 gtk, 所以这里使用了 java
       的 JNI 技术通知 Android UI 需要重绘某些区域, 把控制权交给 Android UI. */spice_callback("OnGraphicsUpdate", "(IIII)V", x, y, w, h); /* 另外提醒一点, 所有的 bitmap 数据都存放在 d->data 里, 所以真正重绘的时候,
       可以从这里取数据, 如果后端的渲染是 cairo 的话, 每次 "display-primary-create
       发生的时候,  spice-gtk 会使用 cairo_image_surface_create_for_data()
       把 d->data 映射到它的画板(canvas)上去. */ return ;}

如果要写 IOS 或者 Android 客户端的话, 应该采用这种方式.

不依赖于其它模块

如果没有 glib 用, 或者其它的什么原因, 那么只能从头自己实现. spice-html5 就是 这么做的, 因为这是浏览器上运行的客户端, 编程语言是 javascript, 也没有 glib.

小玩具

如果对 spice-client 的渲染了如指掌的话, 可以做一些小 hack, 比如我喜欢用酷狗 来听音乐, 因为它的歌词显示得比较好, 但是我用的是 linux 操作系统, 酷狗音乐没有 linux 的版本. 所以如果能只显示虚拟机里面的酷狗歌词到我的 linux 桌面, 那也蛮帅的.

下面是相关的实现代码(trick and ugly):

// file: spice-widget.cstatic void invalidate(SpiceChannel *channel,   gint x, gint y, gint w, gint h, gpointer data){// ......if (d->ad_drawing_area) {// 我在 SpiceDisplayPrivate 定义了一个 GtkWidget *ad_drawing_area 用来// 画歌词的窗口区域, hard codegtk_widget_queue_draw_area(d->ad_drawing_area, 0, 0, 1024, 113);}} // file: spice-widget-cairo.cint spicex_image_create(SpiceDisplay *display){// other codeif (d->ad_drawing_area) {// 我用歌词窗口的信息来创建在 linux 上的窗口的 bitmap 数据.d->ad_ximage = cairo_image_surface_create_for_data((guint8 *)d->data + 1024 * 446 * 4, CAIRO_FORMAT_RGB24, 1024, 113, 1024 * 4);}return 0;} // file: spicy.cstatic gboolean ad_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque){int ww, wh;SpiceDisplayPrivate *d = opaque; 
    ww = gdk_window_get_width(gtk_widget_get_window(widget));wh = gdk_window_get_height(gtk_widget_get_window(widget)); 
    cairo_rectangle(cr, 0, 0, ww, wh); 
    cairo_set_source_surface(cr, d->ad_ximage, 0, 0);cairo_paint(cr); return TRUE;} static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *channel, int id, gint monitor_id){// other codesd->ad_drawing_area = gtk_drawing_area_new();// 注册 draw 信号, 当 gtk_widget_queue_draw_area() 被调用的时候,// 会触发 draw 信号, 表示某些区域需要被更新.g_signal_connect(d->ad_drawing_area, "draw", G_CALLBACK(ad_draw_event), d);// other codes}

附上截图一张 kugou-geci-redir

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多