spice vdagent是怎么與server和client交互的
虛擬串口
vdagent運行在guest os(虛擬機)中玻募,用于確保特性功能能正常運行坤学。guest中所有 vdagent 通信都通過單個管道運行通殃,該管道作為 virtio 串行端口呈現(xiàn)給guest操作系統(tǒng)咱枉。
\\\\.\\Global\\com.redhat.spice.0 //widnows下
/dev/virtio-ports/com.redhat.spice.0 //linux下
qemu可以使用以下參數(shù)啟用 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
數(shù)據(jù)塊
vdagent可以通過virtio串口給spice-server發(fā)送數(shù)據(jù)冤灾,但是可以同時接收來自spice-server和spice-client的數(shù)據(jù),為了區(qū)別數(shù)據(jù)來源烤蜕,所以定義了如下數(shù)據(jù)頭:
enum {
VDP_CLIENT_PORT = 1,
VDP_SERVER_PORT,
};
typedef struct SPICE_ATTR_PACKED VDIChunkHeader {
uint32_t port; //上面的枚舉值
uint32_t size; //sizeof(VDIChunkHeader)+后續(xù)數(shù)據(jù)大小
} VDIChunkHeader;
當vdagent接收消息時封孙,port字段標識數(shù)據(jù)來自何方,當發(fā)送數(shù)據(jù)時讽营,port字段標識數(shù)據(jù)發(fā)送給誰虎忌。spice-server會在收到消息時解析VDIChunkHeader,根據(jù)port字段來決定處理橱鹏、轉(zhuǎn)發(fā)或丟棄膜蠢,值得注意的是,目前spice中沒有vdagent給server發(fā)送的消息莉兰,所以來自vdagent的port=VDP_SERVER_PORT的消息將被丟棄挑围。
消息結(jié)構(gòu)體
vdagent發(fā)送和接收的消息用VDAgentMessage結(jié)構(gòu)體封裝
enum {
VD_AGENT_MOUSE_STATE = 1, //鼠標模式
VD_AGENT_MONITORS_CONFIG, //顯示器信息
VD_AGENT_REPLY, //回復消息
VD_AGENT_CLIPBOARD, //剪切板消息
VD_AGENT_DISPLAY_CONFIG, //顯示性能相關(guān)配置
VD_AGENT_ANNOUNCE_CAPABILITIES, //能力同步
.... //可以自定義類型
VD_AGENT_END_MESSAGE,
};
typedef struct SPICE_ATTR_PACKED VDAgentMessage {
uint32_t protocol; //始終是VD_AGENT_PROTOCOL
uint32_t type; //上述枚舉值,標識message類型
uint64_t opaque;
uint32_t size;
uint8_t data[0];
} VDAgentMessage;
opaque字段是一個占位符贮勃,當只需要發(fā)送一個int值時使用贪惹,如果有payload數(shù)據(jù)則為0苏章,數(shù)據(jù)用data字段發(fā)送寂嘉。
size字段是data的大小。注意VDIChunkHeader 的size = sizeof(VDIChunkHeader) + 后續(xù)數(shù)據(jù)大小 = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + variable_data_len枫绅。
data 是可變長度數(shù)據(jù)的開始泉孩,它的內(nèi)容取決于type字段 ,對于大多數(shù)消息來說并淋,它是一個消息類型特定的結(jié)構(gòu)寓搬,例如 VDAgentMouseState。請注意县耽,數(shù)據(jù)被聲明為 0 大小的數(shù)組句喷,這意味著它在結(jié)構(gòu)中不占用任何大小,它只是為了能夠輕松確定數(shù)據(jù)的起始地址兔毙。
消息類型
鼠標模式(VD_AGENT_MOUSE_STATE)
spice支持兩種鼠標模式唾琼,客戶端模式和服務端模式。
在服務端模式下澎剥,當用戶在 Spice 客戶端窗口內(nèi)點擊時,客戶端鼠標被捕獲,然后將增量坐標發(fā)送給server端敛腌,鼠標通過QEMU ps/2 仿真,guest內(nèi)不會收到消息芜茵。
在客戶端模式下,客戶端發(fā)送鼠標絕對坐標給guest倡蝙,這個坐標可以使用 USB 模擬九串,或直接發(fā)送到 vdagent,來告知guest操作系統(tǒng)鼠標位置(和按鈕點擊)寺鸥。
spice-server處理所有的的鼠標操作蒸辆,只有在客戶端模式下,server才會向client發(fā)送VD_AGENT_MOUSE_STATE消息析既,消息結(jié)構(gòu)體如下:
typedef struct SPICE_ATTR_PACKED VDAgentMouseState {
uint32_t x;
uint32_t y;
uint32_t buttons;
uint8_t display_id;
} VDAgentMouseState;
顯示器配置(VD_AGENT_MONITORS_CONFIG)
當client運行在全屏自適應模式下時躬贡,客戶端發(fā)送連接到客戶端機器上的顯示器信息給vdagent,當 vdagent收到消息時眼坏,將重新配置qxl設備的outputs拂玻,并盡可能匹配消息給出的配置。
typedef struct SPICE_ATTR_PACKED VDAgentMonitorsConfig {
uint32_t num_of_monitors; //顯示器數(shù)量
uint32_t flags; //宰译?檐蚜??
VDAgentMonConfig monitors[0]; //每個顯示器的配置沿侈,如下述結(jié)構(gòu)體
} VDAgentMonitorsConfig;
typedef struct SPICE_ATTR_PACKED VDAgentMonConfig {
uint32_t height;
uint32_t width;
uint32_t depth;
int32_t x;
int32_t y;
} VDAgentMonConfig;
當代理完成輸出配置后闯第,它應該在給客戶端回復 VD_AGENT_REPLY 消息來通知配置結(jié)果,類型設置為 VD_AGENT_MONITORS_CONFIG 缀拭,錯誤設置為 VD_AGENT_SUCCESS 或 VD_AGENT_ERROR 咳短。
顯示效果配置(VD_AGENT_DISPLAY_CONFIG)
顯示效果消息由 spice 客戶端發(fā)送到 vdagent,以通知它任何與性能相關(guān)的特殊設置蛛淋×茫客戶端可以要求 vdagent 禁用來賓操作系統(tǒng)的各種功能,例如字體抗鋸齒以提高性能褐荷。vdagent 應該盡可能滿足勾效。因為大多數(shù)設置都是以 Windows 為中心的,除非真的出了問題叛甫,否則應該使用 VD_AGENT_REPLY 返回成功狀態(tài)层宫。
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),
};
typedef struct SPICE_ATTR_PACKED VDAgentDisplayConfig {
uint32_t flags; //??
uint32_t depth; //??
} VDAgentDisplayConfig;
回復消息(VD_AGENT_REPLY)
該消息由vdagent回復給client,以指示消息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,
};
能力同步(VD_AGENT_ANNOUNCE_CAPABILITIES)
能力同步消息萌腿,有client和vdagent相互發(fā)送給對方,用于通知雙方已實現(xiàn)的功能棠赛,此消息的目的是允許不同的客戶端和 vdagent 版本一起工作哮奇。
enum {
VD_AGENT_CAP_MOUSE_STATE = 0,
VD_AGENT_CAP_MONITORS_CONFIG,
VD_AGENT_CAP_REPLY,
VD_AGENT_CAP_CLIPBOARD,
VD_AGENT_CAP_DISPLAY_CONFIG,
VD_AGENT_CAP_CLIPBOARD_BY_DEMAND,
VD_AGENT_CAP_CLIPBOARD_SELECTION,
VD_AGENT_CAP_SPARSE_MONITORS_CONFIG,
VD_AGENT_CAP_GUEST_LINEEND_LF,
VD_AGENT_CAP_GUEST_LINEEND_CRLF,
VD_AGENT_CAP_MAX_CLIPBOARD,
VD_AGENT_CAP_AUDIO_VOLUME_SYNC,
VD_AGENT_CAP_MONITORS_CONFIG_POSITION,
VD_AGENT_CAP_FILE_XFER_DISABLED,
VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS,
VD_AGENT_CAP_GRAPHICS_DEVICE_INFO,
VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB,
VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL,
VD_AGENT_END_CAP,
};
typedef struct SPICE_ATTR_PACKED VDAgentAnnounceCapabilities {
uint32_t request;
uint32_t caps[0];
} VDAgentAnnounceCapabilities;
request字段是一個bool值膛腐,表示是否希望接收方回復capabilities,在初始化時應該設置為true鼎俘,在回復時應該設為false哲身。
caps 字段保存能力位的可變長度數(shù)組的開頭,該數(shù)組的長度可以使用 VDAgentMessage msg 大小成員上的 VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE 宏來確定贸伐。能力枚舉在 VD_AGENT_CAP 中定義勘天,可以使用 VD_AGENT_HAS_CAPABILITY 和 VD_AGENT_SET_CAPABILITY 宏來測試/設置陣列中的功能位。
guest或client都不應該發(fā)送對方未宣布為支持的能力相關(guān)聯(lián)的消息類型捉邢。舊版本不支持通知能力脯丝,因此在收到通知能力消息之前,假定以下能力所有版本都支持:
VD_AGENT_CAP_MOUSE_STATE
VD_AGENT_CAP_MONITORS_CONFIG
VD_AGENT_CAP_REPLY
剪切板相關(guān)消息
spice剪切板用于在guest和client之間拷貝數(shù)據(jù)伏伐,前提是guest和client都需要實現(xiàn)VD_AGENT_CAP_CLIPBOARD_SELECTION功能宠进,guest和client都能對剪切板進行如下操作,這些操作通過后面括號中的消息觸發(fā)藐翎。
申明所有權(quán)(VD_AGENT_CLIPBOARD_GRAB)
釋放所有權(quán)(VD_AGENT_CLIPBOARD_RELEASE)
請求數(shù)據(jù)(VD_AGENT_CLIPBOARD_REQUEST)
發(fā)送數(shù)據(jù)(VD_AGENT_CLIPBOARD)
接下來以guest->client拷貝文本為例材蹬,反向的邏輯是相同的:
- 虛擬機中agent會監(jiān)聽系統(tǒng)事件,當監(jiān)聽到復制事件時吝镣,會觸發(fā)回調(diào)向client端發(fā)送VD_AGENT_CLIPBOARD_GRAB消息堤器,獲取剪切板的所有權(quán)。
- client端接收到GRAB消息之后末贾,會發(fā)送通過信號觸發(fā)clipboard_grab回調(diào)闸溃,回調(diào)中會調(diào)用gtk的gtk_clipboard_set_with_owner接口注冊剪切板的clipboard_get和clipboard_clear操作,并設置owner用于監(jiān)聽粘貼操作拱撵。
- 當客戶端有粘貼操作時辉川,會觸發(fā)clipboard_get回調(diào),回調(diào)中先連接VD_AGENT_CLIPBOARD消息的回調(diào)clipboard_got_from_guest裕膀,然后向guest發(fā)送VD_AGENT_CLIPBOARD_RELEASE消息员串,請求剪切板數(shù)據(jù),然后會啟動一個mainloop等待數(shù)據(jù)到來昼扛。
- guest收到VD_AGENT_CLIPBOARD_RELEASE消息時,就會通過VD_AGENT_CLIPBOARD消息將剪切板數(shù)據(jù)發(fā)送給client欲诺。
- client接收到消息抄谐,會調(diào)到clipboard_got_from_guest,將數(shù)據(jù)拷貝到系統(tǒng)剪切板扰法。
- 然后在系統(tǒng)事件循環(huán)中會將系統(tǒng)剪切板的數(shù)據(jù)拷貝到目標組件蛹含。
至此完成一次粘貼操作。
VD_AGENT_CLIPBOARD_GRAB
獲取clipboard所有權(quán)
typedef struct SPICE_ATTR_PACKED VDAgentClipboardGrab {
#if 0 /* VD_AGENT_CAP_CLIPBOARD_SELECTION */
uint8_t selection;
uint8_t __reserved[sizeof(uint32_t) - 1 * sizeof(uint8_t)];
#endif
#if 0 /* VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL */
uint32_t serial;
#endif
uint32_t types[0];
} VDAgentClipboardGrab;
VD_AGENT_CLIPBOARD_RELEASE
釋放clipboard所有權(quán)塞颁。
typedef struct SPICE_ATTR_PACKED VDAgentClipboardRelease {
#if 0 /* VD_AGENT_CAP_CLIPBOARD_SELECTION */
uint8_t selection;
uint8_t __reserved[sizeof(uint32_t) - 1 * sizeof(uint8_t)];
#endif
uint8_t dummy_empty_field[0]; /* C/C++ compatibility */
} VDAgentClipboardRelease;
VD_AGENT_CLIPBOARD_REQUEST
請求clipboard數(shù)據(jù)浦箱,type指定數(shù)據(jù)格式吸耿。
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 */
};
typedef struct SPICE_ATTR_PACKED VDAgentClipboardRequest {
#if 0 /* VD_AGENT_CAP_CLIPBOARD_SELECTION */
uint8_t selection;
uint8_t __reserved[sizeof(uint32_t) - 1 * sizeof(uint8_t)];
#endif
uint32_t type;
} VDAgentClipboardRequest;
VD_AGENT_CLIPBOARD
發(fā)送clipboard數(shù)據(jù),type為數(shù)據(jù)格式酷窥,data是數(shù)據(jù)咽安。除非收到VD_AGENT_CLIPBOARD_REQUEST 請求數(shù)據(jù),否則不應發(fā)送數(shù)據(jù)蓬推,以避免浪費帶寬妆棒。要傳輸?shù)募糍N板數(shù)據(jù)很大是很常見的,在這種情況下沸伏,消息會被分成幾個 VD_AGENT_MESSAGE糕珊。
struct VDAgentClipboard {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
uint8_t selection;
uint8_t __reserved[3];
endif
uint32_t type;
uint8_t data[0];
};