From d10f2cc00c1091f63e335df1d6913c7abddf06e5 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 10 Nov 2026 10:06:05 +0100 Subject: monitors: leave virtual outputs such as deck-it's TAB alone deck-it creates a headless TAB output at runtime and places it itself, centred under DP-1. Listed on the Monitors page, Apply would reposition it and Keep would pin it into monitors.lua, where it would outlive the tablet. Mon.js's UNMANAGED drops it from the page entirely. The list is fixed: deck-it's display.name can rename the output, and a renamed one shows up here again (a ponytail ceiling, noted in Mon.js). Co-Authored-By: Claude Opus 5.5 --- desktop/modules/monitors/Mon.js | 10 ++++++++++ desktop/modules/monitors/MonitorsModule.qml | 5 +++-- desktop/modules/monitors/README.md | 5 +++++ desktop/modules/monitors/test_mon.js | 6 +++++- 4 files changed, 23 insertions(+), 3 deletions(-) (limited to 'desktop') diff --git a/desktop/modules/monitors/Mon.js b/desktop/modules/monitors/Mon.js index 865e155..5d63336 100644 --- a/desktop/modules/monitors/Mon.js +++ b/desktop/modules/monitors/Mon.js @@ -18,6 +18,16 @@ const AUTO = ["auto", "auto-left", "auto-right", "auto-up", "auto-down", "auto-center-left", "auto-center-right", "auto-center-up", "auto-center-down"]; +// Outputs the page leaves alone, so Apply never repositions them and Keep +// never writes a line that pins them: a virtual display is created with its +// own placement and should keep it. +// ponytail: a fixed list; a per-monitor toggle once more than one appears. +const UNMANAGED = ["TAB"]; + +function managed(list) { + return list.filter(m => !UNMANAGED.includes(m.name)); +} + // availableModes ("1920x1080@120.00Hz") without the suffix, duplicates // dropped: the ASUS lists 120Hz twice. function modes(avail) { diff --git a/desktop/modules/monitors/MonitorsModule.qml b/desktop/modules/monitors/MonitorsModule.qml index 3fdef48..b35f4e7 100644 --- a/desktop/modules/monitors/MonitorsModule.qml +++ b/desktop/modules/monitors/MonitorsModule.qml @@ -63,8 +63,9 @@ Module { onStreamFinished: { let list; try { list = JSON.parse(this.text); } catch (e) { mm.error = "Cannot read hyprctl monitors."; return; } - mm.orig = list.map(M.draft); - mm.draft = list.map(M.draft); + const mine = M.managed(list); + mm.orig = mine.map(M.draft); + mm.draft = mine.map(M.draft); } } } diff --git a/desktop/modules/monitors/README.md b/desktop/modules/monitors/README.md index 3716110..c9ff88a 100644 --- a/desktop/modules/monitors/README.md +++ b/desktop/modules/monitors/README.md @@ -20,6 +20,11 @@ with no answer runs `hyprctl reload`, which reapplies the untouched file. A setting that blanks a screen never reaches the file. The cards and the layout are locked while the bar waits, since Keep writes the draft as it stands. +Virtual outputs (a tablet's, created at runtime) are left out of the page +entirely: `Mon.js`'s `UNMANAGED` names them, so Apply never repositions one +and Keep never writes a line that pins it, and whatever creates it keeps +placing it. + hyprctl only reports where a monitor ended up, so a monitor set to an `auto-*` position in the file reads back as exact. VRR is assumed to read the same way, as the active state rather than the configured one, so a `vrr = 2` diff --git a/desktop/modules/monitors/test_mon.js b/desktop/modules/monitors/test_mon.js index 5d28ce4..f101dc7 100644 --- a/desktop/modules/monitors/test_mon.js +++ b/desktop/modules/monitors/test_mon.js @@ -17,7 +17,7 @@ const fs = require("fs"); const assert = require("assert"); const src = fs.readFileSync(__dirname + "/Mon.js", "utf8").replace(/^\.pragma library$/m, ""); -const M = new Function(src + "\nreturn { modes, current, draft, size, xy, lua, file, snap, AUTO };")(); +const M = new Function(src + "\nreturn { modes, current, draft, size, xy, lua, file, snap, managed, AUTO };")(); // Duplicates collapse, order kept, the Hz suffix dropped. assert.deepStrictEqual(M.modes(["1920x1080@60.00Hz", "1920x1080@120.00Hz", "1920x1080@120.00Hz", "1280x720@60.00Hz"]), @@ -44,6 +44,10 @@ assert.deepStrictEqual(M.size("2560x1440@60.00", "2", 7), { w: 720, h: 1280 }); // A scale mid-typing draws at 1 rather than Infinity or NaN. assert.deepStrictEqual(M.size("1920x1080@60.00", "", 0), { w: 1920, h: 1080 }); +// A virtual output is left out, a real one kept. +assert.deepStrictEqual(M.managed([asus, { name: "TAB", make: "", model: "" }]).map(m => m.name), + ["DP-3"]); + // Exact positions parse, auto ones fall back to where Hyprland put it. assert.deepStrictEqual(M.xy({ position: "-1080x-960", x: 0, y: 0 }), { x: -1080, y: -960 }); assert.deepStrictEqual(M.xy({ position: "auto-center-left", x: 5, y: 6 }), { x: 5, y: 6 }); -- cgit v1.2.3