1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-07 19:59:14 -04:00
Files
DankMaterialShell/quickshell/Services/IdleService.qml
T
Nachum Barcohen 45eb101f40 fix(lock): wake monitors on input when powered off by lock (#2572)
The "power off monitors on lock" path relies on wake handlers
(MouseArea/Keys) at Lock.qml's root Scope, which sit outside the
WlSessionLock surface and never receive input while locked. The feature
only appeared to work on compositors that auto-restore output power on
input; compositors that fully tear down the output (e.g. MangoWC
disable_monitor) leave the screen off until blind-unlock.

Add a seat-level IdleMonitor (wlr idle-notify, surface-independent) in
IdleService that restores monitor power on any keyboard/pointer
activity. Gated on isShellLocked + monitorsOff + the setting, so it is
inert unless that path actually powered the monitors off.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:05:59 -04:00

194 lines
6.5 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
Singleton {
id: root
readonly property var log: Log.scoped("IdleService")
property bool enabled: true
property bool respectInhibitors: true
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
readonly property bool isOnBattery: BatteryService.batteryAvailable && !BatteryService.isPluggedIn
readonly property int monitorTimeout: isOnBattery ? SettingsData.batteryMonitorTimeout : SettingsData.acMonitorTimeout
readonly property int lockTimeout: isOnBattery ? SettingsData.batteryLockTimeout : SettingsData.acLockTimeout
readonly property int suspendTimeout: isOnBattery ? SettingsData.batterySuspendTimeout : SettingsData.acSuspendTimeout
readonly property int suspendBehavior: isOnBattery ? SettingsData.batterySuspendBehavior : SettingsData.acSuspendBehavior
readonly property int postLockMonitorTimeout: isOnBattery ? SettingsData.batteryPostLockMonitorTimeout : SettingsData.acPostLockMonitorTimeout
readonly property bool postLockMonitorActive: isShellLocked && postLockMonitorTimeout > 0
readonly property bool mediaPlaying: MprisController.activePlayer !== null && MprisController.activePlayer.isPlaying
onEnabledChanged: _applyMonitorEnableds()
onPostLockMonitorActiveChanged: _applyMonitorEnableds()
onMonitorTimeoutChanged: _rearmIdleMonitors()
onLockTimeoutChanged: _rearmIdleMonitors()
onSuspendTimeoutChanged: _rearmIdleMonitors()
onPostLockMonitorTimeoutChanged: _rearmIdleMonitors()
onIsShellLockedChanged: _rearmIdleMonitors()
function _applyMonitorEnableds() {
const base = enabled;
monitorOffMonitor.enabled = base && monitorTimeout > 0 && !postLockMonitorActive;
postLockMonitorOffMonitor.enabled = base && postLockMonitorActive;
lockMonitor.enabled = base && lockTimeout > 0;
suspendMonitor.enabled = base && suspendTimeout > 0;
}
function _rearmIdleMonitors() {
monitorOffMonitor.enabled = false;
postLockMonitorOffMonitor.enabled = false;
lockMonitor.enabled = false;
suspendMonitor.enabled = false;
Qt.callLater(_applyMonitorEnableds);
}
signal lockRequested
signal fadeToLockRequested
signal cancelFadeToLock
signal fadeToDpmsRequested
signal cancelFadeToDpms
signal requestMonitorOff
signal requestMonitorOn
signal requestSuspend
property var lockComponent: null
property bool monitorsOff: false
property bool isShellLocked: false
function wake() {
requestMonitorOn();
}
function reapplyDpmsIfNeeded() {
if (monitorsOff)
CompositorService.powerOffMonitors();
}
IdleMonitor {
id: monitorOffMonitor
timeout: root.monitorTimeout > 0 ? root.monitorTimeout : 86400
respectInhibitors: root.respectInhibitors
enabled: false
onIsIdleChanged: {
if (isIdle) {
if (SettingsData.fadeToDpmsEnabled) {
root.fadeToDpmsRequested();
} else {
root.requestMonitorOff();
}
} else {
if (SettingsData.fadeToDpmsEnabled) {
root.cancelFadeToDpms();
}
root.requestMonitorOn();
}
}
}
IdleMonitor {
id: postLockMonitorOffMonitor
timeout: root.postLockMonitorTimeout > 0 ? root.postLockMonitorTimeout : 86400
respectInhibitors: root.respectInhibitors
enabled: false
onIsIdleChanged: {
if (isIdle) {
root.requestMonitorOff();
} else {
root.requestMonitorOn();
}
}
}
IdleMonitor {
id: lockMonitor
timeout: root.lockTimeout > 0 ? root.lockTimeout : 86400
respectInhibitors: root.respectInhibitors
enabled: false
onIsIdleChanged: {
if (isIdle) {
if (SettingsData.fadeToLockEnabled) {
root.fadeToLockRequested();
} else {
root.lockRequested();
}
} else {
if (SettingsData.fadeToLockEnabled) {
root.cancelFadeToLock();
}
}
}
}
IdleMonitor {
id: suspendMonitor
timeout: root.suspendTimeout > 0 ? root.suspendTimeout : 86400
respectInhibitors: root.respectInhibitors
enabled: false
onIsIdleChanged: {
if (isIdle)
root.requestSuspend();
}
}
// Wakes monitors powered off by the "power off monitors on lock" path.
// Lock.qml's own wake handlers sit outside the session-lock surface and
// never receive input, so wake on input via seat-level idle-notify instead.
IdleMonitor {
id: lockWakeMonitor
timeout: 1
respectInhibitors: false
enabled: root.enabled && root.isShellLocked && root.monitorsOff && SettingsData.lockScreenPowerOffMonitorsOnLock
onIsIdleChanged: {
if (!isIdle && root.monitorsOff)
root.requestMonitorOn();
}
}
Connections {
target: root
function onRequestMonitorOff() {
monitorsOff = true;
CompositorService.powerOffMonitors();
}
function onRequestMonitorOn() {
monitorsOff = false;
CompositorService.powerOnMonitors();
}
function onRequestSuspend() {
SessionService.suspendWithBehavior(root.suspendBehavior);
}
}
onExternalInhibitActiveChanged: {
if (externalInhibitActive) {
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
log.info("External idle inhibit active from:", apps || "unknown");
SessionService.idleInhibited = true;
SessionService.inhibitReason = "External app: " + (apps || "unknown");
} else {
log.info("External idle inhibit released");
SessionService.idleInhibited = false;
SessionService.inhibitReason = "Keep system awake";
}
}
Component.onCompleted: {
_applyMonitorEnableds();
if (externalInhibitActive) {
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
SessionService.idleInhibited = true;
SessionService.inhibitReason = "External app: " + (apps || "unknown");
}
}
}