Krolyxon
Commit 0e2345d

Branches

Refs →

aurora.configquickshellauroraSetKeys.qml

150 lines · 5.9 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Io
5
6// Settings app > Keybinds: read-only, searchable list parsed from ~/.config/hypr/source/keybinds.lua
7// (`hyprctl binds` only shows "__lua <n>" for Lua binds). Comments above binds become the groups.
8ColumnLayout {
9 id: page
10 spacing: 8
11 readonly property string file: Quickshell.env("HOME") + "/.config/hypr/source/keybinds.lua"
12 property var binds: [] // [{group, keys, action}]
13 property string query: ""
14
15 // split the argument list of a call at top-level commas (strings and nested brackets respected)
16 function splitArgs(s) {
17 var out = [], depth = 0, q = "", cur = ""
18 for (var i = 0; i < s.length; i++) {
19 var c = s[i]
20 if (q !== "") {
21 cur += c
22 if (c === q && s[i - 1] !== "\\") q = ""
23 continue
24 }
25 if (c === '"' || c === "'") { q = c; cur += c; continue }
26 if (c === "(" || c === "{" || c === "[") depth++
27 if (c === ")" || c === "}" || c === "]") depth--
28 if (c === "," && depth === 0) { out.push(cur.trim()); cur = ""; continue }
29 cur += c
30 }
31 if (cur.trim() !== "") out.push(cur.trim())
32 return out
33 }
34 function keys(expr) {
35 if (/\.\.\s*n\b/.test(expr)) return "SUPER + 1..0" // the workspace loop
36 var k = expr.replace(/mainMod/g, '"SUPER"').replace(/\.\./g, "").replace(/["']/g, "")
37 return k.split("+").map(function (x) { x = x.trim(); return x.length === 1 ? x.toUpperCase() : x }).filter(function (x) { return x !== "" })
38 .join(" + ").replace(/mouse:272/, "left-drag").replace(/mouse:273/, "right-drag")
39 .replace(/mouse:274/, "middle-drag").replace(/mouse_down/, "scroll down").replace(/mouse_up/, "scroll up")
40 }
41 function action(expr, opts) {
42 var d = /desc(?:ription)?\s*=\s*"([^"]*)"/.exec(opts || "")
43 if (d) return d[1]
44 var e = /exec_cmd\(([\s\S]*)\)\s*$/.exec(expr)
45 if (e) return e[1].replace(/\\"/g, "\u0001").replace(/"/g, "").replace(/\u0001/g, '"') // terminal .. " -e x" -> terminal -e x
46 .replace(/\s*\.\.\s*/g, " ").trim()
47 if (/^function/.test(expr)) return "lua function"
48 return expr.replace(/^hl\.dsp\./, "").replace(/["{}]/g, "").replace(/\s*=\s*/g, "=")
49 .replace(/\(\s*/g, " ").replace(/\s*\)/g, "").replace(/\s+/g, " ").trim()
50 }
51 function parse(t) {
52 var lines = t.split("\n"), out = [], group = "", buf = "", depth = 0
53 for (var i = 0; i < lines.length; i++) {
54 var l = lines[i], tr = l.trim()
55 if (buf === "") {
56 var cm = /^--+\s*(.*?)\s*-*$/.exec(tr)
57 if (cm && cm[1] !== "" && !/^bind\s*=/.test(cm[1]) && cm[1].indexOf("http") < 0 && !/^-+$/.test(cm[1])) group = cm[1].replace(/^-+\s*/, "")
58 if (tr.indexOf("hl.bind(") !== 0) continue
59 }
60 buf += l + "\n"
61 depth += (l.match(/\(/g) || []).length - (l.match(/\)/g) || []).length
62 if (depth > 0) continue
63 var inner = buf.slice(buf.indexOf("hl.bind(") + 8, buf.lastIndexOf(")"))
64 var a = splitArgs(inner)
65 if (a.length >= 2) out.push({ group: group, keys: keys(a[0]), action: action(a[1], a[2]) })
66 buf = ""
67 depth = 0
68 }
69 binds = out
70 }
71
72 readonly property var shown: {
73 var q = query.toLowerCase(), out = [], last = null
74 for (var i = 0; i < binds.length; i++) {
75 var b = binds[i]
76 if (q !== "" && (b.keys + " " + b.action + " " + b.group).toLowerCase().indexOf(q) < 0) continue
77 if (b.group !== last) { out.push({ header: b.group }); last = b.group } // group header row
78 out.push(b)
79 }
80 return out
81 }
82
83 FileView {
84 path: page.file
85 watchChanges: true
86 onFileChanged: reload()
87 onLoaded: page.parse(text())
88 }
89
90 Field {
91 id: search
92 label: Theme.iSearch + " filter"
93 placeholder: "key, command or group"
94 onEdited: v => page.query = v
95 }
96
97 ListView {
98 Layout.fillWidth: true
99 Layout.fillHeight: true
100 clip: true
101 spacing: 2
102 model: page.shown
103 boundsBehavior: Flickable.StopAtBounds
104 delegate: RowLayout {
105 required property var modelData
106 readonly property bool isHeader: modelData.header !== undefined
107 width: ListView.view.width
108 spacing: 10
109 SectionLabel {
110 visible: parent.isHeader
111 Layout.topMargin: 6
112 text: parent.isHeader ? parent.modelData.header : ""
113 }
114 Rectangle {
115 visible: !parent.isHeader
116 Layout.preferredWidth: 220
117 implicitHeight: 24
118 color: Theme.surface
119 border.width: 1
120 border.color: Theme.line
121 Text {
122 anchors.fill: parent
123 anchors.leftMargin: 6
124 verticalAlignment: Text.AlignVCenter
125 text: modelData.keys || ""
126 color: Theme.accent
127 elide: Text.ElideRight
128 font.family: Theme.font
129 font.pixelSize: Theme.fontSize - 1
130 font.bold: true
131 }
132 }
133 Text {
134 visible: !parent.isHeader
135 Layout.fillWidth: true
136 text: modelData.action || ""
137 color: Theme.fg
138 elide: Text.ElideRight
139 font.family: Theme.font
140 font.pixelSize: Theme.fontSize - 1
141 }
142 }
143 }
144 Text {
145 text: page.binds.length + " binds · " + page.file.replace(Quickshell.env("HOME"), "~")
146 color: Theme.dim
147 font.family: Theme.font
148 font.pixelSize: Theme.fontSize - 2
149 }
150}