feat: init lockscreen

This commit is contained in:
kossLAN 2025-06-13 13:31:06 -04:00
parent 4687b955d4
commit 15ffd6d5b0
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
14 changed files with 433 additions and 4 deletions

View file

@ -11,7 +11,7 @@ Singleton {
FileView { FileView {
path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json` path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json`
watchChanges: true watchChanges: true
onFileChanged: reload() // onFileChanged: reload()
onAdapterUpdated: writeAdapter() onAdapterUpdated: writeAdapter()
blockLoading: true blockLoading: true

View file

@ -49,6 +49,11 @@ Item {
anchors.fill: parent anchors.fill: parent
// placeholder for now // placeholder for now
// Text {
// text
// }
ComboBox { ComboBox {
model: ["Power Save", "Balanced", "Performance"] model: ["Power Save", "Balanced", "Performance"]
currentIndex: PowerProfiles.profile currentIndex: PowerProfiles.profile

View file

@ -105,8 +105,6 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 6
Layout.rightMargin: 6
} }
Item { Item {

41
lockscreen/Controller.qml Normal file
View file

@ -0,0 +1,41 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
Scope {
id: root
PersistentProperties {
id: persist
property bool locked: false
}
IpcHandler {
target: "lockscreen"
function lock(): void {
persist.locked = true;
}
}
LockContext {
id: passwordContext
onUnlocked: persist.locked = false
}
WlSessionLock {
id: lock
locked: persist.locked
WlSessionLockSurface {
LockSurface {
anchors.fill: parent
context: passwordContext
}
}
}
}

View file

@ -0,0 +1,53 @@
import QtQuick
import Quickshell
import Quickshell.Services.Pam
Scope {
id: root
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
signal unlocked
signal failed
// Clear the failure text once the user starts typing.
onCurrentTextChanged: showFailure = false
function tryUnlock() {
if (currentText === "")
return;
root.unlockInProgress = true;
pam.start();
}
PamContext {
id: pam
// Its best to have a custom pam config for quickshell, as the system one
// might not be what your interface expects, and break in some way.
// This particular example only supports passwords.
configDirectory: "pam"
config: "password.conf"
// pam_unix will ask for a response for the password prompt
onPamMessage: {
if (this.responseRequired) {
this.respond(root.currentText);
}
}
// pam_unix won't send any important messages so all we need is the completion status.
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked();
root.currentText = "";
} else {
root.showFailure = true;
}
root.unlockInProgress = false;
}
}
}

198
lockscreen/LockSurface.qml Normal file
View file

@ -0,0 +1,198 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import ".."
Rectangle {
id: root
color: Window.active ? ShellSettings.colors["surface"] : ShellSettings.colors["surface_dim"]
required property LockContext context
Item {
anchors.fill: parent
Image {
id: bgImage
source: ShellSettings.settings.wallpaperUrl
fillMode: Image.PreserveAspectCrop
anchors.fill: parent
visible: false
}
FastBlur {
anchors.fill: bgImage
source: bgImage
radius: 80
transparentBorder: true
}
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.3
}
Rectangle {
anchors.fill: parent
color: "transparent"
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(0, 0, 0, 0.2)
}
GradientStop {
position: 0.5
color: Qt.rgba(0, 0, 0, 0.1)
}
GradientStop {
position: 1.0
color: Qt.rgba(0, 0, 0, 0.4)
}
}
}
}
// Date and time display
ColumnLayout {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 120
}
spacing: 10
Text {
id: clock
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
font.pointSize: 72
font.weight: Font.Light
color: "white"
text: {
const now = this.date;
let hours = now.getHours();
const minutes = now.getMinutes().toString().padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // 0 should be 12
return `${hours}:${minutes}`;
}
property var date: new Date()
Layout.alignment: Qt.AlignHCenter
Timer {
running: true
repeat: true
interval: 1000
onTriggered: clock.date = new Date()
}
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 0
radius: 20
samples: 41
color: Qt.rgba(1, 1, 1, 0.3)
}
}
}
// login section
ColumnLayout {
visible: Window.active
anchors.centerIn: parent
spacing: 30
Rectangle {
id: profileImage
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 120
Layout.preferredHeight: 120
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: profileImage.width
height: profileImage.height
radius: width / 2
color: "black"
}
}
Image {
source: "root:resources/general/pfp.png"
anchors.fill: parent
}
}
// password input, should probably split this out into a seperate comp
LoginField {
id: passwordBox
enabled: !root.context.unlockInProgress
Layout.preferredWidth: 250
Layout.preferredHeight: 30
Layout.maximumHeight: 30
Layout.alignment: Qt.AlignHCenter
onTextChanged: root.context.currentText = this.text
onAccepted: root.context.tryUnlock()
Connections {
target: root.context
function onCurrentTextChanged() {
if (!passwordBox.shaking) {
passwordBox.text = root.context.currentText;
}
}
function onShowFailureChanged() {
if (root.context.showFailure && !passwordBox.shaking) {
passwordBox.shaking = true;
}
}
}
}
}
// hint text
Text {
text: "Press Enter to unlock"
color: Qt.rgba(1, 1, 1, 0.5)
font.pointSize: 12
horizontalAlignment: Text.AlignHCenter
opacity: passwordBox.text.length > 0 ? 1.0 : 0.0
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 60
}
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
// testing button
Button {
visible: false
text: "Emergency Unlock"
onClicked: root.context.unlocked()
anchors {
right: parent.right
bottom: parent.bottom
margins: 20
}
}
}

