Switch from gnome polkit to QS

This commit is contained in:
David Heinemeier Hansson
2026-05-16 22:20:25 +02:00
parent ad20041c22
commit 8f643282b9
9 changed files with 398 additions and 13 deletions
-1
View File
@@ -2,7 +2,6 @@ hl.on("hyprland.start", function()
hl.exec_cmd("omarchy-restart-shell")
hl.exec_cmd("uwsm-app -- hypridle")
hl.exec_cmd("uwsm-app -- fcitx5 --disable notificationitem")
hl.exec_cmd("/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1")
hl.exec_cmd("omarchy-first-run")
hl.exec_cmd("omarchy-powerprofiles-init")
hl.exec_cmd("uwsm-app -- omarchy-hyprland-monitor-watch")
+1 -1
View File
@@ -160,7 +160,7 @@ inside a single long-running Quickshell process (`omarchy-shell`).
The shell hot-reloads `shell.json` on save — no restart needed for layout
changes. For more invasive changes (new plugin, packaged update):
**Commands:** `omarchy-restart-shell`, `omarchy refresh shell`, `omarchy launch settings`
**Commands:** `omarchy-restart-shell`, `omarchy refresh shell`, `omarchy launch bar settings`
### Terminals
+9 -7
View File
@@ -31,6 +31,9 @@ default/quickshell/omarchy-shell/
settings/
image-picker/
menu/
notifications/
osd/
polkit/
```
The plugin discovery path is documented in [plugins/README.md](plugins/README.md).
@@ -87,10 +90,8 @@ The full schema lives in `services/PluginRegistry.qml`.
1. Drop the plugin into `~/.config/omarchy/plugins/<plugin-id>/`.
The directory must contain a `manifest.json` plus the QML files
referenced from its `entryPoints`.
2. `omarchy-shell-ipc shell rescanPlugins` — or open the Plugin Manager
tab in `omarchy launch settings` and click **Rescan**.
3. Enable the plugin (Plugin Manager **Enable** toggle, or
`omarchy-shell-ipc shell setPluginEnabled <id> true`).
2. `omarchy-shell-ipc shell rescanPlugins`.
3. Enable the plugin with `omarchy-shell-ipc shell setPluginEnabled <id> true`.
4. If it's a `bar-widget`, add it to a layout section from the bar editor.
First-party plugins under `default/quickshell/omarchy-shell/plugins/`
@@ -153,8 +154,8 @@ The `shell-defaults.json` bundled with the shell describes the
fresh-install state. When the user has no `shell.json`, the shell uses
the defaults verbatim. Once the user customizes anything, `shell.json`
becomes the authoritative file — we do **not** deep-merge defaults back
in. Pressing **Reset to defaults** in `omarchy launch settings`
rewrites `shell.json` from the current `shell-defaults.json`.
in. Pressing **Reset bar to defaults** in `omarchy launch bar settings`
rewrites the `bar` subtree from the current `shell-defaults.json`.
### shell.json shape
@@ -189,7 +190,8 @@ rewrites `shell.json` from the current `shell-defaults.json`.
separate per-plugin settings file, no merge layers. The fields on each
entry are the values the plugin sees.
3. **Enabled ⇔ present.** A plugin is enabled iff its id appears somewhere
in shell.json. To disable, remove it. (The bar settings UI does both.)
in shell.json. For bar widgets, the bar settings UI adds/removes layout
entries; other plugin kinds are enabled with the shell IPC.
4. **Multiple instances** are allowed when a manifest sets
`allowMultiple: true`. Each instance is independent — e.g. two clocks
in different timezones are just two `{"id":"calendar", "timezone": ...}`
@@ -31,7 +31,7 @@ and customization schema.
Visual editor for the bar layout. Summoned by
`omarchy-shell-ipc shell summon omarchy.settings "{}"` (which is what
`omarchy launch settings` ultimately calls). Provides:
`omarchy launch bar settings` ultimately calls). Provides:
- bar position and center-anchor controls
- per-section add/move/remove/edit of bar widget entries
@@ -14,9 +14,9 @@ the shell for its whole session.
## Customizing
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`shell-defaults.json`](../../shell-defaults.json). Once you customize anything via `omarchy launch settings` or by editing shell.json directly, your file is canonical — there is no deep-merge.
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`shell-defaults.json`](../../shell-defaults.json). Once you customize anything via `omarchy launch bar settings` or by editing shell.json directly, your file is canonical — there is no deep-merge.
Launch the visual editor with `omarchy launch settings` (or run `omarchy-launch-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand.
Launch the visual editor with `omarchy launch bar settings` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand.
Example `shell.json` (bar subtree only shown):
@@ -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"
}
}
-1
View File
@@ -98,7 +98,6 @@ pinta
playerctl
plocate
plymouth
polkit-gnome
postgresql-libs
power-profiles-daemon
python-gobject
+13
View File
@@ -0,0 +1,13 @@
echo "Switch polkit authentication to omarchy-shell"
# The default Hyprland autostart now uses omarchy-shell's native Quickshell
# Polkit agent. Remove any stale user override that starts the old GTK agent.
if [[ -f ~/.config/hypr/autostart.lua ]]; then
sed -i '/polkit-gnome-authentication-agent-1/d' ~/.config/hypr/autostart.lua
fi
pkill -u "$USER" -f '^/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1$' 2>/dev/null || true
omarchy-pkg-drop polkit-gnome
omarchy-restart-shell