repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Lock.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Io
import Quickshell.Services.Pam
import Quickshell.Wayland
Item {
id: root
property Theme theme
property Time timeService
property Media mediaService
property Notifications notificationService
property Power powerService
readonly property bool nativeActionRoute: true
property bool active: false
property bool logicalLocked: false
property bool unlocking: false
property bool unlocked: false
property bool teardownClean: true
property string failure: ""
property int inputLength: 0
property string pendingSecret: ""
property int failureNonce: 0
property real auroraTime: 0
property real angryMix: 0
property bool auroraAvailable: false
property var lastAction: ({})
readonly property string pamConfig: "nuguland-lock.conf"
readonly property var boundedActions: ["request", "type", "submit", "cancel"]
function stateObject() {
return {
"backend": "Lock.qml",
"usesNativeSessionLock": true,
"usesPam": true,
"pamConfig": pamConfig,
"nativeActionRoute": nativeActionRoute,
"helperProcesses": [],
"serviceOwnedActions": true,
"boundedActions": boundedActions,
"surface": "native-session-lock-surface",
"keyboardFocus": true,
"passwordOnly": true,
"auroraFullscreen": true,
"angryAuroraOnFailure": true,
"active": active,
"locked": logicalLocked,
"nativeLocked": sessionLock.locked,
"secure": sessionLock.secure,
"unlocking": unlocking,
"unlocked": unlocked,
"teardownClean": teardownClean,
"failure": failure,
"inputLength": inputLength,
"failureNonce": failureNonce,
"lastAction": lastAction
};
}
function stateJson() {
return JSON.stringify(stateObject());
}
function request() {
active = true;
logicalLocked = true;
unlocking = false;
unlocked = false;
teardownClean = false;
failure = "";
inputLength = 0;
pendingSecret = "";
angryMix = 0;
pam.active = true;
lastAction = {
"kind": "request",
"nativeActionRoute": nativeActionRoute
};
return stateJson();
}
function typeSecret(secret) {
inputLength = String(secret).length;
failure = "";
lastAction = {
"kind": "type",
"secretLength": inputLength
};
return stateJson();
}
function fail(message) {
active = true;
logicalLocked = true;
unlocking = false;
unlocked = false;
teardownClean = false;
inputLength = 0;
pendingSecret = "";
failure = String(message || "incorrect password").toLowerCase();
failureNonce += 1;
angryMix = 1;
lastAction = {
"kind": "fail",
"message": failure
};
return stateJson();
}
function finishUnlock(action) {
active = false;
logicalLocked = false;
unlocking = false;
unlocked = true;
teardownClean = true;
failure = "";
inputLength = 0;
pendingSecret = "";
angryMix = 0;
lastAction = action;
}
function submit(secret) {
if (unlocking)
return stateJson();
const value = String(secret);
unlocking = true;
failure = "";
inputLength = value.length;
pendingSecret = value;
pam.active = true;
pam.start();
lastAction = {
"kind": "submit",
"secretLength": value.length,
"nativeActionRoute": nativeActionRoute
};
return stateJson();
}
function cancel() {
pam.abort();
active = false;
logicalLocked = false;
unlocking = false;
unlocked = false;
teardownClean = true;
failure = "";
inputLength = 0;
pendingSecret = "";
angryMix = 0;
lastAction = {
"kind": "cancel"
};
return stateJson();
}
FileView {
id: auroraBlob
path: Qt.resolvedUrl("shaders/aurora.frag.qsb").toString().replace(/^file:\/\//, "")
preload: true
blockLoading: true
printErrors: false
onLoaded: root.auroraAvailable = true
onLoadFailed: root.auroraAvailable = false
}
Timer {
interval: 67
repeat: true
running: root.logicalLocked
onTriggered: {
root.auroraTime += 0.04;
root.angryMix += (0 - root.angryMix) * 0.018;
}
}
WlSessionLock {
id: sessionLock
locked: root.logicalLocked
WlSessionLockSurface {
color: root.theme ? root.theme.ui.backdrop : "black"
Rectangle {
anchors.fill: parent
color: root.theme ? root.theme.ui.backdrop : "black"
}
Loader {
anchors.fill: parent
active: root.auroraAvailable
sourceComponent: ShaderEffect {
property real iTime: root.auroraTime
property vector2d iResolution: Qt.vector2d(width, height)
property vector2d iMouse: Qt.vector2d(0.5 + root.angryMix * 0.18, 0.48 - root.angryMix * 0.1)
property real iFirestormMix: root.angryMix
anchors.fill: parent
blending: true
fragmentShader: Qt.resolvedUrl("shaders/aurora.frag.qsb")
}
}
Item {
id: lockUi
property real shakeOffset: 0
width: 280
height: 58
x: (parent.width - width) / 2 + shakeOffset
y: (parent.height - height) / 2
Rectangle {
anchors.fill: parent
radius: 16
color: root.theme ? root.theme.withAlpha(root.theme.ui.backdrop, 0.54) : "#66000000"
border.width: 2
border.color: root.theme ? root.theme.withAlpha(root.angryMix > 0.08 ? root.theme.error : root.theme.ui.accent, 0.92) : "white"
}
TextInput {
id: secretInput
anchors.fill: parent
anchors.leftMargin: 22
anchors.rightMargin: 22
echoMode: TextInput.Password
passwordCharacter: "•"
color: root.theme ? root.theme.content.normal : "white"
selectedTextColor: root.theme ? root.theme.content.backdrop : "black"
selectionColor: root.theme ? root.theme.ui.accent : "white"
font.family: "IBM Plex Sans"
font.pixelSize: 24
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
focus: root.logicalLocked
activeFocusOnTab: true
inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
onTextChanged: {
if (root.logicalLocked)
root.typeSecret(text);
}
Keys.onReturnPressed: event => {
event.accepted = true;
root.submit(secretInput.text);
secretInput.text = "";
}
Keys.onEnterPressed: event => {
event.accepted = true;
root.submit(secretInput.text);
secretInput.text = "";
}
Component.onCompleted: forceActiveFocus()
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: secretInput.forceActiveFocus()
}
Connections {
function onLogicalLockedChanged() {
if (root.logicalLocked)
Qt.callLater(secretInput.forceActiveFocus);
}
function onFailureNonceChanged() {
secretInput.text = "";
Qt.callLater(secretInput.forceActiveFocus);
shakeAnimation.restart();
}
target: root
}
SequentialAnimation {
id: shakeAnimation
NumberAnimation {
target: lockUi
property: "shakeOffset"
to: -18
duration: 36
easing.type: Easing.OutCubic
}
NumberAnimation {
target: lockUi
property: "shakeOffset"
to: 16
duration: 54
easing.type: Easing.InOutCubic
}
NumberAnimation {
target: lockUi
property: "shakeOffset"
to: -10
duration: 48
easing.type: Easing.InOutCubic
}
NumberAnimation {
target: lockUi
property: "shakeOffset"
to: 0
duration: 70
easing.type: Easing.OutCubic
}
}
}
}
}
PamContext {
id: pam
configDirectory: Qt.resolvedUrl("pam")
config: root.pamConfig
onPamMessage: {
if (pam.responseRequired) {
pam.respond(root.pendingSecret);
root.pendingSecret = "";
}
}
onCompleted: result => {
if (result === PamResult.Success)
root.finishUnlock({
"kind": "pam-unlock"
});
else
root.fail("incorrect password");
}
onError: error => {
root.fail(PamError.toString(error));
}
}
}