1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/Services/NightModeAutomationService.qml
2025-08-29 12:04:52 -04:00

505 lines
16 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.Common
Singleton {
id: root
property bool automationAvailable: false
property bool locationProviderAvailable: false
property var availableProviders: []
property string currentProvider: ""
property bool isAutomaticNightTime: false
property string currentLocation: ""
property real latitude: 0.0
property real longitude: 0.0
function testGeoclueConnection() {
geoclueTestProcess.running = false
geoclueTestProcess.command = [
"timeout", "32",
"gammastep",
"-m", "wayland",
"-l", "geoclue2",
"-O", "6500",
"-v"
]
geoclueTestProcess.running = true
}
Component.onCompleted: {
checkAvailability()
updateFromSessionData()
if (typeof globalThis !== 'undefined') {
globalThis.testNightMode = manualNightModeTest
globalThis.resetNightMode = manualResetTest
globalThis.clearNightModeLocation = clearLocation
globalThis.nightModeService = root
}
}
function checkAvailability() {
gammaStepTestProcess.running = true
}
function startAutomation() {
if (!automationAvailable) {
console.warn("NightModeAutomationService: Gammastep not available")
return
}
// Stop any existing automation processes first
stopAutomation()
const mode = SessionData.nightModeAutoMode || "manual"
switch (mode) {
case "time":
startTimeBasedMode()
break
case "location":
startLocationBasedMode()
break
case "manual":
default:
stopAutomation()
break
}
}
function stopAutomation() {
// Stop the unified process
gammaStepProcess.running = false
isAutomaticNightTime = false
}
function startTimeBasedMode() {
checkTimeBasedMode()
}
function startLocationBasedMode() {
gammaStepProcess.running = false
const temperature = SessionData.nightModeTemperature || 4500
const dayTemp = 6500
gammaStepProcess.processType = "automation"
if (latitude !== 0.0 && longitude !== 0.0) {
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-l", `${latitude.toFixed(6)}:${longitude.toFixed(6)}`,
"-t", `${dayTemp}:${temperature}`,
"-v"
])
gammaStepProcess.running = true
return
}
// Check if location providers are available
if (!locationProviderAvailable) {
console.warn("NightModeAutomationService: No location provider available, falling back to time-based mode")
SessionData.setNightModeAutoMode("time")
startTimeBasedMode()
return
}
if (currentProvider === "geoclue2") {
const temperature = SessionData.nightModeTemperature || 4500
const dayTemp = 6500
gammaStepProcess.processType = "automation"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-l", "geoclue2",
"-t", `${dayTemp}:${temperature}`,
"-v"
])
gammaStepProcess.running = true
return
} else {
console.warn("NightModeAutomationService: No working location provider, falling back to time-based mode")
SessionData.setNightModeAutoMode("time")
startTimeBasedMode()
return
}
}
function checkTimeBasedMode() {
if (!SessionData.nightModeAutoEnabled || SessionData.nightModeAutoMode !== "time") {
return
}
const now = new Date()
const currentHour = now.getHours()
const currentMinute = now.getMinutes()
const currentTime = currentHour * 60 + currentMinute
const startTime = SessionData.nightModeStartTime || "20:00"
const endTime = SessionData.nightModeEndTime || "06:00"
const startParts = startTime.split(":")
const endParts = endTime.split(":")
const startMinutes = parseInt(startParts[0]) * 60 + parseInt(startParts[1])
const endMinutes = parseInt(endParts[0]) * 60 + parseInt(endParts[1])
let shouldBeNight = false
if (startMinutes > endMinutes) {
shouldBeNight = (currentTime >= startMinutes) || (currentTime < endMinutes)
} else {
shouldBeNight = (currentTime >= startMinutes) && (currentTime < endMinutes)
}
if (shouldBeNight !== isAutomaticNightTime) {
isAutomaticNightTime = shouldBeNight
if (shouldBeNight) {
requestNightModeActivation()
} else {
requestNightModeDeactivation()
}
}
}
function requestNightModeActivation() {
const temperature = SessionData.nightModeTemperature || 4500
gammaStepProcess.running = false
gammaStepProcess.processType = "activate"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-O", String(temperature)
])
gammaStepProcess.running = true
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode !== "manual") {
SessionData.setNightModeEnabled(true)
}
}
function requestNightModeDeactivation() {
gammaStepProcess.running = false
gammaStepProcess.processType = "reset"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-O", "6500"
])
gammaStepProcess.running = true
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode !== "manual") {
SessionData.setNightModeEnabled(false)
}
}
function setLocation(lat, lon) {
latitude = lat
longitude = lon
currentLocation = `${lat.toFixed(6)},${lon.toFixed(6)}`
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
startLocationBasedMode()
}
}
function clearLocation() {
latitude = 0.0
longitude = 0.0
currentLocation = ""
SessionData.setLatitude(0.0)
SessionData.setLongitude(0.0)
SessionData.saveSettings()
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
startLocationBasedMode()
}
}
function buildGammastepCommand(gammastepArgs) {
const commandStr = "pkill gammastep; " + ["gammastep"].concat(gammastepArgs).join(" ")
return ["sh", "-c", commandStr]
}
function updateFromSessionData() {
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
setLocation(SessionData.latitude, SessionData.longitude)
} else {
latitude = 0.0
longitude = 0.0
currentLocation = ""
}
}
function detectLocationProviders() {
locationProviderDetectionProcess.running = true
}
function manualNightModeTest() {
const temperature = SessionData.nightModeTemperature || 4500
gammaStepProcess.running = false
gammaStepProcess.processType = "test"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-O", String(temperature)
])
gammaStepProcess.running = true
testFeedbackTimer.interval = 2000
testFeedbackTimer.feedbackMessage = "Night mode test applied"
testFeedbackTimer.start()
}
function manualResetTest() {
gammaStepProcess.running = false
gammaStepProcess.processType = "test"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-O", "6500"
])
gammaStepProcess.running = true
testFeedbackTimer.interval = 2000
testFeedbackTimer.feedbackMessage = "Screen reset to normal temperature"
testFeedbackTimer.start()
}
SystemClock {
id: systemClock
precision: SystemClock.Minutes
onDateChanged: {
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
checkTimeBasedMode()
}
}
}
Process {
id: gammaStepTestProcess
command: ["which", "gammastep"]
running: false
onExited: function(exitCode) {
automationAvailable = (exitCode === 0)
if (automationAvailable) {
detectLocationProviders()
if (SessionData.nightModeAutoEnabled) {
startAutomation()
}
}
}
}
Process {
id: locationProviderDetectionProcess
command: ["gammastep", "-l", "list"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
// Parse provider names - they start with whitespace and are single words
const lines = text.trim().split('\n')
const providers = lines.filter(line => {
const trimmed = line.trim()
// Provider names are single words that start with whitespace in original line
return line.startsWith(' ') &&
trimmed.length > 0 &&
!trimmed.includes(' ') &&
!trimmed.includes(':') &&
!trimmed.includes('.')
}).map(line => line.trim())
availableProviders = providers
locationProviderAvailable = providers.length > 0
if (locationProviderAvailable && !currentProvider) {
currentProvider = providers[0]
}
// Providers detected
}
}
}
onExited: function(exitCode) {
if (exitCode !== 0) {
locationProviderAvailable = false
}
}
}
Process {
id: geoclueTestProcess
running: false
onExited: function(exitCode) {
if (exitCode === 0) {
const temperature = SessionData.nightModeTemperature || 4500
const dayTemp = 6500
gammaStepProcess.processType = "automation"
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-l", "geoclue2",
"-t", `${dayTemp}:${temperature}`,
"-v"
])
gammaStepProcess.running = true
} else {
SessionData.setNightModeAutoMode("time")
startTimeBasedMode()
}
}
}
Process {
id: gammaStepProcess
running: false
property string processType: "" // "automation", "activate", "reset", "test"
stdout: StdioCollector {
onStreamFinished: {}
}
stderr: StdioCollector {
onStreamFinished: {}
}
onRunningChanged: {
if (running) {
// Start timeout only for test commands - automation, activate, and reset should not timeout
if (processType === "test") {
processTimeoutTimer.start()
}
} else {
// Stop timeout when process ends
processTimeoutTimer.stop()
}
}
onExited: function(exitCode) {
processTimeoutTimer.stop()
const isSuccessfulCompletion = (exitCode === 0) ||
((processType === "activate" || processType === "reset") && exitCode === 15)
if (!isSuccessfulCompletion) {
switch(processType) {
case "automation":
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
if (exitCode === 15 || exitCode === 124) {
SessionData.setNightModeAutoMode("time")
startTimeBasedMode()
} else {
restartTimer.start()
}
} else if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
restartTimer.start()
}
break
}
}
}
}
Timer {
id: processTimeoutTimer
interval: 30000 // 30 second timeout (increased from 10s for one-shot commands)
running: false
repeat: false
onTriggered: {
if (gammaStepProcess.running) {
console.warn("NightModeAutomationService: Test process timed out, killing process")
// Only kill test processes that have timed out
if (gammaStepProcess.processType === "test") {
gammaStepProcess.running = false
} else {
console.warn("NightModeAutomationService: Non-test process still running after timeout, but not killing")
}
}
}
}
Timer {
id: restartTimer
interval: 10000
running: false
repeat: false
onTriggered: {
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
startLocationBasedMode()
}
}
}
Timer {
id: testFeedbackTimer
interval: 2000
running: false
repeat: false
property string feedbackMessage: ""
onTriggered: {
if (feedbackMessage.length > 0) {
console.log(feedbackMessage)
feedbackMessage = ""
}
}
}
Connections {
target: SessionData
function onNightModeAutoEnabledChanged() {
console.log("NightModeAutomationService: Auto enabled changed to", SessionData.nightModeAutoEnabled)
if (SessionData.nightModeAutoEnabled) {
startAutomation()
} else {
stopAutomation()
}
}
function onNightModeAutoModeChanged() {
if (SessionData.nightModeAutoEnabled) {
startAutomation()
}
}
function onNightModeStartTimeChanged() {
console.log("NightModeAutomationService: Start time changed to", SessionData.nightModeStartTime)
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
checkTimeBasedMode()
}
}
function onNightModeEndTimeChanged() {
console.log("NightModeAutomationService: End time changed to", SessionData.nightModeEndTime)
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
checkTimeBasedMode()
}
}
function onNightModeTemperatureChanged() {
if (SessionData.nightModeAutoEnabled) {
startAutomation()
}
}
function onLatitudeChanged() {
updateFromSessionData()
}
function onLongitudeChanged() {
updateFromSessionData()
}
}
}