From 7e48d70ed2f8976bd9ea3c816d309b9ed94fcc04 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sat, 7 Feb 2026 01:15:23 +0100 Subject: [PATCH] Add option to display lockscreen only on certain monitors --- Assets/Translations/en.json | 1 + Assets/settings-search-index.json | 30 +- Commons/Settings.qml | 1 + Modules/LockScreen/LockScreen.qml | 409 +++++++++--------- Modules/Panels/Settings/SettingsContent.qml | 1 + .../AppearanceSubTab.qml} | 0 .../Tabs/LockScreen/LockScreenTab.qml | 42 ++ .../Tabs/LockScreen/MonitorsSubTab.qml | 58 +++ 8 files changed, 338 insertions(+), 204 deletions(-) rename Modules/Panels/Settings/Tabs/{LockScreenTab.qml => LockScreen/AppearanceSubTab.qml} (100%) create mode 100644 Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml create mode 100644 Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7cdcbd65c..0f18b6c28 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1184,6 +1184,7 @@ "lock-on-suspend-label": "Lock on suspend", "lock-screen-animations-description": "Enable or disable lockscreen animations", "lock-screen-animations-label": "Lockscreen animations", + "monitors-desc": "Show lock screen on specific monitors. Defaults to all if none are chosen.", "show-hibernate-description": "Show the option 'hibernate' in the power controls.", "show-hibernate-label": "Show hibernate", "show-session-buttons-description": "Allow access to power settings from the lock screen.", diff --git a/Assets/settings-search-index.json b/Assets/settings-search-index.json index 0c00fee31..f64e72fed 100644 --- a/Assets/settings-search-index.json +++ b/Assets/settings-search-index.json @@ -899,7 +899,8 @@ "widget": "NComboBox", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.clock-format-label", @@ -907,7 +908,8 @@ "widget": "NTextInput", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.lock-on-suspend-label", @@ -915,7 +917,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.compact-lockscreen-label", @@ -923,7 +926,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.lock-screen-animations-label", @@ -939,7 +943,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.allow-password-with-fprintd-label", @@ -947,7 +952,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.show-session-buttons-label", @@ -955,7 +961,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.lock-screen.show-hibernate-label", @@ -963,7 +970,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.session-menu.enable-countdown-label", @@ -971,7 +979,8 @@ "widget": "NToggle", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "panels.session-menu.countdown-duration-label", @@ -979,7 +988,8 @@ "widget": "NValueSlider", "tab": 11, "tabLabel": "panels.lock-screen.title", - "subTab": null + "subTab": 0, + "subTabLabel": "common.appearance" }, { "labelKey": "actions.enable-wifi", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a44bdce94..a9fc83e33 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -287,6 +287,7 @@ Singleton { property bool allowPasswordWithFprintd: false property string clockStyle: "custom" property string clockFormat: "hh\\nmm" + property list lockScreenMonitors: [] // holds lock screen visibility per monitor } // ui diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 121f184a1..8c9882d44 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -74,220 +74,241 @@ Loader { WlSessionLockSurface { id: lockSurface - Item { - id: batteryIndicator - - property bool isReady: BatteryService.batteryReady - property real percent: BatteryService.batteryPercentage - property bool charging: BatteryService.batteryCharging - property bool pluggedIn: BatteryService.batteryPluggedIn - property bool batteryVisible: isReady - property string icon: BatteryService.batteryIcon - } - - Item { - id: keyboardLayout - property string currentLayout: KeyboardLayoutService.currentLayout - } - - // Background with wallpaper, gradient, and screen corners - LockScreenBackground { - id: backgroundComponent - screen: lockSurface.screen - } - - Item { + Loader { anchors.fill: parent + active: true + sourceComponent: (Settings.data.general.lockScreenMonitors.length === 0 || Settings.data.general.lockScreenMonitors.includes(lockSurface.screen.name)) ? fullLockScreenComponent : blackScreenComponent + } - // Mouse area to trigger focus on cursor movement (workaround for Hyprland focus issues) - MouseArea { - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - onPositionChanged: { - if (passwordInput) { - passwordInput.forceActiveFocus(); - } - } - } + Component { + id: fullLockScreenComponent - // Header with avatar, welcome, time, date - LockScreenHeader { - id: headerComponent - } + Item { + Item { + id: batteryIndicator - // Info notification - Rectangle { - width: infoRowLayout.implicitWidth + Style.marginXL * 1.5 - height: 50 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio - radius: Style.radiusL - color: Color.mTertiary - visible: lockContext.showInfo && lockContext.infoMessage && !panelComponent.timerActive - opacity: visible ? 1.0 : 0.0 - - RowLayout { - id: infoRowLayout - anchors.centerIn: parent - spacing: Style.marginM - - NIcon { - icon: "circle-key" - pointSize: Style.fontSizeXL - color: Color.mOnTertiary - } - - NText { - text: lockContext.infoMessage - color: Color.mOnTertiary - pointSize: Style.fontSizeL - horizontalAlignment: Text.AlignHCenter - } + property bool isReady: BatteryService.batteryReady + property real percent: BatteryService.batteryPercentage + property bool charging: BatteryService.batteryCharging + property bool pluggedIn: BatteryService.batteryPluggedIn + property bool batteryVisible: isReady + property string icon: BatteryService.batteryIcon } - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - } - } - - // Error notification - Rectangle { - width: errorRowLayout.implicitWidth + Style.marginXL * 1.5 - height: 50 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio - radius: Style.radiusL - color: Color.mError - visible: lockContext.showFailure && lockContext.errorMessage && !panelComponent.timerActive - opacity: visible ? 1.0 : 0.0 - - RowLayout { - id: errorRowLayout - anchors.centerIn: parent - spacing: Style.marginM - - NIcon { - icon: "alert-circle" - pointSize: Style.fontSizeXL - color: Color.mOnError - } - - NText { - text: lockContext.errorMessage || "Authentication failed" - color: Color.mOnError - pointSize: Style.fontSizeL - horizontalAlignment: Text.AlignHCenter - } + Item { + id: keyboardLayout + property string currentLayout: KeyboardLayoutService.currentLayout } - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } + // Background with wallpaper, gradient, and screen corners + LockScreenBackground { + id: backgroundComponent + screen: lockSurface.screen } - } - // Countdown notification - Rectangle { - width: countdownRowLayout.implicitWidth + Style.marginXL * 1.5 - height: 50 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio - radius: Style.radiusL - color: Color.mSurface - visible: panelComponent.timerActive - opacity: visible ? 1.0 : 0.0 - - RowLayout { - id: countdownRowLayout + Item { anchors.fill: parent - anchors.margins: Style.marginM - spacing: Style.marginM - NIcon { - icon: "clock" - pointSize: Style.fontSizeXL + // Mouse area to trigger focus on cursor movement (workaround for Hyprland focus issues) + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onPositionChanged: { + if (passwordInput) { + passwordInput.forceActiveFocus(); + } + } + } + + // Header with avatar, welcome, time, date + LockScreenHeader { + id: headerComponent + } + + // Info notification + Rectangle { + width: infoRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mTertiary + visible: lockContext.showInfo && lockContext.infoMessage && !panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: infoRowLayout + anchors.centerIn: parent + spacing: Style.marginM + + NIcon { + icon: "circle-key" + pointSize: Style.fontSizeXL + color: Color.mOnTertiary + } + + NText { + text: lockContext.infoMessage + color: Color.mOnTertiary + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Error notification + Rectangle { + width: errorRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mError + visible: lockContext.showFailure && lockContext.errorMessage && !panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: errorRowLayout + anchors.centerIn: parent + spacing: Style.marginM + + NIcon { + icon: "alert-circle" + pointSize: Style.fontSizeXL + color: Color.mOnError + } + + NText { + text: lockContext.errorMessage || "Authentication failed" + color: Color.mOnError + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Countdown notification + Rectangle { + width: countdownRowLayout.implicitWidth + Style.marginXL * 1.5 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio + radius: Style.radiusL + color: Color.mSurface + visible: panelComponent.timerActive + opacity: visible ? 1.0 : 0.0 + + RowLayout { + id: countdownRowLayout + anchors.fill: parent + anchors.margins: Style.marginM + spacing: Style.marginM + + NIcon { + icon: "clock" + pointSize: Style.fontSizeXL + color: Color.mPrimary + } + + NText { + text: I18n.tr("session-menu.action-in-seconds", { + "action": I18n.tr("common." + panelComponent.pendingAction), + "seconds": Math.ceil(panelComponent.timeRemaining / 1000) + }) + color: Color.mOnSurface + pointSize: Style.fontSizeL + horizontalAlignment: Text.AlignHCenter + font.weight: Style.fontWeightBold + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: "x" + tooltipText: I18n.tr("session-menu.cancel-timer") + baseSize: 32 + colorBg: Qt.alpha(Color.mPrimary, 0.1) + colorFg: Color.mPrimary + colorBgHover: Color.mPrimary + onClicked: panelComponent.cancelTimer() + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + + // Hidden input that receives actual text + TextInput { + id: passwordInput + width: 0 + height: 0 + visible: false + enabled: !lockContext.unlockInProgress + font.pointSize: Style.fontSizeM color: Color.mPrimary + echoMode: TextInput.Password + passwordCharacter: "•" + passwordMaskDelay: 0 + text: lockContext.currentText + onTextChanged: lockContext.currentText = text + + Keys.onPressed: function (event) { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + lockContext.tryUnlock(); + event.accepted = true; + } + if (event.key === Qt.Key_Escape && panelComponent.timerActive) { + panelComponent.cancelTimer(); + event.accepted = true; + } + } + + Component.onCompleted: forceActiveFocus() } - NText { - text: I18n.tr("session-menu.action-in-seconds", { - "action": I18n.tr("common." + panelComponent.pendingAction), - "seconds": Math.ceil(panelComponent.timeRemaining / 1000) - }) - color: Color.mOnSurface - pointSize: Style.fontSizeL - horizontalAlignment: Text.AlignHCenter - font.weight: Style.fontWeightBold - } - - Item { - Layout.fillWidth: true - } - - NIconButton { - icon: "x" - tooltipText: I18n.tr("session-menu.cancel-timer") - baseSize: 32 - colorBg: Qt.alpha(Color.mPrimary, 0.1) - colorFg: Color.mPrimary - colorBgHover: Color.mPrimary - onClicked: panelComponent.cancelTimer() - } - } - - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic + // Main panel with password, weather, media, session controls + LockScreenPanel { + id: panelComponent + lockControl: lockContext + batteryIndicator: batteryIndicator + keyboardLayout: keyboardLayout + passwordInput: passwordInput } } } + } - // Hidden input that receives actual text - TextInput { - id: passwordInput - width: 0 - height: 0 - visible: false - enabled: !lockContext.unlockInProgress - font.pointSize: Style.fontSizeM - color: Color.mPrimary - echoMode: TextInput.Password - passwordCharacter: "•" - passwordMaskDelay: 0 - text: lockContext.currentText - onTextChanged: lockContext.currentText = text + Component { + id: blackScreenComponent - Keys.onPressed: function (event) { - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - lockContext.tryUnlock(); - event.accepted = true; - } - if (event.key === Qt.Key_Escape && panelComponent.timerActive) { - panelComponent.cancelTimer(); - event.accepted = true; - } - } - - Component.onCompleted: forceActiveFocus() - } - - // Main panel with password, weather, media, session controls - LockScreenPanel { - id: panelComponent - lockControl: lockContext - batteryIndicator: batteryIndicator - keyboardLayout: keyboardLayout - passwordInput: passwordInput + Rectangle { + anchors.fill: parent + color: "black" } } } diff --git a/Modules/Panels/Settings/SettingsContent.qml b/Modules/Panels/Settings/SettingsContent.qml index dfb7bc95a..04b66cbdb 100644 --- a/Modules/Panels/Settings/SettingsContent.qml +++ b/Modules/Panels/Settings/SettingsContent.qml @@ -14,6 +14,7 @@ import qs.Modules.Panels.Settings.Tabs.Display import qs.Modules.Panels.Settings.Tabs.Dock import qs.Modules.Panels.Settings.Tabs.Hooks import qs.Modules.Panels.Settings.Tabs.Launcher +import qs.Modules.Panels.Settings.Tabs.LockScreen import qs.Modules.Panels.Settings.Tabs.Notifications import qs.Modules.Panels.Settings.Tabs.Osd import qs.Modules.Panels.Settings.Tabs.Plugins diff --git a/Modules/Panels/Settings/Tabs/LockScreenTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml similarity index 100% rename from Modules/Panels/Settings/Tabs/LockScreenTab.qml rename to Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml diff --git a/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml new file mode 100644 index 000000000..497f25630 --- /dev/null +++ b/Modules/Panels/Settings/Tabs/LockScreen/LockScreenTab.qml @@ -0,0 +1,42 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: 0 + + NTabBar { + id: subTabBar + Layout.fillWidth: true + Layout.bottomMargin: Style.marginM + distributeEvenly: true + currentIndex: tabView.currentIndex + + NTabButton { + text: I18n.tr("common.appearance") + tabIndex: 0 + checked: subTabBar.currentIndex === 0 + } + NTabButton { + text: I18n.tr("common.monitors") + tabIndex: 1 + checked: subTabBar.currentIndex === 1 + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + + NTabView { + id: tabView + currentIndex: subTabBar.currentIndex + + AppearanceSubTab {} + MonitorsSubTab {} + } +} diff --git a/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml new file mode 100644 index 000000000..d85346ed3 --- /dev/null +++ b/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml @@ -0,0 +1,58 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + enabled: true + spacing: Style.marginL + Layout.fillWidth: true + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice(); + if (!arr.includes(name)) + arr.push(name); + return arr; + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name; + }); + } + + NText { + text: I18n.tr("panels.lock-screen.monitors-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + label: modelData.name || "Unknown" + description: { + const compositorScale = CompositorService.getDisplayScale(modelData.name); + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }); + } + checked: (Settings.data.general.lockScreenMonitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.general.lockScreenMonitors = root.addMonitor(Settings.data.general.lockScreenMonitors, modelData.name); + } else { + Settings.data.general.lockScreenMonitors = root.removeMonitor(Settings.data.general.lockScreenMonitors, modelData.name); + } + } + } + } +}