Skip to content

Commit 7ff07e4

Browse files
committed
输入链上三层:freedesktop.libxkbcommon、compat.libinput、compat.libseat
freedesktop.libxkbcommon 是 fork,因为 xkbcomp 的 parser 由 bison 从 parser.y 生成 (3960 行),而上游构建期就要求 bison >= 3.6。输出是 .y 文件的纯函数,所以签进仓、 CI 重生成再 diff,消费者构建不需要 bison。 生成时的 `-p _xkbcommon_` 前缀是承重的,不是装饰:它重命名 bison 发出的每一个符号。 不加它,这个 parser 会导出 bison 的默认名字,和同进程里任何另一个生成的 parser 撞车。 DFLT_XKB_CONFIG_ROOT 留空。libxkbcommon 只编译 keymap、自身不含任何布局;上游把 xkeyboard-config 的前缀烤进去,重定位后那是宿主的数据集。xkb_context_getenv 先查 环境变量,所以留空能让「数据集缺失」表现为「找不到 keymap」而不是悄悄用宿主的 —— 与 libgbm 的后端路径、libglvnd 的 vendor 目录同一立场。 HAVE_EACCESS 刻意不定义。utils.h 在这个宏下调用 eaccess(),但它自己不 include <unistd.h>,声明在不在作用域里取决于调用方碰巧先包了什么;上游的探测带 <unistd.h> 前缀所以答「有」。实测五个 TU 报 implicit declaration。不定义时 check_eaccess 返回 true —— 那是上游自己给「两个都没有的平台」准备的回退。 compat.libinput 是内联描述符,不需要 fork(源码在 meson 里列着,没有代码生成), 它是四包链的顶:libinput → libevdev / libudev / mtdev。Lua 插件和 libwacom 都关掉, 两者上游都是可选;LIBINPUT_QUIRKS_DIR 留空,理由同上。 三个测试都做真事:libxkbcommon 从字符串编译 keymap(正好跑通那个生成的 parser), keycode 24 → keysym q → UTF-8 "q",9 → Escape。 compat.libinput 的测试成员本轮先不登记:它同时需要 compat.*(自身/libudev/mtdev) 和 freedesktop.libevdev,而两者都在这个 checkout 里 —— 成员级 [indices] 是替换而非 合并,一个 path 也只能挂一个 namespace。等本 PR 合并、libevdev 发布之后再接上。
1 parent 46548d9 commit 7ff07e4

6 files changed

Lines changed: 478 additions & 0 deletions

