diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 61c81b5..563a17c 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -556,55 +556,7 @@ WlSessionLock { } - // Error message with modern styling - Rectangle { - width: parent.width - height: 56 * Scaling.scale(screen) - radius: 28 - color: Qt.rgba(Colors.error.r, Colors.error.g, Colors.error.b, 0.15) - border.color: Colors.error - border.width: 1 * Scaling.scale(screen) - visible: lock.errorMessage !== "" - Layout.fillWidth: true - RowLayout { - anchors.fill: parent - anchors.margins: 18 * Scaling.scale(screen) - spacing: 12 * Scaling.scale(screen) - - Text { - text: "error" - font.family: "Material Symbols Outlined" - font.pixelSize: 22 * Scaling.scale(screen) - color: Colors.error - } - - Text { - text: lock.errorMessage - color: Colors.error - font.family: "Inter" - font.pixelSize: 16 * Scaling.scale(screen) - font.weight: Font.Medium - Layout.fillWidth: true - } - } - - NumberAnimation on opacity { - from: 0 - to: 1 - duration: 300 - running: lock.errorMessage !== "" - } - - // Shake animation on error - SequentialAnimation on x { - running: lock.errorMessage !== "" - NumberAnimation { to: 10; duration: 50 } - NumberAnimation { to: -10; duration: 100 } - NumberAnimation { to: 10; duration: 100 } - NumberAnimation { to: 0; duration: 50 } - } - } } diff --git a/Modules/Lockscreen/Lockscreen.qml b/Modules/Lockscreen/Lockscreen.qml new file mode 100644 index 0000000..31fc7e8 --- /dev/null +++ b/Modules/Lockscreen/Lockscreen.qml @@ -0,0 +1,912 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import Quickshell.Services.Pam +import Quickshell.Io +import Quickshell.Widgets +import qs.Services +import qs.Widgets + +WlSessionLock { + id: lock + + property string errorMessage: "" + property bool authenticating: false + property string password: "" + property bool pamAvailable: typeof PamContext !== "undefined" + property bool demoMode: true + property string demoPassword: "lysec123" + property int demoStep: 0 + locked: false + + // Demo timer for automatic interaction + Timer { + id: demoTimer + interval: 2000 + running: demoMode && locked + repeat: true + onTriggered: { + if (demoStep === 0) { + // Start typing demo password + lock.password = "" + demoStep = 1 + interval = 150 + } else if (demoStep === 1) { + // Type each character + if (lock.password.length < demoPassword.length) { + lock.password += demoPassword.charAt(lock.password.length) + } else { + demoStep = 2 + interval = 1000 + } + } else if (demoStep === 2) { + // Simulate authentication + lock.authenticating = true + demoStep = 3 + interval = 2000 + } else if (demoStep === 4) { + // Reset for next demo + lock.locked = false + demoStep = 0 + interval = 3000 + } + } + } + + // Demo authentication simulation + Timer { + id: authSimulation + interval: 2000 + running: false + onTriggered: { + lock.authenticating = false + if (lock.password === demoPassword) { + // Success - unlock + lock.locked = false + lock.password = "" + lock.errorMessage = "" + demoStep = 4 + demoTimer.interval = 1000 + } else { + // Error + lock.errorMessage = "Authentication failed" + lock.password = "" + demoStep = 0 + demoTimer.interval = 2000 + } + } + } + + function unlockAttempt() { + console.log("Unlock attempt started"); + + // Demo mode handling + if (demoMode) { + if (lock.authenticating) return + + lock.authenticating = true + authSimulation.start() + return + } + + // Real PAM authentication + if (!pamAvailable) { + lock.errorMessage = "PAM authentication not available."; + console.log("PAM not available"); + return; + } + if (!lock.password) { + lock.errorMessage = "Password required."; + console.log("No password entered"); + return; + } + console.log("Starting PAM authentication..."); + lock.authenticating = true; + lock.errorMessage = ""; + + console.log("[LockScreen] About to create PAM context with userName:", Quickshell.env("USER")); + var pam = Qt.createQmlObject('import Quickshell.Services.Pam; PamContext { config: "login"; user: "' + Quickshell.env("USER") + '" }', lock); + console.log("PamContext created", pam); + + pam.onCompleted.connect(function (result) { + console.log("PAM completed with result:", result); + lock.authenticating = false; + if (result === PamResult.Success) { + console.log("Authentication successful, unlocking..."); + lock.locked = false; + lock.password = ""; + lock.errorMessage = ""; + } else { + console.log("Authentication failed"); + lock.errorMessage = "Authentication failed."; + lock.password = ""; + } + pam.destroy(); + }); + + pam.onError.connect(function (error) { + console.log("PAM error:", error); + lock.authenticating = false; + lock.errorMessage = pam.message || "Authentication error."; + lock.password = ""; + pam.destroy(); + }); + + pam.onPamMessage.connect(function () { + console.log("PAM message:", pam.message, "isError:", pam.messageIsError); + if (pam.messageIsError) { + lock.errorMessage = pam.message; + } + }); + + pam.onResponseRequiredChanged.connect(function () { + console.log("PAM response required:", pam.responseRequired); + if (pam.responseRequired && lock.authenticating) { + console.log("Responding to PAM with password"); + pam.respond(lock.password); + } + }); + + var started = pam.start(); + console.log("PAM start result:", started); + } + + WlSessionLockSurface { + // Wallpaper image + Image { + id: lockBgImage + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: Wallpapers.currentWallpaper !== "" ? Wallpapers.currentWallpaper : "" + cache: true + smooth: true + mipmap: false + } + + // Blurred background + Rectangle { + anchors.fill: parent + color: "transparent" + + // Simple blur effect + layer.enabled: true + layer.smooth: true + layer.samples: 4 + } + + // Animated gradient overlay + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.6) } + GradientStop { position: 0.3; color: Qt.rgba(0, 0, 0, 0.3) } + GradientStop { position: 0.7; color: Qt.rgba(0, 0, 0, 0.4) } + GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.7) } + } + + // Subtle animated particles + Repeater { + model: 20 + Rectangle { + width: Math.random() * 4 + 2 + height: width + radius: width * 0.5 + color: Qt.rgba(Colors.accentPrimary.r, Colors.accentPrimary.g, Colors.accentPrimary.b, 0.3) + x: Math.random() * parent.width + y: Math.random() * parent.height + + SequentialAnimation on opacity { + loops: Animation.Infinite + NumberAnimation { to: 0.8; duration: 2000 + Math.random() * 3000 } + NumberAnimation { to: 0.1; duration: 2000 + Math.random() * 3000 } + } + } + } + } + + // Main content - Centered design + Item { + anchors.fill: parent + + // Top section - Time, date, and user info + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 80 * Scaling.scale(screen) + spacing: 40 * Scaling.scale(screen) + + // Time display - Large and prominent with pulse animation + Column { + spacing: 8 * Scaling.scale(screen) + Layout.alignment: Qt.AlignHCenter + + Text { + id: timeText + text: Qt.formatDateTime(new Date(), "HH:mm") + font.family: "Inter" + font.pixelSize: 140 * Scaling.scale(screen) + font.weight: Font.Bold + font.letterSpacing: -2 + color: Colors.textPrimary + horizontalAlignment: Text.AlignHCenter + + SequentialAnimation on scale { + loops: Animation.Infinite + NumberAnimation { to: 1.02; duration: 2000; easing.type: Easing.InOutQuad } + NumberAnimation { to: 1.0; duration: 2000; easing.type: Easing.InOutQuad } + } + } + + Text { + id: dateText + text: Qt.formatDateTime(new Date(), "dddd, MMMM d") + font.family: "Inter" + font.pixelSize: 26 * Scaling.scale(screen) + font.weight: Font.Light + color: Colors.textSecondary + horizontalAlignment: Text.AlignHCenter + width: timeText.width + } + } + + // User section with animated avatar + Column { + spacing: 16 * Scaling.scale(screen) + Layout.alignment: Qt.AlignHCenter + + // Animated avatar with glow effect + Rectangle { + width: 120 * Scaling.scale(screen) + height: 120 * Scaling.scale(screen) + radius: width * 0.5 + color: "transparent" + border.color: Colors.accentPrimary + border.width: 3 * Scaling.scale(screen) + anchors.horizontalCenter: parent.horizontalCenter + + // Glow effect + Rectangle { + anchors.centerIn: parent + width: parent.width + 24 * Scaling.scale(screen) + height: parent.height + 24 * Scaling.scale(screen) + radius: width * 0.5 + color: "transparent" + border.color: Qt.rgba(Colors.accentPrimary.r, Colors.accentPrimary.g, Colors.accentPrimary.b, 0.3) + border.width: 2 * Scaling.scale(screen) + z: -1 + + SequentialAnimation on scale { + loops: Animation.Infinite + NumberAnimation { to: 1.1; duration: 1500; easing.type: Easing.InOutQuad } + NumberAnimation { to: 1.0; duration: 1500; easing.type: Easing.InOutQuad } + } + } + + NImageRounded { + anchors.centerIn: parent + width: 100 * Scaling.scale(screen) + height: 100 * Scaling.scale(screen) + imagePath: Quickshell.env("HOME") + "/.face" + fallbackIcon: "person" + imageRadius: width * 0.5 + } + + // Hover animation + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: parent.scale = 1.05 + onExited: parent.scale = 1.0 + } + + Behavior on scale { + NumberAnimation { duration: 200; easing.type: Easing.OutBack } + } + } + + + } + } + + // Centered terminal section + Item { + width: 520 * Scaling.scale(screen) + height: 200 * Scaling.scale(screen) + anchors.centerIn: parent + + ColumnLayout { + anchors.centerIn: parent + spacing: 20 * Scaling.scale(screen) + width: parent.width + + + + // Futuristic Terminal-Style Input + Item { + width: parent.width + height: 200 * Scaling.scale(screen) + Layout.fillWidth: true + + // Terminal background with scanlines + Rectangle { + id: terminalBackground + anchors.fill: parent + radius: 16 + color: Colors.applyOpacity(Colors.backgroundPrimary, "E6") + border.color: Colors.accentPrimary + border.width: 2 * Scaling.scale(screen) + + // Scanline effect + Repeater { + model: 20 + Rectangle { + width: parent.width + height: 1 + color: Colors.applyOpacity(Colors.accentPrimary, "1A") + y: index * 10 + opacity: 0.3 + + SequentialAnimation on opacity { + loops: Animation.Infinite + NumberAnimation { to: 0.6; duration: 2000 + Math.random() * 1000 } + NumberAnimation { to: 0.1; duration: 2000 + Math.random() * 1000 } + } + } + } + + // Terminal header + Rectangle { + width: parent.width + height: 40 * Scaling.scale(screen) + color: Colors.applyOpacity(Colors.accentPrimary, "33") + topLeftRadius: 14 + topRightRadius: 14 + + RowLayout { + anchors.fill: parent + anchors.margins: 12 * Scaling.scale(screen) + spacing: 12 * Scaling.scale(screen) + + Text { + text: "●" + color: Colors.error + font.pixelSize: 16 * Scaling.scale(screen) + } + + Text { + text: "●" + color: Colors.warning + font.pixelSize: 16 * Scaling.scale(screen) + } + + Text { + text: "●" + color: Colors.accentPrimary + font.pixelSize: 16 * Scaling.scale(screen) + } + + Text { + text: "SECURE TERMINAL" + color: Colors.textPrimary + font.family: "Monaco" + font.pixelSize: 14 * Scaling.scale(screen) + font.weight: Font.Bold + Layout.fillWidth: true + } + } + } + + // Terminal content area + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.topMargin: 50 * Scaling.scale(screen) + anchors.margins: 12 * Scaling.scale(screen) + spacing: 12 * Scaling.scale(screen) + + // Welcome back typing effect + RowLayout { + Layout.fillWidth: true + spacing: 12 * Scaling.scale(screen) + + Text { + text: "root@noctalia:~$" + color: Colors.accentPrimary + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + font.weight: Font.Bold + } + + Text { + id: welcomeText + text: "" + color: Colors.textPrimary + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + property int currentIndex: 0 + property string fullText: "Welcome back, " + Quickshell.env("USER") + "!" + + Timer { + interval: 100 + running: true + repeat: true + onTriggered: { + if (parent.currentIndex < parent.fullText.length) { + parent.text = parent.fullText.substring(0, parent.currentIndex + 1) + parent.currentIndex++ + } else { + running = false + } + } + } + } + } + + // Command line with integrated password input + RowLayout { + Layout.fillWidth: true + spacing: 12 * Scaling.scale(screen) + + Text { + text: "root@noctalia:~$" + color: Colors.accentPrimary + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + font.weight: Font.Bold + } + + Text { + text: "sudo unlock_session" + color: Colors.textPrimary + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + } + + // Integrated password input (invisible, just for functionality) + TextInput { + id: passwordInput + width: 0 + height: 0 + visible: false + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + color: Colors.textPrimary + echoMode: TextInput.Password + passwordCharacter: "*" + passwordMaskDelay: 0 + + text: lock.password + onTextChanged: { + lock.password = text + // Terminal typing sound effect (visual) + typingEffect.start() + } + + Keys.onPressed: function (event) { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + lock.unlockAttempt(); + } + } + + Component.onCompleted: { + forceActiveFocus(); + } + } + + // Visual password display with integrated cursor + Text { + id: asterisksText + text: "*".repeat(passwordInput.text.length) + color: Colors.textPrimary + font.family: "Monaco" + font.pixelSize: 16 * Scaling.scale(screen) + visible: passwordInput.activeFocus + + // Typing effect animation + SequentialAnimation { + id: typingEffect + NumberAnimation { + target: passwordInput + property: "scale" + to: 1.01 + duration: 50 + } + NumberAnimation { + target: passwordInput + property: "scale" + to: 1.0 + duration: 50 + } + } + } + + // Blinking cursor positioned right after the asterisks + Rectangle { + width: 8 * Scaling.scale(screen) + height: 20 * Scaling.scale(screen) + color: Colors.accentPrimary + visible: passwordInput.activeFocus + anchors.left: asterisksText.right + anchors.leftMargin: 2 * Scaling.scale(screen) + anchors.verticalCenter: asterisksText.verticalCenter + + SequentialAnimation on opacity { + loops: Animation.Infinite + NumberAnimation { to: 1.0; duration: 500 } + NumberAnimation { to: 0.0; duration: 500 } + } + } + } + + // Status messages + Text { + text: lock.authenticating ? "Authenticating..." : (lock.errorMessage !== "" ? "Authentication failed." : "") + color: lock.authenticating ? Colors.accentPrimary : (lock.errorMessage !== "" ? Colors.error : "transparent") + font.family: "Monaco" + font.pixelSize: 14 * Scaling.scale(screen) + Layout.fillWidth: true + + SequentialAnimation on opacity { + running: lock.authenticating + loops: Animation.Infinite + NumberAnimation { to: 1.0; duration: 800 } + NumberAnimation { to: 0.5; duration: 800 } + } + } + + // Execute button + Rectangle { + width: 120 * Scaling.scale(screen) + height: 40 * Scaling.scale(screen) + radius: 12 + color: executeButtonArea.containsMouse ? Colors.accentPrimary : Colors.applyOpacity(Colors.accentPrimary, "33") + border.color: Colors.accentPrimary + border.width: 1 + enabled: !lock.authenticating + Layout.alignment: Qt.AlignRight + + Text { + anchors.centerIn: parent + text: lock.authenticating ? "EXECUTING..." : "EXECUTE" + color: executeButtonArea.containsMouse ? Colors.onAccent : Colors.accentPrimary + font.family: "Monaco" + font.pixelSize: 12 * Scaling.scale(screen) + font.weight: Font.Bold + } + + MouseArea { + id: executeButtonArea + anchors.fill: parent + hoverEnabled: true + onClicked: lock.unlockAttempt() + + SequentialAnimation on scale { + running: containsMouse + NumberAnimation { to: 1.05; duration: 150; easing.type: Easing.OutCubic } + } + + SequentialAnimation on scale { + running: !containsMouse + NumberAnimation { to: 1.0; duration: 150; easing.type: Easing.OutCubic } + } + } + + // Processing animation + SequentialAnimation on scale { + loops: Animation.Infinite + running: lock.authenticating + NumberAnimation { to: 1.02; duration: 600; easing.type: Easing.InOutQuad } + NumberAnimation { to: 1.0; duration: 600; easing.type: Easing.InOutQuad } + } + } + } + + // Terminal glow effect + Rectangle { + anchors.fill: parent + radius: parent.radius + color: "transparent" + border.color: Colors.applyOpacity(Colors.accentPrimary, "4D") + border.width: 1 + z: -1 + + SequentialAnimation on opacity { + loops: Animation.Infinite + NumberAnimation { to: 0.6; duration: 2000; easing.type: Easing.InOutQuad } + NumberAnimation { to: 0.2; duration: 2000; easing.type: Easing.InOutQuad } + } + } + } + + } + + // Error message with modern styling + Rectangle { + width: parent.width + height: 56 * Scaling.scale(screen) + radius: 28 + color: Qt.rgba(Colors.error.r, Colors.error.g, Colors.error.b, 0.15) + border.color: Colors.error + border.width: 1 * Scaling.scale(screen) + visible: lock.errorMessage !== "" + Layout.fillWidth: true + + RowLayout { + anchors.fill: parent + anchors.margins: 18 * Scaling.scale(screen) + spacing: 12 * Scaling.scale(screen) + + Text { + text: "error" + font.family: "Material Symbols Outlined" + font.pixelSize: 22 * Scaling.scale(screen) + color: Colors.error + } + + Text { + text: lock.errorMessage + color: Colors.error + font.family: "Inter" + font.pixelSize: 16 * Scaling.scale(screen) + font.weight: Font.Medium + Layout.fillWidth: true + } + } + + NumberAnimation on opacity { + from: 0 + to: 1 + duration: 300 + running: lock.errorMessage !== "" + } + + // Shake animation on error + SequentialAnimation on x { + running: lock.errorMessage !== "" + NumberAnimation { to: 10; duration: 50 } + NumberAnimation { to: -10; duration: 100 } + NumberAnimation { to: 10; duration: 100 } + NumberAnimation { to: 0; duration: 50 } + } + } + + + } + } + } + + // Demo controls (only visible in demo mode) + Row { + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 50 * Scaling.scale(screen) + spacing: 10 * Scaling.scale(screen) + visible: demoMode + + Rectangle { + width: 80 * Scaling.scale(screen) + height: 30 * Scaling.scale(screen) + radius: 6 + color: Colors.accentPrimary + + Text { + anchors.centerIn: parent + text: "Demo Lock" + color: Colors.onAccent + font.pixelSize: 12 * Scaling.scale(screen) + } + + MouseArea { + anchors.fill: parent + onClicked: { + lock.locked = true + lock.password = "" + lock.errorMessage = "" + lock.demoStep = 0 + demoTimer.interval = 2000 + } + } + } + + Rectangle { + width: 80 * Scaling.scale(screen) + height: 30 * Scaling.scale(screen) + radius: 6 + color: Colors.error + + Text { + anchors.centerIn: parent + text: "Reset" + color: Colors.onError + font.pixelSize: 12 * Scaling.scale(screen) + } + + MouseArea { + anchors.fill: parent + onClicked: { + lock.locked = false + lock.password = "" + lock.errorMessage = "" + lock.authenticating = false + lock.demoStep = 0 + } + } + } + } + + // Enhanced power buttons with hover effects + Row { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 50 * Scaling.scale(screen) + spacing: 20 * Scaling.scale(screen) + + // Shutdown with enhanced styling + Rectangle { + width: 64 * Scaling.scale(screen) + height: 64 * Scaling.scale(screen) + radius: 32 + color: Qt.rgba(Colors.error.r, Colors.error.g, Colors.error.b, shutdownArea.containsMouse ? 0.9 : 0.2) + border.color: Colors.error + border.width: 2 * Scaling.scale(screen) + + // Glow effect + Rectangle { + anchors.centerIn: parent + width: parent.width + 10 * Scaling.scale(screen) + height: parent.height + 10 * Scaling.scale(screen) + radius: width * 0.5 + color: "transparent" + border.color: Qt.rgba(Colors.error.r, Colors.error.g, Colors.error.b, 0.3) + border.width: 2 * Scaling.scale(screen) + opacity: shutdownArea.containsMouse ? 1 : 0 + z: -1 + + Behavior on opacity { + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } + } + } + + MouseArea { + id: shutdownArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock); + } + } + + Text { + anchors.centerIn: parent + text: "power_settings_new" + font.family: "Material Symbols Outlined" + font.pixelSize: 28 * Scaling.scale(screen) + color: shutdownArea.containsMouse ? Colors.onAccent : Colors.error + } + + Behavior on color { + ColorAnimation { duration: 200; easing.type: Easing.OutCubic } + } + scale: shutdownArea.containsMouse ? 1.1 : 1.0 + } + + // Reboot with enhanced styling + Rectangle { + width: 64 * Scaling.scale(screen) + height: 64 * Scaling.scale(screen) + radius: 32 + color: Qt.rgba(Colors.accentPrimary.r, Colors.accentPrimary.g, Colors.accentPrimary.b, rebootArea.containsMouse ? 0.9 : 0.2) + border.color: Colors.accentPrimary + border.width: 2 * Scaling.scale(screen) + + // Glow effect + Rectangle { + anchors.centerIn: parent + width: parent.width + 10 * Scaling.scale(screen) + height: parent.height + 10 * Scaling.scale(screen) + radius: width * 0.5 + color: "transparent" + border.color: Qt.rgba(Colors.accentPrimary.r, Colors.accentPrimary.g, Colors.accentPrimary.b, 0.3) + border.width: 2 * Scaling.scale(screen) + opacity: rebootArea.containsMouse ? 1 : 0 + z: -1 + + Behavior on opacity { + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } + } + } + + MouseArea { + id: rebootArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock); + } + } + + Text { + anchors.centerIn: parent + text: "refresh" + font.family: "Material Symbols Outlined" + font.pixelSize: 28 * Scaling.scale(screen) + color: rebootArea.containsMouse ? Colors.onAccent : Colors.accentPrimary + } + + Behavior on color { + ColorAnimation { duration: 200; easing.type: Easing.OutCubic } + } + scale: rebootArea.containsMouse ? 1.1 : 1.0 + } + + // Logout with enhanced styling + Rectangle { + width: 64 * Scaling.scale(screen) + height: 64 * Scaling.scale(screen) + radius: 32 + color: Qt.rgba(Colors.accentSecondary.r, Colors.accentSecondary.g, Colors.accentSecondary.b, logoutArea.containsMouse ? 0.9 : 0.2) + border.color: Colors.accentSecondary + border.width: 2 * Scaling.scale(screen) + + // Glow effect + Rectangle { + anchors.centerIn: parent + width: parent.width + 10 * Scaling.scale(screen) + height: parent.height + 10 * Scaling.scale(screen) + radius: width * 0.5 + color: "transparent" + border.color: Qt.rgba(Colors.accentSecondary.r, Colors.accentSecondary.g, Colors.accentSecondary.b, 0.3) + border.width: 2 * Scaling.scale(screen) + opacity: logoutArea.containsMouse ? 1 : 0 + z: -1 + + Behavior on opacity { + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } + } + } + + MouseArea { + id: logoutArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock); + } + } + + Text { + anchors.centerIn: parent + text: "exit_to_app" + font.family: "Material Symbols Outlined" + font.pixelSize: 28 * Scaling.scale(screen) + color: logoutArea.containsMouse ? Colors.onAccent : Colors.accentSecondary + } + + Behavior on color { + ColorAnimation { duration: 200; easing.type: Easing.OutCubic } + } + scale: logoutArea.containsMouse ? 1.1 : 1.0 + } + } + + // Timer for updating time + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: { + timeText.text = Qt.formatDateTime(new Date(), "HH:mm"); + dateText.text = Qt.formatDateTime(new Date(), "dddd, MMMM d"); + } + } + } +}