From f88f1ea951e4771f26d17249191310ca8a263c28 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 9 Dec 2025 11:26:28 -0500 Subject: [PATCH] gamma: display automation state in UI --- quickshell/Common/SettingsData.qml | 1 - quickshell/Common/settings/SettingsSpec.js | 1 - quickshell/Modules/Settings/DisplaysTab.qml | 240 ++++++++++++++++++ quickshell/Modules/Settings/PowerSleepTab.qml | 20 +- quickshell/Services/DMSService.qml | 23 +- quickshell/Services/DisplayService.qml | 16 ++ quickshell/Services/IdleService.qml | 46 ---- 7 files changed, 279 insertions(+), 68 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index b155f1c1..ac642efc 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -255,7 +255,6 @@ Singleton { property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend property string batteryProfileName: "" property bool lockBeforeSuspend: false - property bool preventIdleForMedia: false property bool loginctlLockIntegration: true property bool fadeToLockEnabled: false property int fadeToLockGracePeriod: 5 diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index a7dee5c9..fc9d3d66 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -154,7 +154,6 @@ var SPEC = { batterySuspendBehavior: { def: 0 }, batteryProfileName: { def: "" }, lockBeforeSuspend: { def: false }, - preventIdleForMedia: { def: false }, loginctlLockIntegration: { def: true }, fadeToLockEnabled: { def: false }, fadeToLockGracePeriod: { def: 5 }, diff --git a/quickshell/Modules/Settings/DisplaysTab.qml b/quickshell/Modules/Settings/DisplaysTab.qml index d4a27952..4b6144a5 100644 --- a/quickshell/Modules/Settings/DisplaysTab.qml +++ b/quickshell/Modules/Settings/DisplaysTab.qml @@ -8,6 +8,19 @@ import qs.Widgets Item { id: displaysTab + function formatGammaTime(isoString) { + if (!isoString) + return ""; + try { + const date = new Date(isoString); + if (isNaN(date.getTime())) + return ""; + return date.toLocaleTimeString(Qt.locale(), "HH:mm"); + } catch (e) { + return ""; + } + } + function getBarComponentsFromSettings() { const bars = SettingsData.barConfigs || []; return bars.map(bar => ({ @@ -530,6 +543,233 @@ Item { } } } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.2 + visible: gammaStatusSection.visible + } + + Column { + id: gammaStatusSection + width: parent.width + spacing: Theme.spacingM + visible: DisplayService.nightModeEnabled && DisplayService.gammaCurrentTemp > 0 + + Row { + width: parent.width + spacing: Theme.spacingS + + DankIcon { + name: DisplayService.gammaIsDay ? "light_mode" : "dark_mode" + size: Theme.iconSizeSmall + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Current Status") + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + width: parent.width + spacing: Theme.spacingM + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: tempColumn.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + + Column { + id: tempColumn + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: "device_thermostat" + size: Theme.iconSize + color: Theme.primary + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: DisplayService.gammaCurrentTemp + "K" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: I18n.tr("Current Temp") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: periodColumn.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + + Column { + id: periodColumn + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: DisplayService.gammaIsDay ? "wb_sunny" : "nightlight" + size: Theme.iconSize + color: DisplayService.gammaIsDay ? "#FFA726" : "#7E57C2" + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: DisplayService.gammaIsDay ? I18n.tr("Daytime") : I18n.tr("Night") + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: I18n.tr("Current Period") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + + Row { + width: parent.width + spacing: Theme.spacingM + visible: SessionData.nightModeAutoMode === "location" && (DisplayService.gammaSunriseTime || DisplayService.gammaSunsetTime) + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: sunriseColumn.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + visible: DisplayService.gammaSunriseTime + + Column { + id: sunriseColumn + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: "wb_twilight" + size: Theme.iconSize + color: "#FF7043" + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: displaysTab.formatGammaTime(DisplayService.gammaSunriseTime) + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: I18n.tr("Sunrise") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: sunsetColumn.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + visible: DisplayService.gammaSunsetTime + + Column { + id: sunsetColumn + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: "wb_twilight" + size: Theme.iconSize + color: "#5C6BC0" + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: displaysTab.formatGammaTime(DisplayService.gammaSunsetTime) + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: I18n.tr("Sunset") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + + Rectangle { + width: parent.width + height: nextChangeRow.implicitHeight + Theme.spacingM * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + visible: DisplayService.gammaNextTransition + + Row { + id: nextChangeRow + anchors.centerIn: parent + spacing: Theme.spacingM + + DankIcon { + name: "schedule" + size: Theme.iconSize + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: 2 + anchors.verticalCenter: parent.verticalCenter + + StyledText { + text: I18n.tr("Next Transition") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + } + + StyledText { + text: displaysTab.formatGammaTime(DisplayService.gammaNextTransition) + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + } + } + } + } } } } diff --git a/quickshell/Modules/Settings/PowerSleepTab.qml b/quickshell/Modules/Settings/PowerSleepTab.qml index da093077..47135a76 100644 --- a/quickshell/Modules/Settings/PowerSleepTab.qml +++ b/quickshell/Modules/Settings/PowerSleepTab.qml @@ -61,14 +61,6 @@ Item { } } - SettingsToggleRow { - text: I18n.tr("Prevent idle for media") - description: I18n.tr("Inhibit idle timeout when audio or video is playing") - checked: SettingsData.preventIdleForMedia - visible: IdleService.idleMonitorAvailable - onToggled: checked => SettingsData.set("preventIdleForMedia", checked) - } - SettingsToggleRow { text: I18n.tr("Fade to lock screen") description: I18n.tr("Gradually fade the screen before locking with a configurable grace period") @@ -76,6 +68,14 @@ Item { onToggled: checked => SettingsData.set("fadeToLockEnabled", checked) } + SettingsToggleRow { + text: I18n.tr("Lock before suspend") + description: I18n.tr("Automatically lock the screen when the system prepares to suspend") + checked: SettingsData.lockBeforeSuspend + visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration + onToggled: checked => SettingsData.set("lockBeforeSuspend", checked) + } + SettingsDropdownRow { id: fadeGracePeriodDropdown property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"] @@ -114,14 +114,14 @@ Item { function onCurrentIndexChanged() { const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName; const index = powerProfileDropdown.profileValues.indexOf(currentProfile); - powerProfileDropdown.currentValue = powerProfileDropdown.profileOptions[index] + powerProfileDropdown.currentValue = powerProfileDropdown.profileOptions[index]; } } Component.onCompleted: { const currentProfile = powerCategory.currentIndex === 0 ? SettingsData.acProfileName : SettingsData.batteryProfileName; const index = profileValues.indexOf(currentProfile); - currentValue = profileOptions[index] + currentValue = profileOptions[index]; } onValueChanged: value => { diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml index 5847dad8..ba4877b8 100644 --- a/quickshell/Services/DMSService.qml +++ b/quickshell/Services/DMSService.qml @@ -49,6 +49,7 @@ Singleton { signal extWorkspaceStateUpdate(var data) signal wlrOutputStateUpdate(var data) signal evdevStateUpdate(var data) + signal gammaStateUpdate(var data) signal openUrlRequested(string url) signal appPickerRequested(var data) @@ -267,9 +268,9 @@ Singleton { function removeSubscription(service) { if (activeSubscriptions.includes("all")) { - const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "extworkspace", "browser"] - const filtered = allServices.filter(s => s !== service) - subscribe(filtered) + const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "extworkspace", "browser"]; + const filtered = allServices.filter(s => s !== service); + subscribe(filtered); } else { const filtered = activeSubscriptions.filter(s => s !== service); if (filtered.length === 0) { @@ -289,9 +290,9 @@ Singleton { excludeServices = [excludeServices]; } - const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser"] - const filtered = allServices.filter(s => !excludeServices.includes(s)) - subscribe(filtered) + const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser"]; + const filtered = allServices.filter(s => !excludeServices.includes(s)); + subscribe(filtered); } function handleSubscriptionEvent(response) { @@ -355,16 +356,18 @@ Singleton { if (data.capsLock !== undefined) { capsLockState = data.capsLock; } - evdevStateUpdate(data) + evdevStateUpdate(data); + } else if (service === "gamma") { + gammaStateUpdate(data); } else if (service === "browser.open_requested") { if (data.target) { if (data.requestType === "url" || !data.requestType) { - openUrlRequested(data.target) + openUrlRequested(data.target); } else { - appPickerRequested(data) + appPickerRequested(data); } } else if (data.url) { - openUrlRequested(data.url) + openUrlRequested(data.url); } } } diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml index 5d5e8065..f980b7da 100644 --- a/quickshell/Services/DisplayService.qml +++ b/quickshell/Services/DisplayService.qml @@ -41,6 +41,18 @@ Singleton { property bool automationAvailable: false property bool gammaControlAvailable: false + property var gammaState: ({}) + property int gammaCurrentTemp: gammaState?.currentTemp ?? 0 + property string gammaNextTransition: gammaState?.nextTransition ?? "" + property string gammaSunriseTime: gammaState?.sunriseTime ?? "" + property string gammaSunsetTime: gammaState?.sunsetTime ?? "" + property string gammaDawnTime: gammaState?.dawnTime ?? "" + property string gammaNightTime: gammaState?.nightTime ?? "" + property bool gammaIsDay: gammaState?.isDay ?? true + property real gammaSunPosition: gammaState?.sunPosition ?? 0 + property int gammaLowTemp: gammaState?.config?.LowTemp ?? 0 + property int gammaHighTemp: gammaState?.config?.HighTemp ?? 0 + function markDeviceUserControlled(deviceId) { const newControlled = Object.assign({}, userControlledDevices); newControlled[deviceId] = Date.now(); @@ -809,6 +821,10 @@ Singleton { osdSuppressTimer.restart(); } } + + function onGammaStateUpdate(data) { + root.gammaState = data; + } } // Session Data Connections diff --git a/quickshell/Services/IdleService.qml b/quickshell/Services/IdleService.qml index a7e9af5f..e8484113 100644 --- a/quickshell/Services/IdleService.qml +++ b/quickshell/Services/IdleService.qml @@ -58,43 +58,12 @@ Singleton { property var monitorOffMonitor: null property var lockMonitor: null property var suspendMonitor: null - property var mediaInhibitor: null property var lockComponent: null function wake() { requestMonitorOn(); } - function createMediaInhibitor() { - if (!idleInhibitorAvailable) { - return; - } - - if (mediaInhibitor) { - mediaInhibitor.destroy(); - mediaInhibitor = null; - } - - const inhibitorString = ` - import QtQuick - import Quickshell.Wayland - - IdleInhibitor { - active: false - } - `; - - mediaInhibitor = Qt.createQmlObject(inhibitorString, root, "IdleService.MediaInhibitor"); - mediaInhibitor.active = Qt.binding(() => root.mediaPlaying); - } - - function destroyMediaInhibitor() { - if (mediaInhibitor) { - mediaInhibitor.destroy(); - mediaInhibitor = null; - } - } - function createIdleMonitors() { if (!idleMonitorAvailable) { console.info("IdleService: IdleMonitor not available, skipping creation"); @@ -152,10 +121,6 @@ Singleton { root.requestSuspend(); } }); - - if (SettingsData.preventIdleForMedia) { - createMediaInhibitor(); - } } catch (e) { console.warn("IdleService: Error creating IdleMonitors:", e); } @@ -176,17 +141,6 @@ Singleton { } } - Connections { - target: SettingsData - function onPreventIdleForMediaChanged() { - if (SettingsData.preventIdleForMedia) { - createMediaInhibitor(); - } else { - destroyMediaInhibitor(); - } - } - } - Component.onCompleted: { if (!idleMonitorAvailable) { console.warn("IdleService: IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");