5. 全局對(duì)象和注冊(cè)器

[TOC]
如果你記得第2.1章,每個(gè)請(qǐng)求和事件都與一個(gè)對(duì)象ID相關(guān)聯(lián),但到目前為止,我們還沒(méi)有討論對(duì)象是如何創(chuàng)建的。當(dāng)我們收到Wayland消息時(shí),我們必須知道對(duì)象ID代表什么接口才能解碼它。我們還必須以某種方式協(xié)商可用的對(duì)象、創(chuàng)建新對(duì)象以及將ID分配給它們。在Wayland中,我們同時(shí)解決了這兩個(gè)問(wèn)題——當(dāng)我們綁定一個(gè)對(duì)象ID時(shí),我們?cè)谒形磥?lái)的消息中同意它所使用的接口,并將對(duì)象ID到接口的映射存儲(chǔ)在我們的本地狀態(tài)中。

為了引導(dǎo)這些,服務(wù)器提供了一個(gè)全局對(duì)象的列表。這些全局對(duì)象通常根據(jù)其自身的優(yōu)點(diǎn)提供信息和功能,但最常用于代理其他對(duì)象以滿足各種目的,例如創(chuàng)建應(yīng)用程序窗口。這些全局對(duì)象本身也有自己的對(duì)象ID和接口,我們必須以某種方式分配和同意。

現(xiàn)在你可能想到了雞和蛋的問(wèn)題,我將揭示秘密技巧:當(dāng)你建立連接時(shí),對(duì)象ID 1已經(jīng)隱式地分配給wl_display接口。當(dāng)你回想這個(gè)接口時(shí),請(qǐng)注意wl_display::get_registry請(qǐng)求:

<interface name="wl_display" version="1">
  <request name="sync">
    <arg name="callback" type="new_id" interface="wl_callback" />
  </request>

  <request name="get_registry">
    <arg name="registry" type="new_id" interface="wl_registry" />
  </request>

  <!-- ... -->
</interface>

wl_display::get_registry請(qǐng)求可用于將對(duì)象ID綁定到wl_registry接口,該接口是在wayland.xml中發(fā)現(xiàn)的下一個(gè)接口??紤]到wl_display始終具有對(duì)象ID 1,以下電報(bào)消息應(yīng)該有意義(在大端序中):

C->S    00000001 000C0001 00000002            .... .... ....

當(dāng)我們分解這個(gè)消息時(shí),第一個(gè)數(shù)字是對(duì)象ID。第二個(gè)數(shù)字最重要的16位是消息的總長(zhǎng)度(以字節(jié)為單位),最不重要的位是請(qǐng)求操作碼。其余的字(只有一個(gè))是參數(shù)。簡(jiǎn)而言之,這是對(duì)對(duì)象ID 1(wl_display)上的請(qǐng)求1(0索引)的調(diào)用,它接受一個(gè)參數(shù):新對(duì)象的生成ID。請(qǐng)注意,在XML文檔中,這個(gè)新的ID是提前定義的,由wl_registry接口管理:

<interface name="wl_registry" version="1">
  <request name="bind">
    <arg name="name" type="uint" />
    <arg name="id" type="new_id" />
  </request>

  <event name="global">
    <arg name="name" type="uint" />
    <arg name="interface" type="string" />
    <arg name="version" type="uint" />
  </event>

  <event name="global_remove">
    <arg name="name" type="uint" />
  </event>
</interface>

我們將在后面的章節(jié)中討論這個(gè)接口。

5.1 綁定全局對(duì)象

在創(chuàng)建registry對(duì)象時(shí),服務(wù)器將為每個(gè)可用的全局對(duì)象發(fā)出全局事件。然后,你可以綁定到你需要的全局對(duì)象。

綁定是將已知對(duì)象分配一個(gè)ID的過(guò)程。一旦客戶端像這樣綁定到registry,服務(wù)器就會(huì)多次發(fā)出全局事件,以宣傳它支持哪些接口。這些全局對(duì)象中的每一個(gè)都被分配了一個(gè)唯一的名稱,作為無(wú)符號(hào)整數(shù)。接口字符串映射到在協(xié)議中找到的接口名稱:來(lái)自上面的XML文件的wl_display就是一個(gè)這樣的名稱。版本號(hào)也在這里定義-有關(guān)接口版本控制的更多信息,請(qǐng)參閱附錄C。

要綁定到這些接口中的任何一個(gè),我們使用bind請(qǐng)求,這個(gè)請(qǐng)求的工作方式與我們綁定到wl_registry的神奇過(guò)程類似。例如,考慮以下電報(bào)協(xié)議交換:

C->S    00000001 000C0001 00000002            .... .... ....

S->C    00000002 001C0000 00000001 00000007   .... .... .... ....
        776C5f73 686d0000 00000001            wl_s hm.. ....
        [...]

C->S    00000002 00100000 00000001 00000003   .... .... .... ....

