Skip to content

gfx: survive, and configure, a screen with no window manager - #2183

Open
jcelerier wants to merge 7 commits into
masterfrom
fix/eglfs-screen-placement
Open

gfx: survive, and configure, a screen with no window manager#2183
jcelerier wants to merge 7 commits into
masterfrom
fix/eglfs-screen-placement

Conversation

@jcelerier

Copy link
Copy Markdown
Member

Opening a gfx output window on a board with no X and no Wayland killed score outright. setup_x11() falls back to eglfs there, and eglfs gives a screen to whichever window asks first: the second one is not an error, it is qFatal("EGLFS: OpenGL windows cannot be mixed with others.") inside QEglFSWindow::create(), so the editor being up meant the output took the process down.

Measured on an rk3588 with two HDMI outputs, Qt 6.12, one process per case:

what is opened eglfs
one GL window ok
one widget window ok
several widget windows ok (composited)
widget, then a GL window qFatal
GL window, then a widget ok
two GL windows, one screen qFatal
widget @0 + GL @1, setScreen only qFatal
widget @0 + GL @1, geometry set ok
GL @0 + GL @1, geometry set ok

Two findings, neither in the documentation and both confirmed against the Qt 6.12 sources:

  1. A screen backs exactly one native surface. Raster windows after the first are fine — QOpenGLCompositor draws them onto it — which is why the order matters.
  2. setScreen() does not place a window; its geometry does. The platform makes every window fullscreen and rederives the screen from where it landed. setGeometry(screen->geometry()) before show() is what works.

So ScreenNode and MultiWindowNode ask for a screen nobody holds, place by geometry, and decline to open with a clear message when every screen is taken, instead of aborting.

Configuring the displays

On an appliance there is no xrandr and no display panel: what the platform reads at startup is the only say anyone gets. score::gfx::DisplayConfig holds the outputs and what was asked of them, reads the real ones from /sys/class/drm — which answers before there is a QGuiApplication and while another process holds the display — and produces what each platform understands. It lives in score_lib_base because it runs from main() before plug-ins load.

The platforms are not equivalent and are not pretended to be. eglfs takes connector names, modes, formats, layout and cloning through its JSON. vkkhrdisplay has three integers, addresses displays by index, and cannot be told about layout at all, so it gets its own settings rather than a guessed translation. displayCapabilities() is what the UI asks, so the page offers less where less is possible, and says plainly on a desktop that nothing here applies to it.

requiresRestart is a capability, not an assumption: Windows and macOS can change displays while running, and a change that can be applied live can be undone. Neither backend is implemented yet, which is why perOutputConfiguration stays false there — the page must not offer what nothing behind it will do.

Getting back to an editor

A machine can end up showing output and nothing else, by configuration or because a display setting was wrong. The platform never takes a screen back, so the editor cannot be hidden and revealed — it has to not exist. editorUi decides whether one is built, and Ctrl+Alt+Shift+E writes the setting back and relaunches. On vkkhrdisplay it also sets platformOverride, since that platform creates a window for a widget and then draws nothing into it.

resolvePlatform refuses an override unless score was choosing an embedded platform anyway, so an appliance's file cannot hijack a desktop session.

Tests

test_unit_screen_placement and test_unit_display_config: screen selection, sysfs enumeration against a fixture, KMS JSON generation, save/load round-trip, the capability split, and the platform-override rules. Every commit builds on its own, checked by building each in turn.

@jcelerier
jcelerier force-pushed the fix/eglfs-screen-placement branch 2 times, most recently from 17d941b to 809dde6 Compare August 10, 2026 05:05
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 15.294% (+0.07%) from 15.222% — fix/eglfs-screen-placement into master

@jcelerier
jcelerier force-pushed the fix/eglfs-screen-placement branch from 809dde6 to 3d77a23 Compare August 11, 2026 18:26
jcelerier and others added 7 commits August 17, 2026 20:49
Without a window manager -- eglfs and friends, which is what score falls
back to on a board with no X and no Wayland -- a screen backs exactly one
native surface. Qt does not report the second one as an error: it calls
qFatal("EGLFS: OpenGL windows cannot be mixed with others.") from inside
QEglFSWindow::create(), so opening a gfx output while the editor is up
took the whole process down.

