1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 07:22:50 -05:00

feat: Create new Auto theme mode based on region / time of day

This commit is contained in:
purian23
2026-01-24 16:38:45 -05:00
parent 03cfa55e0b
commit 3413cb7b89
11 changed files with 1747 additions and 3 deletions

View File

@@ -82,6 +82,14 @@ Singleton {
property bool nightModeUseIPLocation: false
property string nightModeLocationProvider: ""
property bool themeModeAutoEnabled: false
property string themeModeAutoMode: "time"
property int themeModeStartHour: 18
property int themeModeStartMinute: 0
property int themeModeEndHour: 6
property int themeModeEndMinute: 0
property bool themeModeShareGammaSettings: true
property var pinnedApps: []
property var barPinnedApps: []
property int dockLauncherPosition: 0
@@ -754,6 +762,41 @@ Singleton {
saveSettings();
}
function setThemeModeAutoEnabled(enabled) {
themeModeAutoEnabled = enabled;
saveSettings();
}
function setThemeModeAutoMode(mode) {
themeModeAutoMode = mode;
saveSettings();
}
function setThemeModeStartHour(hour) {
themeModeStartHour = hour;
saveSettings();
}
function setThemeModeStartMinute(minute) {
themeModeStartMinute = minute;
saveSettings();
}
function setThemeModeEndHour(hour) {
themeModeEndHour = hour;
saveSettings();
}
function setThemeModeEndMinute(minute) {
themeModeEndMinute = minute;
saveSettings();
}
function setThemeModeShareGammaSettings(share) {
themeModeShareGammaSettings = share;
saveSettings();
}
function setPinnedApps(apps) {
pinnedApps = apps;
saveSettings();

View File

@@ -94,6 +94,12 @@ Singleton {
property var matugenColors: ({})
property var _pendingGenerateParams: null
// Theme automation
property bool themeModeAutomationActive: false
// Watch for DMSService connection to retry location setup
property bool dmsServiceWasDisconnected: true
readonly property var dank16: {
const raw = matugenColors?.dank16;
if (!raw)
@@ -125,6 +131,8 @@ Singleton {
readonly property string currentThemeId: customThemeRawData?.id || ""
Component.onCompleted: {
console.warn("THEME AUTOMATION DEBUG: Component.onCompleted STARTING");
console.error("THEME AUTOMATION DEBUG: This is an error test");
Quickshell.execDetached(["mkdir", "-p", stateDir]);
Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen;
@@ -176,6 +184,277 @@ Singleton {
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
switchTheme(SettingsData.currentThemeName, false, false);
}
if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) {
console.error("*** THEME STARTUP: themeModeAutoEnabled = true, starting automation ***");
console.error("*** THEME STARTUP: themeModeAutoMode =", SessionData.themeModeAutoMode);
console.error("*** THEME STARTUP: current isLightMode =", root.isLightMode);
console.error("*** THEME STARTUP: DisplayService.gammaIsDay =", DisplayService?.gammaIsDay);
startThemeModeAutomation();
console.error("*** THEME STARTUP: automation started ***");
} else {
console.error("*** THEME STARTUP: automation NOT enabled ***");
if (typeof SessionData !== "undefined") {
console.error("*** THEME STARTUP: themeModeAutoEnabled =", SessionData.themeModeAutoEnabled);
}
}
}
Connections {
target: SessionData
enabled: typeof SessionData !== "undefined"
function onThemeModeAutoEnabledChanged() {
if (SessionData.themeModeAutoEnabled) {
root.startThemeModeAutomation();
} else {
root.stopThemeModeAutomation();
}
}
function onThemeModeAutoModeChanged() {
if (root.themeModeAutomationActive) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
}
}
function onThemeModeStartHourChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeStartMinuteChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeEndHourChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeEndMinuteChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeShareGammaSettingsChanged() {
if (root.themeModeAutomationActive) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
}
}
function onNightModeStartHourChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeStartMinuteChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeEndHourChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeEndMinuteChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onLatitudeChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
// Update backend with new coordinates
if (!SessionData.nightModeUseIPLocation &&
SessionData.latitude !== 0.0 &&
SessionData.longitude !== 0.0 &&
typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
function onLongitudeChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
// Update backend with new coordinates
if (!SessionData.nightModeUseIPLocation &&
SessionData.latitude !== 0.0 &&
SessionData.longitude !== 0.0 &&
typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
function onNightModeUseIPLocationChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
// Update backend with IP location preference
if (typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": SessionData.nightModeUseIPLocation
}, response => {
if (!response.error && !SessionData.nightModeUseIPLocation &&
SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
// If switching from IP to manual, send coordinates
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
}
// React to gamma backend's isDay state changes for location-based mode
// Note: gamma backend calculates isDay independently from whether
// gamma control is enabled for display adjustments
// Use gammaIsDay property instead of gammaState object for proper change notifications
Connections {
target: DisplayService
enabled: typeof DisplayService !== "undefined" &&
typeof SessionData !== "undefined" &&
SessionData.themeModeAutoEnabled &&
SessionData.themeModeAutoMode === "location" &&
!themeAutoBackendAvailable()
function onGammaIsDayChanged() {
console.error("!!! onGammaIsDayChanged FIRED !!!");
console.error("!!! gammaIsDay =", DisplayService.gammaIsDay);
console.error("!!! current isLightMode =", root.isLightMode);
console.error("!!! Will switch?", root.isLightMode !== DisplayService.gammaIsDay);
if (root.isLightMode !== DisplayService.gammaIsDay) {
console.error("!!! CALLING setLightMode from onGammaIsDayChanged");
root.setLightMode(DisplayService.gammaIsDay, true, true);
}
}
}
Connections {
target: DMSService
enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined"
function onLoginctlEvent(event) {
console.error("### onLoginctlEvent FIRED:", event.event, "###");
// Only handle if automation is enabled
if (!SessionData.themeModeAutoEnabled) return;
// Re-evaluate theme mode when system wakes up or unlocks
// This ensures time-based and location-based (non-shared) modes stay accurate
if (event.event === "unlock" || event.event === "resume") {
if (!themeAutoBackendAvailable()) {
root.evaluateThemeMode();
return;
}
DMSService.sendRequest("theme.auto.trigger", {});
}
}
function onThemeAutoStateUpdate(data) {
if (!SessionData.themeModeAutoEnabled) {
return;
}
applyThemeAutoState(data);
}
function onConnectionStateChanged() {
console.error("@@@ onConnectionStateChanged FIRED @@@");
console.error("@@@ DMSService.isConnected =", DMSService.isConnected);
console.error("@@@ SessionData.themeModeAutoEnabled =", SessionData.themeModeAutoEnabled);
console.error("@@@ SessionData.themeModeAutoMode =", SessionData.themeModeAutoMode);
console.error("@@@ SessionData.latitude =", SessionData.latitude);
console.error("@@@ SessionData.longitude =", SessionData.longitude);
if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") {
root.syncTimeThemeSchedule();
}
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
root.syncLocationThemeSchedule();
}
if (themeAutoBackendAvailable() && SessionData.themeModeAutoEnabled) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
applyThemeAutoState(response.result);
}
});
}
// Only handle if automation is enabled
if (!SessionData.themeModeAutoEnabled) {
console.error("@@@ Automation not enabled, skipping @@@");
return;
}
// When DMSService connects, retry location initialization if in location mode
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
console.error("@@@ RETRYING location setup @@@");
if (SessionData.nightModeUseIPLocation) {
console.error("@@@ Using IP location @@@");
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": true
}, response => {
if (!response.error) {
console.log("Theme automation: IP location enabled after connection");
}
});
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
console.error("@@@ Using manual coordinates:", SessionData.latitude, SessionData.longitude, "@@@");
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": false
}, response => {
console.error("@@@ setUseIPLocation(false) response:", JSON.stringify(response), "@@@");
if (!response.error) {
console.error("@@@ Sending setLocation request @@@");
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
}, locationResponse => {
console.error("@@@ setLocation response:", JSON.stringify(locationResponse), "@@@");
});
}
});
} else {
console.error("@@@ No location configured - nightModeUseIPLocation:", SessionData.nightModeUseIPLocation, "@@@");
}
}
}
}
function applyGreeterTheme(themeName) {
@@ -534,17 +813,27 @@ Singleton {
}
function setLightMode(light, savePrefs = true, enableTransition = false) {
console.error(">>> setLightMode CALLED: light =", light, ", savePrefs =", savePrefs, ", enableTransition =", enableTransition);
console.error(">>> BEFORE: root.isLightMode =", root.isLightMode);
if (enableTransition) {
screenTransition();
lightModeTransitionTimer.lightMode = light;
lightModeTransitionTimer.savePrefs = savePrefs;
lightModeTransitionTimer.restart();
console.error(">>> setLightMode: Starting transition timer");
return;
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
console.error(">>> setLightMode: isGreeterMode =", isGreeterMode, ", will save =", savePrefs && typeof SessionData !== "undefined" && !isGreeterMode);
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
console.error(">>> setLightMode: Calling SessionData.setLightMode(", light, ")");
SessionData.setLightMode(light);
console.error(">>> setLightMode: AFTER SessionData.setLightMode - root.isLightMode =", root.isLightMode);
}
if (!isGreeterMode) {
// Skip with matugen because, our script runner will do it.
if (!matugenAvailable) {
@@ -552,6 +841,8 @@ Singleton {
}
generateSystemThemesFromCurrentTheme();
}
console.error(">>> setLightMode DONE: root.isLightMode =", root.isLightMode);
}
function toggleLightMode(savePrefs = true) {
@@ -1453,4 +1744,343 @@ Singleton {
root.switchTheme(defaultTheme, true, false);
}
}
// Theme mode automation functions
function themeAutoBackendAvailable() {
return typeof DMSService !== "undefined" &&
DMSService.isConnected &&
Array.isArray(DMSService.capabilities) &&
DMSService.capabilities.includes("theme.auto");
}
function applyThemeAutoState(state) {
if (!state) {
return;
}
if (state.config && state.config.mode && state.config.mode !== SessionData.themeModeAutoMode) {
return;
}
if (state.isLight !== undefined && root.isLightMode !== state.isLight) {
root.setLightMode(state.isLight, true, true);
}
}
function syncTimeThemeSchedule() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
return;
}
if (!DMSService.isConnected) {
return;
}
const timeModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "time";
if (!timeModeActive) {
return;
}
DMSService.sendRequest("theme.auto.setMode", {"mode": "time"});
const shareSettings = SessionData.themeModeShareGammaSettings;
const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour;
const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour;
const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
DMSService.sendRequest("theme.auto.setSchedule", {
"startHour": startHour,
"startMinute": startMinute,
"endHour": endHour,
"endMinute": endMinute
}, response => {
if (response && response.error) {
console.error("Theme automation: Failed to sync time schedule:", response.error);
}
});
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": true});
DMSService.sendRequest("theme.auto.trigger", {});
}
function syncLocationThemeSchedule() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
return;
}
if (!DMSService.isConnected) {
return;
}
const locationModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location";
if (!locationModeActive) {
return;
}
DMSService.sendRequest("theme.auto.setMode", {"mode": "location"});
if (SessionData.nightModeUseIPLocation) {
DMSService.sendRequest("theme.auto.setUseIPLocation", {"use": true});
} else {
DMSService.sendRequest("theme.auto.setUseIPLocation", {"use": false});
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("theme.auto.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
}
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": true});
DMSService.sendRequest("theme.auto.trigger", {});
}
function evaluateThemeMode() {
if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) {
return;
}
if (themeAutoBackendAvailable()) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
applyThemeAutoState(response.result);
}
});
return;
}
const mode = SessionData.themeModeAutoMode;
// Location mode uses gamma backend or JavaScript fallback
// The Connections block also handles real-time gamma state updates
if (mode === "location") {
evaluateLocationBasedThemeMode();
} else {
// Time-based mode
evaluateTimeBasedThemeMode();
}
}
function evaluateLocationBasedThemeMode() {
console.error("=== THEME AUTOMATION DEBUG: evaluateLocationBasedThemeMode START ===");
console.error("THEME AUTO: DisplayService exists?", typeof DisplayService !== "undefined");
console.error("THEME AUTO: gammaState =", JSON.stringify(DisplayService?.gammaState || {}));
console.error("THEME AUTO: gammaIsDay =", DisplayService?.gammaIsDay);
console.error("THEME AUTO: current Theme.isLightMode =", root.isLightMode);
// When using IP location or manual coordinates, the gamma backend
// can calculate sun position and isDay independently from whether
// gamma control is enabled for display adjustments
// Try to use gamma backend's isDay state (works with both IP and manual location)
// Use gammaIsDay property for reliable value access
if (typeof DisplayService !== "undefined") {
const shouldBeLight = DisplayService.gammaIsDay;
console.error("THEME AUTO: shouldBeLight =", shouldBeLight);
console.error("THEME AUTO: Will switch?", root.isLightMode !== shouldBeLight);
if (root.isLightMode !== shouldBeLight) {
console.error("THEME AUTO: CALLING setLightMode(", shouldBeLight, ", true, true)");
root.setLightMode(shouldBeLight, true, true);
console.error("THEME AUTO: AFTER setLightMode - isLightMode =", root.isLightMode);
} else {
console.error("THEME AUTO: No change needed - already in correct mode");
}
console.error("=== THEME AUTOMATION DEBUG: evaluateLocationBasedThemeMode END ===");
return;
}
// Fallback: Use JavaScript sun calculation with manual coordinates
// This is less accurate but works if backend isn't available
if (!SessionData.nightModeUseIPLocation &&
SessionData.latitude !== 0.0 &&
SessionData.longitude !== 0.0) {
const shouldBeLight = calculateIsDaytime(
SessionData.latitude,
SessionData.longitude
);
if (root.isLightMode !== shouldBeLight) {
root.setLightMode(shouldBeLight, true, true);
}
return;
}
// Warn about missing configuration
if (root.themeModeAutomationActive) {
if (SessionData.nightModeUseIPLocation) {
console.warn("Theme automation: Waiting for IP location from backend");
} else {
console.warn("Theme automation: Location mode requires coordinates");
}
}
}
function evaluateTimeBasedThemeMode() {
const shareSettings = SessionData.themeModeShareGammaSettings;
// Get time settings (shared or independent)
const startHour = shareSettings ?
SessionData.nightModeStartHour : SessionData.themeModeStartHour;
const startMinute = shareSettings ?
SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
const endHour = shareSettings ?
SessionData.nightModeEndHour : SessionData.themeModeEndHour;
const endMinute = shareSettings ?
SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
const now = new Date();
const currentMinutes = now.getHours() * 60 + now.getMinutes();
const startMinutes = startHour * 60 + startMinute;
const endMinutes = endHour * 60 + endMinute;
// Light mode is OUTSIDE the dark period
let shouldBeLight;
if (startMinutes < endMinutes) {
// Normal case: dark period within same day (e.g., 01:00-05:00)
shouldBeLight = currentMinutes < startMinutes || currentMinutes >= endMinutes;
} else {
// Overnight case: dark period crosses midnight (e.g., 18:00-06:00)
shouldBeLight = currentMinutes >= endMinutes && currentMinutes < startMinutes;
}
if (root.isLightMode !== shouldBeLight) {
root.setLightMode(shouldBeLight, true, true);
}
}
function calculateIsDaytime(lat, lng) {
const now = new Date();
const start = new Date(now.getFullYear(), 0, 0);
const diff = now - start;
const dayOfYear = Math.floor(diff / 86400000);
const latRad = lat * Math.PI / 180;
// Solar declination approximation
const declination = 23.45 * Math.sin((360/365) * (dayOfYear - 81) * Math.PI / 180);
const declinationRad = declination * Math.PI / 180;
// Hour angle at sunrise/sunset
const cosHourAngle = -Math.tan(latRad) * Math.tan(declinationRad);
// Handle polar conditions
if (cosHourAngle > 1) {
return false; // Polar night
}
if (cosHourAngle < -1) {
return true; // Midnight sun
}
const hourAngle = Math.acos(cosHourAngle);
const hourAngleDeg = hourAngle * 180 / Math.PI;
// Sunrise/sunset in decimal hours (solar noon ± hour angle)
const sunriseHour = 12 - hourAngleDeg / 15;
const sunsetHour = 12 + hourAngleDeg / 15;
// Adjust for longitude (rough approximation)
const timeZoneOffset = now.getTimezoneOffset() / 60;
const localSunrise = sunriseHour - lng / 15 - timeZoneOffset;
const localSunset = sunsetHour - lng / 15 - timeZoneOffset;
const currentHour = now.getHours() + now.getMinutes() / 60;
// Normalize hours to 0-24 range
const normalizeSunrise = ((localSunrise % 24) + 24) % 24;
const normalizeSunset = ((localSunset % 24) + 24) % 24;
return currentHour >= normalizeSunrise && currentHour < normalizeSunset;
}
// Helper function to send location to backend
function sendLocationToBackend() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
console.error("$$$ sendLocationToBackend: SessionData or DMSService unavailable $$$");
return false;
}
if (!DMSService.isConnected) {
console.error("$$$ sendLocationToBackend: DMSService not connected yet $$$");
return false;
}
console.error("$$$ sendLocationToBackend: SENDING location to backend $$$");
if (SessionData.nightModeUseIPLocation) {
console.error("$$$ Using IP location $$$");
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {"use": true}, response => {
console.error("$$$ IP location response:", JSON.stringify(response), "$$$");
});
return true;
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
console.error("$$$ Using manual coords:", SessionData.latitude, SessionData.longitude, "$$$");
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {"use": false}, response => {
if (!response.error) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
}, locResp => {
console.error("$$$ setLocation response:", JSON.stringify(locResp), "$$$");
});
}
});
return true;
}
console.error("$$$ sendLocationToBackend: No location configured $$$");
return false;
}
Timer {
id: locationRetryTimer
interval: 1000
repeat: true
running: false
property int retryCount: 0
onTriggered: {
console.error("$$$ locationRetryTimer triggered, attempt", retryCount + 1, "$$$");
if (root.sendLocationToBackend()) {
console.error("$$$ Location sent successfully, stopping retry timer $$$");
stop();
retryCount = 0;
root.evaluateThemeMode();
} else {
retryCount++;
if (retryCount >= 10) {
console.error("$$$ Giving up after 10 retries $$$");
stop();
retryCount = 0;
}
}
}
}
function startThemeModeAutomation() {
console.error("XYZABC NEW startThemeModeAutomation VERSION XYZABC");
root.themeModeAutomationActive = true;
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
// Try to send location immediately
const sent = root.sendLocationToBackend();
console.error("XYZABC sendLocationToBackend returned:", sent, "XYZABC");
// If it failed (likely because DMSService not connected), retry
if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") {
console.error("XYZABC Starting retry timer XYZABC");
locationRetryTimer.start();
} else {
console.error("XYZABC Calling evaluateThemeMode XYZABC");
// Evaluate theme mode immediately
root.evaluateThemeMode();
}
}
function stopThemeModeAutomation() {
root.themeModeAutomationActive = false;
if (typeof DMSService !== "undefined" && DMSService.isConnected) {
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": false});
}
}
}

