Spice Agent Protocol (对官网文档的翻译及一些个人理解)

vdagent virtio serial port

在客户机(guest)端的所有虚拟通信都将通过单个管道运行,该管道作为virtio串行端口呈现给客户机系统。 在windows下,这个virtio串口有以下名称:

\\\\.\\Global\\com.redhat.spice.0

在Linux下,这个virtio串口有以下名称:

/dev/virtio-ports/com.redhat.spice.0

当设置如下参数时,Qemu将启用virtio串行端口。

-device virtio-serial-pci,id=virtio-serial0,max_ports=16,bus=pci.0,addr=0x5 -chardev spicevmc,name=vdagent,id=vdagent -device virtserialport,nr=1,bus=virtio-serial0.0,chardev=vdagent,name=com.redhat.spice.0

vdagent data chunks / ports

vdagent 通过virtio串行端口与spice 服务端连接,可以发送数据到spice服务器和spice客户端,也可以接收它们发过来的数据。为了使这成为可能,所有在vdagent virtio串行端口中的数据都要以VDIChunkHeader作为前缀,既消息头。

typedef struct SPICE_ATTR_PACKED VDIChunkHeader {
  uint32_t port;
  uint32_t size;
} VDIChunkHeader;

其中,size是发送或接收到的数据的大小,包括可变数据部分的大小,换句话说,size=sizeof(VDAgentMessage)+variable_data_len。(variable_data_len指的是可变长度数据的大小)

元素port的值则是以下其中之一:

enum {
    VDP_CLIENT_PORT = 1,
    VDP_SERVER_PORT,
};

当vdagent接收消息,元素port的值指示消息来自哪里。当vdagent发送消息,port表示消息的接收方。当要发送的消息是对已接收消息的应答消息,port的值应与已接收到的消息中的port值一致,要发送的消息是对其的回复。

当spice服务器接收到一条消息时,服务器会去掉消息中的chunk头(即VDIChunkHeader),然后根据在chunk头中的port数据,来决定是将数据直接发给客户端还是自己进行处理,或者,如果port值是一个无效值,则会记录错误并且丢弃该消息。

注意,目前还没有来自Agent的消息是针对服务器的,因此所有agent发送的port值为VDP_SERVER_PORT的消息都会被服务器默默的删除掉。

vdagent message struct

Agent发送或接收到的消息都被封装到一个叫VDAgentMessage的结构体中:

typedef struct SPICE_ATTR_PACKED VDAgentMessage {
  uint32_t protocol;
  uint32_t type;
  uint64_t opaque;
  uint32_t size;
  uint8_t data[0];
} VDAgentMessage;

其中,protocol元素的值始终都是VD_AGENT_PROTOCOL。

type元素的值来自以下的枚举值:

enum {
    VD_AGENT_MOUSE_STATE = 1,
    VD_AGENT_MONITORS_CONFIG,
    VD_AGENT_REPLY,
    VD_AGENT_CLIPBOARD,
    VD_AGENT_DISPLAY_CONFIG,
    VD_AGENT_ANNOUNCE_CAPABILITIES,
    VD_AGENT_CLIPBOARD_GRAB,
    VD_AGENT_CLIPBOARD_REQUEST,
    VD_AGENT_CLIPBOARD_RELEASE,
    VD_AGENT_FILE_XFER_START,
    VD_AGENT_FILE_XFER_STATUS,
    VD_AGENT_FILE_XFER_DATA,
    VD_AGENT_CLIENT_DISCONNECTED,
    VD_AGENT_MAX_CLIPBOARD,
    VD_AGENT_END_MESSAGE,
};

元素opaque是消息类型的占位符,只需要将单个整数作为消息数据传递,对于具有更多数据的消息类型,opaque值始终设置为0.

元素size指的是可变长度数据的大小。注意,在virtio端口中的完整数据的大小等于sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + variable_data_len. (variable_data_len指的是可变长度数据的大小,即VDAgentMessage中的size值)。

data是可变长度数据的起始地址。可变长度数据的内容取决于数据类型,对于大多数消息,它是一种数据类型特定的结构体,如VDAgentMouseSate。注意,元素data声明为0大小的数组,意味着该元素不会占用该结构体的任何内存,而且能轻松确定数据的起始地址。

 

vdagent messages

VD_AGENT_MOUSE_STATE

spice支持两种鼠标模式,server和client。

在server模式下,QEMU ps/2鼠标仿真用于向客户机(Guest)发送鼠标状态。当用户点击进spice客户端窗口时,客户端鼠标被捕获,且客户端将鼠标移动动作作为增量坐标发送到客户机(Guest)。

在client模式下,鼠标坐标位置作为绝对值发送给客户机。这需要用到usb表仿真,或者需要将它们发送到vdagent,再由vdagent通知客户机系统鼠标的位置(以及按钮点击)。

