summaryrefslogtreecommitdiffstats
path: root/desktop/modules/hyprland/OptionsModule.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/hyprland/OptionsModule.qml')
-rw-r--r--desktop/modules/hyprland/OptionsModule.qml137
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 }
+ }
+ }
+}