Krolyxon
Commit fb9b856

Branches

Refs →

aurora.configquickshellauroraAuthPrompt.qml

231 lines · 10.0 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Wayland
5
6// Prompt for the auth agents (Agents): a polkit password (Agents.flow) or a bluetooth pairing request
7// (Agents.bt: confirm a passkey, type a PIN/passkey, or show one to type on the device). Polkit wins
8// if both are pending. Overlay layer with exclusive keyboard on the focused monitor; Enter accepts,
9// Esc cancels. Typed text is cleared after every attempt, never logged.
10PanelWindow {
11 id: win
12 readonly property var flow: Agents.flow
13 readonly property var bt: Agents.bt
14 readonly property string mode: Agents.polkitActive ? "polkit" : (Agents.btActive ? "bt" : "")
15 readonly property bool opened: mode !== ""
16 readonly property string btMethod: bt ? bt.method : ""
17 // bluetooth: what the field / big code shows
18 readonly property bool btInput: btMethod === "RequestPinCode" || btMethod === "RequestPasskey"
19 readonly property string btCode: !bt ? "" : (bt.passkey ? bt.passkey.slice(0, 3) + " " + bt.passkey.slice(3) : (bt.pincode || ""))
20 readonly property bool btDisplay: btMethod === "DisplayPasskey" || btMethod === "DisplayPinCode"
21 readonly property bool needsInput: mode === "polkit" || btInput
22
23 visible: opened
24 implicitWidth: 420
25 implicitHeight: col.implicitHeight + 32
26 exclusiveZone: 0
27 color: "transparent"
28 WlrLayershell.layer: WlrLayer.Overlay
29 WlrLayershell.namespace: "aurora-auth"
30 WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
31
32 property bool waiting: false // submitted, polkit is checking
33 property string error: "" // Quickshell doesn't put failures in supplementaryMessage
34 function reset() { pass.text = ""; waiting = false; if (needsInput) pass.forceActiveFocus(); else keys.forceActiveFocus() }
35 function accept() {
36 if (mode === "polkit") {
37 if (!flow || !flow.isResponseRequired || waiting) return
38 waiting = true
39 error = ""
40 flow.submit(pass.text)
41 pass.text = ""
42 } else if (mode === "bt") {
43 if (btInput && pass.text === "") return
44 Agents.btReply(true, pass.text)
45 pass.text = ""
46 }
47 }
48 function cancel() {
49 if (mode === "polkit") { if (flow) flow.cancelAuthenticationRequest() }
50 else if (mode === "bt") Agents.btReply(false)
51 }
52
53 onModeChanged: if (opened) { error = ""; reset() }
54 onBtChanged: if (bt) reset()
55 Connections {
56 target: win.flow
57 function onIsResponseRequiredChanged() { if (win.flow.isResponseRequired) win.reset() }
58 // polkit asks again right after a failure (isResponseRequired goes true with failed set)
59 function onAuthenticationFailed() { win.error = "wrong password, try again"; win.reset() }
60 }
61
62 // keyboard for the button-only bluetooth prompts
63 Item {
64 id: keys
65 Keys.onReturnPressed: win.accept()
66 Keys.onEnterPressed: win.accept()
67 Keys.onEscapePressed: win.cancel()
68 }
69
70 Rectangle {
71 anchors.fill: parent
72 color: Theme.bgA
73 border.width: 2
74 border.color: win.error !== "" || (win.mode === "polkit" && win.flow && win.flow.supplementaryIsError) ? Theme.err : Theme.accent
75
76 ColumnLayout {
77 id: col
78 anchors.left: parent.left
79 anchors.right: parent.right
80 anchors.top: parent.top
81 anchors.margins: 16
82 spacing: 10
83
84 RowLayout {
85 Layout.fillWidth: true
86 spacing: 10
87 Text {
88 text: win.mode === "bt" ? Theme.iBt : Theme.iLock
89 color: Theme.accent
90 font.family: Theme.font
91 font.pixelSize: Theme.fontSize + 8
92 }
93 SectionLabel {
94 Layout.fillWidth: true
95 text: win.mode === "bt" ? "bluetooth pairing" : "authentication required"
96 }
97 }
98 Text {
99 Layout.fillWidth: true
100 color: Theme.fg
101 wrapMode: Text.Wrap
102 font.family: Theme.font
103 font.pixelSize: Theme.fontSize
104 text: {
105 if (win.mode === "polkit") return win.flow ? win.flow.message : ""
106 if (!win.bt) return ""
107 var a = win.bt.alias
108 switch (win.btMethod) {
109 case "RequestConfirmation": return "pair with " + a + "? check that it shows the same code:"
110 case "RequestPinCode": return "enter the PIN for " + a + " (often 0000 or 1234)"
111 case "RequestPasskey": return "enter the 6-digit passkey shown on " + a
112 case "DisplayPasskey":
113 case "DisplayPinCode": return "type this code on " + a + ", then press Enter there:"
114 case "RequestAuthorization": return "allow " + a + " to pair with this computer?"
115 case "AuthorizeService": return "allow " + a + " to use a service?"
116 }
117 return a
118 }
119 }
120 Text {
121 Layout.fillWidth: true
122 visible: text !== ""
123 text: win.mode === "polkit" ? (win.flow ? win.flow.actionId : "")
124 : (win.btMethod === "AuthorizeService" ? win.bt.uuid : "")
125 color: Theme.dim
126 elide: Text.ElideMiddle
127 font.family: Theme.font
128 font.pixelSize: Theme.fontSize - 2
129 }
130
131 // bluetooth passkey / PIN to compare or type on the device
132 Text {
133 Layout.fillWidth: true
134 visible: win.mode === "bt" && win.btCode !== "" && !win.btInput
135 horizontalAlignment: Text.AlignHCenter
136 text: win.btCode
137 color: Theme.accent
138 font.family: Theme.font
139 font.pixelSize: Theme.fontSize * 3
140 font.bold: true
141 }
142
143 // more than one admin: pick who authenticates
144 Flow {
145 Layout.fillWidth: true
146 visible: win.mode === "polkit" && win.flow !== null && win.flow.identities.length > 1
147 spacing: 4
148 Repeater {
149 model: win.mode === "polkit" && win.flow ? win.flow.identities : []
150 delegate: IconBtn {
151 required property var modelData
152 implicitHeight: 24
153 label: modelData.displayName || String(modelData)
154 active: win.flow && win.flow.selectedIdentity === modelData
155 onClicked: win.flow.selectedIdentity = modelData
156 }
157 }
158 }
159
160 Rectangle {
161 Layout.fillWidth: true
162 visible: win.needsInput
163 implicitHeight: 32
164 color: Theme.surface
165 border.width: 1
166 border.color: pass.activeFocus ? Theme.accent : Theme.line
167 Text {
168 id: promptTxt
169 anchors.left: parent.left
170 anchors.leftMargin: 10
171 anchors.verticalCenter: parent.verticalCenter
172 text: win.mode === "bt" ? (win.btMethod === "RequestPinCode" ? "PIN" : "passkey")
173 : (win.flow && win.flow.inputPrompt !== "" ? win.flow.inputPrompt.replace(/:\s*$/, "") : "password")
174 color: Theme.dim
175 font.family: Theme.font
176 font.pixelSize: Theme.fontSize
177 }
178 TextInput {
179 id: pass
180 anchors.fill: parent
181 anchors.leftMargin: promptTxt.implicitWidth + 22
182 anchors.rightMargin: 10
183 verticalAlignment: TextInput.AlignVCenter
184 echoMode: win.mode === "bt" || (win.flow && win.flow.responseVisible) ? TextInput.Normal : TextInput.Password
185 passwordCharacter: "•"
186 maximumLength: win.btMethod === "RequestPasskey" ? 6 : 256
187 validator: RegularExpressionValidator {
188 regularExpression: win.btMethod === "RequestPasskey" ? /[0-9]*/ : /.*/
189 }
190 enabled: !win.waiting
191 clip: true
192 color: Theme.fg
193 selectionColor: Theme.accent
194 selectedTextColor: Theme.bg
195 font.family: Theme.font
196 font.pixelSize: Theme.fontSize
197 Keys.onReturnPressed: win.accept()
198 Keys.onEnterPressed: win.accept()
199 Keys.onEscapePressed: win.cancel()
200 }
201 }
202 Text {
203 Layout.fillWidth: true
204 visible: win.mode === "polkit" && text !== ""
205 text: win.waiting ? "checking…" : (win.error !== "" ? win.error : (win.flow ? win.flow.supplementaryMessage : ""))
206 color: !win.waiting && (win.error !== "" || (win.flow && win.flow.supplementaryIsError)) ? Theme.err : Theme.dim
207 wrapMode: Text.Wrap
208 font.family: Theme.font
209 font.pixelSize: Theme.fontSize - 1
210 }
211
212 RowLayout {
213 Layout.fillWidth: true
214 spacing: 6
215 Item { Layout.fillWidth: true }
216 IconBtn {
217 visible: !win.btDisplay
218 label: win.mode === "bt" ? "reject" : "cancel"
219 implicitHeight: 26
220 onClicked: win.cancel()
221 }
222 IconBtn {
223 label: win.btDisplay ? "close" : (win.mode === "bt" ? (win.btInput ? "pair" : "allow") : "authenticate")
224 implicitHeight: 26
225 active: true
226 onClicked: win.accept()
227 }
228 }
229 }
230 }
231}