1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

idle: add fade to lock option

fixes #694
fixes #805
This commit is contained in:
bbedward
2025-11-24 10:59:36 -05:00
parent 6c4a9bcfb8
commit ee03853901
9 changed files with 582 additions and 385 deletions

View File

@@ -243,6 +243,8 @@ Singleton {
property bool lockBeforeSuspend: false property bool lockBeforeSuspend: false
property bool preventIdleForMedia: false property bool preventIdleForMedia: false
property bool loginctlLockIntegration: true property bool loginctlLockIntegration: true
property bool fadeToLockEnabled: false
property int fadeToLockGracePeriod: 5
property string launchPrefix: "" property string launchPrefix: ""
property var brightnessDevicePins: ({}) property var brightnessDevicePins: ({})
property var wifiNetworkPins: ({}) property var wifiNetworkPins: ({})

View File

@@ -145,6 +145,8 @@ var SPEC = {
lockBeforeSuspend: { def: false }, lockBeforeSuspend: { def: false },
preventIdleForMedia: { def: false }, preventIdleForMedia: { def: false },
loginctlLockIntegration: { def: true }, loginctlLockIntegration: { def: true },
fadeToLockEnabled: { def: false },
fadeToLockGracePeriod: { def: 5 },
launchPrefix: { def: "" }, launchPrefix: { def: "" },
brightnessDevicePins: { def: {} }, brightnessDevicePins: { def: {} },
wifiNetworkPins: { def: {} }, wifiNetworkPins: { def: {} },

View File

@@ -63,6 +63,46 @@ Item {
id: lock id: lock
} }
Variants {
model: Quickshell.screens
delegate: Loader {
id: fadeWindowLoader
required property var modelData
active: SettingsData.fadeToLockEnabled
asynchronous: false
sourceComponent: FadeToLockWindow {
screen: fadeWindowLoader.modelData
onFadeCompleted: {
IdleService.lockRequested();
}
onFadeCancelled: {
console.log("Fade to lock cancelled by user on screen:", fadeWindowLoader.modelData.name);
}
}
Connections {
target: IdleService
enabled: fadeWindowLoader.item !== null
function onFadeToLockRequested() {
if (fadeWindowLoader.item) {
fadeWindowLoader.item.startFade();
}
}
function onCancelFadeToLock() {
if (fadeWindowLoader.item) {
fadeWindowLoader.item.cancelFade();
}
}
}
}
}
Repeater { Repeater {
id: dankBarRepeater id: dankBarRepeater
model: ScriptModel { model: ScriptModel {

View File

@@ -76,10 +76,10 @@ Item {
checked: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration checked: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
enabled: SessionService.loginctlAvailable enabled: SessionService.loginctlAvailable
onToggled: checked => { onToggled: checked => {
if (SessionService.loginctlAvailable) { if (SessionService.loginctlAvailable) {
SettingsData.set("loginctlLockIntegration", checked) SettingsData.set("loginctlLockIntegration", checked);
} }
} }
} }
DankToggle { DankToggle {
@@ -160,6 +160,40 @@ Item {
onToggled: checked => SettingsData.set("preventIdleForMedia", checked) onToggled: checked => SettingsData.set("preventIdleForMedia", checked)
} }
DankToggle {
width: parent.width
text: I18n.tr("Fade to lock screen")
description: I18n.tr("Gradually fade the screen before locking with a configurable grace period")
checked: SettingsData.fadeToLockEnabled
onToggled: checked => SettingsData.set("fadeToLockEnabled", checked)
}
DankDropdown {
id: fadeGracePeriodDropdown
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
width: parent.width
addHorizontalPadding: true
text: I18n.tr("Fade grace period")
options: periodOptions
visible: SettingsData.fadeToLockEnabled
enabled: SettingsData.fadeToLockEnabled
Component.onCompleted: {
const currentPeriod = SettingsData.fadeToLockGracePeriod;
const index = periodValues.indexOf(currentPeriod);
currentValue = index >= 0 ? periodOptions[index] : "5 seconds";
}
onValueChanged: value => {
const index = periodOptions.indexOf(value);
if (index >= 0) {
SettingsData.set("fadeToLockGracePeriod", periodValues[index]);
}
}
}
DankDropdown { DankDropdown {
id: lockDropdown id: lockDropdown
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
@@ -172,29 +206,29 @@ Item {
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout;
const index = lockDropdown.timeoutValues.indexOf(currentTimeout) const index = lockDropdown.timeoutValues.indexOf(currentTimeout);
lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never" lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never";
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout;
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout);
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never";
} }
onValueChanged: value => { onValueChanged: value => {
const index = timeoutOptions.indexOf(value) const index = timeoutOptions.indexOf(value);
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index];
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.set("acLockTimeout", timeout) SettingsData.set("acLockTimeout", timeout);
} else { } else {
SettingsData.set("batteryLockTimeout", timeout) SettingsData.set("batteryLockTimeout", timeout);
} }
} }
} }
} }
DankDropdown { DankDropdown {
@@ -209,29 +243,29 @@ Item {
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout;
const index = monitorDropdown.timeoutValues.indexOf(currentTimeout) const index = monitorDropdown.timeoutValues.indexOf(currentTimeout);
monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never" monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never";
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout;
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout);
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never";
} }
onValueChanged: value => { onValueChanged: value => {
const index = timeoutOptions.indexOf(value) const index = timeoutOptions.indexOf(value);
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index];
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.set("acMonitorTimeout", timeout) SettingsData.set("acMonitorTimeout", timeout);
} else { } else {
SettingsData.set("batteryMonitorTimeout", timeout) SettingsData.set("batteryMonitorTimeout", timeout);
} }
} }
} }
} }
DankDropdown { DankDropdown {
@@ -246,29 +280,29 @@ Item {
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout;
const index = suspendDropdown.timeoutValues.indexOf(currentTimeout) const index = suspendDropdown.timeoutValues.indexOf(currentTimeout);
suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never" suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never";
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout;
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout);
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never";
} }
onValueChanged: value => { onValueChanged: value => {
const index = timeoutOptions.indexOf(value) const index = timeoutOptions.indexOf(value);
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index];
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.set("acSuspendTimeout", timeout) SettingsData.set("acSuspendTimeout", timeout);
} else { } else {
SettingsData.set("batterySuspendTimeout", timeout) SettingsData.set("batterySuspendTimeout", timeout);
} }
} }
} }
} }
Column { Column {
@@ -293,25 +327,25 @@ Item {
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior;
suspendBehaviorSelector.currentIndex = behavior suspendBehaviorSelector.currentIndex = behavior;
} }
} }
Component.onCompleted: { Component.onCompleted: {
const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior const behavior = powerCategory.currentIndex === 0 ? SettingsData.acSuspendBehavior : SettingsData.batterySuspendBehavior;
currentIndex = behavior currentIndex = behavior;
} }
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (selected) { if (selected) {
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SettingsData.set("acSuspendBehavior", index) SettingsData.set("acSuspendBehavior", index);
} else { } else {
SettingsData.set("batterySuspendBehavior", index) SettingsData.set("batterySuspendBehavior", index);
} }
} }
} }
} }
} }
@@ -384,17 +418,17 @@ Item {
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"] property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"]
Component.onCompleted: { Component.onCompleted: {
const currentAction = SettingsData.powerMenuDefaultAction || "logout" const currentAction = SettingsData.powerMenuDefaultAction || "logout";
const index = actionValues.indexOf(currentAction) const index = actionValues.indexOf(currentAction);
currentValue = index >= 0 ? options[index] : "Log Out" currentValue = index >= 0 ? options[index] : "Log Out";
} }
onValueChanged: value => { onValueChanged: value => {
const index = options.indexOf(value) const index = options.indexOf(value);
if (index >= 0) { if (index >= 0) {
SettingsData.set("powerMenuDefaultAction", actionValues[index]) SettingsData.set("powerMenuDefaultAction", actionValues[index]);
} }
} }
} }
Column { Column {
@@ -406,14 +440,14 @@ Item {
text: I18n.tr("Show Reboot") text: I18n.tr("Show Reboot")
checked: SettingsData.powerMenuActions.includes("reboot") checked: SettingsData.powerMenuActions.includes("reboot")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("reboot")) { if (checked && !actions.includes("reboot")) {
actions.push("reboot") actions.push("reboot");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "reboot") actions = actions.filter(a => a !== "reboot");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -421,14 +455,14 @@ Item {
text: I18n.tr("Show Log Out") text: I18n.tr("Show Log Out")
checked: SettingsData.powerMenuActions.includes("logout") checked: SettingsData.powerMenuActions.includes("logout")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("logout")) { if (checked && !actions.includes("logout")) {
actions.push("logout") actions.push("logout");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "logout") actions = actions.filter(a => a !== "logout");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -436,14 +470,14 @@ Item {
text: I18n.tr("Show Power Off") text: I18n.tr("Show Power Off")
checked: SettingsData.powerMenuActions.includes("poweroff") checked: SettingsData.powerMenuActions.includes("poweroff")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("poweroff")) { if (checked && !actions.includes("poweroff")) {
actions.push("poweroff") actions.push("poweroff");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "poweroff") actions = actions.filter(a => a !== "poweroff");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -451,14 +485,14 @@ Item {
text: I18n.tr("Show Lock") text: I18n.tr("Show Lock")
checked: SettingsData.powerMenuActions.includes("lock") checked: SettingsData.powerMenuActions.includes("lock")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("lock")) { if (checked && !actions.includes("lock")) {
actions.push("lock") actions.push("lock");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "lock") actions = actions.filter(a => a !== "lock");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -466,14 +500,14 @@ Item {
text: I18n.tr("Show Suspend") text: I18n.tr("Show Suspend")
checked: SettingsData.powerMenuActions.includes("suspend") checked: SettingsData.powerMenuActions.includes("suspend")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("suspend")) { if (checked && !actions.includes("suspend")) {
actions.push("suspend") actions.push("suspend");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "suspend") actions = actions.filter(a => a !== "suspend");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -482,14 +516,14 @@ Item {
description: I18n.tr("Restart the DankMaterialShell") description: I18n.tr("Restart the DankMaterialShell")
checked: SettingsData.powerMenuActions.includes("restart") checked: SettingsData.powerMenuActions.includes("restart")
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("restart")) { if (checked && !actions.includes("restart")) {
actions.push("restart") actions.push("restart");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "restart") actions = actions.filter(a => a !== "restart");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
DankToggle { DankToggle {
@@ -499,14 +533,14 @@ Item {
checked: SettingsData.powerMenuActions.includes("hibernate") checked: SettingsData.powerMenuActions.includes("hibernate")
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported
onToggled: checked => { onToggled: checked => {
let actions = [...SettingsData.powerMenuActions] let actions = [...SettingsData.powerMenuActions];
if (checked && !actions.includes("hibernate")) { if (checked && !actions.includes("hibernate")) {
actions.push("hibernate") actions.push("hibernate");
} else if (!checked) { } else if (!checked) {
actions = actions.filter(a => a !== "hibernate") actions = actions.filter(a => a !== "hibernate");
} }
SettingsData.set("powerMenuActions", actions) SettingsData.set("powerMenuActions", actions);
} }
} }
} }
} }
@@ -612,12 +646,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionLock) { if (SettingsData.customPowerActionLock) {
text = SettingsData.customPowerActionLock text = SettingsData.customPowerActionLock;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionLock", text.trim()) SettingsData.set("customPowerActionLock", text.trim());
} }
} }
} }
@@ -644,12 +678,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionLogout) { if (SettingsData.customPowerActionLogout) {
text = SettingsData.customPowerActionLogout text = SettingsData.customPowerActionLogout;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionLogout", text.trim()) SettingsData.set("customPowerActionLogout", text.trim());
} }
} }
} }
@@ -676,12 +710,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionSuspend) { if (SettingsData.customPowerActionSuspend) {
text = SettingsData.customPowerActionSuspend text = SettingsData.customPowerActionSuspend;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionSuspend", text.trim()) SettingsData.set("customPowerActionSuspend", text.trim());
} }
} }
} }
@@ -708,12 +742,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionHibernate) { if (SettingsData.customPowerActionHibernate) {
text = SettingsData.customPowerActionHibernate text = SettingsData.customPowerActionHibernate;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionHibernate", text.trim()) SettingsData.set("customPowerActionHibernate", text.trim());
} }
} }
} }
@@ -740,12 +774,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionReboot) { if (SettingsData.customPowerActionReboot) {
text = SettingsData.customPowerActionReboot text = SettingsData.customPowerActionReboot;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionReboot", text.trim()) SettingsData.set("customPowerActionReboot", text.trim());
} }
} }
} }
@@ -772,12 +806,12 @@ Item {
Component.onCompleted: { Component.onCompleted: {
if (SettingsData.customPowerActionPowerOff) { if (SettingsData.customPowerActionPowerOff) {
text = SettingsData.customPowerActionPowerOff text = SettingsData.customPowerActionPowerOff;
} }
} }
onTextEdited: { onTextEdited: {
SettingsData.set("customPowerActionPowerOff", text.trim()) SettingsData.set("customPowerActionPowerOff", text.trim());
} }
} }
} }

