import Quickshell import Quickshell.Hyprland import Quickshell.Wayland import Quickshell.Widgets import Quickshell.Io import Quickshell.Services.Pam import QtQuick import QtQuick.Controls import QtQuick.Effects WlSessionLock { id: lock locked: false required property bool animate property bool exit: false function instalock() {lock.animate = false; lock.locked = true} function open() {lock.animate = true; lock.locked = true;} function close() {lock.locked = false} WlSessionLockSurface { id: surface color: "transparent" Rectangle { anchors.fill: parent opacity: !lock.animate || capture.ready color: "#000" ClippingRectangle { anchors.fill: parent radius: 16 color: "#141414" Image { source: Quickshell.shellPath("assets/lockbg") anchors.fill: parent fillMode: Image.PreserveAspectCrop } } Rectangle { anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: surface.height/6 - this.height/2 } width: 300 height: 40 radius: height/2 color: "#222222" TextInput { id: input anchors.fill: parent focus: true color: "white" font.pixelSize: 20 echoMode: TextInput.NoEcho cursorDelegate: Item {} Keys.onReturnPressed: pam.start() } Row { anchors.verticalCenter: parent.verticalCenter x: (parent.width - this.width)/2 spacing: 4 Repeater { model: input.text.length Rectangle { width: 8 height: width radius: width/2 color: "white" } } Behavior on x { NumberAnimation { duration: 300 easing.type: Easing.OutQuart } } } Text { anchors { horizontalCenter: parent.horizontalCenter top: parent.bottom topMargin: 10 } color: "white" font.pixelSize: 14 text: pam.message } } } ClippingRectangle { anchors.fill: parent color: "white" opacity: capture.ready radius: 12 RectangularShadow { anchors.fill: parent spread: 2 blur: 10 } ScreencopyView { id: capture anchors.fill: parent captureSource: surface.screen readonly property bool ready: { lock.animate && lock.secure && capture.hasContent } Connections { target: lock function onLockedChanged() {capture.captureFrame()} } } transform: Scale { origin { x: width/2; y: height/2 } xScale: 1 yScale: xScale NumberAnimation on xScale { running: capture.ready && Hyprland.focusedWorkspace.toplevels.values.length to: 0.80 duration: 800 easing.type: Easing.InQuart } NumberAnimation on xScale { running: lock.exit && Hyprland.focusedWorkspace.toplevels.values.length from: 0.8 to: 1 duration: 800 easing.type: Easing.OutQuart } } SequentialAnimation on opacity { running: capture.ready PauseAnimation {duration: 500} NumberAnimation { duration: 300 to: 0 } } Timer { running: lock.exit interval: 800 repeat: false onTriggered: { lock.exit = false lock.locked = false } } NumberAnimation on opacity { duration: 300 running: lock.exit from: 0 to: 1 onStarted: { capture.parent.visible = true } } NumberAnimation on radius { running: capture.ready && Hyprland.focusedWorkspace.toplevels.values.length duration: 800 to: 20 } NumberAnimation on radius { running: lock.exit to: 12 duration: 800 } } PamContext { id: pam config: "pam.conf" configDirectory: "." onPamMessage: if (this.responseRequired) this.respond(input.text) onCompleted: result => { switch (result) { case PamResult.Success: lock.exit = true break; case PamResult.Failed: case PamResult.Error: case PamResult.MaxTries: input.clear() fail.start(); break; }} } } }