前幾天我寫了一篇萬字長文《萬字長文詳解 Wayland 協議、架構》,但光講協議分析難免有些枯燥。畢竟,程序員更信奉那句名言:Talk is cheap. Show me the code.
所以這篇文章不打算長篇大論,而是通過編寫一個簡單的 Wayland 客戶端程序,帶大家實際體驗一下 Wayland 的“坑”與門道。
我們要開發的 Wayland 客戶端非常簡單,只需在窗口中顯示一句 “Hello wayland”。其實,寫圖形界面程序一般推薦用 GTK、QT 這樣的 GUI 框架,這樣可以自動適配 X11、Wayland 等后端。但為了演示 Wayland 客戶端的底層寫法,這次我們選擇“手搓”一個。當然,具體實現就交給 AI 助手來完成了。
很快,AI 就給出了第一個版本的 Wayland 客戶端程序。
第一個版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <cairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>// 包含我們剛剛生成的頭文件
#include "xdg-shell-client-protocol.h"// 用于管理我們所有Wayland對象和狀態的結構體
struct state {struct wl_display *display;struct wl_registry *registry;struct wl_compositor *compositor;struct wl_surface *surface;struct wl_shm *shm;struct xdg_wm_base *xdg_wm_base;struct xdg_surface *xdg_surface;struct xdg_toplevel *xdg_toplevel;struct wl_buffer *buffer;void *shm_data;int width, height;_Bool running;
};// 繪制函數
static void draw_frame(struct state *state) {int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;// 清空緩沖區內存memset(state->shm_data, 0, size);// 使用Cairo在共享內存上創建表面cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(state->shm_data, CAIRO_FORMAT_ARGB32, state->width, state->height, stride);cairo_t *cr = cairo_create(cairo_surface);// 繪制背景 (淡藍色)cairo_set_source_rgba(cr, 0.8, 0.9, 1.0, 1.0);cairo_paint(cr);// 繪制文字cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);cairo_select_font_face(cr,?"sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);cairo_set_font_size(cr, 40);cairo_text_extents_t extents;cairo_text_extents(cr,?"Hello World!", &extents);cairo_move_to(cr, state->width/2.0 - extents.width/2.0, state->height/2.0);cairo_show_text(cr,?"Hello World!");// 清理Cairo資源cairo_destroy(cr);cairo_surface_destroy(cairo_surface);// 將繪制好的緩沖區附加到表面wl_surface_attach(state->surface, state->buffer, 0, 0);// 告訴合成器表面的哪個區域被更新了 (這里是整個表面)wl_surface_damage_buffer(state->surface, 0, 0, state->width, state->height);// 提交更改,讓合成器顯示wl_surface_commit(state->surface);
}// --- xdg_toplevel 事件監聽器 ---
static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel,int32_t width, int32_t height, struct wl_array *states) {struct state *state = data;if?(width > 0 && height > 0) {state->width = width;state->height = height;}// 注意: 我們不在這里繪圖,因為我們會在xdg_surface的configure事件后繪圖
}static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) {struct state *state = data;// 合成器通知我們用戶點擊了關閉按鈕state->running = 0;
}static const struct xdg_toplevel_listener xdg_toplevel_listener = {.configure = xdg_toplevel_handle_configure,.close = xdg_toplevel_handle_close,
};// --- xdg_surface 事件監聽器 ---
static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {struct state *state = data;// 必須確認配置事件xdg_surface_ack_configure(xdg_surface, serial);// 在收到配置后,我們就可以繪圖了draw_frame(state);
}static const struct xdg_surface_listener xdg_surface_listener = {.configure = xdg_surface_handle_configure,
};// --- xdg_wm_base 事件監聽器 ---
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {// 客戶端必須響應ping事件,否則合成器會認為客戶端無響應xdg_wm_base_pong(xdg_wm_base, serial);
}static const struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = xdg_wm_base_handle_ping,
};// --- wl_registry 事件監聽器 ---
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,const char *interface, uint32_t version) {struct state *state = data;printf("Got a registry event for %s id %u\n", interface, name);if?(strcmp(interface, wl_compositor_interface.name) == 0) {state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);}?elseif?(strcmp(interface, wl_shm_interface.name) == 0) {state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);}?elseif?(strcmp(interface, xdg_wm_base_interface.name) == 0) {state->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);}
}static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {// This space is?for?rent
}static const struct wl_registry_listener registry_listener = {.global = registry_handle_global,.global_remove = registry_handle_global_remove,
};// 創建共享內存緩沖區
static int create_shm_buffer(struct state *state) {// 使用 memfd_create 創建一個匿名的、基于內存的文件char tmp_name[] =?"/tmp/wayland-shm-XXXXXX";int fd = mkstemp(tmp_name);if?(fd < 0) {fprintf(stderr,?"mkstemp failed\n");return?-1;}// 立即刪除文件名,文件描述符依然有效unlink(tmp_name);int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;if?(ftruncate(fd, size) < 0) {close(fd);fprintf(stderr,?"ftruncate failed\n");return?-1;}// 將文件映射到內存state->shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if?(state->shm_data == MAP_FAILED) {close(fd);fprintf(stderr,?"mmap failed\n");return?-1;}// 從文件描述符創建Wayland共享內存池struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, size);state->buffer = wl_shm_pool_create_buffer(pool, 0, state->width, state->height, stride, WL_SHM_FORMAT_ARGB8888);wl_shm_pool_destroy(pool);close(fd);return?0;
}int main(int argc, char **argv) {struct state state = {0};state.width = 640;state.height = 480;state.running = 1;// 1. 連接到Wayland displaystate.display = wl_display_connect(NULL);if?(state.display == NULL) {fprintf(stderr,?"Can't connect to a Wayland display\n");return?1;}// 2. 獲取registry,用于發現全局對象state.registry = wl_display_get_registry(state.display);wl_registry_add_listener(state.registry, ®istry_listener, &state);// 3. 同步,等待服務器處理我們的請求并發送全局對象事件wl_display_dispatch(state.display);wl_display_roundtrip(state.display);// 檢查是否成功綁定了必要的全局對象if?(state.compositor == NULL || state.shm == NULL || state.xdg_wm_base == NULL) {fprintf(stderr,?"Can't find compositor, shm or xdg_wm_base\n");return?1;}// 4. 創建Wayland表面state.surface = wl_compositor_create_surface(state.compositor);// 5. 通過xdg-shell將表面設置為toplevel窗口state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.surface);xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state);// 設置窗口標題xdg_toplevel_set_title(state.xdg_toplevel,?"Wayland Hello World");// 提交表面,讓xdg-shell知道我們已經配置好了wl_surface_commit(state.surface);// 6. 創建共享內存緩沖區用于繪圖if?(create_shm_buffer(&state) < 0) {fprintf(stderr,?"Failed to create shm buffer\n");return?1;}// 7. 主事件循環while?(state.running && wl_display_dispatch(state.display) != -1) {// 事件處理都在監聽器回調中完成}// 8. 清理資源printf("Cleaning up...\n");if?(state.buffer) wl_buffer_destroy(state.buffer);if?(state.xdg_toplevel) xdg_toplevel_destroy(state.xdg_toplevel);if?(state.xdg_surface) xdg_surface_destroy(state.xdg_surface);if?(state.surface) wl_surface_destroy(state.surface);if?(state.xdg_wm_base) xdg_wm_base_destroy(state.xdg_wm_base);if?(state.shm) wl_shm_destroy(state.shm);if?(state.compositor) wl_compositor_destroy(state.compositor);if?(state.registry) wl_registry_destroy(state.registry);if?(state.display) wl_display_disconnect(state.display);return?0;
}
并且給出了詳細的編譯步驟。
安裝依賴
打開終端,輸入以下命令安裝所有必需的開發包:
sudo apt update sudo apt install libwayland-dev libcairo2-dev cmake build-essential wayland-protocols
生成 xdg-shell 協議相關文件
進入項目目錄,執行以下命令:
# 生成頭文件 wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h # 生成C源文件 wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c
生成的?
xdg-shell-client-protocol.h
?和?xdg-shell-protocol.c
?文件請放到本項目目錄下。獲取源碼
假設你已經有本項目的源碼目錄?
WaylandClientDemo
。編譯程序
在項目目錄下執行:
cd?WaylandClientDemo mkdir build &&?cd?build cmake .. make
編譯成功后會生成?
hello-wayland-v1
?可執行文件。運行程序
保證你當前在Wayland桌面環境下,執行:
./hello-wayland-v1
程序會彈出一個窗口,顯示“Hello Wayland”文字,10秒后自動關閉。
運行后會創建一個Wayland窗口,窗口中間顯示“Hello Wayland”文字。
等等,為什么這個窗口程序不是我們常見的窗口樣式,沒有邊框,沒有標題,也沒有最大化、最小化和關閉按鈕?
這是由于在 Wayland 體系下,窗口的“裝飾”(即邊框、標題欄、按鈕等)有兩種方式:
1. 服務端裝飾(SSD, Server Side Decoration)
由合成器自動為頂層窗口(
xdg_toplevel
)添加裝飾。但有些合成器默認不加裝飾。
2. 客戶端裝飾(CSD, Client Side Decoration)
由應用程序自己繪制裝飾(如 GTK、Chrome 瀏覽器等)。
但這就造成一種尷尬的情形,客戶端和服務器端都以為對方會繪制窗口裝飾,結果都沒有繪制。
為了解決這種矛盾,人們就想出了一個新方法,干脆協商一下吧,商量到底誰來繪制這個窗口裝飾。于是 Wayland 定義了一個專門的擴展協議,叫?xdg-decoration
(全名?xdg-decoration-unstable-v1
),用于協商窗口裝飾的方式。如果合成器支持該協議,客戶端可以請求服務端為窗口添加裝飾(SSD),否則需要自己繪制(CSD)。
第二個版本
將訴求丟給 AI 后,很快就出了第二個版本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <cairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>// 包含我們剛剛生成的頭文件
#include "xdg-shell-client-protocol.h"
#include "xdg-decoration-client-protocol.h" // <<< 引入新生成的頭文件// 用于管理我們所有Wayland對象和狀態的結構體
struct state {struct wl_display *display;struct wl_registry *registry;struct wl_compositor *compositor;struct wl_surface *surface;struct wl_shm *shm;struct xdg_wm_base *xdg_wm_base;struct xdg_surface *xdg_surface;struct xdg_toplevel *xdg_toplevel;// <<< 新增:用于xdg-decoration的對象struct zxdg_decoration_manager_v1 *decoration_manager;struct zxdg_toplevel_decoration_v1 *toplevel_decoration;struct wl_buffer *buffer;void *shm_data;int width, height;_Bool running;
};// 繪制函數
static void draw_frame(struct state *state) {int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;// 清空緩沖區內存memset(state->shm_data, 0, size);// 使用Cairo在共享內存上創建表面cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(state->shm_data, CAIRO_FORMAT_ARGB32, state->width, state->height, stride);cairo_t *cr = cairo_create(cairo_surface);// 繪制背景 (淡藍色)cairo_set_source_rgba(cr, 0.8, 0.9, 1.0, 1.0);cairo_paint(cr);// 繪制文字cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);cairo_select_font_face(cr,?"sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);cairo_set_font_size(cr, 40);cairo_text_extents_t extents;cairo_text_extents(cr,?"Hello World!", &extents);cairo_move_to(cr, state->width/2.0 - extents.width/2.0, state->height/2.0);cairo_show_text(cr,?"Hello World!");// 清理Cairo資源cairo_destroy(cr);cairo_surface_destroy(cairo_surface);// 將繪制好的緩沖區附加到表面wl_surface_attach(state->surface, state->buffer, 0, 0);// 告訴合成器表面的哪個區域被更新了 (這里是整個表面)wl_surface_damage_buffer(state->surface, 0, 0, state->width, state->height);// 提交更改,讓合成器顯示wl_surface_commit(state->surface);
}// --- xdg_toplevel 事件監聽器 ---
static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel,int32_t width, int32_t height, struct wl_array *states) {struct state *state = data;if?(width > 0 && height > 0) {state->width = width;state->height = height;}// 注意: 我們不在這里繪圖,因為我們會在xdg_surface的configure事件后繪圖
}static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) {struct state *state = data;// 合成器通知我們用戶點擊了關閉按鈕state->running = 0;
}static const struct xdg_toplevel_listener xdg_toplevel_listener = {.configure = xdg_toplevel_handle_configure,.close = xdg_toplevel_handle_close,
};// --- xdg_surface 事件監聽器 ---
static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {struct state *state = data;// 必須確認配置事件xdg_surface_ack_configure(xdg_surface, serial);// 在收到配置后,我們就可以繪圖了draw_frame(state);
}static const struct xdg_surface_listener xdg_surface_listener = {.configure = xdg_surface_handle_configure,
};// --- xdg_wm_base 事件監聽器 ---
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {// 客戶端必須響應ping事件,否則合成器會認為客戶端無響應xdg_wm_base_pong(xdg_wm_base, serial);
}static const struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = xdg_wm_base_handle_ping,
};// <<< 新增:xdg_toplevel_decoration 的事件監聽器
static void decoration_handle_configure(void *data,struct zxdg_toplevel_decoration_v1 *decoration,uint32_t mode)
{// 這是協商的核心!合成器通過這個事件告訴我們它最終決定的裝飾模式。printf("==> Compositor negotiated decoration mode: ");switch (mode) {case?ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:printf("Client-Side (we must draw our own!)\n");break;case?ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:printf("Server-Side (compositor will draw for us!)\n");break;default:printf("Unknown\n");break;}
}static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {.configure = decoration_handle_configure,
};// --- wl_registry 事件監聽器 ---
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,const char *interface, uint32_t version) {struct state *state = data;// <<< 我們現在打印所有接口,方便調試printf("Found global: %s (version %u)\n", interface, version);if?(strcmp(interface, wl_compositor_interface.name) == 0) {state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);}?elseif?(strcmp(interface, wl_shm_interface.name) == 0) {state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);}?elseif?(strcmp(interface, xdg_wm_base_interface.name) == 0) {state->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);}// <<< 新增:檢查合成器是否支持 xdg-decoration 協議elseif?(strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {state->decoration_manager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);}
}static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {// This space is?for?rent
}static const struct wl_registry_listener registry_listener = {.global = registry_handle_global,.global_remove = registry_handle_global_remove,
};// 創建共享內存緩沖區
static int create_shm_buffer(struct state *state) {// 使用 memfd_create 創建一個匿名的、基于內存的文件char tmp_name[] =?"/tmp/wayland-shm-XXXXXX";int fd = mkstemp(tmp_name);if?(fd < 0) {fprintf(stderr,?"mkstemp failed\n");return?-1;}// 立即刪除文件名,文件描述符依然有效unlink(tmp_name);int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, state->width);int size = stride * state->height;if?(ftruncate(fd, size) < 0) {close(fd);fprintf(stderr,?"ftruncate failed\n");return?-1;}// 將文件映射到內存state->shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if?(state->shm_data == MAP_FAILED) {close(fd);fprintf(stderr,?"mmap failed\n");return?-1;}// 從文件描述符創建Wayland共享內存池struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, size);state->buffer = wl_shm_pool_create_buffer(pool, 0, state->width, state->height, stride, WL_SHM_FORMAT_ARGB8888);wl_shm_pool_destroy(pool);close(fd);return?0;
}int main(int argc, char **argv) {struct state state = {0};state.width = 640;state.height = 480;state.running = 1;// 1. 連接到Wayland displaystate.display = wl_display_connect(NULL);if?(state.display == NULL) {fprintf(stderr,?"Can't connect to a Wayland display\n");return?1;}// 2. 獲取registry,用于發現全局對象state.registry = wl_display_get_registry(state.display);wl_registry_add_listener(state.registry, ®istry_listener, &state);// 3. 同步,等待服務器處理我們的請求并發送全局對象事件wl_display_dispatch(state.display);wl_display_roundtrip(state.display);// 檢查是否成功綁定了必要的全局對象if?(state.compositor == NULL || state.shm == NULL || state.xdg_wm_base == NULL) {fprintf(stderr,?"Can't find compositor, shm or xdg_wm_base\n");return?1;}// 4. 創建Wayland表面state.surface = wl_compositor_create_surface(state.compositor);// 5. 通過xdg-shell將表面設置為toplevel窗口state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.surface);xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state);// 設置窗口標題xdg_toplevel_set_title(state.xdg_toplevel,?"Wayland Hello World");// <<< 新增:進行裝飾協商if?(state.decoration_manager) {printf("Decoration manager found. Negotiating...\n");// 為我們的窗口獲取一個裝飾對象state.toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(state.decoration_manager, state.xdg_toplevel);// 添加監聽器以接收合成器的決定zxdg_toplevel_decoration_v1_add_listener(state.toplevel_decoration, &decoration_listener, &state);// *明確地*請求服務器端裝飾zxdg_toplevel_decoration_v1_set_mode(state.toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);}?else?{printf("Decoration manager not found. Cannot negotiate decorations.\n");}// 提交表面,讓xdg-shell知道我們已經配置好了wl_surface_commit(state.surface);// 6. 創建共享內存緩沖區用于繪圖if?(create_shm_buffer(&state) < 0) {fprintf(stderr,?"Failed to create shm buffer\n");return?1;}// 7. 主事件循環while?(state.running && wl_display_dispatch(state.display) != -1) {// 事件處理都在監聽器回調中完成}// 8. 清理資源printf("Cleaning up...\n");// <<< 新增:清理裝飾相關的對象if?(state.toplevel_decoration) zxdg_toplevel_decoration_v1_destroy(state.toplevel_decoration);if?(state.decoration_manager) zxdg_decoration_manager_v1_destroy(state.decoration_manager);if?(state.buffer) wl_buffer_destroy(state.buffer);if?(state.xdg_toplevel) xdg_toplevel_destroy(state.xdg_toplevel);if?(state.xdg_surface) xdg_surface_destroy(state.xdg_surface);if?(state.surface) wl_surface_destroy(state.surface);if?(state.xdg_wm_base) xdg_wm_base_destroy(state.xdg_wm_base);if?(state.shm) wl_shm_destroy(state.shm);if?(state.compositor) wl_compositor_destroy(state.compositor);if?(state.registry) wl_registry_destroy(state.registry);if?(state.display) wl_display_disconnect(state.display);return?0;
}
典型流程
客戶端通過?
wl_registry
?獲取?zxdg_decoration_manager_v1
。用?
zxdg_decoration_manager_v1
?為?xdg_toplevel
?創建 decoration 對象。調用?
zxdg_toplevel_decoration_v1_request_mode()
?請求 SSD。合成器響應,決定是否提供裝飾。
運行效果圖如下:
是一個正常的窗口,和我們的預期相符。然而,這個程序在 Ubuntu 24.04 下運行的效果圖:
仍然沒有窗口裝飾。其實從 xdg-decoration 擴展的文件名 xdg-decoration-unstable-v1.xml 就可以看出,該擴展協議還處在 unstable 狀態,然后 Ubuntu 使用的合成器 Mutter 沒實現該擴展協議。這種情況下,就需要 Wayland 客戶端來繪制。
小結
本文通過手搓 Wayland 客戶端的實踐,帶你從零體驗了 Wayland 協議下窗口程序的開發流程。我們首先實現了一個最基礎的“Hello Wayland”窗口,隨后又引入了 xdg-decoration 協議,嘗試與合成器協商窗口裝飾的繪制方式。通過實際運行和對比不同環境下的效果,你可以直觀感受到 Wayland 生態中窗口裝飾的多樣性與復雜性。
Wayland 的窗口裝飾機制分為服務端裝飾(SSD)和客戶端裝飾(CSD),而 xdg-decoration 協議則是二者協商的橋梁。但由于協議本身還處于 unstable 階段,不同合成器的支持情況并不一致,導致實際效果會有差異。比如在 Ubuntu 24.04 的 Mutter 合成器下,仍需客戶端自行繪制裝飾。
總之,Wayland 生態仍在不斷發展,協議和實現也在持續完善。對于開發者來說,理解其原理和流程,有助于更好地適配和優化自己的應用。希望本文的代碼和講解,能為你打開 Wayland 世界的一扇窗。