View File

@@ -0,0 +1,102 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
PanelWindow {
id: root
property bool active: false
signal fadeCompleted
signal fadeCancelled
visible: active
color: "transparent"
WlrLayershell.namespace: "dms:fade-to-lock"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: active ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
anchors {
left: true
right: true
top: true
bottom: true
}
Rectangle {
id: fadeOverlay
anchors.fill: parent
color: "black"
opacity: 0
onOpacityChanged: {
if (opacity >= 0.99 && root.active) {
root.fadeCompleted();
}
}
}
SequentialAnimation {
id: fadeSeq
running: false
NumberAnimation {
target: fadeOverlay
property: "opacity"
from: 0.0
to: 1.0
duration: SettingsData.fadeToLockGracePeriod * 1000
easing.type: Easing.OutCubic
}
}
function startFade() {
if (!SettingsData.fadeToLockEnabled)
return;
active = true;
fadeOverlay.opacity = 0.0;
fadeSeq.stop();
fadeSeq.start();
}
function cancelFade() {
fadeSeq.stop();
fadeOverlay.opacity = 0.0;
active = false;
fadeCancelled();
}
MouseArea {
anchors.fill: parent
enabled: root.active
onClicked: root.cancelFade()
onPressed: root.cancelFade()
}
FocusScope {
anchors.fill: parent
focus: root.active
Keys.onPressed: event => {
root.cancelFade();
event.accepted = true;
}
}
Component.onCompleted: {
if (active) {
forceActiveFocus();
}
}
onActiveChanged: {
if (active) {
forceActiveFocus();
}
}
}

