NightLight: reworked settings, defined fade duration and simplified service.
This commit is contained in:
parent
2c9e675ba4
commit
87f9afbd85
6 changed files with 72 additions and 158 deletions
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue