diff --git a/mcpp.toml b/mcpp.toml index 1c85667..05895f7 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -90,6 +90,7 @@ members = [ "tests/examples/wayland-cursor", "tests/examples/egl", "tests/examples/gl", + "tests/examples/libdisplay-info", "tests/examples/libevdev", "tests/examples/libinput", "tests/examples/libseat", diff --git a/pkgs/f/freedesktop.libdisplay-info.lua b/pkgs/f/freedesktop.libdisplay-info.lua new file mode 100644 index 0000000..8bf3b22 --- /dev/null +++ b/pkgs/f/freedesktop.libdisplay-info.lua @@ -0,0 +1,61 @@ +-- freedesktop.libdisplay-info — EDID and DisplayID parsing, and the gate on +-- wlroots. +-- +-- A monitor describes itself in a 128-byte EDID blob the kernel exposes through +-- DRM. Parsing it by hand is a known trap — decades of vendor quirks — so +-- wlroots, mutter and KWin all use this library for a display's make, model, +-- serial and mode list. +-- +-- WHY THIS ONE MATTERS MORE THAN ITS SIZE SUGGESTS. Read from wlroots 0.18.2's +-- own meson: `backend/drm/meson.build` declares `libdisplay-info` with +-- `required: 'drm' in backends`, and the DRM backend is what a compositor on +-- real hardware uses. With this package everything wlroots needs is in the +-- index — wayland-server/client/protocols/scanner/egl, libdrm, pixman, +-- xkbcommon, libinput, libudev, libseat, egl, glesv2, gbm — so wlroots becomes +-- a question of whether to package it rather than whether it can be packaged. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- FORM B, AND THE INPUT IS PINNED +-- +-- `pnp-id-table.c` is generated — 2,583 lines mapping every PNP vendor ID to a +-- manufacturer name. Upstream's meson looks for hwdata and, not finding it, +-- falls back to the BUILD MACHINE's `/usr/share/hwdata/pnp.ids`. Measured on +-- 2026-08-30: the host's file and hwdata v0.410's differ by 35 lines, and the +-- tables generated from them by 15 (2568 vs 2583) — so an unpinned build makes +-- `di_info_get_make` answer differently depending on where it was compiled. +-- +-- The fork pins hwdata v0.410, checks the input in beside the output, and its +-- CI regenerates and diffs. Same arrangement as freedesktop.libevdev with +-- upstream's bundled kernel headers. +-- +-- NO MODULE WRAPPER, deliberately: this library's consumer is wlroots, which is +-- C, and the surface is 74 functions over ~360 structs. The design doc's rule +-- applies — the module layer is a RESULT of forking, not a reason to fork. +-- +-- ⚠ C++ CONSUMERS MUST WRAP THE INCLUDES. Not one of the seven public headers +-- has an `extern "C"` block, so a C++ TU mangles every declaration and the link +-- fails with `undefined reference to di_info_get_make(di_info const*)` — naming +-- a symbol that is right there. Same as compat.libseat. +package = { + spec = "1", + namespace = "freedesktop", + name = "libdisplay-info", + description = "libdisplay-info 0.2.0 — EDID and DisplayID parsing, with the PNP vendor table pre-generated from a pinned hwdata", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/libdisplay-info", + type = "package", + + xpm = { + linux = { + ["0.2.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libdisplay-info/releases/download/v0.2.0/libdisplay-info-0.2.0-mcpp.tar.gz", + CN = "https://gitcode.com/mcpp-res/libdisplay-info/releases/download/0.2.0/libdisplay-info-0.2.0-mcpp.tar.gz", + }, + sha256 = "df065a6e040799536a6c93a0202f2766b8c3388b546d0ba4a43290a89aa33ccc", + }, + }, + }, + + mcpp = "*/mcpp/displayinfo/mcpp.toml", +} diff --git a/tests/examples/libdisplay-info/mcpp.toml b/tests/examples/libdisplay-info/mcpp.toml new file mode 100644 index 0000000..b1f1e71 --- /dev/null +++ b/tests/examples/libdisplay-info/mcpp.toml @@ -0,0 +1,11 @@ +# libdisplay-info test member — EDID parsing, the gate on wlroots. +[indices] +freedesktop = { path = "../../.." } + +[package] +name = "libdisplay-info-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.freedesktop] +libdisplay-info = "0.2.0" diff --git a/tests/examples/libdisplay-info/tests/libdisplay-info.cpp b/tests/examples/libdisplay-info/tests/libdisplay-info.cpp new file mode 100644 index 0000000..548001c --- /dev/null +++ b/tests/examples/libdisplay-info/tests/libdisplay-info.cpp @@ -0,0 +1,136 @@ +// freedesktop.libdisplay-info — parse a real monitor's EDID. +// +// EDID parsing needs no device, no compositor and no permissions: it is a +// function from 128 bytes to a struct. So unlike most of this stack, the whole +// library is honestly testable on a CI runner. +// +// THE BLOB IS INLINE, not read from the package's own test data. A test member +// resolves its dependency into the mcpp store, and reaching into that tree by +// relative path would tie this file to a layout that is mcpp's business. 256 +// bytes of a real Acer P1276's EDID, taken from upstream's +// `test/data/acer-p1276.edid`, is cheaper than that coupling. +// +// THE EXPECTED VALUES ARE UPSTREAM'S, from the `.ref` beside that blob: +// +// EDID Structure Version & Revision: 1.3 +// Manufacturer: ACR +// Model: 5654 (0x1616) +// +// WHAT IS ACTUALLY UNDER TEST is the generated PNP table. "ACR" is the raw +// vendor id in the blob; turning it into "Acer Technologies" is what +// `pnp-id-table.c` does — the file this package is a fork in order to +// pre-generate. A build that used the host's pnp.ids instead of the pinned one +// would still parse the EDID and could still answer differently here. + +#ifdef __linux__ + +// WRAPPED, and it has to be: not one of libdisplay-info's seven public headers +// contains an `extern "C"` block — zero occurrences, measured — so a C++ +// translation unit mangles every declaration and the link fails with +// `undefined reference to di_info_get_make(di_info const*)`, naming a symbol +// that is right there in the library. Same as compat.libseat. +extern "C" { +#include +#include +} + +#include +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +// An Acer P1276, from upstream/test/data/acer-p1276.edid. +const unsigned char ACER_P1276[] = { + +0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x72, 0x16, 0x16, + 0x3e, 0x13, 0x00, 0x00, 0x17, 0x17, 0x01, 0x03, 0x0e, 0x00, 0x00, 0x00, + 0x2f, 0x41, 0xf0, 0x9e, 0x5c, 0x58, 0x89, 0x25, 0x16, 0x53, 0x5f, 0x3f, + 0xcf, 0x80, 0x31, 0x7c, 0x45, 0x7c, 0x61, 0x7c, 0x81, 0x80, 0xa9, 0x40, + 0x95, 0x00, 0x81, 0xc0, 0x81, 0x00, 0x64, 0x19, 0x00, 0x40, 0x41, 0x00, + 0x26, 0x30, 0x18, 0x88, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, 0x78, 0x1e, 0x64, 0x11, 0x00, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x50, + 0x31, 0x32, 0x37, 0x36, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x4a, 0x47, 0x47, 0x31, 0x31, 0x30, 0x30, + 0x31, 0x35, 0x39, 0x30, 0x30, 0x0a, 0x01, 0xa2, 0x02, 0x03, 0x04, 0x00, + 0x66, 0x21, 0x56, 0xaa, 0x51, 0x00, 0x1e, 0x30, 0x46, 0x8f, 0x33, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xab +}; + +} // namespace + +int main() +{ + std::printf(" %zu bytes of EDID\n", sizeof ACER_P1276); + + di_info *info = di_info_parse_edid(ACER_P1276, sizeof ACER_P1276); + check(info != nullptr, "di_info_parse_edid on a real monitor's EDID"); + if (info == nullptr) { + std::printf("\n%d check(s) failed\n", failures); + return 1; + } + + // ── The generated PNP table, which is why this package is a fork ───── + char *make = di_info_get_make(info); + std::printf(" make = %s\n", make ? make : "(null)"); + check(make != nullptr, "di_info_get_make returned a manufacturer"); + check(make != nullptr && std::strstr(make, "Acer") != nullptr, + "…and the pinned PNP table resolved ACR to Acer"); + + char *model = di_info_get_model(info); + std::printf(" model = %s\n", model ? model : "(null)"); + check(model != nullptr, "di_info_get_model returned a model"); + + // ── The parse agrees with upstream's own reference output ──────────── + const di_edid *edid = di_info_get_edid(info); + check(edid != nullptr, "di_info_get_edid exposes the parsed block"); + if (edid != nullptr) { + const int major = di_edid_get_version(edid); + const int minor = di_edid_get_revision(edid); + std::printf(" EDID version %d.%d\n", major, minor); + check(major == 1 && minor == 3, "…and it is 1.3, as upstream's .ref says"); + + const di_edid_vendor_product *vp = di_edid_get_vendor_product(edid); + check(vp != nullptr, "di_edid_get_vendor_product"); + if (vp != nullptr) { + std::printf(" pnp id %.3s, product 0x%04x\n", + vp->manufacturer, vp->product); + check(std::strncmp(vp->manufacturer, "ACR", 3) == 0, + "…and the raw PNP id is ACR"); + check(vp->product == 0x1616, + "…and the product code is 5654, as the .ref says"); + } + } + + std::free(make); + std::free(model); + di_info_destroy(info); + check(true, "di_info_destroy"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif