Prevent maching the check causing spontaneous locking

This commit is contained in:
Ryan Hughes
2026-05-19 21:01:15 -04:00
parent ad271c1fcb
commit 26281b3863
4 changed files with 95 additions and 60 deletions
+2 -2
View File
@@ -6,8 +6,8 @@ if ! command -v tte &>/dev/null; then
exit 1 exit 1
fi fi
# Exit early if screensave is already running # Exit early if screensaver is already running
pgrep -f org.omarchy.screensaver && exit 0 pgrep -f '[o]rg.omarchy.screensaver' && exit 0
# Allow screensaver to be turned off but also force started # Allow screensaver to be turned off but also force started
if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then
+1 -1
View File
@@ -9,7 +9,7 @@ screensaver_in_focus() {
exit_screensaver() { exit_screensaver() {
hyprctl eval 'hl.config({ cursor = { invisible = false } })' &>/dev/null || hyprctl keyword cursor:invisible false &>/dev/null || true hyprctl eval 'hl.config({ cursor = { invisible = false } })' &>/dev/null || hyprctl keyword cursor:invisible false &>/dev/null || true
pkill -x tte 2>/dev/null 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 exit 0
} }
+1 -1
View File
@@ -16,4 +16,4 @@ if pgrep -x "1password" >/dev/null; then
fi fi
# Avoid running screensaver when locked # Avoid running screensaver when locked
pkill -f '[o]rg.omarchy.screensaver' pkill -f '[o]rg.omarchy.screensaver' 2>/dev/null || true
+91 -56
View File
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
@@ -18,14 +19,14 @@ Item {
readonly property int screensaverDelaySeconds: Math.max(0, screensaverTimeoutSeconds - firstIdleTimeoutSeconds) readonly property int screensaverDelaySeconds: Math.max(0, screensaverTimeoutSeconds - firstIdleTimeoutSeconds)
readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds) readonly property int lockDelaySeconds: Math.max(0, lockTimeoutSeconds - firstIdleTimeoutSeconds)
readonly property bool idleEnabled: persisted.idleEnabled readonly property bool idleEnabled: persisted.idleEnabled
readonly property string screensaverClass: "org.omarchy.screensaver"
property bool idledThisCycle: false property bool idledThisCycle: false
property bool screensaverStartedThisCycle: false property bool screensaverStartedThisCycle: false
property string lastEvent: "starting" property string lastEvent: "starting"
property string lastEventAt: "" property string lastEventAt: ""
property string lastScreensaverState: "unknown" property var screensaverWindows: ({})
property string lastScreensaverCheckAt: "" property int screensaverWindowCount: 0
property int screensaverCheckCount: 0
PersistentProperties { PersistentProperties {
id: persisted id: persisted
@@ -63,7 +64,7 @@ Item {
function launchScreensaver() { function launchScreensaver() {
root.screensaverStartedThisCycle = true root.screensaverStartedThisCycle = true
screensaverResumePollTimer.restart() screensaverLaunchGraceTimer.restart()
runProcess(screensaverProcess, "screensaver", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-launch-screensaver") runProcess(screensaverProcess, "screensaver", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-launch-screensaver")
} }
@@ -71,9 +72,10 @@ Item {
logEvent("lock-system", reason || "requested") logEvent("lock-system", reason || "requested")
screensaverTimer.stop() screensaverTimer.stop()
lockTimer.stop() lockTimer.stop()
screensaverResumePollTimer.stop() screensaverLaunchGraceTimer.stop()
root.idledThisCycle = false root.idledThisCycle = false
root.screensaverStartedThisCycle = false root.screensaverStartedThisCycle = false
resetScreensaverWindows()
runProcess(lockProcess, "lock", "omarchy-system-lock") runProcess(lockProcess, "lock", "omarchy-system-lock")
} }
@@ -86,7 +88,7 @@ Item {
logEvent("idle-cycle-start", "screensaver=" + root.screensaverTimeoutSeconds + " lock=" + root.lockTimeoutSeconds) logEvent("idle-cycle-start", "screensaver=" + root.screensaverTimeoutSeconds + " lock=" + root.lockTimeoutSeconds)
root.idledThisCycle = true root.idledThisCycle = true
root.screensaverStartedThisCycle = false root.screensaverStartedThisCycle = false
screensaverResumePollTimer.stop() resetScreensaverWindows()
if (root.screensaverDelaySeconds === 0) launchScreensaver() if (root.screensaverDelaySeconds === 0) launchScreensaver()
else screensaverTimer.restart() else screensaverTimer.restart()
@@ -99,32 +101,87 @@ Item {
logEvent("idle-cycle-cancel", reason || "requested") logEvent("idle-cycle-cancel", reason || "requested")
screensaverTimer.stop() screensaverTimer.stop()
lockTimer.stop() lockTimer.stop()
screensaverResumePollTimer.stop() screensaverLaunchGraceTimer.stop()
if (root.idledThisCycle) runProcess(wakeProcess, "wake", "omarchy-system-wake") if (root.idledThisCycle) runProcess(wakeProcess, "wake", "omarchy-system-wake")
root.idledThisCycle = false root.idledThisCycle = false
root.screensaverStartedThisCycle = false root.screensaverStartedThisCycle = false
resetScreensaverWindows()
} }
function checkScreensaverAfterActiveSignal() { function resetScreensaverWindows() {
if (!root.idledThisCycle || !root.screensaverStartedThisCycle) return root.screensaverWindows = ({})
if (screensaverCheckProcess.running) return root.screensaverWindowCount = 0
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"] function setScreensaverWindow(address, visible) {
screensaverCheckProcess.running = true 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() { function handleActiveSignal() {
if (!root.idledThisCycle) return if (!root.idledThisCycle) return
// Starting the screensaver can make the compositor report activity. Keep // Starting the screensaver can make the compositor report activity. Keep
// the lock timer running while the screensaver is still alive so the lock // the lock timer running once the screensaver exists (or during its short
// deadline remains `idle.lock` seconds from the original user idle time. // launch grace); Hyprland window events cancel the cycle if it exits before
if (root.screensaverStartedThisCycle) { // the normal lock deadline.
if (root.screensaverStartedThisCycle && (root.screensaverWindowCount > 0 || screensaverLaunchGraceTimer.running)) {
logEvent("idle-monitor-active", "screensaver cycle remains armed") logEvent("idle-monitor-active", "screensaver cycle remains armed")
screensaverResumePollTimer.restart()
return return
} }
@@ -150,25 +207,22 @@ Item {
lock: root.lockTimeoutSeconds, lock: root.lockTimeoutSeconds,
screensaverDelay: root.screensaverDelaySeconds, screensaverDelay: root.screensaverDelaySeconds,
lockDelay: root.lockDelaySeconds, lockDelay: root.lockDelaySeconds,
screensaverWindows: root.screensaverWindowCount,
timers: { timers: {
screensaver: screensaverTimer.running, screensaver: screensaverTimer.running,
lock: lockTimer.running, lock: lockTimer.running,
poll: screensaverResumePollTimer.running, screensaverLaunchGrace: screensaverLaunchGraceTimer.running,
sleepMonitorRestart: sleepMonitorRestartTimer.running sleepMonitorRestart: sleepMonitorRestartTimer.running
}, },
processes: { processes: {
screensaver: screensaverProcess.running, screensaver: screensaverProcess.running,
lock: lockProcess.running, lock: lockProcess.running,
wake: wakeProcess.running, wake: wakeProcess.running,
check: screensaverCheckProcess.running,
sleepLock: sleepLockProcess.running, sleepLock: sleepLockProcess.running,
sleepWake: sleepWakeProcess.running sleepWake: sleepWakeProcess.running
}, },
lastEvent: root.lastEvent, lastEvent: root.lastEvent,
lastEventAt: root.lastEventAt, lastEventAt: root.lastEventAt
lastScreensaverState: root.lastScreensaverState,
lastScreensaverCheckAt: root.lastScreensaverCheckAt,
screensaverCheckCount: root.screensaverCheckCount
}) })
} }
@@ -218,10 +272,19 @@ Item {
} }
Timer { Timer {
id: screensaverResumePollTimer id: screensaverLaunchGraceTimer
interval: 1000 interval: 3000
repeat: true repeat: false
onTriggered: root.checkScreensaverAfterActiveSignal() 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 { Process {
@@ -245,34 +308,6 @@ Item {
onExited: function(exitCode, exitStatus) { root.logEvent("process-exit", "sleep-wake exitCode=" + exitCode + " status=" + exitStatus) } 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 { Process {
id: sleepMonitorProcess id: sleepMonitorProcess
command: ["bash", "-lc", "exec dbus-monitor --system \"type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'\""] command: ["bash", "-lc", "exec dbus-monitor --system \"type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'\""]