Wayland從入門(mén)到入土P0---HelloWorld
聲明
這個(gè)文章就是寫(xiě)著玩的,純Linux新手。本人不對(duì)文章的準(zhǔn)確性做任何負(fù)責(zé),僅以讀后感的形式分享。很多紕漏還請(qǐng)多多交流
引入
Hmmm,怎么說(shuō)呢,X11命不久矣,也就還剩一口氣,現(xiàn)在是Wayland的時(shí)代力(really?)。目前不少Linux應(yīng)用都開(kāi)始原生支持Wayland,包括最近非常令人興奮的Wine的原生Wayland支持,消除XWayland的開(kāi)銷以后應(yīng)該能提升不少性能。
所以想著想著,我覺(jué)得也確實(shí)有必要深入學(xué)習(xí)一下Wayland的工作原理了,說(shuō)不定還能幫助開(kāi)源社區(qū)遷移一些軟件到Wayland上(現(xiàn)在是幻想時(shí)間)。
主要資料
[The Wayland Book] (https://wayland-book.com/):幫助很大,Wayland的初級(jí)工作原理就是從這里學(xué)的。
[Linux Man Pages Online] (https://www.man7.org/linux/man-pages/):在線查L(zhǎng)inux的API手冊(cè),同樣幫助很大,因?yàn)樯婕暗絻?nèi)存操作。
[Wayland Official Document] (https://wayland.freedesktop.org/docs/html/):Wayland官方的簡(jiǎn)單手冊(cè)。
初步認(rèn)識(shí)Wayland
X11/Wayland只是一個(gè)協(xié)議,并非具體實(shí)現(xiàn)
這個(gè)很好理解:
? ?1.C語(yǔ)言標(biāo)準(zhǔn) vs C語(yǔ)言編譯器的具體實(shí)現(xiàn)(GCC等)
? ?2.英語(yǔ)語(yǔ)言和語(yǔ)法 vs 用英語(yǔ)寫(xiě)成的文章
說(shuō)白了,Wayland只是告訴大家:“實(shí)現(xiàn)的時(shí)候這樣搞,我們就可以通用力”,包括X11也是一樣的。
那么具體實(shí)現(xiàn)在哪里?
具體實(shí)現(xiàn)就是Linux的整個(gè)圖形棧,從下到上就是DRM---Driver---Compositer---Protocol(Wayland和X11在這里)
說(shuō)人話就是:我們通過(guò)特定的語(yǔ)言(Wayland或者X11的規(guī)定)和Compositer(混合器)交流。
除了Wayland官方的參考設(shè)計(jì)---合成器Weston以外,更常用的混合器有Gnome的Mutter,KDE的KWin等。
邊看代碼邊讀手冊(cè)
我不喜歡對(duì)著文檔一通分析,這種東西就應(yīng)該直接上手,然后RTFSC和RTFM。
前置任務(wù)
[南京大學(xué)操作系統(tǒng)課程]:JYY永遠(yuǎn)的神。
[Wayland Hello World] (https://github.com/emersion/hello-wayland):Wayland官方的簡(jiǎn)單Hello World。
直接開(kāi)搞
連接服務(wù)器
Wayland是一個(gè)Client---Host模型為基礎(chǔ)的顯示協(xié)議,我們要顯示的窗口就是Client,混成器為Host
跳到最底下的main函數(shù)
struct wl_display *display = wl_display_connect(NULL);
一上來(lái)就用了個(gè)結(jié)構(gòu)體指針,從名字大概可以猜出這是初始化一個(gè)顯示對(duì)象。合情合理,想要顯示東西,那總得找個(gè)地方罷。
**但是,這里的display和我們電腦的顯示器沒(méi)啥關(guān)系,名字有點(diǎn)誤導(dǎo)**
if (display == NULL)
{
? fprintf(stderr, "failed to create display\n");
? return EXIT_FAILURE;
}
如果連接不上服務(wù)器的話,就會(huì)得到空指針。那就直接結(jié)束退出咯
RTFM
struct wl_display wl_display_connect(const char * name)
參數(shù):字符串name的指針
用處:連接指定的Wayland服務(wù)器,一般只有一個(gè)(即wayland-0),留NULL則按照以下順序嘗試:
? ?1.讀取$WAYLAND_DISPLAY
? ?2.無(wú)此變量或連接失敗則嘗試wayland-0
? ?3.無(wú)wayland-0直接失敗退出
Wayland服務(wù)器不是顯示屏,和屏幕數(shù)量無(wú)關(guān),Wayland服務(wù)器一般就是Compositer。
注冊(cè)
這里就上點(diǎn)強(qiáng)度了,三行代碼對(duì)應(yīng)的具體實(shí)現(xiàn)占了main.c的一大半
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display);
RTFM
**先說(shuō)明:我對(duì)這地方也是一知半解的狀態(tài),因?yàn)楣俜轿臋n在這里寫(xiě)的也有點(diǎn)迷糊.....**
前面我們說(shuō)過(guò)(嗎?),Wayland協(xié)議中,規(guī)定了Client<-->Host的通信模型,我們的窗口就是Client,Compositer就是Host。
不難想象,兩邊應(yīng)該有這樣一個(gè)過(guò)程:Host和Client都要告訴對(duì)面自己支持哪些操作。假如一款支持光追的游戲恰好運(yùn)行在1080Ti上,但是他不知道這塊顯卡不支持光追,如果開(kāi)啟就很可能會(huì)得到一個(gè)SEGFALUT(不過(guò)現(xiàn)代API的容錯(cuò)率應(yīng)該很高)
那現(xiàn)在回頭看看這三行代碼都干了啥:
struct wl_registry *registry = wl_display_get_registry(display);
輸入:之前得到的指向服務(wù)器的結(jié)構(gòu)體指針
作用:獲取對(duì)應(yīng)服務(wù)器支持的所有操作接口(interface),返回一個(gè)結(jié)構(gòu)體指針。下面是在我的配置下,Mutter所支持的interfaces:
interface: 'wl_compositor', version: 5, name: 1
interface: 'wl_drm', version: 2, name: 2
interface: 'wl_shm', version: 1, name: 3
interface: 'wl_output', version: 4, name: 4
interface: 'zxdg_output_manager_v1', version: 3, name: 5
interface: 'wl_data_device_manager', version: 3, name: 6
interface: 'zwp_primary_selection_device_manager_v1', version: 1, name: 7
interface: 'wl_subcompositor', version: 1, name: 8
...
但是這個(gè)函數(shù)的返回值并不是這個(gè)列表,這個(gè)列表是用另一個(gè)程序打印出來(lái)的,后面就慢慢清楚了。
wl_registry_add_listener(registry, ®istry_listener, NULL);
前面我們獲取了服務(wù)器所支持的所有操作列表,那下一步,我們應(yīng)該注冊(cè)我們所需要的函數(shù)。
同樣的,在Client--Host這個(gè)通信模型中,兩邊都可以是說(shuō)話的以及聽(tīng)話的,既然我們現(xiàn)在是編寫(xiě)的Client,所以應(yīng)該告訴Host:“帶我一個(gè)”。如果只有一個(gè)窗口那告不告訴也無(wú)所謂,但是Wayland是針對(duì)多窗口設(shè)計(jì)的協(xié)議,因此作為Client要主動(dòng)一些,和對(duì)面建立聯(lián)系,提出需求。
扯遠(yuǎn)了哈,回到主代碼塊上。
在上面的函數(shù)里,我們傳入了結(jié)構(gòu)體registry_listener的指針,于是跳轉(zhuǎn)到對(duì)應(yīng)結(jié)構(gòu)體:
static const struct wl_registry_listener registry_listener = {
? .global = handle_global,
? .global_remove = handle_global_remove,
};
在這里有一些我沒(méi)見(jiàn)過(guò)的操作:1.變量前的"."是什么?2.handle_***是函數(shù),為啥不加括號(hào)?
Google一番之后找到了答案
1.結(jié)構(gòu)體變量前加"."是為了打破結(jié)構(gòu)體賦值時(shí)的順序限制
資料:[GCC文檔] (http://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html)
2.函數(shù)后不加括號(hào),相當(dāng)于只告訴程序:“早上好程序,現(xiàn)在我有這個(gè)函數(shù),我很喜歡這個(gè)函數(shù)”,而不去調(diào)用它。
畢竟函數(shù)的開(kāi)頭“int/void/... function()”,如果去掉括號(hào),那不就能用對(duì)應(yīng)類型的指針去指向它了嗎,所以就成了函數(shù)指針。
既然我們能把函數(shù)變成指針,肯定也能以指針的形式調(diào)用,下面是個(gè)例子
int max(int a, int b)//具體實(shí)現(xiàn)就不寫(xiě)了
int (*res)(int , int) = max;//max函數(shù)轉(zhuǎn)化為指針,名為res
max_val = (*res)(2,5);//調(diào)用函數(shù)指針的方法
總之,函數(shù)指針還有很多的變化,甚至可以實(shí)現(xiàn)調(diào)用同一個(gè)函數(shù),但是對(duì)應(yīng)原型卻完全不同的操作。
資料:[菜鳥(niǎo)教程---函數(shù)指針] (https://www.runoob.com/cprogramming/c-fun-pointer-callback.html)
對(duì)于第二點(diǎn)還是沒(méi)能搞清楚究竟為啥這樣寫(xiě),還得更深入一些。所以跳轉(zhuǎn)到頭文件里
//wayland-client-protocol.h
void (*global)(void *data,
? ? ? ? ?struct wl_registry *wl_registry,
? ? ? ? ?uint32_t name,
? ? ? ? ?const char *interface,
? ? ? ? ?uint32_t version);
//wayland-client-protocol.h
//main.c
static void handle_global(void *data, struct wl_registry *registry,
? ? ? uint32_t name, const char *interface, uint32_t version) {
? if (strcmp(interface, wl_shm_interface.name) == 0) {
? ? ? shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
? } else if (strcmp(interface, wl_seat_interface.name) == 0) {
? ? ? struct wl_seat *seat =
? ? ? ? ? wl_registry_bind(registry, name, &wl_seat_interface, 1);
? ? ? wl_seat_add_listener(seat, &seat_listener, NULL);
? } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
? ? ? compositor = wl_registry_bind(registry, name,
? ? ? ? ? &wl_compositor_interface, 1);
? } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
? ? ? xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
? }
}
//main.c
根據(jù)上面的說(shuō)法,這里就是把 handle_global 轉(zhuǎn)化成了一個(gè)名字叫 global 的函數(shù)指針。不過(guò)還沒(méi)完成捏,請(qǐng)注意global本身不含任何具體實(shí)現(xiàn),所以還得回到 handle_global。
里面用了一系列的if-else語(yǔ)句加strcmp函數(shù),可以猜測(cè)合成器內(nèi)部會(huì)發(fā)生某種循環(huán)匹配。同時(shí)如果匹配成功,則會(huì)執(zhí)行對(duì)應(yīng)的綁定注冊(cè)函數(shù)
wl_registry_bind(struct wl_registry *wl_registry,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?uint32_t name,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const struct wl_interface *interface,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?uint32_t version)
如果你還記得前面的內(nèi)容,我們成功獲取了Host支持的操作的列表wl_registry,既然菜單有了,接下來(lái)我們的Client就應(yīng)該“點(diǎn)菜”,Host為我們“上菜”。
關(guān)于綁定,可以在(https://wayland-book.com/registry/binding.html)看到更多。
多個(gè)類型相同的操作放在一起成為一組接口(interface),然后為每個(gè)Client所需要的interface分配一個(gè)編號(hào)。因?yàn)镠ost會(huì)管理多個(gè)窗口,為了區(qū)分不同Client所發(fā)出的信號(hào),我們會(huì)給每個(gè)Client的每個(gè)interface分配唯一的編號(hào)。
現(xiàn)在再回去看那段if-else代碼,應(yīng)該不難理解,我們是“按需取用”。你當(dāng)然可以將所有interface都做一個(gè)綁定,但是真的沒(méi)啥必要。所以我們使用函數(shù)指針,作為一個(gè)“鉤子”,當(dāng)且僅當(dāng)在我們需要時(shí)候去執(zhí)行函數(shù)獲取對(duì)應(yīng)的數(shù)據(jù)。
讀代碼讀到現(xiàn)在有很多疑問(wèn),最關(guān)鍵還是在我們究竟是如何觸發(fā)handle_global里的函數(shù)的???(要 來(lái) 力)
Anyway,以上的這一套操作下來(lái),我們實(shí)際上還沒(méi)有和Host進(jìn)行過(guò)真正的通信,這也就是下面這一行代碼要干的:
wl_display_roundtrip(display);
參數(shù):指向?qū)?yīng)服務(wù)器的結(jié)構(gòu)體指針
作用:開(kāi)啟和服務(wù)器之間的通信
**我不懂Wayland在設(shè)計(jì)的時(shí)候有沒(méi)有這個(gè)考量,但從“每一幀都是完美的”設(shè)計(jì)思想以及我之前在單片機(jī)上玩弄U8g2的經(jīng)歷來(lái)看,這種“先準(zhǔn)備好數(shù)據(jù)再發(fā)送”的操作似乎是每個(gè)圖形庫(kù)的基本設(shè)計(jì)原則。**
然后我本來(lái)想在這里插入一些GDB調(diào)試畫(huà)面啥的,但太懶了。
總而言之,在上面的準(zhǔn)備工作完成后,開(kāi)啟與Host的通信會(huì)發(fā)生以下一件事:
遍歷wl_registry中的各項(xiàng)條目(各種支持的interface)--->觸發(fā)handle_global函數(shù),通過(guò)*interface傳入對(duì)應(yīng)的名稱--->進(jìn)行匹配,如果符合,則為Client綁定相對(duì)應(yīng)的interface,返回對(duì)應(yīng)結(jié)構(gòu)體指針。
如果你用GDB調(diào)試的話,就可以看到interface的名字就是上文那個(gè)列表的里的名字,從上到下進(jìn)行遍歷
//In case you miss it
interface: 'wl_compositor', version: 5, name: 1
interface: 'wl_drm', version: 2, name: 2
interface: 'wl_shm', version: 1, name: 3
......
(才發(fā)現(xiàn)自己漏了一個(gè)wl_seat函數(shù),這玩意是用來(lái)處理輸入事件的,沒(méi)有他窗口動(dòng)不了,也沒(méi)法處理鍵盤(pán),這里就不展開(kāi)了,具體見(jiàn)[Seats: Handling input] (https://wayland-book.com/seat.html)。這個(gè)Hello程序用它來(lái)使得窗口能被鼠標(biāo)拖動(dòng)。)
淺淺的講完了這三行代碼。到目前為止,我們完成了以下三件事
1.尋找Host
2.獲取Host支持的interfaces
3.注冊(cè)綁定到自己所需要的interfaces
關(guān)于更多interface的信息,可以在[Wayland Protocol Doc] (https://wayland.app/protocols/)找到。
創(chuàng)建數(shù)據(jù)緩存
經(jīng)過(guò)一個(gè)簡(jiǎn)單的異常處理if語(yǔ)句后,來(lái)到了第二個(gè)核心部分,給數(shù)據(jù)創(chuàng)建緩存。
struct wl_buffer *buffer = create_buffer();
create_buffer函數(shù)的具體實(shí)現(xiàn)如下
? int stride = width * 4;
? int size = stride * height;
前面兩行代碼是用來(lái)計(jì)算所需緩沖區(qū)大小的,計(jì)算公式一般為:長(zhǎng)*寬\*單個(gè)像素大小\*2(這里省略了,只有開(kāi)啟雙緩沖后需要兩倍內(nèi)存),單位byte。
像素大小根據(jù)顏色來(lái)定,下面的宏**WL_SHM_FORMAT_ARGB8888**指定了顏色格式,名字很容易看出來(lái),A、R、G、B四個(gè)通道,每個(gè)通道8bit,那我們需要32bits=4byte的空間保存每個(gè)像素。
int fd = create_shm_file(size);
經(jīng)典中的經(jīng)典fd,接觸過(guò)LinuxAPI編程的人應(yīng)該都會(huì)對(duì)這玩意有條件反射。這也就是為什么我推薦去看JYY的操作系統(tǒng)課作為前置知識(shí)。
本來(lái)是想繼續(xù)深入create_shm_file函數(shù)的,但是懶。所以就粗略解釋一下:
首先Unix哲學(xué),一切皆文件。我們想要在內(nèi)存里面開(kāi)辟一塊區(qū)域出來(lái)放圖片,就需要有這么一個(gè)文件,先把位子給占了。
anonymous_shm_open() 結(jié)合 randname() 嘗試創(chuàng)建一個(gè)名為“hello-wayland-(六位隨機(jī)字符)”的文件,大小就是我們上面計(jì)算得出的size。
然后可能比較迷惑的點(diǎn)就是在創(chuàng)建完成后馬上執(zhí)行銷毀函數(shù)**shm_unlink**。這一開(kāi)始我也很奇怪,但是讀了LinuxAPI文檔之后就解惑了
If one or more references to the shared memory object exist when
? ? ?the object is unlinked, the name shall be removed before
? ? ?shm_unlink() returns, but the removal of the memory object
? ? ?contents shall be postponed until all open and map references to
? ? ?the shared memory object have been removed.
調(diào)用了shm_unlink函數(shù)不一定會(huì)導(dǎo)致內(nèi)容立刻消失,已經(jīng)映射的內(nèi)存將會(huì)繼續(xù)存在且可以操作(前提是你創(chuàng)建時(shí)規(guī)定了他可以操作),直到使用它的進(jìn)程退出。所以現(xiàn)在調(diào)用shm_unlink實(shí)際上是為程序退出后釋放資源做了準(zhǔn)備。
現(xiàn)在我們已經(jīng)成功把茅坑占了,接下來(lái)就是為這塊內(nèi)存分配用途
shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
memcpy(shm_data, MagickImage, size);
關(guān)于mmap函數(shù),RTFM。這里將圖片緩沖區(qū)與剛才創(chuàng)建的文件做了一個(gè)內(nèi)存映射,然后把圖片數(shù)據(jù)拷貝進(jìn)去。
為什么要這樣做捏?不能直接寫(xiě) fd 嗎?具體的原因我自己也不大清楚lol,根據(jù)查到的資料大概意思就是fd只是一個(gè)描述符,能讀取但不能直接寫(xiě)(what?)
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
創(chuàng)建一個(gè)共享內(nèi)存池,Client往里面存數(shù)據(jù),Host從里面讀數(shù)據(jù)
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
? ? ?stride, WL_SHM_FORMAT_ARGB8888);
有了池子還不夠,我們還需要告訴對(duì)面池子里存了啥玩意(數(shù)據(jù)的格式),這樣才能保證Host按照我們期望的樣式讀取并顯示內(nèi)容。
這里還有一個(gè)新東西:stride,直譯過(guò)來(lái)就是“步幅”“步進(jìn)”。如果直接 Google 就錯(cuò)了,因?yàn)檫@個(gè)詞在不同場(chǎng)合有不同意思。根據(jù)Wayland文檔的解釋:
stride is the number of bytes from the beginning of one row to the beginning of the next row
stride是用來(lái)指定每行數(shù)據(jù)大小的數(shù)值
提醒Host讀數(shù)據(jù)讀多少時(shí)候該換行了。這里很明顯不能直接用像素?cái)?shù)量,因?yàn)?Host 讀取的單位是字節(jié),每行的字節(jié)多少由長(zhǎng)度和單位像素大小決定。
wl_shm_pool_destroy(pool);
這個(gè)釋放資源的函數(shù)與 shm_unlink 有異曲同工之處,我們可以提前調(diào)用使其進(jìn)入準(zhǔn)備狀態(tài),然后在程序退出時(shí)函數(shù)自動(dòng)執(zhí)行剩下的資源釋放操作。
一套下來(lái),最終數(shù)據(jù)的流動(dòng)方向如下:
圖片數(shù)據(jù)--->Client端--->shm_data--->fd--->Host端--->圖形棧--->顯示
至此,我們已經(jīng)將顯示數(shù)據(jù)準(zhǔn)備完畢
Wayland 中的 surface 概念
大白話:PS里面的圖層+局部修改
為什么要這樣做捏,還是為了性能。如果一個(gè)畫(huà)面里只有按鈕“x”發(fā)生了變化,那我們希望只對(duì)這個(gè)局部進(jìn)行渲染。同時(shí)顯示多窗口畫(huà)面的時(shí)候,我們希望底層的窗口減少刷新頻率。再者,當(dāng)我們程序的刷新率超出顯示器幀率時(shí)候,我們只能選擇性的將幀放出。
不過(guò),現(xiàn)在我們還是回到 HelloWorld 上,看看都干了些啥
surface = wl_compositor_create_surface(compositor);
參數(shù):指向混成器的結(jié)構(gòu)體指針
作用:在對(duì)應(yīng)混成器中創(chuàng)建一個(gè)表面,并返回該表面的指針
這個(gè)表面就是我們的畫(huà)布(顯示器),所有的畫(huà)面最終都會(huì)畫(huà)到上面,大小應(yīng)該就是對(duì)應(yīng)顯示器的分辨率(瞎猜的)
有了畫(huà)布也不夠啊,咋畫(huà)捏?直接畫(huà)全屏?別急,Wayland 有一堆擴(kuò)展的協(xié)議,幫助我們?cè)诋?huà)布上創(chuàng)建合適的窗口。
最最常用的窗口協(xié)議擴(kuò)展就是 xdg shell。根據(jù)官方文檔,xdg shell解決最基本的窗口問(wèn)題,比如最大最小化,隱藏等。
另外,這也是 Wayland 美妙的地方之一,只需要使用 Core 部分的底層函數(shù)(開(kāi)頭wl的函數(shù)),我們就可以在此基礎(chǔ)上抽象出更加方便快捷的擴(kuò)展協(xié)議。 xdg 就是這樣做出來(lái)的
從這個(gè)部分開(kāi)始,文檔全部參考[Wayland Protocol Doc] (https://wayland.app/protocols/)和[Wayland Official Doc] (https://wayland.freedesktop.org/docs/html/)
struct xdg_surface *xdg_surface =
? xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
要初始化 xdg shell,我們要?jiǎng)?chuàng)建一個(gè) xdg surface,沒(méi)錯(cuò),畫(huà)布里面還有畫(huà)布捏。只有這樣才更好操作(實(shí)際上很多UI設(shè)計(jì)工具也遵循了這一設(shè)計(jì)思想)
之后的 xdg_surface_get_toplevel 將窗口的層級(jí)狀態(tài)賦給xdg_toplevel指針,大白話就是:用一個(gè)指針來(lái)儲(chǔ)存這個(gè)窗口是否在頂層/可顯示/可操作的狀態(tài)。
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
兩個(gè)函數(shù)的原型都是:
wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel,
? ? ? ? ? ? ? ? ? ?(void (**)(void)) listener, data);
這時(shí)候又要引出Wayland協(xié)議中的另一個(gè)概念---代理。
(emmm,寫(xiě)到這里心態(tài)已經(jīng)有點(diǎn)崩了,咋這么復(fù)雜捏???)
根據(jù)官方的文檔,Proxy是將高級(jí)信號(hào)轉(zhuǎn)化為低級(jí)別的Wayland線信號(hào),也就是一大堆16進(jìn)制的數(shù)據(jù)。這一步是必要的,但通常就是隨手一寫(xiě)就行了。更具體的解釋在[Wire protocol basics] (https://wayland-book.com/protocol-design/wire-protocol.html)
這里我們真正需要關(guān)注的是“add_listener”這個(gè)操作,這一步對(duì)于我們的窗口能夠響應(yīng)操作非常重要,從這個(gè)直白的名字也能看出,就是添加偵聽(tīng)器,來(lái)偵聽(tīng)我們對(duì)窗口發(fā)出的信號(hào)。
于是上面兩段代碼連起來(lái)的效果就是:
1.我們先用 xdg_wm_base_get_xdg_surface(); 和 xdg_surface_get_toplevel(); 兩個(gè)函數(shù),將窗口的兩個(gè)基本屬性所對(duì)應(yīng)的指針獲取
2.然后用 xdg_surface_add_listener();xdg_toplevel_add_listener();給對(duì)應(yīng)的指針綁定到對(duì)應(yīng)的偵聽(tīng)器,這樣我們就可以監(jiān)測(cè)窗口屬性的變化。
不過(guò),別急,和之前的 wl_registry_add_listener() 一樣,我們這里并不包含具體實(shí)現(xiàn),真正的原型在 xdg_surface_listener 和 xdg_toplevel_listener 里。
現(xiàn)在來(lái)看一下兩個(gè)函數(shù)對(duì)應(yīng)的原型,首先是第一個(gè):
static void xdg_surface_handle_configure(void *data,
? ? ? struct xdg_surface *xdg_surface, uint32_t serial) {
? xdg_surface_ack_configure(xdg_surface, serial);
? wl_surface_commit(surface);
}
static const struct xdg_surface_listener xdg_surface_listener = {
? .configure = xdg_surface_handle_configure,
};
這里我們將 xdg_surface_listener 這個(gè)鉤子掛上 xdg_surface_handle_configure 函數(shù)實(shí)現(xiàn),每次觸發(fā)時(shí)都會(huì)執(zhí)行兩個(gè)函數(shù):
xdg_surface_ack_configure();
wl_surface_commit();
xdg_surface_ack_configure() 必須在wl_surface_commit之前的某個(gè)時(shí)間點(diǎn)被執(zhí)行一次,idk why。
wl_surface_commit() 從名字大概可以看出,這東西就是更新表面時(shí)用到的。根據(jù)文檔,此操作會(huì)讓混成器立刻執(zhí)行更新動(dòng)作。但是這里我的解釋十分甚至九分的不精確,還是看自己看看[文檔](https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_surface)罷
xdg_toplevel_listener() 里面的具體實(shí)現(xiàn)就不寫(xiě)了,在這里就一個(gè)作用,監(jiān)測(cè)窗口是否被關(guān)閉,如果被關(guān)閉則設(shè)變量running為false
等等,我們的窗口還沒(méi)關(guān)閉按鈕,咋搞?和游戲一樣唄,我們的窗口沒(méi)有不代表別的地方?jīng)]有啊。我用的GNOME,只要退到全局窗口視圖,就可以看見(jiàn)關(guān)閉按鈕了。如果要實(shí)現(xiàn)我們自己的關(guān)閉按鈕也是很簡(jiǎn)單的(這里就不搞了)。
一個(gè)比較有趣的地方在于,有的參數(shù)不允許留空指針(不掛函數(shù)),像是我們沒(méi)提到的輸入處理。這里程序用了個(gè)名字叫noop()的空函數(shù)代替。
static void noop() {
? // This space intentionally left blank
}
static const struct wl_pointer_listener pointer_listener = {
? .enter = noop,
? .leave = noop,
? .motion = noop,
? .button = pointer_handle_button,
? .axis = noop,
};
從中也可以看出,如果我們要針對(duì)鼠標(biāo)的動(dòng)作/進(jìn)入/離開(kāi)掛上對(duì)應(yīng)的函數(shù)也是比較簡(jiǎn)單的(大概)
噢草,終于要接近尾聲了,我們又執(zhí)行了一次提交,以及和服務(wù)器通信
wl_surface_commit(surface);
wl_display_roundtrip(display);
到現(xiàn)在,我們又做了以下的事情:
1.把要顯示的數(shù)據(jù)準(zhǔn)備好
2.創(chuàng)建了表面(畫(huà)布),輸入事件
3.將需要監(jiān)測(cè)的數(shù)據(jù)加上偵聽(tīng)器
所以到現(xiàn)在我們還缺啥?有構(gòu)思(數(shù)據(jù))和畫(huà)布(表面),現(xiàn)在應(yīng)該動(dòng)手畫(huà)畫(huà)了
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_commit(surface);
wl_surface_attach,將緩沖區(qū)內(nèi)容轉(zhuǎn)移到表面上,然后提交變化。注意,自wl_surface的第五個(gè)版本起,wl_surface_attach后面的xy參數(shù)只能設(shè)置為0,其他值無(wú)效。
while (wl_display_dispatch(display) != -1 && running) {
? // This space intentionally left blank
}
wl_display_dispatch().....這啥玩意啊,貌似是檢測(cè)窗口事件數(shù)量的?查了一圈沒(méi)找到很清楚的解釋lol
寫(xiě)到這里,我突然意識(shí)到一件事:我們上面寫(xiě)的那么多東西,會(huì)不會(huì)也是一種編程?用“Wayland語(yǔ)言”在給我們的混成器下達(dá)指令。
總之最后我們的程序就停在了這個(gè)死循環(huán)里面,well,雖然我們的程序是死循環(huán),但是混成器可不是,接下來(lái)所有的事件都會(huì)在混成器里面處理。我們依然可以愉快的拖動(dòng)窗口欣賞可愛(ài)的小貓。

但是這個(gè)寫(xiě)法是很不好的,很多系統(tǒng)(或者說(shuō)混成器?)檢測(cè)到我們的主程序進(jìn)入死循環(huán)一段時(shí)間后會(huì)彈出無(wú)響應(yīng)的標(biāo)識(shí)(比如GNOME)。正常的用法應(yīng)該是在這個(gè)死循環(huán)里面執(zhí)行我們其他的函數(shù)(比如寫(xiě)一個(gè)游戲程序?)。為了阻止無(wú)響應(yīng),我們可以使用官方的參考混成器weston來(lái)做實(shí)驗(yàn)。
weston啟動(dòng)以后,如果檢測(cè)到有正在運(yùn)行的混成器,則它自己會(huì)變成一個(gè)披著Client皮的Host,直接在現(xiàn)有混成器上顯示出一個(gè)簡(jiǎn)單桌面。這個(gè)時(shí)候會(huì)創(chuàng)建第二個(gè)host:wayland-1,前面我們提到了連接Wayland服務(wù)器的方法,將服務(wù)器名稱寫(xiě)死wayland-1是個(gè)可行的方法,更好的方法是留NULL,然后設(shè)置環(huán)境變量$WAYLAND_DISPLAY=wayland-1。