Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/guide/headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

The emulated core is deterministic and independent of wall-clock pacing, so
the preferred way to verify behaviour -- in CI, in regression tests, or
while developing -- is a headless run: the window stays hidden, the core
runs unthrottled, and the result is reproducible.
while developing -- is a headless run: nothing is presented to the screen,
the core runs unthrottled, and the result is reproducible.

Capture runs (`--screenshot-after`, `--dump-frames`) never open a window or
connect to the host's display server at all, so they work in environments
without one: SSH sessions, CI runners, and sandboxes that block
window-server access.

## Screenshots

Expand Down
13 changes: 13 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,15 @@ fn main() -> Result<()> {
.unwrap_or(true)
});
video::set_pixel_aspect(config::resolve_pixel_aspect(cfg.pixel_aspect));
// Capture runs (--screenshot-after / --dump-frames) never present a
// frame, so they skip the host window and event loop entirely: winit's
// event-loop setup registers with the display server, which aborts or
// blocks on hosts without one (SSH sessions, sandboxes without
// window-server access), and a capture run must work anywhere.
// --control-gui keeps the windowed path: it explicitly asks for an
// interactive session.
let windowless_capture =
(cli.screenshot_after.is_some() || cli.frame_dump.is_some()) && cli.control_gui.is_none();
#[cfg_attr(not(feature = "control"), allow(unused_mut))]
let mut app = App::new(
emu,
Expand Down Expand Up @@ -1628,6 +1637,10 @@ fn main() -> Result<()> {
if realtime_priority && paced {
priority::elevate_pacer_thread();
}
if windowless_capture {
info!("headless capture: running without a window (no display connection)");
return app.run_headless();
}
info!(
"entering event loop. {HOST_SHORTCUT_MODIFIER_LABEL}+Q to quit, {HOST_SHORTCUT_MODIFIER_LABEL}+S to screenshot, {HOST_SHORTCUT_MODIFIER_LABEL}+G to capture/release mouse."
);
Expand Down
Loading
Loading