第一條消息與我們之前分析的消息完全相同。第二條消息來(lái)自服務(wù)器:對(duì)象2(客戶端在第一條消息中將其分配給wl_registry)操作碼0(“global”),參數(shù)為1,“wl_shm”和1-分別是名稱、接口和此全局的版本。客戶端通過(guò)在對(duì)象ID 2(wl_registry::bind)上調(diào)用操作碼0并分配對(duì)象ID 3給全局名稱1來(lái)響應(yīng)-綁定到wl_shm全局。未來(lái)這個(gè)對(duì)象的事件和請(qǐng)求由wl_shm協(xié)議定義,可以在wayland.xml中找到。

一旦你創(chuàng)建了這個(gè)對(duì)象,你可以利用它的接口來(lái)完成各種任務(wù)-在wl_shm的情況下,管理客戶端和服務(wù)器之間的共享內(nèi)存。本書(shū)的大部分剩余部分都致力于解釋這些全局的用法。

有了這些信息,我們可以編寫(xiě)我們的第一個(gè)有用的Wayland客戶端:一個(gè)簡(jiǎn)單地打印服務(wù)器上所有可用全局的客戶端。

#include <stdint.h>
#include <stdio.h>
#include <wayland-client.h>

static void
registry_handle_global(void *data, struct wl_registry *registry,
        uint32_t name, const char *interface, uint32_t version)
{
    printf("interface: '%s', version: %d, name: %d\n",
            interface, version, name);
}

static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
        uint32_t name)
{
    // This space deliberately left blank
}

static const struct wl_registry_listener
registry_listener = {
    .global = registry_handle_global,
    .global_remove = registry_handle_global_remove,
};

int
main(int argc, char *argv[])
{
    struct wl_display *display = wl_display_connect(NULL);
    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);
    wl_display_roundtrip(display);
    return 0;
}

請(qǐng)隨意參考前面的章節(jié)來(lái)解釋這個(gè)程序。我們連接到顯示器(第4.1章),獲取registry(本章節(jié)),向其添加一個(gè)監(jiān)聽(tīng)器(第3.4章),然后來(lái)回傳遞,通過(guò)處理全局事件來(lái)打印此合成器上可用的全局變量。親自嘗試一下:

$ cc -o globals -lwayland-client globals.c

注意:在本章節(jié)最后一次我們將以十六進(jìn)制的形式顯示電報(bào)協(xié)議轉(zhuǎn)儲(chǔ),可能也是最后一次一般性地看到它們。在你的程序運(yùn)行之前,將環(huán)境中的WAYLAND_DEBUG變量設(shè)置為1,這是更好的追蹤Wayland客戶端或服務(wù)器的方法?,F(xiàn)在就用“globals”程序試試吧!

5.2 注冊(cè)全局變量

使用libwayland-server注冊(cè)全局變量有所不同。當(dāng)您使用wayland-scanner生成“服務(wù)器代碼”時(shí),它會(huì)創(chuàng)建接口(類似于監(jiān)聽(tīng)器)和用于發(fā)送事件的粘合代碼。第一個(gè)任務(wù)是注冊(cè)全局變量,使用一個(gè)函數(shù)來(lái)設(shè)置一個(gè)資源1,當(dāng)全局變量被綁定時(shí)。就代碼而言,結(jié)果看起來(lái)像這樣:

static void
wl_output_handle_bind(struct wl_client *client, void *data,
    uint32_t version, uint32_t id)
{
    struct my_state *state = data;
    // TODO
}

int
main(int argc, char *argv[])
{
    struct wl_display *display = wl_display_create();
    struct my_state state = { ... };
    // ...
    wl_global_create(wl_display, &wl_output_interface,
        1, &state, wl_output_handle_bind);
    // ...
}

如果您將此代碼,例如修補(bǔ)到第4.1章的服務(wù)器示例中,您將使“globals”程序能夠看到我們上次編寫(xiě)的wl_output全局。但是,任何嘗試綁定到此全局的嘗試都將遇到我們的TODO。為了填補(bǔ)這一空白,我們需要提供wl_output接口的實(shí)現(xiàn)。

static void
wl_output_handle_resource_destroy(struct wl_resource *resource)
{
    struct my_output *client_output = wl_resource_get_user_data(resource);

    // TODO: Clean up resource

    remove_to_list(client_output->state->client_outputs, client_output);
}

static void
wl_output_handle_release(struct wl_client *client, struct wl_resource *resource)
{
    wl_resource_destroy(resource);
}

static const struct wl_output_interface
wl_output_implementation = {
    .release = wl_output_handle_release,
};

static void
wl_output_handle_bind(struct wl_client *client, void *data,
    uint32_t version, uint32_t id)
{
    struct my_state *state = data;

    struct my_output *client_output = calloc(1, sizeof(struct client_output));

    struct wl_resource *resource = wl_resource_create(
        client, &wl_output_interface, wl_output_interface.version, id);

    wl_resource_set_implementation(resource, &wl_output_implementation,
        client_output, wl_output_handle_resource_destroy);

    client_output->resource = resource;
    client_output->state = state;

    // TODO: Send geometry event, et al

    add_to_list(state->client_outputs, client_output);
}

