Skip to content

Commit 8528f14

Browse files
authored
feat(cursor): 第六个成员 libwayland-cursor——客户端自己的鼠标指针 (#2)
Wayland 没有服务端光标:客户端要指针,就得自己加载光标主题、把图像变成 wl_buffer、附到一个交给 wl_pointer.set_cursor 的 surface 上。这个库是前两步。 没有它,每个应用都得自己解析 XCursor 文件格式——而那正是这里的 xcursor.c。 不要和索引里的 compat.xcursor 搞混:那是 X11 的 libXcursor.so.1,要和 X 服务器 说话。这个不依赖 X,产出的是经 wl_shm 的 wl_buffer。 漏掉的原因和 libwayland-egl 一样:fork 建了合成器需要的四个成员就停了,而合成器 自己画光标。 ## 与 egl 成员的关键差别:这个真的链 client wayland-cursor.c 调 wl_shm_create_pool / wl_shm_pool_create_buffer / wl_shm_pool_resize / wl_buffer_destroy,所以上游的 dependencies: [ wayland_client_dep ] 是链接边,不只是头。egl 成员对同一个包的 依赖只是头。两者在 manifest 里长得一样而实质不同,所以都写明了。 ## 又一处编译期宿主路径 xcursor.c:493 写死 "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:" "/usr/share/cursors/xorg-x11:" ICONDIR 重定位之后这些指的是**宿主的**主题,和 Vulkan 静默落到 llvmpipe 同一形状。 xcursor_library_path() 先读 getenv("XCURSOR_PATH") 且设置了就原样返回 (xcursor.c:515),所以这是兜底而非唯一入口——生态给光标主题时声明 XCURSOR_PATH,和给键盘布局声明 XKB_CONFIG_ROOT 完全对称。 所以 -DICONDIR="" -DXCURSORPATH="",空了之后没有 XCURSOR_PATH 的客户端找不到 主题、wl_cursor_theme_load 返回一个没有光标的主题——看得见,而不是「在开发者 机器上有指针、在别处没有」。 SONAME 是 .so.0 不是 .so.1:上游故意把这个库版本成 0.<minor>.<micro>,好让 wayland 1.x 永远不会逼出一次 cursor SONAME 升版,meson.build 里有注释和一个 wayland 到 2.x 就触发的 error()。 ## CI SONAME 循环加一行;新增一步逐个点名六个函数、断言 DT_NEEDED 有 libwayland-client.so.0、并 grep 二进制确认宿主光标路径确实没被编进去——最后 这条只能这么查,因为值错了的失败方式是**在恰好有 /usr/share/icons 的机器上 静默正常工作**。 本地实测:6/6 符号、链了 client、无宿主路径、三个 SONAME 全对。
1 parent 3d1757a commit 8528f14

4 files changed

Lines changed: 178 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ jobs:
107107
# ends in `.1` and would have been searched for by its full name.
108108
for pair in "client:libwayland-client.so.0" \
109109
"server:libwayland-server.so.0" \
110-
"egl:libwayland-egl.so.1"; do
110+
"egl:libwayland-egl.so.1" \
111+
"cursor:libwayland-cursor.so.0"; do
111112
member=${pair%%:*}; want=${pair#*:}
112113
so=$(find . -name "${want%.*}" -type f | head -1)
113114
test -n "$so" || { echo "$member: no library built"; exit 1; }
@@ -169,6 +170,38 @@ jobs:
169170
fi
170171
echo "libwayland-egl exports exactly its own four functions"
171172
173+
- name: libwayland-cursor carries its six functions and no host paths
174+
run: |
175+
c=$(find . -name libwayland-cursor.so -type f | head -1)
176+
test -n "$c" || { echo "::error::libwayland-cursor was not built"; exit 1; }
177+
178+
has() { readelf --dyn-syms -W "$1" | grep -q " $2$"; }
179+
for f in wl_cursor_theme_load wl_cursor_theme_destroy \
180+
wl_cursor_theme_get_cursor wl_cursor_image_get_buffer \
181+
wl_cursor_frame wl_cursor_frame_and_duration; do
182+
has "$c" "$f" || { echo "::error::libwayland-cursor lost $f"; exit 1; }
183+
done
184+
185+
# It DOES link the client, unlike libwayland-egl: wayland-cursor.c
186+
# calls wl_shm_create_pool and friends. A missing DT_NEEDED here would
187+
# mean those calls were satisfied from somewhere else.
188+
readelf -d "$c" | grep -q 'libwayland-client.so.0' || {
189+
echo "::error::libwayland-cursor does not link libwayland-client"; exit 1; }
190+
191+
# And the compiled-in HOST cursor paths are gone. xcursor.c:493 bakes
192+
# in "~/.icons:/usr/share/icons:/usr/share/pixmaps:..." unless
193+
# XCURSORPATH is defined, and after relocation those name the host's
194+
# themes. The recipe empties it so a theme comes from XCURSOR_PATH or
195+
# not at all — and grepping the binary is the only way to see that it
196+
# worked, because a wrong value here fails by SILENTLY WORKING on a
197+
# machine that happens to have themes in /usr/share/icons.
198+
if strings "$c" | grep -qE '/usr/share/icons|/usr/X11R6|xorg-x11'; then
199+
echo "::error::a host cursor path was compiled into libwayland-cursor"
200+
strings "$c" | grep -E '/usr/share/icons|/usr/X11R6|xorg-x11'
201+
exit 1
202+
fi
203+
echo "libwayland-cursor: six functions, links the client, no host paths"
204+
172205
- name: mcpp/generated/ matches what the scanner produces
173206
run: |
174207
# The generated protocol code is checked in because it is the

mcpp.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ members = [
4040
# cover what a COMPOSITOR needs, and a compositor renders into GBM buffers
4141
# instead.
4242
"mcpp/egl",
43+
# The pointer image. Wayland has no server-side cursor, so a client that
44+
# wants one loads a theme itself — and without this member that means
45+
# parsing the XCursor file format by hand. Missing for the same reason
46+
# mcpp/egl was: a compositor draws its own cursor and never asks.
47+
"mcpp/cursor",
4348
]

mcpp/cursor/mcpp.toml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# libwayland-cursor — the pointer image a Wayland client sets on itself.
2+
#
3+
# Wayland has no server-side cursor: a client that wants a pointer must load a
4+
# cursor theme, turn the image into a `wl_buffer`, and attach it to a surface it
5+
# passes to `wl_pointer.set_cursor`. This library is the first two steps, and
6+
# without it every application would have to parse the XCursor file format
7+
# itself — which is what this file's `xcursor.c` is.
8+
#
9+
# NOT the same thing as `compat.xcursor` in the index: that is X11's
10+
# `libXcursor.so.1`, which talks to an X server. This one has no X dependency
11+
# and produces a `wl_buffer` through `wl_shm`.
12+
#
13+
# It was missing for the same reason libwayland-egl was: the fork built the four
14+
# members a COMPOSITOR needs, and a compositor draws its own cursor.
15+
#
16+
# ─────────────────────────────────────────────────────────────────────────
17+
# THIS ONE REALLY LINKS THE CLIENT, unlike mcpp/egl
18+
#
19+
# `wayland-cursor.c` calls `wl_shm_create_pool`, `wl_shm_pool_create_buffer`,
20+
# `wl_shm_pool_resize` and `wl_buffer_destroy` — so upstream's
21+
# `dependencies: [ wayland_client_dep ]` is a link edge, not just headers. The
22+
# egl member's dependency on the same package is headers only; the two look
23+
# alike in the manifest and are not alike, which is why both say so.
24+
[package]
25+
namespace = "freedesktop"
26+
name = "wayland-cursor"
27+
version = "1.26.0"
28+
standard = "c++23"
29+
description = "libwayland-cursor 1.26.0 — load an XCursor theme into a wl_buffer, for clients that set their own pointer"
30+
license = "MIT"
31+
repo = "https://github.com/mcpplibs/wayland"
32+
33+
[build]
34+
sources = [
35+
"../../upstream/cursor/wayland-cursor.c",
36+
"../../upstream/cursor/os-compatibility.c",
37+
"../../upstream/cursor/xcursor.c",
38+
"src/wayland-cursor.cppm",
39+
]
40+
41+
include_dirs = [
42+
"../../upstream/cursor",
43+
"../../upstream/src",
44+
"../generated",
45+
]
46+
47+
cflags = [
48+
# os-compatibility.c needs memfd_create / mkostemp and does not define
49+
# _GNU_SOURCE itself. The empty form for the same reason the client member
50+
# uses it: the neighbouring files DO define it, and a valued -D would make
51+
# each of them warn about a redefinition.
52+
"-D_GNU_SOURCE=",
53+
54+
# The cursor theme search path, and it is EMPTY on purpose — the same
55+
# decision as compat.libinput's LIBINPUT_QUIRKS_DIR and libgbm's backend
56+
# path, reached for the same reason and with the same escape hatch.
57+
#
58+
# `xcursor.c:493` compiles in
59+
#
60+
# "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:"
61+
# "/usr/share/cursors/xorg-x11:" ICONDIR
62+
#
63+
# — a list of HOST paths, baked in. After relocation those name the host's
64+
# themes, so a sandboxed client would silently render the host machine's
65+
# cursors, which is the same silent host edge that gave Vulkan an llvmpipe
66+
# device instead of the GPU.
67+
#
68+
# `xcursor_library_path()` reads `getenv("XCURSOR_PATH")` FIRST and returns
69+
# it verbatim when set (xcursor.c:515), so this is a fallback and not the
70+
# only way in: an ecosystem that ships cursor themes declares XCURSOR_PATH,
71+
# exactly as it declares XKB_CONFIG_ROOT for keyboard layouts.
72+
#
73+
# Emptied, a client with no XCURSOR_PATH finds no theme and
74+
# `wl_cursor_theme_load` returns a theme with no cursors — visible, rather
75+
# than a pointer that works on the developer's machine and nowhere else.
76+
'-DICONDIR=""',
77+
'-DXCURSORPATH=""',
78+
79+
"-fPIC",
80+
"-fvisibility=hidden",
81+
]
82+
83+
[lib]
84+
path = "src/wayland-cursor.cppm"
85+
86+
[targets.wayland-cursor]
87+
kind = "shared"
88+
# `.so.0`, not `.so.1`: upstream versions this library `0.<minor>.<micro>` on
89+
# purpose so that wayland 1.x never forces a cursor SONAME bump — there is a
90+
# comment in upstream/cursor/meson.build explaining it, and an `error()` that
91+
# fires if wayland ever goes to 2.x.
92+
soname = "libwayland-cursor.so.0"
93+
94+
[dependencies]
95+
# A LINK dependency, see the header comment. libwayland-client provides the
96+
# wl_shm calls this library makes.
97+
freedesktop.wayland = { path = "../client" }
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// freedesktop.wayland.cursor — libwayland-cursor, as a C++23 module.
2+
//
3+
// A module wrapper and nothing more: every name below is upstream's, spelled
4+
// upstream's way, with upstream's signature. `import freedesktop.wayland.cursor;`
5+
// replaces the #include and changes nothing else.
6+
//
7+
// HAND-WRITTEN, like the egl wrapper and for the same reason: the public
8+
// surface is two structs and six functions, small enough to read in one glance
9+
// and checked one by one in `tests/`. `mcpp/tools/genmod.py` exists because the
10+
// client and server wrappers re-export roughly two hundred names each.
11+
//
12+
// Nothing here is `static inline`, so every name is a plain `using ::name;` —
13+
// no `export inline` forwarders, unlike the client and server modules where
14+
// wayland-scanner's convenience wrappers force them.
15+
//
16+
// THE TWO STRUCTS ARE EXPORTED AS TYPES, NOT AS OPAQUE HANDLES, and the
17+
// difference matters to a caller: `wl_cursor` and `wl_cursor_image` have their
18+
// fields in the public header — `image_count`, `images`, `hotspot_x` — and a
19+
// client reads them directly to place the pointer. `wl_cursor_theme` is opaque
20+
// and stays so.
21+
module;
22+
23+
#include <wayland-cursor.h>
24+
25+
export module freedesktop.wayland.cursor;
26+
27+
export {
28+
29+
// -- types --
30+
using ::wl_cursor_theme; // opaque
31+
using ::wl_cursor; // fields are public: image_count, images, name
32+
using ::wl_cursor_image; // fields are public: width, height, hotspot_*, delay
33+
34+
// -- functions --
35+
using ::wl_cursor_theme_load;
36+
using ::wl_cursor_theme_destroy;
37+
using ::wl_cursor_theme_get_cursor;
38+
using ::wl_cursor_image_get_buffer;
39+
using ::wl_cursor_frame;
40+
using ::wl_cursor_frame_and_duration;
41+
42+
} // export

0 commit comments

Comments
 (0)