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

  1. 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.
  2. Check the failing pair in the message: ensure hdisplay < hsync_start < hsync_end <= htotal (and the same ordering vertically) in your config.
  3. Cross-check against the monitor's working mode: 'xrandr' on a running X session shows real timings you can compare with.
  4. 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

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.