View File

@@ -15,23 +15,23 @@ Scope {
property bool processingExternalEvent: false property bool processingExternalEvent: false
Component.onCompleted: { Component.onCompleted: {
IdleService.lockComponent = this IdleService.lockComponent = this;
} }
function lock() { function lock() {
if (SettingsData.customPowerActionLock && SettingsData.customPowerActionLock.length > 0) { if (SettingsData.customPowerActionLock && SettingsData.customPowerActionLock.length > 0) {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock]);
return return;
} }
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) { if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
DMSService.lockSession(response => { DMSService.lockSession(response => {
if (response.error) { if (response.error) {
console.warn("Lock: Failed to call loginctl.lock:", response.error) console.warn("Lock: Failed to call loginctl.lock:", response.error);
shouldLock = true shouldLock = true;
} }
}) });
} else { } else {
shouldLock = true shouldLock = true;
} }
} }
@@ -39,32 +39,32 @@ Scope {
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) { if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
DMSService.unlockSession(response => { DMSService.unlockSession(response => {
if (response.error) { if (response.error) {
console.warn("Lock: Failed to call loginctl.unlock:", response.error) console.warn("Lock: Failed to call loginctl.unlock:", response.error);
shouldLock = false shouldLock = false;
} }
}) });
} else { } else {
shouldLock = false shouldLock = false;
} }
} }
function activate() { function activate() {
lock() lock();
} }
Connections { Connections {
target: SessionService target: SessionService
function onSessionLocked() { function onSessionLocked() {
processingExternalEvent = true processingExternalEvent = true;
shouldLock = true shouldLock = true;
processingExternalEvent = false processingExternalEvent = false;
} }
function onSessionUnlocked() { function onSessionUnlocked() {
processingExternalEvent = true processingExternalEvent = true;
shouldLock = false shouldLock = false;
processingExternalEvent = false processingExternalEvent = false;
} }
} }
@@ -72,7 +72,7 @@ Scope {
target: IdleService target: IdleService
function onLockRequested() { function onLockRequested() {
lock() lock();
} }
} }
@@ -93,11 +93,11 @@ Scope {
screenName: lockSurface.screen?.name ?? "" screenName: lockSurface.screen?.name ?? ""
isLocked: shouldLock isLocked: shouldLock
onUnlockRequested: { onUnlockRequested: {
root.unlock() root.unlock();
} }
onPasswordChanged: newPassword => { onPasswordChanged: newPassword => {
root.sharedPasswordBuffer = newPassword root.sharedPasswordBuffer = newPassword;
} }
} }
} }
} }
@@ -113,21 +113,21 @@ Scope {
if (!root.processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) { if (!root.processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
DMSService.lockSession(response => { DMSService.lockSession(response => {
if (response.error) { if (response.error) {
console.warn("Lock: Failed to call loginctl.lock:", response.error) console.warn("Lock: Failed to call loginctl.lock:", response.error);
root.shouldLock = true root.shouldLock = true;
} }
}) });
} else { } else {
root.shouldLock = true root.shouldLock = true;
} }
} }
function demo() { function demo() {
demoWindow.showDemo() demoWindow.showDemo();
} }
function isLocked(): bool { function isLocked(): bool {
return sessionLock.locked return sessionLock.locked;
} }
} }
} }

