最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Wayland從入門(mén)到入土P0---HelloWorld

2023-06-02 15:03 作者:怎么取名字這么難額啊  | 我要投稿

聲明

這個(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, &registry_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, &registry_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。


Wayland從入門(mén)到入土P0---HelloWorld的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
上虞市| 绍兴县| 宣化县| 中牟县| 溆浦县| 沙洋县| 连城县| 兴城市| 麦盖提县| 乌恰县| 灵川县| 平顶山市| 黄山市| 安平县| 淳化县| 东安县| 榆树市| 和田县| 万宁市| 龙井市| 英超| 永仁县| 安远县| 东乌| 新邵县| 枣强县| 丰县| 永平县| 新野县| 拜城县| 阿拉尔市| 竹溪县| 吐鲁番市| 金秀| 万载县| 彝良县| 深州市| 上犹县| 巫溪县| 延安市| 涿州市|