// Copyright (C) 2026 Danilo M. // // 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 } } } }