View File

@@ -1,6 +1,5 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtCore
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
@@ -30,52 +29,60 @@ Item {
signal unlockRequested signal unlockRequested
function pickRandomFact() { function pickRandomFact() {
randomFact = Facts.getRandomFact() randomFact = Facts.getRandomFact();
} }
Component.onCompleted: { Component.onCompleted: {
if (demoMode) { if (demoMode) {
pickRandomFact() pickRandomFact();
} }
WeatherService.addRef() WeatherService.addRef();
UserInfoService.refreshUserInfo() UserInfoService.refreshUserInfo();
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
updateHyprlandLayout() updateHyprlandLayout();
hyprlandLayoutUpdateTimer.start() hyprlandLayoutUpdateTimer.start();
} }
lockerReadyArmed = true lockerReadyArmed = true;
} }
onDemoModeChanged: { onDemoModeChanged: {
if (demoMode) { if (demoMode) {
pickRandomFact() pickRandomFact();
} }
} }
Component.onDestruction: { Component.onDestruction: {
WeatherService.removeRef() WeatherService.removeRef();
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
hyprlandLayoutUpdateTimer.stop() hyprlandLayoutUpdateTimer.stop();
} }
} }
function sendLockerReadyOnce() { function sendLockerReadyOnce() {
if (lockerReadySent) return; if (lockerReadySent)
if (root.unlocking) return; return;
if (root.unlocking)
return;
lockerReadySent = true; lockerReadySent = true;
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) { if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
DMSService.sendRequest("loginctl.lockerReady", null, resp => { DMSService.sendRequest("loginctl.lockerReady", null, resp => {
if (resp?.error) console.warn("lockerReady failed:", resp.error) if (resp?.error)
else console.log("lockerReady sent (afterAnimating/afterRendering)"); console.warn("lockerReady failed:", resp.error);
else
console.log("lockerReady sent (afterAnimating/afterRendering)");
}); });
} }
} }
function maybeSend() { function maybeSend() {
if (!lockerReadyArmed) return; if (!lockerReadyArmed)
if (root.unlocking) return; return;
if (!root.visible || root.opacity <= 0) return; if (root.unlocking)
return;
if (!root.visible || root.opacity <= 0)
return;
Qt.callLater(() => { Qt.callLater(() => {
if (root.visible && root.opacity > 0 && !root.unlocking) if (root.visible && root.opacity > 0 && !root.unlocking)
sendLockerReadyOnce(); sendLockerReadyOnce();
@@ -86,8 +93,12 @@ Item {
target: root.Window.window target: root.Window.window
enabled: target !== null enabled: target !== null
function onAfterAnimating() { maybeSend(); } function onAfterAnimating() {
function onAfterRendering() { maybeSend(); } maybeSend();
}
function onAfterRendering() {
maybeSend();
}
} }
onVisibleChanged: maybeSend() onVisibleChanged: maybeSend()
@@ -95,7 +106,7 @@ Item {
function updateHyprlandLayout() { function updateHyprlandLayout() {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
hyprlandLayoutProcess.running = true hyprlandLayoutProcess.running = true;
} }
} }
@@ -106,27 +117,27 @@ Item {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
try { try {
const data = JSON.parse(text) const data = JSON.parse(text);
const mainKeyboard = data.keyboards.find(kb => kb.main === true) const mainKeyboard = data.keyboards.find(kb => kb.main === true);
hyprlandKeyboard = mainKeyboard.name hyprlandKeyboard = mainKeyboard.name;
if (mainKeyboard && mainKeyboard.active_keymap) { if (mainKeyboard && mainKeyboard.active_keymap) {
const parts = mainKeyboard.active_keymap.split(" ") const parts = mainKeyboard.active_keymap.split(" ");
if (parts.length > 0) { if (parts.length > 0) {
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase() hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase();
} else { } else {
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase() hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase();
} }
} else { } else {
hyprlandCurrentLayout = "" hyprlandCurrentLayout = "";
} }
if (mainKeyboard && mainKeyboard.layout_names) { if (mainKeyboard && mainKeyboard.layout_names) {
hyprlandLayoutCount = mainKeyboard.layout_names.length hyprlandLayoutCount = mainKeyboard.layout_names.length;
} else { } else {
hyprlandLayoutCount = 0 hyprlandLayoutCount = 0;
} }
} catch (e) { } catch (e) {
hyprlandCurrentLayout = "" hyprlandCurrentLayout = "";
hyprlandLayoutCount = 0 hyprlandLayoutCount = 0;
} }
} }
} }
@@ -143,8 +154,8 @@ Item {
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: { active: {
var currentWallpaper = SessionData.getMonitorWallpaper(screenName) var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#")) return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#"));
} }
asynchronous: true asynchronous: true
@@ -158,8 +169,8 @@ Item {
anchors.fill: parent anchors.fill: parent
source: { source: {
var currentWallpaper = SessionData.getMonitorWallpaper(screenName) var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "" return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
} }
fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode) fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode)
smooth: true smooth: true
@@ -213,8 +224,8 @@ Item {
spacing: 0 spacing: 0
property string fullTimeStr: { property string fullTimeStr: {
const format = SettingsData.getEffectiveTimeFormat() const format = SettingsData.getEffectiveTimeFormat();
return systemClock.date.toLocaleTimeString(Qt.locale(), format) return systemClock.date.toLocaleTimeString(Qt.locale(), format);
} }
property var timeParts: fullTimeStr.split(':') property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || "" property string hours: timeParts[0] || ""
@@ -222,8 +233,8 @@ Item {
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : "" property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '') property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
property string ampm: { property string ampm: {
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i) const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
return match ? match[0].trim() : "" return match ? match[0].trim() : "";
} }
property bool hasSeconds: timeParts.length > 2 property bool hasSeconds: timeParts.length > 2
@@ -322,9 +333,9 @@ Item {
anchors.verticalCenterOffset: -25 anchors.verticalCenterOffset: -25
text: { text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) { if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat);
} }
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat) return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat);
} }
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: "white" color: "white"
@@ -347,14 +358,14 @@ Item {
Layout.preferredHeight: 60 Layout.preferredHeight: 60
imageSource: { imageSource: {
if (PortalService.profileImage === "") { if (PortalService.profileImage === "") {
return "" return "";
} }
if (PortalService.profileImage.startsWith("/")) { if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage return "file://" + PortalService.profileImage;
} }
return PortalService.profileImage return PortalService.profileImage;
} }
fallbackIcon: "person" fallbackIcon: "person"
} }
@@ -414,20 +425,20 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2 anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
anchors.rightMargin: { anchors.rightMargin: {
let margin = Theme.spacingM let margin = Theme.spacingM;
if (loadingSpinner.visible) { if (loadingSpinner.visible) {
margin += loadingSpinner.width margin += loadingSpinner.width;
} }
if (enterButton.visible) { if (enterButton.visible) {
margin += enterButton.width + 2 margin += enterButton.width + 2;
} }
if (virtualKeyboardButton.visible) { if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width margin += virtualKeyboardButton.width;
} }
if (revealButton.visible) { if (revealButton.visible) {
margin += revealButton.width margin += revealButton.width;
} }
return margin return margin;
} }
opacity: 0 opacity: 0
focus: true focus: true
@@ -436,36 +447,36 @@ Item {
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
onTextChanged: { onTextChanged: {
if (!demoMode) { if (!demoMode) {
root.passwordBuffer = text root.passwordBuffer = text;
} }
} }
onAccepted: { onAccepted: {
if (!demoMode && !pam.passwd.active) { if (!demoMode && !pam.passwd.active) {
console.log("Enter pressed, starting PAM authentication") console.log("Enter pressed, starting PAM authentication");
pam.passwd.start() pam.passwd.start();
} }
} }
Keys.onPressed: event => { Keys.onPressed: event => {
if (demoMode) { if (demoMode) {
return return;
} }
if (pam.passwd.active) { if (pam.passwd.active) {
console.log("PAM is active, ignoring input") console.log("PAM is active, ignoring input");
event.accepted = true event.accepted = true;
return return;
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (!demoMode) { if (!demoMode) {
forceActiveFocus() forceActiveFocus();
} }
} }
onVisibleChanged: { onVisibleChanged: {
if (visible && !demoMode) { if (visible && !demoMode) {
forceActiveFocus() forceActiveFocus();
} }
} }
@@ -473,9 +484,9 @@ Item {
if (!activeFocus && !demoMode && visible && passwordField && !powerMenu.isVisible) { if (!activeFocus && !demoMode && visible && passwordField && !powerMenu.isVisible) {
Qt.callLater(() => { Qt.callLater(() => {
if (passwordField && passwordField.forceActiveFocus) { if (passwordField && passwordField.forceActiveFocus) {
passwordField.forceActiveFocus() passwordField.forceActiveFocus();
} }
}) });
} }
} }
@@ -483,9 +494,9 @@ Item {
if (enabled && !demoMode && visible && passwordField && !powerMenu.isVisible) { if (enabled && !demoMode && visible && passwordField && !powerMenu.isVisible) {
Qt.callLater(() => { Qt.callLater(() => {
if (passwordField && passwordField.forceActiveFocus) { if (passwordField && passwordField.forceActiveFocus) {
passwordField.forceActiveFocus() passwordField.forceActiveFocus();
} }
}) });
} }
} }
} }
@@ -506,15 +517,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (demoMode) { if (demoMode) {
return "" return "";
} }
if (root.unlocking) { if (root.unlocking) {
return "Unlocking..." return "Unlocking...";
} }
if (pam.passwd.active) { if (pam.passwd.active) {
return "Authenticating..." return "Authenticating...";
} }
return "Password..." return "Password...";
} }
color: root.unlocking ? Theme.primary : (pam.passwd.active ? Theme.primary : Theme.outline) color: root.unlocking ? Theme.primary : (pam.passwd.active ? Theme.primary : Theme.outline)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -543,12 +554,12 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (demoMode) { if (demoMode) {
return "••••••••" return "••••••••";
} }
if (parent.showPassword) { if (parent.showPassword) {
return root.passwordBuffer return root.passwordBuffer;
} }
return "•".repeat(root.passwordBuffer.length) return "•".repeat(root.passwordBuffer.length);
} }
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge
@@ -589,9 +600,9 @@ Item {
enabled: visible enabled: visible
onClicked: { onClicked: {
if (keyboardController.isKeyboardActive) { if (keyboardController.isKeyboardActive) {
keyboardController.hide() keyboardController.hide();
} else { } else {
keyboardController.show() keyboardController.show();
} }
} }
} }
@@ -690,8 +701,8 @@ Item {
enabled: !demoMode enabled: !demoMode
onClicked: { onClicked: {
if (!demoMode) { if (!demoMode) {
console.log("Enter button clicked, starting PAM authentication") console.log("Enter button clicked, starting PAM authentication");
pam.passwd.start() pam.passwd.start();
} }
} }
@@ -717,15 +728,15 @@ Item {
Layout.preferredHeight: 20 Layout.preferredHeight: 20
text: { text: {
if (root.pamState === "error") { if (root.pamState === "error") {
return "Authentication error - try again" return "Authentication error - try again";
} }
if (root.pamState === "max") { if (root.pamState === "max") {
return "Too many attempts - locked out" return "Too many attempts - locked out";
} }
if (root.pamState === "fail") { if (root.pamState === "fail") {
return "Incorrect password - try again" return "Incorrect password - try again";
} }
return "" return "";
} }
color: Theme.error color: Theme.error
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -793,11 +804,11 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: { visible: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
return NiriService.keyboardLayoutNames.length > 1 return NiriService.keyboardLayoutNames.length > 1;
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return hyprlandLayoutCount > 1 return hyprlandLayoutCount > 1;
} }
return false return false;
} }
Row { Row {
@@ -823,17 +834,18 @@ Item {
StyledText { StyledText {
text: { text: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
const layout = NiriService.getCurrentKeyboardLayoutName() const layout = NiriService.getCurrentKeyboardLayoutName();
if (!layout) return "" if (!layout)
const parts = layout.split(" ") return "";
const parts = layout.split(" ");
if (parts.length > 0) { if (parts.length > 0) {
return parts[0].substring(0, 2).toUpperCase() return parts[0].substring(0, 2).toUpperCase();
} }
return layout.substring(0, 2).toUpperCase() return layout.substring(0, 2).toUpperCase();
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return hyprlandCurrentLayout return hyprlandCurrentLayout;
} }
return "" return "";
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Light font.weight: Font.Light
@@ -851,15 +863,10 @@ Item {
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.cycleKeyboardLayout() NiriService.cycleKeyboardLayout();
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
Quickshell.execDetached([ Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]);
"hyprctl", updateHyprlandLayout();
"switchxkblayout",
hyprlandKeyboard,
"next"
])
updateHyprlandLayout()
} }
} }
} }
@@ -898,7 +905,7 @@ Item {
interval: 256 interval: 256
repeat: true repeat: true
onTriggered: { 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] 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];
} }
} }
@@ -914,13 +921,13 @@ Item {
width: 2 width: 2
height: { height: {
if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) { if (MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing && CavaService.values.length > index) {
const rawLevel = CavaService.values[index] || 0 const rawLevel = CavaService.values[index] || 0;
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100 const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
const maxHeight = Theme.iconSize - 2 const maxHeight = Theme.iconSize - 2;
const minHeight = 3 const minHeight = 3;
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight) return minHeight + (scaledLevel / 100) * (maxHeight - minHeight);
} }
return 3 return 3;
} }
radius: 1.5 radius: 1.5
color: "white" color: "white"
@@ -940,11 +947,12 @@ Item {
StyledText { StyledText {
text: { text: {
const player = MprisController.activePlayer const player = MprisController.activePlayer;
if (!player?.trackTitle) return "" if (!player?.trackTitle)
const title = player.trackTitle return "";
const artist = player.trackArtist || "" const title = player.trackTitle;
return artist ? title + " • " + artist : title const artist = player.trackArtist || "";
return artist ? title + " • " + artist : title;
} }
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: "white" color: "white"
@@ -1099,15 +1107,15 @@ Item {
DankIcon { DankIcon {
name: { name: {
if (!AudioService.sink?.audio) { if (!AudioService.sink?.audio) {
return "volume_up" return "volume_up";
} }
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) { if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
return "volume_off" return "volume_off";
} }
if (AudioService.sink.audio.volume * 100 < 33) { if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_down" return "volume_down";
} }
return "volume_up" return "volume_up";
} }
size: Theme.iconSize - 2 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" color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
@@ -1133,95 +1141,95 @@ Item {
name: { name: {
if (BatteryService.isCharging) { if (BatteryService.isCharging) {
if (BatteryService.batteryLevel >= 90) { if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full";
} }
if (BatteryService.batteryLevel >= 80) { if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90";
} }
if (BatteryService.batteryLevel >= 60) { if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80";
} }
if (BatteryService.batteryLevel >= 50) { if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60";
} }
if (BatteryService.batteryLevel >= 30) { if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50";
} }
if (BatteryService.batteryLevel >= 20) { if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30";
} }
return "battery_charging_20" return "battery_charging_20";
} }
if (BatteryService.isPluggedIn) { if (BatteryService.isPluggedIn) {
if (BatteryService.batteryLevel >= 90) { if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full";
} }
if (BatteryService.batteryLevel >= 80) { if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90";
} }
if (BatteryService.batteryLevel >= 60) { if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80";
} }
if (BatteryService.batteryLevel >= 50) { if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60";
} }
if (BatteryService.batteryLevel >= 30) { if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50";
} }
if (BatteryService.batteryLevel >= 20) { if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30";
} }
return "battery_charging_20" return "battery_charging_20";
} }
if (BatteryService.batteryLevel >= 95) { if (BatteryService.batteryLevel >= 95) {
return "battery_full" return "battery_full";
} }
if (BatteryService.batteryLevel >= 85) { if (BatteryService.batteryLevel >= 85) {
return "battery_6_bar" return "battery_6_bar";
} }
if (BatteryService.batteryLevel >= 70) { if (BatteryService.batteryLevel >= 70) {
return "battery_5_bar" return "battery_5_bar";
} }
if (BatteryService.batteryLevel >= 55) { if (BatteryService.batteryLevel >= 55) {
return "battery_4_bar" return "battery_4_bar";
} }
if (BatteryService.batteryLevel >= 40) { if (BatteryService.batteryLevel >= 40) {
return "battery_3_bar" return "battery_3_bar";
} }
if (BatteryService.batteryLevel >= 25) { if (BatteryService.batteryLevel >= 25) {
return "battery_2_bar" return "battery_2_bar";
} }
return "battery_1_bar" return "battery_1_bar";
} }
size: Theme.iconSize size: Theme.iconSize
color: { color: {
if (BatteryService.isLowBattery && !BatteryService.isCharging) { if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error return Theme.error;
} }
if (BatteryService.isCharging || BatteryService.isPluggedIn) { if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary return Theme.primary;
} }
return "white" return "white";
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -1246,9 +1254,9 @@ Item {
buttonSize: 40 buttonSize: 40
onClicked: { onClicked: {
if (demoMode) { if (demoMode) {
console.log("Demo: Power Menu") console.log("Demo: Power Menu");
} else { } else {
powerMenu.show() powerMenu.show();
} }
} }
} }
@@ -1272,18 +1280,18 @@ Item {
id: pam id: pam
lockSecured: !demoMode lockSecured: !demoMode
onUnlockRequested: { onUnlockRequested: {
root.unlocking = true root.unlocking = true;
lockerReadyArmed = false lockerReadyArmed = false;
passwordField.text = "" passwordField.text = "";
root.passwordBuffer = "" root.passwordBuffer = "";
root.unlockRequested() root.unlockRequested();
} }
onStateChanged: { onStateChanged: {
root.pamState = state root.pamState = state;
if (state !== "") { if (state !== "") {
placeholderDelay.restart() placeholderDelay.restart();
passwordField.text = "" passwordField.text = "";
root.passwordBuffer = "" root.passwordBuffer = "";
} }
} }
} }
@@ -1312,7 +1320,7 @@ Item {
showLogout: true showLogout: true
onClosed: { onClosed: {
if (!demoMode && passwordField && passwordField.forceActiveFocus) { if (!demoMode && passwordField && passwordField.forceActiveFocus) {
Qt.callLater(() => passwordField.forceActiveFocus()) Qt.callLater(() => passwordField.forceActiveFocus());
} }
} }
} }

