diff --git a/shell/lock/Lock.qml b/shell/lock/Lock.qml index 3d5bc80..ff76589 100644 --- a/shell/lock/Lock.qml +++ b/shell/lock/Lock.qml @@ -20,12 +20,21 @@ Scope { lock: _lock } + // Capture screenshots before locking, with timeout for security + Timer { + id: _lockTimeout + interval: 150 + onTriggered: _lock.locked = true + } + Connections { target: S.LockService function onLockRequested() { - if (S.LockService.enabled) - _lock.locked = true; + if (!S.LockService.enabled) + return; + S.ScreenshotService.capture(Quickshell.screens.length); + _lockTimeout.start(); } function onUnlockRequested() { @@ -39,6 +48,17 @@ Scope { } } + Connections { + target: S.ScreenshotService + + function onCaptureComplete() { + if (_lockTimeout.running) { + _lockTimeout.stop(); + _lock.locked = true; + } + } + } + Connections { target: _lock diff --git a/shell/lock/LockSurface.qml b/shell/lock/LockSurface.qml index 6ed9a26..1755dfd 100644 --- a/shell/lock/LockSurface.qml +++ b/shell/lock/LockSurface.qml @@ -15,15 +15,16 @@ WlSessionLockSurface { property real _unlockFade: 1 - // Clear desktop screenshot - visible immediately - ScreencopyView { + // Clear desktop screenshot from ScreenshotService - visible immediately + Image { anchors.fill: parent - captureSource: root.screen - visible: S.Modules.lock.screenshot ?? true + source: S.ScreenshotService.get(root.screen?.name ?? "") + visible: (S.Modules.lock.screenshot ?? true) && source !== "" opacity: _unlockFade + fillMode: Image.PreserveAspectCrop } - // Overlay group: blur + dim + hexes, revealed per-pixel by wave position + // Overlay group: blur + hexes, revealed per-pixel by wave position Item { id: _overlay anchors.fill: parent @@ -47,10 +48,11 @@ WlSessionLockSurface { } // Blurred screenshot - ScreencopyView { + Image { anchors.fill: parent - captureSource: root.screen - visible: S.Modules.lock.screenshot ?? true + source: S.ScreenshotService.get(root.screen?.name ?? "") + visible: (S.Modules.lock.screenshot ?? true) && source !== "" + fillMode: Image.PreserveAspectCrop layer.enabled: true layer.effect: MultiEffect { diff --git a/shell/modules/ScreenCapture.qml b/shell/modules/ScreenCapture.qml new file mode 100644 index 0000000..7c0c677 --- /dev/null +++ b/shell/modules/ScreenCapture.qml @@ -0,0 +1,44 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import "../services" as S + +PanelWindow { + id: root + + required property var screen + + color: "transparent" + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusiveZone: -1 + WlrLayershell.namespace: "nova-screenshot" + mask: Region {} + + anchors.top: true + anchors.left: true + anchors.right: true + anchors.bottom: true + + ScreencopyView { + id: _capture + anchors.fill: parent + captureSource: root.screen + } + + Connections { + target: S.ScreenshotService + function onCaptureRequested() { + _capture.captureFrame(); + } + } + + Connections { + target: _capture + function onHasContentChanged() { + if (_capture.hasContent) + _capture.grabToImage(result => { + S.ScreenshotService.store(root.screen.name, result); + }); + } + } +} diff --git a/shell/modules/qmldir b/shell/modules/qmldir index 8679680..c7e2c9f 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -32,6 +32,7 @@ PowerModule 1.0 PowerModule.qml PowerProfileModule 1.0 PowerProfileModule.qml PrivacyModule 1.0 PrivacyModule.qml ProcessList 1.0 ProcessList.qml +ScreenCapture 1.0 ScreenCapture.qml ScreenCorners 1.0 ScreenCorners.qml TemperatureModule 1.0 TemperatureModule.qml ThemedIcon 1.0 ThemedIcon.qml diff --git a/shell/services/ScreenshotService.qml b/shell/services/ScreenshotService.qml new file mode 100644 index 0000000..5987f9a --- /dev/null +++ b/shell/services/ScreenshotService.qml @@ -0,0 +1,44 @@ +pragma Singleton + +import QtQuick + +QtObject { + id: root + + // Screen name -> in-memory image URL from grabToImage + property var screenshots: ({}) + property int _pending: 0 + + // Keep references to prevent garbage collection of the image data + property var _results: ({}) + + signal captureRequested + signal captureComplete + + function capture(screenCount) { + _pending = screenCount; + if (_pending === 0) { + captureComplete(); + return; + } + captureRequested(); + } + + function store(screenName, result) { + const s = Object.assign({}, screenshots); + s[screenName] = result.url; + screenshots = s; + + const r = Object.assign({}, _results); + r[screenName] = result; + _results = r; + + _pending--; + if (_pending <= 0) + captureComplete(); + } + + function get(screenName) { + return screenshots[screenName] || ""; + } +} diff --git a/shell/services/qmldir b/shell/services/qmldir index 135d0ea..1167a3c 100644 --- a/shell/services/qmldir +++ b/shell/services/qmldir @@ -11,6 +11,7 @@ singleton NetworkService 1.0 NetworkService.qml singleton NiriIpc 1.0 NiriIpc.qml singleton NotifService 1.0 NotifService.qml singleton PowerProfileService 1.0 PowerProfileService.qml +singleton ScreenshotService 1.0 ScreenshotService.qml singleton SystemStats 1.0 SystemStats.qml singleton Theme 1.0 Theme.qml # keep-sorted end diff --git a/shell/shell.qml b/shell/shell.qml index 6fe03cf..01ddb67 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -44,6 +44,13 @@ ShellRoot { } } + LazyLoader { + active: Modules.lock.enable && (Modules.lock.screenshot ?? true) + ScreenCapture { + screen: scope.modelData + } + } + LazyLoader { active: Modules.screenCorners.enable ScreenCorners {