Repository navigation
feat(linux): populate the per-monitor display registry (multi-screen M0) - #1851
Merged
Merged
Conversation
This was referenced Oct 7, 2026
Merged
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
…ed names, card prefix) Review of #1851, finding 1. A bare name match could tie a monitor to the wrong connector. The NVIDIA X driver numbers outputs from 0 (DP-0, DP-1) while nvidia-drm connectors start at 1, and two GPUs can each have an HDMI-A-1. - New first rule (randr-edid): when the X server publishes the output's own EDID property (native X; XWayland does not), that is the identity. The connector is the one enabled connector with the same vendor/product/serial, or, among identical ones, the one whose name agrees. os_display_x11_read_monitor_edids reads it, with Xrandr still dlopen'd and the new entry points optional. - A name match now counts only when the connector agrees physically: its modes hold the monitor's device mode, or its EDID mm is within tolerance. The device mode is the compositor's (Mutter) mode, else the RandR rect, never the DRM-derived mode, which was itself found by name. Same-named connectors on two cards are both considered. - The connector name keeps its card prefix ("card1-HDMI-A-1"), and so does the monitor_id hash. - DRM connectors are sorted by name, so DRM-only order is stable across boots. - Connected-but-disabled connectors are excluded from the mm and mode joins. Tests: NVIDIA off-by-one naming, the same name on two cards, a disabled twin of the panel, X server EDID with and without identical connectors, and the sorted sysfs read. plugin-discovery.md §3.4 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
… release them with the instance Review of #1851, finding 2. On POSIX, every installed plug-in was loaded and probed as a claim source even when the outcome was already settled. With XRT_PREFERRED_PLUGIN_ID=sim-display, every in-process app dlopened the Leia plug-in and ran its SR probe against the shared DS1, and that claim-only instance was never released. - The active plug-in wins every monitor it claims (#1521), and only a different pinned plug-in outranks it. When the active plug-in claims every monitor and no other plug-in is preferred, the POSIX source set is now the active plug-in alone, so nothing else is dlopened or probed. The check is repeated on each resolve (a later descriptor set may hold a monitor it does not claim). This covers XRT_PREFERRED_PLUGIN_ID=sim-display and a sim-display-only box. - New target_plugin_release_claim_sources(), called from t_instance_destroy. It calls destroy() on every claim-only instance (never the active one), pairing with the probe() that made it, and drops the cache so the next resolve re-collects. dlopen handles stay loaded, as for every plug-in. Windows keeps its existing behaviour (all registered plug-ins are claim sources for the process lifetime); Android has no claim-only sources. Verified on ds1-linux: XRT_PREFERRED_PLUGIN_ID=sim-display `displays --claims` never loads leia-sr; the default run still resolves eDP-1 -> sim-display and HDMI-1 -> leia-sr, and selftest logs the release of sim-display at teardown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
Review of #1851, finding 3. The placement of the back-compat claim (for a plug-in without probe_displays) now comes from a pure, exported policy, target_plugin_backcompat_claim_index(). synth_primary_edid_claim feeds it the active plug-in's noted panel and the side-table records, so the loader and the tests run the same code. New tests_target_backcompat_claim (links target_lists, like tests_input_host_geometry): - no panel known gives the primary (or descriptor 0, or a primary that is not first); - the DS1 panel lands on the HDMI monitor, not the eDP primary, by connector device mode, and by pixel size when no device mode is known; - a plug-in origin inside a known monitor is trusted first and settles two same-size monitors; - an origin is never matched against DRM-only records (origin unknown); - a panel that matches nothing, or has no pixel size, falls back to the primary. The join cases from the same review item (NVIDIA off-by-one naming, identical panels, disabled connector) landed with the join fix in the previous commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
Review of #1851, finding 4. Refresh came only from the EDID's first detailed timing, and only when that timing matched the mode in use. eDP panels often carry no detailed timing in the base block, so eDP-1 printed 0Hz. Mutter's DisplayConfig already returns each connector's current mode with its refresh. os_display_connector_annotate now keeps it (os_display_desktop_info::native_refresh_mhz, compositor source only; DRM sysfs has no refresh). The join prefers it over the EDID guess. ds1-linux now reports eDP-1 @ 60Hz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Review fixes pushed: one commit per finding.
1. Join hardening (
2. Claim sources (
3. Placement tests (
4. Refresh (
Updated Checks: Not verified on hardware: the X-server EDID rule (this box runs XWayland, which publishes no EDID) and NVIDIA naming. Unit tests cover both. 🤖 Generated with Claude Code |
…RM sysfs) os_display_edid_enumerate was a stub off Windows, so the per-monitor DP registry (#69 / ADR-015) stayed empty on Linux. The new desktop-Linux enumerator ties each RandR monitor (placement rect, primary) to one DRM connector from /sys/class/drm (EDID, connector name, modes): by output name, else a unique physical-size match, else a unique pixel-mode match. It never guesses: an ambiguous monitor is listed without EDID identity. With no X server every connected, enabled connector becomes a DRM-only record flagged origin_unknown. The EDID parser reads the manufacturer/product ids (bytes 8-11), the serial (12-15), the cm size (21/22) and the first detailed timing's pixels, mm and refresh. os_display_edid_monitor gains runtime-private fields (serial, mm, device mode, connector, RandR name, join method, origin_unknown); the plug-in-facing xrt_display_descriptor is unchanged. aux_os stays free of logging and new link dependencies. Multi-screen plan M0, part of #1850. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k-compat claim on the panel POSIX ensure_display_sources consulted only the active plug-in. It now loads every manifest plug-in as a claim source through a POSIX collect_display_sources_platform, the twin of the Windows one: same roots and order as discovery, DXR_PLUGIN_EXCLUSIVE honoured, the active plug-in reused. try_load_one is split into load_and_probe_one + the active-plug-in wrapper, as on Windows. Non-active plug-ins are claim sources only; active selection is unchanged. Android keeps the single active source. A plug-in without probe_displays gets a synthesized EDID claim on the primary monitor. With the registry now populated on Linux, that put the vendor's claim on the laptop screen, and because the active plug-in wins every monitor it claims (#1521) the laptop would route to the vendor DP and the 3D panel to sim-display. Off-Windows the ACTIVE plug-in's synthesized claim now lands on the monitor its panel matches (ADR-033 rules: origin, connector mode, pixel size). The match uses the get_display_info noted by the builder and the display-info apply path (target_plugin_note_active_panel). Windows is unchanged, and a real probe_displays always decides. monitor_id also hashes the connector name when there is one (unchanged on Windows). The loader logs each monitor's join method once at INFO. `displayxr-cli displays` prints serial, mm, device mode, output, connector and join. `displays --claims` brings the system up headlessly first, so its placement matches the runtime's. Multi-screen plan M0, part of #1850. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hardware-free tests for multi-screen M0. The fixtures are the DS1's real EDID base block (ACR 0001, serial 0x322EF05E, DTD 3840x2160@60, 344x193 mm) and an eDP block with no serial and no detailed timing (cm fallback). Cases: name, mm and mode joins; identical panels left unjoined; one connector per monitor; DRM-only records with an unknown origin when no X server is reachable; the sysfs reader against a temp-dir fixture (connected/disconnected, enabled, edid, modes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lug-in claims New §3.4 covers the RandR<->DRM join rules, the DRM-only fallback, the runtime-private extras and monitor_id, POSIX claim collection from every plug-in (and the XRT_PREFERRED_PLUGIN_ID consequence), and the active plug-in's back-compat claim following its panel off-Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed names, card prefix) Review of #1851, finding 1. A bare name match could tie a monitor to the wrong connector. The NVIDIA X driver numbers outputs from 0 (DP-0, DP-1) while nvidia-drm connectors start at 1, and two GPUs can each have an HDMI-A-1. - New first rule (randr-edid): when the X server publishes the output's own EDID property (native X; XWayland does not), that is the identity. The connector is the one enabled connector with the same vendor/product/serial, or, among identical ones, the one whose name agrees. os_display_x11_read_monitor_edids reads it, with Xrandr still dlopen'd and the new entry points optional. - A name match now counts only when the connector agrees physically: its modes hold the monitor's device mode, or its EDID mm is within tolerance. The device mode is the compositor's (Mutter) mode, else the RandR rect, never the DRM-derived mode, which was itself found by name. Same-named connectors on two cards are both considered. - The connector name keeps its card prefix ("card1-HDMI-A-1"), and so does the monitor_id hash. - DRM connectors are sorted by name, so DRM-only order is stable across boots. - Connected-but-disabled connectors are excluded from the mm and mode joins. Tests: NVIDIA off-by-one naming, the same name on two cards, a disabled twin of the panel, X server EDID with and without identical connectors, and the sorted sysfs read. plugin-discovery.md §3.4 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… release them with the instance Review of #1851, finding 2. On POSIX, every installed plug-in was loaded and probed as a claim source even when the outcome was already settled. With XRT_PREFERRED_PLUGIN_ID=sim-display, every in-process app dlopened the Leia plug-in and ran its SR probe against the shared DS1, and that claim-only instance was never released. - The active plug-in wins every monitor it claims (#1521), and only a different pinned plug-in outranks it. When the active plug-in claims every monitor and no other plug-in is preferred, the POSIX source set is now the active plug-in alone, so nothing else is dlopened or probed. The check is repeated on each resolve (a later descriptor set may hold a monitor it does not claim). This covers XRT_PREFERRED_PLUGIN_ID=sim-display and a sim-display-only box. - New target_plugin_release_claim_sources(), called from t_instance_destroy. It calls destroy() on every claim-only instance (never the active one), pairing with the probe() that made it, and drops the cache so the next resolve re-collects. dlopen handles stay loaded, as for every plug-in. Windows keeps its existing behaviour (all registered plug-ins are claim sources for the process lifetime); Android has no claim-only sources. Verified on ds1-linux: XRT_PREFERRED_PLUGIN_ID=sim-display `displays --claims` never loads leia-sr; the default run still resolves eDP-1 -> sim-display and HDMI-1 -> leia-sr, and selftest logs the release of sim-display at teardown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1851, finding 3. The placement of the back-compat claim (for a plug-in without probe_displays) now comes from a pure, exported policy, target_plugin_backcompat_claim_index(). synth_primary_edid_claim feeds it the active plug-in's noted panel and the side-table records, so the loader and the tests run the same code. New tests_target_backcompat_claim (links target_lists, like tests_input_host_geometry): - no panel known gives the primary (or descriptor 0, or a primary that is not first); - the DS1 panel lands on the HDMI monitor, not the eDP primary, by connector device mode, and by pixel size when no device mode is known; - a plug-in origin inside a known monitor is trusted first and settles two same-size monitors; - an origin is never matched against DRM-only records (origin unknown); - a panel that matches nothing, or has no pixel size, falls back to the primary. The join cases from the same review item (NVIDIA off-by-one naming, identical panels, disabled connector) landed with the join fix in the previous commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1851, finding 4. Refresh came only from the EDID's first detailed timing, and only when that timing matched the mode in use. eDP panels often carry no detailed timing in the base block, so eDP-1 printed 0Hz. Mutter's DisplayConfig already returns each connector's current mode with its refresh. os_display_connector_annotate now keeps it (os_display_desktop_info::native_refresh_mhz, compositor source only; DRM sysfs has no refresh). The join prefers it over the EDID guess. ds1-linux now reports eDP-1 @ 60Hz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
force-pushed
the
feat/multi-screen-m0-screen-registry
branch
from
October 7, 2026 20:59
010da94 to
be5490f
Compare
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
…ed names, card prefix) Review of #1851, finding 1. A bare name match could tie a monitor to the wrong connector. The NVIDIA X driver numbers outputs from 0 (DP-0, DP-1) while nvidia-drm connectors start at 1, and two GPUs can each have an HDMI-A-1. - New first rule (randr-edid): when the X server publishes the output's own EDID property (native X; XWayland does not), that is the identity. The connector is the one enabled connector with the same vendor/product/serial, or, among identical ones, the one whose name agrees. os_display_x11_read_monitor_edids reads it, with Xrandr still dlopen'd and the new entry points optional. - A name match now counts only when the connector agrees physically: its modes hold the monitor's device mode, or its EDID mm is within tolerance. The device mode is the compositor's (Mutter) mode, else the RandR rect, never the DRM-derived mode, which was itself found by name. Same-named connectors on two cards are both considered. - The connector name keeps its card prefix ("card1-HDMI-A-1"), and so does the monitor_id hash. - DRM connectors are sorted by name, so DRM-only order is stable across boots. - Connected-but-disabled connectors are excluded from the mm and mode joins. Tests: NVIDIA off-by-one naming, the same name on two cards, a disabled twin of the panel, X server EDID with and without identical connectors, and the sorted sysfs read. plugin-discovery.md §3.4 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
… release them with the instance Review of #1851, finding 2. On POSIX, every installed plug-in was loaded and probed as a claim source even when the outcome was already settled. With XRT_PREFERRED_PLUGIN_ID=sim-display, every in-process app dlopened the Leia plug-in and ran its SR probe against the shared DS1, and that claim-only instance was never released. - The active plug-in wins every monitor it claims (#1521), and only a different pinned plug-in outranks it. When the active plug-in claims every monitor and no other plug-in is preferred, the POSIX source set is now the active plug-in alone, so nothing else is dlopened or probed. The check is repeated on each resolve (a later descriptor set may hold a monitor it does not claim). This covers XRT_PREFERRED_PLUGIN_ID=sim-display and a sim-display-only box. - New target_plugin_release_claim_sources(), called from t_instance_destroy. It calls destroy() on every claim-only instance (never the active one), pairing with the probe() that made it, and drops the cache so the next resolve re-collects. dlopen handles stay loaded, as for every plug-in. Windows keeps its existing behaviour (all registered plug-ins are claim sources for the process lifetime); Android has no claim-only sources. Verified on ds1-linux: XRT_PREFERRED_PLUGIN_ID=sim-display `displays --claims` never loads leia-sr; the default run still resolves eDP-1 -> sim-display and HDMI-1 -> leia-sr, and selftest logs the release of sim-display at teardown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 7, 2026
Review of #1851, finding 3. The placement of the back-compat claim (for a plug-in without probe_displays) now comes from a pure, exported policy, target_plugin_backcompat_claim_index(). synth_primary_edid_claim feeds it the active plug-in's noted panel and the side-table records, so the loader and the tests run the same code. New tests_target_backcompat_claim (links target_lists, like tests_input_host_geometry): - no panel known gives the primary (or descriptor 0, or a primary that is not first); - the DS1 panel lands on the HDMI monitor, not the eDP primary, by connector device mode, and by pixel size when no device mode is known; - a plug-in origin inside a known monitor is trusted first and settles two same-size monitors; - an origin is never matched against DRM-only records (origin unknown); - a panel that matches nothing, or has no pixel size, falls back to the primary. The join cases from the same review item (NVIDIA off-by-one naming, identical panels, disabled connector) landed with the join fix in the previous commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1850. Refs #69 and #1849 (the plan + ADR-047). Multi-screen plan M0 (docs/roadmap/multi-screen.md).
What and why
The per-monitor DP registry (ADR-015) was empty on Linux.
os_display_edid_enumeratewas a stub off Windows, sobuild_dp_registry()did nothing, and POSIX asked only the active plug-in for claims. M0 makes the registry a real screen registry on desktop Linux.Linux monitor enumeration (
os_display_edid_linux.c, desktop Linux only; Android and macOS keep the stub). Each RandR monitor fromos_display_desktop_enumerate(placement rect, primary flag) is tied to one connected DRM connector in/sys/class/drm/card*-*(EDID, status, enabled, modes). The first rule that fires wins:HDMI-A-1→HDMI-1.An ambiguous monitor is listed without EDID identity, never guessed. A connector is used at most once. With no X server, every connected and enabled connector becomes a DRM-only record flagged
origin_unknown. The EDID parser reads the manufacturer and product id (bytes 8-11), the serial (12-15), the size in cm (21/22), and the first detailed timing's pixels, mm and refresh. The loader logs the join method per monitor once at INFO.Claims from every plug-in on POSIX. A new POSIX
collect_display_sources_platformmirrors the Windows one. It uses the same manifest roots and order as discovery, honoursDXR_PLUGIN_EXCLUSIVE, and reuses the active plug-in instead of loading it twice.try_load_oneis split intoload_and_probe_oneplus the active-plug-in wrapper, as on Windows. The other plug-ins are claim sources only: no device is created from them, and active selection is unchanged.The registry is populated at instance create.
displayxr-cli displaysanddisplays --claimslist both monitors with geometry, mm, EDID ids, serial, connector, the resolved plug-in and its confidence.A decision the plan did not cover: where the back-compat claim lands
A plug-in without
probe_displaysgets a synthesized EDID claim on the primary monitor. On this box the primary monitor is eDP-1, and the active plug-in wins every monitor it claims (#1521). The registry would therefore have sent eDP-1 to leia-sr and HDMI-1 to sim-display, which is backwards.Off-Windows, the active plug-in's synthesized claim now lands on the monitor its panel matches. The panel comes from the plug-in's own
get_display_info, which the builder and the display-info apply path record withtarget_plugin_note_active_panel. The match reuses the ADR-033 rules: non-zero origin, then connector mode, then pixel size, with ties broken on physical size (os_display_desktop_select_by_size). Windows keeps the primary-monitor claim unchanged. A plug-in that implementsprobe_displaysnever takes the synthesized path, so the Leia plug-in's real claim decides as soon as that plug-in PR lands.displays --claimsbrings the system up headlessly first, so it reports the same placement as the runtime.Other notes
xrt_display_descriptoris untouched. Connector, mm, device mode and the join method live inos_display_edid_monitor, which is runtime-internal, and in a loader side table keyed bymonitor_id.monitor_idchange on Linux. The id now also hashes the connector name when the platform has one, so DRM-only records at (0, 0) stay distinct. Windows has no connector, so its ids are unchanged.dp_factory_*, and the registry-routed consumers (in-process GL, the D3D11 service) are Windows-only. Weaving is unchanged.XRT_PREFERRED_PLUGIN_ID=sim-display, an installed vendor plug-in is now loaded and probed for its claims.DXR_PLUGIN_EXCLUSIVEstill keeps it out of the process.displayxr-cli displays --claimson ds1-linuxLaptop eDP-1 plus an Acer DS1 on HDMI, GNOME Wayland with XWayland, installed
leia-sr2.10.0 active, devsim-display:Log line:
'leia-sr' has no probe_displays — back-compat claim placed on its panel (monitor 0x886e…, 3840x2160 at (3456,0)) instead of the primary monitor.apis=(none)on leia-sr is not caused by this PR. The installed plug-in (2.10.0) fails the existing #1243vk_bundleABI guard against this runtime (sizeof 2168 vs 2176 in Release), and the scalar path refuses the same factory today. A matching plug-in build showsvk.With
DISPLAYunset (no X server), the same box lists two DRM-only records,HDMI-A-1andeDP-1, each withjoin=drm-onlyand(origin unknown).Tests
tests_aux_display_edid_linux(desktop Linux only, hardware-free):./scripts/build_linux.shand its selftest pass, in both Debug and Release.ctest: the new test plustests_aux_display_desktop_selectpass.tests_rig_composerandtests_oxr_view_space*fail on this box with or without this branch. The same failure counts appear on a Sep-30mainbuild; they depend on the live Leia panel.build-mingw-check.sh aux_ospasses. MinGW cannot compiletarget_plugin_loader.c(<Windows.h>case), so the Windows side of the loader change relies on CI.🤖 Generated with Claude Code