diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-30 20:34:19 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-30 20:34:19 +0100 |
| commit | ffe1a7ccdd51f760fa1e742c0d09cc229a405544 (patch) | |
| tree | 40c7828f339139fd936ab2521e3eb396c12b21fc /desktop/modules/monitors/Mon.js | |
| parent | ca5b313ae55b393b993488e83e90eeadf6ada38c (diff) | |
| download | quickshell-ffe1a7ccdd51f760fa1e742c0d09cc229a405544.tar.gz quickshell-ffe1a7ccdd51f760fa1e742c0d09cc229a405544.zip | |
monitors: pure helpers for the settings page
The Lua for a monitor and for monitors.lua, logical size, edge snapping
and the mode list, with the duplicates the ASUS reports collapsed and the
current mode matched by nearest refresh (99.943 against 99.94).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/monitors/Mon.js')
| -rw-r--r-- | desktop/modules/monitors/Mon.js | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/desktop/modules/monitors/Mon.js b/desktop/modules/monitors/Mon.js new file mode 100644 index 0000000..9b5e158 --- /dev/null +++ b/desktop/modules/monitors/Mon.js @@ -0,0 +1,100 @@ +.pragma library +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Pure helpers for the Monitors page, tested by test_mon.js under node. +// No String.matchAll, Object.fromEntries or flatMap: the shell's engine has +// none of them (AGENTS.md). + +// Hyprland's automatic positions, as the card offers them. +const AUTO = ["auto", "auto-left", "auto-right", "auto-up", "auto-down", + "auto-center-left", "auto-center-right", "auto-center-up", "auto-center-down"]; + +// availableModes ("1920x1080@120.00Hz") without the suffix, duplicates +// dropped: the ASUS lists 120Hz twice. +function modes(avail) { + const out = []; + for (const a of avail) { + const m = a.replace(/Hz$/, ""); + if (!out.includes(m)) out.push(m); + } + return out; +} + +// The entry of list at w x h with the refresh nearest r: refreshRate reads +// 99.943 against a listed 99.94. Built from the numbers when none matches. +function current(list, w, h, r) { + let best = "", dist = Infinity; + for (const m of list) { + const p = /^(\d+)x(\d+)@([\d.]+)$/.exec(m); + if (!p || +p[1] !== w || +p[2] !== h) continue; + const e = Math.abs(+p[3] - r); + if (e < dist) { dist = e; best = m; } + } + return best || w + "x" + h + "@" + r.toFixed(2); +} + +// A monitor from hyprctl monitors -j as the page edits it. x and y are +// where Hyprland put it, kept to draw an auto position. +function draft(m) { + const list = modes(m.availableModes); + return { name: m.name, make: m.make, model: m.model, modes: list, + mode: current(list, m.width, m.height, m.refreshRate), + scale: String(m.scale), transform: m.transform, vrr: !!m.vrr, + position: m.x + "x" + m.y, x: m.x, y: m.y }; +} + +// Logical size: an odd transform is a quarter turn, scale divides. +function size(mode, scale, transform) { + const p = /^(\d+)x(\d+)/.exec(mode); + let w = +p[1], h = +p[2]; + if (transform % 2) { const t = w; w = h; h = t; } + return { w: w / +scale, h: h / +scale }; +} + +// The layout position: exact XxY, or where Hyprland last put an auto one. +function xy(d) { + const p = /^(-?\d+)x(-?\d+)$/.exec(d.position); + return p ? { x: +p[1], y: +p[2] } : { x: d.x, y: d.y }; +} + +// One monitor as one Lua expression, for hyprctl eval and the file alike. +function lua(d) { + return 'hl.monitor({ output = "' + d.name + '", mode = "' + d.mode + '", position = "' + d.position + + '", scale = "' + d.scale + '", transform = ' + d.transform + (d.vrr ? ", vrr = 1" : "") + " })"; +} + +const HEADER = "-- Monitors, written by the keystone settings window (Hyprland > Monitors).\n" + + "-- Rewritten whole on Keep: change them there, an edit here is replaced.\n"; + +function file(list) { + return HEADER + list.map(d => "\n-- " + d.name + ": " + d.make + " " + d.model + "\n" + lua(d) + "\n").join(""); +} + +// The candidate within t of v nearest to it, else v. +function nearest(v, cands, t) { + let best = v, dist = t; + for (const c of cands) { + if (Math.abs(c - v) <= dist) { dist = Math.abs(c - v); best = c; } + } + return best; +} + +// r ({x, y, w, h}, logical) moved so an edge within t lies on an edge of one +// of others: beside it, or aligned with it. Each axis on its own, rounded. +function snap(r, others, t) { + const xs = [], ys = []; + for (const o of others) { + xs.push(o.x - r.w, o.x + o.w, o.x, o.x + o.w - r.w); + ys.push(o.y - r.h, o.y + o.h, o.y, o.y + o.h - r.h); + } + return { x: Math.round(nearest(r.x, xs, t)), y: Math.round(nearest(r.y, ys, t)) }; +} |