Measured on an rk3588 with two HDMI outputs (Qt 6.12): only the first
window on a screen gets a native surface, and every later one on that
screen must be raster -- widget-then-GL aborts, GL-then-widget composites
fine. setScreen() does not decide where a window lands, either: the
platform makes it fullscreen and rederives the screen from the geometry,
undoing setScreen() on its own. Setting the geometry to the target
screen's is what works.

So ask for a screen nobody holds, place by geometry, and when they are
all taken say so and leave the window unopened rather than aborting.
setRenderer has to tolerate a window that was never shown and therefore
has no render state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wXVcUPzvcDTVzLpWJzeqP
On an appliance there is no xrandr and no display settings panel: the
platform reads its configuration once, when it starts, and that is the
only chance anyone gets to say what the outputs should do. Until now
score had no way to say it, so an embedded board came up with whatever
the driver happened to pick.

score::gfx::DisplayConfig holds the outputs and what was asked of them,
reads the real ones out of /sys/class/drm -- which answers before there
is a QGuiApplication and while another process holds the display -- and
turns the result into what each platform actually understands. It lives
in score_lib_base rather than the gfx plug-in because it has to run from
main() before QGuiApplication exists, long before plug-ins load.

The two platforms are not equivalent and are not pretended to be. eglfs
takes connector names, modes, formats, layout and cloning through its
JSON. vkkhrdisplay has three integers -- physical device, display, mode
-- addresses displays by index rather than name, and cannot be told
about layout at all, so it gets its own settings instead of a
translation that would be a guess. displayCapabilities() is what the UI
asks, so the dialog offers less on vkkhrdisplay rather than lying, and
says plainly on a desktop that none of it applies here.

Only what was actually set is written: an absent key means the driver
decides, which is a better default than anything score could invent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wXVcUPzvcDTVzLpWJzeqP
"Restart to take effect" was written into the feature, but it is not a
property of the feature -- it is a property of the two Linux embedded
backends, which read their configuration once when the platform plug-in
starts. Windows and macOS can be told to change displays while running,
through ChangeDisplaySettingsEx and CGDisplayConfiguration, and a change
that can be applied live can also be undone, so those want a "keep this
setting?" confirmation that eglfs can never offer.

So requiresRestart becomes a capability rather than an assumption, and
appliesToSystemDisplays marks the platforms where score *could* rearrange
the machine's displays -- deliberately not doing so yet, and saying so,
because doing it unasked on somebody's desktop would be hostile.

Enumeration no longer returns nothing off Linux: with no /sys/class/drm
it falls back to Qt's screen list, which knows less -- one mode, the
current one -- but means the dialog lists real outputs on Windows and
macOS instead of being empty. The fallback needs a QGuiApplication, which
the settings UI has and startup does not, so startup still gets the
kernel or nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wXVcUPzvcDTVzLpWJzeqP
A machine with no window manager can end up showing a render output and
nothing else, either because that is what it is for or because a display
setting was wrong and there is no desktop to fix it from. Until now there
was no way back that did not involve another machine and a keyboard.

The platform gives a screen to whichever window asks first and never
takes it back, so the editor cannot be hidden and revealed: it has to not
exist. editorUi says whether to build one at all, and Ctrl+Alt+Shift+E
writes it back and starts score again, which is the only thing that can
change the order. On vkkhrdisplay it also writes platformOverride, since
that platform creates a window for a widget and then draws nothing into
it -- the way back to an editor there is to come up under eglfs.

resolvePlatform refuses to apply an override unless score was choosing an
embedded platform anyway, so a file written for an appliance cannot
hijack a desktop session that happens to read it.

oneWindowPerScreen had grown a second definition once main() needed it
before plug-ins load; it is now one function in score_lib_base, reading
the platform when there is one and the environment when there is not.

The graphics settings become two tabs rather than a page with a dialog
hidden behind a button.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wXVcUPzvcDTVzLpWJzeqP
Qt for WebAssembly has no QProcess, and score builds there. Nothing that
hands a screen to a single window exists on wasm either, so the relaunch
is unreachable there -- but it still has to compile. The setting is
written regardless, so a later launch honours it.
score_plugin_faust keeps faust's include directory private, so linking
the plug-in does not hand the headers over. A distribution puts them
where the compiler looks anyway and nobody noticed; an SDK build, which
is how macOS is built, does not.
@jcelerier
jcelerier force-pushed the fix/eglfs-screen-placement branch from 3d77a23 to 3210a27 Compare August 18, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants