mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
1059 lines
36 KiB
QML
1059 lines
36 KiB
QML
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
|
|
|
|
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("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("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("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": 6500
|
|
}, response => {
|
|
if (response.error) {
|
|
console.error("DisplayService: Failed to set temperature:", response.error);
|
|
ToastService.showError("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("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("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("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("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("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();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Quickshell
|
|
|
|
function onScreensChanged() {
|
|
rescanDevices();
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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"
|
|
}
|
|
|
|
// IPC Handler for night mode control
|
|
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 {
|
|
return root.nightModeEnabled ? "Night mode is enabled" : "Night mode is disabled";
|
|
}
|
|
|
|
function temperature(value: string): string {
|
|
if (!value) {
|
|
return "Current temperature: " + SessionData.nightModeTemperature + "K";
|
|
}
|
|
|
|
const temp = parseInt(value);
|
|
if (isNaN(temp)) {
|
|
return "Invalid temperature. Use a value between 2500 and 6000 (in steps of 500)";
|
|
}
|
|
|
|
// Validate temperature is in valid range and steps
|
|
if (temp < 2500 || temp > 6000) {
|
|
return "Temperature must be between 2500K and 6000K";
|
|
}
|
|
|
|
// Round to nearest 500
|
|
const rounded = Math.round(temp / 500) * 500;
|
|
|
|
SessionData.setNightModeTemperature(rounded);
|
|
|
|
// Restart night mode with new temperature if active
|
|
if (root.nightModeEnabled) {
|
|
if (SessionData.nightModeAutoEnabled) {
|
|
root.startAutomation();
|
|
} else {
|
|
root.applyNightModeDirectly();
|
|
}
|
|
}
|
|
|
|
if (rounded !== temp) {
|
|
return "Night mode temperature set to " + rounded + "K (rounded from " + temp + "K)";
|
|
} else {
|
|
return "Night mode temperature set to " + rounded + "K";
|
|
}
|
|
}
|
|
|
|
target: "night"
|
|
}
|
|
}
|