diff --git a/lockscreen/AuthContext.qml b/lockscreen/AuthContext.qml deleted file mode 100644 index f5c5745..0000000 --- a/lockscreen/AuthContext.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick -import Quickshell -import Quickshell.Io - -QtObject { - property int status: AuthContext.Status.FirstLogin - signal unlocked(); - - enum Status { - FirstLogin, - Authenticating, - LoginFailed - } - - property string password - - property var pamtester: Process { - property bool failed: true - - command: ["pamtester", "login", Quickshell.env("USER"), "authenticate"] - - onStarted: this.write(`${password}\n`) - - stdout: SplitParser { - // fails go to stderr - onRead: pamtester.failed = false - } - - onExited: { - if (failed) { - status = AuthContext.Status.LoginFailed; - } else { - unlocked(); - } - } - } - - function tryLogin(password: string) { - this.password = password - status = AuthContext.Status.Authenticating; - pamtester.running = true; - } -} diff --git a/lockscreen/LockContext.qml b/lockscreen/LockContext.qml new file mode 100644 index 0000000..73cdb5b --- /dev/null +++ b/lockscreen/LockContext.qml @@ -0,0 +1,54 @@ +import QtQuick +import Quickshell +import Quickshell.Services.Pam + +Scope { + id: root + signal unlocked() + signal failed() + + // These properties are in the context and not individual lock surfaces + // so all surfaces can share the same state. + property string currentText: "" + property bool unlockInProgress: false + property bool showFailure: false + + // 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(); + } else { + root.currentText = ""; + root.showFailure = true; + } + + root.unlockInProgress = false; + } + } +} diff --git a/lockscreen/LockSurface.qml b/lockscreen/LockSurface.qml new file mode 100644 index 0000000..08d107b --- /dev/null +++ b/lockscreen/LockSurface.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Fusion +import Quickshell.Wayland + +Rectangle { + id: root + required property LockContext context + readonly property ColorGroup colors: Window.active ? palette.active : palette.inactive + + color: colors.window + + Button { + text: "Its not working, let me out" + onClicked: context.unlocked(); + } + + Label { + id: clock + property var date: new Date() + + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 100 + } + + // The native font renderer tends to look nicer at large sizes. + renderType: Text.NativeRendering + font.pointSize: 80 + + // updates the clock every second + Timer { + running: true + repeat: true + interval: 1000 + + onTriggered: clock.date = new Date(); + } + + // updated when the date changes + text: { + const hours = this.date.getHours().toString().padStart(2, '0'); + const minutes = this.date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; + } + } + + ColumnLayout { + // Uncommenting this will make the password entry invisible except on the active monitor. + // visible: Window.active + + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.verticalCenter + } + + RowLayout { + TextField { + id: passwordBox + + implicitWidth: 400 + padding: 10 + + focus: true + enabled: !root.context.unlockInProgress + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData + + // Update the text in the context when the text in the box changes. + onTextChanged: root.context.currentText = this.text; + + // Try to unlock when enter is pressed. + onAccepted: root.context.tryUnlock(); + + // Update the text in the box to match the text in the context. + // This makes sure multiple monitors have the same text. + Connections { + target: root.context + + function onCurrentTextChanged() { + passwordBox.text = root.context.currentText; + } + } + } + + Button { + text: "Unlock" + padding: 10 + + // don't steal focus from the text box + focusPolicy: Qt.NoFocus + + enabled: !root.context.unlockInProgress && root.context.currentText !== ""; + onClicked: root.context.tryUnlock(); + } + } + + Label { + visible: root.context.showFailure + text: "Incorrect password" + } + } +} diff --git a/lockscreen/Lockscreen.qml b/lockscreen/Lockscreen.qml deleted file mode 100644 index 83f58b2..0000000 --- a/lockscreen/Lockscreen.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick -import QtQuick.Controls.Basic - -Item { - required property AuthContext context - - Item { - anchors.centerIn: parent - - TextField { - id: entryBox - anchors.centerIn: parent - width: 600 - font.pointSize: 24 - - enabled: context.status != AuthContext.Status.Authenticating - focus: true - horizontalAlignment: TextInput.AlignHCenter - echoMode: TextInput.Password - inputMethodHints: Qt.ImhSensitiveData - placeholderText: "Enter password" - - onAccepted: { - if (text != "") context.tryLogin(text) - } - - onEnabledChanged: { - if (enabled) text = "" - } - } - - Text { - id: status - color: "white" - font.pointSize: 24 - - anchors { - horizontalCenter: entryBox.horizontalCenter - top: entryBox.bottom - topMargin: 40 - } - - text: { - switch (context.status) { - case AuthContext.Status.FirstLogin: return "" - case AuthContext.Status.Authenticating: return "Authenticating" - case AuthContext.Status.LoginFailed: return "Login Failed" - } - } - } - } -} diff --git a/lockscreen/README.md b/lockscreen/README.md index 9a1f1b0..1926b9b 100644 --- a/lockscreen/README.md +++ b/lockscreen/README.md @@ -1,7 +1,7 @@ # Lockscreen -This is a barebones lockscreen with a password input box. -Note that you MUST have `pamtester` installed or you won't be able to log in. +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`. diff --git a/lockscreen/image.png b/lockscreen/image.png index 956c8aa..f9d25d3 100644 Binary files a/lockscreen/image.png and b/lockscreen/image.png differ diff --git a/lockscreen/pam/password.conf b/lockscreen/pam/password.conf new file mode 100644 index 0000000..7e5d75a --- /dev/null +++ b/lockscreen/pam/password.conf @@ -0,0 +1 @@ +auth required pam_unix.so diff --git a/lockscreen/shell.qml b/lockscreen/shell.qml index c873c68..c1e9b1e 100644 --- a/lockscreen/shell.qml +++ b/lockscreen/shell.qml @@ -1,38 +1,31 @@ -import QtQuick -import QtQuick.Controls.Basic import Quickshell import Quickshell.Wayland ShellRoot { - AuthContext { - id: authContext - onUnlocked: lock.locked = false + // This stores all the information shared between the lock surfaces on each screen. + LockContext { + id: lockContext + + onUnlocked: { + // Unlock the screen before exiting, or the compositor will display a + // fallback lock you can't interact with. + lock.locked = false; + + Qt.quit(); + } } WlSessionLock { id: lock + + // Lock the session immediately when quickshell starts. locked: true - onLockedChanged: { - if (!locked) Qt.quit(); - } - WlSessionLockSurface { - // You probably want to replace this with an image. - color: "#303030" - - // For your own sanity you should probably keep this - // while working on the lockscreen. - Button { - text: "Help! I misconfigured my lockscreen!" - onClicked: lock.locked = false - } - - Lockscreen { + LockSurface { anchors.fill: parent - context: authContext + context: lockContext } } } - } diff --git a/lockscreen/test.qml b/lockscreen/test.qml index 2917665..22dddb9 100644 --- a/lockscreen/test.qml +++ b/lockscreen/test.qml @@ -2,17 +2,24 @@ import QtQuick import Quickshell ShellRoot { - AuthContext { - id: authContext - onUnlocked: Qt.quit() + LockContext { + id: lockContext + onUnlocked: Qt.quit(); } FloatingWindow { - color: "#303030" - - Lockscreen { + LockSurface { anchors.fill: parent - context: authContext + context: lockContext + } + } + + // exit the example if the window closes + Connections { + target: Quickshell + + function onLastWindowClosed() { + Qt.quit(); } } }