diff --git a/Modules/Lock/Lock.qml b/Modules/Lock/Lock.qml index 9d5aeb30..a2f69f23 100644 --- a/Modules/Lock/Lock.qml +++ b/Modules/Lock/Lock.qml @@ -5,6 +5,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import qs.Common +import qs.Services Item { id: root @@ -54,6 +55,7 @@ Item { onStreamFinished: { if (text.includes("true")) { console.log("Session is locked on startup, activating lock screen") + LockScreenService.resetState(); loader.activeAsync = true } } @@ -77,12 +79,14 @@ Item { onRead: (line) => { if (line.includes("org.freedesktop.login1.Session.Lock")) { console.log("login1: Lock signal received -> show lock") + LockScreenService.resetState(); loader.activeAsync = true } else if (line.includes("org.freedesktop.login1.Session.Unlock")) { console.log("login1: Unlock signal received -> hide lock") loader.active = false } else if (line.includes("LockedHint") && line.includes("true")) { console.log("login1: LockedHint=true -> show lock") + LockScreenService.resetState(); loader.activeAsync = true } else if (line.includes("LockedHint") && line.includes("false")) { console.log("login1: LockedHint=false -> hide lock") @@ -135,6 +139,7 @@ Item { function lock(): void { console.log("Lock screen requested via IPC") + LockScreenService.resetState(); loader.activeAsync = true } diff --git a/Modules/Lock/LockScreenContent.qml b/Modules/Lock/LockScreenContent.qml index be693314..6bb69f41 100644 --- a/Modules/Lock/LockScreenContent.qml +++ b/Modules/Lock/LockScreenContent.qml @@ -16,68 +16,24 @@ Item { property string passwordBuffer: "" property bool demoMode: false - property bool unlocking: false property var powerModal: null property string confirmAction: "" - property var facts: [ - "A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.", - "A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.", - "Right now, 100 trillion solar neutrinos are passing through your body every second.", - "The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.", - "The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.", - "There's a nebula out there that's actually colder than empty space itself.", - "We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.", - "Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.", - "Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.", - "Distant galaxies can move away from us faster than light because space itself is stretching.", - "The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.", - "The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.", - "A day on Venus lasts longer than its entire year around the Sun.", - "On Mercury, the time between sunrises is 176 Earth days long.", - "In about 4.5 billion years, our galaxy will smash into Andromeda.", - "Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.", - "PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.", - "Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.", - "Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.", - "Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.", - "Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.", - "Counting to a billion at one number per second would take over 31 years.", - "Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.", - "Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.", - "Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.", - "Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.", - "Even at light-speed, you'd never catch up to most galaxies—space expands faster.", - "Only around 5% of galaxies are ever reachable—even at light-speed.", - "If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.", - "If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.", - "Our oldest radio signals will reach the Milky Way's center in 26,000 years.", - "Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.", - "The Moon moves 3.8 centimeters farther from Earth every year.", - "The universe creates 275 million new stars every single day.", - "Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.", - "If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.", - "The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have." - ]; - property string randomFact: "" - signal unlockRequested() - - function pickRandomFact() { - randomFact = facts[Math.floor(Math.random() * facts.length)]; - } + signal unlockRequested Component.onCompleted: { - pickRandomFact(); - WeatherService.addRef(); - UserInfoService.refreshUserInfo(); + if (demoMode) + LockScreenService.pickRandomFact() + + WeatherService.addRef() + UserInfoService.refreshUserInfo() } onDemoModeChanged: { if (demoMode) - pickRandomFact(); - + LockScreenService.pickRandomFact() } Component.onDestruction: { - WeatherService.removeRef(); + WeatherService.removeRef() } Image { @@ -105,9 +61,7 @@ Item { duration: Theme.mediumDuration easing.type: Theme.standardEasing } - } - } Rectangle { @@ -137,7 +91,11 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - text: SettingsData.use24HourClock ? Qt.formatTime(systemClock.date, "H:mm") : Qt.formatTime(systemClock.date, "h:mm AP") + text: SettingsData.use24HourClock ? Qt.formatTime( + systemClock.date, + "H:mm") : Qt.formatTime( + systemClock.date, + "h:mm AP") font.pixelSize: 120 font.weight: Font.Light color: "white" @@ -148,12 +106,12 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: clockText.bottom anchors.topMargin: -20 - text: Qt.formatDate(systemClock.date, SettingsData.lockDateFormat) + text: Qt.formatDate(systemClock.date, + SettingsData.lockDateFormat) font.pixelSize: Theme.fontSizeXLarge color: "white" opacity: 0.9 } - } ColumnLayout { @@ -188,12 +146,12 @@ Item { source: { if (PortalService.profileImage === "") - return ""; + return "" if (PortalService.profileImage.startsWith("/")) - return "file://" + PortalService.profileImage; + return "file://" + PortalService.profileImage - return PortalService.profileImage; + return PortalService.profileImage } smooth: true asynchronous: true @@ -228,7 +186,6 @@ Item { color: "black" antialiasing: true } - } Rectangle { @@ -243,7 +200,6 @@ Item { size: Theme.iconSize + 4 color: Theme.primaryText } - } DankIcon { @@ -251,9 +207,9 @@ Item { name: "warning" size: Theme.iconSize + 4 color: Theme.primaryText - visible: PortalService.profileImage !== "" && profileImageLoader.status === Image.Error + visible: PortalService.profileImage !== "" + && profileImageLoader.status === Image.Error } - } Rectangle { @@ -262,8 +218,11 @@ Item { Layout.fillWidth: true Layout.preferredHeight: 60 radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9) - border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3) + color: Qt.rgba(Theme.surfaceContainer.r, + Theme.surfaceContainer.g, + Theme.surfaceContainer.b, 0.9) + border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba( + 1, 1, 1, 0.3) border.width: passwordField.activeFocus ? 2 : 1 DankIcon { @@ -289,25 +248,26 @@ Item { echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password onTextChanged: { if (!demoMode) - root.passwordBuffer = text; - + root.passwordBuffer = text } onAccepted: { - if (!demoMode && root.passwordBuffer.length > 0 && !pam.active) { - console.log("Enter pressed, starting PAM authentication"); - pam.start(); + if (!demoMode && root.passwordBuffer.length > 0 + && !pam.active) { + console.log("Enter pressed, starting PAM authentication") + pam.start() } } - Keys.onPressed: (event) => { - if (demoMode) - return ; + Keys.onPressed: event => { + if (demoMode) + return - if (pam.active) { - console.log("PAM is active, ignoring input"); - event.accepted = true; - return ; - } - } + if (pam.active) { + console.log( + "PAM is active, ignoring input") + event.accepted = true + return + } + } Timer { id: focusTimer @@ -316,7 +276,6 @@ Item { running: !demoMode onTriggered: passwordField.forceActiveFocus() } - } StyledText { @@ -331,26 +290,26 @@ Item { anchors.verticalCenter: parent.verticalCenter text: { if (demoMode) - return ""; + return "" - if (root.unlocking) - return "Unlocking..."; + if (LockScreenService.unlocking) + return "Unlocking..." if (pam.active) - return "Authenticating..."; + return "Authenticating..." - return "hunter2"; + return "Password..." } - color: root.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline) + color: LockScreenService.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline) font.pixelSize: Theme.fontSizeMedium - opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0 + opacity: (demoMode + || root.passwordBuffer.length === 0) ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.standardEasing } - } Behavior on color { @@ -358,9 +317,7 @@ Item { duration: Theme.shortDuration easing.type: Theme.standardEasing } - } - } StyledText { @@ -371,15 +328,18 @@ Item { anchors.verticalCenter: parent.verticalCenter text: { if (demoMode) - return "••••••••"; + return "••••••••" else if (parent.showPassword) - return root.passwordBuffer; + return root.passwordBuffer else - return "•".repeat(Math.min(root.passwordBuffer.length, 25)); + return "•".repeat( + Math.min( + root.passwordBuffer.length, 25)) } color: Theme.surfaceText font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge - opacity: (demoMode || root.passwordBuffer.length > 0) ? 1 : 0 + opacity: (demoMode + || root.passwordBuffer.length > 0) ? 1 : 0 elide: Text.ElideRight Behavior on opacity { @@ -387,9 +347,7 @@ Item { duration: Theme.mediumDuration easing.type: Theme.standardEasing } - } - } DankActionButton { @@ -400,7 +358,8 @@ Item { anchors.verticalCenter: parent.verticalCenter iconName: parent.showPassword ? "visibility_off" : "visibility" buttonSize: 32 - visible: !demoMode && root.passwordBuffer.length > 0 && !pam.active && !root.unlocking + visible: !demoMode && root.passwordBuffer.length > 0 + && !pam.active && !LockScreenService.unlocking enabled: visible onClicked: parent.showPassword = !parent.showPassword } @@ -415,17 +374,18 @@ Item { height: 24 radius: 12 color: "transparent" - visible: !demoMode && (pam.active || root.unlocking) + visible: !demoMode && (pam.active + || LockScreenService.unlocking) DankIcon { anchors.centerIn: parent name: "check_circle" size: 20 color: Theme.primary - visible: root.unlocking + visible: LockScreenService.unlocking SequentialAnimation on scale { - running: root.unlocking + running: LockScreenService.unlocking NumberAnimation { from: 0 @@ -442,14 +402,12 @@ Item { easing.type: Easing.BezierSpline easing.bezierCurve: Anims.emphasizedAccel } - } - } Item { anchors.fill: parent - visible: pam.active && !root.unlocking + visible: pam.active && !LockScreenService.unlocking Rectangle { width: 20 @@ -457,7 +415,9 @@ Item { radius: 10 anchors.centerIn: parent color: "transparent" - border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) + border.color: Qt.rgba(Theme.primary.r, + Theme.primary.g, + Theme.primary.b, 0.3) border.width: 2 } @@ -475,21 +435,22 @@ Item { height: parent.height / 2 anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9) + color: Qt.rgba(Theme.surfaceContainer.r, + Theme.surfaceContainer.g, + Theme.surfaceContainer.b, + 0.9) } RotationAnimation on rotation { - running: pam.active && !root.unlocking + running: pam.active + && !LockScreenService.unlocking loops: Animation.Infinite duration: Anims.durLong from: 0 to: 360 } - } - } - } DankActionButton { @@ -500,12 +461,14 @@ Item { anchors.verticalCenter: parent.verticalCenter iconName: "keyboard_return" buttonSize: 36 - visible: (demoMode || (root.passwordBuffer.length > 0 && !pam.active && !root.unlocking)) + visible: (demoMode || (root.passwordBuffer.length > 0 + && !pam.active + && !LockScreenService.unlocking)) enabled: !demoMode onClicked: { if (!demoMode) { - console.log("Enter button clicked, starting PAM authentication"); - pam.start(); + console.log("Enter button clicked, starting PAM authentication") + pam.start() } } @@ -514,9 +477,7 @@ Item { duration: Theme.shortDuration easing.type: Theme.standardEasing } - } - } Behavior on border.color { @@ -524,40 +485,36 @@ Item { duration: Theme.shortDuration easing.type: Theme.standardEasing } - } - } - } StyledText { Layout.fillWidth: true - Layout.preferredHeight: placeholder.pamState ? 20 : 0 + Layout.preferredHeight: LockScreenService.pamState ? 20 : 0 text: { - if (placeholder.pamState === "error") - return "Authentication error - try again"; + if (LockScreenService.pamState === "error") + return "Authentication error - try again" - if (placeholder.pamState === "max") - return "Too many attempts - locked out"; + if (LockScreenService.pamState === "max") + return "Too many attempts - locked out" - if (placeholder.pamState === "fail") - return "Incorrect password - try again"; + if (LockScreenService.pamState === "fail") + return "Incorrect password - try again" - return ""; + return "" } color: Theme.error font.pixelSize: Theme.fontSizeSmall horizontalAlignment: Text.AlignHCenter - visible: placeholder.pamState !== "" - opacity: placeholder.pamState !== "" ? 1 : 0 + visible: LockScreenService.pamState !== "" + opacity: LockScreenService.pamState !== "" ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } - } Behavior on Layout.preferredHeight { @@ -565,11 +522,8 @@ Item { duration: Theme.shortDuration easing.type: Theme.standardEasing } - } - } - } StyledText { @@ -590,186 +544,224 @@ Item { anchors.margins: Theme.spacingXL spacing: Theme.spacingL - // Weather section - Row { - spacing: 6 - visible: WeatherService.weather.available + // Weather section + Row { + spacing: 6 + visible: WeatherService.weather.available + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + name: WeatherService.getWeatherIcon( + WeatherService.weather.wCode) + size: Theme.iconSize + color: "white" anchors.verticalCenter: parent.verticalCenter - - DankIcon { - name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) - size: Theme.iconSize - color: "white" - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Light - color: "white" - anchors.verticalCenter: parent.verticalCenter - } } - // Separator - Rectangle { - width: 1 - height: 24 - color: Qt.rgba(255, 255, 255, 0.2) + StyledText { + text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Light + color: "white" anchors.verticalCenter: parent.verticalCenter - visible: WeatherService.weather.available && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio) || BatteryService.batteryAvailable) - } - - // System status icons - Row { - spacing: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - visible: NetworkService.networkStatus !== "disconnected" || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio) - - // Network icon - DankIcon { - name: { - if (NetworkService.networkStatus === "ethernet") - return "lan" - else if (NetworkService.networkStatus === "wifi") { - switch (NetworkService.wifiSignalStrengthStr) { - case "excellent": - return "wifi" - case "good": - return "wifi_2_bar" - case "fair": - return "wifi_1_bar" - case "poor": - return "signal_wifi_0_bar" - default: - return "wifi" - } - } - else - return "wifi_off" - } - size: Theme.iconSize - 2 - color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5) - anchors.verticalCenter: parent.verticalCenter - visible: NetworkService.networkStatus !== "disconnected" - } - - // Bluetooth icon - DankIcon { - name: "bluetooth" - size: Theme.iconSize - 2 - color: "white" - anchors.verticalCenter: parent.verticalCenter - visible: BluetoothService.available && BluetoothService.enabled - } - - // Volume icon - DankIcon { - name: { - if (AudioService.sink && AudioService.sink.audio) { - if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) - return "volume_off" - else if (AudioService.sink.audio.volume * 100 < 33) - return "volume_down" - else - return "volume_up" - } - return "volume_up" - } - size: Theme.iconSize - 2 - color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white" - anchors.verticalCenter: parent.verticalCenter - visible: AudioService.sink && AudioService.sink.audio - } - } - - // Separator - Rectangle { - width: 1 - height: 24 - color: Qt.rgba(255, 255, 255, 0.2) - anchors.verticalCenter: parent.verticalCenter - visible: BatteryService.batteryAvailable && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio)) - } - - // Battery section - Row { - spacing: 4 - visible: BatteryService.batteryAvailable - anchors.verticalCenter: parent.verticalCenter - - DankIcon { - name: { - if (BatteryService.isCharging) { - if (BatteryService.batteryLevel >= 90) - return "battery_charging_full" - if (BatteryService.batteryLevel >= 80) - return "battery_charging_90" - if (BatteryService.batteryLevel >= 60) - return "battery_charging_80" - if (BatteryService.batteryLevel >= 50) - return "battery_charging_60" - if (BatteryService.batteryLevel >= 30) - return "battery_charging_50" - if (BatteryService.batteryLevel >= 20) - return "battery_charging_30" - return "battery_charging_20" - } - - // Check if plugged in but not charging (like at 80% charge limit) - if (BatteryService.isPluggedIn) { - if (BatteryService.batteryLevel >= 90) - return "battery_charging_full" - if (BatteryService.batteryLevel >= 80) - return "battery_charging_90" - if (BatteryService.batteryLevel >= 60) - return "battery_charging_80" - if (BatteryService.batteryLevel >= 50) - return "battery_charging_60" - if (BatteryService.batteryLevel >= 30) - return "battery_charging_50" - if (BatteryService.batteryLevel >= 20) - return "battery_charging_30" - return "battery_charging_20" - } - - // On battery power - if (BatteryService.batteryLevel >= 95) - return "battery_full" - if (BatteryService.batteryLevel >= 85) - return "battery_6_bar" - if (BatteryService.batteryLevel >= 70) - return "battery_5_bar" - if (BatteryService.batteryLevel >= 55) - return "battery_4_bar" - if (BatteryService.batteryLevel >= 40) - return "battery_3_bar" - if (BatteryService.batteryLevel >= 25) - return "battery_2_bar" - return "battery_1_bar" - } - size: Theme.iconSize - color: { - if (BatteryService.isLowBattery && !BatteryService.isCharging) - return Theme.error - if (BatteryService.isCharging || BatteryService.isPluggedIn) - return Theme.primary - return "white" - } - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: BatteryService.batteryLevel + "%" - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Light - color: "white" - anchors.verticalCenter: parent.verticalCenter - } } } + // Separator + Rectangle { + width: 1 + height: 24 + color: Qt.rgba(255, 255, 255, 0.2) + anchors.verticalCenter: parent.verticalCenter + visible: WeatherService.weather.available + && (NetworkService.networkStatus !== "disconnected" + || BluetoothService.enabled + || (AudioService.sink && AudioService.sink.audio) + || BatteryService.batteryAvailable) + } + + // System status icons + Row { + spacing: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + visible: NetworkService.networkStatus !== "disconnected" + || (BluetoothService.available + && BluetoothService.enabled) + || (AudioService.sink && AudioService.sink.audio) + + // Network icon + DankIcon { + name: { + if (NetworkService.networkStatus === "ethernet") { + return "lan" + } else if (NetworkService.networkStatus === "wifi") { + switch (NetworkService.wifiSignalStrengthStr) { + case "excellent": + return "wifi" + case "good": + return "wifi_2_bar" + case "fair": + return "wifi_1_bar" + case "poor": + return "signal_wifi_0_bar" + default: + return "wifi" + } + } else { + return "wifi_off" + } + } + size: Theme.iconSize - 2 + color: NetworkService.networkStatus + !== "disconnected" ? "white" : Qt.rgba(255, + 255, 255, 0.5) + anchors.verticalCenter: parent.verticalCenter + visible: NetworkService.networkStatus !== "disconnected" + } + + // Bluetooth icon + DankIcon { + name: "bluetooth" + size: Theme.iconSize - 2 + color: "white" + anchors.verticalCenter: parent.verticalCenter + visible: BluetoothService.available + && BluetoothService.enabled + } + + // Volume icon + DankIcon { + name: { + if (AudioService.sink && AudioService.sink.audio) { + if (AudioService.sink.audio.muted + || AudioService.sink.audio.volume === 0) + return "volume_off" + else if (AudioService.sink.audio.volume * 100 < 33) + return "volume_down" + else + return "volume_up" + } + return "volume_up" + } + size: Theme.iconSize - 2 + color: (AudioService.sink && AudioService.sink.audio + && (AudioService.sink.audio.muted + || AudioService.sink.audio.volume + === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white" + anchors.verticalCenter: parent.verticalCenter + visible: AudioService.sink && AudioService.sink.audio + } + } + + // Separator + Rectangle { + width: 1 + height: 24 + color: Qt.rgba(255, 255, 255, 0.2) + anchors.verticalCenter: parent.verticalCenter + visible: BatteryService.batteryAvailable + && (NetworkService.networkStatus !== "disconnected" + || BluetoothService.enabled + || (AudioService.sink && AudioService.sink.audio)) + } + + // Battery section + Row { + spacing: 4 + visible: BatteryService.batteryAvailable + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + name: { + if (BatteryService.isCharging) { + if (BatteryService.batteryLevel >= 90) + return "battery_charging_full" + + if (BatteryService.batteryLevel >= 80) + return "battery_charging_90" + + if (BatteryService.batteryLevel >= 60) + return "battery_charging_80" + + if (BatteryService.batteryLevel >= 50) + return "battery_charging_60" + + if (BatteryService.batteryLevel >= 30) + return "battery_charging_50" + + if (BatteryService.batteryLevel >= 20) + return "battery_charging_30" + + return "battery_charging_20" + } + // Check if plugged in but not charging (like at 80% charge limit) + if (BatteryService.isPluggedIn) { + if (BatteryService.batteryLevel >= 90) + return "battery_charging_full" + + if (BatteryService.batteryLevel >= 80) + return "battery_charging_90" + + if (BatteryService.batteryLevel >= 60) + return "battery_charging_80" + + if (BatteryService.batteryLevel >= 50) + return "battery_charging_60" + + if (BatteryService.batteryLevel >= 30) + return "battery_charging_50" + + if (BatteryService.batteryLevel >= 20) + return "battery_charging_30" + + return "battery_charging_20" + } + // On battery power + if (BatteryService.batteryLevel >= 95) + return "battery_full" + + if (BatteryService.batteryLevel >= 85) + return "battery_6_bar" + + if (BatteryService.batteryLevel >= 70) + return "battery_5_bar" + + if (BatteryService.batteryLevel >= 55) + return "battery_4_bar" + + if (BatteryService.batteryLevel >= 40) + return "battery_3_bar" + + if (BatteryService.batteryLevel >= 25) + return "battery_2_bar" + + return "battery_1_bar" + } + size: Theme.iconSize + color: { + if (BatteryService.isLowBattery + && !BatteryService.isCharging) + return Theme.error + + if (BatteryService.isCharging + || BatteryService.isPluggedIn) + return Theme.primary + + return "white" + } + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: BatteryService.batteryLevel + "%" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Light + color: "white" + anchors.verticalCenter: parent.verticalCenter + } + } + } + Row { anchors.bottom: parent.bottom anchors.left: parent.left @@ -782,9 +774,9 @@ Item { buttonSize: 40 onClicked: { if (demoMode) - console.log("Demo: Power"); + console.log("Demo: Power") else - powerDialog.open(); + LockScreenService.showPowerDialog() } } @@ -793,9 +785,9 @@ Item { buttonSize: 40 onClicked: { if (demoMode) - console.log("Demo: Reboot"); + console.log("Demo: Reboot") else - rebootDialog.open(); + LockScreenService.showRebootDialog() } } @@ -804,12 +796,11 @@ Item { buttonSize: 40 onClicked: { if (demoMode) - console.log("Demo: Logout"); + console.log("Demo: Logout") else - logoutDialog.open(); + LockScreenService.showLogoutDialog() } } - } StyledText { @@ -817,15 +808,14 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.margins: Theme.spacingL width: Math.min(parent.width - Theme.spacingXL * 2, implicitWidth) - text: randomFact + text: LockScreenService.randomFact font.pixelSize: Theme.fontSizeSmall color: "white" opacity: 0.8 horizontalAlignment: Text.AlignHCenter wrapMode: Text.NoWrap - visible: randomFact !== "" + visible: LockScreenService.randomFact !== "" } - } FileView { @@ -841,44 +831,46 @@ Item { config: pamConfigWatcher.loaded ? "dankshell" : "login" onResponseRequiredChanged: { if (demoMode) - return ; + return - console.log("PAM response required:", responseRequired); + console.log("PAM response required:", responseRequired) if (!responseRequired) - return ; + return - console.log("Responding to PAM with password buffer length:", root.passwordBuffer.length); - respond(root.passwordBuffer); + console.log("Responding to PAM with password buffer length:", + root.passwordBuffer.length) + respond(root.passwordBuffer) } - onCompleted: (res) => { - if (demoMode) - return ; + onCompleted: res => { + if (demoMode) + return - console.log("PAM authentication completed with result:", res); - if (res === PamResult.Success) { - console.log("Authentication successful, unlocking"); - root.unlocking = true; - passwordField.text = ""; - root.passwordBuffer = ""; - root.unlockRequested(); - return ; - } - console.log("Authentication failed:", res); - if (res === PamResult.Error) - placeholder.pamState = "error"; - else if (res === PamResult.MaxTries) - placeholder.pamState = "max"; - else if (res === PamResult.Failed) - placeholder.pamState = "fail"; - placeholderDelay.restart(); - } + console.log( + "PAM authentication completed with result:", res) + if (res === PamResult.Success) { + console.log("Authentication successful, unlocking") + LockScreenService.setUnlocking(true) + passwordField.text = "" + root.passwordBuffer = "" + root.unlockRequested() + return + } + console.log("Authentication failed:", res) + if (res === PamResult.Error) + LockScreenService.setPamState("error") + else if (res === PamResult.MaxTries) + LockScreenService.setPamState("max") + else if (res === PamResult.Failed) + LockScreenService.setPamState("fail") + placeholderDelay.restart() + } } Timer { id: placeholderDelay interval: 4000 - onTriggered: placeholder.pamState = "" + onTriggered: LockScreenService.setPamState("") } MouseArea { @@ -891,16 +883,16 @@ Item { id: powerDialog function open() { - visible = true; + LockScreenService.showPowerDialog() } function close() { - visible = false; + LockScreenService.hidePowerDialog() } anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.8) - visible: false + visible: LockScreenService.powerDialogVisible z: 1000 Rectangle { @@ -939,7 +931,11 @@ Item { width: 100 height: 40 radius: Theme.cornerRadius - color: cancelMouse1.pressed ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.7) : cancelMouse1.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant + color: cancelMouse1.pressed ? Qt.rgba( + Theme.surfaceVariant.r, + Theme.surfaceVariant.g, + Theme.surfaceVariant.b, + 0.7) : cancelMouse1.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant StyledText { anchors.centerIn: parent @@ -956,14 +952,17 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: powerDialog.close() } - } Rectangle { width: 100 height: 40 radius: Theme.cornerRadius - color: powerMouse.pressed ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8) : powerMouse.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 1) : Theme.error + color: powerMouse.pressed ? Qt.rgba( + Theme.error.r, + Theme.error.g, + Theme.error.b, + 0.8) : powerMouse.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 1) : Theme.error StyledText { anchors.centerIn: parent @@ -980,35 +979,31 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - powerDialog.close(); - Quickshell.execDetached(["systemctl", "poweroff"]); + powerDialog.close() + Quickshell.execDetached( + ["systemctl", "poweroff"]) } } - } - } - } - } - } Rectangle { id: rebootDialog function open() { - visible = true; + LockScreenService.showRebootDialog() } function close() { - visible = false; + LockScreenService.hideRebootDialog() } anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.8) - visible: false + visible: LockScreenService.rebootDialogVisible z: 1000 Rectangle { @@ -1047,7 +1042,11 @@ Item { width: 100 height: 40 radius: Theme.cornerRadius - color: cancelMouse2.pressed ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.7) : cancelMouse2.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant + color: cancelMouse2.pressed ? Qt.rgba( + Theme.surfaceVariant.r, + Theme.surfaceVariant.g, + Theme.surfaceVariant.b, + 0.7) : cancelMouse2.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant StyledText { anchors.centerIn: parent @@ -1064,14 +1063,17 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: rebootDialog.close() } - } Rectangle { width: 100 height: 40 radius: Theme.cornerRadius - color: rebootMouse.pressed ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : rebootMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary + color: rebootMouse.pressed ? Qt.rgba( + Theme.primary.r, + Theme.primary.g, + Theme.primary.b, + 0.8) : rebootMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary StyledText { anchors.centerIn: parent @@ -1088,35 +1090,30 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - rebootDialog.close(); - Quickshell.execDetached(["systemctl", "reboot"]); + rebootDialog.close() + Quickshell.execDetached(["systemctl", "reboot"]) } } - } - } - } - } - } Rectangle { id: logoutDialog function open() { - visible = true; + LockScreenService.showLogoutDialog() } function close() { - visible = false; + LockScreenService.hideLogoutDialog() } anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.8) - visible: false + visible: LockScreenService.logoutDialogVisible z: 1000 Rectangle { @@ -1155,7 +1152,11 @@ Item { width: 100 height: 40 radius: Theme.cornerRadius - color: cancelMouse3.pressed ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.7) : cancelMouse3.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant + color: cancelMouse3.pressed ? Qt.rgba( + Theme.surfaceVariant.r, + Theme.surfaceVariant.g, + Theme.surfaceVariant.b, + 0.7) : cancelMouse3.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant StyledText { anchors.centerIn: parent @@ -1172,14 +1173,17 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: logoutDialog.close() } - } Rectangle { width: 100 height: 40 radius: Theme.cornerRadius - color: logoutMouse.pressed ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8) : logoutMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary + color: logoutMouse.pressed ? Qt.rgba( + Theme.primary.r, + Theme.primary.g, + Theme.primary.b, + 0.8) : logoutMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary StyledText { anchors.centerIn: parent @@ -1196,19 +1200,13 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - logoutDialog.close(); - NiriService.quit(); + logoutDialog.close() + NiriService.quit() } } - } - } - } - } - } - } diff --git a/Services/LockScreenService.qml b/Services/LockScreenService.qml new file mode 100644 index 00000000..db0cfa67 --- /dev/null +++ b/Services/LockScreenService.qml @@ -0,0 +1,105 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +Singleton { + id: root + + property bool unlocking: false + property string randomFact: "" + property string pamState: "" + property bool powerDialogVisible: false + property bool rebootDialogVisible: false + property bool logoutDialogVisible: false + + property var facts: [ + "A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.", + "A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.", + "Right now, 100 trillion solar neutrinos are passing through your body every second.", + "The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.", + "The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.", + "There's a nebula out there that's actually colder than empty space itself.", + "We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.", + "Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.", + "Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.", + "Distant galaxies can move away from us faster than light because space itself is stretching.", + "The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.", + "The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.", + "A day on Venus lasts longer than its entire year around the Sun.", + "On Mercury, the time between sunrises is 176 Earth days long.", + "In about 4.5 billion years, our galaxy will smash into Andromeda.", + "Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.", + "PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.", + "Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.", + "Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.", + "Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.", + "Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.", + "Counting to a billion at one number per second would take over 31 years.", + "Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.", + "Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.", + "Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.", + "Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.", + "Even at light-speed, you'd never catch up to most galaxies—space expands faster.", + "Only around 5% of galaxies are ever reachable—even at light-speed.", + "If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.", + "If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.", + "Our oldest radio signals will reach the Milky Way's center in 26,000 years.", + "Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.", + "The Moon moves 3.8 centimeters farther from Earth every year.", + "The universe creates 275 million new stars every single day.", + "Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.", + "If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.", + "The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have." + ] + + function pickRandomFact() { + randomFact = facts[Math.floor(Math.random() * facts.length)]; + } + + function resetState() { + unlocking = false; + pamState = ""; + powerDialogVisible = false; + rebootDialogVisible = false; + logoutDialogVisible = false; + pickRandomFact(); + } + + function setPamState(state) { + pamState = state; + } + + function setUnlocking(value) { + unlocking = value; + } + + function showPowerDialog() { + powerDialogVisible = true; + } + + function hidePowerDialog() { + powerDialogVisible = false; + } + + function showRebootDialog() { + rebootDialogVisible = true; + } + + function hideRebootDialog() { + rebootDialogVisible = false; + } + + function showLogoutDialog() { + logoutDialogVisible = true; + } + + function hideLogoutDialog() { + logoutDialogVisible = false; + } + + Component.onCompleted: { + pickRandomFact(); + } +} \ No newline at end of file