View File

@@ -1,9 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common
Rectangle { Rectangle {
id: root id: root
@@ -14,7 +12,7 @@ Rectangle {
required property bool isLocked required property bool isLocked
signal passwordChanged(string newPassword) signal passwordChanged(string newPassword)
signal unlockRequested() signal unlockRequested
color: "transparent" color: "transparent"
@@ -28,14 +26,14 @@ Rectangle {
onUnlockRequested: root.unlockRequested() onUnlockRequested: root.unlockRequested()
onPasswordBufferChanged: { onPasswordBufferChanged: {
if (root.sharedPasswordBuffer !== passwordBuffer) { if (root.sharedPasswordBuffer !== passwordBuffer) {
root.passwordChanged(passwordBuffer) root.passwordChanged(passwordBuffer);
} }
} }
} }
onIsLockedChanged: { onIsLockedChanged: {
if (!isLocked) { if (!isLocked) {
lockContent.unlocking = false lockContent.unlocking = false;
} }
} }
} }

View File

@@ -4,26 +4,24 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Services.Mpris
import qs.Common import qs.Common
import qs.Services
Singleton { Singleton {
id: root id: root
readonly property bool idleMonitorAvailable: { readonly property bool idleMonitorAvailable: {
try { try {
return typeof IdleMonitor !== "undefined" return typeof IdleMonitor !== "undefined";
} catch (e) { } catch (e) {
return false return false;
} }
} }
readonly property bool idleInhibitorAvailable: { readonly property bool idleInhibitorAvailable: {
try { try {
return typeof IdleInhibitor !== "undefined" return typeof IdleInhibitor !== "undefined";
} catch (e) { } catch (e) {
return false return false;
} }
} }
@@ -44,32 +42,37 @@ Singleton {
onSuspendTimeoutChanged: _rearmIdleMonitors() onSuspendTimeoutChanged: _rearmIdleMonitors()
function _rearmIdleMonitors() { function _rearmIdleMonitors() {
_enableGate = false _enableGate = false;
Qt.callLater(() => { _enableGate = true }) Qt.callLater(() => {
_enableGate = true;
});
} }
signal lockRequested() signal lockRequested
signal requestMonitorOff() signal fadeToLockRequested
signal requestMonitorOn() signal cancelFadeToLock
signal requestSuspend() signal requestMonitorOff
signal requestMonitorOn
signal requestSuspend
property var monitorOffMonitor: null property var monitorOffMonitor: null
property var lockMonitor: null property var lockMonitor: null
property var suspendMonitor: null property var suspendMonitor: null
property var mediaInhibitor: null property var mediaInhibitor: null
property var lockComponent: null
function wake() { function wake() {
requestMonitorOn() requestMonitorOn();
} }
function createMediaInhibitor() { function createMediaInhibitor() {
if (!idleInhibitorAvailable) { if (!idleInhibitorAvailable) {
return return;
} }
if (mediaInhibitor) { if (mediaInhibitor) {
mediaInhibitor.destroy() mediaInhibitor.destroy();
mediaInhibitor = null mediaInhibitor = null;
} }
const inhibitorString = ` const inhibitorString = `
@@ -79,23 +82,23 @@ Singleton {
IdleInhibitor { IdleInhibitor {
active: false active: false
} }
` `;
mediaInhibitor = Qt.createQmlObject(inhibitorString, root, "IdleService.MediaInhibitor") mediaInhibitor = Qt.createQmlObject(inhibitorString, root, "IdleService.MediaInhibitor");
mediaInhibitor.active = Qt.binding(() => root.mediaPlaying) mediaInhibitor.active = Qt.binding(() => root.mediaPlaying);
} }
function destroyMediaInhibitor() { function destroyMediaInhibitor() {
if (mediaInhibitor) { if (mediaInhibitor) {
mediaInhibitor.destroy() mediaInhibitor.destroy();
mediaInhibitor = null mediaInhibitor = null;
} }
} }
function createIdleMonitors() { function createIdleMonitors() {
if (!idleMonitorAvailable) { if (!idleMonitorAvailable) {
console.info("IdleService: IdleMonitor not available, skipping creation") console.info("IdleService: IdleMonitor not available, skipping creation");
return return;
} }
try { try {
@@ -108,60 +111,68 @@ Singleton {
respectInhibitors: true respectInhibitors: true
timeout: 0 timeout: 0
} }
` `;
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor") monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0) monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0);
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors) monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout) monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout);
monitorOffMonitor.isIdleChanged.connect(function() { monitorOffMonitor.isIdleChanged.connect(function () {
if (monitorOffMonitor.isIdle) { if (monitorOffMonitor.isIdle) {
root.requestMonitorOff() root.requestMonitorOff();
} else { } else {
root.requestMonitorOn() root.requestMonitorOn();
} }
}) });
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor") lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0) lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors) lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
lockMonitor.timeout = Qt.binding(() => root.lockTimeout) lockMonitor.timeout = Qt.binding(() => root.lockTimeout);
lockMonitor.isIdleChanged.connect(function() { lockMonitor.isIdleChanged.connect(function () {
if (lockMonitor.isIdle) { if (lockMonitor.isIdle) {
root.lockRequested() if (SettingsData.fadeToLockEnabled) {
root.fadeToLockRequested();
} else {
root.lockRequested();
}
} else {
if (SettingsData.fadeToLockEnabled) {
root.cancelFadeToLock();
}
} }
}) });
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor") suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0) suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors) suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout) suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout);
suspendMonitor.isIdleChanged.connect(function() { suspendMonitor.isIdleChanged.connect(function () {
if (suspendMonitor.isIdle) { if (suspendMonitor.isIdle) {
root.requestSuspend() root.requestSuspend();
} }
}) });
if (SettingsData.preventIdleForMedia) { if (SettingsData.preventIdleForMedia) {
createMediaInhibitor() createMediaInhibitor();
} }
} catch (e) { } catch (e) {
console.warn("IdleService: Error creating IdleMonitors:", e) console.warn("IdleService: Error creating IdleMonitors:", e);
} }
} }
Connections { Connections {
target: root target: root
function onRequestMonitorOff() { function onRequestMonitorOff() {
CompositorService.powerOffMonitors() CompositorService.powerOffMonitors();
} }
function onRequestMonitorOn() { function onRequestMonitorOn() {
CompositorService.powerOnMonitors() CompositorService.powerOnMonitors();
} }
function onRequestSuspend() { function onRequestSuspend() {
SessionService.suspendWithBehavior(root.suspendBehavior) SessionService.suspendWithBehavior(root.suspendBehavior);
} }
} }
@@ -169,7 +180,7 @@ Singleton {
target: SessionService target: SessionService
function onPrepareForSleep() { function onPrepareForSleep() {
if (SettingsData.lockBeforeSuspend) { if (SettingsData.lockBeforeSuspend) {
root.lockRequested() root.lockRequested();
} }
} }
} }
@@ -178,19 +189,19 @@ Singleton {
target: SettingsData target: SettingsData
function onPreventIdleForMediaChanged() { function onPreventIdleForMediaChanged() {
if (SettingsData.preventIdleForMedia) { if (SettingsData.preventIdleForMedia) {
createMediaInhibitor() createMediaInhibitor();
} else { } else {
destroyMediaInhibitor() destroyMediaInhibitor();
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (!idleMonitorAvailable) { if (!idleMonitorAvailable) {
console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.") console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
} else { } else {
console.info("IdleService: Initialized with idle monitoring support") console.info("IdleService: Initialized with idle monitoring support");
createIdleMonitors() createIdleMonitors();
} }
} }
} }