Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Assets/Translations/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -2262,4 +2264,4 @@
"weak": "Weak"
}
}
}
}
8 changes: 5 additions & 3 deletions Assets/Translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -2262,4 +2264,4 @@
"weak": "Weak"
}
}
}
}
9 changes: 9 additions & 0 deletions Assets/settings-search-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions Commons/Settings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 110 additions & 5 deletions Modules/LockScreen/LockScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -31,6 +38,10 @@ Loader {
} else {
LockKeysService.unregisterComponent("lockscreen");
}

// Trigger locksreen when loader is active
if (root.active)
startLockScreen();
}

onNeedsSpectrumChanged: {
Expand All @@ -51,27 +62,117 @@ 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
repeat: false
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 = "";
Expand All @@ -88,10 +189,11 @@ Loader {

WlSessionLock {
id: lockSession
locked: root.active
locked: root.locked

WlSessionLockSurface {
id: lockSurface
color: "black"

Loader {
anchors.fill: parent
Expand Down Expand Up @@ -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 {
Expand Down
58 changes: 44 additions & 14 deletions Modules/LockScreen/LockScreenBackground.qml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: {
Expand Down Expand Up @@ -103,35 +115,53 @@ 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
cache: false
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
}
}
Expand Down
8 changes: 8 additions & 0 deletions Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 6 additions & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
shellcheck,
jsonfmt,
lefthook,
python3,
kdePackages,
mkShellNoCC,
}:
Expand All @@ -29,6 +30,11 @@ mkShellNoCC {

# CoC
lefthook # githooks
python3 # dev scripts
kdePackages.qtdeclarative # qmlfmt, qmllint, qmlls and etc; Qt6
];

shellHook = ''
lefthook install
'';
}