aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-30 20:26:03 +0100
committerDanilo M. <danix@danix.xyz>2026-10-30 20:26:03 +0100
commit5c8aaa581a1c92bd23b0bfb06734f1042facddb4 (patch)
tree5c2641577fd10269731013c29364befebc04af28
parentcfad2b0218dd05676a9a520f21911e49e5e4e5b9 (diff)
downloadquickshell-5c8aaa581a1c92bd23b0bfb06734f1042facddb4.tar.gz
quickshell-5c8aaa581a1c92bd23b0bfb06734f1042facddb4.zip
docs: spec for the monitors settings page
The page owns sections/monitors.lua and regenerates it whole. A change is tried live through hyprctl eval and written only on Keep; Revert or a 15s timeout is hyprctl reload, so a setting that blanks a screen never reaches the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--docs/superpowers/specs/2026-10-30-monitors-page-design.md96
1 files changed, 96 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-30-monitors-page-design.md b/docs/superpowers/specs/2026-10-30-monitors-page-design.md
new file mode 100644
index 0000000..3d06740
--- /dev/null
+++ b/docs/superpowers/specs/2026-10-30-monitors-page-design.md
@@ -0,0 +1,96 @@
+# Monitors: a settings page for the outputs
+
+Date: 2026-10-30
+
+## Goal
+
+Set up the connected monitors from the settings window: mode, scale,
+rotation, VRR and position, with a to-scale layout to drag them into place.
+A change is tried live and only written once kept, so a setting that blanks
+a screen never reaches the config.
+
+## Where it lives
+
+`desktop/modules/monitors/`, a settings `Module` in the Hyprland group of the
+sidebar, beside Keyboard shortcuts and Options:
+
+- `MonitorsModule.qml`: reads `hyprctl monitors -j`, holds the draft, runs
+ the eval, writes the file, runs the revert.
+- `MonitorsPage.qml`: the layout canvas and one card per monitor.
+- `Mon.js`: pure helpers, no QML: the Lua for a monitor and for the whole
+ file, logical size, snapping, mode deduplication.
+- `test_mon.js`: `node desktop/modules/monitors/test_mon.js`, the one check.
+- `README.md`.
+
+## The file
+
+The page owns `~/.config/hypr/sections/monitors.lua` (in `hypr-theme`,
+required by `hyprland.lua`) and regenerates it whole: a header saying the
+keystone settings window writes it, then one `hl.monitor({ ... })` block per
+connected output, in `hyprctl monitors` order, with the fields `output`,
+`mode`, `position`, `scale`, `transform`, and `vrr` only when on. Only
+connected outputs are written; a monitor that is unplugged when the page
+saves drops out of the file. Hand comments in the current file (two) are
+not kept.
+
+## Draft
+
+Read from `hyprctl monitors -j` each time the page opens and after every
+`configreloaded` raw event, unless an Apply is pending. Per monitor:
+
+- `mode`: `WxH@R` from `availableModes`, duplicates removed (the ASUS lists
+ `1920x1080@120.00Hz` twice). Shown with refresh to two decimals. The
+ current mode is matched against the list by width, height and the nearest
+ refresh, since `refreshRate` reads `99.94300` against `99.94Hz`.
+- `scale`: a field, any number Hyprland accepts.
+- `transform`: 0, 90, 180, 270 and a flipped toggle, stored as 0..7.
+- `vrr`: a toggle.
+- `position`: one of `auto`, `auto-left`, `auto-right`, `auto-up`,
+ `auto-down`, `auto-center-left`, `auto-center-right`, `auto-center-up`,
+ `auto-center-down`, or exact `XxY`. hyprctl only reports the resolved
+ `x`/`y`, so the page starts from the exact position; picking an auto value
+ is a choice in the card, and a drag always sets exact coordinates.
+
+Dirty when any field differs from what was read. SaveBar carries Apply and
+Discard.
+
+## Layout canvas
+
+Each monitor is a rectangle at its logical size: width and height swapped
+for an odd transform, both divided by scale. The whole arrangement is fitted
+to the canvas with a margin. Each rectangle shows the output name and model.
+Dragging moves one; on release its position snaps so that a near edge (within
+a threshold in logical pixels) lies on an edge of another monitor, and it
+becomes exact `XxY` in the draft, rounded to integers. A monitor set to an
+auto position is drawn where Hyprland last put it, and dragging it switches
+it to exact.
+
+## Apply, keep, revert
+
+1. Apply runs `hyprctl eval` with every monitor's `hl.monitor` line in one
+ call. Exit 7 is a refusal: nothing changed, the error text shows in
+ SaveBar's `error`, the draft stays dirty.
+2. On success the change is live. A bar shows "Keep these settings?
+ Reverting in 15s", counting down, with Keep and Revert.
+3. Keep writes `monitors.lua` through `FileView.setText`; Hyprland's
+ autoreload rereads it, which is now the same settings.
+4. Revert, or the countdown reaching zero, runs `hyprctl reload`, which
+ reapplies the untouched file. The draft is reread from hyprctl.
+
+Closing the settings window while pending counts as Revert. If the shell dies
+while pending, the file is still the old one and the next reload restores it.
+
+## Not handled
+
+Disabling an output, mirroring, bit depth and colour management; settings for
+an output that is not connected; renaming outputs. The island and the window
+switcher's hot corner are tied to DP-1 by name, which a move does not affect.
+
+## Check
+
+`test_mon.js` asserts: the Lua emitted for a monitor, including `vrr` only
+when on; the logical size of a rotated and of a scaled monitor; a drag within
+the threshold snapping to the other monitor's edge and one outside it not
+snapping; duplicate modes collapsing; the nearest-refresh match picking
+`99.94` for `99.943`. The eval refusal path was measured by hand: `transform
+= 9` exits 7 with `value 9 is more than the maximum of 7`.