diff --git a/Assets/Translations/en-GB.json b/Assets/Translations/en-GB.json index 38ed26c1e2..9d01a8e103 100644 --- a/Assets/Translations/en-GB.json +++ b/Assets/Translations/en-GB.json @@ -1440,8 +1440,10 @@ "enable-lockscreen-media-controls-label": "Lock screen media controls", "lock-on-suspend-description": "Automatically lock the screen when suspending the system.", "lock-on-suspend-label": "Lock on suspend", - "lock-screen-animations-description": "Enable or disable lockscreen animations.", - "lock-screen-animations-label": "Lockscreen animations", + "lock-screen-animations-description": "Enable or disable lock screen animations and transitions.", + "lock-screen-animations-label": "Lock screen animations", + "lock-screen-copy-bg-description": "Instead of the wallpaper, use a screenshot of the desktop as the background of the lockscreen.", + "lock-screen-copy-bg-label": "Use screenshot as background", "lock-screen-blur-strength-description": "Applies a blur effect to the lock screen wallpaper.", "lock-screen-blur-strength-label": "Lock screen blur strength", "lock-screen-tint-strength-description": "Applies a tint overlay to the lock screen wallpaper.", @@ -2262,4 +2264,4 @@ "weak": "Weak" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 24dca05a72..89a88be698 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1440,8 +1440,10 @@ "enable-lockscreen-media-controls-label": "Lock screen media controls", "lock-on-suspend-description": "Automatically lock the screen when suspending the system.", "lock-on-suspend-label": "Lock on suspend", - "lock-screen-animations-description": "Enable or disable lockscreen animations.", - "lock-screen-animations-label": "Lockscreen animations", + "lock-screen-animations-description": "Enable or disable lock screen animations and transitions.", + "lock-screen-animations-label": "Lock screen animations", + "lock-screen-copy-bg-description": "Instead of the wallpaper, use a screenshot of the desktop as the background of the lockscreen.", + "lock-screen-copy-bg-label": "Use screenshot as background", "lock-screen-blur-strength-description": "Applies a blur effect to the lock screen wallpaper.", "lock-screen-blur-strength-label": "Lock screen blur strength", "lock-screen-tint-strength-description": "Applies a tint overlay to the lock screen wallpaper.", @@ -2262,4 +2264,4 @@ "weak": "Weak" } } -} +} \ No newline at end of file diff --git a/Assets/settings-search-index.json b/Assets/settings-search-index.json index 7a05103cd2..c6b68c96ec 100644 --- a/Assets/settings-search-index.json +++ b/Assets/settings-search-index.json @@ -1718,6 +1718,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.lock-screen.lock-screen-copy-bg-label", + "descriptionKey": "panels.lock-screen.lock-screen-copy-bg-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.lock-screen.lock-screen-blur-strength-label", "descriptionKey": "panels.lock-screen.lock-screen-blur-strength-description", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index aecf878359..5cd62943de 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -314,6 +314,7 @@ Singleton { property bool animationDisabled: false property bool compactLockScreen: false property bool lockScreenAnimations: false + property bool lockScreenCopyBg: false property bool lockOnSuspend: true property bool showSessionButtonsOnLockScreen: true property bool showHibernateOnLockScreen: false diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 88698b58f5..e3571fbd56 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -9,13 +9,20 @@ import qs.Services.Compositor import qs.Services.Hardware import qs.Services.Keyboard import qs.Services.Media +import qs.Services.Power import qs.Services.UI import qs.Widgets Loader { id: root + + // Starts the lock screen when activated active: false + // Only controls the state of the session lock + // Decoupled because we need to capture the screen first + property bool locked: false + // Track if the visualizer should be shown (lockscreen active + media playing + non-compact mode) readonly property bool needsSpectrum: root.active && !Settings.data.general.compactLockScreen && Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" @@ -31,6 +38,10 @@ Loader { } else { LockKeysService.unregisterComponent("lockscreen"); } + + // Trigger locksreen when loader is active + if (root.active) + startLockScreen(); } onNeedsSpectrumChanged: { @@ -51,6 +62,24 @@ Loader { LockKeysService.unregisterComponent("lockscreen"); } + // Dictionary of screen name to ItemGrabResult of screen captures + property var screenCaptures: ({}) + + // Wait until all screens are captured before locking + onScreenCapturesChanged: { + let captureCount = Object.keys(screenCaptures).length; + if (captureCount > 0 && captureCount === Quickshell.screens.length) { + screenCopies.active = false; + root.locked = true; + } + } + + // When lock is requested, clear previous captures and activate SCVs + function startLockScreen() { + screenCaptures = {}; + screenCopies.active = true; + } + Timer { id: unloadAfterUnlockTimer interval: 250 @@ -58,20 +87,92 @@ Loader { onTriggered: root.active = false } - function scheduleUnloadAfterUnlock() { + function unlockAndUnload() { + locked = false; unloadAfterUnlockTimer.start(); } + // Image grabber for each connected screen + Loader { + id: screenCopies + active: false + + sourceComponent: Instantiator { + model: Quickshell.screens + + delegate: PanelWindow { + anchors { + top: true + left: true + right: true + bottom: true + } + exclusionMode: ExclusionMode.Ignore + screen: modelData + color: "transparent" + + ScreencopyView { + anchors.fill: parent + captureSource: screen + + // Only grab an image when content is ready + onHasContentChanged: { + if (hasContent) { + grabToImage(function (result) { + // Store a ref to the result to avoid GC + root.screenCaptures[screen.name] = result; + // Manually call changed to trigger mutation signal + root.screenCapturesChanged(); + }); + } + } + } + } + } + } + sourceComponent: Component { Item { id: lockContainer + readonly property bool canTransition: Settings.data.general.lockScreenAnimations && !PowerProfileService.noctaliaPerformanceMode + + property real transitionProgress: canTransition ? 0.0 : 1.0 + + NumberAnimation { + id: lockEnter + target: lockContainer + property: "transitionProgress" + to: 1.0 + duration: Style.animationNormal + easing.type: Easing.OutCubic + running: root.locked && canTransition + } + + function unlockScreen() { + root.unlockAndUnload(); + lockContext.currentText = ""; + } + + NumberAnimation { + id: lockExit + target: lockContainer + property: "transitionProgress" + to: 0.0 + duration: Style.animationNormal + easing.type: Easing.InCubic + onFinished: unlockScreen() + } + LockContext { id: lockContext onUnlocked: { - lockSession.locked = false; - root.scheduleUnloadAfterUnlock(); - lockContext.currentText = ""; + if (canTransition) { + lockEnter.stop(); + lockExit.start(); + return; + } + unlockScreen(); } onFailed: { lockContext.currentText = ""; @@ -88,10 +189,11 @@ Loader { WlSessionLock { id: lockSession - locked: root.active + locked: root.locked WlSessionLockSurface { id: lockSurface + color: "black" Loader { anchors.fill: parent @@ -123,10 +225,13 @@ Loader { LockScreenBackground { id: backgroundComponent screen: lockSurface.screen + screenGrab: lockSurface.screen ? (root.screenCaptures[lockSurface.screen.name] || null) : null + transitionProgress: lockContainer.transitionProgress } Item { anchors.fill: parent + opacity: lockContainer.transitionProgress // Mouse area to trigger focus on cursor movement (workaround for Hyprland focus issues) MouseArea { diff --git a/Modules/LockScreen/LockScreenBackground.qml b/Modules/LockScreen/LockScreenBackground.qml index dde083925f..4834132964 100644 --- a/Modules/LockScreen/LockScreenBackground.qml +++ b/Modules/LockScreen/LockScreenBackground.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Effects import Quickshell +import Quickshell.Wayland import qs.Commons import qs.Services.Compositor import qs.Services.Power @@ -10,11 +11,22 @@ Item { id: root anchors.fill: parent + // Screen of the lock surface + required property ShellScreen screen + + // ItemGrabResult of the screen copy for this surface + required property var screenGrab + // Cached wallpaper path - exposed for parent components property string resolvedWallpaperPath: "" - property color tintColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface - required property var screen + // Enter / exit transtion value + property real transitionProgress: 1.0 + + readonly property color tintColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface + readonly property bool useEffects: !PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper + readonly property bool useScreencopy: Settings.data.general.lockScreenCopyBg + readonly property bool useWallpaper: Settings.data.wallpaper.enabled && resolvedWallpaperPath !== "" // Request preprocessed wallpaper when lock screen becomes active or dimensions change Component.onCompleted: { @@ -103,15 +115,29 @@ Item { }); } - // Background - solid color or black fallback + // A copy of the screen to use as background + // Also used as fade in / fade out backdrop + Image { + id: lockBgScreencopy + anchors.fill: parent + source: screenGrab ? screenGrab.url : "" + + cache: false + fillMode: Image.PreserveAspectCrop + visible: screenGrab && lockBgRender.visible && lockBgRender.opacity < 1.0 + } + + // If using a solid color wallpaper Rectangle { anchors.fill: parent - color: Settings.data.wallpaper.useSolidColor ? Settings.data.wallpaper.solidColor : "#000000" + opacity: (Settings.data.wallpaper.useSolidColor && useEffects) ? transitionProgress : 0.0 + color: Settings.data.wallpaper.solidColor } + // If using an image wallpaper Image { id: lockBgImage - visible: source !== "" && Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) + visible: false // rendered with effects below anchors.fill: parent fillMode: Image.PreserveAspectCrop source: resolvedWallpaperPath @@ -119,19 +145,23 @@ Item { smooth: true mipmap: false antialiasing: true + } - layer.enabled: Settings.data.general.lockScreenBlur > 0 && !PowerProfileService.noctaliaPerformanceMode - layer.smooth: false - layer.effect: MultiEffect { - blurEnabled: true - blur: Settings.data.general.lockScreenBlur - blurMax: 48 - } + // Applies the image wallpaper or screen copy with effects + MultiEffect { + id: lockBgRender + anchors.fill: parent + opacity: transitionProgress + visible: useEffects && ((screenGrab && useScreencopy) || useWallpaper) + source: useScreencopy ? lockBgScreencopy : lockBgImage + + blurEnabled: Settings.data.general.lockScreenBlur > 0 + blur: Settings.data.general.lockScreenBlur * transitionProgress + blurMax: 64 - // Tint overlay Rectangle { anchors.fill: parent - color: root.tintColor + color: tintColor opacity: Settings.data.general.lockScreenTint } } diff --git a/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml index 545b047855..f4843cda12 100644 --- a/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml @@ -93,6 +93,14 @@ ColumnLayout { defaultValue: Settings.getDefaultValue("general.lockScreenAnimations") } + NToggle { + label: I18n.tr("panels.lock-screen.lock-screen-copy-bg-label") + description: I18n.tr("panels.lock-screen.lock-screen-copy-bg-description") + checked: Settings.data.general.lockScreenCopyBg + onToggled: checked => Settings.data.general.lockScreenCopyBg = checked + defaultValue: Settings.getDefaultValue("general.lockScreenCopyBg") + } + NValueSlider { Layout.fillWidth: true label: I18n.tr("panels.lock-screen.lock-screen-blur-strength-label") diff --git a/nix/shell.nix b/nix/shell.nix index 311d817d96..5de595b1f4 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -7,6 +7,7 @@ shellcheck, jsonfmt, lefthook, + python3, kdePackages, mkShellNoCC, }: @@ -29,6 +30,11 @@ mkShellNoCC { # CoC lefthook # githooks + python3 # dev scripts kdePackages.qtdeclarative # qmlfmt, qmllint, qmlls and etc; Qt6 ]; + + shellHook = '' + lefthook install + ''; }