File tree

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ members = [
9090
"tests/examples/libevdev",
9191
"tests/examples/libseat",
9292
"tests/examples/libudev",
93+
"tests/examples/libxkbcommon",
9394
"tests/examples/mtdev",
9495
"tests/examples/pixman",
9596
"tests/examples/wayland-protocols",

‎pkgs/c/compat.libinput.lua‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
-- compat.libinput — libinput 1.31.3, the input stack a compositor drives.
2+
--
3+
-- Touchpads, mice, tablets, touchscreens, keyboards: libinput turns evdev
4+
-- streams into gestures, scroll, pointer acceleration and tap-to-click. It is
5+
-- the last piece of the input chain — libevdev reads the device, libudev finds
6+
-- it, mtdev normalises multitouch, and libinput makes it behave.
7+
--
8+
-- ─────────────────────────────────────────────────────────────────────────
9+
-- SHAPE: inline, and it is the top of a four-package chain
10+
--
11+
-- compat.libinput
12+
-- ├── freedesktop.libevdev the evdev wrapper
13+
-- ├── compat.libudev libudev-zero, no systemd
14+
-- └── compat.mtdev multitouch protocol A -> B
15+
--
16+
-- None of the four needs a fork of libinput itself: the sources are listed in
17+
-- meson and there is no code generation. The forks in that list are there for
18+
-- their own generated files, not for this one.
19+
--
20+
-- ─────────────────────────────────────────────────────────────────────────
21+
-- WHAT IS DELIBERATELY OFF
22+
--
23+
-- LUA PLUGINS. libinput 1.31 grew a Lua plugin system; upstream makes it
24+
-- optional (`required: get_option('lua-plugins')`). It is off here because a
25+
-- device-input library that embeds a scripting runtime is a larger promise
26+
-- than this package should make on its own — `compat.lua` is in the index for
27+
-- a project that wants it, and turning it on is a descriptor change rather
28+
-- than a fork.
29+
--
30+
-- LIBWACOM. Tablet model data, an optional dependency upstream guards the same
31+
-- way. Nothing in this index provides it, and without it libinput falls back
32+
-- to generic tablet handling rather than failing.
33+
--
34+
-- The quirks database (`quirks.c`) reads `.quirks` files at runtime from a
35+
-- directory compiled in as `LIBINPUT_QUIRKS_DIR`. That path is EMPTY here for
36+
-- the same reason libgbm's backend path and libxkbcommon's config root are:
37+
-- upstream's default points into the build prefix, which after relocation is
38+
-- the HOST's dataset. Empty means devices get libinput's built-in defaults
39+
-- rather than a host machine's model quirks — correct behaviour, one fewer
40+
-- silent host edge.
41+
package = {
42+
spec = "1",
43+
namespace = "compat",
44+
name = "libinput",
45+
description = "libinput 1.31.3 — touchpad, mouse, tablet and keyboard input handling for compositors",
46+
licenses = {"MIT"},
47+
repo = "https://gitlab.freedesktop.org/libinput/libinput",
48+
type = "package",
49+
50+
xpm = {
51+
linux = {
52+
["1.31.3"] = {
53+
url = {
54+
GLOBAL = "https://gitlab.freedesktop.org/libinput/libinput/-/archive/1.31.3/libinput-1.31.3.tar.gz",
55+
CN = "https://gitcode.com/mcpp-res/libinput/releases/download/1.31.3/libinput-1.31.3.tar.gz",
56+
},
57+
sha256 = "b6749bf6f1890f6631c0a70a027c35fec9d2e096a39f720548896e41474a9854",
58+
},
59+
},
60+
},
61+
62+
mcpp = {
63+
language = "c++23",
64+
import_std = false,
65+
c_standard = "c11",
66+
67+
include_dirs = { "*/src", "*/include", "mcpp_generated" },
68+
69+
generated_files = {
70+
["mcpp_generated/config.h"] = [==[
71+
#ifndef MCPP_LIBINPUT_CONFIG_H
72+
#define MCPP_LIBINPUT_CONFIG_H
73+
/* meson's config.h, written out for Linux with a GCC-compatible toolchain.
74+
75+
LIBINPUT_QUIRKS_DIR is EMPTY on purpose. quirks.c reads model-specific
76+
`.quirks` files from it at runtime; upstream's default points into the
77+
build prefix, which after relocation is the HOST's dataset. Empty means a
78+
device gets libinput's built-in defaults instead of another machine's
79+
quirks — the same stance compat.libgbm takes with GBM_BACKENDS_PATH.
80+
81+
HAVE_LUA and HAVE_LIBWACOM are absent: both are optional upstream and both
82+
would add a dependency this package does not need to do its job. */
83+
#define LIBINPUT_QUIRKS_DIR ""
84+
#define LIBINPUT_QUIRKS_SRCDIR ""
85+
#define HAVE_LIBEVDEV_DISABLE_PROPERTY 1
86+
#define HAVE_MEMFD_CREATE 1
87+
#define HAVE_LOCALE_H 1
88+
#define HAVE_STRERRORNAME_NP 1
89+
#endif
90+
]==],
91+
},
92+
93+
sources = {
94+
"*/src/util-files.c",
95+
"*/src/util-list.c",
96+
"*/src/util-ratelimit.c",
97+
"*/src/util-strings.c",
98+
"*/src/util-prop-parsers.c",
99+
"*/src/filter.c",
100+
"*/src/filter-custom.c",
101+
"*/src/filter-flat.c",
102+
"*/src/filter-low-dpi.c",
103+
"*/src/filter-mouse.c",
104+
"*/src/filter-touchpad.c",
105+
"*/src/filter-touchpad-flat.c",
106+
"*/src/filter-touchpad-x230.c",
107+
"*/src/filter-tablet.c",
108+
"*/src/filter-trackpoint.c",
109+
"*/src/filter-trackpoint-flat.c",
110+
"*/src/quirks.c",
111+
"*/src/libinput.c",
112+
"*/src/libinput-plugin.c",
113+
"*/src/libinput-plugin-button-debounce.c",
114+
"*/src/libinput-plugin-mouse-wheel.c",
115+
"*/src/libinput-plugin-mouse-wheel-lowres.c",
116+
"*/src/libinput-plugin-tablet-double-tool.c",
117+
"*/src/libinput-plugin-tablet-eraser-button.c",
118+
"*/src/libinput-plugin-tablet-forced-tool.c",
119+
"*/src/libinput-plugin-tablet-proximity-timer.c",
120+
"*/src/libinput-private-config.c",
121+
"*/src/evdev.c",
122+
"*/src/evdev-fallback.c",
123+
"*/src/evdev-plugin.c",
124+
"*/src/evdev-totem.c",
125+
"*/src/evdev-middle-button.c",
126+
"*/src/evdev-mt-touchpad.c",
127+
"*/src/evdev-mt-touchpad-tap.c",
128+
"*/src/evdev-mt-touchpad-thumb.c",
129+
"*/src/evdev-mt-touchpad-buttons.c",
130+
"*/src/evdev-mt-touchpad-edge-scroll.c",
131+
"*/src/evdev-mt-touchpad-gestures.c",
132+
"*/src/evdev-tablet.c",
133+
"*/src/evdev-tablet-pad.c",
134+
"*/src/evdev-tablet-pad-leds.c",
135+
"*/src/path-seat.c",
136+
"*/src/udev-seat.c",
137+
"*/src/timer.c",
138+
"*/src/util-libinput.c",
139+
-- gated on have_mtdev upstream; mtdev is a declared dependency
140+
-- here, so the plugin is unconditional.
141+
"*/src/libinput-plugin-mtdev.c",
142+
},
143+
144+
cflags = {
145+
"-D_GNU_SOURCE",
146+
"-DHAVE_CONFIG_H",
147+
"-fPIC",
148+
},
149+
150+
ldflags = { "-lm", "-lrt" },
151+
152+
deps = {
153+
["freedesktop.libevdev"] = "1.13.7",
154+
["compat.libudev"] = "1.0.5",
155+
["compat.mtdev"] = "1.1.7",
156+
},
157+
158+
targets = { ["input"] = { kind = "lib" } },
159+
},
160+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- freedesktop.libxkbcommon — libxkbcommon 1.13.2, keyboard handling.
2+
--
3+
-- A compositor gets keycodes from the kernel and has to turn them into keysyms
4+
-- and text: which layout, which modifiers, which compose sequence. That is
5+
-- libxkbcommon, and every Wayland compositor links it.
6+
--
7+
-- ─────────────────────────────────────────────────────────────────────────
8+
-- SHAPE: a fork, for one generated file
9+
--
10+
-- The xkbcomp parser is bison's output from `src/xkbcomp/parser.y` — 3,960
11+
-- lines — and upstream requires bison >= 3.6 at build time. That output is a
12+
-- pure function of the .y file, so it is precomputable: the generator runs
13+
-- once in [mcpplibs/libxkbcommon](https://github.com/mcpplibs/libxkbcommon),
14+
-- the result is checked in, and that fork's CI regenerates and diffs. No bison
15+
-- in a consumer's build.
16+
--
17+
-- The `-p _xkbcommon_` prefix the generator passes is load-bearing rather
18+
-- than cosmetic: it renames every symbol bison emits. Without it this parser
19+
-- would export bison's default names and collide with any other generated
20+
-- parser in the same process.
21+
--
22+
-- ─────────────────────────────────────────────────────────────────────────
23+
-- IT COMPILES KEYMAPS; IT CONTAINS NONE
24+
--
25+
-- The layouts are xkeyboard-config's, a separate dataset. `DFLT_XKB_CONFIG_ROOT`
26+
-- in this build is EMPTY on purpose: `xkb_context_getenv(ctx,
27+
-- "XKB_CONFIG_ROOT")` is consulted first (src/context.c:236) and upstream's
28+
-- default points into the build prefix, which after relocation is the HOST's
29+
-- dataset. Same stance as compat.libgbm with `GBM_BACKENDS_PATH` and
30+
-- freedesktop.egl with `__EGL_VENDOR_LIBRARY_DIRS` — empty, so a missing
31+
-- dataset says "no keymap found" rather than silently using the host's.
32+
--
33+
-- A consumer that only needs `xkb_keymap_new_from_string` — which is how a
34+
-- compositor receives a client's keymap over the wire, and how this package's
35+
-- test exercises the parser — needs no dataset at all.
36+
package = {
37+
spec = "1",
38+
namespace = "freedesktop",
39+
name = "libxkbcommon",
40+
description = "libxkbcommon 1.13.2 — keymap compilation and keyboard state, xkbcomp parser pre-generated",
41+
licenses = {"MIT"},
42+
repo = "https://github.com/mcpplibs/libxkbcommon",
43+
type = "package",
44+
45+
xpm = {
46+
linux = {
47+
["1.13.2"] = {
48+
url = {
49+
GLOBAL = "https://github.com/mcpplibs/libxkbcommon/archive/refs/tags/1.13.2.tar.gz",
50+
CN = "https://gitcode.com/mcpp-res/libxkbcommon/releases/download/1.13.2/libxkbcommon-1.13.2.tar.gz",
51+
},
52+
sha256 = "34f467ef6ec9926a27d174903080b9bde49e4563bf592a183c52eb7f41be93c8",
53+
},
54+
},
55+
},
56+
57+
mcpp = "*/mcpp/xkbcommon/mcpp.toml",
58+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// compat.libinput — the input chain, exercised without a seat.
2+
//
3+
// libinput needs a udev context and either a seat or explicit device paths.
4+
// A CI runner has /sys (so udev enumeration works) but not the permissions to
5+
// open /dev/input/event*, so what is asserted is everything up to and
6+
// including the udev handoff — which is exactly where a packaging mistake
7+
// lands, because that is the seam between the four packages in this chain:
8+
//
9+
// compat.libinput
10+
// ├── freedesktop.libevdev
11+
// ├── compat.libudev
12+
// └── compat.mtdev
13+
//
14+
// Opening real devices is opt-in (MCPP_RUN_INPUT_DEVICES=1).
15+
16+
#ifdef __linux__
17+
18+
#include <libinput.h>
19+
#include <libudev.h>
20+
21+
#include <fcntl.h>
22+
#include <unistd.h>
23+
24+
#include <cstdio>
25+
#include <cstdlib>
26+
#include <cstring>
27+
#include <cerrno>
28+
#include <dlfcn.h>
29+
30+
namespace {
31+
32+
int failures = 0;
33+
34+
void check(bool ok, const char *what)
35+
{
36+
std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED");
37+
if (!ok) {
38+
++failures;
39+
}
40+
}
41+
42+
// libinput hands device opening back to the caller — that is how a compositor
43+
// routes it through libseat. Here it is a plain open(), which is what fails
44+
// without permissions and is why the device half is opt-in.
45+
int open_restricted(const char *path, int flags, void *)
46+
{
47+
const int fd = ::open(path, flags);
48+
return fd < 0 ? -errno : fd;
49+
}
50+
51+
void close_restricted(int fd, void *)
52+
{
53+
::close(fd);
54+
}
55+
56+
const libinput_interface IFACE = {open_restricted, close_restricted};
57+
58+
} // namespace
59+
60+
int main()
61+
{
62+
// ── 1. The chain is linked: libinput, libudev, libevdev, mtdev ───────
63+
// Each of these is a symbol only one of the four packages defines, so the
64+
// set of them says the whole chain resolved rather than just the top.
65+
check(::dlsym != nullptr, "the process has a dynamic linker (trivially)");
66+
udev *u = udev_new();
67+
check(u != nullptr, "libudev: udev_new (compat.libudev)");
68+
if (u == nullptr) {
69+
std::printf("\n%d check(s) failed\n", failures);
70+
return 1;
71+
}
72+
73+
// ── 2. libinput comes up on that udev context ────────────────────────
74+
libinput *li = libinput_udev_create_context(&IFACE, nullptr, u);
75+
check(li != nullptr, "libinput_udev_create_context on it");
76+
if (li == nullptr) {
77+
udev_unref(u);
78+
std::printf("\n%d check(s) failed\n", failures);
79+
return 1;
80+
}
81+
82+
// ── 3. Assigning a seat is where udev enumeration actually runs ──────
83+
// "seat0" is the conventional name and needs no seat manager to NAME —
84+
// libinput asks udev for devices tagged with it. Devices it cannot open
85+
// are skipped, so this succeeds with no permissions.
86+
const int rc = libinput_udev_assign_seat(li, "seat0");
87+
std::printf(" libinput_udev_assign_seat(\"seat0\") = %d\n", rc);
88+
check(rc == 0, "libinput enumerated the seat through libudev");
89+
90+
// ── 4. The event loop is live ────────────────────────────────────────
91+
// A dispatch with nothing pending must return cleanly rather than block
92+
// or fault: it is what a compositor calls every frame.
93+
check(libinput_dispatch(li) == 0, "libinput_dispatch with nothing pending");
94+
const int fd = libinput_get_fd(li);
95+
std::printf(" epoll fd: %d\n", fd);
96+
check(fd >= 0, "libinput_get_fd returns a pollable descriptor");
97+
98+
// Count whatever devices were actually openable. Zero is fine on a
99+
// runner; the assertion is that draining the queue terminates.
100+
int devices = 0, events = 0;
101+
for (libinput_event *ev = libinput_get_event(li); ev != nullptr;
102+
ev = libinput_get_event(li)) {
103+
if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) {
104+
++devices;
105+
if (devices <= 3) {
106+
std::printf(" device: %s\n",
107+
libinput_device_get_name(libinput_event_get_device(ev)));
108+
}
109+
}
110+
++events;
111+
libinput_event_destroy(ev);
112+
}
113+
std::printf(" drained %d event(s), %d device(s) opened\n", events, devices);
114+
check(true, "the event queue drained without blocking");
115+
116+
if (std::getenv("MCPP_RUN_INPUT_DEVICES") != nullptr) {
117+
check(devices > 0,
118+
"MCPP_RUN_INPUT_DEVICES is set, so devices must have opened");
119+
} else {
120+
std::puts(" (opening devices needs permissions: set "
121+
"MCPP_RUN_INPUT_DEVICES=1 where they exist)");
122+
}
123+
124+
libinput_unref(li);
125+
udev_unref(u);
126+
127+
std::printf("\n%d check(s) failed\n", failures);
128+
return failures == 0 ? 0 : 1;
129+
}
130+
131+
#else
132+
int main() { return 0; }
133+
#endif
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# libxkbcommon test project.
2+
[indices]
3+
freedesktop = { path = "../../.." }
4+
5+
[package]
6+
name = "libxkbcommon-tests"
7+
version = "0.1.0"
8+
standard = "c++23"
9+
10+
[target.'cfg(linux)'.dependencies.freedesktop]
11+
libxkbcommon = "1.13.2"

0 commit comments

Comments
 (0)