View File

@@ -35,6 +35,14 @@ var SPEC = {
nightModeUseIPLocation: { def: false },
nightModeLocationProvider: { def: "" },
themeModeAutoEnabled: { def: false },
themeModeAutoMode: { def: "time" },
themeModeStartHour: { def: 18 },
themeModeStartMinute: { def: 0 },
themeModeEndHour: { def: 6 },
themeModeEndMinute: { def: 0 },
themeModeShareGammaSettings: { def: true },
weatherLocation: { def: "New York, NY" },
weatherCoordinates: { def: "40.7128,-74.0060" },

View File

@@ -962,6 +962,368 @@ Item {
}
}
SettingsCard {
tab: "theme"
tags: ["automatic", "color", "mode", "schedule", "sunrise", "sunset"]
title: I18n.tr("Automatic Color Mode")
settingKey: "automaticColorMode"
iconName: "schedule"
Column {
width: parent.width
spacing: Theme.spacingM
DankToggle {
id: themeModeAutoToggle
width: parent.width
text: I18n.tr("Enable Automatic Switching")
description: I18n.tr("Automatically switch between light and dark modes based on time or sunrise/sunset")
checked: SessionData.themeModeAutoEnabled
onToggled: checked => {
SessionData.setThemeModeAutoEnabled(checked);
}
Connections {
target: SessionData
function onThemeModeAutoEnabledChanged() {
themeModeAutoToggle.checked = SessionData.themeModeAutoEnabled;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoEnabled
DankToggle {
width: parent.width
text: I18n.tr("Share Gamma Control Settings")
description: I18n.tr("Use the same time and location settings as gamma control")
checked: SessionData.themeModeShareGammaSettings
onToggled: checked => {
SessionData.setThemeModeShareGammaSettings(checked);
}
}
Item {
width: parent.width
height: 45 + Theme.spacingM
DankTabBar {
id: themeModeTabBar
width: 200
height: 45
anchors.horizontalCenter: parent.horizontalCenter
model: [
{ "text": "Time", "icon": "access_time" },
{ "text": "Location", "icon": "place" }
]
Component.onCompleted: {
currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
Qt.callLater(updateIndicator);
}
onTabClicked: index => {
SessionData.setThemeModeAutoMode(index === 1 ? "location" : "time");
currentIndex = index;
}
Connections {
target: SessionData
function onThemeModeAutoModeChanged() {
themeModeTabBar.currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
Qt.callLater(themeModeTabBar.updateIndicator);
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoMode === "time" && !SessionData.themeModeShareGammaSettings
Column {
spacing: Theme.spacingXS
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: Theme.spacingM
StyledText {
text: ""
width: 80
height: 20
}
StyledText {
text: I18n.tr("Hour")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 70
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: I18n.tr("Minute")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 70
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Dark Start")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: 80
height: 40
verticalAlignment: Text.AlignVCenter
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeStartHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++) hours.push(i.toString());
return hours;
}
onValueChanged: value => {
SessionData.setThemeModeStartHour(parseInt(value));
}
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeStartMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: value => {
SessionData.setThemeModeStartMinute(parseInt(value));
}
}
}
Row {
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Light Start")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: 80
height: 40
verticalAlignment: Text.AlignVCenter
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeEndHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++) hours.push(i.toString());
return hours;
}
onValueChanged: value => {
SessionData.setThemeModeEndHour(parseInt(value));
}
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeEndMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: value => {
SessionData.setThemeModeEndMinute(parseInt(value));
}
}
}
}
StyledText {
text: I18n.tr("Light mode will be active from Light Start to Dark Start")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoMode === "location" && !SessionData.themeModeShareGammaSettings
DankToggle {
id: themeModeIpLocationToggle
width: parent.width
text: I18n.tr("Use IP Location")
description: I18n.tr("Automatically detect location based on IP address")
checked: SessionData.nightModeUseIPLocation || false
onToggled: checked => {
SessionData.setNightModeUseIPLocation(checked);
}
Connections {
target: SessionData
function onNightModeUseIPLocationChanged() {
themeModeIpLocationToggle.checked = SessionData.nightModeUseIPLocation;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: !SessionData.nightModeUseIPLocation
StyledText {
text: I18n.tr("Manual Coordinates")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
Row {
spacing: Theme.spacingL
anchors.horizontalCenter: parent.horizontalCenter
Column {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Latitude")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.latitude.toString()
placeholderText: "0.0"
onEditingFinished: {
const lat = parseFloat(text);
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
SessionData.setLatitude(lat);
}
}
}
}
Column {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Longitude")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.longitude.toString()
placeholderText: "0.0"
onEditingFinished: {
const lon = parseFloat(text);
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
SessionData.setLongitude(lon);
}
}
}
}
}
StyledText {
text: I18n.tr("Uses sunrise/sunset times to automatically adjust theme mode based on your location.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
text: I18n.tr("Light mode will be active from sunrise to sunset")
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
visible: SessionData.nightModeUseIPLocation || (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0)
}
}
StyledText {
width: parent.width
text: I18n.tr("Using shared settings from Gamma Control")
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
visible: SessionData.themeModeShareGammaSettings
}
Rectangle {
width: parent.width
height: statusColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: statusColumn
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: SessionData.isLightMode ? "light_mode" : "dark_mode"
size: Theme.iconSize
color: SessionData.isLightMode ? "#FFA726" : "#7E57C2"
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: SessionData.isLightMode ? I18n.tr("Light Mode Active") : I18n.tr("Dark Mode Active")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Automation: ") + (SessionData.themeModeAutoEnabled ? I18n.tr("Enabled") : I18n.tr("Disabled"))
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
}
SettingsCard {
tab: "theme"
tags: ["light", "dark", "mode", "appearance"]

View File

@@ -56,6 +56,7 @@ Singleton {
signal wlrOutputStateUpdate(var data)
signal evdevStateUpdate(var data)
signal gammaStateUpdate(var data)
signal themeAutoStateUpdate(var data)
signal openUrlRequested(string url)
signal appPickerRequested(var data)
signal screensaverStateUpdate(var data)
@@ -64,7 +65,7 @@ Singleton {
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -304,7 +305,7 @@ Singleton {
excludeServices = [excludeServices];
}
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
const filtered = allServices.filter(s => !excludeServices.includes(s));
subscribe(filtered);
}
@@ -373,6 +374,8 @@ Singleton {
evdevStateUpdate(data);
} else if (service === "gamma") {
gammaStateUpdate(data);
} else if (service === "theme.auto") {
themeAutoStateUpdate(data);
} else if (service === "browser.open_requested") {
if (data.target) {
if (data.requestType === "url" || !data.requestType) {