Talk is cheap. Show me the code.手搓一個 Wayland 客戶端程序

前幾天我寫了一篇萬字長文《萬字長文詳解 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, &registry_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;
}

并且給出了詳細的編譯步驟。

  1. 安裝依賴

    打開終端,輸入以下命令安裝所有必需的開發包:

    sudo apt update
    sudo apt install libwayland-dev libcairo2-dev cmake build-essential wayland-protocols
  2. 生成 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?文件請放到本項目目錄下。

  3. 獲取源碼

    假設你已經有本項目的源碼目錄?WaylandClientDemo

  4. 編譯程序

    在項目目錄下執行:

    cd?WaylandClientDemo
    mkdir build &&?cd?build
    cmake ..
    make

    編譯成功后會生成?hello-wayland-v1?可執行文件。

  5. 運行程序

    保證你當前在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, &registry_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;
}

典型流程

  1. 客戶端通過?wl_registry?獲取?zxdg_decoration_manager_v1

  2. 用?zxdg_decoration_manager_v1?為?xdg_toplevel?創建 decoration 對象。

  3. 調用?zxdg_toplevel_decoration_v1_request_mode()?請求 SSD。

  4. 合成器響應,決定是否提供裝飾。

運行效果圖如下:

是一個正常的窗口,和我們的預期相符。然而,這個程序在 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 世界的一扇窗。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/89115.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/89115.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/89115.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Golang JSON 標準庫用法詳解

JSON (JavaScript Object Notation) 是一種輕量級的數據交換格式&#xff0c;Go語言的標準庫encoding/json提供了強大的JSON處理能力。下面我將詳細介紹各種用法并提供示例代碼。 1. 基本編碼&#xff08;Marshal&#xff09; 將Go數據結構轉換為JSON字符串。 package maini…

Day.42

hook函數&#xff1a; import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt torch.manual_seed(42) np.random.seed(42) 張量鉤子&#xff1a; x torch.tensor([2.0], requires_gradTrue) y x ** 2 z y ** 3 def tensor_hook…

【.net core】【sqlsugar】在where條件查詢時使用原生SQL

//初始化查詢 var query repository.IQueryable();//添加原生SQL WHERE條件 query query.Where(" fieldA < 123"); 對應調用ISugarQueryable接口類中&#xff1a; ISugarQueryable<T> Where(string whereString, object parameters null);

網絡 : 傳輸層【TCP協議】

網絡 : 傳輸層【TCP協議】 一、TCP協議段格式1.1 32位序號與確認號1.1.1 32位序號1.1.2 確認號 1.2 4位首部長度1.3 6位標志位1.4 16位窗口大小 二、確認應答(ACK)機制三、超時重傳機制四、連接管理機制4.1 三次握手(連接)listen的第二個參數 4.2 四次揮手(斷開連接)**TIME_WAI…

人大金倉Kingbase數據庫 Ksql: 未找到命令

人大金倉Kingbase數據庫 Ksql: 未找到命令 1. 定位 Kingbase 安裝目錄 Kingbase 數據庫通常安裝在 /kingbase/ES/V8/Server 目錄下。可以通過以下命令定位&#xff1a; cd /kingbase/ES/V8/Server2. 驗證 ksql 工具是否安裝成功 執行以下命令檢查 ksql 客戶端工具的版本信息…

Flask(四) 模板渲染render_template

文章目錄 &#x1f4e6; 過程詳解&#xff08;路由 <-> HTML 模板&#xff09;&#x1f9e0; 數據是怎么傳過去的&#xff1f;多變量示例 ? Jinja2 支持條件判斷、循環、模板繼承&#xff1a;? 安全性&#x1f512; Flask 默認也會對變量進行 HTML 轉義&#xff1a;&am…

[附源碼+數據庫+畢業論文+開題報告]基于Spring+MyBatis+MySQL+Maven+jsp實現的寵物領養管理系統,推薦!

摘 要 互聯網發展至今&#xff0c;無論是其理論還是技術都已經成熟&#xff0c;而且它廣泛參與在社會中的方方面面。它讓信息都可以通過網絡傳播&#xff0c;搭配信息管理工具可以很好地為人們提供服務。針對寵物領養信息管理混亂&#xff0c;出錯率高&#xff0c;信息安全性差…

【ArcGIS】水資源單項評價

【ArcGIS】水資源單項評價 一、水資源單項評價1、評價思路 二、操作步驟1、處理環境設置2、數據處理3、要素轉柵格4、水資源評價 一、水資源單項評價 1、評價思路 &#xff08;1&#xff09;省級層面宜選用四級/五級水資源分區或縣級行政區為評價單元&#xff0c;按照水資源總…

Windows環境下C語言匯編語言編輯器及環境安裝

