Switch from gnome polkit to QS
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Polkit
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string omarchyPath: ""
|
||||
property var shell: null
|
||||
property var manifest: null
|
||||
|
||||
property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace"
|
||||
property string styleFile: Quickshell.env("OMARCHY_MENU_STYLE_FILE") || (Quickshell.env("HOME") + "/.local/state/omarchy/toggles/quickshell-menu.json")
|
||||
property color accent: Color.accent
|
||||
property color background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
property color border: foreground
|
||||
property int cornerRadius: 0
|
||||
property int contentMargin: 18
|
||||
property int contentSpacing: 12
|
||||
property int fieldHeight: 42
|
||||
|
||||
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
|
||||
property bool fingerprintFirst: false
|
||||
property int shakeOffset: 0
|
||||
|
||||
readonly property bool dialogVisible: polkitAgent.isActive || closing
|
||||
readonly property bool fingerprintWaiting: dialogVisible && !responseRequired && !submitted && (fingerprintFirst || promptLooksFingerprint(currentPrompt + " " + currentSupplementary))
|
||||
readonly property int cardWidth: Math.min(312, Math.max(260, panel.width - 48))
|
||||
readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - 48) : fieldHeight + contentMargin * 2
|
||||
|
||||
function withAlpha(color, alpha) {
|
||||
return Qt.rgba(color.r, color.g, color.b, alpha)
|
||||
}
|
||||
|
||||
function loadStyle(raw) {
|
||||
try {
|
||||
var style = JSON.parse(raw || "{}")
|
||||
root.cornerRadius = Number(style.radius || 0)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function messageText() {
|
||||
return "Authentication is needed..."
|
||||
}
|
||||
|
||||
function promptLooksFingerprint(text) {
|
||||
var s = String(text || "").toLowerCase()
|
||||
return s.indexOf("finger") !== -1 || s.indexOf("fprint") !== -1 || s.indexOf("swipe") !== -1
|
||||
}
|
||||
|
||||
function loadPamConfig(raw) {
|
||||
fingerprintFirst = false
|
||||
var lines = String(raw || "").split("\n")
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].replace(/^\s+|\s+$/g, "")
|
||||
if (!line || line.charAt(0) === "#") continue
|
||||
if (!line.match(/^auth\s+/)) continue
|
||||
fingerprintFirst = line.indexOf("pam_fprintd.so") !== -1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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 = ""
|
||||
syncFromFlow()
|
||||
Qt.callLater(refocus)
|
||||
}
|
||||
|
||||
function refocus() {
|
||||
if (!dialogVisible) return
|
||||
if (fingerprintWaiting) 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: root.styleFile
|
||||
watchChanges: true
|
||||
onLoaded: root.loadStyle(text())
|
||||
onFileChanged: { reload(); root.loadStyle(text()) }
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/etc/pam.d/polkit-1"
|
||||
watchChanges: true
|
||||
printErrors: false
|
||||
onLoaded: root.loadPamConfig(text())
|
||||
onLoadFailed: root.fingerprintFirst = false
|
||||
onFileChanged: reload()
|
||||
}
|
||||
|
||||
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.withAlpha(root.background, 0.5)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.refocus()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
radius: root.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: root.shakeOffset
|
||||
color: root.background
|
||||
border.color: root.accent
|
||||
border.width: 2
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: cardRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.contentMargin
|
||||
spacing: 14
|
||||
|
||||
Text {
|
||||
text: "\uf023"
|
||||
color: root.errorFlash ? Color.urgent : root.accent
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: 20
|
||||
width: 26
|
||||
height: root.fieldHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 40
|
||||
height: root.fieldHeight
|
||||
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
anchors.fill: parent
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
activeFocusOnPress: true
|
||||
clip: true
|
||||
selectionColor: root.withAlpha(root.accent, 0.45)
|
||||
selectedTextColor: root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: 19
|
||||
echoMode: root.responseVisible ? TextInput.Normal : TextInput.Password
|
||||
passwordCharacter: "\u2022"
|
||||
color: root.errorFlash ? Color.urgent : root.foreground
|
||||
cursorVisible: activeFocus && !root.submitted && !root.errorFlash
|
||||
readOnly: root.submitted || root.errorFlash
|
||||
enabled: root.dialogVisible && !root.fingerprintWaiting
|
||||
visible: !root.fingerprintWaiting
|
||||
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.urgent : root.foreground
|
||||
opacity: root.errorFlash ? 1 : 0.36
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: 19
|
||||
elide: Text.ElideRight
|
||||
visible: passwordInput.visible && passwordInput.text.length === 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 2
|
||||
height: 24
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: root.errorFlash ? Color.urgent : 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "omarchy.polkit",
|
||||
"name": "Polkit Agent",
|
||||
"version": "1.0.0",
|
||||
"author": "Omarchy",
|
||||
"description": "Theme-aware authentication dialog for privileged actions.",
|
||||
"kinds": ["service"],
|
||||
"activation": "persistent",
|
||||
"keepLoaded": true,
|
||||
"entryPoints": {
|
||||
"service": "PolkitAgent.qml"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user