{"record":{"id":"a194ea23f3a0e6a5","repo":"niri-wm/niri","slug":"hdisplay-must-be-hsync-start","errorCode":null,"errorMessage":"hdisplay {} must be < hsync_start {}","messagePattern":"hdisplay (.+?) must be < hsync_start (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/backend/tty.rs","lineNumber":3012,"sourceCode":"    }\n\n    trace!(\"queueing estimated vblank timer to fire in {duration:?}\");\n\n    let timer = Timer::from_duration(duration);\n    let token = niri\n        .event_loop\n        .insert_source(timer, move |_, _, data| {\n            data.backend\n                .tty()\n                .on_estimated_vblank_timer(&mut data.niri, output.clone());\n            TimeoutAction::Drop\n        })\n        .unwrap();\n    output_state.redraw_state = RedrawState::WaitingForEstimatedVBlank(token);\n}\n\npub fn calculate_drm_mode_from_modeline(modeline: &Modeline) -> anyhow::Result<DrmMode> {\n    ensure!(\n        modeline.hdisplay < modeline.hsync_start,\n        \"hdisplay {} must be < hsync_start {}\",\n        modeline.hdisplay,\n        modeline.hsync_start\n    );\n    ensure!(\n        modeline.hsync_start < modeline.hsync_end,\n        \"hsync_start {} must be < hsync_end {}\",\n        modeline.hsync_start,\n        modeline.hsync_end\n    );\n    ensure!(\n        modeline.hsync_end < modeline.htotal,\n        \"hsync_end {} must be < htotal {}\",\n        modeline.hsync_end,\n        modeline.htotal\n    );\n    ensure!(","sourceCodeStart":2994,"sourceCodeEnd":3030,"githubUrl":"https://github.com/niri-wm/niri/blob/606284464d4a99bb35710fee68192bc71085ee7c/src/backend/tty.rs#L2994-L3030","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: fields swapped, hdisplay (2608) >= hsync_start (2560)\noutput DP-1 {\n    modeline \"241.50 2608 2560 2640 2720 1440 1443 1448 1481 +hsync -vsync\"\n}\n\n// after: correct ordering from `cvt 2560 1440 144`\noutput DP-1 {\n    modeline \"241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync\"\n}","handlingStrategy":"validation","validationCode":"// Rust: validate modeline ordering before it reaches calculate_drm_mode_from_modeline\nfn validate_modeline(m: &Modeline) -> anyhow::Result<()> {\n    anyhow::ensure!(m.hdisplay < m.hsync_start, \"hdisplay {} must be < hsync_start {}\", m.hdisplay, m.hsync_start);\n    anyhow::ensure!(m.hsync_start < m.hsync_end, \"hsync_start must be < hsync_end\");\n    anyhow::ensure!(m.hsync_end <= m.htotal, \"hsync_end must be <= htotal\");\n    anyhow::ensure!(m.vdisplay < m.vsync_start, \"vdisplay must be < vsync_start\");\n    anyhow::ensure!(m.vsync_start < m.vsync_end, \"vsync_start must be < vsync_end\");\n    anyhow::ensure!(m.vsync_end <= m.vtotal, \"vsync_end must be <= vtotal\");\n    Ok(())\n}","typeGuard":"fn is_valid_modeline(m: &Modeline) -> bool {\n    m.hdisplay < m.hsync_start\n        && m.hsync_start < m.hsync_end\n        && m.hsync_end <= m.htotal\n        && m.vdisplay < m.vsync_start\n        && m.vsync_start < m.vsync_end\n        && m.vsync_end <= m.vtotal\n}","tryCatchPattern":"match calculate_drm_mode_from_modeline(&modeline) {\n    Ok(mode) => mode,\n    Err(err) => {\n        // mirror niri startup behavior: warn and use advertised/preferred modes\n        warn!(\"invalid custom modeline; falling back to advertised modes: {err:?}\");\n        pick_mode(&connector, config.mode).context(\"no mode\")?\n    }\n}","preventionTips":["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."],"tags":["modeline","drm","config","validation","output","mode"],"backgroundTag":"modeline-validation-failed","analyzedSha":"606284464d4a99bb35710fee68192bc71085ee7c","analyzedAt":"2026-08-16T21:48:00.228Z","schemaVersion":2},"datasetVersion":"2026-08-18T00:17:09.346Z"}