feat(host): list-monitors CLI, and two bugs it caught on-glass

An operator configuring an unattended host has to learn the connector
names from somewhere, and "curl the management API before the host is
configured" is not it. `punktfunk-host list-monitors` prints them with
geometry and flags the pinned one.

Running it under a real sway immediately caught two defects the unit tests
could not:

  - the query used the `swaymsg` command helper, which inserts `--`, so
    `-t get_outputs` came back as "Unknown/invalid command '-t'". Queries
    now go through a `swaymsg_query` helper that cannot make that mistake.
  - compositors write the literal "Unknown" rather than leaving make/model
    empty, so the picker label read "Unknown Unknown". One `describe`
    helper now treats that as absent, for all four backends.

Verified on a headless two-output sway: names, modes, positions, scale and
the case-insensitive pin all resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 23:51:41 +02:00
co-authored by Claude Opus 5
parent 93c2765db7
commit 53c772702f
6 changed files with 118 additions and 32 deletions
@@ -840,14 +840,9 @@ pub(crate) fn list_monitors() -> Result<Vec<crate::monitors::PhysicalMonitor>> {
let (w, h, refresh) = current_mode_full(&state, connector)
.map(|(_id, w, h, hz)| (w.max(0) as u32, h.max(0) as u32, (hz * 1000.0) as u32))
.unwrap_or((0, 0, 0));
let label = format!("{vendor} {product}");
crate::monitors::PhysicalMonitor {
connector: connector.clone(),
description: if label.trim().is_empty() {
connector.clone()
} else {
label.trim().to_string()
},
description: crate::monitors::describe(vendor, product, connector),
width: w,
height: h,
refresh_mhz: refresh,