Krolyxon
Commit e5c3c69

Branches

Refs →

aurorae5c3c69

aurora: settings app windows + input pages (live hyprland options)

HyprConf applies changes with `hyprctl eval` and persists them as
overrides in ~/.config/aurora/hypr.lua, loaded last by hyprland.lua.
Author
krolyxon
Date
2026-10-20 00:47 +0530
Parent
55505e6
Commit
e5c3c69bebb4ba6c0a298a37491c86c84ef4fff4

+435 −1

7 files changed

  1. M .config/hypr/hyprland.lua +5 −0
  2. M .config/quickshell/aurora/Choice.qml +1 −0
  3. A .config/quickshell/aurora/Field.qml +55 −0
  4. A .config/quickshell/aurora/HyprConf.qml +175 −0
  5. A .config/quickshell/aurora/SetInput.qml +78 −0
  6. A .config/quickshell/aurora/SetWindows.qml +114 −0
  7. M .config/quickshell/aurora/SettingsApp.qml +7 −1
M .config/hypr/hyprland.lua View
@@ -49,3 +49,8 @@ require("source.appearance")
4949 require("source.input")
5050 require("source.keybinds")
5151 require("source.window_rules")
52+
53+-- aurora's settings app: overrides on top of everything above (~/.config/aurora/hypr.lua, generated, not in the repo).
54+-- Missing or broken file = the defaults from the files above.
55+local ok, overrides = pcall(dofile, HOME .. "/.config/aurora/hypr.lua")
56+if ok and type(overrides) == "table" then hl.config(overrides) end
M .config/quickshell/aurora/Choice.qml View
@@ -26,6 +26,7 @@ RowLayout {
2626 delegate: IconBtn {
2727 required property var modelData
2828 Layout.fillWidth: true
29+ Layout.preferredWidth: 1 // equal widths, whatever the label length
2930 implicitHeight: 26
3031 label: modelData.label
3132 active: c.value === modelData.value
A .config/quickshell/aurora/Field.qml View
@@ -0,0 +1,55 @@
1+import QtQuick
2+import QtQuick.Layouts
3+
4+// label + one-line text input; `submitted` fires on Enter or when focus leaves with a changed value (settings app)
5+RowLayout {
6+ id: f
7+ property string label: ""
8+ property string value: ""
9+ property string placeholder: ""
10+ signal submitted(string v)
11+ Layout.fillWidth: true
12+ spacing: 8
13+ Text {
14+ Layout.preferredWidth: 150
15+ text: f.label
16+ color: Theme.fg
17+ elide: Text.ElideRight
18+ font.family: Theme.font
19+ font.pixelSize: Theme.fontSize
20+ }
21+ Rectangle {
22+ Layout.fillWidth: true
23+ implicitHeight: 28
24+ color: Theme.surface
25+ border.width: 1
26+ border.color: input.activeFocus ? Theme.accent : Theme.line
27+ TextInput {
28+ id: input
29+ anchors.fill: parent
30+ anchors.leftMargin: 8
31+ anchors.rightMargin: 8
32+ verticalAlignment: TextInput.AlignVCenter
33+ clip: true
34+ text: f.value
35+ color: Theme.fg
36+ selectionColor: Theme.accent
37+ selectedTextColor: Theme.bg
38+ font.family: Theme.font
39+ font.pixelSize: Theme.fontSize
40+ onAccepted: { if (text !== f.value) f.submitted(text); focus = false }
41+ onActiveFocusChanged: if (!activeFocus && text !== f.value) f.submitted(text)
42+ Keys.onEscapePressed: e => { text = f.value; focus = false; e.accepted = true }
43+ }
44+ Text {
45+ anchors.fill: parent
46+ anchors.leftMargin: 8
47+ verticalAlignment: Text.AlignVCenter
48+ visible: input.text === "" && !input.activeFocus
49+ text: f.placeholder
50+ color: Theme.dim
51+ font.family: Theme.font
52+ font.pixelSize: Theme.fontSize
53+ }
54+ }
55+}
A .config/quickshell/aurora/HyprConf.qml View
@@ -0,0 +1,175 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+
6+// Hyprland options edited from the settings app. Only what the user changed is stored, as overrides:
7+// ~/.config/aurora/hypr.json {"general:gaps_in": 5, ...} (state, read back here)
8+// ~/.config/aurora/hypr.lua return { general = { gaps_in = 5 } } (dofile'd at the end of hyprland.lua)
9+// A change is applied live with `hyprctl eval 'hl.config({...})'` and survives reloads/logins through hypr.lua.
10+// reset(opt) drops the override and reloads Hyprland's config, so the repo value comes back.
11+Singleton {
12+ id: root
13+ readonly property string dir: Quickshell.env("HOME") + "/.config/aurora"
14+
15+ // every option the settings pages show: path -> type (int | float | bool | str)
16+ readonly property var types: ({
17+ "general:gaps_in": "int", "general:gaps_out": "int", "general:border_size": "int",
18+ "general:layout": "str", "general:resize_on_border": "bool",
19+ "decoration:rounding": "int", "decoration:active_opacity": "float", "decoration:inactive_opacity": "float",
20+ "decoration:dim_inactive": "bool", "decoration:shadow:enabled": "bool",
21+ "decoration:blur:enabled": "bool", "decoration:blur:size": "int", "decoration:blur:passes": "int",
22+ "decoration:blur:xray": "bool",
23+ "animations:enabled": "bool",
24+ "master:mfact": "float", "master:new_status": "str", "master:new_on_top": "bool", "master:orientation": "str",
25+ "dwindle:preserve_split": "bool", "dwindle:smart_split": "bool",
26+ "misc:on_focus_under_fullscreen": "int", "misc:exit_window_retains_fullscreen": "bool",
27+ "misc:focus_on_activate": "bool",
28+ "input:kb_layout": "str", "input:kb_variant": "str", "input:kb_options": "str",
29+ "input:repeat_rate": "int", "input:repeat_delay": "int", "input:sensitivity": "float",
30+ "input:accel_profile": "str", "input:follow_mouse": "int", "input:natural_scroll": "bool",
31+ "input:left_handed": "bool",
32+ "input:touchpad:natural_scroll": "bool", "input:touchpad:tap_to_click": "bool",
33+ "input:touchpad:disable_while_typing": "bool", "input:touchpad:scroll_factor": "float",
34+ "cursor:inactive_timeout": "int", "cursor:hide_on_key_press": "bool"
35+ })
36+
37+ property var values: ({}) // current value of every option in `types` (from getoption)
38+ property var overrides: ({}) // only the changed ones
39+ property string message: "" // last hyprctl error, shown by the pages
40+ property bool loaded: false
41+
42+ function get(opt) { return values[opt] }
43+ function isSet(opt) { return overrides.hasOwnProperty(opt) }
44+
45+ function set(opt, v) {
46+ if (!types.hasOwnProperty(opt)) return
47+ var o = Object.assign({}, overrides), c = Object.assign({}, values)
48+ o[opt] = v
49+ c[opt] = v
50+ overrides = o
51+ values = c
52+ pending[opt] = v
53+ applyTimer.restart() // coalesces slider drags into one hyprctl call
54+ saveTimer.restart()
55+ }
56+ function reset(opt) {
57+ if (!isSet(opt)) return
58+ var o = Object.assign({}, overrides)
59+ delete o[opt]
60+ overrides = o
61+ save()
62+ reloadProc.running = true // config-only reload re-reads the repo files + the new hypr.lua
63+ }
64+ function resetAll(prefixes) { // e.g. ["input:"]: reset every override under those sections
65+ var o = Object.assign({}, overrides), changed = false
66+ for (var k in o)
67+ for (var i = 0; i < prefixes.length; i++)
68+ if (k.indexOf(prefixes[i]) === 0) { delete o[k]; changed = true; break }
69+ if (!changed) return
70+ overrides = o
71+ save()
72+ reloadProc.running = true
73+ }
74+ function refresh() {
75+ readProc.command = ["sh", "-c", 'for o; do hyprctl -j getoption "$o" | tr -d "\\n"; echo; done', "sh"]
76+ .concat(Object.keys(types))
77+ readProc.running = true
78+ }
79+
80+ // ---- lua generation: keys only ever come from `types`, values are typed + escaped ----
81+ function luaValue(opt, v) {
82+ var t = types[opt]
83+ if (t === "bool") return v ? "true" : "false"
84+ if (t === "int") return String(Math.round(Number(v)) || 0)
85+ if (t === "float") { var n = Number(v); return String(isFinite(n) ? n : 0) }
86+ return '"' + String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]/g, " ") + '"'
87+ }
88+ function luaTable(map) { // {"a:b:c": v} -> "{ a = { b = { c = v } } }"
89+ var tree = {}
90+ for (var opt in map) {
91+ if (!types.hasOwnProperty(opt)) continue
92+ var parts = opt.split(":"), node = tree
93+ for (var i = 0; i < parts.length - 1; i++) {
94+ if (typeof node[parts[i]] !== "object") node[parts[i]] = {}
95+ node = node[parts[i]]
96+ }
97+ node[parts[parts.length - 1]] = { lua: luaValue(opt, map[opt]) }
98+ }
99+ function emit(n, ind) {
100+ var keys = Object.keys(n).sort(), out = []
101+ for (var j = 0; j < keys.length; j++) {
102+ var x = n[keys[j]]
103+ out.push(ind + " " + keys[j] + " = " + (x.hasOwnProperty("lua") ? x.lua : emit(x, ind + " ")))
104+ }
105+ return out.length ? "{\n" + out.join(",\n") + "\n" + ind + "}" : "{}"
106+ }
107+ return emit(tree, "")
108+ }
109+
110+ function save() {
111+ jsonFile.setText(JSON.stringify(overrides, null, 2) + "\n")
112+ luaFile.setText("-- generated by aurora's settings app (HyprConf.qml); edit there, not here\n"
113+ + "return " + luaTable(overrides) + "\n")
114+ }
115+
116+ property var pending: ({})
117+ Timer {
118+ id: applyTimer
119+ interval: 60
120+ onTriggered: {
121+ var code = "hl.config(" + root.luaTable(root.pending) + ")"
122+ root.pending = {}
123+ applyProc.command = ["hyprctl", "eval", code]
124+ applyProc.running = true
125+ }
126+ }
127+ Timer { id: saveTimer; interval: 400; onTriggered: root.save() }
128+
129+ Process {
130+ id: applyProc
131+ stdout: StdioCollector {
132+ onStreamFinished: root.message = text.trim() === "ok" || text.trim() === "" ? "" : text.trim()
133+ }
134+ }
135+ Process {
136+ id: reloadProc
137+ command: ["hyprctl", "reload", "config-only"]
138+ onExited: root.refresh()
139+ }
140+ Process {
141+ id: readProc
142+ stdout: StdioCollector {
143+ onStreamFinished: {
144+ var c = {}, lines = text.split("\n")
145+ for (var i = 0; i < lines.length; i++) {
146+ if (lines[i].trim() === "") continue
147+ try {
148+ var j = JSON.parse(lines[i]), v
149+ if (j.hasOwnProperty("css")) v = parseInt(String(j.css).split(" ")[0]) // gaps: "2 2 2 2"
150+ else if (j.hasOwnProperty("int")) v = j.int
151+ else if (j.hasOwnProperty("float")) v = j.float
152+ else if (j.hasOwnProperty("bool")) v = j.bool
153+ else if (j.hasOwnProperty("str")) v = j.str === "[[EMPTY]]" ? "" : j.str // unset string option
154+ if (v !== undefined) c[j.option] = root.types[j.option] === "bool" ? !!v : v
155+ } catch (e) {}
156+ }
157+ root.values = c
158+ root.loaded = true
159+ }
160+ }
161+ }
162+
163+ FileView {
164+ id: jsonFile
165+ path: root.dir + "/hypr.json"
166+ blockLoading: true
167+ printErrors: false // no file until the first change
168+ onLoaded: {
169+ try { root.overrides = JSON.parse(text()) || {} } catch (e) { root.overrides = {} }
170+ }
171+ }
172+ FileView { id: luaFile; path: root.dir + "/hypr.lua"; preload: false; printErrors: false } // write-only
173+
174+ Component.onCompleted: refresh()
175+}
A .config/quickshell/aurora/SetInput.qml View
@@ -0,0 +1,78 @@
1+import QtQuick
2+import QtQuick.Layouts
3+
4+// Settings app > Input: keyboard, mouse, touchpad, cursor, through HyprConf (live + persisted).
5+Flickable {
6+ id: flick
7+ clip: true
8+ contentWidth: width
9+ contentHeight: col.implicitHeight
10+ boundsBehavior: Flickable.StopAtBounds
11+
12+ Component.onCompleted: HyprConf.refresh()
13+ function v(o, d) { var x = HyprConf.values[o]; return x === undefined ? d : x }
14+
15+ ColumnLayout {
16+ id: col
17+ width: flick.width
18+ spacing: 8
19+
20+ Text {
21+ Layout.fillWidth: true
22+ visible: HyprConf.message !== ""
23+ text: HyprConf.message
24+ color: Theme.err
25+ wrapMode: Text.WordWrap
26+ font.family: Theme.font
27+ font.pixelSize: Theme.fontSize - 1
28+ }
29+
30+ SectionLabel { text: "keyboard" }
31+ Field { label: "layouts"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) }
32+ Field { label: "variants"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) }
33+ Field { label: "xkb options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) }
34+ Knob { label: "repeat rate"; unit: "/s"; from: 10; to: 100; value: flick.v("input:repeat_rate", 25); onMoved: x => HyprConf.set("input:repeat_rate", x) }
35+ Knob { label: "repeat delay"; unit: "ms"; from: 150; to: 1000; value: flick.v("input:repeat_delay", 600); onMoved: x => HyprConf.set("input:repeat_delay", Math.round(x / 10) * 10) }
36+
37+ SectionLabel { text: "mouse" }
38+ Knob { label: "sensitivity"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) }
39+ Choice {
40+ label: "acceleration"
41+ options: [{ value: "", label: "default" }, { value: "flat", label: "flat" }, { value: "adaptive", label: "adaptive" }]
42+ value: flick.v("input:accel_profile", "")
43+ onPicked: x => HyprConf.set("input:accel_profile", x)
44+ }
45+ Choice {
46+ label: "focus follows mouse"
47+ options: [{ value: 0, label: "off" }, { value: 1, label: "always" }, { value: 2, label: "loose" }, { value: 3, label: "hover only" }]
48+ value: flick.v("input:follow_mouse", -1)
49+ onPicked: x => HyprConf.set("input:follow_mouse", x)
50+ }
51+ Switch { label: "natural scroll"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) }
52+ Switch { label: "left handed"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) }
53+
54+ SectionLabel { text: "touchpad" }
55+ Switch { label: "natural scroll"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) }
56+ Switch { label: "tap to click"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) }
57+ Switch { label: "disable while typing"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) }
58+ Knob { label: "scroll speed"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) }
59+
60+ SectionLabel { text: "cursor" }
61+ Knob { label: "hide when idle"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) }
62+ Switch { label: "hide while typing"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) }
63+
64+ Item { Layout.preferredHeight: 6 }
65+ RowLayout {
66+ Layout.fillWidth: true
67+ Text {
68+ Layout.fillWidth: true
69+ text: "per-device settings (hl.device) stay in source/input.lua"
70+ elide: Text.ElideRight
71+ color: Theme.dim
72+ font.family: Theme.font
73+ font.pixelSize: Theme.fontSize - 2
74+ }
75+ IconBtn { icon: Theme.iRefresh; label: "reset to config"; onClicked: HyprConf.resetAll(["input:", "cursor:"]) }
76+ }
77+ }
78+}
A .config/quickshell/aurora/SetWindows.qml View
@@ -0,0 +1,114 @@
1+import QtQuick
2+import QtQuick.Layouts
3+
4+// Settings app > Windows: Hyprland look + layout + fullscreen behaviour, through HyprConf (live + persisted).
5+Flickable {
6+ id: flick
7+ clip: true
8+ contentWidth: width
9+ contentHeight: col.implicitHeight
10+ boundsBehavior: Flickable.StopAtBounds
11+
12+ Component.onCompleted: HyprConf.refresh()
13+ // current value, or d while getoption hasn't answered yet
14+ function v(o, d) { var x = HyprConf.values[o]; return x === undefined ? d : x }
15+
16+ ColumnLayout {
17+ id: col
18+ width: flick.width
19+ spacing: 8
20+
21+ Text {
22+ Layout.fillWidth: true
23+ visible: HyprConf.message !== ""
24+ text: HyprConf.message
25+ color: Theme.err
26+ wrapMode: Text.WordWrap
27+ font.family: Theme.font
28+ font.pixelSize: Theme.fontSize - 1
29+ }
30+
31+ SectionLabel { text: "gaps & borders" }
32+ Knob { label: "inner gaps"; unit: "px"; from: 0; to: 30; value: flick.v("general:gaps_in", 0); onMoved: x => HyprConf.set("general:gaps_in", x) }
33+ Knob { label: "outer gaps"; unit: "px"; from: 0; to: 40; value: flick.v("general:gaps_out", 0); onMoved: x => HyprConf.set("general:gaps_out", x) }
34+ Knob { label: "border"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) }
35+ Knob { label: "rounding"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) }
36+ Switch { label: "resize on border"; hint: "drag window edges to resize"; checked: flick.v("general:resize_on_border", false); onToggled: on => HyprConf.set("general:resize_on_border", on) }
37+
38+ SectionLabel { text: "opacity" }
39+ Knob { label: "focused"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:active_opacity", 1); onMoved: x => HyprConf.set("decoration:active_opacity", x) }
40+ Knob { label: "unfocused"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:inactive_opacity", 1); onMoved: x => HyprConf.set("decoration:inactive_opacity", x) }
41+ Switch { label: "dim unfocused"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) }
42+
43+ SectionLabel { text: "effects" }
44+ Switch { label: "blur"; hint: "behind transparent windows and layers"; checked: flick.v("decoration:blur:enabled", false); onToggled: on => HyprConf.set("decoration:blur:enabled", on) }
45+ Knob { label: "blur size"; from: 1; to: 15; value: flick.v("decoration:blur:size", 1); onMoved: x => HyprConf.set("decoration:blur:size", x) }
46+ Knob { label: "blur passes"; from: 1; to: 6; value: flick.v("decoration:blur:passes", 1); onMoved: x => HyprConf.set("decoration:blur:passes", x) }
47+ Switch { label: "blur x-ray"; hint: "floating windows blur the wallpaper, not what's under them"; checked: flick.v("decoration:blur:xray", false); onToggled: on => HyprConf.set("decoration:blur:xray", on) }
48+ Switch { label: "shadows"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) }
49+ Switch { label: "animations"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) }
50+
51+ SectionLabel { text: "layout" }
52+ Choice {
53+ label: "tiling layout"
54+ options: [{ value: "master", label: "master" }, { value: "dwindle", label: "dwindle" }]
55+ value: flick.v("general:layout", "")
56+ onPicked: x => HyprConf.set("general:layout", x)
57+ }
58+ ColumnLayout {
59+ Layout.fillWidth: true
60+ visible: flick.v("general:layout", "") === "master"
61+ spacing: 8
62+ Knob { label: "master width"; from: 0.1; to: 0.9; decimals: 2; value: flick.v("master:mfact", 0.55); onMoved: x => HyprConf.set("master:mfact", x) }
63+ Choice {
64+ label: "new window"
65+ options: [{ value: "master", label: "master" }, { value: "slave", label: "stack" }, { value: "inherit", label: "inherit" }]
66+ value: flick.v("master:new_status", "")
67+ onPicked: x => HyprConf.set("master:new_status", x)
68+ }
69+ Choice {
70+ label: "master side"
71+ options: [{ value: "left", label: "left" }, { value: "right", label: "right" }, { value: "top", label: "top" },
72+ { value: "bottom", label: "bottom" }, { value: "center", label: "center" }]
73+ value: flick.v("master:orientation", "")
74+ onPicked: x => HyprConf.set("master:orientation", x)
75+ }
76+ Switch { label: "new windows on top of the stack"; checked: flick.v("master:new_on_top", false); onToggled: on => HyprConf.set("master:new_on_top", on) }
77+ }
78+ ColumnLayout {
79+ Layout.fillWidth: true
80+ visible: flick.v("general:layout", "") === "dwindle"
81+ spacing: 8
82+ Switch { label: "preserve split"; hint: "keep the split direction when windows move"; checked: flick.v("dwindle:preserve_split", false); onToggled: on => HyprConf.set("dwindle:preserve_split", on) }
83+ Switch { label: "smart split"; hint: "split by where the cursor is"; checked: flick.v("dwindle:smart_split", false); onToggled: on => HyprConf.set("dwindle:smart_split", on) }
84+ }
85+
86+ SectionLabel { text: "fullscreen & focus" }
87+ Choice {
88+ label: "focus under fullscreen"
89+ options: [{ value: 0, label: "ignore" }, { value: 1, label: "take over" }, { value: 2, label: "exit fullscreen" }]
90+ value: flick.v("misc:on_focus_under_fullscreen", -1)
91+ onPicked: x => HyprConf.set("misc:on_focus_under_fullscreen", x)
92+ }
93+ Switch { label: "closing keeps fullscreen"; hint: "the next window takes over when the fullscreen one closes (dwm monocle)"; checked: flick.v("misc:exit_window_retains_fullscreen", false); onToggled: on => HyprConf.set("misc:exit_window_retains_fullscreen", on) }
94+ Switch { label: "focus on activate"; hint: "apps asking for attention get focused"; checked: flick.v("misc:focus_on_activate", false); onToggled: on => HyprConf.set("misc:focus_on_activate", on) }
95+
96+ Item { Layout.preferredHeight: 6 }
97+ RowLayout {
98+ Layout.fillWidth: true
99+ Text {
100+ Layout.fillWidth: true
101+ text: "saved to ~/.config/aurora/hypr.lua (loaded last by hyprland.lua)"
102+ elide: Text.ElideLeft
103+ color: Theme.dim
104+ font.family: Theme.font
105+ font.pixelSize: Theme.fontSize - 2
106+ }
107+ IconBtn {
108+ icon: Theme.iRefresh
109+ label: "reset to config"
110+ onClicked: HyprConf.resetAll(["general:", "decoration:", "animations:", "master:", "dwindle:", "misc:"])
111+ }
112+ }
113+ }
114+}
M .config/quickshell/aurora/SettingsApp.qml View
@@ -26,7 +26,9 @@ FloatingWindow {
2626 { id: "bluetooth", icon: Theme.iBt, name: "bluetooth", group: "connections" },
2727 { id: "sound", icon: Theme.iVolHigh, name: "sound", group: "connections" },
2828 { id: "appearance", icon: Theme.iPalette, name: "appearance", group: "look" },
29− { id: "shell", icon: Theme.iDock, name: "shell", group: "look" }
29+ { id: "shell", icon: Theme.iDock, name: "shell", group: "look" },
30+ { id: "windows", icon: Theme.iWindow, name: "windows", group: "hyprland" },
31+ { id: "input", icon: Theme.iKeyboard, name: "input", group: "hyprland" }
3032 ]
3133 readonly property int index: {
3234 for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i
@@ -136,6 +138,8 @@ FloatingWindow {
136138 case "sound": return soundC
137139 case "appearance": return appearanceC
138140 case "shell": return shellC
141+ case "windows": return windowsC
142+ case "input": return inputC
139143 default: return netC
140144 }
141145 }
@@ -149,4 +153,6 @@ FloatingWindow {
149153 Component { id: soundC; AudioPage {} }
150154 Component { id: appearanceC; SetAppearance { onOverlayRequested: name => win.overlayRequested(name) } }
151155 Component { id: shellC; SetShell {} }
156+ Component { id: windowsC; SetWindows {} }
157+ Component { id: inputC; SetInput {} }
152158 }