当鼠标处于client模式时,spice服务器会发送包含了鼠标状态更新的VD_AGENT_MOUSE_STATE消息。这些消息的变量数据由以下结构组成:

typedef struct SPICE_ATTR_PACKED VDAgentMouseState {
  uint32_t x;
  uint32_t y;
  uint32_t buttons;
  uint8_t display_id;
} VDAgentMouseState;

请注意,这些消息是由spice 服务器发送的,不是由spice 客户端发送的,因为服务器执行所有的鼠标处理(如在vdagent连接/断开连接时切换client模式或server模式)。

VD_AGENT_MONITORS_CONFIG

当客户端以全屏自动配置模式运行时,这个类型的消息将会被发送到Agent。该消息包含连接到客户端机器的显示器的信息。当收到该类型消息时,agent会尽可能的重新配置客户机中的qxl vga设备的输出,来匹配消息中的输出。例如,如果客户端具有的输出比在虚拟机中配置的更多,那么已有的输出应该被重新配置以匹配消息中的内容。

这些消息的变量数据由以下结构组成:

typedef struct SPICE_ATTR_PACKED VDAgentMonitorsConfig {
  uint32_t num_of_monitors;
  uint32_t flags;
  VDAgentMonConfig monitors[0];
} VDAgentMonitorsConfig

紧跟着该消息后面的是num_of_moniters个以下结构体:

typedef struct SPICE_ATTR_PACKED VDAgentMonConfig {
  uint32_t height;
  uint32_t width;
  uint32_t depth;
  int32_t x;
  int32_t y;
} VDAgentMonConfig;

当agent完成了输出的配置,agent应该返回VD_AGENT_REPLY消息,该消息中的type值设置为VD_AGENT_MONITORS_CONFIG,error值设置为VD_AGENT_SUCCESS或者VD_AGENT_ERROR,以指示配置输出是成功或是错误。

VD_AGENT_REPLY

该消息由vdagent发送给client,表示vdagent已经完成了对VD_AGENT_MONITORS_CONFIG 或者 VD_AGENT_DISPLAY_CONFIG消息的处理,以及处理成功还是失败。

typedef struct SPICE_ATTR_PACKED VDAgentReply {
  uint32_t type;
  uint32_t error;
} VDAgentReply;

enum {
  VD_AGENT_SUCCESS = 1,
  VD_AGENT_ERROR,
};

 

Clipboard(剪贴板)

spice允许spice客户端(client)与客户机(guest)之间通过客户机中的agent来共享剪贴板数据。

为此,客户机上的agent和客户端起着对称的作用:它们都可以声明拥有权(GRAB),释放拥有权(RELEASE),请求剪贴板数据(REQUEST)以及发送剪贴板数据。例如,当在某个应用中进行了copy的操作之后收到了剪贴板数据可用的系统通知,GRAB消息就会发送给对方(当任意一方收到了GRAB消息,就可以请求数据,若事先没有收到对方发来的GRAB消息的话,不会主动request数据)。当剪贴板清空时,grab信息必须被释放(release)掉。

另一端在grab处于活动状态时可以请求数据,之后会收到对方回复的带有剪贴板信息的CLIPBOARD消息(即对方发来REQUEST请求的话,就要返回CLIPBOARD类型数据)。

剪贴板数据类型如下:

enum {
  VD_AGENT_CLIPBOARD_NONE = 0,
  VD_AGENT_CLIPBOARD_UTF8_TEXT,
  VD_AGENT_CLIPBOARD_IMAGE_PNG,  /* All clients with image support should support this one */
  VD_AGENT_CLIPBOARD_IMAGE_BMP,  /* optional */
  VD_AGENT_CLIPBOARD_IMAGE_TIFF, /* optional */
  VD_AGENT_CLIPBOARD_IMAGE_JPG,  /* optional */
};

如果双方都实现了VD_AGENT_CAP_CLIPBOARD_SELECTION的功能,clipboard信息前面都要带有一个uint8_t的值,指示要操作的clipboard selection。关于clipboard selection,该协议末尾有关于VD_AGENT_CAP_CLIPBOARD_SELECTION的文档解释。

VD_AGENT_CLIPBOARD

struct VDAgentClipboard {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
  uint8_t selection;
  uint8_t __reserved[3];
endif
  uint32_t type;
  uint8_t data[0];
};

VD_AGENT_CLIPBOARD 用于发送剪贴板数据。除非接收到VD_AGENT_CLIPBOARD_REQUEST 的数据请求,否则这个数据不会被发送,以避免浪费宽带。被传送的剪贴板数据通常来说都挺大的,在这种情况下,可以预期到,要传送的数据是要分割成多个VD_AGENT_MESSAGE发送的。

其中,type值是剪贴板数据类型,即前面剪贴板数据枚举类型中的一个。

