From 26281b3863cbd8261580a0f2485e0553211908e8 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Tue, 19 May 2026 21:01:15 -0400 Subject: [PATCH] Prevent maching the check causing spontaneous locking --- bin/omarchy-launch-screensaver | 4 +- bin/omarchy-screensaver | 2 +- bin/omarchy-system-lock | 2 +- shell/plugins/idle/Service.qml | 147 ++++++++++++++++++++------------- 4 files changed, 95 insertions(+), 60 deletions(-) diff --git a/bin/omarchy-launch-screensaver b/bin/omarchy-launch-screensaver index 1702adb7..13480bbf 100755 --- a/bin/omarchy-launch-screensaver +++ b/bin/omarchy-launch-screensaver @@ -6,8 +6,8 @@ if ! command -v tte &>/dev/null; then exit 1 fi -# Exit early if screensave is already running -pgrep -f org.omarchy.screensaver && exit 0 +# Exit early if screensaver is already running +pgrep -f '[o]rg.omarchy.screensaver' && exit 0 # Allow screensaver to be turned off but also force started if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then diff --git a/bin/omarchy-screensaver b/bin/omarchy-screensaver index 9a3ed6cf..040d772a 100755 --- a/bin/omarchy-screensaver +++ b/bin/omarchy-screensaver @@ -9,7 +9,7 @@ screensaver_in_focus() { exit_screensaver() { hyprctl eval 'hl.config({ cursor = { invisible = false } })' &>/dev/null || hyprctl keyword cursor:invisible false &>/dev/null || true pkill -x tte 2>/dev/null - pkill -f org.omarchy.screensaver 2>/dev/null + pkill -f '[o]rg.omarchy.screensaver' 2>/dev/null exit 0 } diff --git a/bin/omarchy-system-lock b/bin/omarchy-system-lock index 1cc653d6..bd30359f 100755 --- a/bin/omarchy-system-lock +++ b/bin/omarchy-system-lock @@ -16,4 +16,4 @@ if pgrep -x "1password" >/dev/null; then fi # Avoid running screensaver when locked -pkill -f '[o]rg.omarchy.screensaver' +pkill -f '[o]rg.omarchy.screensaver' 2>/dev/null || true diff --git a/shell/plugins/idle/Service.qml b/shell/plugins/idle/Service.qml index 79c0cdd4..c6651cc2 100644 --- a/shell/plugins/idle/Service.qml +++ b/shell/plugins/idle/Service.qml @@ -1,5 +1,6 @@ import QtQuick import Quickshell +import Quickshell.Hyprland import Quickshell.Io import Quickshell.Wayland @@ -18,14 +19,14 @@ Item { readonly property int screensaverDelaySeconds: Math.max(0, screensaverTimeoutSeconds - firstIdleTimeoutSeconds) readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds) readonly property bool idleEnabled: persisted.idleEnabled + readonly property string screensaverClass: "org.omarchy.screensaver" property bool idledThisCycle: false property bool screensaverStartedThisCycle: false property string lastEvent: "starting" property string lastEventAt: "" - property string lastScreensaverState: "unknown" - property string lastScreensaverCheckAt: "" - property int screensaverCheckCount: 0 + property var screensaverWindows: ({}) + property int screensaverWindowCount: 0 PersistentProperties { id: persisted @@ -63,7 +64,7 @@ Item { function launchScreensaver() { root.screensaverStartedThisCycle = true - screensaverResumePollTimer.restart() + screensaverLaunchGraceTimer.restart() runProcess(screensaverProcess, "screensaver", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-launch-screensaver") } @@ -71,9 +72,10 @@ Item { logEvent("lock-system", reason || "requested") screensaverTimer.stop() lockTimer.stop() - screensaverResumePollTimer.stop() + screensaverLaunchGraceTimer.stop() root.idledThisCycle = false root.screensaverStartedThisCycle = false + resetScreensaverWindows() runProcess(lockProcess, "lock", "omarchy-system-lock") } @@ -86,7 +88,7 @@ Item { logEvent("idle-cycle-start", "screensaver=" + root.screensaverTimeoutSeconds + " lock=" + root.lockTimeoutSeconds) root.idledThisCycle = true root.screensaverStartedThisCycle = false - screensaverResumePollTimer.stop() + resetScreensaverWindows() if (root.screensaverDelaySeconds === 0) launchScreensaver() else screensaverTimer.restart() @@ -99,32 +101,87 @@ Item { logEvent("idle-cycle-cancel", reason || "requested") screensaverTimer.stop() lockTimer.stop() - screensaverResumePollTimer.stop() + screensaverLaunchGraceTimer.stop() if (root.idledThisCycle) runProcess(wakeProcess, "wake", "omarchy-system-wake") root.idledThisCycle = false root.screensaverStartedThisCycle = false + resetScreensaverWindows() } - function checkScreensaverAfterActiveSignal() { - if (!root.idledThisCycle || !root.screensaverStartedThisCycle) return - if (screensaverCheckProcess.running) return - root.lastScreensaverState = "checking" - root.lastScreensaverCheckAt = nowIso() - screensaverCheckProcess.command = ["bash", "-lc", "if hyprctl clients -j 2>/dev/null | jq -e '.[] | select(.class == \"org.omarchy.screensaver\" or .initialClass == \"org.omarchy.screensaver\")' >/dev/null; then echo running-window; elif pgrep -f '[o]rg.omarchy.screensaver' >/dev/null; then echo running-process; elif omarchy-toggle-enabled screensaver-off; then echo disabled; else echo stopped; fi"] - screensaverCheckProcess.running = true + function resetScreensaverWindows() { + root.screensaverWindows = ({}) + root.screensaverWindowCount = 0 + } + + function setScreensaverWindow(address, visible) { + var key = String(address || "") + if (!key) return + + var next = {} + var count = 0 + for (var existing in root.screensaverWindows) { + if (existing !== key && root.screensaverWindows[existing]) { + next[existing] = true + count++ + } + } + + if (visible) { + next[key] = true + count++ + } + + root.screensaverWindows = next + root.screensaverWindowCount = count + } + + function handleScreensaverWindowOpened(address) { + setScreensaverWindow(address, true) + screensaverLaunchGraceTimer.stop() + } + + function handleScreensaverWindowClosed(address) { + setScreensaverWindow(address, false) + + if (!root.idleEnabled || !root.idledThisCycle || !root.screensaverStartedThisCycle) return + if (root.screensaverWindowCount > 0) return + + // The user dismissed the screensaver before the lock deadline. Treat that + // as activity and cancel the pending lock; the lock timer is only allowed + // to fire while the screensaver remains up. + root.cancelIdleCycle("screensaver-dismissed") + } + + function eventParts(event, count) { + try { + if (event && event.parse) return event.parse(count) + } catch (error) {} + return String(event && event.data ? event.data : "").split(",") + } + + function handleHyprlandEvent(event) { + var name = String(event && event.name ? event.name : "") + if (name === "openwindow") { + var open = eventParts(event, 4) + if (String(open[2] || "") === root.screensaverClass) root.handleScreensaverWindowOpened(open[0]) + } else if (name === "closewindow") { + var close = eventParts(event, 1) + var address = String(close[0] || "") + if (root.screensaverWindows[address]) root.handleScreensaverWindowClosed(address) + } } 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) { + // the lock timer running once the screensaver exists (or during its short + // launch grace); Hyprland window events cancel the cycle if it exits before + // the normal lock deadline. + if (root.screensaverStartedThisCycle && (root.screensaverWindowCount > 0 || screensaverLaunchGraceTimer.running)) { logEvent("idle-monitor-active", "screensaver cycle remains armed") - screensaverResumePollTimer.restart() return } @@ -150,25 +207,22 @@ Item { lock: root.lockTimeoutSeconds, screensaverDelay: root.screensaverDelaySeconds, lockDelay: root.lockDelaySeconds, + screensaverWindows: root.screensaverWindowCount, timers: { screensaver: screensaverTimer.running, lock: lockTimer.running, - poll: screensaverResumePollTimer.running, + screensaverLaunchGrace: screensaverLaunchGraceTimer.running, sleepMonitorRestart: sleepMonitorRestartTimer.running }, processes: { screensaver: screensaverProcess.running, lock: lockProcess.running, wake: wakeProcess.running, - check: screensaverCheckProcess.running, sleepLock: sleepLockProcess.running, sleepWake: sleepWakeProcess.running }, lastEvent: root.lastEvent, - lastEventAt: root.lastEventAt, - lastScreensaverState: root.lastScreensaverState, - lastScreensaverCheckAt: root.lastScreensaverCheckAt, - screensaverCheckCount: root.screensaverCheckCount + lastEventAt: root.lastEventAt }) } @@ -218,10 +272,19 @@ Item { } Timer { - id: screensaverResumePollTimer - interval: 1000 - repeat: true - onTriggered: root.checkScreensaverAfterActiveSignal() + id: screensaverLaunchGraceTimer + interval: 3000 + repeat: false + onTriggered: { + if (root.idleEnabled && root.idledThisCycle && root.screensaverStartedThisCycle && root.screensaverWindowCount === 0 && !idleMonitor.isIdle) { + root.cancelIdleCycle("screensaver-not-running") + } + } + } + + Connections { + target: Hyprland + function onRawEvent(event) { root.handleHyprlandEvent(event) } } Process { @@ -245,34 +308,6 @@ Item { onExited: function(exitCode, exitStatus) { root.logEvent("process-exit", "sleep-wake exitCode=" + exitCode + " status=" + exitStatus) } } - Process { - id: screensaverCheckProcess - stdout: SplitParser { - onRead: function(line) { - root.lastScreensaverState = String(line || "").trim() - root.lastScreensaverCheckAt = root.nowIso() - root.screensaverCheckCount++ - root.logEvent("screensaver-check", root.lastScreensaverState) - } - } - onExited: function(exitCode, exitStatus) { - root.logEvent("process-exit", "screensaver-check exitCode=" + exitCode + " status=" + exitStatus + " state=" + root.lastScreensaverState) - if (!root.idleEnabled || !root.idledThisCycle || !root.screensaverStartedThisCycle) return - - var state = String(root.lastScreensaverState || "").trim() - if (state.indexOf("running") === 0) return - if (state === "disabled") { - screensaverResumePollTimer.stop() - return - } - - // If the screensaver disappears while we're still in the idle cycle, - // treat that as the user returning and require authentication before - // showing the desktop again. - root.lockSystem("screensaver-stopped state=" + state) - } - } - Process { id: sleepMonitorProcess command: ["bash", "-lc", "exec dbus-monitor --system \"type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'\""]