Krolyxon
Commit 4b1d495

Branches

Refs →

aurora4b1d495

aurora: search settings (ctrl+f)

Android-style search in the settings app: Ctrl+F or the search pill, results
replace the page, Enter/click opens the section, scrolls to the row and flashes it.
SetIndex parses the pages (SectionLabel + Switch/Knob/Choice/Field/Timeout labels,
hints, choice options) plus optional keywords on the rows; the last result filters
the keybinds.
Author
krolyxon
Date
2026-10-30 15:26 +0530
Parent
61591c8
Commit
4b1d495771e02bdd4bbbbe467164f3d16c6a01b8

+626 −120

21 files changed

  1. M .config/quickshell/aurora/AudioPage.qml +4 −4
  2. M .config/quickshell/aurora/BatteryInfo.qml +1 −1
  3. M .config/quickshell/aurora/BtPage.qml +1 −1
  4. M .config/quickshell/aurora/Choice.qml +1 −0
  5. M .config/quickshell/aurora/Field.qml +1 −0
  6. M .config/quickshell/aurora/GpuSwitch.qml +1 −1
  7. M .config/quickshell/aurora/IconBtn.qml +1 −0
  8. M .config/quickshell/aurora/Knob.qml +1 −0
  9. M .config/quickshell/aurora/NetPage.qml +1 −1
  10. M .config/quickshell/aurora/SectionLabel.qml +1 −0
  11. M .config/quickshell/aurora/SetAbout.qml +3 −3
  12. M .config/quickshell/aurora/SetAppearance.qml +21 −14
  13. M .config/quickshell/aurora/SetDisplay.qml +7 −2
  14. A .config/quickshell/aurora/SetIndex.qml +170 −0
  15. M .config/quickshell/aurora/SetInput.qml +22 −20
  16. M .config/quickshell/aurora/SetKeys.qml +1 −0
  17. M .config/quickshell/aurora/SetPower.qml +15 −6
  18. M .config/quickshell/aurora/SetShell.qml +17 −16
  19. M .config/quickshell/aurora/SetWindows.qml +17 −13
  20. M .config/quickshell/aurora/SettingsApp.qml +339 −38
  21. M .config/quickshell/aurora/Switch.qml +1 −0