data是可变长度数据的起始地址。在VD_AGENT_CLIPBOARD类型的消息中,data指向的应该是存放具体的剪贴板数据内容的内存的首地址。注意,元素data声明为0大小的数组,意味着该元素不会占用该结构体的任何内存,而且能轻松确定数据的起始地址。

VD_AGENT_CLIPBOARD_REQUEST

struct VDAgentClipboardRequest {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
  uint8_t selection;
  uint8_t __reserved[3];
endif
  uint32_t type;
};

请求具有指定类型的剪贴板数据。

type值是剪贴板数据类型,即前面剪贴板数据枚举类型中的一个。

VD_AGENT_CLIPBOARD_GRAB

struct VDAgentClipboardGrab {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
  uint8_t selection;
  uint8_t __reserved[3];
endif
  uint32_t types[0];
};

抓取剪贴板。个人理解为声明剪贴板拥有权。发出grab消息的A方声明了B方的剪贴板的拥有权,当B方要访问B方自己的剪贴板时,将会向发出grab消息的A方发送VD_AGENT_CLIPBOARD_REQUEST类型数据,A方接收到REQUEST信息后,就会发送CLIPBOARD信息给B方,且CLIPBOARD中的数据类型就是该GRAB中的types中的类型。

VD_AGENT_CLIPBOARD_RELEASE

struct VDAgentClipboardRelease {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
  uint8_t selection;
  uint8_t __reserved[3];
endif
};

释放剪贴板。(例如当剪贴板变空时)

重要:

如果一条GRAB信息已被发送并且在当前处于活动状态,然后又从对方那里接收到连续的GRAB信息,这时不应该发送RELEASE信息给对方来取消上一条发过去的grab。因为那条处于活动状态的grab已经被对方暗中释放掉了。如果给对方发送额外的RELEASE信息,只会使对方感到困惑。

VD_AGENT_DISPLAY_CONFIG

该类型数据被spice客户端发送给vdagent,来通知vdagent一些特殊性能的相关设置。客户端可以要求vdagent禁用客户机系统的的许多功能,如字体反混叠等,以提高性能。vdagent可以在这些方面做一些努力,尤其是因为大多数设置都是以windows为中心的。应该使用VD_AGENT_REPLY 返回成功状态,除非出现问题。(这个类型数据没仔细看过)

typedef struct SPICE_ATTR_PACKED VDAgentDisplayConfig {
  uint32_t flags;
  uint32_t depth;
} VDAgentDisplayConfig;

enum {
  VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER = (1 << 0),
  VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH = (1 << 1),
  VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION = (1 << 2),
  VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH = (1 << 3),
};

VD_AGENT_ANNOUNCE_CAPABILITIES

该数据类型可以(并且应该)被spice客户端和vdagent发送。它宣布了发送方将会发送哪些信息/能力(一个capability可以包含多条信息)或者当收到这些数据时知道怎么处理。此消息的目的是允许不同版本的客户端和vdagent能一起工作。

typedef struct SPICE_ATTR_PACKED VDAgentAnnounceCapabilities {
  uint32_t  request;
  uint32_t caps[0];
} VDAgentAnnounceCapabilities;

其中,request字段是一个布尔值,指示了信息的接收者是否需要回复一个 VD_AGENT_ANNOUNCE_CAPABILITIES 的信息,因为发送者可能也想知道接收者的capabilities。在最开始发起capabilities交换时这个应该设置为true,当发送一个宣告capabilities作为对所接收到的信息的应答时,该值设置为false。

结构体中的caps成员是一个可变长度数组的首地址。该数组的长度可以使用VDAgentMessage结构体中的size成员上的VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE 宏来确定。不同capabilities 的索引位于定义在VD_AGENT_CAP 常量的枚举中。并且有VD_AGENT_HAS_CAPABILITY和VD_AGENT_SET_CAPABILITY宏来测试/设置数组中的capability 位。

一方不应该发送另一方不支持的功能相关联的类型的信息。旧版本不支持announce capabilities,因此,直到收到announce capabilities信息,应该假设以下功能(所有版本都支持):

VD_AGENT_CAP_MOUSE_STATE
VD_AGENT_CAP_MONITORS_CONFIG
VD_AGENT_CAP_REPLY

VD_AGENT_CAP_CLIPBOARD_SELECTION

当客户端和服务器都具有selection capability,VDAgentClipboard信息必须在前面加上一个uint8_t,该uint8_t指示哪个 clipboard selection 可以操作+3个字节填充以供未来功能或扩展使用。

根据X11 / Gtk方案定义了几个剪贴板选择:

  • VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD: 默认剪贴板,由大多数操作系统实现,以处理显式的复制/粘贴操作。
  • VD_AGENT_CLIPBOARD_SELECTION_PRIMARY: PRIMARY剪贴板,用于鼠标选择。
  • VD_AGENT_CLIPBOARD_SELECTION_SECONDARY: the SECONDARY clipboard.

转载于:https://my.oschina.net/amui/blog/869556

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值