import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import "../../theme" import "../../services" Item { id: root focus: true // Force focus when lockscreen becomes visible onVisibleChanged: { if (visible) { root.forceActiveFocus() secretInput.forceActiveFocus() } } Component.onCompleted: { root.forceActiveFocus() secretInput.forceActiveFocus() } // Secret hidden TextInput to capture keyboard presses reliably TextInput { id: secretInput anchors.fill: parent opacity: 0 focus: true activeFocusOnTab: true echoMode: TextInput.Password onTextChanged: { // Synchronize with LockscreenService if (text !== LockscreenService.userPassword) { LockscreenService.userPassword = text LockscreenService.typedCount = text.length } } Keys.onPressed: (event) => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { event.accepted = true LockscreenService.submitPassword() } else if (event.key === Qt.Key_Escape) { event.accepted = true secretInput.text = "" LockscreenService.clearPassword() } } } Connections { target: LockscreenService function onIsLockedChanged() { if (LockscreenService.isLocked) { secretInput.text = "" root.forceActiveFocus() secretInput.forceActiveFocus() } } function onAuthFailedChanged() { if (LockscreenService.authFailed) { shakeAnim.restart() secretInput.text = "" } } } // Shake animation on incorrect password SequentialAnimation { id: shakeAnim running: false NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: -24; duration: 40; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: 24; duration: 50; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: -16; duration: 50; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: 16; duration: 50; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: -8; duration: 40; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: 8; duration: 40; easing.type: Easing.OutQuad } NumberAnimation { target: cardContainer; property: "anchors.horizontalCenterOffset"; to: 0; duration: 40; easing.type: Easing.OutQuad } } // Click anywhere to focus input MouseArea { anchors.fill: parent onClicked: { root.forceActiveFocus() secretInput.forceActiveFocus() } } // Main Centered Content Container ColumnLayout { id: mainColumn anchors.centerIn: parent spacing: 28 width: Math.min(520, parent.width - 40) // 1. Clock & Date Header ColumnLayout { Layout.alignment: Qt.AlignHCenter spacing: 6 Text { id: clockText text: { let date = new Date() let h = date.getHours().toString().padStart(2, '0') let m = date.getMinutes().toString().padStart(2, '0') let s = date.getSeconds().toString().padStart(2, '0') return `${h}:${m}:${s}` } color: Theme.fg font.pixelSize: 64 font.weight: Font.Bold font.family: "Sans-Serif" Layout.alignment: Qt.AlignHCenter Timer { interval: 1000 running: true repeat: true onTriggered: { let date = new Date() let h = date.getHours().toString().padStart(2, '0') let m = date.getMinutes().toString().padStart(2, '0') let s = date.getSeconds().toString().padStart(2, '0') clockText.text = `${h}:${m}:${s}` } } } Text { id: dateText text: { let date = new Date() let options = { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' } return date.toLocaleDateString(Qt.locale(), options) } color: Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.75) font.pixelSize: 15 font.weight: Font.Medium Layout.alignment: Qt.AlignHCenter } } Item { Layout.preferredHeight: 10 } // 2. User Avatar & Badge ColumnLayout { Layout.alignment: Qt.AlignHCenter spacing: 12 Rectangle { width: 96 height: 96 radius: 48 color: Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.15) border.color: Theme.accent border.width: 2 Layout.alignment: Qt.AlignHCenter // Avatar Icon / Initial Badge Text { anchors.centerIn: parent text: LockscreenService.username ? LockscreenService.username.charAt(0).toUpperCase() : "🔒" color: Theme.accent font.pixelSize: 42 font.weight: Font.Bold } // Breathing Glow Ring Rectangle { anchors.fill: parent anchors.margins: -4 radius: 52 color: "transparent" border.color: Theme.accent border.width: 1.5 opacity: avatarPulse.opacityVal Item { id: avatarPulse property real opacityVal: 0.3 SequentialAnimation on opacityVal { loops: Animation.Infinite running: true NumberAnimation { to: 0.8; duration: 1500; easing.type: Easing.InOutQuad } NumberAnimation { to: 0.2; duration: 1500; easing.type: Easing.InOutQuad } } } } } Text { text: LockscreenService.username || "User" color: Theme.fg font.pixelSize: 20 font.weight: Font.Bold Layout.alignment: Qt.AlignHCenter } } Item { Layout.preferredHeight: 4 } // 3. Elegant Glass Input Card with Animated Typing Dots Rectangle { id: cardContainer Layout.fillWidth: true implicitHeight: cardLayout.implicitHeight + 36 radius: 20 color: Theme.isDark ? Qt.rgba(30/255, 30/255, 46/255, 0.65) : Qt.rgba(240/255, 240/255, 245/255, 0.75) border.color: LockscreenService.authFailed ? Theme.red : (secretInput.activeFocus ? Theme.accent : Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.18)) border.width: LockscreenService.authFailed ? 2 : (secretInput.activeFocus ? 2 : 1) Behavior on border.color { ColorAnimation { duration: 180 } } ColumnLayout { id: cardLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: 18 spacing: 14 // Password Row: Input Field + Submit Button RowLayout { Layout.fillWidth: true spacing: 12 // Lock Icon Text { text: "🔒" font.pixelSize: 18 Layout.alignment: Qt.AlignVCenter } // Password Visual Placeholder / Input Area Rectangle { Layout.fillWidth: true implicitHeight: 44 radius: 12 color: Qt.rgba(0, 0, 0, 0.25) border.color: Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.1) // Empty State Placeholder Text Text { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 16 visible: LockscreenService.typedCount === 0 && !LockscreenService.isAuthenticating text: "Type password to unlock..." color: Theme.comment font.pixelSize: 14 } // Authenticating Spinner / Status Text Text { anchors.centerIn: parent visible: LockscreenService.isAuthenticating text: "Verifying password..." color: Theme.accent font.pixelSize: 14 font.weight: Font.Medium } // ANIMATED TYPING DOTS (Ultra-smooth scale, opacity, & glow entry) Row { anchors.centerIn: parent spacing: 12 visible: LockscreenService.typedCount > 0 && !LockscreenService.isAuthenticating Repeater { model: LockscreenService.typedCount Rectangle { id: dot width: 14 height: 14 radius: 7 color: index === LockscreenService.typedCount - 1 ? Theme.accent : Theme.fg Behavior on color { ColorAnimation { duration: 180 } } scale: 1.0 opacity: 1.0 ParallelAnimation { running: true NumberAnimation { target: dot; property: "scale"; from: 0.1; to: 1.0; duration: 240; easing.type: Easing.OutBack; easing.overshoot: 1.8 } NumberAnimation { target: dot; property: "opacity"; from: 0.0; to: 1.0; duration: 180; easing.type: Easing.OutCubic } } // Inner Glow Core Rectangle { anchors.centerIn: parent width: 6 height: 6 radius: 3 color: "#ffffff" } } } } // Focus Click Area MouseArea { anchors.fill: parent onClicked: { root.forceActiveFocus() secretInput.forceActiveFocus() } } } // Submit / Unlock Arrow Button Rectangle { width: 44 height: 44 radius: 12 color: submitMouse.containsMouse ? Theme.accent : Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.25) border.color: Theme.accent Behavior on color { ColorAnimation { duration: 120 } } Text { anchors.centerIn: parent text: "➔" color: submitMouse.containsMouse ? "#ffffff" : Theme.accent font.pixelSize: 18 font.weight: Font.Bold } MouseArea { id: submitMouse anchors.fill: parent hoverEnabled: true onClicked: LockscreenService.submitPassword() } } } // Dynamic Typing Status / Letter Counter & Feedback Message RowLayout { Layout.fillWidth: true // Typing Letter Counter Badge (Shows EXACT count of letters typed) Rectangle { implicitWidth: counterText.implicitWidth + 16 implicitHeight: 24 radius: 12 color: LockscreenService.typedCount > 0 ? Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.2) : "transparent" visible: LockscreenService.typedCount > 0 Behavior on color { ColorAnimation { duration: 150 } } Text { id: counterText anchors.centerIn: parent text: `⌨️ ${LockscreenService.typedCount} ${LockscreenService.typedCount === 1 ? 'letter' : 'letters'} typed` color: Theme.accent font.pixelSize: 11 font.weight: Font.Bold } } Item { Layout.fillWidth: true } // Auth Error Message Display Text { text: LockscreenService.authErrorMsg color: Theme.red font.pixelSize: 12 font.weight: Font.Bold visible: LockscreenService.authFailed } } } } Item { Layout.preferredHeight: 14 } // 4. Session Action Buttons (Power & Unlock) RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 16 // Suspend Button Rectangle { implicitWidth: 110 implicitHeight: 38 radius: 10 color: suspendMouse.containsMouse ? Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.15) : Qt.rgba(0, 0, 0, 0.3) border.color: Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.15) RowLayout { anchors.centerIn: parent spacing: 6 Text { text: "🌙"; font.pixelSize: 13 } Text { text: "Sleep"; color: Theme.fg; font.pixelSize: 12; font.weight: Font.Medium } } MouseArea { id: suspendMouse anchors.fill: parent hoverEnabled: true onClicked: { Quickshell.execDetached(["systemctl", "suspend"]) } } } // Reboot Button Rectangle { implicitWidth: 110 implicitHeight: 38 radius: 10 color: rebootMouse.containsMouse ? Qt.rgba(Theme.orange.r, Theme.orange.g, Theme.orange.b, 0.25) : Qt.rgba(0, 0, 0, 0.3) border.color: Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.15) RowLayout { anchors.centerIn: parent spacing: 6 Text { text: "🔄"; font.pixelSize: 13 } Text { text: "Restart"; color: Theme.fg; font.pixelSize: 12; font.weight: Font.Medium } } MouseArea { id: rebootMouse anchors.fill: parent hoverEnabled: true onClicked: SessionService.reboot() } } // Shutdown Button Rectangle { implicitWidth: 110 implicitHeight: 38 radius: 10 color: powerMouse.containsMouse ? Qt.rgba(Theme.red.r, Theme.red.g, Theme.red.b, 0.25) : Qt.rgba(0, 0, 0, 0.3) border.color: Qt.rgba(Theme.fg.r, Theme.fg.g, Theme.fg.b, 0.15) RowLayout { anchors.centerIn: parent spacing: 6 Text { text: "⚡"; font.pixelSize: 13 } Text { text: "Power Off"; color: Theme.fg; font.pixelSize: 12; font.weight: Font.Medium } } MouseArea { id: powerMouse anchors.fill: parent hoverEnabled: true onClicked: SessionService.shutdown() } } } } }