|
|
|
@@ -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'\""]
|
|
|
|
|