Move from hypridle to QS

This commit is contained in:
David Heinemeier Hansson
2026-05-19 12:02:28 +02:00
parent 48dacca6ea
commit 0f5044167c
25 changed files with 326 additions and 99 deletions
-6
View File
@@ -1,6 +0,0 @@
#!/bin/bash
# omarchy:summary=Overwrite the user config for hypridle with the Omarchy default and restart the service.
omarchy-refresh-config hypr/hypridle.conf
omarchy-restart-hypridle
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# omarchy:summary=Restart the hypridle service (used for idle detection and auto-lock).
omarchy-restart-app hypridle
+16 -7
View File
@@ -1,13 +1,22 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Toggle hypridle so the system either idles normally or stays awake # omarchy:summary=Toggle idle behavior so the system either idles normally or stays awake
if pgrep -x hypridle >/dev/null; then if ! state=$(omarchy-shell idle toggle); then
pkill -x hypridle exit 1
omarchy-notification-send -g "󰅶" "Staying awake" "Idle behavior disabled"
else
uwsm-app -- hypridle >/dev/null 2>&1 &
omarchy-notification-send -g "󰾪" "Idle behavior on" "System can now sleep / lock on idle"
fi fi
case $state in
enabled)
omarchy-notification-send -g "󰾪" "Idle behavior on" "System can now lock on idle"
;;
disabled)
omarchy-notification-send -g "󰅶" "Staying awake" "Idle behavior disabled"
;;
*)
echo "Unexpected idle state: $state" >&2
exit 1
;;
esac
omarchy-shell bar refreshIndicators >/dev/null 2>&1 || true omarchy-shell bar refreshIndicators >/dev/null 2>&1 || true
-19
View File
@@ -1,19 +0,0 @@
general {
lock_cmd = omarchy-system-lock # lock screen and 1password
before_sleep_cmd = OMARCHY_LOCK_ONLY=true omarchy-system-lock # lock before suspend without scheduling display off.
after_sleep_cmd = sleep 1 && omarchy-system-wake # delay for PAM readiness, then turn on display.
inhibit_sleep = 3 # wait until screen is locked
}
# Start screensaver after 2.5 minutes
listener {
timeout = 150
on-timeout = pidof hyprlock || omarchy-launch-screensaver
}
# Lock system after 5 minutes (screensaver resets idle timer, so have to just do half + 2s margin)
listener {
timeout = 152
on-timeout = omarchy-system-lock
on-resume = omarchy-system-wake
}
-1
View File
@@ -4,7 +4,6 @@ hl.on("hyprland.start", function()
hl.exec_cmd("dbus-update-activation-environment --systemd --all") hl.exec_cmd("dbus-update-activation-environment --systemd --all")
hl.exec_cmd("quickshell -n -p $HOME/.local/share/omarchy/shell") hl.exec_cmd("quickshell -n -p $HOME/.local/share/omarchy/shell")
hl.exec_cmd("uwsm-app -- hypridle")
hl.exec_cmd("uwsm-app -- fcitx5 --disable notificationitem") hl.exec_cmd("uwsm-app -- fcitx5 --disable notificationitem")
hl.exec_cmd("omarchy-first-run") hl.exec_cmd("omarchy-first-run")
hl.exec_cmd("omarchy-powerprofiles-init") hl.exec_cmd("omarchy-powerprofiles-init")
+1 -2
View File
@@ -136,8 +136,7 @@ Run `omarchy --help` for the full list. The most common groups:
├── looknfeel.conf # Appearance (gaps, borders, animations) ├── looknfeel.conf # Appearance (gaps, borders, animations)
├── envs.conf # Environment variables ├── envs.conf # Environment variables
├── autostart.conf # Startup applications ├── autostart.conf # Startup applications
├── hypridle.conf # Idle behavior (screen off, lock, suspend) ├── hyprlock.conf # Lock screen appearance and auth
├── hyprlock.conf # Lock screen appearance
└── hyprsunset.conf # Night light / blue light filter └── hyprsunset.conf # Night light / blue light filter
``` ```
+1 -4
View File
@@ -76,7 +76,7 @@
"trigger.share.receive": {"icon":"󰥦","label":"Receive","keywords":"localsend","action":"uwsm-app -- localsend"}, "trigger.share.receive": {"icon":"󰥦","label":"Receive","keywords":"localsend","action":"uwsm-app -- localsend"},
"trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"}, "trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"},
"trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","keywords":"night light hyprsunset","action":"omarchy-toggle-nightlight"}, "trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","keywords":"night light hyprsunset","action":"omarchy-toggle-nightlight"},
"trigger.toggle.idle-lock": {"icon":"󱫖","label":"Idle Lock","keywords":"hypridle","action":"omarchy-toggle-idle"}, "trigger.toggle.idle-lock": {"icon":"󱫖","label":"Idle Lock","keywords":"idle quickshell","action":"omarchy-toggle-idle"},
"trigger.toggle.notifications": {"icon":"󰂛","label":"Notifications","keywords":"silence dnd","action":"omarchy-toggle-notification-silencing"}, "trigger.toggle.notifications": {"icon":"󰂛","label":"Notifications","keywords":"silence dnd","action":"omarchy-toggle-notification-silencing"},
"trigger.toggle.top-bar": {"icon":"󰍜","label":"Bar","keywords":"top bar quickshell","action":"omarchy-toggle-bar"}, "trigger.toggle.top-bar": {"icon":"󰍜","label":"Bar","keywords":"top bar quickshell","action":"omarchy-toggle-bar"},
"trigger.toggle.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"}, "trigger.toggle.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"},
@@ -162,7 +162,6 @@
"setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"}, "setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"},
"setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"}, "setup.security.fido2": {"icon":"","label":"Fido2","keywords":"key","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""}, "setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
"setup.config.hypridle": {"icon":"","label":"Hypridle","keywords":"idle","action":"omarchy-launch-config-editor ~/.config/hypr/hypridle.conf && omarchy-restart-hypridle"},
"setup.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"lock","action":"omarchy-launch-config-editor ~/.config/hypr/hyprlock.conf"}, "setup.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"lock","action":"omarchy-launch-config-editor ~/.config/hypr/hyprlock.conf"},
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"}, "setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"night light","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
"setup.config.walker": {"icon":"󰌧","label":"Walker","keywords":"launcher","action":"omarchy-launch-config-editor ~/.config/walker/config.toml && omarchy-restart-walker"}, "setup.config.walker": {"icon":"󰌧","label":"Walker","keywords":"launcher","action":"omarchy-launch-config-editor ~/.config/walker/config.toml && omarchy-restart-walker"},
@@ -309,13 +308,11 @@
"update.channel.rc": {"icon":"🟡","label":"RC","keywords":"release candidate","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set rc'"}, "update.channel.rc": {"icon":"🟡","label":"RC","keywords":"release candidate","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set rc'"},
"update.channel.edge": {"icon":"🟠","label":"Edge","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set edge'"}, "update.channel.edge": {"icon":"🟠","label":"Edge","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set edge'"},
"update.channel.dev": {"icon":"🔴","label":"Dev","keywords":"development","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set dev'"}, "update.channel.dev": {"icon":"🔴","label":"Dev","keywords":"development","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-channel-set dev'"},
"update.process.hypridle": {"icon":"","label":"Hypridle","keywords":"restart","action":"omarchy-restart-hypridle"},
"update.process.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"restart","action":"omarchy-restart-hyprsunset"}, "update.process.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"restart","action":"omarchy-restart-hyprsunset"},
"update.process.osd": {"icon":"","label":"OSD","keywords":"restart","action":"omarchy-restart-shell"}, "update.process.osd": {"icon":"","label":"OSD","keywords":"restart","action":"omarchy-restart-shell"},
"update.process.walker": {"icon":"󰌧","label":"Walker","keywords":"restart launcher","action":"omarchy-restart-walker"}, "update.process.walker": {"icon":"󰌧","label":"Walker","keywords":"restart launcher","action":"omarchy-restart-walker"},
"update.process.shell": {"icon":"󰍜","label":"Shell","keywords":"restart bar quickshell omarchy-shell","action":"omarchy-restart-shell"}, "update.process.shell": {"icon":"󰍜","label":"Shell","keywords":"restart bar quickshell omarchy-shell","action":"omarchy-restart-shell"},
"update.config.hyprland": {"icon":"","label":"Hyprland","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprland"}, "update.config.hyprland": {"icon":"","label":"Hyprland","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprland"},
"update.config.hypridle": {"icon":"","label":"Hypridle","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hypridle"},
"update.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprlock"}, "update.config.hyprlock": {"icon":"","label":"Hyprlock","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprlock"},
"update.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprsunset"}, "update.config.hyprsunset": {"icon":"","label":"Hyprsunset","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-hyprsunset"},
"update.config.plymouth": {"icon":"󱣴","label":"Plymouth","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-plymouth"}, "update.config.plymouth": {"icon":"󱣴","label":"Plymouth","keywords":"refresh","action":"omarchy-launch-floating-terminal-with-presentation omarchy-refresh-plymouth"},
+6 -1
View File
@@ -67,6 +67,10 @@ individual plugins (`bar`, `image-selector`, …).
```json ```json
{ {
"version": 1, "version": 1,
"idle": {
"screensaver": 150,
"lock": 300
},
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
@@ -92,7 +96,8 @@ Rules:
merge layers. merge layers.
3. Third-party enabled ⇔ present; first-party plugins are always enabled. 3. Third-party enabled ⇔ present; first-party plugins are always enabled.
4. `allowMultiple: true` in the manifest permits multiple instances. 4. `allowMultiple: true` in the manifest permits multiple instances.
5. `version: 1` is required. 5. `idle.screensaver` and `idle.lock` are seconds since user idle began.
6. `version: 1` is required.
`shell-defaults.json` describes the fresh-install state. When no `shell-defaults.json` describes the fresh-install state. When no
user `shell.json` exists, defaults are used verbatim. Once the user user `shell.json` exists, defaults are used verbatim. Once the user
-1
View File
@@ -49,7 +49,6 @@ gum
gvfs-mtp gvfs-mtp
gvfs-nfs gvfs-nfs
gvfs-smb gvfs-smb
hypridle
hyprland hyprland
hyprland-guiutils hyprland-guiutils
hyprland-preview-share-picker hyprland-preview-share-picker
-3
View File
@@ -1,3 +0,0 @@
echo "Use omarchy-system-lock and omarchy-system-wake in hypridle"
omarchy-refresh-hypridle
+10
View File
@@ -0,0 +1,10 @@
echo "Move idle handling from hypridle to omarchy-shell"
if [[ -f ~/.config/hypr/autostart.lua ]]; then
sed -i '/hypridle/d' ~/.config/hypr/autostart.lua
fi
omarchy-refresh-shell
pkill -x hypridle 2>/dev/null || true
omarchy-pkg-drop hypridle
omarchy-restart-shell || true
+8 -1
View File
@@ -160,6 +160,10 @@ rewrites the `bar` subtree from the current `shell-defaults.json`.
```json ```json
{ {
"version": 1, "version": 1,
"idle": {
"screensaver": 150,
"lock": 300
},
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
@@ -193,7 +197,10 @@ rewrites the `bar` subtree from the current `shell-defaults.json`.
`allowMultiple: true`. Each instance is independent — e.g. two clocks `allowMultiple: true`. Each instance is independent — e.g. two clocks
in different timezones are just two `{"id":"calendar", "timezone": ...}` in different timezones are just two `{"id":"calendar", "timezone": ...}`
entries with their own values. entries with their own values.
5. **`version: 1` is required** at the top level. The shell will fall back 5. **Idle timings are top-level.** `idle.screensaver` and `idle.lock`
are seconds since user idle began, so the default lock fires at 300s
even if the 150s screensaver starts first.
6. **`version: 1` is required** at the top level. The shell will fall back
to defaults rather than load an unknown version. to defaults rather than load an unknown version.
## Implementation history ## Implementation history
+1
View File
@@ -18,6 +18,7 @@ User-installed plugins live alongside these conceptually but on disk under
| Clipboard mgr | `omarchy.clipboard-picker`| `overlay` | `clipboard-picker/ClipboardPicker.qml`| | Clipboard mgr | `omarchy.clipboard-picker`| `overlay` | `clipboard-picker/ClipboardPicker.qml`|
| Omarchy menu | `omarchy.menu` | `menu` | `menu/Menu.qml` | | Omarchy menu | `omarchy.menu` | `menu` | `menu/Menu.qml` |
| Notifications | `omarchy.notifications` | `service` | `notifications/Service.qml` | | Notifications | `omarchy.notifications` | `service` | `notifications/Service.qml` |
| Idle monitor | `omarchy.idle` | `service` | `idle/Service.qml` |
| OSD | `omarchy.osd` | `panel` | `osd/Osd.qml` | | OSD | `omarchy.osd` | `panel` | `osd/Osd.qml` |
| Polkit agent | `omarchy.polkit` | `service` | `polkit/PolkitAgent.qml` | | Polkit agent | `omarchy.polkit` | `service` | `polkit/PolkitAgent.qml` |
+6 -11
View File
@@ -12,8 +12,7 @@ import qs.Ui
Item { Item {
id: root id: root
// The omarchy-shell host injects omarchyPath when it instantiates this Bar. // The omarchy-shell host injects omarchyPath from OMARCHY_PATH.
// Default fallback keeps the file loadable in isolation (e.g. for QML tooling).
required property string omarchyPath required property string omarchyPath
// Injected by the host shell. Shared with the bar settings panel so both // Injected by the host shell. Shared with the bar settings panel so both
// see the same widget catalogue. // see the same widget catalogue.
@@ -110,10 +109,6 @@ Item {
return "file://" + path.split("/").map(encodeURIComponent).join("/") return "file://" + path.split("/").map(encodeURIComponent).join("/")
} }
function commandWithOmarchyPath(command) {
return "OMARCHY_PATH=" + shellQuote(omarchyPath) + " " + command
}
function isPlainObject(value) { function isPlainObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value) return value !== null && typeof value === "object" && !Array.isArray(value)
} }
@@ -539,7 +534,7 @@ Item {
Process { Process {
id: updateProc id: updateProc
command: ["bash", "-lc", root.commandWithOmarchyPath("omarchy-update-available")] command: ["bash", "-lc", "omarchy-update-available"]
stdout: StdioCollector { waitForEnd: true } stdout: StdioCollector { waitForEnd: true }
onExited: function(exitCode) { onExited: function(exitCode) {
root.updateAvailable = exitCode === 0 root.updateAvailable = exitCode === 0
@@ -556,7 +551,7 @@ Item {
Process { Process {
id: voxtypeProc id: voxtypeProc
command: ["bash", "-lc", root.commandWithOmarchyPath("omarchy-voxtype-status")] command: ["bash", "-lc", "omarchy-voxtype-status"]
running: true running: true
stdout: SplitParser { stdout: SplitParser {
onRead: function(data) { onRead: function(data) {
@@ -567,7 +562,7 @@ Item {
Process { Process {
id: screenRecordingProc id: screenRecordingProc
command: ["bash", "-lc", root.commandWithOmarchyPath(root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/screen-recording.sh"))] command: ["bash", "-lc", root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/screen-recording.sh")]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: root.updateIndicator("screenRecording", text) onStreamFinished: root.updateIndicator("screenRecording", text)
@@ -576,7 +571,7 @@ Item {
Process { Process {
id: notificationSilencingProc id: notificationSilencingProc
command: ["bash", "-lc", root.commandWithOmarchyPath(root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/notification-silencing.sh"))] command: ["bash", "-lc", root.shellQuote(root.omarchyPath + "/shell/scripts/indicators/notification-silencing.sh")]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: root.updateIndicator("notifications", text) onStreamFinished: root.updateIndicator("notifications", text)
@@ -1037,7 +1032,7 @@ Item {
Process { Process {
id: customProc id: customProc
command: ["bash", "-lc", root.commandWithOmarchyPath(String(customRoot.setting("exec", "")))] command: ["bash", "-lc", String(customRoot.setting("exec", ""))]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: customRoot.update(text) onStreamFinished: customRoot.update(text)
+9 -3
View File
@@ -33,11 +33,17 @@ Item {
Process { Process {
id: statusProc id: statusProc
command: ["bash", "-lc", "pgrep -x hypridle >/dev/null 2>&1 && echo running || echo stopped"] command: ["bash", "-lc", "omarchy-shell idle status 2>/dev/null"]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: { onStreamFinished: {
root.active = String(text || "").trim() === "stopped" var raw = String(text || "").trim()
try {
var parsed = JSON.parse(raw)
root.active = parsed && parsed.enabled === false
} catch (e) {
root.active = false
}
} }
} }
} }
@@ -65,7 +71,7 @@ Item {
bar: root.bar bar: root.bar
text: root.icon text: root.icon
active: false active: false
tooltipText: root.active ? "No sleep, lock, or screensaver" : "" tooltipText: root.active ? "No lock or screensaver" : ""
onPressed: function() { root.toggle() } onPressed: function() { root.toggle() }
} }
} }
+1 -1
View File
@@ -1328,7 +1328,7 @@ iwctl known-networks list 2>/dev/null \\
// Bar.qml does not need to mirror network state. // Bar.qml does not need to mirror network state.
Process { Process {
id: networkProc id: networkProc
command: ["bash", "-lc", root.bar ? root.bar.commandWithOmarchyPath(root.networkCommand()) : ""] command: ["bash", "-lc", root.bar ? root.networkCommand() : ""]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: root.updateNetwork(text) onStreamFinished: root.updateNetwork(text)
+1 -1
View File
@@ -552,7 +552,7 @@ Item {
// Poll the weather pill text/class every minute. Local to this widget. // Poll the weather pill text/class every minute. Local to this widget.
Process { Process {
id: weatherProc id: weatherProc
command: ["bash", "-lc", root.bar ? root.bar.commandWithOmarchyPath(root.bar.shellQuote(root.bar.omarchyPath + "/shell/scripts/weather.sh")) : ""] command: ["bash", "-lc", root.bar ? root.bar.shellQuote(root.bar.omarchyPath + "/shell/scripts/weather.sh") : ""]
stdout: StdioCollector { stdout: StdioCollector {
waitForEnd: true waitForEnd: true
onStreamFinished: root.updateWeather(text) onStreamFinished: root.updateWeather(text)
+1 -1
View File
@@ -7,7 +7,7 @@ import qs.Commons
Item { Item {
id: root id: root
property string omarchyPath: Quickshell.env("OMARCHY_PATH") || (Quickshell.env("HOME") + "/.local/share/omarchy") property string omarchyPath: Quickshell.env("OMARCHY_PATH")
property var shell: null property var shell: null
property var manifest: null property var manifest: null
+234
View File
@@ -0,0 +1,234 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
Item {
id: root
// Injected by omarchy-shell (the first-party service loader).
property var shell: null
readonly property int defaultScreensaverSeconds: 150
readonly property int defaultLockSeconds: 300
readonly property var idleConfig: shell && shell.shellConfig && shell.shellConfig.idle ? shell.shellConfig.idle : ({})
readonly property int screensaverTimeoutSeconds: secondsFromConfig(idleConfig.screensaver, defaultScreensaverSeconds)
readonly property int lockTimeoutSeconds: secondsFromConfig(idleConfig.lock, defaultLockSeconds)
readonly property int firstIdleTimeoutSeconds: Math.min(screensaverTimeoutSeconds, lockTimeoutSeconds)
readonly property int screensaverDelaySeconds: Math.max(0, screensaverTimeoutSeconds - firstIdleTimeoutSeconds)
readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds)
readonly property bool idleEnabled: persisted.idleEnabled
property bool idledThisCycle: false
property bool screensaverStartedThisCycle: false
PersistentProperties {
id: persisted
reloadableId: "omarchy-idle"
property bool idleEnabled: true
}
function secondsFromConfig(value, fallback) {
var n = Number(value)
if (!isFinite(n) || n < 0) return fallback
return Math.floor(n)
}
function runProcess(process, command) {
if (process.running) return false
process.command = ["bash", "-lc", command]
process.running = true
return true
}
function launchScreensaver() {
root.screensaverStartedThisCycle = true
runProcess(screensaverProcess, "pidof hyprlock >/dev/null || omarchy-launch-screensaver")
}
function lockSystem() {
screensaverTimer.stop()
lockTimer.stop()
screensaverResumePollTimer.stop()
root.idledThisCycle = false
root.screensaverStartedThisCycle = false
runProcess(lockProcess, "omarchy-system-lock")
}
function startIdleCycle() {
if (root.idledThisCycle) return
root.idledThisCycle = true
root.screensaverStartedThisCycle = false
screensaverResumePollTimer.stop()
if (root.screensaverDelaySeconds === 0) launchScreensaver()
else screensaverTimer.restart()
if (root.lockDelaySeconds === 0) lockSystem()
else lockTimer.restart()
}
function cancelIdleCycle() {
screensaverTimer.stop()
lockTimer.stop()
screensaverResumePollTimer.stop()
if (root.idledThisCycle) runProcess(wakeProcess, "omarchy-system-wake")
root.idledThisCycle = false
root.screensaverStartedThisCycle = false
}
function checkScreensaverAfterActiveSignal() {
if (!root.idledThisCycle || !root.screensaverStartedThisCycle) return
if (screensaverCheckProcess.running) return
screensaverCheckProcess.command = ["bash", "-lc", "pgrep -f org.omarchy.screensaver >/dev/null && echo running || echo stopped"]
screensaverCheckProcess.running = true
}
function handleActiveSignal() {
if (!root.idledThisCycle) return
// Starting the screensaver can make the compositor report activity. Keep
// the lock timer running while the screensaver is still alive so the lock
// deadline remains `idle.lock` seconds from the original user idle time.
if (root.screensaverStartedThisCycle) {
screensaverResumePollTimer.restart()
return
}
cancelIdleCycle()
}
function handleIdleChanged() {
if (!root.idleEnabled) return
if (idleMonitor.isIdle) startIdleCycle()
else handleActiveSignal()
}
function statusJson() {
return JSON.stringify({
enabled: root.idleEnabled,
idle: idleMonitor.isIdle,
inIdleCycle: root.idledThisCycle,
sleepMonitor: sleepMonitorProcess.running,
screensaver: root.screensaverTimeoutSeconds,
lock: root.lockTimeoutSeconds
})
}
function handleSleepPreparing(preparing) {
if (!root.idleEnabled) return
if (preparing) {
cancelIdleCycle()
runProcess(sleepLockProcess, "OMARCHY_LOCK_ONLY=true omarchy-system-lock")
} else {
runProcess(sleepWakeProcess, "sleep 1 && omarchy-system-wake")
}
}
function setIdleEnabled(value) {
var enabled = !!value
if (persisted.idleEnabled === enabled) return enabled ? "enabled" : "disabled"
persisted.idleEnabled = enabled
if (!enabled) cancelIdleCycle()
else Qt.callLater(root.handleIdleChanged)
return enabled ? "enabled" : "disabled"
}
IdleMonitor {
id: idleMonitor
enabled: root.idleEnabled
timeout: root.firstIdleTimeoutSeconds
respectInhibitors: true
onIsIdleChanged: root.handleIdleChanged()
}
Timer {
id: screensaverTimer
interval: root.screensaverDelaySeconds * 1000
repeat: false
onTriggered: root.launchScreensaver()
}
Timer {
id: lockTimer
interval: root.lockDelaySeconds * 1000
repeat: false
onTriggered: if (root.idleEnabled && root.idledThisCycle) root.lockSystem()
}
Timer {
id: screensaverResumePollTimer
interval: 1000
repeat: true
onTriggered: root.checkScreensaverAfterActiveSignal()
}
Process { id: screensaverProcess }
Process { id: lockProcess }
Process { id: wakeProcess }
Process { id: sleepLockProcess }
Process { id: sleepWakeProcess }
Process {
id: screensaverCheckProcess
stdout: StdioCollector {
id: screensaverCheckStdout
waitForEnd: true
}
onExited: {
if (!root.idledThisCycle || !root.screensaverStartedThisCycle) return
if (String(screensaverCheckStdout.text || "").trim() === "running") return
root.cancelIdleCycle()
}
}
Process {
id: sleepMonitorProcess
command: ["bash", "-lc", "exec dbus-monitor --system \"type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'\""]
running: true
stdout: SplitParser {
onRead: function(line) {
var text = String(line || "").trim()
if (text === "boolean true") root.handleSleepPreparing(true)
else if (text === "boolean false") root.handleSleepPreparing(false)
}
}
onExited: sleepMonitorRestartTimer.restart()
}
Timer {
id: sleepMonitorRestartTimer
interval: 5000
repeat: false
onTriggered: if (!sleepMonitorProcess.running) sleepMonitorProcess.running = true
}
Component.onCompleted: Qt.callLater(root.handleIdleChanged)
IpcHandler {
target: "idle"
function status(): string {
return root.statusJson()
}
function enable(): string {
return root.setIdleEnabled(true)
}
function disable(): string {
return root.setIdleEnabled(false)
}
function toggle(): string {
return root.setIdleEnabled(!root.idleEnabled)
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"schemaVersion": 1,
"id": "omarchy.idle",
"name": "Idle Monitor",
"version": "1.0.0",
"author": "Omarchy",
"description": "Quickshell-based idle detection for screensaver, lock, and display wake.",
"kinds": [
"service"
],
"keepLoaded": true,
"entryPoints": {
"service": "Service.qml"
}
}
+3 -5
View File
@@ -9,9 +9,8 @@ import qs.Commons
Item { Item {
id: root id: root
// Injected by omarchy-shell so helper scripts resolve without relying on // Injected by omarchy-shell; defaults to the session OMARCHY_PATH.
// OMARCHY_PATH being set in the environment. property string omarchyPath: Quickshell.env("OMARCHY_PATH")
property string omarchyPath: ""
property string imageDirs: Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIRS") || Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIR") || Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/backgrounds") property string imageDirs: Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIRS") || Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIR") || Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/backgrounds")
property string imageRows: "" property string imageRows: ""
property string loadedImageRows: "" property string loadedImageRows: ""
@@ -53,8 +52,7 @@ Item {
} }
function scriptPath(name) { function scriptPath(name) {
var base = omarchyPath || Quickshell.env("OMARCHY_PATH") || (Quickshell.env("HOME") + "/.local/share/omarchy") return omarchyPath + "/shell/scripts/" + name
return base + "/shell/scripts/" + name
} }
function focusPicker() { function focusPicker() {
+1 -1
View File
@@ -8,7 +8,7 @@ Item {
id: root id: root
// Injected by omarchy-shell when this plugin is summoned. // Injected by omarchy-shell when this plugin is summoned.
property string omarchyPath: Quickshell.env("OMARCHY_PATH") || (Quickshell.env("HOME") + "/.local/share/omarchy") property string omarchyPath: Quickshell.env("OMARCHY_PATH")
// Plugin lifecycle hooks. The host calls open(payloadJson) after // Plugin lifecycle hooks. The host calls open(payloadJson) after
// `omarchy-shell shell summon omarchy.menu ...` and close() when hidden. // `omarchy-shell shell summon omarchy.menu ...` and close() when hidden.
+1 -11
View File
@@ -33,17 +33,7 @@ Item {
property var shell: null property var shell: null
// ---------------- paths -------------------------------------------------- // ---------------- paths --------------------------------------------------
property string omarchyPath: { property string omarchyPath: Quickshell.env("OMARCHY_PATH")
var env = Quickshell.env("OMARCHY_PATH")
if (env) return env
var dir = String(Quickshell.shellDir || "")
while (dir.length > 1 && dir.charAt(dir.length - 1) === "/")
dir = dir.substring(0, dir.length - 1)
var suffix = "/shell"
if (dir.length > suffix.length && dir.substring(dir.length - suffix.length) === suffix)
return dir.substring(0, dir.length - suffix.length)
return Quickshell.env("HOME") + "/.local/share/omarchy"
}
readonly property string home: Quickshell.env("HOME") readonly property string home: Quickshell.env("HOME")
readonly property string userConfigPath: home + "/.config/omarchy/shell.json" readonly property string userConfigPath: home + "/.config/omarchy/shell.json"
readonly property string defaultsPath: omarchyPath + "/shell/shell-defaults.json" readonly property string defaultsPath: omarchyPath + "/shell/shell-defaults.json"
+4
View File
@@ -1,5 +1,9 @@
{ {
"version": 1, "version": 1,
"idle": {
"screensaver": 150,
"lock": 300
},
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
+7 -15
View File
@@ -21,21 +21,9 @@ ShellRoot {
property string home: Quickshell.env("HOME") property string home: Quickshell.env("HOME")
// The omarchy-shell host is the long-running entry point. Plugins live in // The omarchy-shell host is the long-running entry point. Plugins live in
// sibling directories under plugins/. Most child code (the bar, future // sibling directories under plugins/. OMARCHY_PATH is provided by the uwsm
// plugins) accept omarchyPath as a required property; resolve it once here // session environment and is the single source of truth for this checkout.
// from the shellDir so we don't depend on OMARCHY_PATH being set in the env. property string omarchyPath: Quickshell.env("OMARCHY_PATH")
function deriveOmarchyPath() {
var env = Quickshell.env("OMARCHY_PATH")
if (env) return env
var dir = String(Quickshell.shellDir || "")
while (dir.length > 1 && dir.charAt(dir.length - 1) === "/")
dir = dir.substring(0, dir.length - 1)
var suffix = "/shell"
if (dir.length > suffix.length && dir.substring(dir.length - suffix.length) === suffix)
return dir.substring(0, dir.length - suffix.length)
return home + "/.local/share/omarchy"
}
property string omarchyPath: deriveOmarchyPath()
readonly property string shellPath: omarchyPath + "/shell" readonly property string shellPath: omarchyPath + "/shell"
readonly property string firstPartyPluginsDir: shellPath + "/plugins" readonly property string firstPartyPluginsDir: shellPath + "/plugins"
readonly property string defaultsPath: shellPath + "/shell-defaults.json" readonly property string defaultsPath: shellPath + "/shell-defaults.json"
@@ -46,6 +34,10 @@ ShellRoot {
// closely enough to render a usable bar; not authoritative. // closely enough to render a usable bar; not authoritative.
readonly property var builtinShellConfig: ({ readonly property var builtinShellConfig: ({
version: 1, version: 1,
idle: {
screensaver: 150,
lock: 300
},
bar: { bar: {
position: "top", position: "top",
transparent: false, transparent: false,