swadhinbiswas Initial de-omarchy: Omarchy UI layer for Arch-based distros · 8/22/2026
9f393b2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Polkit
import Quickshell.Wayland
import qs.Commons
import qs.Ui
import "PolkitModel.js" as PolkitModel
Item {
id: root
property string fontFamily: Style.font.menuFamily
// Bound to the central [polkit] section in shell.toml via Color.qml.
property color accent: Color.polkit.accent
property color background: Color.polkit.background
property color foreground: Color.polkit.text
property color border: Color.polkit.border
property color borderError: Color.polkit.borderError
property var borderSpec: Border.surfaceSpec("polkit", errorFlash ? "border-error" : "border", errorFlash ? borderError : border, Math.max(1, Style.space(2)), "border-alpha")
property color scrim: Color.polkit.scrim
readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding
property int fieldHeight: Math.max(Style.space(42), Style.spacing.controlHeight)
property bool closing: false
property bool submitted: false
property string currentMessage: ""
property string currentPrompt: ""
property string currentSupplementary: ""
property bool responseRequired: false
property bool responseVisible: false
property bool failed: false
property bool errorFlash: false
// pam_fprintd appears in the polkit PAM stack (a sensor is enrolled).
property bool fingerprintConfigured: false
// Lid shut right now — the reader is physically unreachable, so we fall back
// to the password even when a sensor is enrolled. Refreshed per request.
property bool laptopClosed: false
property int shakeOffset: 0
readonly property bool dialogVisible: polkitAgent.isActive || closing
// We show one method at a time. Fingerprint owns the dialog while PAM is
// waiting on the reader (lid open, sensor enrolled); the moment PAM asks for
// a password — including immediately when the lid is shut and the clamshell
// gate skips pam_fprintd — we switch to the password field instead.
readonly property bool fingerprintMode: fingerprintConfigured && !laptopClosed && dialogVisible && !responseRequired && !submitted && !errorFlash
readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - Style.gapsOut * 2) : fieldHeight + contentMargin * 2
// Password mode is a wide field; fingerprint mode collapses to a square that
// just frames the centered sensor icon.
readonly property int cardWidth: fingerprintMode ? cardHeight : Math.min(Style.space(312), Math.max(Style.space(260), panel.width - Style.gapsOut * 2))
function authorizationLabel(message) {
return PolkitModel.authorizationLabel(message)
}
function loadPamConfig(raw) {
fingerprintConfigured = PolkitModel.fingerprintConfiguredFromPamConfig(raw)
}
function refreshLidState() {
if (!laptopClosedProc.running) laptopClosedProc.running = true
}
function resetSnapshot() {
currentMessage = ""
currentPrompt = ""
currentSupplementary = ""
responseRequired = false
responseVisible = false
failed = false
errorFlash = false
submitted = false
passwordInput.text = ""
}
function syncFromFlow() {
var flow = polkitAgent.flow
if (!flow) return
currentMessage = String(flow.message || "Authentication is needed...")
currentPrompt = String(flow.inputPrompt || "")
currentSupplementary = String(flow.supplementaryMessage || "")
responseRequired = !!flow.isResponseRequired
responseVisible = !!flow.responseVisible
failed = !!flow.failed
if (responseRequired) submitted = false
}
function beginFlow() {
closeTimer.stop()
closing = false
submitted = false
passwordInput.text = ""
refreshLidState()
syncFromFlow()
Qt.callLater(refocus)
}
function refocus() {
if (!dialogVisible) return
// In fingerprint mode there is no field to type into — park focus on the
// key catcher so Escape still cancels; otherwise focus the password field.
if (fingerprintMode) keyCatcher.forceActiveFocus()
else passwordInput.forceActiveFocus()
}
function submitResponse() {
var flow = polkitAgent.flow
if (!flow || !flow.isResponseRequired) return
submitted = true
errorFlash = false
flow.submit(passwordInput.text)
passwordInput.text = ""
keyCatcher.forceActiveFocus()
}
function cancelRequest() {
var flow = polkitAgent.flow
passwordInput.text = ""
submitted = false
closing = true
closeTimer.restart()
if (flow) flow.cancelAuthenticationRequest()
}
function triggerFailureFeedback() {
submitted = false
errorFlash = true
passwordInput.text = ""
errorTimer.restart()
shakeAnimation.restart()
Qt.callLater(refocus)
}
Timer {
id: closeTimer
interval: 300
repeat: false
onTriggered: {
closing = false
resetSnapshot()
}
}
Timer {
id: errorTimer
interval: 1200
repeat: false
onTriggered: root.errorFlash = false
}
SequentialAnimation {
id: shakeAnimation
NumberAnimation { target: root; property: "shakeOffset"; to: -8; duration: 35; easing.type: Easing.OutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 8; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 0; duration: 55; easing.type: Easing.OutQuad }
}
FileView {
path: "/etc/pam.d/polkit-1"
watchChanges: true
printErrors: false
onLoaded: root.loadPamConfig(text())
onLoadFailed: root.fingerprintConfigured = false
onFileChanged: reload()
}
Process {
id: laptopClosedProc
command: ["bash", "-c", "omarchy-hw-laptop-closed && echo closed || echo open"]
stdout: StdioCollector { id: laptopClosedOut; waitForEnd: true }
onExited: root.laptopClosed = String(laptopClosedOut.text || "").trim() === "closed"
}
PolkitAgent {
id: polkitAgent
path: "/org/omarchy/PolkitAgent"
onAuthenticationRequestStarted: root.beginFlow()
onIsActiveChanged: {
if (isActive) root.syncFromFlow()
else if (!root.closing) root.resetSnapshot()
}
onIsRegisteredChanged: {
if (isRegistered) console.log("omarchy polkit agent registered")
else console.warn("omarchy polkit agent is not registered; another agent may be running")
}
}
Connections {
target: polkitAgent.flow
function onIsResponseRequiredChanged() {
root.syncFromFlow()
if (!polkitAgent.flow || !polkitAgent.flow.isResponseRequired) passwordInput.text = ""
Qt.callLater(root.refocus)
}
function onInputPromptChanged() { root.syncFromFlow() }
function onResponseVisibleChanged() { root.syncFromFlow() }
function onSupplementaryMessageChanged() { root.syncFromFlow() }
function onFailedChanged() { root.syncFromFlow() }
function onAuthenticationFailed() {
root.syncFromFlow()
root.triggerFailureFeedback()
}
function onAuthenticationSucceeded() {
root.closing = true
closeTimer.restart()
}
function onAuthenticationRequestCancelled() {
root.closing = true
closeTimer.restart()
}
}
PanelWindow {
id: panel
visible: root.dialogVisible
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
WlrLayershell.namespace: "omarchy-polkit"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
color: root.scrim
}
MouseArea {
anchors.fill: parent
onClicked: root.refocus()
}
BorderSurface {
id: card
width: root.cardWidth
height: root.cardHeight
radius: root.cornerRadius
anchors.centerIn: parent
anchors.horizontalCenterOffset: root.shakeOffset
color: root.background
borderSpec: root.borderSpec
padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: root.refocus() }
Item {
id: keyCatcher
anchors.fill: parent
focus: true
Keys.priority: Keys.BeforeItem
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.cancelRequest()
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (root.responseRequired) root.submitResponse()
event.accepted = true
}
}
}
// Fingerprint mode shows just the sensor icon, centered and alone \u2014 no
// padlock, no field, no prompt text.
OpticalGlyph {
anchors.centerIn: parent
width: Math.round(root.fieldHeight * 0.7)
height: width
visible: root.fingerprintMode
text: "\udb80\ude37"
fontFamily: root.fontFamily
fontSize: Math.round(root.fieldHeight * 0.7)
color: root.errorFlash ? Color.polkit.textError : root.accent
}
Row {
id: cardRow
visible: !root.fingerprintMode
anchors.fill: parent
anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: Style.space(14)
Text {
text: "\uf023"
color: root.errorFlash ? Color.polkit.textError : root.accent
font.family: root.fontFamily
font.pixelSize: Style.font.iconLarge
width: Style.space(26)
height: root.fieldHeight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Item {
width: parent.width - Style.space(40)
height: root.fieldHeight
TextInput {
id: passwordInput
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
activeFocusOnPress: true
clip: true
selectionColor: Util.alpha(root.accent, 0.45)
selectedTextColor: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.iconLarge
echoMode: root.responseVisible ? TextInput.Normal : TextInput.Password
passwordCharacter: "\u2022"
color: root.errorFlash ? Color.polkit.textError : root.foreground
cursorVisible: activeFocus && !root.submitted && !root.errorFlash
readOnly: root.submitted || root.errorFlash
enabled: root.dialogVisible
onAccepted: root.submitResponse()
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.cancelRequest()
event.accepted = true
}
}
}
Text {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: root.errorFlash ? "Wrong" : (root.submitted ? "Checking..." : "Enter password")
color: root.errorFlash ? Color.polkit.textError : root.foreground
opacity: root.errorFlash ? 1 : 0.36
font.family: root.fontFamily
font.pixelSize: Style.font.iconLarge
elide: Text.ElideRight
visible: passwordInput.text.length === 0
}
Rectangle {
width: Math.max(1, Style.space(2))
height: Style.space(24)
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: root.errorFlash ? Color.polkit.textError : root.foreground
visible: passwordInput.visible && passwordInput.activeFocus && passwordInput.text.length === 0 && !root.submitted && !root.errorFlash
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
enabled: passwordInput.visible
onClicked: passwordInput.forceActiveFocus()
}
}
}
}
Rectangle {
width: Math.min(justificationText.implicitWidth + Style.space(24), panel.width - Style.gapsOut * 2)
height: Style.space(28)
anchors.horizontalCenter: card.horizontalCenter
anchors.bottom: card.top
anchors.bottomMargin: Style.space(10)
radius: root.cornerRadius
color: root.background
Text {
id: justificationText
anchors.fill: parent
anchors.leftMargin: Style.space(12)
anchors.rightMargin: Style.space(12)
text: root.authorizationLabel(root.currentMessage)
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideMiddle
}
}
}
}