-
-
Notifications
You must be signed in to change notification settings - Fork 872
Open
Labels
FEATUREFeature advice/requestFeature advice/request
Description
What would you like to be added?
Lock Screen output control
How will it help?
Lock Screen customizing/theming
Extra info
Hi, I'm customizing my QML screenlock.
Problems:
- I'm using manual noise
- Idk how to aply the Config.options.background.workspaceZoom to scale the image
- I would prefer to only show the CentralBox above the apps in use with the blur, insted of loading the wallpaper
Solved:
- animation (@vaguesyntax)
- CentralBox in a single screen (not a good way yet)
Would be nice to have a theme library for this repo.
recording_2025-11-26_01.49.41.mp4
I think it is usable:
https://github.com/masthierryi/LinuxThings/blob/main/LockSurface.qml
Code ~/.config/quickshell/ii/modules/ii/lock/LockSurface.qml
Details
import Quickshell
import Quickshell.Wayland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Fusion
import Qt5Compat.GraphicalEffects
Rectangle {
id: root
required property LockContext context
// Monitor name where the login field (centralBox) should appear
property string targetMonitor: "HDMI-A-1"
// Base wallpaper, same crop as the workspace
Image {
id: wallpaperLayer
anchors.fill: parent
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
smooth: true
opacity: 0 // invisible but still renders for blur
}
// Main Blur
FastBlur {
id: globalBlur
anchors.fill: parent
source: wallpaperLayer
radius: 40
}
// Darkening
Rectangle {
anchors.fill: parent
color: "#000000"
opacity: 0.6 // adjust between 0.15–0.35
}
// NOISE
Image {
anchors.fill: parent
source: "noise.png"
fillMode: Image.Tile
opacity: 0.04
smooth: false
}
color: "transparent"
// Function to force focus on the password field (kept)
function forceFieldFocus() {
passwordBox.forceActiveFocus();
}
// FORCE UNLOCK
RippleButton {
anchors {
top: parent.top
left: parent.left
leftMargin: 10
topMargin: 10
}
implicitHeight: 40
// Background color using the Appearance.qml theme
colBackground: Appearance.colors.colLayer2
onClicked: {
context.unlocked(LockContext.ActionEnum.Unlock);
GlobalStates.screenLocked = false;
}
contentItem: StyledText {
// Text color using the Appearance.qml theme
color: Appearance.colors.colOnLayer2
text: "[[ DEBUG BYPASS ]]"
}
}
Rectangle {
id: centralBox
// show only in the main monitor
visible: Wayland.outputName === root.targetMonitor // Use Wayland.outputName
// dont work
Component.onCompleted: {
console.log("Current Monitor Name (LockSurface):", Wayland.outputName);
console.log("Target Monitor:", targetMonitor);
}
property int spacingRow: 40
property int sqrsize: 450 // makes the image fit snugly with centralBox
width: sqrsize + spacingRow + rightColumn.implicitWidth
height: sqrsize
radius: 0 // rounding corners of the centralBox
// Dynamic background color (using Appearance.colors.colLayer0 with transparency)
color: Appearance.colors.colBackgroundSurfaceContainer
anchors.centerIn: parent
border.color: Appearance.colors.colOutlineVariant
border.width: 2
RowLayout {
id: mainRow
anchors.fill: parent
anchors.margins: centralBox.border.width
spacing: centralBox.spacingRow
// Dynamic Wallpaper
// LockSurface.qml - inside Image { id: preview }
Item {
id: container
width: centralBox.sqrsize - centralBox.border.width * 2
height: centralBox.sqrsize - centralBox.border.width * 2 // ensures a square aspect
// p.s.: I didn't want the border underneath the image, so I used this workaround
// the "- centralBox.border.width * 2" can be removed and anchors.margins should be changed to zero
clip: true
Image {
id: preview
anchors.fill: parent
fillMode: Image.PreserveAspectCrop // <-- center-cropped, no stretching
asynchronous: true
smooth: true
source: Config.options.background.wallpaperPath
}
}
ColumnLayout {
id: rightColumn
Layout.fillWidth: false
Layout.preferredWidth: implicitWidth
Layout.minimumWidth: implicitWidth
Layout.maximumWidth: implicitWidth
spacing: 20
// Clock (using dynamic font from Appearance.qml)
Label {
id: clock
Layout.topMargin: -10 // workaround, can be removed freely
Layout.bottomMargin: -5 // workaround, can be removed freely
property var date: new Date()
renderType: Text.NativeRendering
font {
// Using the title/clock font
family: Appearance.font.family.monospace
pixelSize: 60 // Keep large size for the clock
variableAxes: Appearance.font.variableAxes.title
}
color: Appearance.colors.colOnSurface // Main text color
Timer {
running: true
repeat: true
interval: 60
onTriggered: clock.date = new Date()
}
text: {
const h = clock.date.getHours().toString().padStart(2, "0");
const m = clock.date.getMinutes().toString().padStart(2, "0");
return `${h}:${m}`;
}
}
// System Information (using dynamic font from Appearance.qml)
Label {
textFormat: Text.RichText
wrapMode: Text.NoWrap
rightPadding: centralBox.spacingRow
Layout.bottomMargin: 10 // workaround, can be removed freely
// Using the monospace font (if Iosevka is configured as such)
font {
family: Appearance.font.family.monospace
pixelSize: Appearance.font.pixelSize.small
variableAxes: Appearance.font.variableAxes.numbers
}
// Get the primary color from your Appearance.qml theme
readonly property string accentColor: Appearance.colors.colPrimary
// Build the text using <font color='...'> tags
text: `
<font color='${accentColor}'>CPU:</font> Intel Xeon E5-2650 v4 @2.90 GHz<br>
<font color='${accentColor}'>GPU:</font> AMD Radeon RX 580 2048SP<br>
<br>
<font color='${accentColor}'>OS:</font> Arch Linux x86_64<br>
<font color='${accentColor}'>Kernel:</font> Linux 6.17.8-arch1-1<br>
<font color='${accentColor}'>WM:</font> Hyprland 0.52.1 (Wayland)<br>
<br>
<font color='${accentColor}'>Shell:</font> fish 4.2.1<br>
<font color='${accentColor}'>Terminal:</font> kitty 0.44.0<br><br>
<font color='${accentColor}'>masthierryi</font> @masthierryi-arch
`
// Dynamic color (fallback, if tag fails or for untagged text)
color: Appearance.colors.colSubtext
}
RowLayout {
spacing: 0
TextField {
id: passwordBox
implicitWidth: 300
// Style
padding: 12
clip: true
font.pixelSize: Appearance.font.pixelSize.small
Component.onCompleted: {
forceFieldFocus();
}
focus: true
enabled: !root.context.unlockInProgress
echoMode: TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
onTextChanged: root.context.currentText = this.text
onAccepted: root.context.tryUnlock()
Connections {
target: root.context
function onCurrentTextChanged() {
passwordBox.text = root.context.currentText
}
}
Keys.onPressed: event => {
root.context.resetClearTimer();
}
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
// Using dynamic animation properties (Duration and Easing)
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -30; duration: Appearance.animation.elementMoveFast.duration / 3 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 30; duration: Appearance.animation.elementMoveFast.duration / 3 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -15; duration: Appearance.animation.elementMoveFast.duration / 4 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 15; duration: Appearance.animation.elementMoveFast.duration / 4 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 0; duration: Appearance.animation.elementMoveFast.duration / 5 }
}
Connections {
target: GlobalStates
function onScreenUnlockFailedChanged() {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();
}
}
background: Rectangle {
// Semi-transparent black background (~70%)
color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerLowest, 0.4)
border.color: "transparent"
radius: Appearance.rounding.verysmall
Behavior on border.width {
NumberAnimation { duration: 150 }
}
}
property bool materialShapeChars: Config.options.lock.materialShapeChars
// Dynamic color, using ColorUtils from Appearance.qml and colOnLayer1
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0)
Loader {
active: passwordBox.materialShapeChars
anchors {
fill: parent
leftMargin: passwordBox.padding
rightMargin: passwordBox.padding
}
sourceComponent: PasswordChars {
length: root.context.currentText.length
}
}
}
}
}
}
}
}vaguesyntax
Metadata
Metadata
Assignees
Labels
FEATUREFeature advice/requestFeature advice/request