M .config/quickshell/aurora/AudioPage.qml View
@@ -44,7 +44,7 @@ Flickable {
4444 width: flick.width
4545 spacing: 6
4646
47− SectionLabel { text: "levels" }
47+ SectionLabel { text: "levels"; keywords: "volume mute microphone mic" }
4848 SliderRow {
4949 Layout.fillWidth: true
5050 Layout.topMargin: 4
@@ -64,7 +64,7 @@ Flickable {
6464 onIconClicked: if (flick.inp) flick.inp.muted = !flick.inp.muted
6565 }
6666
67− SectionLabel { text: "output"; count: String(flick.sinks.length); Layout.topMargin: 14 }
67+ SectionLabel { text: "output"; keywords: "speakers headphones hdmi default device sink"; count: String(flick.sinks.length); Layout.topMargin: 14 }
6868 Repeater {
6969 model: flick.sinks
7070 delegate: ListRow {
@@ -78,7 +78,7 @@ Flickable {
7878 }
7979 }
8080
81− SectionLabel { text: "input"; count: String(flick.sources.length); Layout.topMargin: 14 }
81+ SectionLabel { text: "input"; keywords: "microphone mic default device source"; count: String(flick.sources.length); Layout.topMargin: 14 }
8282 Repeater {
8383 model: flick.sources
8484 delegate: ListRow {
@@ -92,7 +92,7 @@ Flickable {
9292 }
9393 }
9494
95− SectionLabel { text: "apps"; count: String(flick.streams.length); Layout.topMargin: 14 }
95+ SectionLabel { text: "apps"; keywords: "mixer per-app app volume streams"; count: String(flick.streams.length); Layout.topMargin: 14 }
9696 Lb {
9797 visible: flick.streams.length === 0
9898 Layout.leftMargin: 6
M .config/quickshell/aurora/BatteryInfo.qml View
@@ -26,7 +26,7 @@ ColumnLayout {
2626 Layout.fillWidth: true
2727 spacing: 12
2828
29− SectionLabel { text: "battery" }
29+ SectionLabel { text: "battery"; keywords: "charge level percentage health time left" }
3030 RowLayout {
3131 Layout.leftMargin: 6
3232 spacing: 14
M .config/quickshell/aurora/BtPage.qml View
@@ -83,7 +83,7 @@ ColumnLayout {
8383 }
8484 }
8585
86− SectionLabel { text: "devices"; count: page.adapter && page.adapter.enabled ? String(page.devices.length) : "" }
86+ SectionLabel { text: "devices"; keywords: "pair pairing connect forget headphones headset earbuds speaker controller"; count: page.adapter && page.adapter.enabled ? String(page.devices.length) : "" }
8787
8888 Flickable {
8989 id: flick
M .config/quickshell/aurora/Choice.qml View
@@ -5,6 +5,7 @@ import QtQuick.Layouts
55 RowLayout {
66 id: c
77 property string label: ""
8+ property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source)
89 property var options: []
910 property var value
1011 signal picked(var v)
M .config/quickshell/aurora/Field.qml View
@@ -6,6 +6,7 @@ import QtQuick.Layouts
66 RowLayout {
77 id: f
88 property string label: ""
9+ property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source)
910 property string value: ""
1011 property string placeholder: ""
1112 signal submitted(string v)
M .config/quickshell/aurora/GpuSwitch.qml View
@@ -16,7 +16,7 @@ ColumnLayout {
1616 visible: Gpu.mode !== ""
1717 Layout.fillWidth: true
1818 spacing: 10
19− SectionLabel { text: "graphics · envycontrol" }
19+ SectionLabel { text: "graphics · envycontrol"; keywords: "gpu graphics mode nvidia hybrid integrated dgpu prime" }
2020 RowLayout {
2121 Layout.fillWidth: true
2222 spacing: 6
M .config/quickshell/aurora/IconBtn.qml View
@@ -5,6 +5,7 @@ Rectangle {
55 id: b
66 property string icon: ""
77 property string label: ""
8+ property string keywords: "" // settings search: a button is indexed only with keywords (SetIndex reads the source)
89 property bool active: false
910 property int iconSize: 16
1011 signal clicked()
M .config/quickshell/aurora/Knob.qml View
@@ -5,6 +5,7 @@ import QtQuick.Layouts
55 RowLayout {
66 id: k
77 property string label: ""
8+ property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source)
89 property real value: 0
910 property real from: 0
1011 property real to: 1
M .config/quickshell/aurora/NetPage.qml View
@@ -17,7 +17,7 @@ ColumnLayout {
1717 visible: Net.wiredDevs.length > 0
1818 Layout.fillWidth: true
1919 spacing: 4
20− SectionLabel { text: "wired" }
20+ SectionLabel { text: "wired"; keywords: "ethernet lan cable usb tethering phone" }
2121 Repeater {
2222 model: Net.wiredDevs
2323 delegate: ListRow {
M .config/quickshell/aurora/SectionLabel.qml View
@@ -5,6 +5,7 @@ import QtQuick.Layouts
55 RowLayout {
66 id: r
77 property string text: ""
8+ property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source)
89 property string count: ""
910 property string action: ""
1011 signal actionClicked()
M .config/quickshell/aurora/SetAbout.qml View
@@ -72,7 +72,7 @@ Flickable {
7272 }
7373 }
7474
75− SectionLabel { text: "system"; Layout.topMargin: 6 }
75+ SectionLabel { text: "system"; keywords: "kernel cpu gpu host hostname distro packages version uptime"; Layout.topMargin: 6 }
7676 Repeater {
7777 model: flick.info
7878 delegate: RowLayout {
@@ -98,7 +98,7 @@ Flickable {
9898 }
9999 }
100100
101− SectionLabel { text: "memory & storage"; Layout.topMargin: 6 }
101+ SectionLabel { text: "memory & storage"; keywords: "ram disk space drives usage"; Layout.topMargin: 6 }
102102 StatBar {
103103 label: "memory"
104104 frac: SysStats.memTotal > 0 ? SysStats.memUsed / SysStats.memTotal : 0
@@ -115,7 +115,7 @@ Flickable {
115115 }
116116 }
117117
118− SectionLabel { text: "credits"; Layout.topMargin: 6 }
118+ SectionLabel { text: "credits"; keywords: "license glyph-rice fonts"; Layout.topMargin: 6 }
119119 Text {
120120 Layout.fillWidth: true
121121 Layout.leftMargin: 18
M .config/quickshell/aurora/SetAppearance.qml View
@@ -17,7 +17,7 @@ Flickable {
1717 width: flick.width
1818 spacing: 10
1919
20− SectionLabel { text: "wallpaper" }
20+ SectionLabel { text: "wallpaper"; keywords: "background image picture" }
2121 Text {
2222 Layout.fillWidth: true
2323 Layout.leftMargin: 18
@@ -34,12 +34,14 @@ Flickable {
3434 Layout.fillWidth: true
3535 icon: Theme.iImage
3636 label: "open picker"
37+ keywords: "wallpaper picker choose background"
3738 onClicked: flick.overlayRequested("wall")
3839 }
3940 IconBtn {
4041 Layout.fillWidth: true
4142 icon: Theme.iShuffle
4243 label: "random from folder"
44+ keywords: "shuffle random wallpaper background"
4345 onClicked: Wall.random()
4446 }
4547 }
@@ -64,29 +66,34 @@ Flickable {
6466 }
6567 }
6668
67− SectionLabel { text: "wallpaper look" }
69+ SectionLabel { text: "wallpaper look"; keywords: "wallpaper style halftone" }
6870 Choice {
6971 label: "drawn as"
72+ keywords: "wallpaper style look"
7073 options: [{ value: "dots", label: "mono dots" }, { value: "colour", label: "colour dots" }, { value: "photo", label: "photo" }]
7174 value: Settings.wallLook
7275 onPicked: v => Settings.wallLook = v
7376 }
74− Knob { label: "dot size"; from: 6; to: 24; value: Settings.wallPitch; unit: "px"; onMoved: v => Settings.wallPitch = v }
75− Knob { label: "brightness"; from: 20; to: 100; value: Settings.wallBright; unit: "%"; onMoved: v => Settings.wallBright = v }
77+ Knob { label: "dot size"; keywords: "pitch grid"; from: 6; to: 24; value: Settings.wallPitch; unit: "px"; onMoved: v => Settings.wallPitch = v }
78+ Knob { label: "brightness"; keywords: "wallpaper dim dark"; from: 20; to: 100; value: Settings.wallBright; unit: "%"; onMoved: v => Settings.wallBright = v }
7679 Switch { label: "dot grid"; hint: "on the desktop when there is no wallpaper"; checked: Settings.dotGrid; onToggled: on => Settings.dotGrid = on }
7780
78− SectionLabel { text: "desktop" }
79− Switch { label: "24-hour clock"; checked: Settings.clock24; onToggled: on => Settings.clock24 = on }
80− Switch { label: "blinking colon"; checked: Settings.blink; onToggled: on => Settings.blink = on }
81− Switch { label: "animations"; hint: "panels slide and fade, dots light up in waves"; checked: Settings.motion; onToggled: on => Settings.motion = on }
81+ SectionLabel { text: "desktop"; keywords: "home screen" }
82+ Switch { label: "24-hour clock"; keywords: "time format 12-hour am pm"; checked: Settings.clock24; onToggled: on => Settings.clock24 = on }
83+ Switch { label: "blinking colon"; keywords: "clock blink"; checked: Settings.blink; onToggled: on => Settings.blink = on }
84+ Switch { label: "animations"; keywords: "motion transitions reduce"; hint: "panels slide and fade, dots light up in waves"; checked: Settings.motion; onToggled: on => Settings.motion = on }
8285 Choice {
8386 label: "app icons"
87+ keywords: "dock icons"
8488 options: [{ value: "dots", label: "dots" }, { value: "mono", label: "mono" }, { value: "colour", label: "colour" }]
8589 value: Settings.appIcons
8690 onPicked: v => Settings.appIcons = v
8791 }
8892
89− SectionLabel { text: "colours" }
93+ SectionLabel {
94+ text: "colours"
95+ keywords: "colors theme scheme accent palette matugen material tonal-spot vibrant expressive fidelity content fruit-salad rainbow neutral monochrome"
96+ }
9097 Text {
9198 Layout.fillWidth: true
9299 Layout.leftMargin: 18
@@ -110,13 +117,13 @@ Flickable {
110117 }
111118 }
112119 }
113− Knob { label: "contrast"; from: -1; to: 1; decimals: 2; value: Settings.matuContrast; onMoved: v => Settings.matuContrast = v }
120+ Knob { label: "contrast"; keywords: "colours colors"; from: -1; to: 1; decimals: 2; value: Settings.matuContrast; onMoved: v => Settings.matuContrast = v }
114121
115122 SectionLabel { text: "sizes" }
116− Knob { label: "bar height"; from: 32; to: 56; value: Settings.barSize; onMoved: v => Settings.barSize = v }
123+ Knob { label: "bar height"; keywords: "top bar size"; from: 32; to: 56; value: Settings.barSize; onMoved: v => Settings.barSize = v }
117124
118− SectionLabel { text: "transparency" }
119− Knob { label: "bar opacity"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v }
120− Knob { label: "panel opacity"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v }
125+ SectionLabel { text: "transparency"; keywords: "transparent opacity alpha translucent" }
126+ Knob { label: "bar opacity"; keywords: "transparent"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v }
127+ Knob { label: "panel opacity"; keywords: "transparent control panel"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v }
121128 }
122129 }
M .config/quickshell/aurora/SetDisplay.qml View
@@ -176,9 +176,10 @@ Flickable {
176176 font.pixelSize: 14
177177 }
178178
179− SectionLabel { text: "layout" }
179+ SectionLabel { text: "layout"; keywords: "monitors screens" }
180180 Choice {
181181 label: "screens"
182+ keywords: "monitors extend external laptop mirror"
182183 options: [{ value: "extend", label: "extend" }, { value: "laptop", label: "laptop only" },
183184 { value: "external", label: "external only" }]
184185 value: flick.layout
@@ -243,6 +244,7 @@ Flickable {
243244 Choice {
244245 visible: !mon.m.disabled
245246 label: "scale"
247+ keywords: "hidpi dpi zoom size"
246248 options: [{ value: 1, label: "100%" }, { value: 1.25, label: "125%" }, { value: 1.5, label: "150%" },
247249 { value: 1.75, label: "175%" }, { value: 2, label: "200%" }]
248250 value: Math.round(mon.m.scale * 100) / 100
@@ -251,6 +253,7 @@ Flickable {
251253 Field {
252254 visible: !mon.m.disabled
253255 label: "position"
256+ keywords: "arrange placement monitor"
254257 placeholder: "1920x0"
255258 value: mon.m.x + "x" + mon.m.y
256259 onSubmitted: x => {
@@ -261,10 +264,11 @@ Flickable {
261264 }
262265 }
263266
264− SectionLabel { text: "brightness & colour" }
267+ SectionLabel { text: "brightness & colour"; keywords: "backlight color" }
265268 Knob {
266269 visible: Bright.available
267270 label: "laptop panel"
271+ keywords: "brightness backlight screen"
268272 unit: "%"
269273 from: 1
270274 to: 100
@@ -274,6 +278,7 @@ Flickable {
274278 Switch {
275279 visible: Toggles.hasNightLight
276280 label: "night light"
281+ keywords: "blue light warm redshift gammastep"
277282 hint: "gammastep's warm colours (its schedule comes from ~/.config/gammastep)"
278283 checked: Toggles.nightLight
279284 onToggled: on => Toggles.setNightLight(on)
A .config/quickshell/aurora/SetIndex.qml View
@@ -0,0 +1,170 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+
6+// The settings app's sections and its search index. Like Android indexes each preference XML, the rows are parsed out
7+// of the page files themselves: SectionLabel (a group) and Switch / Knob / Choice / Field / Timeout with a literal
8+// `label:`, plus their `hint:`, `keywords:` (synonyms, read only here) and a Choice's option labels. IconBtn only with
9+// `keywords:` (opt-in). Non-literal labels aren't indexed. `extras` covers rows without a label (wi-fi, bluetooth power).
10+// Editing a page hot-reloads the shell, which re-parses it.
11+Singleton {
12+ id: idx
13+
14+ // files: parsed in this order (a page's composite rows like BatteryInfo / GpuSwitch count as the page)
15+ readonly property var sections: [
16+ { id: "network", word: "network", caption: "network · wi-fi, wired, tethering", files: ["NetPage.qml"],
17+ keywords: "wifi internet ethernet" },
18+ { id: "bluetooth", word: "bluetooth", caption: "bluetooth · devices, pairing", files: ["BtPage.qml"], keywords: "bt" },
19+ { id: "sound", word: "sound", caption: "sound · levels, devices, apps", files: ["AudioPage.qml"],
20+ keywords: "audio volume speakers microphone" },
21+ { id: "appearance", word: "look", caption: "look · wallpaper, home screen, colours, sizes", files: ["SetAppearance.qml"],
22+ keywords: "appearance theme style" },
23+ { id: "shell", word: "shell", caption: "shell · home screen, dock, osd, notifications", files: ["SetShell.qml"],
24+ keywords: "aurora" },
25+ { id: "windows", word: "windows", caption: "windows · hyprland gaps, borders, effects, layout", files: ["SetWindows.qml"],
26+ keywords: "hyprland tiling" },
27+ { id: "input", word: "input", caption: "input · keyboard, mouse, touchpad", files: ["SetInput.qml"], keywords: "" },
28+ { id: "keybinds", word: "keys", caption: "keys · hyprland binds", files: [],
29+ keywords: "keybinds shortcuts hotkeys bindings" },
30+ { id: "display", word: "display", caption: "display · monitors, layout, night light", files: ["SetDisplay.qml"],
31+ keywords: "screen monitor resolution" },
32+ { id: "power", word: "power", caption: "power · battery, profiles, idle", files: ["BatteryInfo.qml", "SetPower.qml", "GpuSwitch.qml"],
33+ keywords: "energy sleep" },
34+ { id: "about", word: "about", caption: "about · this system", files: ["SetAbout.qml"], keywords: "info version" }
35+ ]
36+
37+ // rows the parser can't see. group = the SectionLabel to land on ("" = the top of the section)
38+ readonly property var extras: [
39+ { section: "network", group: "", label: "wi-fi on / off", hint: "rescan for networks",
40+ keywords: "wifi wireless wlan radio internet scan" },
41+ { section: "network", group: "networks", label: "wi-fi networks", hint: "connect, password, forget",
42+ keywords: "wifi ssid connect password forget hotspot" },
43+ { section: "bluetooth", group: "", label: "bluetooth on / off", hint: "search for devices",
44+ keywords: "bt adapter radio discover scan" }
45+ ]
46+
47+ readonly property string dir: Quickshell.env("HOME") + "/.config/quickshell/aurora/"
48+ property var parsed: ({}) // file -> [{kind, group, label, hint, keywords, context}]
49+
50+ readonly property var files: {
51+ var out = []
52+ for (var i = 0; i < sections.length; i++)
53+ for (var j = 0; j < sections[i].files.length; j++) out.push(sections[i].files[j])
54+ return out
55+ }
56+ Variants {
57+ model: idx.files
58+ FileView {
59+ required property string modelData
60+ path: idx.dir + modelData
61+ onLoaded: idx.parse(modelData, text())
62+ }
63+ }
64+
65+ // every entry: {kind: section|group|row, section, word, group, label, hint, order, l, k, c, h}
66+ // l/k/c/h = lowercased label / keywords (own synonyms, options) / context (section, group + its keywords) / hint,
67+ // for search()
68+ readonly property var entries: {
69+ var out = []
70+ function add(s, e) {
71+ out.push({ kind: e.kind, section: s.id, word: s.word, group: e.group, label: e.label, hint: e.hint,
72+ order: out.length, l: norm(e.label), k: norm(e.keywords),
73+ c: norm(s.word + " " + s.id + " " + (e.context || "")), h: norm(e.hint) })
74+ }
75+ for (var i = 0; i < sections.length; i++) {
76+ var s = sections[i]
77+ add(s, { kind: "section", group: "", label: s.word, hint: s.caption.replace(/^[^·]*·\s*/, ""),
78+ keywords: s.id + " " + s.keywords })
79+ for (var j = 0; j < s.files.length; j++) {
80+ var p = parsed[s.files[j]] || []
81+ for (var n = 0; n < p.length; n++) add(s, p[n])
82+ }
83+ for (var x = 0; x < extras.length; x++)
84+ if (extras[x].section === s.id)
85+ add(s, { kind: "group", group: extras[x].group,
86+ label: extras[x].label, hint: extras[x].hint, keywords: extras[x].keywords })
87+ }
88+ return out
89+ }
90+
91+ // the text of `key: "literal"` at the block's own level, or null (expressions don't count)
92+ function lit(top, key) {
93+ var m = new RegExp("(?:^|[\\s;])" + key + ":\\s*\"((?:[^\"\\\\]|\\\\.)*)\"\\s*(?:;|\\n|$)").exec(top)
94+ return m ? m[1].replace(/\\(.)/g, "$1") : null
95+ }
96+ // from just after a `{`: the block's own text (nested {} [] left out) and the nested text, skipping strings/comments
97+ function block(t, i) {
98+ var depth = 1, top = "", inner = "", q = ""
99+ for (; i < t.length && depth > 0; i++) {
100+ var ch = t[i]
101+ if (q !== "") {
102+ if (depth === 1) top += ch; else inner += ch
103+ if (ch === "\\") { i++; if (depth === 1) top += t[i]; else inner += t[i] }
104+ else if (ch === q) q = ""
105+ continue
106+ }
107+ if (ch === "/" && t[i + 1] === "/") { var nl = t.indexOf("\n", i); i = (nl < 0 ? t.length : nl) - 1; continue }
108+ if (ch === '"' || ch === "'" || ch === "`") q = ch
109+ if (ch === "{" || ch === "[") { depth++; continue }
110+ if (ch === "}" || ch === "]") { depth--; continue }
111+ if (depth === 1) top += ch; else inner += ch
112+ }
113+ return { top: top, inner: inner }
114+ }
115+ function parse(file, t) {
116+ var out = [], group = "", groupKw = "", re = /\b(SectionLabel|Switch|Knob|Choice|Field|Timeout|IconBtn)\s*\{/g, m
117+ while ((m = re.exec(t)) !== null) {
118+ var b = block(t, re.lastIndex), kw = lit(b.top, "keywords")
119+ if (m[1] === "SectionLabel") {
120+ group = lit(b.top, "text") || ""
121+ groupKw = group !== "" ? kw || "" : ""
122+ if (group !== "") out.push({ kind: "group", group: group, label: group, hint: "", keywords: groupKw, context: "" })
123+ continue
124+ }
125+ var label = lit(b.top, "label")
126+ if (!label || (m[1] === "IconBtn" && kw === null)) continue
127+ var opts = []
128+ if (m[1] === "Choice") {
129+ var om, ore = /\blabel:\s*"([^"]*)"/g
130+ while ((om = ore.exec(b.inner)) !== null) opts.push(om[1])
131+ }
132+ out.push({ kind: "row", group: group, label: label, hint: lit(b.top, "hint") || "",
133+ keywords: (kw || "") + " " + opts.join(" "), context: group + " " + groupKw })
134+ }
135+ var p = Object.assign({}, parsed)
136+ p[file] = out
137+ parsed = p
138+ }
139+
140+ // every word has to match the entry (its own text or its section/group); at least one its own text.
141+ // own: label prefix > a label word > keywords > a hint word > inside a label word. Best first, then page order.
142+ // A hyphenated word is indexed split and joined ("24-hour" -> "24 hour 24hour"), the query joins them ("wi-fi" = "wifi").
143+ function norm(s) {
144+ s = s.toLowerCase()
145+ return " " + s.replace(/-/g, " ") + (s.indexOf("-") >= 0 ? " " + s.replace(/-/g, "") : "")
146+ }
147+ function search(q) {
148+ var words = q.toLowerCase().replace(/-/g, "").trim().split(/\s+/).filter(function (w) { return w !== "" })
149+ if (!words.length) return []
150+ var hits = []
151+ for (var i = 0; i < entries.length; i++) {
152+ var e = entries[i], total = 0, own = false
153+ for (var j = 0; j < words.length; j++) {
154+ var w = words[j], at = e.l.indexOf(" " + w)
155+ var sc = Math.max(at === 0 ? 1000 : at > 0 ? 500 : e.l.indexOf(w) > 0 ? 60 : 0,
156+ e.k.indexOf(" " + w) >= 0 ? 250 : e.k.indexOf(w) >= 0 ? 150
157+ : e.h.indexOf(" " + w) >= 0 ? 80 : 0)
158+ if (sc > 0) own = true
159+ else sc = e.c.indexOf(" " + w) >= 0 ? 200 : e.c.indexOf(w) >= 0 ? 100 : 0
160+ if (sc <= 0) { total = 0; break }
161+ total += sc
162+ }
163+ if (total > 0 && own) hits.push({ e: e, sc: total })
164+ }
165+ hits.sort(function (a, b) { return b.sc - a.sc || a.e.order - b.e.order })
166+ var res = []
167+ for (var n = 0; n < hits.length && n < 60; n++) res.push(hits[n].e)
168+ return res
169+ }
170+}
M .config/quickshell/aurora/SetInput.qml View
@@ -28,43 +28,45 @@ Flickable {
2828 font.pixelSize: 14
2929 }
3030
31− SectionLabel { text: "keyboard" }
32− Field { label: "layouts"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) }
33− Field { label: "variants"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) }
34− Field { label: "xkb options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) }
35− 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) }
36− 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) }
31+ SectionLabel { text: "keyboard"; keywords: "xkb typing" }
32+ Field { label: "layouts"; keywords: "keyboard layout language kb_layout"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) }
33+ Field { label: "variants"; keywords: "keyboard variant dvorak colemak"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) }
34+ Field { label: "xkb options"; keywords: "caps lock compose kb_options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) }
35+ Knob { label: "repeat rate"; keywords: "key repeat speed"; unit: "/s"; from: 10; to: 100; value: flick.v("input:repeat_rate", 25); onMoved: x => HyprConf.set("input:repeat_rate", x) }
36+ Knob { label: "repeat delay"; keywords: "key repeat"; 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) }
3737
38− SectionLabel { text: "mouse" }
39− Knob { label: "sensitivity"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) }
38+ SectionLabel { text: "mouse"; keywords: "pointer" }
39+ Knob { label: "sensitivity"; keywords: "mouse speed pointer"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) }
4040 Choice {
4141 label: "acceleration"
42+ keywords: "mouse accel"
4243 options: [{ value: "", label: "default" }, { value: "flat", label: "flat" }, { value: "adaptive", label: "adaptive" }]
4344 value: flick.v("input:accel_profile", "")
4445 onPicked: x => HyprConf.set("input:accel_profile", x)
4546 }
4647 Choice {
4748 label: "focus follows mouse"
49+ keywords: "focus hover"
4850 options: [{ value: 0, label: "off" }, { value: 1, label: "always" }, { value: 2, label: "loose" }, { value: 3, label: "hover only" }]
4951 value: flick.v("input:follow_mouse", -1)
5052 onPicked: x => HyprConf.set("input:follow_mouse", x)
5153 }
52− Switch { label: "natural scroll"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) }
53− Switch { label: "left handed"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) }
54+ Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) }
55+ Switch { label: "left handed"; keywords: "swap buttons"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) }
5456
55− SectionLabel { text: "touchpad" }
56− Switch { label: "natural scroll"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) }
57− Switch { label: "tap to click"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) }
58− Switch { label: "disable while typing"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) }
59− 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) }
57+ SectionLabel { text: "touchpad"; keywords: "trackpad" }
58+ Switch { label: "natural scroll"; keywords: "reverse scrolling"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) }
59+ Switch { label: "tap to click"; keywords: "tap"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) }
60+ Switch { label: "disable while typing"; keywords: "palm rejection dwt"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) }
61+ Knob { label: "scroll speed"; keywords: "scroll factor"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) }
6062
61− SectionLabel { text: "gestures" }
63+ SectionLabel { text: "gestures"; keywords: "swipe touchpad fingers" }
6264 Switch { label: "invert swipe"; hint: "reverse the 3-finger workspace swipe"; checked: flick.v("gestures:workspace_swipe_invert", true); onToggled: on => HyprConf.set("gestures:workspace_swipe_invert", on) }
6365 Knob { label: "swipe distance"; unit: "px"; from: 100; to: 1000; value: flick.v("gestures:workspace_swipe_distance", 300); onMoved: x => HyprConf.set("gestures:workspace_swipe_distance", Math.round(x / 10) * 10) }
6466
65− SectionLabel { text: "cursor" }
66− 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) }
67− Switch { label: "hide while typing"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) }
67+ SectionLabel { text: "cursor"; keywords: "mouse pointer" }
68+ Knob { label: "hide when idle"; keywords: "cursor inactive timeout"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) }
69+ Switch { label: "hide while typing"; keywords: "cursor"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) }
6870
6971 Item { Layout.preferredHeight: 6 }
7072 RowLayout {
@@ -78,7 +80,7 @@ Flickable {
7880 font.family: Theme.ui
7981 font.pixelSize: 13
8082 }
81− IconBtn { icon: Theme.iRefresh; label: "reset to config"; onClicked: HyprConf.resetAll(["input:", "cursor:", "gestures:"]) }
83+ IconBtn { icon: Theme.iRefresh; label: "reset to config"; keywords: "reset defaults restore hyprland"; onClicked: HyprConf.resetAll(["input:", "cursor:", "gestures:"]) }
8284 }
8385 }
8486 }
M .config/quickshell/aurora/SetKeys.qml View
@@ -91,6 +91,7 @@ ColumnLayout {
9191 id: search
9292 label: Theme.iSearch + " filter"
9393 placeholder: "key, command or group"
94+ value: page.query // the settings search sets it
9495 onEdited: v => page.query = v
9596 }
9697
M .config/quickshell/aurora/SetPower.qml View
@@ -25,6 +25,7 @@ Flickable {
2525 component Timeout: RowLayout {
2626 id: t
2727 property string label: ""
28+ property string keywords: ""
2829 property string key: ""
2930 property int value: 0
3031 property int max: 1800
@@ -68,10 +69,11 @@ Flickable {
6869
6970 BatteryInfo {}
7071
71− SectionLabel { text: "battery care"; Layout.topMargin: 6; visible: Battery.hasBat }
72+ SectionLabel { text: "battery care"; keywords: "battery health"; Layout.topMargin: 6; visible: Battery.hasBat }
7273 Choice {
7374 visible: Battery.supported
7475 label: "charge limit"
76+ keywords: "conservation 80% battery health longevity"
7577 options: [{ value: "off", label: "off" }, { value: "conservation", label: "conservation" }]
7678 value: Settings.chargeMode === "off" ? "off" : "conservation"
7779 onPicked: v => Settings.chargeMode = v
@@ -91,6 +93,7 @@ Flickable {
9193 Knob {
9294 visible: Battery.hasBat
9395 label: "warn at"
96+ keywords: "low battery warning"
9497 from: 5; to: 40; unit: "%"
9598 value: Settings.batLow
9699 onMoved: v => Settings.batLow = v
@@ -98,6 +101,7 @@ Flickable {
98101 Knob {
99102 visible: Battery.hasBat
100103 label: "critical at"
104+ keywords: "critical battery"
101105 from: 2; to: 15; unit: "%"
102106 value: Settings.batCritical
103107 onMoved: v => Settings.batCritical = v
@@ -105,6 +109,7 @@ Flickable {
105109 Choice {
106110 visible: Battery.hasBat
107111 label: "when critical"
112+ keywords: "low battery action"
108113 options: [{ value: "none", label: "warn" }, { value: "suspend", label: "suspend" },
109114 { value: "poweroff", label: "power off" }]
110115 value: Settings.batCriticalAction
@@ -115,6 +120,7 @@ Flickable {
115120 Layout.topMargin: 6
116121 visible: Battery.hasProfiles
117122 label: "power profile"
123+ keywords: "performance balanced saver power-profiles-daemon"
118124 options: flick.profiles.concat(Battery.hasBat ? [{ value: "auto", label: "auto" }] : [])
119125 value: Battery.profile
120126 onPicked: v => Battery.setProfile(v)
@@ -122,6 +128,7 @@ Flickable {
122128 Choice {
123129 visible: Battery.hasProfiles && Settings.powerAuto
124130 label: "on AC"
131+ keywords: "plugged in charger power profile"
125132 options: flick.profiles
126133 value: Settings.powerAc
127134 onPicked: v => Settings.powerAc = v
@@ -129,19 +136,21 @@ Flickable {
129136 Choice {
130137 visible: Battery.hasProfiles && Settings.powerAuto
131138 label: "on battery"
139+ keywords: "unplugged power profile"
132140 options: flick.profiles
133141 value: Settings.powerBat
134142 onPicked: v => Settings.powerBat = v
135143 }
136144 GpuSwitch { Layout.topMargin: 6 }
137145
138− SectionLabel { text: "when idle"; Layout.topMargin: 6 }
139− Timeout { label: "dim the screen"; key: "idleDim"; value: Idle.dim }
140− Timeout { label: "screen off"; key: "idleOff"; value: Idle.screenOff }
141− Timeout { label: "lock"; key: "idleLock"; value: Idle.lock }
142− Timeout { label: "suspend"; key: "idleSuspend"; value: Idle.suspend; max: 3600 }
146+ SectionLabel { text: "when idle"; keywords: "idle timeout sleep hypridle"; Layout.topMargin: 6 }
147+ Timeout { label: "dim the screen"; keywords: "idle dim timeout"; key: "idleDim"; value: Idle.dim }
148+ Timeout { label: "screen off"; keywords: "dpms display off idle timeout"; key: "idleOff"; value: Idle.screenOff }
149+ Timeout { label: "lock"; keywords: "lock screen hyprlock idle timeout"; key: "idleLock"; value: Idle.lock }
150+ Timeout { label: "suspend"; keywords: "sleep idle timeout"; key: "idleSuspend"; value: Idle.suspend; max: 3600 }
143151 Switch {
144152 label: "caffeine"
153+ keywords: "stay awake keep awake inhibit idle"
145154 hint: "keep the screen awake until turned off (not saved)"
146155 checked: Toggles.caffeine
147156 onToggled: on => Toggles.caffeine = on
M .config/quickshell/aurora/SetShell.qml View
@@ -14,13 +14,13 @@ Flickable {
1414 width: flick.width
1515 spacing: 8
1616
17− SectionLabel { text: "desktop" }
18− Switch { label: "clock"; hint: "Doto clock, date, stats and what's playing on the desktop"; checked: Settings.desktopClock; onToggled: on => Settings.desktopClock = on }
19− Switch { label: "visualizer"; hint: "cava as columns of dots along the bottom while music plays"; checked: Settings.desktopViz; onToggled: on => Settings.desktopViz = on }
20− Knob { label: "visualizer bars"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v }
17+ SectionLabel { text: "desktop"; keywords: "home screen widgets" }
18+ Switch { label: "clock"; keywords: "desktop clock widget date"; hint: "Doto clock, date, stats and what's playing on the desktop"; checked: Settings.desktopClock; onToggled: on => Settings.desktopClock = on }
19+ Switch { label: "visualizer"; keywords: "cava spectrum music audio"; hint: "cava as columns of dots along the bottom while music plays"; checked: Settings.desktopViz; onToggled: on => Settings.desktopViz = on }
20+ Knob { label: "visualizer bars"; keywords: "cava spectrum"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v }
2121
22− SectionLabel { text: "dock" }
23− Switch { label: "dock"; hint: "autohides at the bottom edge"; checked: Settings.dock; onToggled: on => Settings.dock = on }
22+ SectionLabel { text: "dock"; keywords: "taskbar launcher pinned apps" }
23+ Switch { label: "dock"; keywords: "taskbar launcher"; hint: "autohides at the bottom edge"; checked: Settings.dock; onToggled: on => Settings.dock = on }
2424 Text {
2525 text: "pinned apps (pin more from the dock's right-click)"
2626 color: Theme.fgDim
@@ -43,18 +43,19 @@ Flickable {
4343 }
4444 }
4545
46− SectionLabel { text: "on-screen display" }
47− Knob { label: "osd timeout"; unit: "ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 }
46+ SectionLabel { text: "on-screen display"; keywords: "osd popup indicator" }
47+ Knob { label: "osd timeout"; keywords: "on-screen display duration volume brightness popup"; unit: "ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 }
4848
4949 SectionLabel { text: "network" }
50− Switch { label: "captive portals"; hint: "open the login page in the browser when a network needs one"; checked: Settings.portalAutoOpen; onToggled: on => Settings.portalAutoOpen = on }
50+ Switch { label: "captive portals"; keywords: "hotel campus wifi login sign in hotspot"; hint: "open the login page in the browser when a network needs one"; checked: Settings.portalAutoOpen; onToggled: on => Settings.portalAutoOpen = on }
5151
5252 SectionLabel { text: "bar" }
53− Switch { label: "privacy indicators"; hint: "show in the bar when an app records the mic, uses the camera or shares the screen"; checked: Settings.privacyIndicators; onToggled: on => Settings.privacyIndicators = on }
53+ Switch { label: "privacy indicators"; keywords: "microphone mic camera webcam screen sharing recording"; hint: "show in the bar when an app records the mic, uses the camera or shares the screen"; checked: Settings.privacyIndicators; onToggled: on => Settings.privacyIndicators = on }
5454
55− SectionLabel { text: "weather" }
55+ SectionLabel { text: "weather"; keywords: "forecast temperature" }
5656 Field {
5757 label: "location"
58+ keywords: "city place weather"
5859 placeholder: "by IP · or a city · or lat,lon"
5960 value: Settings.weatherLoc
6061 onSubmitted: v => Settings.weatherLoc = v.trim()
@@ -70,13 +71,13 @@ Flickable {
7071 font.pixelSize: 13
7172 }
7273
73− SectionLabel { text: "notifications" }
74− Switch { label: "do not disturb"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on }
75− Switch { label: "sound"; checked: Settings.notifSound; onToggled: on => Settings.notifSound = on }
74+ SectionLabel { text: "notifications"; keywords: "popups toasts alerts" }
75+ Switch { label: "do not disturb"; keywords: "dnd silence quiet focus"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on }
76+ Switch { label: "sound"; keywords: "notification chime"; checked: Settings.notifSound; onToggled: on => Settings.notifSound = on }
7677 RowLayout {
7778 Layout.fillWidth: true
7879 spacing: 8
79− Knob { label: "sound volume"; from: 0; to: 1; decimals: 2; value: Settings.notifVolume; onMoved: v => Settings.notifVolume = v }
80+ Knob { label: "sound volume"; keywords: "notification chime loudness"; from: 0; to: 1; decimals: 2; value: Settings.notifVolume; onMoved: v => Settings.notifVolume = v }
8081 IconBtn { icon: Theme.iPlay; implicitHeight: 26; onClicked: Notifs.playSound() }
8182 }
8283
@@ -92,7 +93,7 @@ Flickable {
9293 font.family: Theme.ui
9394 font.pixelSize: 13
9495 }
95− IconBtn { icon: Theme.iRefresh; label: "reset shell settings"; onClicked: Settings.reset() }
96+ IconBtn { icon: Theme.iRefresh; label: "reset shell settings"; keywords: "reset defaults restore"; onClicked: Settings.reset() }
9697 }
9798 }
9899 }
M .config/quickshell/aurora/SetWindows.qml View
@@ -29,29 +29,30 @@ Flickable {
2929 font.pixelSize: 14
3030 }
3131
32− SectionLabel { text: "gaps & borders" }
33− 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) }
34− 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) }
35− Knob { label: "border"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) }
36− Knob { label: "rounding"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) }
32+ SectionLabel { text: "gaps & borders"; keywords: "spacing margins" }
33+ Knob { label: "inner gaps"; keywords: "spacing padding gaps_in"; unit: "px"; from: 0; to: 30; value: flick.v("general:gaps_in", 0); onMoved: x => HyprConf.set("general:gaps_in", x) }
34+ Knob { label: "outer gaps"; keywords: "spacing margin gaps_out"; unit: "px"; from: 0; to: 40; value: flick.v("general:gaps_out", 0); onMoved: x => HyprConf.set("general:gaps_out", x) }
35+ Knob { label: "border"; keywords: "border size thickness outline"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) }
36+ Knob { label: "rounding"; keywords: "corners radius rounded"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) }
3737 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) }
3838
39− SectionLabel { text: "opacity" }
40− 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) }
41− 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) }
42− Switch { label: "dim unfocused"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) }
39+ SectionLabel { text: "opacity"; keywords: "transparency transparent alpha" }
40+ Knob { label: "focused"; keywords: "active window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:active_opacity", 1); onMoved: x => HyprConf.set("decoration:active_opacity", x) }
41+ Knob { label: "unfocused"; keywords: "inactive window opacity"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:inactive_opacity", 1); onMoved: x => HyprConf.set("decoration:inactive_opacity", x) }
42+ Switch { label: "dim unfocused"; keywords: "inactive windows darken"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) }
4343
4444 SectionLabel { text: "effects" }
45− 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+ Switch { label: "blur"; keywords: "frosted glass"; hint: "behind transparent windows and layers"; checked: flick.v("decoration:blur:enabled", false); onToggled: on => HyprConf.set("decoration:blur:enabled", on) }
4646 Knob { label: "blur size"; from: 1; to: 15; value: flick.v("decoration:blur:size", 1); onMoved: x => HyprConf.set("decoration:blur:size", x) }
4747 Knob { label: "blur passes"; from: 1; to: 6; value: flick.v("decoration:blur:passes", 1); onMoved: x => HyprConf.set("decoration:blur:passes", x) }
4848 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) }
49− Switch { label: "shadows"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) }
50− Switch { label: "animations"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) }
49+ Switch { label: "shadows"; keywords: "drop shadow"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) }
50+ Switch { label: "animations"; keywords: "window motion transitions"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) }
5151
5252 SectionLabel { text: "layout" }
5353 Choice {
5454 label: "tiling layout"
55+ keywords: "tiling mode"
5556 options: [{ value: "master", label: "master" }, { value: "dwindle", label: "dwindle" }]
5657 value: flick.v("general:layout", "")
5758 onPicked: x => HyprConf.set("general:layout", x)
@@ -60,15 +61,17 @@ Flickable {
6061 Layout.fillWidth: true
6162 visible: flick.v("general:layout", "") === "master"
6263 spacing: 8
63− 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) }
64+ Knob { label: "master width"; keywords: "mfact ratio split size"; from: 0.1; to: 0.9; decimals: 2; value: flick.v("master:mfact", 0.55); onMoved: x => HyprConf.set("master:mfact", x) }
6465 Choice {
6566 label: "new window"
67+ keywords: "placement"
6668 options: [{ value: "master", label: "master" }, { value: "slave", label: "stack" }, { value: "inherit", label: "inherit" }]
6769 value: flick.v("master:new_status", "")
6870 onPicked: x => HyprConf.set("master:new_status", x)
6971 }
7072 Choice {
7173 label: "master side"
74+ keywords: "orientation"
7275 options: [{ value: "left", label: "left" }, { value: "right", label: "right" }, { value: "top", label: "top" },
7376 { value: "bottom", label: "bottom" }, { value: "center", label: "center" }]
7477 value: flick.v("master:orientation", "")
@@ -109,6 +112,7 @@ Flickable {
109112 IconBtn {
110113 icon: Theme.iRefresh
111114 label: "reset to config"
115+ keywords: "reset defaults restore hyprland"
112116 onClicked: HyprConf.resetAll(["general:", "decoration:", "animations:", "master:", "dwindle:", "misc:"])
113117 }
114118 }
M .config/quickshell/aurora/SettingsApp.qml View
@@ -1,10 +1,13 @@
11 import QtQuick
2+import QtQuick.Layouts
23 import Quickshell
34
45 // The settings app: a normal Hyprland window (class org.quickshell, title "aurora settings"), opened maximized by a
56 // window rule. glyph's prefs layout: the sections as a column of Doto words (the selected one large with the live dot),
67 // the page on the right under a caption, key hints at the bottom. One Loader, so only the shown section runs.
78 // Tab / Shift+Tab, Up/Down (outside text fields) or Ctrl+J/K = sections, Esc closes.
9+// Ctrl+F (or the search button) searches every setting (SetIndex): the results replace the page, Enter / click opens
10+// the section, scrolls to the row and flashes it.
811 // Opened with the `settings [section]` IPC. After glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), prefs/Prefs.qml.
912 FloatingWindow {
1013 id: win
@@ -21,20 +24,10 @@ FloatingWindow {
2124 color: Theme.bg
2225 // closed by the compositor (killactive): keep shell state in sync
2326 onVisibleChanged: if (!visible && opened) closeRequested()
27+ onOpenedChanged: if (!opened) stopSearch()
28+ onSectionChanged: if (searching) stopSearch() // switched from outside (IPC, Tab)
2429
25− readonly property var sections: [
26− { id: "network", word: "network", caption: "network · wi-fi, wired, tethering" },
27− { id: "bluetooth", word: "bluetooth", caption: "bluetooth · devices, pairing" },
28− { id: "sound", word: "sound", caption: "sound · levels, devices, apps" },
29− { id: "appearance", word: "look", caption: "look · wallpaper, home screen, colours, sizes" },
30− { id: "shell", word: "shell", caption: "shell · home screen, dock, osd, notifications" },
31− { id: "windows", word: "windows", caption: "windows · hyprland gaps, borders, effects, layout" },
32− { id: "input", word: "input", caption: "input · keyboard, mouse, touchpad" },
33− { id: "keybinds", word: "keys", caption: "keys · hyprland binds" },
34− { id: "display", word: "display", caption: "display · monitors, layout, night light" },
35− { id: "power", word: "power", caption: "power · battery, profiles, idle" },
36− { id: "about", word: "about", caption: "about · this system" }
37− ]
30+ readonly property var sections: SetIndex.sections
3831 readonly property int index: {
3932 for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i
4033 return 0
@@ -43,19 +36,134 @@ FloatingWindow {
4336 // typing in a field: arrows belong to it
4437 function inText() { var a = win.activeFocusItem; return a !== null && a.cursorPosition !== undefined }
4538
39+ // ---- search ----
40+ property bool searching: false
41+ property string query: ""
42+ property int pick: 0
43+ // the matches, then always "keybinds matching ..." (SetKeys has its own filter)
44+ readonly property var results: {
45+ var q = query.trim()
46+ if (!searching || q === "") return []
47+ return SetIndex.search(q).concat([{ kind: "keys", section: "keybinds", word: "keys", group: "",
48+ label: "keybinds matching “" + q + "”", hint: "filter the hyprland binds", query: q }])
49+ }
50+ readonly property var hitSections: {
51+ var h = {}
52+ for (var i = 0; i < results.length; i++) if (results[i].kind !== "keys") h[results[i].section] = true
53+ return h
54+ }
55+ onPickChanged: list.positionViewAtIndex(pick, ListView.Contain)
56+
57+ function startSearch() {
58+ if (searching) searchInput.selectAll()
59+ searching = true
60+ searchInput.forceActiveFocus()
61+ }
62+ function stopSearch() {
63+ if (!searching) return
64+ searching = false
65+ searchInput.text = ""
66+ root.forceActiveFocus()
67+ }
68+ function crumb(e) {
69+ if (e.kind === "section") return "section"
70+ return e.word + (e.kind === "row" && e.group !== "" ? " · " + e.group : "")
71+ }
72+
73+ // ---- landing on a result: open its section, find the row, scroll to it, flash it ----
74+ property var landing: null
75+ property int landTries: 0
76+ property Item hlTarget: null // the flashed row (nulled when the page goes away)
77+ property Item hlFlick: null // the Flickable it scrolls in, if any
78+ function go(e) {
79+ stopSearch()
80+ hlTarget = null
81+ landing = e
82+ landTries = 0
83+ if (e.section !== section) sectionRequested(e.section)
84+ landTimer.restart()
85+ }
86+ // the page's items in document order: past the group's SectionLabel, the first one with this label
87+ function findRow(e) {
88+ var hit = null, header = null, past = e.group === ""
89+ function walk(it) {
90+ var kids = it.children
91+ for (var i = 0; i < kids.length && !hit; i++) {
92+ var c = kids[i]
93+ if (e.group !== "" && c.actionClicked !== undefined && c.text === e.group) {
94+ past = true
95+ header = c
96+ if (e.kind === "group") { hit = c; return }
97+ } else if (past && e.kind === "row" && c.label === e.label) {
98+ hit = c
99+ return
100+ }
101+ walk(c)
102+ }
103+ }
104+ if (loader.item) walk(loader.item)
105+ return { hit: hit, header: header }
106+ }
107+ function land() {
108+ var e = landing
109+ if (!e || !loader.item) { landTimer.stop(); landing = null; return }
110+ var top = e.kind === "section" || e.kind === "keys" || e.group === "" && e.kind === "group"
111+ var f = top ? { hit: null, header: null } : findRow(e)
112+ if (!top && !f.hit && ++landTries < 16) return // rows from async data (monitors) may come late
113+ landTimer.stop()
114+ landing = null
115+ if (e.kind === "keys") loader.item.query = e.query
116+ if (!top && !f.hit) console.warn("settings search: no row for", e.section, "/", e.group, "/", e.label)
117+ // a hidden row (master-only knobs under dwindle...) -> its group's header
118+ var t = f.hit && f.hit.visible ? f.hit : f.header && f.header.visible ? f.header : null
119+ scrollTo(t)
120+ if (t) { hlTarget = t; flashAnim.restart() }
121+ }
122+ // t's row ~30% from the top of its Flickable; null = the top of the page
123+ function scrollTo(t) {
124+ var fl = null
125+ if (!t) fl = loader.item && loader.item.contentY !== undefined ? loader.item : null
126+ for (var p = t ? t.parent : null; p && p !== loader; p = p.parent)
127+ if (p.contentY !== undefined && p.contentHeight !== undefined) { fl = p; break }
128+ hlFlick = fl
129+ if (!fl) return
130+ var y = t ? t.mapToItem(fl.contentItem, 0, 0).y - fl.height * 0.3 : 0
131+ scrollAnim.stop()
132+ scrollAnim.target = fl
133+ scrollAnim.to = Math.max(0, Math.min(y, fl.contentHeight - fl.height))
134+ scrollAnim.start()
135+ }
136+
46137 Item {
47138 id: root
48139 anchors.fill: parent
49140 focus: true
50141 Keys.onPressed: e => {
51142 var ctrl = e.modifiers & Qt.ControlModifier
52− if (e.key === Qt.Key_Escape) { win.closeRequested(); e.accepted = true }
143+ if (e.key === Qt.Key_Escape) { if (win.searching) win.stopSearch(); else win.closeRequested(); e.accepted = true }
144+ else if (ctrl && e.key === Qt.Key_F) { win.startSearch(); e.accepted = true }
53145 else if (e.key === Qt.Key_Tab || (ctrl && e.key === Qt.Key_J)) { win.cycle(1); e.accepted = true }
54146 else if (e.key === Qt.Key_Backtab || (ctrl && e.key === Qt.Key_K)) { win.cycle(-1); e.accepted = true }
55147 }
56148 // a click outside the page drops the focus out of text fields (arrows switch sections again)
57149 MouseArea { anchors.fill: parent; onClicked: root.forceActiveFocus() }
58150
151+ Timer { id: landTimer; interval: 60; repeat: true; onTriggered: win.land() }
152+ NumberAnimation {
153+ id: scrollAnim
154+ property: "contentY"
155+ duration: Theme.t
156+ easing.type: Easing.BezierSpline
157+ easing.bezierCurve: Theme.glide
158+ }
159+ SequentialAnimation {
160+ id: flashAnim
161+ NumberAnimation { target: flash; property: "opacity"; from: 0; to: 1; duration: Theme.t / 2 }
162+ PauseAnimation { duration: 1400 }
163+ NumberAnimation { target: flash; property: "opacity"; to: 0; duration: Theme.motion ? 800 : 0 }
164+ ScriptAction { script: win.hlTarget = null }
165+ }
166+
59167 Item {
60168 id: stage
61169 anchors.fill: parent
@@ -75,6 +183,9 @@ FloatingWindow {
75183 required property int index
76184 readonly property bool on: win.index === index
77185 spacing: 20
186+ // while searching, sections without a match fade
187+ opacity: !win.searching || win.query.trim() === "" || win.hitSections[modelData.id] ? 1 : 0.3
188+ Behavior on opacity { enabled: Theme.motion; NumberAnimation { duration: Theme.t } }
78189 Rectangle {
79190 anchors.verticalCenter: parent.verticalCenter
80191 visible: w.on
@@ -94,7 +205,7 @@ FloatingWindow {
94205 Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
95206 }
96207 HoverHandler { id: wm; cursorShape: Qt.PointingHandCursor }
97− TapHandler { onTapped: { win.sectionRequested(w.modelData.id); root.forceActiveFocus() } }
208+ TapHandler { onTapped: { win.stopSearch(); win.sectionRequested(w.modelData.id); root.forceActiveFocus() } }
98209 }
99210 }
100211 }
@@ -103,34 +214,224 @@ FloatingWindow {
103214 Item {
104215 id: pageBox
105216 x: Math.max(words.x + 560, Math.round(win.width * 0.42))
106− y: 96
217+ y: 80
107218 width: Math.min(900, win.width - x - 80)
108219 height: parent.height - y - 110
109− Lb {
110− id: caption
111− x: 18
112− text: win.sections[win.index].caption
220+
221+ // the caption + search button, or the search pill
222+ Item {
223+ id: head
224+ width: parent.width
225+ height: 52
226+ Lb {
227+ x: 18
228+ anchors.verticalCenter: parent.verticalCenter
229+ visible: !win.searching
230+ text: win.sections[win.index].caption
231+ }
232+ IconBtn {
233+ anchors.right: parent.right
234+ anchors.verticalCenter: parent.verticalCenter
235+ visible: !win.searching
236+ icon: Theme.iSearch
237+ label: "search"
238+ onClicked: win.startSearch()
239+ }
240+ Rectangle {
241+ anchors.fill: parent
242+ visible: win.searching
243+ radius: height / 2
244+ color: Theme.s2
245+ Icon {
246+ id: lens
247+ x: 20
248+ anchors.verticalCenter: parent.verticalCenter
249+ text: Theme.iSearch
250+ color: Theme.grey
251+ font.pixelSize: 20
252+ }
253+ TextInput {
254+ id: searchInput
255+ anchors.left: lens.right
256+ anchors.leftMargin: 14
257+ anchors.right: found.left
258+ anchors.rightMargin: 12
259+ anchors.verticalCenter: parent.verticalCenter
260+ font.family: Theme.ui
261+ font.pixelSize: 18
262+ color: Theme.fg
263+ selectionColor: Theme.fill
264+ selectedTextColor: Theme.fillFg
265+ clip: true
266+ cursorDelegate: Rectangle {
267+ width: 2
268+ color: Theme.live
269+ visible: searchInput.activeFocus
270+ Timer { running: searchInput.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity }
271+ }
272+ Text {
273+ visible: !searchInput.text
274+ text: "search settings"
275+ font: searchInput.font
276+ color: Theme.mute
277+ }
278+ onTextChanged: { win.query = text; win.pick = 0 }
279+ // handled here, so the window's Tab / Ctrl+J/K / Esc don't fire
280+ Keys.onPressed: e => {
281+ var ctrl = e.modifiers & Qt.ControlModifier, n = win.results.length
282+ if (e.key === Qt.Key_Escape) win.stopSearch()
283+ else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) { if (n) win.go(win.results[win.pick]) }
284+ else if (e.key === Qt.Key_Down || e.key === Qt.Key_Tab || (ctrl && e.key === Qt.Key_J)) { if (n) win.pick = (win.pick + 1) % n }
285+ else if (e.key === Qt.Key_Up || e.key === Qt.Key_Backtab || (ctrl && e.key === Qt.Key_K)) { if (n) win.pick = (win.pick + n - 1) % n }
286+ else if (ctrl && e.key === Qt.Key_F) selectAll()
287+ else return
288+ e.accepted = true
289+ }
290+ }
291+ Lb {
292+ id: found
293+ anchors.right: parent.right
294+ anchors.rightMargin: 22
295+ anchors.verticalCenter: parent.verticalCenter
296+ text: win.results.length ? (win.results.length - 1) + " found" : ""
297+ }
298+ }
113299 }
114− Loader {
115− anchors.top: caption.bottom
116− anchors.topMargin: 24
300+
301+ Item {
302+ anchors.top: head.bottom
303+ anchors.topMargin: 16
117304 anchors.left: parent.left
118305 anchors.right: parent.right
119306 anchors.bottom: parent.bottom
120− active: win.opened
121− sourceComponent: {
122− switch (win.section) {
123− case "bluetooth": return btC
124− case "sound": return soundC
125− case "appearance": return appearanceC
126− case "shell": return shellC
127− case "windows": return windowsC
128− case "input": return inputC
129− case "keybinds": return keysC
130− case "display": return displayC
131− case "power": return powerC
132− case "about": return aboutC
133− default: return netC
307+
308+ // the found row's flash, behind the page (rows have no background of their own)
309+ Item {
310+ id: flashBox
311+ anchors.fill: parent
312+ clip: true
313+ Rectangle {
314+ id: flash
315+ // re-mapped while the page scrolls or relayouts
316+ readonly property rect r: {
317+ var t = win.hlTarget
318+ if (!t) return Qt.rect(0, 0, 0, 0)
319+ void ((win.hlFlick ? win.hlFlick.contentY : 0) + t.y + t.width + t.height + flashBox.width)
320+ return t.mapToItem(flashBox, 0, 0, t.width, t.height)
321+ }
322+ readonly property real pad: 4
323+ visible: win.hlTarget !== null && !win.searching
324+ opacity: 0
325+ width: parent.width
326+ y: r.y - pad
327+ height: r.height + 2 * pad
328+ radius: height / 2
329+ color: Theme.fade(Theme.fill, 0.2)
330+ }
331+ }
332+
333+ Loader {
334+ id: loader
335+ anchors.fill: parent
336+ visible: !win.searching // kept loaded: Esc returns to the same spot
337+ active: win.opened
338+ sourceComponent: {
339+ switch (win.section) {
340+ case "bluetooth": return btC
341+ case "sound": return soundC
342+ case "appearance": return appearanceC
343+ case "shell": return shellC
344+ case "windows": return windowsC
345+ case "input": return inputC
346+ case "keybinds": return keysC
347+ case "display": return displayC
348+ case "power": return powerC
349+ case "about": return aboutC
350+ default: return netC
351+ }
352+ }
353+ }
354+
355+ // ---- search results ----
356+ Lb {
357+ x: 18
358+ visible: win.searching && win.query.trim() === ""
359+ text: "type a setting, a word or a section"
360+ }
361+ ListView {
362+ id: list
363+ anchors.fill: parent
364+ visible: win.searching
365+ clip: true
366+ spacing: 2
367+ boundsBehavior: Flickable.StopAtBounds
368+ model: win.results
369+ header: Item {
370+ width: list.width
371+ height: win.results.length === 1 ? 44 : 0
372+ Lb {
373+ x: 18
374+ anchors.verticalCenter: parent.verticalCenter
375+ visible: parent.height > 0
376+ text: "no setting matches"
377+ }
378+ }
379+ delegate: Rectangle {
380+ id: r
381+ required property var modelData
382+ required property int index
383+ readonly property bool on: win.pick === index
384+ width: ListView.view.width
385+ height: Math.max(56, txt.implicitHeight + 20)
386+ radius: 28
387+ color: on || rm.containsMouse ? Theme.s2 : "transparent"
388+ Rectangle {
389+ x: 20
390+ anchors.verticalCenter: parent.verticalCenter
391+ visible: r.on
392+ width: 10
393+ height: 10
394+ radius: 5
395+ color: Theme.live
396+ }
397+ ColumnLayout {
398+ id: txt
399+ anchors.left: parent.left
400+ anchors.leftMargin: 44
401+ anchors.right: parent.right
402+ anchors.rightMargin: 18
403+ anchors.verticalCenter: parent.verticalCenter
404+ spacing: 3
405+ Text {
406+ Layout.fillWidth: true
407+ text: r.modelData.label
408+ color: Theme.fg
409+ elide: Text.ElideRight
410+ font.family: Theme.ui
411+ font.pixelSize: 15
412+ }
413+ RowLayout {
414+ Layout.fillWidth: true
415+ spacing: 12
416+ Lb { text: win.crumb(r.modelData); px: 10 }
417+ Text {
418+ Layout.fillWidth: true
419+ visible: r.modelData.hint !== ""
420+ text: r.modelData.hint
421+ color: Theme.fgDim
422+ elide: Text.ElideRight
423+ font.family: Theme.ui
424+ font.pixelSize: 13
425+ }
426+ }
427+ }
428+ MouseArea {
429+ id: rm
430+ anchors.fill: parent
431+ hoverEnabled: true
432+ cursorShape: Qt.PointingHandCursor
433+ onClicked: win.go(r.modelData)
434+ }
134435 }
135436 }
136437 }
@@ -142,7 +443,7 @@ FloatingWindow {
142443 anchors.bottomMargin: 50
143444 spacing: 34
144445 Repeater {
145− model: ["tab · ↑↓ section", "esc close"]
446+ model: win.searching ? ["↑↓ result", "↵ open", "esc back"] : ["ctrl+f search", "tab · ↑↓ section", "esc close"]
146447 Lb { required property string modelData; text: modelData }
147448 }
148449 }
M .config/quickshell/aurora/Switch.qml View
@@ -8,6 +8,7 @@ Rectangle {
88 id: s
99 property string label: ""
1010 property string hint: ""
11+ property string keywords: "" // synonyms for the settings search (SetIndex reads them from the source)
1112 property bool checked: false
1213 signal toggled(bool on)
1314