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 {
path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json`
watchChanges: true
onFileChanged: reload()
// onFileChanged: reload()
onAdapterUpdated: writeAdapter()
blockLoading: true

View file

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

View file

@ -105,8 +105,6 @@ ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 6
Layout.rightMargin: 6
}
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
color: "red"
implicitWidth: 500
exclusionMode: ExclusionMode.Normal
exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
anchors {
top: true

View file

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