5. 全局對象和注冊器

[TOC]
如果你記得第2.1章,每個請求和事件都與一個對象ID相關聯,但到目前為止,我們還沒有討論對象是如何創建的。當我們收到Wayland消息時,我們必須知道對象ID代表什么接口才能解碼它。我們還必須以某種方式協商可用的對象、創建新對象以及將ID分配給它們。在Wayland中,我們同時解決了這兩個問題——當我們綁定一個對象ID時,我們在所有未來的消息中同意它所使用的接口,并將對象ID到接口的映射存儲在我們的本地狀態中。

為了引導這些,服務器提供了一個全局對象的列表。這些全局對象通常根據其自身的優點提供信息和功能,但最常用于代理其他對象以滿足各種目的,例如創建應用程序窗口。這些全局對象本身也有自己的對象ID和接口,我們必須以某種方式分配和同意。

現在你可能想到了雞和蛋的問題,我將揭示秘密技巧:當你建立連接時,對象ID 1已經隱式地分配給wl_display接口。當你回想這個接口時,請注意wl_display::get_registry請求:

<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請求可用于將對象ID綁定到wl_registry接口,該接口是在wayland.xml中發現的下一個接口。考慮到wl_display始終具有對象ID 1,以下電報消息應該有意義(在大端序中):

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

當我們分解這個消息時,第一個數字是對象ID。第二個數字最重要的16位是消息的總長度(以字節為單位),最不重要的位是請求操作碼。其余的字(只有一個)是參數。簡而言之,這是對對象ID 1(wl_display)上的請求1(0索引)的調用,它接受一個參數:新對象的生成ID。請注意,在XML文檔中,這個新的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>

我們將在后面的章節中討論這個接口。

5.1 綁定全局對象

在創建registry對象時,服務器將為每個可用的全局對象發出全局事件。然后,你可以綁定到你需要的全局對象。

綁定是將已知對象分配一個ID的過程。一旦客戶端像這樣綁定到registry,服務器就會多次發出全局事件,以宣傳它支持哪些接口。這些全局對象中的每一個都被分配了一個唯一的名稱,作為無符號整數。接口字符串映射到在協議中找到的接口名稱:來自上面的XML文件的wl_display就是一個這樣的名稱。版本號也在這里定義-有關接口版本控制的更多信息,請參閱附錄C。

要綁定到這些接口中的任何一個,我們使用bind請求,這個請求的工作方式與我們綁定到wl_registry的神奇過程類似。例如,考慮以下電報協議交換:

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

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

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

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

一旦你創建了這個對象,你可以利用它的接口來完成各種任務-在wl_shm的情況下,管理客戶端和服務器之間的共享內存。本書的大部分剩余部分都致力于解釋這些全局的用法。

有了這些信息,我們可以編寫我們的第一個有用的Wayland客戶端:一個簡單地打印服務器上所有可用全局的客戶端。

#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;
}

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

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

注意:在本章節最后一次我們將以十六進制的形式顯示電報協議轉儲,可能也是最后一次一般性地看到它們。在你的程序運行之前,將環境中的WAYLAND_DEBUG變量設置為1,這是更好的追蹤Wayland客戶端或服務器的方法。現在就用“globals”程序試試吧!

5.2 注冊全局變量

使用libwayland-server注冊全局變量有所不同。當您使用wayland-scanner生成“服務器代碼”時,它會創建接口(類似于監聽器)和用于發送事件的粘合代碼。第一個任務是注冊全局變量,使用一個函數來設置一個資源1,當全局變量被綁定時。就代碼而言,結果看起來像這樣:

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);
    // ...
}

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

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);
}

這一大段內容需要我們逐一解釋。在底部,我們將“bind”處理程序擴展為創建用于跟蹤此對象的服務器端狀態的wl_resource(使用客戶端分配的ID)。在此過程中,我們通過將指向接口實現(wl_output_implementation)的指針傳遞給wl_resource_create。這是一個常量靜態結構,在文件中定義。類型(struct wl_output_interface)由wayland-scanner生成,包含每個此接口支持的請求的函數指針。我們還借此機會分配一個小容器來存儲任何我們需要的額外狀態,這些狀態是libwayland無法為我們處理的,具體性質因協議而異。

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

我們的wl_output_handle_release函數在客戶端發送釋放請求時被調用,表明客戶端不再需要此資源-因此我們銷毀它。這反過來觸發了wl_output_handle_resource_destroy函數,稍后我們將擴展該函數以釋放之前為其分配的任何狀態。此函數還作為析構函數傳遞給wl_resource_create,如果客戶端終止而沒有顯式發送釋放請求,則將調用該函數。

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

<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僅用于說明目的,但在實踐中使用時需要考慮一些特殊情況。在您的客戶端或服務器中實現此事件之前,請先查看協議XML。

1資源表示每個客戶端對象的實例在服務器端的狀態。

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

當輸出被綁定時,發送此事件似乎是我們的責任。這很容易添加:

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僅用于說明目的,但在實踐中使用時需要考慮一些特殊情況。在您的客戶端或服務器中實現此事件之前,請先查看協議XML。

1資源表示每個客戶端對象的實例在服務器端的狀態。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容