這一大段內(nèi)容需要我們逐一解釋。在底部,我們將“bind”處理程序擴(kuò)展為創(chuàng)建用于跟蹤此對(duì)象的服務(wù)器端狀態(tài)的wl_resource(使用客戶端分配的ID)。在此過(guò)程中,我們通過(guò)將指向接口實(shí)現(xiàn)(wl_output_implementation)的指針傳遞給wl_resource_create。這是一個(gè)常量靜態(tài)結(jié)構(gòu),在文件中定義。類型(struct wl_output_interface)由wayland-scanner生成,包含每個(gè)此接口支持的請(qǐng)求的函數(shù)指針。我們還借此機(jī)會(huì)分配一個(gè)小容器來(lái)存儲(chǔ)任何我們需要的額外狀態(tài),這些狀態(tài)是libwayland無(wú)法為我們處理的,具體性質(zhì)因協(xié)議而異。

注意:這里有兩個(gè)截然不同的東西,它們共享相同的名稱:struct wl_output_interface是一個(gè)接口的實(shí)例,其中wl_output_interface是由wayland-scanner生成的包含與實(shí)現(xiàn)相關(guān)的元數(shù)據(jù)的全局常量變量(如版本,在上面的示例中)。

我們的wl_output_handle_release函數(shù)在客戶端發(fā)送釋放請(qǐng)求時(shí)被調(diào)用,表明客戶端不再需要此資源-因此我們銷毀它。這反過(guò)來(lái)觸發(fā)了wl_output_handle_resource_destroy函數(shù),稍后我們將擴(kuò)展該函數(shù)以釋放之前為其分配的任何狀態(tài)。此函數(shù)還作為析構(gòu)函數(shù)傳遞給wl_resource_create,如果客戶端終止而沒(méi)有顯式發(fā)送釋放請(qǐng)求,則將調(diào)用該函數(shù)。

我們代碼中剩下的另一個(gè)“TODO”是發(fā)送“name”事件以及其他幾個(gè)事件。如果我們查看wayland.xml,我們會(huì)在接口上看到這個(gè)事件:

<event name="geometry">
  <description summary="properties of the output">
The geometry event describes geometric properties of the output.
The event is sent when binding to the output object and whenever
any of the properties change.

The physical size can be set to zero if it doesn't make sense for this
output (e.g. for projectors or virtual outputs).
  </description>
  <arg name="x" type="int" />
  <arg name="y" type="int" />
  <arg name="physical_width" type="int" />
  <arg name="physical_height" type="int" />
  <arg name="subpixel" type="int" enum="subpixel" />
  <arg name="make" type="string" />
  <arg name="model" type="string" />
  <arg name="transform" type="int" enum="transform" />
</event>注意:在此處顯示wl_output::geometry僅用于說(shuō)明目的,但在實(shí)踐中使用時(shí)需要考慮一些特殊情況。在您的客戶端或服務(wù)器中實(shí)現(xiàn)此事件之前,請(qǐng)先查看協(xié)議XML。

1資源表示每個(gè)客戶端對(duì)象的實(shí)例在服務(wù)器端的狀態(tài)。

2如果您對(duì)更復(fù)雜的版本感興趣,我們的“globals”程序稱為weston-info,可從Weston 項(xiàng)目中獲取。

當(dāng)輸出被綁定時(shí),發(fā)送此事件似乎是我們的責(zé)任。這很容易添加:

static void
wl_output_handle_bind(struct wl_client *client, void *data,
    uint32_t version, uint32_t id)
{
    struct my_state *state = data;

    struct my_output *client_output = calloc(1, sizeof(struct client_output));

    struct wl_resource *resource = wl_resource_create(
        client, &wl_output_implementation, wl_output_interface.version, id);

    wl_resource_set_implementation(resource, wl_output_implementation,
        client_output, wl_output_handle_resource_destroy);

    client_output->resource = resource;
    client_output->state = state;

    wl_output_send_geometry(resource, 0, 0, 1920, 1080,
        WL_OUTPUT_SUBPIXEL_UNKNOWN, "Foobar, Inc",
        "Fancy Monitor 9001 4K HD 120 FPS Noscope",
        WL_OUTPUT_TRANSFORM_NORMAL);

    add_to_list(state->client_outputs, client_output);
}

注意:在此處顯示wl_output::geometry僅用于說(shuō)明目的,但在實(shí)踐中使用時(shí)需要考慮一些特殊情況。在您的客戶端或服務(wù)器中實(shí)現(xiàn)此事件之前,請(qǐng)先查看協(xié)議XML。

1資源表示每個(gè)客戶端對(duì)象的實(shí)例在服務(wù)器端的狀態(tài)。

2如果您對(duì)更復(fù)雜的版本感興趣,我們的“globals”程序稱為weston-info,可從Weston 項(xiàng)目中獲取。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,327評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,554評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,883評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,114評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評(píng)論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,615評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,343評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容