From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../quickshell/Services/DisplayService.qml | 1170 ++++++++++++++++++++ 1 file changed, 1170 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DisplayService.qml') 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" + } +} -- cgit v1.3