NightLight: reworked settings, defined fade duration and simplified service.

This commit is contained in:
LemmyCook 2025-08-31 00:13:40 -04:00
parent 2c9e675ba4
commit 87f9afbd85
6 changed files with 72 additions and 158 deletions

View file

@ -250,13 +250,11 @@ Singleton {
// night light // night light
property JsonObject nightLight: JsonObject { property JsonObject nightLight: JsonObject {
property bool enabled: false property bool enabled: false
property real intensity: 0.8 property bool autoSchedule: true
property string startTime: "20:00" property string nightTemp: "4000"
property string stopTime: "07:00" property string dayTemp: "6500"
property bool autoSchedule: false property string manualSunrise: "06:30"
// wlsunset temperatures (Kelvin) property string manualSunset: "18:30"
property int lowTemp: 3500
property int highTemp: 6500
} }
} }
} }

View file

@ -76,7 +76,7 @@ Item {
settingsPanel.requestedTab = SettingsPanel.Tab.AudioService settingsPanel.requestedTab = SettingsPanel.Tab.AudioService
settingsPanel.open(screen) settingsPanel.open(screen)
} }
onRightClicked : { onRightClicked: {
pwvucontrolProcess.running = true pwvucontrolProcess.running = true
} }
} }

View file

@ -263,38 +263,6 @@ ColumnLayout {
} }
} }
// Intensity settings
ColumnLayout {
visible: Settings.data.nightLight.enabled
NLabel {
label: "Intensity"
description: "Higher values create warmer tones."
}
RowLayout {
spacing: Style.marginS * scaling
NSlider {
from: 0
to: 1
stepSize: 0.01
value: Settings.data.nightLight.intensity
onMoved: {
Settings.data.nightLight.intensity = value
NightLightService.apply()
}
Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling
}
NText {
text: `${Math.round(Settings.data.nightLight.intensity * 100)}%`
Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: 60 * scaling
horizontalAlignment: Text.AlignRight
}
}
}
// Temperature // Temperature
ColumnLayout { ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
@ -302,7 +270,7 @@ ColumnLayout {
NLabel { NLabel {
label: "Color temperature" label: "Color temperature"
description: "Select two temperatures in Kelvin" description: "Choose two temperatures in Kelvin."
} }
RowLayout { RowLayout {
@ -313,21 +281,23 @@ ColumnLayout {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
NText { NText {
text: "Low" text: "Night"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NTextInput { NTextInput {
text: Settings.data.nightLight.lowTemp.toString() text: Settings.data.nightLight.nightTemp
inputMethodHints: Qt.ImhDigitsOnly inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onEditingFinished: { onEditingFinished: {
var v = parseInt(text) var nightTemp = parseInt(text)
if (!isNaN(v)) { var dayTemp = parseInt(Settings.data.nightLight.dayTemp)
Settings.data.nightLight.lowTemp = Math.max(1000, Math.min(6500, v)) if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
NightLightService.apply() // Clamp value between [1000 .. (dayTemp-500)]
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp))
text = Settings.data.nightLight.nightTemp = clampedValue.toString()
} }
} }
} }
@ -335,20 +305,22 @@ ColumnLayout {
Item {} Item {}
NText { NText {
text: "High" text: "Day"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NTextInput { NTextInput {
text: Settings.data.nightLight.highTemp.toString() text: Settings.data.nightLight.dayTemp
inputMethodHints: Qt.ImhDigitsOnly inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onEditingFinished: { onEditingFinished: {
var v = parseInt(text) var dayTemp = parseInt(text)
if (!isNaN(v)) { var nightTemp = parseInt(Settings.data.nightLight.nightTemp)
Settings.data.nightLight.highTemp = Math.max(1000, Math.min(10000, v)) if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
NightLightService.apply() // Clamp value between [(nightTemp+500) .. 6500]
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp))
text = Settings.data.nightLight.dayTemp = clampedValue.toString()
} }
} }
} }
@ -356,43 +328,41 @@ ColumnLayout {
} }
NToggle { NToggle {
label: "Auto Schedule" label: "Automatic Scheduling"
description: "Automatically enable night light based on time schedule." description: `Based on the sunset and sunrise time in <i>${LocationService.data.stableName}</i> - recommended.`
checked: Settings.data.nightLight.autoSchedule checked: Settings.data.nightLight.autoSchedule
onToggled: checked => { onToggled: checked => Settings.data.nightLight.autoSchedule = checked
Settings.data.nightLight.autoSchedule = checked
NightLightService.apply()
}
visible: Settings.data.nightLight.enabled visible: Settings.data.nightLight.enabled
} }
// Schedule settings // Schedule settings
ColumnLayout { ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
visible: Settings.data.nightLight.enabled && Settings.data.nightLight.autoSchedule visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule
NLabel {
label: "Schedule"
description: "Set a start and end time for automatic schedule."
}
RowLayout { RowLayout {
Layout.fillWidth: false Layout.fillWidth: false
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NLabel {
label: "Manual Scheduling"
}
Item {// add a little more spacing
}
NText { NText {
text: "Start Time" text: "Sunrise Time"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
} }
NComboBox { NComboBox {
model: timeOptions model: timeOptions
currentKey: Settings.data.nightLight.startTime currentKey: Settings.data.nightLight.manualSunrise
placeholder: "Select start time" placeholder: "Select start time"
onSelected: key => { onSelected: key => {
Settings.data.nightLight.startTime = key Settings.data.nightLight.manualSunrise = key
NightLightService.apply()
} }
preferredWidth: 120 * scaling preferredWidth: 120 * scaling
} }
@ -401,17 +371,16 @@ ColumnLayout {
} }
NText { NText {
text: "Stop Time" text: "Sunset Time"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
} }
NComboBox { NComboBox {
model: timeOptions model: timeOptions
currentKey: Settings.data.nightLight.stopTime currentKey: Settings.data.nightLight.manualSunset
placeholder: "Select stop time" placeholder: "Select stop time"
onSelected: key => { onSelected: key => {
Settings.data.nightLight.stopTime = key Settings.data.nightLight.manualSunset = key
NightLightService.apply()
} }
preferredWidth: 120 * scaling preferredWidth: 120 * scaling
} }

View file

@ -11,75 +11,45 @@ Singleton {
// Night Light properties - directly bound to settings // Night Light properties - directly bound to settings
readonly property var params: Settings.data.nightLight readonly property var params: Settings.data.nightLight
// Deprecated overlay flag removed; service only manages wlsunset now property var lastCommand: []
property bool isActive: false
property bool isRunning: false
property string lastCommand: ""
property var nextCommand: []
Component.onCompleted: apply() function apply() {
var command = buildCommand()
// Compare with previous command to avoid unecessary restart
if (JSON.stringify(command) !== JSON.stringify(lastCommand)) {
lastCommand = command
runner.command = command
// Set running to false so it may restarts below if still enabled
runner.running = false
}
runner.running = params.enabled
}
function buildCommand() { function buildCommand() {
var cmd = ["wlsunset"] var cmd = ["wlsunset"]
// Use user-configured temps; if intensity is used, bias lowTemp towards user low cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`)
var i = Math.max(0, Math.min(1, params.intensity)) if (params.autoSchedule) {
var loCfg = params.lowTemp || 3500 cmd.push("-l", `${LocationService.data.stableLatitude}`, "-L", `${LocationService.data.stableLongitude}`)
var hiCfg = params.highTemp || 6500
var lowTemp = Math.round(hiCfg - (hiCfg - loCfg) * Math.pow(i, 0.6))
cmd.push("-t", lowTemp.toString())
cmd.push("-T", hiCfg.toString())
if (params.autoSchedule && LocationService.data.coordinatesReady && LocationService.data.stableLatitude !== ""
&& LocationService.data.stableLongitude !== "") {
cmd.push("-l", LocationService.data.stableLatitude)
cmd.push("-L", LocationService.data.stableLongitude)
} else { } else {
// Manual schedule cmd.push("-S", params.manualSunrise)
if (params.startTime && params.stopTime) { cmd.push("-s", params.manualSunset)
cmd.push("-S", params.startTime)
cmd.push("-s", params.stopTime)
}
// Optional: do not pass duration, use wlsunset defaults
} }
cmd.push("-d", 60 * 15) // 15min progressive fade at sunset/sunrise
return cmd return cmd
} }
function stopIfRunning() {
// Best-effort stop; wlsunset runs as foreground, so pkill is simplest
Quickshell.execDetached(["pkill", "-x", "wlsunset"])
isRunning = false
}
function apply() {
if (!params.enabled) {
// Disable immediately
debounceStart.stop()
nextCommand = []
stopIfRunning()
return
}
// Debounce rapid changes (slider)
nextCommand = buildCommand()
lastCommand = nextCommand.join(" ")
stopIfRunning()
debounceStart.restart()
}
// Observe setting changes and location readiness // Observe setting changes and location readiness
Connections { Connections {
target: Settings.data.nightLight target: Settings.data.nightLight
function onEnabledChanged() { function onEnabledChanged() {
apply() apply()
} }
function onIntensityChanged() { function onNightTempChanged() {
apply() apply()
} }
function onAutoScheduleChanged() { function onDayTempChanged() {
apply()
}
function onStartTimeChanged() {
apply()
}
function onStopTimeChanged() {
apply() apply()
} }
} }
@ -87,16 +57,7 @@ Singleton {
Connections { Connections {
target: LocationService.data target: LocationService.data
function onCoordinatesReadyChanged() { function onCoordinatesReadyChanged() {
if (params.enabled && params.autoSchedule) apply()
apply()
}
function onStableLatitudeChanged() {
if (params.enabled && params.autoSchedule)
apply()
}
function onStableLongitudeChanged() {
if (params.enabled && params.autoSchedule)
apply()
} }
} }
@ -105,28 +66,10 @@ Singleton {
id: runner id: runner
running: false running: false
onStarted: { onStarted: {
isRunning = true Logger.log("NightLight", "Wlsunset started:", runner.command)
Logger.log("NightLight", "Started wlsunset:", root.lastCommand)
} }
onExited: function (code, status) { onExited: function (code, status) {
isRunning = false Logger.log("NightLight", "Wlsunset exited:", code, status)
Logger.log("NightLight", "wlsunset exited:", code, status)
// Do not auto-restart here; debounceStart handles starts
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Debounce timer to avoid flicker when moving sliders
Timer {
id: debounceStart
interval: 300
repeat: false
onTriggered: {
if (params.enabled && nextCommand.length > 0) {
runner.command = nextCommand
runner.running = true
}
} }
} }
} }

View file

@ -9,8 +9,9 @@ Text {
font.family: Settings.data.ui.fontDefault font.family: Settings.data.ui.fontDefault
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
color: Color.mOnSurface
renderType: Text.QtRendering
font.hintingPreference: Font.PreferNoHinting font.hintingPreference: Font.PreferNoHinting
font.kerning: true font.kerning: true
color: Color.mOnSurface
renderType: Text.QtRendering
textFormat: Text.RichText
} }

View file

@ -103,7 +103,10 @@ ShellRoot {
// Save a ref. to our lockScreen so we can access it easily // Save a ref. to our lockScreen so we can access it easily
PanelService.lockScreen = lockScreen PanelService.lockScreen = lockScreen
// Ensure our singleton is created as soon as possible so we start fetching weather asap // Ensure our location singleton is created as soon as possible so we start fetching weather asap
LocationService.init() LocationService.init()
// Kickoff NightLight service
NightLightService.apply()
} }
} }