diff options
Diffstat (limited to 'desktop/modules/hyprland/OptionsModule.qml')
| -rw-r--r-- | desktop/modules/hyprland/OptionsModule.qml | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/desktop/modules/hyprland/OptionsModule.qml b/desktop/modules/hyprland/OptionsModule.qml new file mode 100644 index 0000000..45f62b0 --- /dev/null +++ b/desktop/modules/hyprland/OptionsModule.qml @@ -0,0 +1,137 @@ +// 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. + +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import QtQuick +import "../.." +import "Hypr.js" as H + +// Every Hyprland option: its description, default and range from hyprctl +// descriptions, its value from one batched getoption (descriptions' own +// "current" field lags the real value). A change is written as one line in +// sections/keystone.lua, which hyprland.lua requires last, so it wins over +// the hand-written sections without editing them. Each line is checked by +// running it through hyprctl eval first, which refuses an unknown key or a +// value of the wrong shape and changes nothing. +Module { + id: om + name: "hyprland" + label: "Options" + // nf-fa-gears, present in Inconsolata Nerd Font. + icon: "" + settings: true + group: "Hyprland" + keywords: ["option", "config", "gaps", "border", "decoration", "animation", "input", "misc", "keystone.lua"] + + readonly property string file: (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + + "/hypr/sections/keystone.lua" + readonly property string header: "-- Hyprland options changed from the keystone settings window, Hyprland >\n" + + "-- Options: one line per option, rewritten by that page. hyprland.lua requires\n" + + "-- this last, so a line here wins over the sections above it.\n" + + property var descs: [] + // name -> getoption object. + property var values: ({}) + // The names keystone.lua sets. + property var overridden: [] + // keystone.lua as last read or written. FileView.text() still returns + // the old text right after setText(), so a second edit made from it + // would drop the first. + property string text: "" + function write(t) { + om.text = t; + om.overridden = H.overridden(t); + om.fv.setText(t); + } + + function refresh() { + descProc.running = false; + descProc.running = true; + } + + property Connections reloads: Connections { + target: Hyprland + function onRawEvent(event) { if (event.name === "configreloaded") om.refresh(); } + } + + property Process descProc: Process { + command: ["hyprctl", "descriptions", "-j"] + stdout: StdioCollector { + onStreamFinished: { + try { om.descs = JSON.parse(this.text); } catch (e) { return; } + om.searchItems = om.descs.map(d => ({ text: d.name, key: "opt:" + d.name })); + om.readValues(); + } + } + } + + function readValues() { + valueProc.command = ["hyprctl", "-j", "--batch", om.descs.map(d => "getoption " + d.name).join(";")]; + valueProc.running = false; + valueProc.running = true; + } + + property Process valueProc: Process { + stdout: StdioCollector { onStreamFinished: om.values = H.parseBatch(this.text) } + } + + property FileView fv: FileView { + path: om.file + atomicWrites: true + watchChanges: true + printErrors: false + onFileChanged: reload() + onLoaded: { om.text = text(); om.overridden = H.overridden(om.text); } + onLoadFailed: { om.text = ""; om.overridden = []; } + } + + // Sets name to the Lua expression expr, calling done(error) with "" on + // success. The eval applies it at once; the write keeps it. + property var pending: null + function set(name, expr, done) { + if (expr.trim() === "" || expr.includes("\n")) { done("Write the value as one Lua expression."); return; } + om.pending = { name: name, expr: expr, done: done }; + probe.command = ["hyprctl", "eval", H.line(name, expr)]; + probe.running = false; + probe.running = true; + } + + property Process probe: Process { + stdout: StdioCollector { id: probeOut } + onExited: code => { + const p = om.pending; + om.pending = null; + if (!p) return; + if (code !== 0) { + p.done(probeOut.text.trim().split("\n")[0].replace(/^error: .*?:1: /, "") || "Hyprland refused it."); + return; + } + om.write(H.override(om.text || om.header, p.name, p.expr)); + om.readValues(); + p.done(""); + } + } + + // Drops name's line; the reload the write causes restores the value the + // other sections, or Hyprland's default, give it. + function reset(name) { + om.write(H.drop(om.text, name)); + } + + page: Component { + Page { + title: "Options" + OptionsPage { width: parent.width; mod: om } + } + } +} |