92
lockscreen/LoginField.qml Normal file
View file

@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
TextField {
id: root
color: "white"
scale: activeFocus ? 1.05 : 1.0
padding: 8
focus: true
echoMode: TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
font.pointSize: 11
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: Qt.rgba(1, 1, 1, 0.1)
border.color: root.activeFocus ? Qt.rgba(1, 1, 1, 0.5) : Qt.rgba(1, 1, 1, 0.2)
border.width: 1
radius: 8
layer.enabled: true
layer.effect: FastBlur {
radius: 10
transparentBorder: true
}
}
transform: Translate {
id: shakeTransform
x: 0
}
property bool shaking: false
onShakingChanged: {
if (shaking)
shakeAnimation.start();
}
Behavior on scale {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
SequentialAnimation {
id: shakeAnimation
NumberAnimation {
target: shakeTransform
property: "x"
to: -8
duration: 50
easing.type: Easing.OutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: 8
duration: 100
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: -6
duration: 80
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: 6
duration: 80
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: -3
duration: 60
easing.type: Easing.InOutQuad
}
onFinished: {
root.shaking = false;
root.text = "";
}
}
}

10
lockscreen/README.md Normal file
View file

@ -0,0 +1,10 @@
# Lockscreen
This is a simple but functional lockscreen that follows the system color scheme.
The only authentication method it supports is a password.
You can run the lockscreen with `quickshell -p shell.qml`.
You can run the lockscreen in test mode (as a window) with `quickshell -p test.qml`.
![](./image.png)

BIN
lockscreen/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1 @@
auth sufficient pam_fprintd.so

View file

@ -0,0 +1 @@
auth required pam_unix.so

25
lockscreen/test.qml Normal file
View file

@ -0,0 +1,25 @@
import QtQuick
import Quickshell
ShellRoot {
LockContext {
id: lockContext
onUnlocked: Qt.quit();
}
FloatingWindow {
LockSurface {
anchors.fill: parent
context: lockContext
}
}
// exit the example if the window closes
Connections {
target: Quickshell
function onLastWindowClosed() {
Qt.quit();
}
}
}

View file

@ -42,7 +42,7 @@ Singleton {
id: notificationPanel id: notificationPanel
color: "red" color: "red"
implicitWidth: 500 implicitWidth: 500
exclusionMode: ExclusionMode.Normal exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
anchors { anchors {
top: true top: true

View file

@ -8,6 +8,7 @@ import "notifications" as Notifications
import "volume-osd" as VolumeOSD import "volume-osd" as VolumeOSD
import "settings" as Settings import "settings" as Settings
import "launcher" as Launcher import "launcher" as Launcher
import "lockscreen" as LockScreen
import "wallpaper" as Wallpaper import "wallpaper" as Wallpaper
import "screencapture" as ScreenCapture import "screencapture" as ScreenCapture
@ -29,6 +30,10 @@ ShellRoot {
Bar.Bar { Bar.Bar {
screen: scope.modelData screen: scope.modelData
} }
LockScreen.Controller {
}
} }
} }