import QtQuick import QtQuick.Controls import QtQuick.Effects import QtQuick.Shapes import Quickshell import Quickshell.Wayland import Quickshell.Services.Pam WlSessionLock { id: lock readonly property string bgPath: "assets/lockbg.jpg" readonly property string logoPath: "assets/hyprland.svg" function open() { locked = true } function close() { exitAnimation.start() } locked: false component OutQuint500: Behavior { enabled: lock.secure PropertyAnimation { duration: 500 easing.type: Easing.OutQuint } } component ExitAnimation: NumberAnimation { running: exitAnimation.running to: 0 duration: 800 easing.type: Easing.InQuint } surface: WlSessionLockSurface { id: surface color: "transparent" Item { id: content anchors.fill: parent layer.enabled: true readonly property real angle: -1 * Math.tan(10 * Math.PI/180) // 10 degrees property bool idle: true Keys.onReturnPressed: { idle = false if (!pam.active) { pam.start() } } Item { id: bgMask anchors.fill: parent layer.enabled: true visible: false Rectangle { anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } height: 0 NumberAnimation on height { running: lock.secure to: surface.height duration: 800 easing.type: Easing.OutQuint } ExitAnimation on height {duration: 1000} } } Image { source: lock.bgPath layer.enabled: true anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } width: surface.width height: surface.height fillMode: Image.PreserveAspectCrop Rectangle { color: "white" anchors.fill: parent PropertyAnimation on opacity { running: lock.secure from: 1 to: 0 easing.type: Easing.Linear duration: 800 } } layer.effect: MultiEffect { maskEnabled: true maskSource: bgMask } } Item { anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } height: logo.height * 0.3 Rectangle { anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } height: parent.height * !content.idle opacity: !content.idle OutQuint500 on height {} OutQuint500 on opacity {} color: "#323232" RectangularShadow { anchors { fill: parent leftMargin: -blur rightMargin: -blur } z: -1 blur: 12 opacity: 0.6 ExitAnimation on opacity {} } ExitAnimation on height {} } Item { anchors.fill: parent clip: true visible: false Timer { running: true repeat: false interval: 500 onTriggered: parent.visible = true } Rectangle { anchors { top: inputBox.top right: inputBox.right bottom: inputBox.bottom rightMargin: !content.idle * width * -1 } width: surface.width * 0.05 antialiasing: true color: "#EEAA00" transform: Shear { xFactor: content.angle } ColorAnimation on color { onStarted: lockIcon.source = "assets/unlocked.svg" running: exitAnimation.running from: Qt.lighter("#A5CE00", 2.5) to: "#A5CE00" duration: 400 } SequentialAnimation on color { id: failAnimation running: false ColorAnimation { from: Qt.lighter("#CC3378", 2.5) to: "#CC3378" duration: 400 } ColorAnimation { duration: 1500 } ColorAnimation { to: "#EEAA00" duration: 600 easing.type: Easing.InOutQuint } } OutQuint500 on anchors.rightMargin {} Image { id: lockIcon anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter horizontalCenterOffset: content.angle * height * 0.5 } width: parent.width/2 height: parent.width/2 layer.enabled: true layer.effect: MultiEffect { blurMax: 4 shadowEnabled: true shadowOpacity: 0.4 shadowVerticalOffset: height * 0.04 } source: "assets/locked.svg" fillMode: Image.Pad sourceSize { width: width height: height } transform: Shear { xFactor: content.angle * -1 } } RectangularShadow { z: -1 anchors { top: parent.top bottom: parent.bottom topMargin: -blur bottomMargin: -blur horizontalCenter: parent.right horizontalCenterOffset: -blur/3 } blur: 16 width: blur opacity: 0.4 } } Rectangle { id: inputBox anchors { top: parent.top bottom: parent.bottom } antialiasing: true x: surface.width/3 + (surface.width/4) * content.idle width: (surface.width/3) * !content.idle OutQuint500 on x {} OutQuint500 on width {} ExitAnimation on x {to: surface.width/2 * -1} color: "#5F46BB" transform: Shear { xFactor: content.angle } RectangularShadow { z: -1 anchors { top: parent.top bottom: parent.bottom topMargin: -blur bottomMargin: -blur horizontalCenter: parent.right horizontalCenterOffset: -blur/3 } blur: 12 width: blur opacity: 0.8 } } Column { anchors { verticalCenter: inputBox.verticalCenter verticalCenterOffset: pamMessage.height * 1/3 left: inputBox.right leftMargin: surface.width/3 * -1 + logo.width * 0.29 } width: inputBox.width spacing: 10 Text { id: prompt anchors.left: parent.left anchors.leftMargin: passInputBg.height * 0.15/2 text: "o shit waddup!" color: "white" font { family: "comfortaa" pixelSize: 20 } } Rectangle { id: passInputBg anchors.left: parent.left anchors.leftMargin: height * 0.15 radius: 12 color: Qt.darker(inputBox.color, 1.5) height: prompt.font.pixelSize + 20 width: surface.width * 0.18 transform: Shear { xFactor: content.angle } TextInput { id: passInput anchors.fill: passInputBg anchors.margins: 10 focus: !pam.active font: prompt.font color: "white" echoMode: TextInput.Password transform: Shear { xFactor: content.angle * -1 } onTextChanged: content.idle = !text.length } } Text { id: pamMessage text: pam.responseRequired ? "" : pam.message height: 20 width: 1 color: "white" font: { family: prompt.family pixelSize: 12 } } } } } Item { id: logo anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } width: Math.min(surface.width, surface.height) * 0.6 height: width layer.enabled: true layer.smooth: true readonly property real borderWidth: width * 0.04 transform: [ Scale { origin {x: logo.width/2; y: logo.height/2 } xScale: ((content.idle ? 1.0 : 0.5) + (logoHover.hovered ? 0.05 : 0)) * lock.secure yScale: xScale OutQuint500 on xScale {} }, Scale { origin {x: logo.width/2; y: logo.height/2 } yScale: xScale NumberAnimation on xScale { running: lock.secure from: 0 to: 1 duration: 800 easing.type: Easing.OutQuint } }, Translate { x: (content.idle ? 0 : (surface.width/6 * -1)) OutQuint500 on x {} ExitAnimation on x {to: surface.width * -1} } ] // Background Shape { id: circleBg layer.enabled: true anchors.fill: parent preferredRendererType: Shape.CurveRenderer containsMode: Shape.FillContains ShapePath { fillGradient: LinearGradient { orientation: Gradient.Vertical GradientStop { position: 0; color: "#FB64A8" } GradientStop { position: 1; color: "#CA5289" } } strokeWidth: 0 PathAngleArc { moveToStart: true centerX: logo.width/2 centerY: logo.height/2 radiusX: logo.width * 0.45 + logo.borderWidth/2 radiusY: radiusX startAngle: 0 sweepAngle: 360 } } TapHandler { onTapped: content.idle = !content.idle } HoverHandler { id: logoHover } } Item { id: triangles anchors.fill: parent clip: true layer.enabled: true Instantiator { model: 24 delegate: Shape { id: tri property real x_size: Math.random() * logo.width + logo.width/8 property real y_size: x_size * 0.8 property real y_anim: 0 property real y_off: Math.random() * logo.height x: Math.random() * (logo.width + x_size*2) - x_size y: (y_anim + y_off) % (logo.height + y_size) - y_size ShapePath { strokeWidth: content.idle ? 2 : 4 strokeColor: "#9A4272" fillColor: "transparent" capStyle: ShapePath.FlatCap joinStyle: ShapePath.MiterJoin startX: tri.x_size/2 startY: 0 PathLine {x: 0; y: tri.y_size} PathLine {x: tri.x_size; y: tri.y_size} PathLine {x: tri.x_size/2; y: 0} } PropertyAnimation on y_anim { from: logo.height + tri.y_size running: lock.secure to: 0 duration: Math.random() * 15000 + 12000 loops: Animation.Infinite } } onObjectAdded: (index, obj) => obj.parent = triangles } layer.effect: ShaderEffect { property var src: triangles property var mask: circleBg property bool fadeDirection: true vertexShader: "default.vert.qsb" fragmentShader: "trifade.frag.qsb" } } // Group border and logo to shadow together Item { anchors.fill: parent layer.enabled: true // Circle border Shape { anchors.fill: parent preferredRendererType: Shape.CurveRenderer ShapePath { fillColor: "transparent" strokeColor: "white" strokeWidth: logo.borderWidth PathAngleArc { moveToStart: true centerX: logo.width/2 centerY: logo.height/2 radiusX: logo.height * 0.45 radiusY: radiusX startAngle: 0 sweepAngle: 360 } } } // Logo Image { anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter // verticalCenterOffset: logo.height * 0.065 * -1 verticalCenterOffset: logo.height * 0.015 * -1 } width: logo.width * 0.6 height: width source: lock.logoPath sourceSize { width: width height: height } } layer.effect: MultiEffect { blurMax: 48 shadowEnabled: true shadowOpacity: 0.4 shadowVerticalOffset: logo.height * 0.015 } } } } Timer { id: exitAnimation running: false repeat: false interval: 1200 onTriggered: lock.locked = false } PamContext { id: pam config: "pam.conf" configDirectory: "." onPamMessage: if (this.responseRequired) this.respond(passInput.text) onCompleted: result => { switch (result) { case PamResult.Success: exitAnimation.start() break; case PamResult.Failed: case PamResult.Error: case PamResult.MaxTries: passInput.clear() content.idle = false failAnimation.start(); break; }} } } }