安裝MinGw&#xff1a; 1.下載安裝文件 MinGW - 適用于 Windows 的極簡主義 GNU 下載 |SourceForge.net 點擊下載 下載之后就是如下圖的安裝文件 2.安裝 雙擊安裝文件進行安裝&#xff0c;點擊Install下一步 選擇安裝位置&#xff0c;默認是安裝在C盤&#xff0c;點擊Change…

【數據分析】分段邏輯回歸示例分析(模擬數據)

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹加載R包步驟 1:模擬數據步驟 2:構建邏輯回歸和分段模型步驟 3:計算預測值和置信區間步驟 4:提取 OR 和統計值步驟 5:繪圖展示結步驟 6:輸出圖片原始代碼總結系統信息參考介紹…

Webpack 構建過程詳解

Webpack 是一個功能強大的模塊打包工具,它能夠將項目中的各種資源(如 JavaScript、CSS、圖片等)打包成一個或多個文件,以便于在瀏覽器中運行。本文將以 Webpack 5 為例介紹它的構建過程: 1. 初始化階段 在這個階段,Webpack 從配置文件和命令行參數中讀取并解析配置。然…

Flutter基礎(UI監聽)

文本按鈕&#xff08;TextButton&#xff09; 文本按鈕是沒有邊框的按鈕&#xff0c;當點擊時會有漣漪效果。 TextButton(onPressed: () {// 點擊按鈕后要執行的代碼print(文本按鈕被點擊了);},child: Text(點擊我), ) 手勢檢測器&#xff08;GestureDetector&#xff09; …

Linux 下的 regulator 子系統

1、簡介 regulator 框架是 Linux 內核中用于管理電壓和電流調節器&#xff08;如 LDO、DCDC 轉換器等&#xff09;的一個子系統。它提供了一個抽象層&#xff0c;使得驅動程序和內核的其他部分可以以一致的方式與調節器進行交互&#xff0c;而無需了解底層硬件的細節。 主要功能…

12345政務熱線系統:接訴即辦,賦能智慧城市治理

一、12345熱線&#xff1a;民情直通車&#xff0c;治理新引擎 “12345”政務熱線是黨委政府了解社情民意、解決群眾合理訴求、傾聽批評建議、改進工作作風的重要渠道。當前&#xff0c;全國各城市已基本建成12345政務服務熱線體系&#xff0c;形成“接訴即辦”的高效響應機制。…

【SpringBoot核心】Spring Boot + MyBatis 深度整合與最佳實踐

目錄 引言Spring Boot 基礎回顧MyBatis 核心概念解析Spring Boot 整合 MyBatisMyBatis 高級特性Spring Boot + MyBatis 最佳實踐性能優化與擴展實戰案例:電商系統開發常見問題與解決方案總結與展望1. 引言 1.1 技術背景與現狀 在現代企業級應用開發中,數據持久化是一個核心…

力扣第77題-組合-力扣第78題-子集

力扣鏈接:77. 組合 - 力扣&#xff08;LeetCode&#xff09; 給定兩個整數 n 和 k&#xff0c;返回范圍 [1, n] 中所有可能的 k 個數的組合。 你可以按 任何順序 返回答案。 示例 1&#xff1a; 輸入&#xff1a;n 4, k 2 輸出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3…

嵌入式MTD設備與Flash管理解析

理解MTD是嵌入式系統中處理Flash存儲的關鍵一步&#xff01;我來幫你梳理清楚&#xff1a; MTD 是什么&#xff1f; MTD 是 Memory Technology Device 的縮寫&#xff0c;中文常譯為內存技術設備。它是 Linux 內核及其衍生系統&#xff08;如嵌入式 Linux&#xff09;中用于管…

基于 GEE 利用 Sentinel-2 數據計算并下載植被指數數據

目錄 1 植被指數 2 完整代碼 3 運行結果 1 植被指數 植被指數全名NDVI歸一化差值植被指數GNDVI綠色歸一化差值植被指數EVI增強植被指數EVI2雙波段增強植被指數DVI差值植被指數GDVI綠色差植被值指數RVI比值植被指數SAVI土壤調整植被指數OSAVI優化土壤調整植被指數MSAVI修改…

python基礎23(2025.6.29)分布式爬蟲(增量式爬蟲去重)redis應用_(未完成!)

本次寫一個爬取網易新聞的案例。因為redis能處理高并發&#xff0c;存儲數據也可以&#xff0c;故不用mysql。而且新聞網站容易更新很多&#xff0c;而mysql只能持久化存儲。 import scrapy import re import json import redis # 用它來去除重復, 記錄訪問過的urlclass Wang…

Springboot 集成 SpringState 狀態機

Springboot 集成 SpringState 狀態機 1.SpringState 簡介2.狀態機示例2.1 項目結構和依賴包2.2 定義事件類和狀態類2.3 Spring 事件監聽器2.4 狀態機持久化類2.4.1 Redis 狀態機持久化容器2.4.2 Redis 配置2.4.3 狀態機監聽器 2.5 裝機器容器2.6 狀態機事件發送器2.7 狀態機配置…