pre-capture lock screen screenshots via hidden window and ScreenshotService

This commit is contained in:
Damocles 2026-04-18 12:32:55 +02:00
parent 62cd0f9a76
commit 73e480d14b
7 changed files with 129 additions and 10 deletions

View file

@ -20,12 +20,21 @@ Scope {
lock: _lock lock: _lock
} }
// Capture screenshots before locking, with timeout for security
Timer {
id: _lockTimeout
interval: 150
onTriggered: _lock.locked = true
}
Connections { Connections {
target: S.LockService target: S.LockService
function onLockRequested() { function onLockRequested() {
if (S.LockService.enabled) if (!S.LockService.enabled)
_lock.locked = true; return;
S.ScreenshotService.capture(Quickshell.screens.length);
_lockTimeout.start();
} }
function onUnlockRequested() { function onUnlockRequested() {
@ -39,6 +48,17 @@ Scope {
} }
} }
Connections {
target: S.ScreenshotService
function onCaptureComplete() {
if (_lockTimeout.running) {
_lockTimeout.stop();
_lock.locked = true;
}
}
}
Connections { Connections {
target: _lock target: _lock

View file

@ -15,15 +15,16 @@ WlSessionLockSurface {
property real _unlockFade: 1 property real _unlockFade: 1
// Clear desktop screenshot - visible immediately // Clear desktop screenshot from ScreenshotService - visible immediately
ScreencopyView { Image {
anchors.fill: parent anchors.fill: parent
captureSource: root.screen source: S.ScreenshotService.get(root.screen?.name ?? "")
visible: S.Modules.lock.screenshot ?? true visible: (S.Modules.lock.screenshot ?? true) && source !== ""
opacity: _unlockFade 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 { Item {
id: _overlay id: _overlay
anchors.fill: parent anchors.fill: parent
@ -47,10 +48,11 @@ WlSessionLockSurface {
} }
// Blurred screenshot // Blurred screenshot
ScreencopyView { Image {
anchors.fill: parent anchors.fill: parent
captureSource: root.screen source: S.ScreenshotService.get(root.screen?.name ?? "")
visible: S.Modules.lock.screenshot ?? true visible: (S.Modules.lock.screenshot ?? true) && source !== ""
fillMode: Image.PreserveAspectCrop
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {

View file

@ -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);
});
}
}
}

View file

@ -32,6 +32,7 @@ PowerModule 1.0 PowerModule.qml
PowerProfileModule 1.0 PowerProfileModule.qml PowerProfileModule 1.0 PowerProfileModule.qml
PrivacyModule 1.0 PrivacyModule.qml PrivacyModule 1.0 PrivacyModule.qml
ProcessList 1.0 ProcessList.qml ProcessList 1.0 ProcessList.qml
ScreenCapture 1.0 ScreenCapture.qml
ScreenCorners 1.0 ScreenCorners.qml ScreenCorners 1.0 ScreenCorners.qml
TemperatureModule 1.0 TemperatureModule.qml TemperatureModule 1.0 TemperatureModule.qml
ThemedIcon 1.0 ThemedIcon.qml ThemedIcon 1.0 ThemedIcon.qml

View file

@ -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] || "";
}
}

View file

@ -11,6 +11,7 @@ singleton NetworkService 1.0 NetworkService.qml
singleton NiriIpc 1.0 NiriIpc.qml singleton NiriIpc 1.0 NiriIpc.qml
singleton NotifService 1.0 NotifService.qml singleton NotifService 1.0 NotifService.qml
singleton PowerProfileService 1.0 PowerProfileService.qml singleton PowerProfileService 1.0 PowerProfileService.qml
singleton ScreenshotService 1.0 ScreenshotService.qml
singleton SystemStats 1.0 SystemStats.qml singleton SystemStats 1.0 SystemStats.qml
singleton Theme 1.0 Theme.qml singleton Theme 1.0 Theme.qml
# keep-sorted end # keep-sorted end

View file

@ -44,6 +44,13 @@ ShellRoot {
} }
} }
LazyLoader {
active: Modules.lock.enable && (Modules.lock.screenshot ?? true)
ScreenCapture {
screen: scope.modelData
}
}
LazyLoader { LazyLoader {
active: Modules.screenCorners.enable active: Modules.screenCorners.enable
ScreenCorners { ScreenCorners {