niri-wm/niri · error · anyhow::Error

Condition failed: `outputs.len() == 1`

Error message

Condition failed: `outputs.len() == 1`

What it means

Niri::screenshot_all_outputs implements the compositor-wide screenshot (the 'screenshot' action / screenshot API). It collects all outputs from the global space and asserts exactly one, with a FIXME noting multi-output support (multi-scale handling and cropping) is not implemented. With two or more active outputs the ensure! fails and the screenshot aborts with this condition message.

Source

Thrown at src/niri.rs:5798

        Ok(())
    }

    #[cfg(feature = "dbus")]
    pub fn screenshot_all_outputs(
        &mut self,
        renderer: &mut GlesRenderer,
        include_pointer: bool,
        on_done: impl FnOnce(PathBuf) + Send + 'static,
    ) -> anyhow::Result<()> {
        let _span = tracy_client::span!("Niri::screenshot_all_outputs");

        self.update_render_elements(None);

        let outputs: Vec<_> = self.global_space.outputs().cloned().collect();

        // FIXME: support multiple outputs, needs fixing multi-scale handling and cropping.
        anyhow::ensure!(outputs.len() == 1);

        let output = outputs.into_iter().next().unwrap();
        let geom = self.global_space.output_geometry(&output).unwrap();

        let output_scale = output.current_scale().integer_scale();
        let geom = geom.to_physical(output_scale);

        let size = geom.size;
        let transform = output.current_transform();
        let size = transform.transform_size(size);

        let ctx = RenderCtx {
            renderer,
            target: RenderTarget::ScreenCapture,
            xray: None,
        };
        let elements = self.render_to_vec(ctx, &output, include_pointer);
        let elements = elements.iter().rev();

View on GitHub (pinned to 606284464d)

Solutions

  1. Use the per-output screenshot instead: 'niri msg action screenshot-screen' (screenshots the focused output) — this path has no single-output restriction.
  2. If the all-outputs screenshot is required, temporarily reduce to one active output (disable the others via 'niri msg output <name> off') and re-enable afterwards.
  3. Track/update niri: the FIXME (multi-scale handling and cropping) marks this as a known gap that later versions may close.

Example fix

// before: multiple monitors -> Condition failed: `outputs.len() == 1`
niri msg action screenshot

// after: screenshot the focused output, works with any output count
niri msg action screenshot-screen
Defensive patterns

Strategy: validation

Validate before calling

// Rust: gate the all-outputs screenshot on the supported precondition
let count = niri.global_space().outputs().count();
if count != 1 {
    // route to the per-output path that supports any number of outputs
    return screenshot_focused_output(niri, renderer, on_done);
}

Type guard

fn supports_all_outputs_screenshot(niri: &Niri) -> bool {
    niri.global_space().outputs().count() == 1
}

Try / catch

match niri.screenshot_all_outputs(renderer, include_pointer, on_done) {
    Ok(path) => Ok(path),
    Err(e) if e.to_string().contains("outputs.len() == 1") => {
        // known limitation: retry per-output instead of failing the user action
        niri.screenshot_screen(renderer, /* focused */, include_pointer, on_done)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Invoking a full screenshot while more than one output is connected and enabled: two monitors, or a laptop panel plus an external display. Per-output 'screenshot-screen' takes a different code path and is unaffected.

Common situations: Users with multi-monitor setups pressing the screenshot bind that calls screenshot (all outputs) instead of screenshot-screen; automation/scripts using the screenshot IPC action on multi-head systems; mirroring setups that still count as two outputs.


AI-assisted analysis of niri-wm/niri@606284464d (2026-08-16). Data as JSON: /api/errors/1e4fb3b64bae1745. Report an issue: GitHub.