summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml1170
1 files changed, 1170 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml
new file mode 100644
index 0000000..553dd26
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml
@@ -0,0 +1,1170 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+
+Singleton {
+ id: root
+
+ property bool brightnessAvailable: devices.length > 0
+ property var devices: []
+ property var deviceBrightness: ({})
+ property var deviceBrightnessUserSet: ({})
+ property var deviceMaxCache: ({})
+ property var userControlledDevices: ({})
+ property var pendingOsdDevices: ({})
+ property int brightnessVersion: 0
+ property string currentDevice: ""
+ property string lastIpcDevice: ""
+ property int brightnessLevel: {
+ brightnessVersion;
+ const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice);
+ if (!deviceToUse) {
+ return 50;
+ }
+
+ return getDeviceBrightness(deviceToUse);
+ }
+ property int maxBrightness: 100
+ property bool brightnessInitialized: false
+ property bool suppressOsd: true
+
+ signal brightnessChanged(bool showOsd)
+ signal deviceSwitched
+
+ property bool nightModeActive: nightModeEnabled
+
+ property bool nightModeEnabled: false
+ 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();
+ userControlledDevices = newControlled;
+ }
+
+ function isDeviceUserControlled(deviceId) {
+ const controlTime = userControlledDevices[deviceId];
+ if (!controlTime) {
+ return false;
+ }
+ return (Date.now() - controlTime) < 1000;
+ }
+
+ function clearDeviceUserControlled(deviceId) {
+ const newControlled = Object.assign({}, userControlledDevices);
+ delete newControlled[deviceId];
+ userControlledDevices = newControlled;
+ }
+
+ function markDevicePendingOsd(deviceId) {
+ const newPending = Object.assign({}, pendingOsdDevices);
+ newPending[deviceId] = true;
+ pendingOsdDevices = newPending;
+ }
+
+ function clearDevicePendingOsd(deviceId) {
+ const newPending = Object.assign({}, pendingOsdDevices);
+ delete newPending[deviceId];
+ pendingOsdDevices = newPending;
+ }
+
+ function updateSingleDevice(device) {
+ if (device.class === "leds") {
+ return;
+ }
+
+ const isUserControlled = isDeviceUserControlled(device.id);
+ if (isUserControlled) {
+ return;
+ }
+
+ const deviceIndex = devices.findIndex(d => d.id === device.id);
+ if (deviceIndex !== -1) {
+ const newDevices = [...devices];
+ const cachedMax = deviceMaxCache[device.id];
+
+ let displayMax = cachedMax || (device.class === "ddc" ? device.max : 100);
+ if (displayMax > 0 && !cachedMax) {
+ const newCache = Object.assign({}, deviceMaxCache);
+ newCache[device.id] = displayMax;
+ deviceMaxCache = newCache;
+ }
+
+ newDevices[deviceIndex] = {
+ "id": device.id,
+ "name": device.id,
+ "class": device.class,
+ "current": device.current,
+ "percentage": device.currentPercent,
+ "max": device.max,
+ "backend": device.backend,
+ "displayMax": displayMax
+ };
+ devices = newDevices;
+ }
+
+ const isExponential = SessionData.getBrightnessExponential(device.id);
+ const userSetValue = deviceBrightnessUserSet[device.id];
+
+ let displayValue = device.currentPercent;
+ if (isExponential) {
+ if (userSetValue !== undefined) {
+ const exponent = SessionData.getBrightnessExponent(device.id);
+ const expectedHardware = Math.round(Math.pow(userSetValue / 100.0, exponent) * 100.0);
+ if (Math.abs(device.currentPercent - expectedHardware) > 2) {
+ const newUserSet = Object.assign({}, deviceBrightnessUserSet);
+ delete newUserSet[device.id];
+ deviceBrightnessUserSet = newUserSet;
+ SessionData.clearBrightnessUserSetValue(device.id);
+ displayValue = linearToExponential(device.currentPercent, device.id);
+ } else {
+ displayValue = userSetValue;
+ }
+ } else {
+ displayValue = linearToExponential(device.currentPercent, device.id);
+ }
+ }
+
+ const oldValue = deviceBrightness[device.id];
+ const newBrightness = Object.assign({}, deviceBrightness);
+ newBrightness[device.id] = displayValue;
+ deviceBrightness = newBrightness;
+ brightnessVersion++;
+
+ const isPendingOsd = pendingOsdDevices[device.id] === true;
+ if (isPendingOsd) {
+ clearDevicePendingOsd(device.id);
+ if (!suppressOsd) {
+ brightnessChanged(true);
+ }
+ return;
+ }
+
+ if (!brightnessInitialized || oldValue === displayValue) {
+ return;
+ }
+ if (suppressOsd) {
+ return;
+ }
+ brightnessChanged(true);
+ }
+
+ function updateFromBrightnessState(state) {
+ if (!state || !state.devices) {
+ return;
+ }
+
+ const newMaxCache = Object.assign({}, deviceMaxCache);
+ devices = state.devices.map(d => {
+ const cachedMax = deviceMaxCache[d.id];
+ let displayMax = cachedMax || (d.class === "ddc" ? d.max : 100);
+ if (displayMax > 0 && !cachedMax) {
+ newMaxCache[d.id] = displayMax;
+ }
+ return {
+ "id": d.id,
+ "name": d.id,
+ "class": d.class,
+ "current": d.current,
+ "percentage": d.currentPercent,
+ "max": d.max,
+ "backend": d.backend,
+ "displayMax": displayMax
+ };
+ });
+ deviceMaxCache = newMaxCache;
+
+ const newBrightness = {};
+ let anyDeviceBrightnessChanged = false;
+
+ for (const device of state.devices) {
+ const isExponential = SessionData.getBrightnessExponential(device.id);
+ const userSetValue = deviceBrightnessUserSet[device.id];
+ const oldValue = deviceBrightness[device.id];
+
+ if (isExponential) {
+ if (userSetValue !== undefined) {
+ newBrightness[device.id] = userSetValue;
+ } else {
+ newBrightness[device.id] = linearToExponential(device.currentPercent, device.id);
+ }
+ } else {
+ newBrightness[device.id] = device.currentPercent;
+ }
+
+ const newValue = newBrightness[device.id];
+ if (oldValue !== undefined && oldValue !== newValue) {
+ anyDeviceBrightnessChanged = true;
+ }
+ }
+ deviceBrightness = newBrightness;
+ brightnessVersion++;
+
+ brightnessAvailable = devices.length > 0;
+
+ if (devices.length > 0 && !currentDevice) {
+ const lastDevice = SessionData.lastBrightnessDevice || "";
+ const deviceExists = devices.some(d => d.id === lastDevice);
+ if (deviceExists) {
+ setCurrentDevice(lastDevice, false);
+ } else {
+ const backlight = devices.find(d => d.class === "backlight");
+ const nonKbdDevice = devices.find(d => !d.id.includes("kbd"));
+ const defaultDevice = backlight || nonKbdDevice || devices[0];
+ setCurrentDevice(defaultDevice.id, false);
+ }
+ }
+
+ const shouldShowOsd = brightnessInitialized && anyDeviceBrightnessChanged;
+
+ if (!brightnessInitialized) {
+ brightnessInitialized = true;
+ }
+
+ if (shouldShowOsd) {
+ brightnessChanged(true);
+ }
+ }
+
+ function setBrightness(percentage, device, suppressOsd) {
+ const actualDevice = device === "" ? getDefaultDevice() : (device || currentDevice || getDefaultDevice());
+ if (!actualDevice) {
+ console.warn("DisplayService: No device selected for brightness change");
+ return;
+ }
+
+ if (actualDevice !== lastIpcDevice) {
+ lastIpcDevice = actualDevice;
+ }
+
+ const deviceInfo = getCurrentDeviceInfoByName(actualDevice);
+ const isExponential = SessionData.getBrightnessExponential(actualDevice);
+
+ let minValue = 0;
+ let maxValue = 100;
+
+ switch (true) {
+ case isExponential:
+ minValue = 1;
+ maxValue = 100;
+ break;
+ default:
+ minValue = (deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc")) ? 1 : 0;
+ maxValue = deviceInfo?.displayMax || 100;
+ break;
+ }
+
+ if (maxValue <= 0) {
+ console.warn("DisplayService: Invalid max value for device", actualDevice, "- skipping brightness change");
+ return;
+ }
+
+ const clampedValue = Math.max(minValue, Math.min(maxValue, percentage));
+
+ if (!DMSService.isConnected) {
+ console.warn("DisplayService: Not connected to DMS");
+ return;
+ }
+
+ const isLedDevice = deviceInfo?.class === "leds";
+
+ if (suppressOsd) {
+ markDeviceUserControlled(actualDevice);
+ } else if (!isLedDevice) {
+ markDevicePendingOsd(actualDevice);
+ }
+
+ const newBrightness = Object.assign({}, deviceBrightness);
+ newBrightness[actualDevice] = clampedValue;
+ deviceBrightness = newBrightness;
+ brightnessVersion++;
+
+ if (isLedDevice && !suppressOsd) {
+ brightnessChanged(true);
+ }
+
+ if (isExponential) {
+ const newUserSet = Object.assign({}, deviceBrightnessUserSet);
+ newUserSet[actualDevice] = clampedValue;
+ deviceBrightnessUserSet = newUserSet;
+ SessionData.setBrightnessUserSetValue(actualDevice, clampedValue);
+ }
+
+ const params = {
+ "device": actualDevice,
+ "percent": clampedValue
+ };
+ if (isExponential) {
+ params.exponential = true;
+ params.exponent = SessionData.getBrightnessExponent(actualDevice);
+ }
+
+ DMSService.sendRequest("brightness.setBrightness", params, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to set brightness:", response.error);
+ ToastService.showError(I18n.tr("Failed to set brightness"), response.error, "", "brightness");
+ } else {
+ ToastService.dismissCategory("brightness");
+ }
+ });
+ }
+
+ function setCurrentDevice(deviceName, saveToSession = false) {
+ if (currentDevice === deviceName) {
+ return;
+ }
+
+ currentDevice = deviceName;
+ lastIpcDevice = deviceName;
+
+ if (saveToSession) {
+ SessionData.setLastBrightnessDevice(deviceName);
+ }
+
+ deviceSwitched();
+ }
+
+ function getDeviceBrightness(deviceName) {
+ if (!deviceName) {
+ return 50;
+ }
+
+ if (deviceName in deviceBrightness) {
+ return deviceBrightness[deviceName];
+ }
+
+ return 50;
+ }
+
+ function linearToExponential(linearPercent, deviceName) {
+ const exponent = SessionData.getBrightnessExponent(deviceName);
+ const hardwarePercent = linearPercent / 100.0;
+ const normalizedPercent = Math.pow(hardwarePercent, 1.0 / exponent);
+ return Math.round(normalizedPercent * 100.0);
+ }
+
+ function getDefaultDevice() {
+ for (const device of devices) {
+ if (device.class === "backlight") {
+ return device.id;
+ }
+ }
+ return devices.length > 0 ? devices[0].id : "";
+ }
+
+ function getPinnedDeviceForFocusedScreen() {
+ const focusedScreen = CompositorService.getFocusedScreen();
+ if (!focusedScreen)
+ return "";
+
+ const pins = SettingsData.brightnessDevicePins || {};
+ const screenKey = SettingsData.getScreenDisplayName(focusedScreen);
+ if (!screenKey)
+ return "";
+
+ const pinnedDevice = pins[screenKey];
+ if (!pinnedDevice)
+ return "";
+
+ const deviceExists = devices.some(d => d.id === pinnedDevice);
+ if (!deviceExists)
+ return "";
+
+ return pinnedDevice;
+ }
+
+ function getPreferredDevice() {
+ const pinned = getPinnedDeviceForFocusedScreen();
+ if (pinned)
+ return pinned;
+
+ return getDefaultDevice();
+ }
+
+ function getCurrentDeviceInfo() {
+ const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice);
+ if (!deviceToUse) {
+ return null;
+ }
+
+ for (const device of devices) {
+ if (device.id === deviceToUse) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ function isCurrentDeviceReady() {
+ const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice);
+ return deviceToUse !== "";
+ }
+
+ function getCurrentDeviceInfoByName(deviceName) {
+ if (!deviceName) {
+ return null;
+ }
+
+ for (const device of devices) {
+ if (device.id === deviceName) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ function getDeviceMax(deviceName) {
+ const deviceInfo = getCurrentDeviceInfoByName(deviceName);
+ if (!deviceInfo) {
+ return 100;
+ }
+ return deviceInfo.displayMax || 100;
+ }
+
+ // Night Mode Functions - Simplified
+ function enableNightMode() {
+ if (!gammaControlAvailable) {
+ ToastService.showWarning("Night mode failed: DMS gamma control not available");
+ return;
+ }
+
+ nightModeEnabled = true;
+ SessionData.setNightModeEnabled(true);
+
+ DMSService.sendRequest("wayland.gamma.setEnabled", {
+ "enabled": true
+ }, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to enable gamma control:", response.error);
+ ToastService.showError(I18n.tr("Failed to enable night mode"), response.error, "", "night-mode");
+ nightModeEnabled = false;
+ SessionData.setNightModeEnabled(false);
+ return;
+ }
+ ToastService.dismissCategory("night-mode");
+
+ if (SessionData.nightModeAutoEnabled) {
+ startAutomation();
+ } else {
+ applyNightModeDirectly();
+ }
+ });
+ }
+
+ function disableNightMode() {
+ nightModeEnabled = false;
+ SessionData.setNightModeEnabled(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(I18n.tr("Failed to disable night mode"), response.error, "", "night-mode");
+ } else {
+ ToastService.dismissCategory("night-mode");
+ }
+ });
+ }
+
+ function toggleNightMode() {
+ if (nightModeEnabled) {
+ disableNightMode();
+ } else {
+ enableNightMode();
+ }
+ }
+
+ function applyNightModeDirectly() {
+ const temperature = SessionData.nightModeTemperature || 4000;
+
+ 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", {
+ "low": temperature,
+ "high": temperature
+ }, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to set temperature:", response.error);
+ ToastService.showError(I18n.tr("Failed to set night mode temperature"), response.error, "", "night-mode");
+ } else {
+ ToastService.dismissCategory("night-mode");
+ }
+ });
+ });
+ });
+ }
+
+ function startAutomation() {
+ if (!automationAvailable) {
+ return;
+ }
+
+ const mode = SessionData.nightModeAutoMode || "time";
+
+ switch (mode) {
+ case "time":
+ startTimeBasedMode();
+ break;
+ case "location":
+ startLocationBasedMode();
+ break;
+ }
+ }
+
+ function startTimeBasedMode() {
+ const temperature = SessionData.nightModeTemperature || 4000;
+ const highTemp = SessionData.nightModeHighTemperature || 6500;
+ 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": highTemp
+ }, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to set temperature:", response.error);
+ ToastService.showError(I18n.tr("Failed to set night mode temperature"), response.error, "", "night-mode");
+ 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(I18n.tr("Failed to set night mode schedule"), response.error, "", "night-mode");
+ } else {
+ ToastService.dismissCategory("night-mode");
+ }
+ });
+ });
+ });
+ }
+
+ function startLocationBasedMode() {
+ const temperature = SessionData.nightModeTemperature || 4000;
+ const highTemp = SessionData.nightModeHighTemperature || 6500;
+
+ 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.setTemperature", {
+ "low": temperature,
+ "high": highTemp
+ }, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to set temperature:", response.error);
+ ToastService.showError(I18n.tr("Failed to set night mode temperature"), response.error, "", "night-mode");
+ 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(I18n.tr("Failed to enable IP location"), response.error, "", "night-mode");
+ } else {
+ ToastService.dismissCategory("night-mode");
+ }
+ });
+ } 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(I18n.tr("Failed to set night mode location"), response.error, "", "night-mode");
+ } else {
+ ToastService.dismissCategory("night-mode");
+ }
+ });
+ });
+ } else {
+ console.warn("DisplayService: Location mode selected but no coordinates set and IP location disabled");
+ }
+ });
+ });
+ }
+
+ function setNightModeAutomationMode(mode) {
+ SessionData.setNightModeAutoMode(mode);
+ }
+
+ function evaluateNightMode() {
+ if (!nightModeEnabled) {
+ return;
+ }
+
+ if (SessionData.nightModeAutoEnabled) {
+ restartTimer.nextAction = "automation";
+ restartTimer.start();
+ } else {
+ restartTimer.nextAction = "direct";
+ restartTimer.start();
+ }
+ }
+
+ 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;
+ }
+
+ evaluateNightMode();
+ });
+ }
+ }
+ });
+ }
+
+ Timer {
+ id: restartTimer
+ property string nextAction: ""
+ interval: 250
+ repeat: false
+
+ onTriggered: {
+ if (nextAction === "automation") {
+ startAutomation();
+ } else if (nextAction === "direct") {
+ applyNightModeDirectly();
+ }
+ nextAction = "";
+ }
+ }
+
+ function rescanDevices() {
+ if (!DMSService.isConnected) {
+ return;
+ }
+
+ DMSService.sendRequest("brightness.rescan", null, response => {
+ if (response.error) {
+ console.error("DisplayService: Failed to rescan brightness devices:", response.error);
+ }
+ });
+ }
+
+ function updateDeviceBrightnessDisplay(deviceName) {
+ brightnessVersion++;
+ brightnessChanged();
+ }
+
+ Timer {
+ id: osdSuppressTimer
+ interval: 2000
+ running: true
+ onTriggered: suppressOsd = false
+ }
+
+ Component.onCompleted: {
+ nightModeEnabled = SessionData.nightModeEnabled;
+ deviceBrightnessUserSet = Object.assign({}, SessionData.brightnessUserSetValues);
+ if (DMSService.isConnected) {
+ checkGammaControlAvailability();
+ }
+ }
+
+ Timer {
+ id: screenChangeRescanTimer
+ property int rescanAttempt: 0
+ interval: 3000
+ repeat: false
+ onTriggered: {
+ rescanDevices();
+ rescanAttempt++;
+ if (rescanAttempt < 3) {
+ interval = rescanAttempt === 1 ? 5000 : 8000;
+ restart();
+ } else {
+ rescanAttempt = 0;
+ interval = 3000;
+ }
+ }
+ }
+
+ Connections {
+ target: Quickshell
+
+ function onScreensChanged() {
+ screenChangeRescanTimer.rescanAttempt = 0;
+ screenChangeRescanTimer.interval = 3000;
+ screenChangeRescanTimer.restart();
+ }
+ }
+
+ Connections {
+ target: DMSService
+
+ function onConnectionStateChanged() {
+ if (DMSService.isConnected) {
+ checkGammaControlAvailability();
+ } else {
+ brightnessAvailable = false;
+ gammaControlAvailable = false;
+ automationAvailable = false;
+ }
+ }
+
+ function onCapabilitiesReceived() {
+ checkGammaControlAvailability();
+ }
+
+ function onBrightnessStateUpdate(data) {
+ updateFromBrightnessState(data);
+ }
+
+ function onBrightnessDeviceUpdate(device) {
+ updateSingleDevice(device);
+ }
+
+ function onLoginctlEvent(event) {
+ if (event.event === "unlock" || event.event === "resume") {
+ suppressOsd = true;
+ osdSuppressTimer.restart();
+ evaluateNightMode();
+ }
+ }
+
+ function onGammaStateUpdate(data) {
+ root.gammaState = data;
+ }
+ }
+
+ // Session Data Connections
+ Connections {
+ target: SessionData
+
+ function onNightModeEnabledChanged() {
+ nightModeEnabled = SessionData.nightModeEnabled;
+ evaluateNightMode();
+ }
+
+ function onNightModeAutoEnabledChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeAutoModeChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeStartHourChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeStartMinuteChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeEndHourChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeEndMinuteChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeTemperatureChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeHighTemperatureChanged() {
+ evaluateNightMode();
+ }
+ function onLatitudeChanged() {
+ evaluateNightMode();
+ }
+ function onLongitudeChanged() {
+ evaluateNightMode();
+ }
+ function onNightModeUseIPLocationChanged() {
+ evaluateNightMode();
+ }
+ }
+
+ // IPC Handler for external control
+ IpcHandler {
+ function set(percentage: string, device: string): string {
+ if (!root.brightnessAvailable)
+ return "Brightness control not available";
+
+ const value = parseInt(percentage);
+ if (isNaN(value))
+ return "Invalid brightness value: " + percentage;
+
+ const actualDevice = device || root.getPreferredDevice();
+
+ if (actualDevice && !root.devices.some(d => d.id === actualDevice))
+ return "Device not found: " + actualDevice;
+
+ const deviceInfo = actualDevice ? root.getCurrentDeviceInfoByName(actualDevice) : null;
+ const minValue = (deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc")) ? 1 : 0;
+ const clampedValue = Math.max(minValue, Math.min(100, value));
+
+ root.lastIpcDevice = actualDevice;
+ if (actualDevice && actualDevice !== root.currentDevice)
+ root.setCurrentDevice(actualDevice, false);
+
+ root.setBrightness(clampedValue, actualDevice);
+
+ return actualDevice ? "Brightness set to " + clampedValue + "% on " + actualDevice : "Brightness set to " + clampedValue + "%";
+ }
+
+ function increment(step: string, device: string): string {
+ if (!root.brightnessAvailable)
+ return "Brightness control not available";
+
+ const actualDevice = device || root.getPreferredDevice();
+
+ if (actualDevice && !root.devices.some(d => d.id === actualDevice))
+ return "Device not found: " + actualDevice;
+
+ const stepValue = parseInt(step || "5");
+
+ root.lastIpcDevice = actualDevice;
+ if (actualDevice && actualDevice !== root.currentDevice)
+ root.setCurrentDevice(actualDevice, false);
+
+ const isExponential = SessionData.getBrightnessExponential(actualDevice);
+ const currentBrightness = root.getDeviceBrightness(actualDevice);
+ const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice);
+
+ const maxValue = isExponential ? 100 : (deviceInfo?.displayMax || 100);
+ const newBrightness = Math.min(maxValue, currentBrightness + stepValue);
+
+ root.setBrightness(newBrightness, actualDevice);
+
+ return "Brightness increased by " + stepValue + "%" + (device ? " on " + actualDevice : "");
+ }
+
+ function decrement(step: string, device: string): string {
+ if (!root.brightnessAvailable)
+ return "Brightness control not available";
+
+ const actualDevice = device || root.getPreferredDevice();
+
+ if (actualDevice && !root.devices.some(d => d.id === actualDevice))
+ return "Device not found: " + actualDevice;
+
+ const stepValue = parseInt(step || "5");
+
+ root.lastIpcDevice = actualDevice;
+ if (actualDevice && actualDevice !== root.currentDevice)
+ root.setCurrentDevice(actualDevice, false);
+
+ const isExponential = SessionData.getBrightnessExponential(actualDevice);
+ const currentBrightness = root.getDeviceBrightness(actualDevice);
+ const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice);
+
+ let minValue = 0;
+ switch (true) {
+ case isExponential:
+ minValue = 1;
+ break;
+ case deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc"):
+ minValue = 1;
+ break;
+ default:
+ minValue = 0;
+ break;
+ }
+
+ const newBrightness = Math.max(minValue, currentBrightness - stepValue);
+
+ root.setBrightness(newBrightness, actualDevice);
+
+ return "Brightness decreased by " + stepValue + "%" + (device ? " on " + actualDevice : "");
+ }
+
+ function status(): string {
+ if (!root.brightnessAvailable) {
+ return "Brightness control not available";
+ }
+
+ return "Device: " + root.currentDevice + " - Brightness: " + root.brightnessLevel + "%";
+ }
+
+ function list(): string {
+ if (!root.brightnessAvailable) {
+ return "No brightness devices available";
+ }
+
+ let result = "Available devices:\n";
+ for (const device of root.devices) {
+ const isExp = SessionData.getBrightnessExponential(device.id);
+ result += device.id + " (" + device.class + ")" + (isExp ? " [exponential]" : "") + "\n";
+ }
+ return result;
+ }
+
+ function enableExponential(device: string): string {
+ const targetDevice = device || root.currentDevice;
+ if (!targetDevice) {
+ return "No device specified";
+ }
+
+ if (!root.devices.some(d => d.id === targetDevice)) {
+ return "Device not found: " + targetDevice;
+ }
+
+ SessionData.setBrightnessExponential(targetDevice, true);
+ return "Exponential mode enabled for " + targetDevice;
+ }
+
+ function disableExponential(device: string): string {
+ const targetDevice = device || root.currentDevice;
+ if (!targetDevice) {
+ return "No device specified";
+ }
+
+ if (!root.devices.some(d => d.id === targetDevice)) {
+ return "Device not found: " + targetDevice;
+ }
+
+ SessionData.setBrightnessExponential(targetDevice, false);
+ return "Exponential mode disabled for " + targetDevice;
+ }
+
+ function toggleExponential(device: string): string {
+ const targetDevice = device || root.currentDevice;
+ if (!targetDevice) {
+ return "No device specified";
+ }
+
+ if (!root.devices.some(d => d.id === targetDevice)) {
+ return "Device not found: " + targetDevice;
+ }
+
+ const currentState = SessionData.getBrightnessExponential(targetDevice);
+ SessionData.setBrightnessExponential(targetDevice, !currentState);
+ return "Exponential mode " + (!currentState ? "enabled" : "disabled") + " for " + targetDevice;
+ }
+
+ target: "brightness"
+ }
+
+ IpcHandler {
+ function toggle(): string {
+ root.toggleNightMode();
+ return root.nightModeEnabled ? "Night mode enabled" : "Night mode disabled";
+ }
+
+ function enable(): string {
+ root.enableNightMode();
+ return "Night mode enabled";
+ }
+
+ function disable(): string {
+ root.disableNightMode();
+ return "Night mode disabled";
+ }
+
+ function status(): string {
+ if (!root.gammaControlAvailable)
+ return "Night mode: unavailable (no gamma control)";
+
+ const parts = ["Night mode: " + (root.nightModeEnabled ? "enabled" : "disabled")];
+
+ if (root.gammaCurrentTemp > 0)
+ parts.push("Current temperature: " + root.gammaCurrentTemp + "K");
+
+ parts.push("Target night temperature: " + SessionData.nightModeTemperature + "K");
+
+ if (SessionData.nightModeAutoEnabled) {
+ parts.push("Target day temperature: " + SessionData.nightModeHighTemperature + "K");
+ parts.push("Automation: " + SessionData.nightModeAutoMode);
+ parts.push("Period: " + (root.gammaIsDay ? "day" : "night"));
+
+ if (root.gammaNextTransition)
+ parts.push("Next transition: " + root.gammaNextTransition);
+ if (root.gammaSunriseTime)
+ parts.push("Sunrise: " + root.gammaSunriseTime);
+ if (root.gammaSunsetTime)
+ parts.push("Sunset: " + root.gammaSunsetTime);
+ }
+
+ return parts.join("\n");
+ }
+
+ function getCurrentTemp(): string {
+ if (!root.gammaControlAvailable)
+ return "Gamma control not available";
+ if (root.gammaCurrentTemp <= 0)
+ return "No current temperature reported";
+ return root.gammaCurrentTemp.toString();
+ }
+
+ function getTargetTemp(): string {
+ return SessionData.nightModeTemperature.toString();
+ }
+
+ function getDayTemp(): string {
+ return SessionData.nightModeHighTemperature.toString();
+ }
+
+ function setTargetTemp(value: string): string {
+ if (!value)
+ return "Usage: night setTargetTemp <2500-6000>";
+
+ const temp = parseInt(value);
+ if (isNaN(temp))
+ return "Invalid temperature: " + value;
+ if (temp < 2500 || temp > 6000)
+ return "Temperature must be between 2500K and 6000K";
+
+ const rounded = Math.round(temp / 500) * 500;
+ SessionData.setNightModeTemperature(rounded);
+
+ if (root.nightModeEnabled) {
+ switch (true) {
+ case SessionData.nightModeAutoEnabled:
+ root.startAutomation();
+ break;
+ default:
+ root.applyNightModeDirectly();
+ break;
+ }
+ }
+
+ if (rounded !== temp)
+ return "Night temperature set to " + rounded + "K (rounded from " + temp + "K)";
+ return "Night temperature set to " + rounded + "K";
+ }
+
+ function setDayTemp(value: string): string {
+ if (!value)
+ return "Usage: night setDayTemp <2500-6500>";
+
+ const temp = parseInt(value);
+ if (isNaN(temp))
+ return "Invalid temperature: " + value;
+ if (temp < 2500 || temp > 6500)
+ return "Temperature must be between 2500K and 6500K";
+
+ const rounded = Math.round(temp / 500) * 500;
+ SessionData.setNightModeHighTemperature(rounded);
+
+ if (root.nightModeEnabled && SessionData.nightModeAutoEnabled)
+ root.startAutomation();
+
+ if (rounded !== temp)
+ return "Day temperature set to " + rounded + "K (rounded from " + temp + "K)";
+ return "Day temperature set to " + rounded + "K";
+ }
+
+ function getSchedule(): string {
+ if (!SessionData.nightModeAutoEnabled)
+ return "Automation disabled";
+
+ const parts = ["Mode: " + SessionData.nightModeAutoMode];
+ parts.push("Period: " + (root.gammaIsDay ? "day" : "night"));
+
+ if (root.gammaDawnTime)
+ parts.push("Dawn: " + root.gammaDawnTime);
+ if (root.gammaSunriseTime)
+ parts.push("Sunrise: " + root.gammaSunriseTime);
+ if (root.gammaSunsetTime)
+ parts.push("Sunset: " + root.gammaSunsetTime);
+ if (root.gammaNightTime)
+ parts.push("Night: " + root.gammaNightTime);
+ if (root.gammaNextTransition)
+ parts.push("Next transition: " + root.gammaNextTransition);
+ if (root.gammaSunPosition > 0)
+ parts.push("Sun position: " + root.gammaSunPosition.toFixed(2) + "°");
+
+ return parts.join("\n");
+ }
+
+ target: "night"
+ }
+}