diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 24dca05a72..3ef06922ce 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1305,6 +1305,8 @@ "fade-duration-label": "Fade duration", "lock-description": "Seconds of inactivity before the lock screen activates.", "lock-label": "Lock screen", + "lock-screen-off-description": "Seconds of inactivity before monitors are turned off while the lock screen is active. Set to 0 to disable.", + "lock-screen-off-label": "Turn off screen while locked", "resume-command-label": "Resume command", "screen-off-description": "Seconds of inactivity before monitors are turned off.", "screen-off-label": "Turn off screen", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 262d6a7604..bc550dc50d 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -544,6 +544,7 @@ "screenOffTimeout": 600, "lockTimeout": 660, "suspendTimeout": 1800, + "lockScreenOffTimeout": 0, "fadeDuration": 5, "screenOffCommand": "", "lockCommand": "", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index aecf878359..1b629c655e 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -789,6 +789,7 @@ Singleton { property int screenOffTimeout: 600 // seconds, 0 = disabled property int lockTimeout: 660 // seconds, 0 = disabled property int suspendTimeout: 1800 // seconds, 0 = disabled + property int lockScreenOffTimeout: 0 // seconds, 0 = disabled — turn off monitors while lock screen is active property int fadeDuration: 5 // seconds of fade-to-black before action fires property string screenOffCommand: "" property string lockCommand: "" diff --git a/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml b/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml index c26bb55774..77e2480cb2 100644 --- a/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml @@ -127,6 +127,15 @@ ColumnLayout { } } + DefaultActionRow { + actionName: I18n.tr("panels.idle.lock-screen-off-label") + actionDescription: I18n.tr("panels.idle.lock-screen-off-description") + timeoutValue: Settings.data.idle.lockScreenOffTimeout + defaultValue: Settings.getDefaultValue("idle.lockScreenOffTimeout") + showSettings: false + onActionTimeoutChanged: val => Settings.data.idle.lockScreenOffTimeout = val + } + DefaultActionRow { actionName: I18n.tr("common.suspend") actionDescription: I18n.tr("panels.idle.suspend-description") @@ -172,6 +181,7 @@ ColumnLayout { property int defaultValue property string command property string resumeCommand + property bool showSettings: true signal actionTimeoutChanged(int newValue) signal actionCommandChanged(string newCmd) @@ -189,11 +199,18 @@ ColumnLayout { onValueChanged: rowRoot.actionTimeoutChanged(value) } - NIconButton { + Item { Layout.alignment: Qt.AlignVCenter - icon: "settings" - tooltipText: I18n.tr("common.edit") - onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged) + implicitWidth: Style.toOdd(Style.baseWidgetSize * Style.uiScaleRatio) + implicitHeight: Style.toOdd(Style.baseWidgetSize * Style.uiScaleRatio) + + NIconButton { + visible: rowRoot.showSettings + anchors.centerIn: parent + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged) + } } } } diff --git a/Services/Power/IdleService.qml b/Services/Power/IdleService.qml index 2978157e63..ae015f68dc 100644 --- a/Services/Power/IdleService.qml +++ b/Services/Power/IdleService.qml @@ -43,6 +43,7 @@ Singleton { property var _customMonitors: ({}) property var _queuedStages: [] property bool _screenOffActive: false + property var _lockScreenOffMonitor: null // Signals for external listeners (plugins, modules) signal screenOffRequested @@ -199,6 +200,7 @@ Singleton { if (PanelService.lockScreen && !PanelService.lockScreen.active) { PanelService.lockScreen.active = true; } + _applyLockScreenOffMonitor(); root.lockRequested(); } else if (stage === "suspend") { if (Settings.data.idle.suspendCommand) @@ -240,6 +242,61 @@ Singleton { function onCustomCommandsChanged() { root._applyCustomMonitors(); } + function onLockScreenOffTimeoutChanged() { + if (PanelService.lockScreen && PanelService.lockScreen.active) + root._applyLockScreenOffMonitor(); + else + root._destroyLockScreenOffMonitor(); + } + } + + // ------------------------------------------------------- + // React to lock screen active state changes + Connections { + target: PanelService.lockScreen + function onActiveChanged() { + if (PanelService.lockScreen.active) + root._applyLockScreenOffMonitor(); + else + root._destroyLockScreenOffMonitor(); + } + } + + function _applyLockScreenOffMonitor() { + _destroyLockScreenOffMonitor(); + const t = Settings.data.idle.lockScreenOffTimeout; + if (!Settings.data.idle.enabled || t <= 0) + return; + try { + const qml = ` + import Quickshell.Wayland + IdleMonitor { timeout: ${t} } + `; + + const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_lockScreenOff"); + monitor.isIdleChanged.connect(function () { + if (monitor.isIdle) { + Logger.i("IdleService", "Lock-screen idle: turning off monitors"); + CompositorService.turnOffMonitors(); + root._screenOffActive = true; + } else { + root._restoreMonitors(); + } + }); + root._lockScreenOffMonitor = monitor; + root._monitorsCreated = true; + Logger.i("IdleService", "Lock-screen-off monitor created, timeout", t, "s"); + } catch (e) { + Logger.w("IdleService", "Failed to create lock-screen-off monitor:", e); + } + } + + function _destroyLockScreenOffMonitor() { + if (_lockScreenOffMonitor) { + _lockScreenOffMonitor.destroy(); + root._lockScreenOffMonitor = null; + Logger.d("IdleService", "Lock-screen-off monitor destroyed"); + } } function _applyTimeouts() {