summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml1707
1 files changed, 1707 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml b/raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml
new file mode 100644
index 0000000..b296e98
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/Lock/LockScreenContent.qml
@@ -0,0 +1,1707 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Layouts
+import QtQuick.Window
+import Quickshell
+import Quickshell.Hyprland
+import Quickshell.Io
+import Quickshell.Services.Mpris
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ function encodeFileUrl(path) {
+ if (!path)
+ return "";
+ return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
+ }
+
+ property string passwordBuffer: ""
+ property bool demoMode: false
+ property string screenName: ""
+ property bool unlocking: false
+ property string pamState: ""
+ property string hyprlandCurrentLayout: ""
+ property string hyprlandKeyboard: ""
+ property int hyprlandLayoutCount: 0
+ property bool lockerReadySent: false
+ property bool lockerReadyArmed: false
+
+ signal unlockRequested
+
+ function resetLockState() {
+ lockerReadySent = false;
+ lockerReadyArmed = true;
+ unlocking = false;
+ pamState = "";
+ if (pam)
+ pam.lockMessage = "";
+ }
+
+ function currentAuthFeedbackText() {
+ if (!pam)
+ return "";
+ if (pam.u2fState === "insert" && !pam.u2fPending)
+ return I18n.tr("Insert your security key...");
+ if (pam.u2fState === "waiting" && !pam.u2fPending)
+ return I18n.tr("Touch your security key...");
+ if (pam.lockMessage && pam.lockMessage.length > 0)
+ return pam.lockMessage;
+ if (root.pamState === "error")
+ return I18n.tr("Authentication error - try again");
+ if (root.pamState === "max")
+ return I18n.tr("Too many attempts - locked out");
+ if (root.pamState === "fail")
+ return I18n.tr("Incorrect password - try again");
+ if (pam.fprintState === "error") {
+ const detail = (pam.fprint.message || "").trim();
+ return detail.length > 0 ? I18n.tr("Fingerprint error: %1").arg(detail) : I18n.tr("Fingerprint error");
+ }
+ if (pam.fprintState === "max")
+ return I18n.tr("Maximum fingerprint attempts reached. Please use password.");
+ if (pam.fprintState === "fail")
+ return I18n.tr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(SettingsData.maxFprintTries);
+ return "";
+ }
+
+ function authFeedbackIsHint() {
+ return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending;
+ }
+
+ Component.onCompleted: {
+ WeatherService.addRef();
+ UserInfoService.getUserInfo();
+
+ if (CompositorService.isHyprland)
+ updateHyprlandLayout();
+
+ lockerReadyArmed = true;
+ }
+
+ Component.onDestruction: {
+ WeatherService.removeRef();
+ }
+
+ function sendLockerReadyOnce() {
+ if (lockerReadySent)
+ return;
+ if (root.unlocking)
+ return;
+ lockerReadySent = true;
+ if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
+ DMSService.sendRequest("loginctl.lockerReady", null, resp => {
+ if (resp?.error)
+ console.warn("lockerReady failed:", resp.error);
+ else
+ console.log("lockerReady sent (afterAnimating/afterRendering)");
+ });
+ }
+ }
+
+ function maybeSend() {
+ if (!lockerReadyArmed)
+ return;
+ if (root.unlocking)
+ return;
+ if (!root.visible || root.opacity <= 0)
+ return;
+ Qt.callLater(() => {
+ if (root.visible && root.opacity > 0 && !root.unlocking)
+ sendLockerReadyOnce();
+ });
+ }
+
+ Connections {
+ target: root.Window.window
+ enabled: target !== null
+
+ function onAfterAnimating() {
+ maybeSend();
+ }
+ function onAfterRendering() {
+ maybeSend();
+ }
+ }
+
+ onVisibleChanged: maybeSend()
+ onOpacityChanged: maybeSend()
+
+ function updateHyprlandLayout() {
+ if (CompositorService.isHyprland) {
+ hyprlandLayoutProcess.running = true;
+ }
+ }
+
+ Process {
+ id: hyprlandLayoutProcess
+ running: false
+ command: ["hyprctl", "-j", "devices"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ try {
+ const data = JSON.parse(text);
+ const mainKeyboard = data.keyboards.find(kb => kb.main === true);
+ if (!mainKeyboard) {
+ hyprlandCurrentLayout = "";
+ hyprlandLayoutCount = 0;
+ return;
+ }
+ hyprlandKeyboard = mainKeyboard.name;
+ if (mainKeyboard.active_keymap) {
+ const parts = mainKeyboard.active_keymap.split(" ");
+ hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase();
+ } else {
+ hyprlandCurrentLayout = "";
+ }
+ hyprlandLayoutCount = mainKeyboard.layout ? mainKeyboard.layout.split(",").length : 0;
+ } catch (e) {
+ hyprlandCurrentLayout = "";
+ hyprlandLayoutCount = 0;
+ }
+ }
+ }
+ }
+
+ Connections {
+ target: CompositorService.isHyprland ? Hyprland : null
+ enabled: CompositorService.isHyprland
+
+ function onRawEvent(event) {
+ if (event.name === "activelayout")
+ updateHyprlandLayout();
+ }
+ }
+
+ Loader {
+ anchors.fill: parent
+ active: {
+ var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
+ return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#"));
+ }
+ asynchronous: true
+
+ sourceComponent: DankBackdrop {
+ screenName: root.screenName
+ }
+ }
+
+ Image {
+ id: wallpaperBackground
+
+ anchors.fill: parent
+ source: {
+ var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
+ return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
+ }
+ fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(screenName))
+ smooth: true
+ asynchronous: false
+ cache: true
+ visible: source !== ""
+ layer.enabled: true
+
+ layer.effect: MultiEffect {
+ autoPaddingEnabled: false
+ blurEnabled: true
+ blur: 0.8
+ blurMax: 32
+ blurMultiplier: 1
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: "black"
+ opacity: 0.4
+ }
+
+ SystemClock {
+ id: systemClock
+
+ precision: SystemClock.Seconds
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+
+ Item {
+ id: clockContainer
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.verticalCenter
+ anchors.bottomMargin: 60
+ width: parent.width
+ height: clockText.implicitHeight
+ visible: SettingsData.lockScreenShowTime
+
+ Row {
+ id: clockText
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ spacing: 0
+
+ property string fullTimeStr: {
+ const format = SettingsData.getEffectiveTimeFormat();
+ return systemClock.date.toLocaleTimeString(Qt.locale(), format);
+ }
+ property var timeParts: fullTimeStr.split(':')
+ property string hours: timeParts[0] || ""
+ property string minutes: timeParts[1] || ""
+ property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
+ property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
+ property string ampm: {
+ const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
+ return match ? match[0].trim() : "";
+ }
+ property bool hasSeconds: timeParts.length > 2
+
+ StyledText {
+ width: 75
+ text: clockText.hours.length > 1 ? clockText.hours[0] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ StyledText {
+ width: 75
+ text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ StyledText {
+ text: ":"
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ }
+
+ StyledText {
+ width: 75
+ text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ StyledText {
+ width: 75
+ text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ StyledText {
+ text: clockText.hasSeconds ? ":" : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ visible: clockText.hasSeconds
+ }
+
+ StyledText {
+ width: 75
+ text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ visible: clockText.hasSeconds
+ }
+
+ StyledText {
+ width: 75
+ text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ visible: clockText.hasSeconds
+ }
+
+ StyledText {
+ width: 20
+ text: " "
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ visible: clockText.ampm !== ""
+ }
+
+ StyledText {
+ text: clockText.ampm
+ font.pixelSize: 120
+ font.weight: Font.Light
+ color: "white"
+ visible: clockText.ampm !== ""
+ }
+ }
+ }
+
+ StyledText {
+ id: dateText
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: clockContainer.bottom
+ anchors.topMargin: 4
+ visible: SettingsData.lockScreenShowDate
+ text: {
+ if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
+ return systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.lockDateFormat);
+ }
+ return systemClock.date.toLocaleDateString(I18n.locale(), Locale.LongFormat);
+ }
+ font.pixelSize: Theme.fontSizeXLarge
+ color: "white"
+ opacity: 0.9
+ }
+
+ Item {
+ id: lockNotificationPanel
+
+ readonly property int notificationMode: SettingsData.lockScreenNotificationMode
+ readonly property var notifications: NotificationService.groupedNotifications
+ readonly property int totalCount: {
+ let count = 0;
+ for (const group of notifications) {
+ count += group.count || 0;
+ }
+ return count;
+ }
+ readonly property bool hasNotifications: totalCount > 0
+ readonly property var appNameGroups: {
+ const groups = {};
+ for (const group of notifications) {
+ const appName = (group.appName || "Unknown").toLowerCase();
+ if (!groups[appName]) {
+ groups[appName] = {
+ appName: group.appName || I18n.tr("Unknown"),
+ count: 0,
+ latestNotification: group.latestNotification
+ };
+ }
+ groups[appName].count += group.count || 0;
+ if (group.latestNotification && (!groups[appName].latestNotification || group.latestNotification.time > groups[appName].latestNotification.time)) {
+ groups[appName].latestNotification = group.latestNotification;
+ }
+ }
+ return Object.values(groups).sort((a, b) => b.count - a.count);
+ }
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: dateText.visible ? dateText.bottom : clockContainer.bottom
+ anchors.topMargin: Theme.spacingM
+ width: Math.min(380, parent.width - Theme.spacingXL * 2)
+ height: notificationMode === 0 || !hasNotifications ? 0 : contentLoader.height
+ visible: notificationMode > 0 && hasNotifications
+ clip: true
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+
+ Loader {
+ id: contentLoader
+ anchors.left: parent.left
+ anchors.right: parent.right
+ active: lockNotificationPanel.notificationMode > 0 && lockNotificationPanel.hasNotifications
+ sourceComponent: {
+ switch (lockNotificationPanel.notificationMode) {
+ case 1:
+ return countOnlyComponent;
+ case 2:
+ return appNamesComponent;
+ case 3:
+ return fullContentComponent;
+ default:
+ return null;
+ }
+ }
+ }
+
+ Component {
+ id: countOnlyComponent
+
+ Rectangle {
+ width: parent.width
+ height: 44
+ radius: Theme.cornerRadius
+ color: Qt.rgba(0, 0, 0, 0.3)
+ border.color: Qt.rgba(1, 1, 1, 0.1)
+ border.width: 1
+
+ Row {
+ anchors.centerIn: parent
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "notifications"
+ size: Theme.iconSize
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: lockNotificationPanel.totalCount === 1 ? I18n.tr("1 notification") : I18n.tr("%1 notifications").arg(lockNotificationPanel.totalCount)
+ font.pixelSize: Theme.fontSizeMedium
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
+
+ Component {
+ id: appNamesComponent
+
+ Rectangle {
+ width: parent.width
+ height: Math.min(appNamesColumn.implicitHeight + Theme.spacingM * 2, 200)
+ radius: Theme.cornerRadius
+ color: Qt.rgba(0, 0, 0, 0.3)
+ border.color: Qt.rgba(1, 1, 1, 0.1)
+ border.width: 1
+ clip: true
+
+ Flickable {
+ anchors.fill: parent
+ anchors.margins: Theme.spacingM
+ contentHeight: appNamesColumn.implicitHeight
+ clip: true
+ boundsBehavior: Flickable.StopAtBounds
+
+ Column {
+ id: appNamesColumn
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: lockNotificationPanel.appNameGroups.slice(0, 5)
+
+ Row {
+ required property var modelData
+ width: parent.width
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: "notifications"
+ size: Theme.iconSize - 4
+ color: "white"
+ opacity: 0.8
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: modelData.appName || I18n.tr("Unknown")
+ font.pixelSize: Theme.fontSizeMedium
+ color: "white"
+ elide: Text.ElideRight
+ width: parent.width - Theme.iconSize - countBadge.width - Theme.spacingS * 2
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ id: countBadge
+ width: countText.implicitWidth + Theme.spacingS * 2
+ height: 20
+ radius: 10
+ color: Qt.rgba(1, 1, 1, 0.2)
+ visible: modelData.count > 1
+ anchors.verticalCenter: parent.verticalCenter
+
+ StyledText {
+ id: countText
+ anchors.centerIn: parent
+ text: modelData.count > 99 ? "99+" : modelData.count.toString()
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ }
+ }
+ }
+ }
+
+ StyledText {
+ visible: lockNotificationPanel.appNameGroups.length > 5
+ text: I18n.tr("+ %1 more").arg(lockNotificationPanel.appNameGroups.length - 5)
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ opacity: 0.7
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: fullContentComponent
+
+ Rectangle {
+ width: parent.width
+ height: Math.min(fullContentColumn.implicitHeight + Theme.spacingM * 2, 280)
+ radius: Theme.cornerRadius
+ color: Qt.rgba(0, 0, 0, 0.3)
+ border.color: Qt.rgba(1, 1, 1, 0.1)
+ border.width: 1
+ clip: true
+
+ Flickable {
+ anchors.fill: parent
+ anchors.margins: Theme.spacingM
+ contentHeight: fullContentColumn.implicitHeight
+ clip: true
+ boundsBehavior: Flickable.StopAtBounds
+
+ Column {
+ id: fullContentColumn
+ width: parent.width
+ spacing: Theme.spacingM
+
+ Repeater {
+ model: {
+ const items = [];
+ for (const group of lockNotificationPanel.appNameGroups) {
+ if (group.latestNotification && items.length < 5) {
+ items.push(group.latestNotification);
+ }
+ }
+ return items;
+ }
+
+ Rectangle {
+ required property var modelData
+ required property int index
+ width: parent.width
+ height: notifContent.implicitHeight + Theme.spacingS * 2
+ radius: Theme.cornerRadius - 4
+ color: Qt.rgba(1, 1, 1, 0.05)
+
+ Column {
+ id: notifContent
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: Theme.spacingS
+ spacing: 2
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingXS
+
+ StyledText {
+ text: modelData.appName || I18n.tr("Unknown")
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: "white"
+ opacity: 0.7
+ elide: Text.ElideRight
+ width: parent.width - timeText.implicitWidth - Theme.spacingXS
+ }
+
+ StyledText {
+ id: timeText
+ text: modelData.timeStr || ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ opacity: 0.5
+ }
+ }
+
+ StyledText {
+ width: parent.width
+ text: modelData.summary || ""
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: "white"
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ visible: text.length > 0
+ }
+
+ StyledText {
+ width: parent.width
+ text: {
+ const body = modelData.body || "";
+ return body.replace(/<[^>]*>/g, '').replace(/\n/g, ' ');
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ opacity: 0.8
+ elide: Text.ElideRight
+ maximumLineCount: 2
+ wrapMode: Text.WordWrap
+ visible: text.length > 0
+ }
+ }
+ }
+ }
+
+ StyledText {
+ visible: lockNotificationPanel.appNameGroups.length > 5
+ text: I18n.tr("+ %1 more").arg(lockNotificationPanel.appNameGroups.length - 5)
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ opacity: 0.7
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: passwordLayout
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: lockNotificationPanel.visible ? lockNotificationPanel.bottom : (dateText.visible ? dateText.bottom : clockContainer.bottom)
+ anchors.topMargin: Theme.spacingL
+ spacing: Theme.spacingM
+ width: 380
+
+ RowLayout {
+ spacing: Theme.spacingL
+ Layout.fillWidth: true
+
+ DankCircularImage {
+ Layout.preferredWidth: 60
+ Layout.preferredHeight: 60
+ imageSource: {
+ if (PortalService.profileImage === "")
+ return "";
+ if (PortalService.profileImage.startsWith("/"))
+ return encodeFileUrl(PortalService.profileImage);
+ return PortalService.profileImage;
+ }
+ fallbackIcon: "person"
+ visible: SettingsData.lockScreenShowProfileImage
+ }
+
+ Rectangle {
+ property bool showPassword: false
+
+ 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)
+ border.width: passwordField.activeFocus ? 2 : 1
+ visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
+
+ Item {
+ id: lockIconContainer
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ width: 20
+ height: 20
+
+ DankIcon {
+ id: lockIcon
+
+ anchors.centerIn: parent
+ name: {
+ if (pam.u2fPending)
+ return "passkey";
+ if (pam.fprint.tries >= SettingsData.maxFprintTries)
+ return "fingerprint_off";
+ if (pam.fprint.active)
+ return "fingerprint";
+ if (pam.u2f.active)
+ return "passkey";
+ return "lock";
+ }
+ size: 20
+ color: {
+ if (pam.fprint.tries >= SettingsData.maxFprintTries)
+ return Theme.error;
+ if (pam.u2fState !== "")
+ return Theme.tertiary;
+ return passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText;
+ }
+ opacity: pam.passwd.active ? 0 : 1
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+ }
+
+ TextInput {
+ id: passwordField
+
+ anchors.fill: parent
+ anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
+ anchors.rightMargin: {
+ let margin = Theme.spacingM;
+ if (loadingSpinner.visible) {
+ margin += loadingSpinner.width;
+ }
+ if (enterButton.visible) {
+ margin += enterButton.width + 2;
+ }
+ if (virtualKeyboardButton.visible) {
+ margin += virtualKeyboardButton.width;
+ }
+ if (revealButton.visible) {
+ margin += revealButton.width;
+ }
+ return margin;
+ }
+ opacity: 0
+ focus: true
+ enabled: !demoMode
+ activeFocusOnTab: !demoMode
+ echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
+ onTextChanged: {
+ if (!demoMode) {
+ root.passwordBuffer = text;
+ }
+ }
+ onAccepted: {
+ if (!demoMode && !root.unlocking && !pam.passwd.active && !pam.u2fPending) {
+ pam.passwd.start();
+ }
+ }
+ Keys.onPressed: event => {
+ if (demoMode) {
+ return;
+ }
+
+ if (root.unlocking) {
+ event.accepted = true;
+ return;
+ }
+
+ if (event.key === Qt.Key_Escape) {
+ if (pam.u2fPending) {
+ pam.cancelU2fPending();
+ event.accepted = true;
+ return;
+ }
+ clear();
+ }
+
+ if (pam.passwd.active) {
+ console.log("PAM is active, ignoring input");
+ event.accepted = true;
+ return;
+ }
+ }
+
+ Component.onCompleted: {
+ if (!demoMode) {
+ forceActiveFocus();
+ }
+ }
+
+ onVisibleChanged: {
+ if (visible && !demoMode) {
+ forceActiveFocus();
+ }
+ }
+
+ onActiveFocusChanged: {
+ if (!activeFocus && !demoMode && visible && passwordField && !powerMenu.isVisible) {
+ Qt.callLater(() => {
+ if (passwordField && passwordField.forceActiveFocus) {
+ passwordField.forceActiveFocus();
+ }
+ });
+ }
+ }
+
+ onEnabledChanged: {
+ if (enabled && !demoMode && visible && passwordField && !powerMenu.isVisible) {
+ Qt.callLater(() => {
+ if (passwordField && passwordField.forceActiveFocus) {
+ passwordField.forceActiveFocus();
+ }
+ });
+ }
+ }
+ }
+
+ KeyboardController {
+ id: keyboardController
+ target: passwordField
+ rootObject: root
+ }
+
+ StyledText {
+ id: placeholder
+
+ anchors.left: lockIconContainer.right
+ anchors.leftMargin: Theme.spacingM
+ anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))
+ anchors.rightMargin: 2
+ anchors.verticalCenter: parent.verticalCenter
+ text: {
+ if (demoMode) {
+ return "";
+ }
+ if (root.unlocking) {
+ return "Unlocking...";
+ }
+ if (pam.u2fPending) {
+ if (pam.u2fState === "insert")
+ return "Insert your security key...";
+ return "Touch your security key...";
+ }
+ if (pam.passwd.active) {
+ return "Authenticating...";
+ }
+ return "Password...";
+ }
+ color: root.unlocking ? Theme.primary : (pam.passwd.active ? Theme.primary : Theme.outline)
+ font.pixelSize: Theme.fontSizeMedium
+ opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+
+ StyledText {
+ anchors.left: lockIconContainer.right
+ anchors.leftMargin: Theme.spacingM
+ anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))
+ anchors.rightMargin: 2
+ anchors.verticalCenter: parent.verticalCenter
+ text: {
+ if (demoMode) {
+ return "••••••••";
+ }
+ if (parent.showPassword) {
+ return root.passwordBuffer;
+ }
+ return "•".repeat(root.passwordBuffer.length);
+ }
+ color: Theme.surfaceText
+ font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge
+ opacity: (demoMode || root.passwordBuffer.length > 0) ? 1 : 0
+ clip: true
+ elide: Text.ElideNone
+ horizontalAlignment: implicitWidth > width ? Text.AlignRight : Text.AlignLeft
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+
+ DankActionButton {
+ id: revealButton
+
+ anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))
+ anchors.rightMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: parent.showPassword ? "visibility_off" : "visibility"
+ buttonSize: 32
+ visible: !demoMode && root.passwordBuffer.length > 0 && !pam.passwd.active && !root.unlocking
+ enabled: visible
+ onClicked: parent.showPassword = !parent.showPassword
+ }
+ DankActionButton {
+ id: virtualKeyboardButton
+
+ anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)
+ anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "keyboard"
+ buttonSize: 32
+ visible: !demoMode && !pam.passwd.active && !root.unlocking && !pam.u2fPending
+ enabled: visible
+ onClicked: {
+ if (keyboardController.isKeyboardActive) {
+ keyboardController.hide();
+ } else {
+ keyboardController.show();
+ }
+ }
+ }
+
+ Rectangle {
+ id: loadingSpinner
+
+ anchors.right: enterButton.visible ? enterButton.left : parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ width: 24
+ height: 24
+ radius: 12
+ color: "transparent"
+ visible: !demoMode && (pam.passwd.active || root.unlocking)
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "check_circle"
+ size: 20
+ color: Theme.primary
+ visible: root.unlocking
+
+ SequentialAnimation on scale {
+ running: root.unlocking
+
+ NumberAnimation {
+ from: 0
+ to: 1.2
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.emphasizedDecel
+ }
+
+ NumberAnimation {
+ from: 1.2
+ to: 1
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.emphasizedAccel
+ }
+ }
+ }
+
+ Item {
+ anchors.fill: parent
+ visible: pam.passwd.active && !root.unlocking
+
+ Rectangle {
+ width: 20
+ height: 20
+ radius: 10
+ anchors.centerIn: parent
+ color: "transparent"
+ border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
+ border.width: 2
+ }
+
+ Rectangle {
+ width: 20
+ height: 20
+ radius: 10
+ anchors.centerIn: parent
+ color: "transparent"
+ border.color: Theme.primary
+ border.width: 2
+
+ Rectangle {
+ width: parent.width
+ 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)
+ }
+
+ RotationAnimation on rotation {
+ running: pam.passwd.active && !root.unlocking
+ loops: Animation.Infinite
+ duration: Anims.durLong
+ from: 0
+ to: 360
+ }
+ }
+ }
+ }
+
+ DankActionButton {
+ id: enterButton
+
+ anchors.right: parent.right
+ anchors.rightMargin: 2
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "keyboard_return"
+ buttonSize: 36
+ visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
+ enabled: !demoMode
+ onClicked: {
+ if (!demoMode && !root.unlocking && !pam.u2fPending) {
+ pam.passwd.start();
+ }
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+ }
+
+ StyledText {
+ id: authFeedbackText
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: text.length > 0 ? Math.min(implicitHeight, Math.ceil(Theme.fontSizeSmall * 4.5)) : 0
+ text: root.currentAuthFeedbackText()
+ color: root.authFeedbackIsHint() ? Theme.outline : Theme.error
+ font.pixelSize: Theme.fontSizeSmall
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ maximumLineCount: 3
+ elide: Text.ElideRight
+ opacity: text.length > 0 ? 1 : 0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+ }
+
+ Row {
+ anchors.top: passwordLayout.bottom
+ anchors.topMargin: Theme.spacingS
+ anchors.horizontalCenter: passwordLayout.horizontalCenter
+ spacing: 4
+ opacity: DMSService.capsLockState ? 1 : 0
+
+ DankIcon {
+ name: "shift_lock"
+ size: 14
+ color: Theme.error
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: I18n.tr("Caps Lock is on")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.error
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+
+ StyledText {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: Theme.spacingXL
+ text: I18n.tr("DEMO MODE - Click anywhere to exit")
+ font.pixelSize: Theme.fontSizeSmall
+ color: "white"
+ opacity: 0.7
+ visible: demoMode
+ }
+
+ Row {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: Theme.spacingXL
+ spacing: Theme.spacingL
+ visible: SettingsData.lockScreenShowSystemIcons
+
+ Item {
+ width: keyboardLayoutRow.width
+ height: keyboardLayoutRow.height
+ anchors.verticalCenter: parent.verticalCenter
+ visible: {
+ if (CompositorService.isNiri) {
+ return NiriService.keyboardLayoutNames.length > 1;
+ } else if (CompositorService.isHyprland) {
+ return hyprlandLayoutCount > 1;
+ }
+ return false;
+ }
+
+ Row {
+ id: keyboardLayoutRow
+ spacing: 4
+
+ Item {
+ width: Theme.iconSize
+ height: Theme.iconSize
+
+ DankIcon {
+ name: "keyboard"
+ size: Theme.iconSize
+ color: "white"
+ anchors.centerIn: parent
+ }
+ }
+
+ Item {
+ width: childrenRect.width
+ height: Theme.iconSize
+
+ StyledText {
+ text: {
+ if (CompositorService.isNiri) {
+ const layout = NiriService.getCurrentKeyboardLayoutName();
+ if (!layout)
+ return "";
+ const parts = layout.split(" ");
+ if (parts.length > 0) {
+ return parts[0].substring(0, 2).toUpperCase();
+ }
+ return layout.substring(0, 2).toUpperCase();
+ } else if (CompositorService.isHyprland) {
+ return hyprlandCurrentLayout;
+ }
+ return "";
+ }
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Light
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+
+ MouseArea {
+ id: keyboardLayoutArea
+ anchors.fill: parent
+ enabled: !demoMode
+ hoverEnabled: enabled
+ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ onClicked: {
+ if (CompositorService.isNiri) {
+ NiriService.cycleKeyboardLayout();
+ } else if (CompositorService.isHyprland) {
+ Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]);
+ updateHyprlandLayout();
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ width: 1
+ height: 24
+ color: Qt.rgba(255, 255, 255, 0.2)
+ anchors.verticalCenter: parent.verticalCenter
+ visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer
+ }
+
+ Row {
+ spacing: Theme.spacingS
+ visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer
+ anchors.verticalCenter: parent.verticalCenter
+
+ Item {
+ width: 20
+ height: Theme.iconSize
+ anchors.verticalCenter: parent.verticalCenter
+
+ Loader {
+ active: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
+
+ sourceComponent: Component {
+ Ref {
+ service: CavaService
+ }
+ }
+ }
+
+ Timer {
+ running: !CavaService.cavaAvailable && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
+ interval: 256
+ repeat: true
+ onTriggered: {
+ CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25];
+ }
+ }
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 1.5
+
+ Repeater {
+ model: 6
+ delegate: Rectangle {
+ required property int index
+
+ width: 2
+ height: {
+ if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) {
+ const rawLevel = CavaService.values[index] || 0;
+ const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
+ const maxHeight = Theme.iconSize - 2;
+ const minHeight = 3;
+ return minHeight + (scaledLevel / 100) * (maxHeight - minHeight);
+ }
+ return 3;
+ }
+ radius: 1.5
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.standardDecel
+ }
+ }
+ }
+ }
+ }
+ }
+
+ StyledText {
+ text: {
+ const player = MprisController.activePlayer;
+ if (!player?.trackTitle)
+ return "";
+ const title = player.trackTitle;
+ const artist = player.trackArtist || "";
+ return artist ? title + " • " + artist : title;
+ }
+ font.pixelSize: Theme.fontSizeLarge
+ color: "white"
+ opacity: 0.9
+ anchors.verticalCenter: parent.verticalCenter
+ elide: Text.ElideRight
+ width: Math.min(implicitWidth, 400)
+ wrapMode: Text.NoWrap
+ maximumLineCount: 1
+ }
+
+ Row {
+ spacing: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+
+ Rectangle {
+ width: 20
+ height: 20
+ radius: 10
+ anchors.verticalCenter: parent.verticalCenter
+ color: prevArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
+ visible: MprisController.activePlayer
+ opacity: (MprisController.activePlayer?.canGoPrevious ?? false) ? 1 : 0.3
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "skip_previous"
+ size: 12
+ color: "white"
+ }
+
+ MouseArea {
+ id: prevArea
+ anchors.fill: parent
+ enabled: MprisController.activePlayer?.canGoPrevious ?? false
+ hoverEnabled: enabled
+ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ onClicked: MprisController.previousOrRewind()
+ }
+ }
+
+ Rectangle {
+ width: 24
+ height: 24
+ radius: 12
+ anchors.verticalCenter: parent.verticalCenter
+ color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? Qt.rgba(255, 255, 255, 0.9) : Qt.rgba(255, 255, 255, 0.2)
+ visible: MprisController.activePlayer
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
+ size: 14
+ color: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing ? "black" : "white"
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: MprisController.activePlayer
+ hoverEnabled: enabled
+ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ onClicked: MprisController.activePlayer?.togglePlaying()
+ }
+ }
+
+ Rectangle {
+ width: 20
+ height: 20
+ radius: 10
+ anchors.verticalCenter: parent.verticalCenter
+ color: nextArea.containsMouse ? Qt.rgba(255, 255, 255, 0.2) : "transparent"
+ visible: MprisController.activePlayer
+ opacity: (MprisController.activePlayer?.canGoNext ?? false) ? 1 : 0.3
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "skip_next"
+ size: 12
+ color: "white"
+ }
+
+ MouseArea {
+ id: nextArea
+ anchors.fill: parent
+ enabled: MprisController.activePlayer?.canGoNext ?? false
+ hoverEnabled: enabled
+ cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ onClicked: MprisController.activePlayer?.next()
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ width: 1
+ height: 24
+ color: Qt.rgba(255, 255, 255, 0.2)
+ anchors.verticalCenter: parent.verticalCenter
+ visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer && WeatherService.weather.available
+ }
+
+ 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
+ }
+
+ StyledText {
+ text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°"
+ font.pixelSize: Theme.fontSizeLarge
+ font.weight: Font.Light
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ 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)
+ }
+
+ Row {
+ spacing: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
+
+ DankIcon {
+ name: "screen_record"
+ size: Theme.iconSize - 2
+ color: NiriService.hasActiveCast ? "white" : Qt.rgba(255, 255, 255, 0.5)
+ anchors.verticalCenter: parent.verticalCenter
+ visible: NiriService.hasCasts
+ }
+
+ DankIcon {
+ name: {
+ if (NetworkService.wifiToggling)
+ return "sync";
+ switch (NetworkService.networkStatus) {
+ case "ethernet":
+ return "lan";
+ case "vpn":
+ return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon;
+ default:
+ return NetworkService.wifiSignalIcon;
+ }
+ }
+ size: Theme.iconSize - 2
+ color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5)
+ anchors.verticalCenter: parent.verticalCenter
+ visible: NetworkService.networkAvailable
+ }
+
+ DankIcon {
+ name: "vpn_lock"
+ size: Theme.iconSize - 2
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: NetworkService.vpnAvailable && NetworkService.vpnConnected
+ }
+
+ DankIcon {
+ name: "bluetooth"
+ size: Theme.iconSize - 2
+ color: "white"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: BluetoothService.available && BluetoothService.enabled
+ }
+
+ DankIcon {
+ name: {
+ if (!AudioService.sink?.audio) {
+ return "volume_up";
+ }
+ if (AudioService.sink.audio.muted)
+ return "volume_off";
+ if (AudioService.sink.audio.volume === 0)
+ return "volume_mute";
+ if (AudioService.sink.audio.volume * 100 < 33) {
+ return "volume_down";
+ }
+ 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
+ }
+ }
+
+ 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))
+ }
+
+ 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";
+ }
+ 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";
+ }
+ 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
+ }
+ }
+ }
+
+ DankActionButton {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.margins: Theme.spacingXL
+ visible: SettingsData.lockScreenShowPowerActions
+ iconName: "power_settings_new"
+ iconColor: Theme.error
+ buttonSize: 40
+ onClicked: {
+ if (demoMode) {
+ console.log("Demo: Power Menu");
+ } else {
+ powerMenu.show();
+ }
+ }
+ }
+ }
+
+ Pam {
+ id: pam
+ lockSecured: !demoMode
+ onUnlockRequested: {
+ root.unlocking = true;
+ lockerReadyArmed = false;
+ passwordField.text = "";
+ root.passwordBuffer = "";
+ root.unlockRequested();
+ }
+ onStateChanged: {
+ root.pamState = state;
+ if (state !== "") {
+ root.unlocking = false;
+ placeholderDelay.restart();
+ passwordField.text = "";
+ root.passwordBuffer = "";
+ }
+ }
+ onU2fPendingChanged: {
+ if (u2fPending) {
+ passwordField.text = "";
+ root.passwordBuffer = "";
+ if (keyboardController.isKeyboardActive)
+ keyboardController.hide();
+ }
+ }
+ }
+
+ Connections {
+ target: pam
+
+ function onUnlockInProgressChanged() {
+ if (!pam.unlockInProgress && root.unlocking)
+ root.unlocking = false;
+ }
+ }
+
+ Binding {
+ target: pam
+ property: "buffer"
+ value: root.passwordBuffer
+ }
+
+ Timer {
+ id: placeholderDelay
+
+ interval: 4000
+ onTriggered: root.pamState = ""
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: demoMode
+ onClicked: root.unlockRequested()
+ }
+
+ LockPowerMenu {
+ id: powerMenu
+ showLogout: true
+ onClosed: {
+ if (!demoMode && passwordField && passwordField.forceActiveFocus) {
+ Qt.callLater(() => passwordField.forceActiveFocus());
+ }
+ }
+ }
+}