niri-wm/niri · error · anyhow::Error
hdisplay {} must be < hsync_start {}
Error message
hdisplay {} must be < hsync_start {} What it means
calculate_drm_mode_from_modeline converts a user-supplied XRandR-style modeline into a DrmMode, first validating the horizontal timing ordering: hdisplay < hsync_start. The ensure! fails when the active horizontal region is not strictly before sync start, i.e. the numbers were typed or copied out of order. It indicates a malformed modeline in the output config (or an IPC 'output mode' request), not a driver problem.
Source
Thrown at src/backend/tty.rs:3012
}
trace!("queueing estimated vblank timer to fire in {duration:?}");
let timer = Timer::from_duration(duration);
let token = niri
.event_loop
.insert_source(timer, move |_, _, data| {
data.backend
.tty()
.on_estimated_vblank_timer(&mut data.niri, output.clone());
TimeoutAction::Drop
})
.unwrap();
output_state.redraw_state = RedrawState::WaitingForEstimatedVBlank(token);
}
pub fn calculate_drm_mode_from_modeline(modeline: &Modeline) -> anyhow::Result<DrmMode> {
ensure!(
modeline.hdisplay < modeline.hsync_start,
"hdisplay {} must be < hsync_start {}",
modeline.hdisplay,
modeline.hsync_start
);
ensure!(
modeline.hsync_start < modeline.hsync_end,
"hsync_start {} must be < hsync_end {}",
modeline.hsync_start,
modeline.hsync_end
);
ensure!(
modeline.hsync_end < modeline.htotal,
"hsync_end {} must be < htotal {}",
modeline.hsync_end,
modeline.htotal
);
ensure!(View on GitHub (pinned to 606284464d)
Solutions
- Regenerate the modeline instead of hand-editing: 'cvt 2560 1440 144' (or 'gtf') and copy the line verbatim, keeping field order clock hdisplay hsync_start hsync_end htotal vdisplay vsync_start vsync_end vtotal.
- Check the failing pair in the message: ensure hdisplay < hsync_start < hsync_end <= htotal (and the same ordering vertically) in your config.
- Cross-check against the monitor's working mode: 'xrandr' on a running X session shows real timings you can compare with.
- If you only need a resolution/refresh the connector already knows, drop the modeline and use 'mode WxH@R' — or 'mode custom WxH@R' — to let niri compute a CVT mode.
Example fix
// before: fields swapped, hdisplay (2608) >= hsync_start (2560)
output DP-1 {
modeline "241.50 2608 2560 2640 2720 1440 1443 1448 1481 +hsync -vsync"
}
// after: correct ordering from `cvt 2560 1440 144`
output DP-1 {
modeline "241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync"
} Defensive patterns
Strategy: validation
Validate before calling
// Rust: validate modeline ordering before it reaches calculate_drm_mode_from_modeline
fn validate_modeline(m: &Modeline) -> anyhow::Result<()> {
anyhow::ensure!(m.hdisplay < m.hsync_start, "hdisplay {} must be < hsync_start {}", m.hdisplay, m.hsync_start);
anyhow::ensure!(m.hsync_start < m.hsync_end, "hsync_start must be < hsync_end");
anyhow::ensure!(m.hsync_end <= m.htotal, "hsync_end must be <= htotal");
anyhow::ensure!(m.vdisplay < m.vsync_start, "vdisplay must be < vsync_start");
anyhow::ensure!(m.vsync_start < m.vsync_end, "vsync_start must be < vsync_end");
anyhow::ensure!(m.vsync_end <= m.vtotal, "vsync_end must be <= vtotal");
Ok(())
} Type guard
fn is_valid_modeline(m: &Modeline) -> bool {
m.hdisplay < m.hsync_start
&& m.hsync_start < m.hsync_end
&& m.hsync_end <= m.htotal
&& m.vdisplay < m.vsync_start
&& m.vsync_start < m.vsync_end
&& m.vsync_end <= m.vtotal
} Try / catch
match calculate_drm_mode_from_modeline(&modeline) {
Ok(mode) => mode,
Err(err) => {
// mirror niri startup behavior: warn and use advertised/preferred modes
warn!("invalid custom modeline; falling back to advertised modes: {err:?}");
pick_mode(&connector, config.mode).context("no mode")?
}
} Prevention
- Generate modelines with cvt/gtf and paste them unmodified rather than editing numbers by hand.
- Validate the nine numeric fields in config tooling (niri validate) before deploying to machines.
- Remember field order: clock hdisplay hsync_start hsync_end htotal then the vertical four — most bad modelines are off-by-one-field shifts.
- Compare against 'xrandr' timings from a working setup before forcing a modeline.
When it happens
Trigger: Configuring output DP-1 { modeline "..." } with hand-edited numbers where hdisplay >= hsync_start (e.g. swapping the second and third fields), or generating a modeline with a broken cvt/gtf invocation and pasting it unverified. At startup niri only warns ('invalid custom modeline; falling back to advertised modes'), while other callers (e.g. changing mode over IPC) surface the Err directly.
Common situations: Manual transcription of modelines from forum posts with dropped/duplicated fields; editing the clock or hdisplay without shifting the sync timings; using a 9-field vs 10-field modeline format and misaligning fields; typos after copying from xrandr --newmode output.
Related errors
AI-assisted analysis of niri-wm/niri@606284464d (2026-08-16).
Data as JSON: /api/errors/a194ea23f3a0e6a5.
Report an issue: GitHub.