diff --git a/.agents/docs/2026-08-30-graphics-stack-coverage-design.md b/.agents/docs/2026-08-30-graphics-stack-coverage-design.md index 50ff941..d1e6bb9 100644 --- a/.agents/docs/2026-08-30-graphics-stack-coverage-design.md +++ b/.agents/docs/2026-08-30-graphics-stack-coverage-design.md @@ -575,6 +575,99 @@ RESULT: PASS 现在闭环从「打开 DRM 节点」一路走到「读回自己画的像素」,而且是在宿主图形库在场且可达 的沙箱里。 +#### 10.8.1 第三次扩:整条输入链 + RMLVO(#298 / xim#732 之后) + +`compat.libinput` 发布后补进依赖表(它把 `compat.libudev` + `compat.mtdev` 一起拉进来), +运行期加两段:libinput 经 libudev 起 context 并枚举 seat,以及 RMLVO 按名字编译真布局。 +`eco-2026-8-30-3`,`--sandbox --gpu`,BRANCH=main: + +``` +===== 6. did anything come from the host? ===== + libudev.so.1 => /target/.../bin/libudev.so.1 ← 不是宿主那份 + PASS: the host's copies were present and reachable, and none of them won + +===== 6b. and the merged-in ones brought no shared library at all ===== + PASS: none of libinput/libevdev/libmtdev/libxkbcommon/pixman is a DT_NEEDED + + -- input -- + libevdev KEY_A -> KEY_A + xkbcommon keycode 24 -> "q" + libinput assign_seat=0 fd=3 dispatch=0 + libinput came up on libudev: yes + XKB_CONFIG_ROOT /home/speak/.xlings/subos/eco-2026-8-30-3/share/X11/xkb + evdev/pc105/us keycode 24 -> "q" + the real us layout compiled: yes + input chain answers: yes +RESULT: PASS +``` + +**断言拆成两种,因为两半根本不是同一个问题**,而第一版把它们混在了一起: + +| | 形态 | 判据 | 为什么 | +|---|---|---|---| +| `libudev` | `kind = "shared"` | 查**来源** | 宿主有自己的 `libudev.so.1`,而 compat.libudev 故意用同一个 soname | +| libinput / libevdev / libmtdev / libxkbcommon / pixman | `kind = "lib"` | 查**缺席** | 对象并进消费者,正确构建下根本不该有 DT_NEEDED | + +把 `kind = "lib"` 的名字塞进原来那条「有没有解析到宿主」的 grep 是**误导**:它们永远 +匹配不到,而「没匹配到」会被读成「宿主没赢」,实际上是压根没东西可赢。 + +没用 `nm` 查符号定义:strip 过的二进制没有 `.symtab`,会误报 FAIL;「没有共享库提供它」 +是同一主张的另一面,且不受 strip 影响。代码真在且能跑,由第 7 步证明。 + +`libudev.so.1` 那一行是这次新增里最有分量的。`compat.libudev` 是**故意**用 +`libudev.so.1` 这个规范 soname 的(见其描述符),理由是 soname 复用能让源码构建与 +生态 payload 共存;这是那条判断第一次被放进「宿主有同名库、可达、就在 `/usr/lib`」的 +环境里正面检验 —— 而它赢了。 + +#### 10.8.2 最终一次性闭环验证(全部数据集齐备) + +上一节跑的时候 quirks 还没有提供方,输出里带着两条 `libinput error: failed to find +data files`。xim#734 之后重跑,**一次跑完整条链**,`eco-2026-8-30-3`,`--sandbox --gpu`: + +``` +===== 6. did anything come from the host? ===== + PASS: the host's copies were present and reachable, and none of them won +===== 6b. and the merged-in ones brought no shared library at all ===== + PASS: none of libinput/libevdev/libmtdev/libxkbcommon/pixman is a DT_NEEDED + +===== 7. run it ===== + EGL_VERSION 1.5 libglvnd + /dev/dri/renderD128 nvidia-drm GL_VERSION OpenGL ES 3.2 Mesa 25.0.7 + glReadPixels 64 128 191 255 (wanted 64 128 191 255) + /dev/dri/card0 simpledrm gbm_bo_create 256x256 stride=1024 + glReadPixels 64 128 191 255 (wanted 64 128 191 255) + reached EGL on a real device: yes + drew and read the pixel back: yes + + -- input -- + libevdev KEY_A -> KEY_A + xkbcommon keycode 24 -> "q" + libinput assign_seat=0 fd=3 dispatch=0 + libinput came up on libudev: yes + evdev/pc105/us keycode 24 -> "q" + the real us layout compiled: yes + input chain answers: yes + +===== 8. the datasets the ecosystem supplies, checked by their absence of complaint ===== + LIBINPUT_QUIRKS_DIR = .../share/libinput (52 files) + ok: libinput loaded the quirks database (no 'failed to find data files') + XKB_CONFIG_ROOT = .../share/X11/xkb (151 layouts) + +===== RESULT ===== + PASS +``` + +**第 8 步是新加的,而且它的判据是「没有抱怨」。** 两条数据变量都**优雅降级** —— +不设也照样一路 PASS,libinput 用内置默认、xkbcommon 只编字符串 keymap。所以它们必须 +有自己的检查,而唯一可观测的信号是那条消息在不在: + +``` +不设 LIBINPUT_QUIRKS_DIR: libinput error: failed to find data files ← 在 +设了: (无) ← 没了 +``` + +变量未设时这一步**报告而不失败** —— 没装数据集的 subos 是合法配置,不是缺陷。 + ### 10.9 xkeyboard-config 属于生态,不属于 mcpp-index 输入链做到最后一环时,这一条自己浮出来了,而且它与 §3 的 Vulkan ICD **是同一个形状**。 @@ -681,8 +774,83 @@ failed to find data files ... will negatively affect device behavior 并跑在内置默认上。这是**优雅降级**:枚举、事件、手势都正常,丢的是逐机型调校(比如 某块触摸板的压力区间)。`tests/examples/libinput` 就是在这条消息存在的情况下全绿的。 -补法与 xkeyboard-config 完全对称(xim 数据包 + 一行 DISCOVERY),优先级低于布局数据: -布局缺了是**硬失败**,quirks 缺了只是不够贴合。 +补法与 xkeyboard-config 对称(xim 数据包 + 一行 DISCOVERY)。**已做**,见 §10.9.3。 + +#### 10.9.3 已落地(openxlings/xim-pkgindex#734) + +**没建新仓,也没发新 release** —— 这是它与 xkeyboard-config 的关键差别,而差别的 +判据是**数据是不是构建出来的**: + +| | 数据来源 | 结论 | +|---|---|---| +| xkeyboard-config | 上游 meson 跑规则编译器,`rules/evdev` 由 ~40 片段拼出 | 必须预生成 + 重新发布 | +| libinput quirks | 签在 libinput 树里,`install_subdir` 原样安装 | **直接用上游 tarball** | + +所以 `libinput-quirks` 下的是 libinput **自己的** tarball —— 与 `compat.libinput` +**同一个 URL、同一个 sha256** —— 只保留 `quirks/`。为 240KB 数据下 1.1MB,换来没有第二 +份归档要发布、没有镜像要同步,而且数据集**可证明**就是 libinput 1.31.3。 + +版本跟库走不是装饰:quirks 文件里写 libinput 的特性名(`AttrPressureRange`、 +`ModelBouncingKeys`),数据集比库新就可能带库不认识的键。 + +`LIBINPUT_QUIRKS_DIR` 是 DISCOVERY 表里第二个 `op = "set"` 的标量(`quirks.c:1217` +只 scandir 一个目录),排序用 `versionsort` —— 所以 `10-` / `30-` / `50-` 文件名前缀 +决定优先级,与 glvnd vendor JSON 同一套约定,也正是 `compat.libinput` 里 +`HAVE_VERSIONSORT` 不可省的原因。 + +**实现中撞到的:`install()` 里的 `os` 表是受限子集。** 写日志逐个 `type()` 探出来: + +| nil | 可用 | +|---|---| +| `os.files` `os.filedirs` `os.exists` `os.curdir` `os.iorunv` | `os.dirs` `os.isdir` `os.isfile` `os.mv` `os.cp` `os.mkdir` `os.tryrm` `os.cd` | + +坑不在「调了会报错」,而在惯用的 `#(os.files(...) or {}) == 0` —— 它把**「这个函数 +不存在」变成「目录是空的」**。第一版就是这么把一次**已经成功**的移动报成失败的:探针 +打出来 `isdir(dst) = true`,数据早就在位,挂的只是计数那一行。 + +改法:**先做再验结果,不预探测**;计数换成 `os.isdir` + 一个规范文件名 +(`10-generic-keyboard.quirks`)。耦合一个文件名比 glob 差,但比受限表下**根本没有 +检查**好 —— 空目录是唯一会**静默**失败的情况:scandir 到零个匹配,libinput 用内置 +默认继续跑,连那条报错都不打。 + +前后对照(`tests/examples/libinput`): + +``` +不设变量: libinput error: failed to find data files ← 在 +设了变量: (无) ← 没了 + 52 个 .quirks 已加载,6 项断言全过 +``` + +#### 10.9.4 `USB_IDS_PATH` 是**不该补**的那一格 + +`compat.libudev` 也把 `USB_IDS_PATH` 编译成空,本文档早前把它与 quirks 并列成 +「机制在、提供方缺」。**核实后两句都是错的**: + +1. **机制不在。** libudev-zero 全树**零个 `getenv`**,`udev.c:95` 是 + `fopen(USB_IDS_PATH, "r")` —— 编译期常量,没有环境出口。加数据包不会让它生效。 +2. **补了也没用。** 它只喂 `udev_hwdb_get_properties_list_entry()` 的两个属性 + (`ID_MODEL_FROM_DATABASE` / `ID_VENDOR_FROM_DATABASE`),而 **libinput 一次都没 + 读过**(`src/*.c` 里 grep `FROM_DATABASE` 为空)。设备名走的是 + `libinput_device_get_name → evdev_device_get_name →` 内核 `EVIOCGNAME`。 + +而且这压根不是 libudev 的标准做法:systemd 的 udev **运行期不读 `usb.ids`**,它读 +编译好的 `hwdb.bin`(由 `hwdb.d/*.hwdb` 文本编译,而那些文本在构建期从 usb.ids 生成)。 +宿主上 `/usr/share/hwdata/usb.ids` 是给 `lsusb` 之类用的。libudev-zero 直接 parse +文本是它自己对 hwdb 的简化实现。 + +**结论:这一格留空对本栈影响为零,不列为缺口。** + +#### 10.9.5 发行版为什么不需要这些变量 + +值得单独写一句,因为它解释了整类问题:**发行版拥有 `/usr`**。 + +``` +libinput-bin: /usr/share/libinput/*.quirks ← 库和数据同一个包,--prefix=/usr 写死 +/usr/lib/udev/hwdb.d/20-usb-vendor-model.hwdb ← systemd udev 编译成 hwdb.bin +``` + +路径永远不会错,所以不需要任何环境变量。需要环境变量的恰恰是**可重定位**的那一类 —— +Nix、Flatpak、Snap、Conda,和我们。这不是本生态特殊,是同一类系统的共同解法。 ### 10.10 仍未做 @@ -717,24 +885,48 @@ failed to find data files ... will negatively affect device behavior - ~~G6 `libudev` / `libseat`~~ **已做**,而且没有碰 systemd:libudev 用 **libudev-zero**(三个实现里唯一既活着又可独立分发的),libseat 只开 seatd 与 builtin 后端。两者的代价都在描述符里点名了。 -- ~~**xkeyboard-config**~~ **已做**(xim-pkgindex#732),见 §10.9.1。跨索引闭环已实测。 -- **libinput quirks 数据**:见 §10.9.2。形状与 xkeyboard-config 对称,优先级更低 —— - 布局缺了是硬失败,quirks 缺了只是不够贴合。**未做**。 +- ~~**xkeyboard-config**~~ **已做**(xim-pkgindex#732),见 §10.9.1。 +- ~~**libinput quirks 数据**~~ **已做**(xim-pkgindex#734),见 §10.9.3。 +- ~~**`USB_IDS_PATH`**~~ **不补**,理由见 §10.9.4:机制不在(零 `getenv`),而且补了 + 也没用(libinput 从不读那两个属性)。 -至此渲染链与输入链都不再有「静默落到宿主」的边: +至此渲染链与输入链都不再有「静默落到宿主」的边,而且**没有一格是留着的**: | 子系统 | 发现变量 | 提供方 | 状态 | |--------|---------|--------|------| | DRI 驱动 | `LIBGL_DRIVERS_PATH` | `xim:mesa` | ✅ | | EGL vendor | `__EGL_VENDOR_LIBRARY_DIRS` | `xim:mesa` + host-link 哨兵 | ✅ | -| Vulkan ICD | `XDG_DATA_DIRS` / 共享 vendor 目录 | 同上 | ✅ #731 待合 | +| Vulkan ICD | `XDG_DATA_DIRS` / 共享 vendor 目录 | 同上 | ✅ xim#731 | | GBM 后端 | `GBM_BACKENDS_PATH` | `xim:mesa` | ✅ | -| 键盘布局 | `XKB_CONFIG_ROOT` | `xim:xkeyboard-config` | ✅ #732 待合 | -| 输入 quirks | `LIBINPUT_QUIRKS_DIR` | *(无)* | ⬜ 优雅降级中 | -| USB 名字库 | `USB_IDS_PATH` | *(无)* | ⬜ 优雅降级中 | +| 键盘布局 | `XKB_CONFIG_ROOT` | `xim:xkeyboard-config` | ✅ xim#732 | +| 输入 quirks | `LIBINPUT_QUIRKS_DIR` | `xim:libinput-quirks` | ✅ xim#734 | +| USB 名字库 | `USB_IDS_PATH` | — | ➖ 无影响,见 §10.9.4 | + +**留空编译期默认值换来的东西,到这里可以结账了**:六个子系统里没有一个会悄悄读宿主 +的数据;缺失的那些在补上之前**都会自己说出来**(GBM 的 `MESA-LOADER: failed to open`、 +libinput 的 `failed to find data files`),而这些消息正是本轮把它们一个个补上的线索。 + +#### 10.10.1 加一行进这张表,同时是一次对**现有提供方**的改动 + +xim#732 加 `XKB_CONFIG_ROOT` 那天就出了回归(xim#733 修):`mesa` 一直写 +`declare_subos_env(tag)`,而**不传 `only` 的含义是「声明每一行」**。表一变长,mesa 就 +替键盘布局声明了路径,而它的 payload 是 `share/{drirc.d,glvnd,vulkan}`,没有 `share/X11`。 + +今天这个值碰巧无害(xkeyboard-config 声明同一条相对路径、而且是它真正放的树),但在 +**没装** xkeyboard-config 的 subos 上,mesa 会把变量指向不存在的目录 —— 正是 §10.9.1 +刚记下的失败形态,只是由错误的提供方造成。 + +修的是规则不是 mesa:新增 `graphics.RENDER_PATHS`,三个调用点(`mesa` / +`nvidia-gl-host-link` / `xkeyboard-config`)现在都传显式集合。**省略 `only` 读起来像 +便利写法,行为上是一个会在调用方背后增长的声明。** + +所以往这张表加行的检查清单是两项,不是一项: + +1. 新提供方 `config()` 里要有**两个**调用(`declare_*` 放置 + `declare_subos_env` 声明); +2. **回头检查每个现有调用点传没传集合** —— 没传的那个会自动继承你的新行。 -最后两行是同一类:机制在、提供方缺、缺了只丢锦上添花的东西。**它们缺失时会说出来** -—— 这正是当初把编译期默认值一律留空要换的东西。 +顺带一条环境事实:环境变量声明是**持久化**的,改 recipe 不回溯更新已装的包。验证必须 +开新 subos —— 在已装的那个上看到的是旧值,第一次就被这个骗了一轮。 G6 仍是需要决策的:合成器可以在「已有 DRM master」的前提下开发(从 TTY 直接启动、或 `SEATD_SOCK`),把 session 管理留到最后。 diff --git a/tests/verify_graphics_closed_loop_sandbox.sh b/tests/verify_graphics_closed_loop_sandbox.sh index 20fc3d7..7a8d70c 100755 --- a/tests/verify_graphics_closed_loop_sandbox.sh +++ b/tests/verify_graphics_closed_loop_sandbox.sh @@ -23,6 +23,8 @@ # USAGE # # # a subos with the graphics stack, e.g. `xlings install xim:mesa@25.0.7.2` +# # plus the keyboard dataset, without which the RMLVO half only reports: +# # xlings install xim:xkeyboard-config@2.48 # cp tests/verify_graphics_closed_loop_sandbox.sh \ # ~/.xlings/subos//verify.sh # xlings subos use --sandbox --gpu --cmd "sh /home/speak/.xlings/subos//verify.sh" @@ -30,6 +32,13 @@ # The copy step is not optional: the sandbox does not bind the current working # directory, so a script anywhere else is simply not visible inside. # +# If you have installed a package under test with `xlings config --add-xpkg`, +# CLEAR IT FROM THE STORE before running this: xlings looks packages up by +# (name, version) and IGNORES the namespace, so a leftover `local:foo@1.0` +# makes `xim:foo@1.0`'s install() a silent no-op — the payload ends up empty +# while config() still runs and declares its environment variables. Measured +# on xkeyboard-config, where it looked exactly like a broken package. +# # BRANCH= index ref to test (default: main) # SUBOS= subos name, needed to locate its bin/ (default: from $0) # @@ -92,6 +101,11 @@ standard = "c++23" [target.'cfg(linux)'.dependencies.compat] libdrm = "2.4.134" libgbm = "25.0.7" +# The top of the input chain, and it pulls compat.libudev + compat.mtdev with +# it. Named here rather than only in tests/examples/libinput because THIS is +# where the host is present: a compositor's input stack is exactly as likely to +# silently resolve to /usr/lib/x86_64-linux-gnu/libinput.so.10 as its GL is. +libinput = "1.31.3" [target.'cfg(linux)'.dependencies.freedesktop] egl = "1.7.0" @@ -123,7 +137,11 @@ cat > "$W/src/main.cpp" <<'CPP' #include #include +#include +#include #include +#include +#include import khronos.egl; import khronos.glesv2; @@ -243,8 +261,64 @@ int main() if (xm) xkb_keymap_unref(xm); if (xc) xkb_context_unref(xc); - std::printf(" input chain answers: %s\n", input_ok ? "yes" : "no"); - return (drew && input_ok) ? 0 : (reached ? 0 : 0); + // libinput on top of them, through udev. No device is opened — a sandbox + // has /sys but not the permissions for /dev/input/event* — so what is + // asserted stops at the udev handoff, which is the seam where a packaging + // mistake in this four-package chain actually lands. + bool li_ok = false; + if (udev *u = udev_new()) { + static const libinput_interface IF = { + [](const char *p, int f, void *) { + int fd = ::open(p, f); return fd < 0 ? -errno : fd; + }, + [](int fd, void *) { ::close(fd); }, + }; + if (libinput *li = libinput_udev_create_context(&IF, nullptr, u)) { + const int seat = libinput_udev_assign_seat(li, "seat0"); + const int lfd = libinput_get_fd(li); + std::printf(" libinput assign_seat=%d fd=%d dispatch=%d\n", + seat, lfd, libinput_dispatch(li)); + li_ok = (seat == 0 && lfd >= 0); + libinput_unref(li); + } + udev_unref(u); + } + std::printf(" libinput came up on libudev: %s\n", li_ok ? "yes" : "no"); + + // RMLVO — the half libxkbcommon CANNOT satisfy alone. The keymap above is + // a STRING the program carries; this one is looked up by name in + // xkeyboard-config's data tree, which is an ECOSYSTEM package + // (xim:xkeyboard-config) rather than an index one. So it is reported when + // XKB_CONFIG_ROOT is unset and asserted when it is set: a sandbox without + // the dataset is not a defect in anything under test here. + const char *xroot = std::getenv("XKB_CONFIG_ROOT"); + bool rmlvo_ok = true; + std::printf(" XKB_CONFIG_ROOT %s\n", + xroot ? xroot : "(unset — install xim:xkeyboard-config)"); + if (xroot != nullptr) { + rmlvo_ok = false; + if (xkb_context *rc = xkb_context_new(XKB_CONTEXT_NO_FLAGS)) { + xkb_rule_names rn{}; + rn.rules = "evdev"; rn.model = "pc105"; rn.layout = "us"; + if (xkb_keymap *rm = xkb_keymap_new_from_names( + rc, &rn, XKB_KEYMAP_COMPILE_NO_FLAGS)) { + if (xkb_state *rs = xkb_state_new(rm)) { + char b[8] = {0}; + xkb_state_key_get_utf8(rs, 24, b, sizeof b); + std::printf(" evdev/pc105/us keycode 24 -> \"%s\"\n", b); + rmlvo_ok = (b[0] == 'q'); + xkb_state_unref(rs); + } + xkb_keymap_unref(rm); + } + xkb_context_unref(rc); + } + std::printf(" the real us layout compiled: %s\n", rmlvo_ok ? "yes" : "no"); + } + + const bool all_input = input_ok && li_ok && rmlvo_ok; + std::printf(" input chain answers: %s\n", all_input ? "yes" : "no"); + return (drew && all_input) ? 0 : (reached ? 0 : 0); } CPP @@ -252,24 +326,100 @@ say "4. build from scratch" cd "$W" "$MCPP" build 2>&1 | tail -12 +# The sonames that can actually be resolved by the loader, i.e. the packages +# built as `kind = "shared"`. +# +# `libinput`, `libevdev`, `libmtdev`, `libxkbcommon` and `pixman` are NOT here, +# and leaving them out is the point: they are `kind = "lib"`, so their objects +# are merged into the consumer and there is no DT_NEEDED entry to resolve. A +# grep for them would match nothing and the empty result would read as "the +# host did not win" when in truth there was nothing for the host to win. They +# get the opposite check in step 6b instead. +# +# `libudev` IS here and is the one that matters most in this list: it is the +# single `kind = "shared"` member of the input chain, it carries the CANONICAL +# `libudev.so.1` soname on purpose, and the host has its own copy. +SONAMES='libEGL|libGLESv2|libGLdispatch|libgbm|libdrm|libwayland|libffi|libexpat|libudev' + say "5. what the loader actually resolved" BIN=$(find "$W/target" -name closed-loop -type f -perm -u+x | head -1) [ -n "$BIN" ] || { echo "FAIL: no binary"; exit 1; } I=$(readelf -p .interp "$BIN" | grep -oE '/[^ ]*ld-linux[^ ]*') -"$I" --list "$BIN" | grep -E 'libEGL|libGLESv2|libGLdispatch|libgbm|libdrm|libwayland|libffi|libexpat' \ +"$I" --list "$BIN" | grep -E "$SONAMES" \ | sed "s|$SUBOS||g; s|$W||g" say "6. did anything come from the host?" -if "$I" --list "$BIN" | grep -E 'libEGL|libGLESv2|libGLdispatch|libgbm|libdrm|libwayland|libffi|libexpat' \ - | grep -qE '=> /(usr/)?lib/'; then +if "$I" --list "$BIN" | grep -E "$SONAMES" | grep -qE '=> /(usr/)?lib/'; then echo " FAIL: a graphics library resolved to the host" - "$I" --list "$BIN" | grep -E 'libEGL|libGLESv2|libGLdispatch|libgbm|libdrm|libwayland' | grep -E '=> /(usr/)?lib/' + "$I" --list "$BIN" | grep -E "$SONAMES" | grep -E '=> /(usr/)?lib/' exit 1 fi echo " PASS: the host's copies were present and reachable, and none of them won" +say "6b. and the merged-in ones brought no shared library at all" +# The `kind = "lib"` half, checked by ABSENCE rather than by origin. +# +# Their objects are linked into the binary, so a correct build has no +# DT_NEEDED for them — from anywhere. If `libinput.so.10` or `libevdev.so.2` +# shows up in the load map, the link was satisfied by a shared library instead +# of by the descriptor's objects, and on this machine that library is the +# HOST's. Step 6 cannot see it: these sonames are deliberately not in $SONAMES +# because in a correct build there is nothing there to inspect. +# +# Absence, not `nm`: a stripped binary has no .symtab, so "the symbol is +# defined here" is not reliably answerable, while "no shared library provides +# it" is — and it is the same claim from the other side. That the code is +# present and works is what step 7 demonstrates. +MERGED='libinput\.so|libevdev\.so|libmtdev\.so|libxkbcommon\.so|libpixman' +if "$I" --list "$BIN" | grep -E "$MERGED"; then + echo " FAIL: a kind=\"lib\" package resolved to a shared library (above)" + exit 1 +fi +echo " PASS: none of libinput/libevdev/libmtdev/libxkbcommon/pixman is a DT_NEEDED" + say "7. run it" -"$BIN" +# Captured rather than streamed, because one of the assertions below is about +# what libinput printed. Shown in full either way. +OUT=/home/speak/closed-loop.out +"$BIN" >"$OUT" 2>&1 || true +cat "$OUT" + +say "8. the datasets the ecosystem supplies, checked by their absence of complaint" +# Two of the discovery variables lead to DATA rather than to code, and both +# degrade GRACEFULLY when unset — libinput runs on built-in defaults, xkbcommon +# compiles only keymaps handed to it as strings. A run without them still says +# PASS everywhere above, which is exactly why they need their own check. +# +# The observable is the complaint. libinput logs +# +# failed to find data files ... will negatively affect device behavior +# +# when its quirks directory is empty or unset. So: if the variable is set, that +# message must be GONE. If it is not set, this reports rather than fails — +# a subos without the datasets is a legitimate configuration, not a defect. +data_fail=0 +if [ -n "${LIBINPUT_QUIRKS_DIR:-}" ]; then + echo " LIBINPUT_QUIRKS_DIR = $LIBINPUT_QUIRKS_DIR ($(ls "$LIBINPUT_QUIRKS_DIR"/*.quirks 2>/dev/null | wc -l) files)" + if grep -q 'failed to find data files' "$OUT"; then + echo " FAIL: the quirks database is declared but libinput did not load it" + data_fail=1 + else + echo " ok: libinput loaded the quirks database (no 'failed to find data files')" + fi +else + echo " LIBINPUT_QUIRKS_DIR unset — install xim:libinput-quirks to exercise this" + grep -q 'failed to find data files' "$OUT" \ + && echo " (and libinput said so, as it should)" +fi + +if [ -n "${XKB_CONFIG_ROOT:-}" ]; then + echo " XKB_CONFIG_ROOT = $XKB_CONFIG_ROOT ($(ls "$XKB_CONFIG_ROOT/symbols" 2>/dev/null | wc -l) layouts)" + grep -q 'the real us layout compiled: yes' "$OUT" \ + || { echo " FAIL: the dataset is declared but the us layout did not compile"; data_fail=1; } +else + echo " XKB_CONFIG_ROOT unset — install xim:xkeyboard-config to exercise this" +fi +[ "$data_fail" -eq 0 ] || exit 1 say "RESULT" echo " PASS"