From 7bf73ab14d6f64beb8967b26e95e26f6c79e35a8 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 20 Oct 2025 18:23:26 -0400 Subject: [PATCH] gamma/nightmode: use dms V6 implementation - Scraps gammastep depednency --- .github/workflows/copr-release.yml | 1 - Common/SessionData.qml | 8 + Modules/Settings/DisplaysTab.qml | 34 +-- README.md | 1 - Services/DisplayService.qml | 388 +++++++++++++++-------------- dms.spec | 1 - nix/default.nix | 1 - translations/en.json | 40 ++- translations/template.json | 42 ++++ 9 files changed, 312 insertions(+), 204 deletions(-) diff --git a/.github/workflows/copr-release.yml b/.github/workflows/copr-release.yml index a05fe98a..942bfc16 100644 --- a/.github/workflows/copr-release.yml +++ b/.github/workflows/copr-release.yml @@ -106,7 +106,6 @@ jobs: Recommends: hyprpicker Recommends: matugen Recommends: wl-clipboard - Recommends: gammastep Recommends: NetworkManager Recommends: qt6-qtmultimedia Suggests: qt6ct diff --git a/Common/SessionData.qml b/Common/SessionData.qml index b7e837b3..72543e73 100644 --- a/Common/SessionData.qml +++ b/Common/SessionData.qml @@ -49,6 +49,7 @@ Singleton { property int nightModeEndMinute: 0 property real latitude: 0.0 property real longitude: 0.0 + property bool nightModeUseIPLocation: false property string nightModeLocationProvider: "" property var pinnedApps: [] @@ -112,6 +113,7 @@ Singleton { } latitude = settings.latitude !== undefined ? settings.latitude : 0.0 longitude = settings.longitude !== undefined ? settings.longitude : 0.0 + nightModeUseIPLocation = settings.nightModeUseIPLocation !== undefined ? settings.nightModeUseIPLocation : false nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : "" pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0 @@ -171,6 +173,7 @@ Singleton { "nightModeEndMinute": nightModeEndMinute, "latitude": latitude, "longitude": longitude, + "nightModeUseIPLocation": nightModeUseIPLocation, "nightModeLocationProvider": nightModeLocationProvider, "pinnedApps": pinnedApps, "selectedGpuIndex": selectedGpuIndex, @@ -536,6 +539,11 @@ Singleton { saveSettings() } + function setNightModeUseIPLocation(use) { + nightModeUseIPLocation = use + saveSettings() + } + function setLatitude(lat) { console.log("SessionData: Setting latitude to", lat) latitude = lat diff --git a/Modules/Settings/DisplaysTab.qml b/Modules/Settings/DisplaysTab.qml index ffc63998..8aa67d01 100644 --- a/Modules/Settings/DisplaysTab.qml +++ b/Modules/Settings/DisplaysTab.qml @@ -116,8 +116,9 @@ Item { width: parent.width text: I18n.tr("Night Mode") - description: I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") + description: DisplayService.gammaControlAvailable ? I18n.tr("Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.") : I18n.tr("Gamma control not available. Requires DMS API v6+.") checked: DisplayService.nightModeEnabled + enabled: DisplayService.gammaControlAvailable onToggled: checked => { DisplayService.toggleNightMode() } @@ -136,6 +137,7 @@ Item { spacing: 0 leftPadding: Theme.spacingM rightPadding: Theme.spacingM + visible: DisplayService.gammaControlAvailable DankDropdown { width: parent.width - parent.leftPadding - parent.rightPadding @@ -162,6 +164,7 @@ Item { text: I18n.tr("Automatic Control") description: I18n.tr("Only adjust gamma based on time or location rules.") checked: SessionData.nightModeAutoEnabled + visible: DisplayService.gammaControlAvailable onToggled: checked => { if (checked && !DisplayService.nightModeEnabled) { DisplayService.toggleNightMode() @@ -183,7 +186,7 @@ Item { id: automaticSettings width: parent.width spacing: Theme.spacingS - visible: SessionData.nightModeAutoEnabled + visible: SessionData.nightModeAutoEnabled && DisplayService.gammaControlAvailable Connections { target: SessionData @@ -360,27 +363,28 @@ Item { width: parent.width DankToggle { + id: ipLocationToggle width: parent.width - text: I18n.tr("Auto-location") - description: DisplayService.geoclueAvailable ? I18n.tr("Use automatic location detection (geoclue2)") : I18n.tr("Geoclue service not running - cannot auto-detect location") - checked: SessionData.nightModeLocationProvider === "geoclue2" - enabled: DisplayService.geoclueAvailable + text: I18n.tr("Use IP Location") + description: I18n.tr("Automatically detect location based on IP address") + checked: SessionData.nightModeUseIPLocation || false onToggled: checked => { - if (checked && DisplayService.geoclueAvailable) { - SessionData.setNightModeLocationProvider("geoclue2") - SessionData.setLatitude(0.0) - SessionData.setLongitude(0.0) - } else { - SessionData.setNightModeLocationProvider("") - } - } + SessionData.setNightModeUseIPLocation(checked) + } + + Connections { + target: SessionData + function onNightModeUseIPLocationChanged() { + ipLocationToggle.checked = SessionData.nightModeUseIPLocation + } + } } Column { width: parent.width spacing: Theme.spacingM - visible: SessionData.nightModeLocationProvider !== "geoclue2" leftPadding: Theme.spacingM + visible: !SessionData.nightModeUseIPLocation StyledText { text: I18n.tr("Manual Coordinates") diff --git a/README.md b/README.md index 9883acc0..23279286 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,6 @@ sudo sh -c "curl -L https://github.com/AvengeMedia/dgop/releases/latest/download - `wl-clipboard`: Required for copying various elements to clipboard. - `cava`: Audio visualizer - `cliphist`: Clipboard history -- `gammastep`: Night mode support - `qt6-multimedia`: System sound support ## Compositor Configuration diff --git a/Services/DisplayService.qml b/Services/DisplayService.qml index c0ec9c9b..57886eef 100644 --- a/Services/DisplayService.qml +++ b/Services/DisplayService.qml @@ -38,13 +38,8 @@ Singleton { property bool nightModeEnabled: false property bool automationAvailable: false - property bool geoclueAvailable: false - property bool isAutomaticNightTime: false - - function buildGammastepCommand(gammastepArgs) { - const commandStr = "pkill gammastep; " + ["gammastep"].concat(gammastepArgs).join(" ") - return ["sh", "-c", commandStr] - } + property bool gammaControlAvailable: false + readonly property int dayTemp: 6500 function setBrightnessInternal(percentage, device) { const clampedValue = Math.max(1, Math.min(100, percentage)) @@ -216,34 +211,49 @@ Singleton { // Night Mode Functions - Simplified function enableNightMode() { - if (!automationAvailable) { - gammaStepTestProcess.running = true + if (!gammaControlAvailable) { + ToastService.showWarning("Night mode failed: DMS gamma control not available") return } nightModeEnabled = true SessionData.setNightModeEnabled(true) - // Apply immediately or start automation - if (SessionData.nightModeAutoEnabled) { - startAutomation() - } else { - applyNightModeDirectly() - } + DMSService.sendRequest("wayland.gamma.setEnabled", { + "enabled": true + }, response => { + if (response.error) { + console.error("DisplayService: Failed to enable gamma control:", response.error) + ToastService.showError("Failed to enable night mode: " + response.error) + nightModeEnabled = false + SessionData.setNightModeEnabled(false) + return + } + + if (SessionData.nightModeAutoEnabled) { + startAutomation() + } else { + applyNightModeDirectly() + } + }) } function disableNightMode() { nightModeEnabled = false SessionData.setNightModeEnabled(false) - stopAutomation() - // Nuclear approach - kill ALL gammastep processes multiple times - Quickshell.execDetached(["pkill", "-f", "gammastep"]) - Quickshell.execDetached(["pkill", "-9", "gammastep"]) - Quickshell.execDetached(["killall", "gammastep"]) - // Also stop all related processes - gammaStepProcess.running = false - automationProcess.running = false - gammaStepTestProcess.running = false + + if (!gammaControlAvailable) { + return + } + + DMSService.sendRequest("wayland.gamma.setEnabled", { + "enabled": false + }, response => { + if (response.error) { + console.error("DisplayService: Failed to disable gamma control:", response.error) + ToastService.showError("Failed to disable night mode: " + response.error) + } + }) } function toggleNightMode() { @@ -255,14 +265,35 @@ Singleton { } function applyNightModeDirectly() { - const temperature = SessionData.nightModeTemperature || 4500 - gammaStepProcess.command = buildGammastepCommand(["-m", "wayland", "-O", String(temperature)]) - gammaStepProcess.running = true - } + const temperature = SessionData.nightModeTemperature || 4000 - function resetToNormalMode() { - // Just kill gammastep to return to normal display temperature - Quickshell.execDetached(["pkill", "gammastep"]) + DMSService.sendRequest("wayland.gamma.setManualTimes", { + "sunrise": null, + "sunset": null + }, response => { + if (response.error) { + console.error("DisplayService: Failed to clear manual times:", response.error) + return + } + + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": false + }, response => { + if (response.error) { + console.error("DisplayService: Failed to disable IP location:", response.error) + return + } + + DMSService.sendRequest("wayland.gamma.setTemperature", { + "temp": temperature + }, response => { + if (response.error) { + console.error("DisplayService: Failed to set temperature:", response.error) + ToastService.showError("Failed to set night mode temperature: " + response.error) + } + }) + }) + }) } function startAutomation() { @@ -282,70 +313,103 @@ Singleton { } } - function stopAutomation() { - automationProcess.running = false - gammaStepProcess.running = false - isAutomaticNightTime = false - // Nuclear approach - kill ALL gammastep processes multiple times - Quickshell.execDetached(["pkill", "-f", "gammastep"]) - Quickshell.execDetached(["pkill", "-9", "gammastep"]) - Quickshell.execDetached(["killall", "gammastep"]) - } - function startTimeBasedMode() { - checkTimeBasedMode() + const temperature = SessionData.nightModeTemperature || 4000 + const sunriseHour = SessionData.nightModeEndHour + const sunriseMinute = SessionData.nightModeEndMinute + const sunsetHour = SessionData.nightModeStartHour + const sunsetMinute = SessionData.nightModeStartMinute + + const sunrise = `${String(sunriseHour).padStart(2, '0')}:${String(sunriseMinute).padStart(2, '0')}` + const sunset = `${String(sunsetHour).padStart(2, '0')}:${String(sunsetMinute).padStart(2, '0')}` + + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": false + }, response => { + if (response.error) { + console.error("DisplayService: Failed to disable IP location:", response.error) + return + } + + DMSService.sendRequest("wayland.gamma.setTemperature", { + "low": temperature, + "high": dayTemp + }, response => { + if (response.error) { + console.error("DisplayService: Failed to set temperature:", response.error) + ToastService.showError("Failed to set night mode temperature: " + response.error) + return + } + + DMSService.sendRequest("wayland.gamma.setManualTimes", { + "sunrise": sunrise, + "sunset": sunset + }, response => { + if (response.error) { + console.error("DisplayService: Failed to set manual times:", response.error) + ToastService.showError("Failed to set night mode schedule: " + response.error) + } + }) + }) + }) } function startLocationBasedMode() { - const temperature = SessionData.nightModeTemperature || 4500 + const temperature = SessionData.nightModeTemperature || 4000 const dayTemp = 6500 - if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - automationProcess.command = buildGammastepCommand(["-m", "wayland", "-l", `${SessionData.latitude.toFixed(6)}:${SessionData.longitude.toFixed(6)}`, "-t", `${dayTemp}:${temperature}`, "-v"]) - automationProcess.running = true - return - } - - if (SessionData.nightModeLocationProvider === "geoclue2") { - automationProcess.command = buildGammastepCommand(["-m", "wayland", "-l", "geoclue2", "-t", `${dayTemp}:${temperature}`, "-v"]) - automationProcess.running = true - return - } - - console.warn("DisplayService: Location mode selected but no coordinates or geoclue provider set") - } - - function checkTimeBasedMode() { - if (!nightModeEnabled || !SessionData.nightModeAutoEnabled || SessionData.nightModeAutoMode !== "time") { - return - } - - const currentTime = systemClock.hours * 60 + systemClock.minutes - - const startMinutes = SessionData.nightModeStartHour * 60 + SessionData.nightModeStartMinute - const endMinutes = SessionData.nightModeEndHour * 60 + SessionData.nightModeEndMinute - - let shouldBeNight = false - - if (startMinutes > endMinutes) { - shouldBeNight = (currentTime >= startMinutes) || (currentTime < endMinutes) - } else { - shouldBeNight = (currentTime >= startMinutes) && (currentTime < endMinutes) - } - - if (shouldBeNight !== isAutomaticNightTime) { - isAutomaticNightTime = shouldBeNight - - if (shouldBeNight) { - applyNightModeDirectly() - } else { - resetToNormalMode() + DMSService.sendRequest("wayland.gamma.setManualTimes", { + "sunrise": null, + "sunset": null + }, response => { + if (response.error) { + console.error("DisplayService: Failed to clear manual times:", response.error) + return } - } - } - function detectLocationProviders() { - geoclueDetectionProcess.running = true + DMSService.sendRequest("wayland.gamma.setTemperature", { + "low": temperature, + "high": dayTemp + }, response => { + if (response.error) { + console.error("DisplayService: Failed to set temperature:", response.error) + ToastService.showError("Failed to set night mode temperature: " + response.error) + return + } + + if (SessionData.nightModeUseIPLocation) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": true + }, response => { + if (response.error) { + console.error("DisplayService: Failed to enable IP location:", response.error) + ToastService.showError("Failed to enable IP location: " + response.error) + } + }) + } else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": false + }, response => { + if (response.error) { + console.error("DisplayService: Failed to disable IP location:", response.error) + return + } + + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }, response => { + if (response.error) { + console.error("DisplayService: Failed to set location:", response.error) + ToastService.showError("Failed to set night mode location: " + response.error) + } + }) + }) + } else { + console.warn("DisplayService: Location mode selected but no coordinates set and IP location disabled") + } + }) + }) } function setNightModeAutomationMode(mode) { @@ -353,9 +417,6 @@ Singleton { } function evaluateNightMode() { - // Always stop all processes first to clean slate - stopAutomation() - if (!nightModeEnabled) { return } @@ -369,8 +430,50 @@ Singleton { } } - function checkNightModeAvailability() { - gammastepAvailabilityProcess.running = true + function checkGammaControlAvailability() { + if (!DMSService.isConnected) { + return + } + + if (DMSService.apiVersion < 6) { + gammaControlAvailable = false + automationAvailable = false + return + } + + if (!DMSService.capabilities.includes("gamma")) { + gammaControlAvailable = false + automationAvailable = false + return + } + + DMSService.sendRequest("wayland.gamma.getState", null, response => { + if (response.error) { + gammaControlAvailable = false + automationAvailable = false + console.error("DisplayService: Gamma control not available:", response.error) + } else { + gammaControlAvailable = true + automationAvailable = true + + if (nightModeEnabled) { + DMSService.sendRequest("wayland.gamma.setEnabled", { + "enabled": true + }, enableResponse => { + if (enableResponse.error) { + console.error("DisplayService: Failed to enable gamma control on startup:", enableResponse.error) + return + } + + if (SessionData.nightModeAutoEnabled) { + startAutomation() + } else { + applyNightModeDirectly() + } + }) + } + } + }) } Timer { @@ -392,27 +495,9 @@ Singleton { Component.onCompleted: { ddcDetectionProcess.running = true refreshDevices() - checkNightModeAvailability() - - // Initialize night mode state from session nightModeEnabled = SessionData.nightModeEnabled } - Component.onDestruction: { - gammaStepProcess.running = false - automationProcess.running = false - } - - SystemClock { - id: systemClock - precision: SystemClock.Minutes - onDateChanged: { - if (nightModeEnabled && SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") { - checkTimeBasedMode() - } - } - } - Process { id: ddcDetectionProcess @@ -679,83 +764,20 @@ Singleton { } } - Process { - id: gammastepAvailabilityProcess - command: ["which", "gammastep"] - running: false + Connections { + target: DMSService - onExited: function (exitCode) { - automationAvailable = (exitCode === 0) - if (automationAvailable) { - detectLocationProviders() - - // If night mode should be enabled on startup - if (nightModeEnabled && SessionData.nightModeAutoEnabled) { - startAutomation() - } else if (nightModeEnabled) { - applyNightModeDirectly() - } + function onConnectionStateChanged() { + if (DMSService.isConnected) { + checkGammaControlAvailability() } else { - console.log("DisplayService: gammastep not available") + gammaControlAvailable = false + automationAvailable = false } } - } - Process { - id: geoclueDetectionProcess - command: ["sh", "-c", "busctl --system list | grep -qF org.freedesktop.GeoClue2"] - running: false - - onExited: function (exitCode) { - geoclueAvailable = (exitCode === 0) - } - } - - Process { - id: gammaStepTestProcess - command: ["which", "gammastep"] - running: false - - onExited: function (exitCode) { - if (exitCode === 0) { - automationAvailable = true - nightModeEnabled = true - SessionData.setNightModeEnabled(true) - - if (SessionData.nightModeAutoEnabled) { - startAutomation() - } else { - applyNightModeDirectly() - } - } else { - console.warn("DisplayService: gammastep not found") - ToastService.showWarning("Night mode failed: gammastep not found") - } - } - } - - Process { - id: gammaStepProcess - running: false - - onExited: function (exitCode) { - if (nightModeEnabled && exitCode !== 0 && exitCode !== 15) { - console.warn("DisplayService: Night mode process failed:", exitCode) - } - } - } - - Process { - id: automationProcess - running: false - property string processType: "automation" - - onExited: function (exitCode) { - if (nightModeEnabled && SessionData.nightModeAutoEnabled && exitCode !== 0 && exitCode !== 15) { - console.warn("DisplayService: Night mode automation failed:", exitCode) - // Location mode failed - console.warn("DisplayService: Location-based night mode failed") - } + function onCapabilitiesReceived() { + checkGammaControlAvailability() } } @@ -795,7 +817,7 @@ Singleton { function onLongitudeChanged() { evaluateNightMode() } - function onNightModeLocationProviderChanged() { + function onNightModeUseIPLocationChanged() { evaluateNightMode() } } diff --git a/dms.spec b/dms.spec index 7195f79b..5932eb9d 100644 --- a/dms.spec +++ b/dms.spec @@ -43,7 +43,6 @@ Recommends: quickshell-git Recommends: wl-clipboard # Recommended system packages -Recommends: gammastep Recommends: NetworkManager Recommends: qt6-qtmultimedia Suggests: qt6ct diff --git a/nix/default.nix b/nix/default.nix index ba53ae24..6f24d4d7 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -151,7 +151,6 @@ in { ++ lib.optionals cfg.enableClipboard [pkgs.cliphist pkgs.wl-clipboard] ++ lib.optionals cfg.enableVPN [pkgs.glib pkgs.networkmanager] ++ lib.optional cfg.enableBrightnessControl pkgs.brightnessctl - ++ lib.optional cfg.enableNightMode pkgs.gammastep ++ lib.optional cfg.enableDynamicTheming pkgs.matugen ++ lib.optional cfg.enableAudioWavelength pkgs.cava ++ lib.optional cfg.enableCalendarEvents pkgs.khal diff --git a/translations/en.json b/translations/en.json index 7039b46c..1e5d7469 100644 --- a/translations/en.json +++ b/translations/en.json @@ -386,7 +386,7 @@ { "term": "Back", "context": "Back", - "reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:454", + "reference": "Modules/DankBar/Widgets/SystemTrayBar.qml:467", "comment": "" }, { @@ -860,7 +860,7 @@ { "term": "DEMO MODE - Click anywhere to exit", "context": "DEMO MODE - Click anywhere to exit", - "reference": "Modules/Lock/LockScreenContent.qml:634", + "reference": "Modules/Lock/LockScreenContent.qml:628", "comment": "" }, { @@ -1187,6 +1187,18 @@ "reference": "Modals/FileBrowser/FileInfo.qml:200", "comment": "" }, + { + "term": "Failed to set profile image", + "context": "Failed to set profile image", + "reference": "Services/PortalService.qml:150", + "comment": "" + }, + { + "term": "Failed to set profile image: ", + "context": "Failed to set profile image: ", + "reference": "Services/PortalService.qml:159", + "comment": "" + }, { "term": "Feels Like", "context": "Feels Like", @@ -2141,6 +2153,12 @@ "reference": "Modules/Settings/DankBarTab.qml:88", "comment": "" }, + { + "term": "Permission denied to set profile image.", + "context": "Permission denied to set profile image.", + "reference": "Services/PortalService.qml:155", + "comment": "" + }, { "term": "Personalization", "context": "Personalization", @@ -2297,6 +2315,18 @@ "reference": "Modules/ProcessList/ProcessListView.qml:41", "comment": "" }, + { + "term": "Profile Image Error", + "context": "Profile Image Error", + "reference": "Services/PortalService.qml:162", + "comment": "" + }, + { + "term": "Profile image is too large. Please use a smaller image.", + "context": "Profile image is too large. Please use a smaller image.", + "reference": "Services/PortalService.qml:153", + "comment": "" + }, { "term": "QML, JavaScript, Go", "context": "QML, JavaScript, Go", @@ -2549,6 +2579,12 @@ "reference": "Modules/Settings/PersonalizationTab.qml:930", "comment": "" }, + { + "term": "Selected image file not found.", + "context": "Selected image file not found.", + "reference": "Services/PortalService.qml:157", + "comment": "" + }, { "term": "Separator", "context": "Separator", diff --git a/translations/template.json b/translations/template.json index 2a1225dd..a82e8493 100644 --- a/translations/template.json +++ b/translations/template.json @@ -1385,6 +1385,20 @@ "reference": "", "comment": "" }, + { + "term": "Failed to set profile image", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { + "term": "Failed to set profile image: ", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Feels Like", "translation": "", @@ -2498,6 +2512,13 @@ "reference": "", "comment": "" }, + { + "term": "Permission denied to set profile image.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Personalization", "translation": "", @@ -2680,6 +2701,20 @@ "reference": "", "comment": "" }, + { + "term": "Profile Image Error", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, + { + "term": "Profile image is too large. Please use a smaller image.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "QML, JavaScript, Go", "translation": "", @@ -2974,6 +3009,13 @@ "reference": "", "comment": "" }, + { + "term": "Selected image file not found.", + "translation": "", + "context": "", + "reference": "", + "comment": "" + }, { "term": "Separator", "translation": "",