Replace our NightLight solution with wlsunset.
NightLight: add temperature solution NTextInput: add input hint support
This commit is contained in:
parent
57a67bf4df
commit
2a686b55c4
9 changed files with 167 additions and 130 deletions
|
|
@ -275,6 +275,9 @@ Singleton {
|
||||||
property string startTime: "20:00"
|
property string startTime: "20:00"
|
||||||
property string stopTime: "07:00"
|
property string stopTime: "07:00"
|
||||||
property bool autoSchedule: false
|
property bool autoSchedule: false
|
||||||
|
// wlsunset temperatures (Kelvin)
|
||||||
|
property int lowTemp: 3500
|
||||||
|
property int highTemp: 6500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,19 @@ Item {
|
||||||
|
|
||||||
NPill {
|
NPill {
|
||||||
id: pill
|
id: pill
|
||||||
icon: NightLightService.isActive ? "bedtime" : "bedtime_off"
|
icon: Settings.data.nightLight.enabled ? "bedtime" : "bedtime_off"
|
||||||
iconCircleColor: NightLightService.isActive ? Color.mSecondary : Color.mOnSurfaceVariant
|
iconCircleColor: Settings.data.nightLight.enabled ? Color.mSecondary : Color.mOnSurfaceVariant
|
||||||
collapsedIconColor: NightLightService.isActive ? Color.mOnSecondary : Color.mOnSurface
|
collapsedIconColor: Settings.data.nightLight.enabled ? Color.mOnSecondary : Color.mOnSurface
|
||||||
autoHide: false
|
autoHide: false
|
||||||
text: NightLightService.isActive ? "On" : "Off"
|
text: Settings.data.nightLight.enabled ? "On" : "Off"
|
||||||
tooltipText: {
|
tooltipText: {
|
||||||
if (!Settings.isLoaded || !Settings.data.nightLight.enabled) {
|
if (!Settings.isLoaded || !Settings.data.nightLight.enabled) {
|
||||||
return "Night Light: Disabled\nLeft click to open settings.\nRight click to enable."
|
return "Night Light: Disabled\nLeft click to open settings.\nRight click to enable."
|
||||||
}
|
}
|
||||||
|
|
||||||
var status = NightLightService.isActive ? "Active" : "Inactive (outside schedule)"
|
|
||||||
var intensity = Math.round(Settings.data.nightLight.intensity * 100)
|
var intensity = Math.round(Settings.data.nightLight.intensity * 100)
|
||||||
var schedule = Settings.data.nightLight.autoSchedule ? `Schedule: ${Settings.data.nightLight.startTime} - ${Settings.data.nightLight.stopTime}` : "Manual mode"
|
var schedule = Settings.data.nightLight.autoSchedule ? `Auto schedule` : `Manual: ${Settings.data.nightLight.startTime} - ${Settings.data.nightLight.stopTime}`
|
||||||
|
return `Night Light: Enabled\nIntensity: ${intensity}%\n${schedule}\nLeft click to open settings.\nRight click to toggle.`
|
||||||
return `Intensity: ${intensity}%\n${schedule}\nLeft click to open settings.\nRight click to toggle.`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
@ -42,14 +40,11 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
// Right click - toggle night light
|
// Right click - toggle night light (debounced apply handled by service)
|
||||||
Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled
|
Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled
|
||||||
|
NightLightService.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
onWheel: delta => {
|
// Wheel handler removed to avoid frequent rapid restarts/flicker
|
||||||
var diff = delta > 0 ? 0.05 : -0.05
|
|
||||||
Settings.data.nightLight.intensity = Math.max(0, Math.min(1.0,
|
|
||||||
Settings.data.nightLight.intensity + diff))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Commons
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: Loader {
|
|
||||||
required property ShellScreen modelData
|
|
||||||
readonly property real scaling: ScalingService.scale(modelData)
|
|
||||||
|
|
||||||
active: NightLightService.isActive
|
|
||||||
|
|
||||||
sourceComponent: PanelWindow {
|
|
||||||
screen: modelData
|
|
||||||
color: Color.transparent
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure a full click through
|
|
||||||
mask: Region {}
|
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
WlrLayershell.namespace: "noctalia-nightlight"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: NightLightService.overlayColor
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationSlow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -239,7 +239,10 @@ ColumnLayout {
|
||||||
label: "Enable Night Light"
|
label: "Enable Night Light"
|
||||||
description: "Apply a warm color filter to reduce blue light emission."
|
description: "Apply a warm color filter to reduce blue light emission."
|
||||||
checked: Settings.data.nightLight.enabled
|
checked: Settings.data.nightLight.enabled
|
||||||
onToggled: checked => Settings.data.nightLight.enabled = checked
|
onToggled: checked => {
|
||||||
|
Settings.data.nightLight.enabled = checked
|
||||||
|
NightLightService.apply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intensity settings
|
// Intensity settings
|
||||||
|
|
@ -257,7 +260,10 @@ ColumnLayout {
|
||||||
to: 1
|
to: 1
|
||||||
stepSize: 0.01
|
stepSize: 0.01
|
||||||
value: Settings.data.nightLight.intensity
|
value: Settings.data.nightLight.intensity
|
||||||
onMoved: Settings.data.nightLight.intensity = value
|
onMoved: {
|
||||||
|
Settings.data.nightLight.intensity = value
|
||||||
|
NightLightService.apply()
|
||||||
|
}
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumWidth: 150 * scaling
|
Layout.minimumWidth: 150 * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -271,11 +277,59 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temperature settings (inline like schedule)
|
||||||
|
RowLayout {
|
||||||
|
visible: Settings.data.nightLight.enabled
|
||||||
|
Layout.fillWidth: false
|
||||||
|
spacing: Style.marginM * scaling
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: "Low"
|
||||||
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
|
color: Color.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
NTextInput {
|
||||||
|
text: Settings.data.nightLight.lowTemp.toString()
|
||||||
|
inputMethodHints: Qt.ImhDigitsOnly
|
||||||
|
Layout.preferredWidth: 100 * scaling
|
||||||
|
onEditingFinished: {
|
||||||
|
var v = parseInt(text)
|
||||||
|
if (!isNaN(v)) {
|
||||||
|
Settings.data.nightLight.lowTemp = Math.max(1000, Math.min(6500, v))
|
||||||
|
NightLightService.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: "High"
|
||||||
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
|
color: Color.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
NTextInput {
|
||||||
|
text: Settings.data.nightLight.highTemp.toString()
|
||||||
|
inputMethodHints: Qt.ImhDigitsOnly
|
||||||
|
Layout.preferredWidth: 100 * scaling
|
||||||
|
onEditingFinished: {
|
||||||
|
var v = parseInt(text)
|
||||||
|
if (!isNaN(v)) {
|
||||||
|
Settings.data.nightLight.highTemp = Math.max(1000, Math.min(10000, v))
|
||||||
|
NightLightService.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
label: "Auto Schedule"
|
label: "Auto Schedule"
|
||||||
description: "Automatically enable night light based on time schedule."
|
description: "Automatically enable night light based on time schedule."
|
||||||
checked: Settings.data.nightLight.autoSchedule
|
checked: Settings.data.nightLight.autoSchedule
|
||||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
onToggled: checked => {
|
||||||
|
Settings.data.nightLight.autoSchedule = checked
|
||||||
|
NightLightService.apply()
|
||||||
|
}
|
||||||
visible: Settings.data.nightLight.enabled
|
visible: Settings.data.nightLight.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +357,7 @@ ColumnLayout {
|
||||||
model: timeOptions
|
model: timeOptions
|
||||||
currentKey: Settings.data.nightLight.startTime
|
currentKey: Settings.data.nightLight.startTime
|
||||||
placeholder: "Select start time"
|
placeholder: "Select start time"
|
||||||
onSelected: key => Settings.data.nightLight.startTime = key
|
onSelected: key => { Settings.data.nightLight.startTime = key; NightLightService.apply() }
|
||||||
preferredWidth: 120 * scaling
|
preferredWidth: 120 * scaling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,7 +373,7 @@ ColumnLayout {
|
||||||
model: timeOptions
|
model: timeOptions
|
||||||
currentKey: Settings.data.nightLight.stopTime
|
currentKey: Settings.data.nightLight.stopTime
|
||||||
placeholder: "Select stop time"
|
placeholder: "Select stop time"
|
||||||
onSelected: key => Settings.data.nightLight.stopTime = key
|
onSelected: key => { Settings.data.nightLight.stopTime = key; NightLightService.apply() }
|
||||||
preferredWidth: 120 * scaling
|
preferredWidth: 120 * scaling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@ Features a modern modular architecture with a status bar, notification system, c
|
||||||
- `gpu-screen-recorder` - Screen recording functionality
|
- `gpu-screen-recorder` - Screen recording functionality
|
||||||
- `brightnessctl` - For internal/laptop monitor brightness
|
- `brightnessctl` - For internal/laptop monitor brightness
|
||||||
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)
|
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)
|
||||||
- `xdg-desktop-portal-gnome` - Desktop integration (or alternative portal)
|
|
||||||
|
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
@ -79,6 +78,7 @@ Features a modern modular architecture with a status bar, notification system, c
|
||||||
- `swww` - Wallpaper animations and effects
|
- `swww` - Wallpaper animations and effects
|
||||||
- `matugen` - Material You color scheme generation
|
- `matugen` - Material You color scheme generation
|
||||||
- `cava` - Audio visualizer component
|
- `cava` - Audio visualizer component
|
||||||
|
- `wlsunset` - To be able to use NightLight
|
||||||
|
|
||||||
> There are 2 more optional dependencies.
|
> There are 2 more optional dependencies.
|
||||||
> Any `polkit agent` to be able to use the ArchUpdater widget.
|
> Any `polkit agent` to be able to use the ArchUpdater widget.
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,106 @@ import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// 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 bool isActive: false
|
||||||
|
property bool isRunning: false
|
||||||
|
property string lastCommand: ""
|
||||||
|
property var nextCommand: []
|
||||||
|
|
||||||
// Computed properties
|
Component.onCompleted: apply()
|
||||||
readonly property color overlayColor: params.enabled ? calculateOverlayColor() : "transparent"
|
|
||||||
property bool isActive: params.enabled && (params.autoSchedule ? isWithinSchedule() : true)
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
function buildCommand() {
|
||||||
Logger.log("NightLight", "Service started")
|
var cmd = ["wlsunset"]
|
||||||
}
|
// Use user-configured temps; if intensity is used, bias lowTemp towards user low
|
||||||
|
var i = Math.max(0, Math.min(1, params.intensity))
|
||||||
function calculateOverlayColor() {
|
var loCfg = params.lowTemp || 3500
|
||||||
if (!isActive) {
|
var hiCfg = params.highTemp || 6500
|
||||||
return "transparent"
|
var lowTemp = Math.round(hiCfg - (hiCfg - loCfg) * Math.pow(i, 0.6))
|
||||||
}
|
cmd.push("-t", lowTemp.toString())
|
||||||
|
cmd.push("-T", hiCfg.toString())
|
||||||
// More vibrant color formula - stronger effect at high warmth
|
if (params.autoSchedule && LocationService.data.coordinatesReady && LocationService.data.stableLatitude !== "" && LocationService.data.stableLongitude !== "") {
|
||||||
var red = 1.0
|
cmd.push("-l", LocationService.data.stableLatitude)
|
||||||
var green = 1.0 - (0.43 * params.intensity)
|
cmd.push("-L", LocationService.data.stableLongitude)
|
||||||
var blue = 1.0 - (0.84 * params.intensity)
|
|
||||||
var alpha = (params.intensity * 0.25) // Higher alpha for more noticeable effect
|
|
||||||
|
|
||||||
return Qt.rgba(red, green, blue, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isWithinSchedule() {
|
|
||||||
if (!params.autoSchedule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = new Date()
|
|
||||||
var currentTime = now.getHours() * 60 + now.getMinutes()
|
|
||||||
|
|
||||||
var startParts = params.startTime.split(":")
|
|
||||||
var stopParts = params.stopTime.split(":")
|
|
||||||
var startMinutes = parseInt(startParts[0]) * 60 + parseInt(startParts[1])
|
|
||||||
var stopMinutes = parseInt(stopParts[0]) * 60 + parseInt(stopParts[1])
|
|
||||||
|
|
||||||
// Handle overnight schedule (e.g., 20:00 to 07:00)
|
|
||||||
if (stopMinutes < startMinutes) {
|
|
||||||
return currentTime >= startMinutes || currentTime <= stopMinutes
|
|
||||||
} else {
|
} else {
|
||||||
return currentTime >= startMinutes && currentTime <= stopMinutes
|
// Manual schedule
|
||||||
|
if (params.startTime && params.stopTime) {
|
||||||
|
cmd.push("-S", params.startTime)
|
||||||
|
cmd.push("-s", params.stopTime)
|
||||||
|
}
|
||||||
|
// Optional: do not pass duration, use wlsunset defaults
|
||||||
}
|
}
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timer to check schedule changes
|
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
|
||||||
|
Connections {
|
||||||
|
target: Settings.data.nightLight
|
||||||
|
function onEnabledChanged() { apply() }
|
||||||
|
function onIntensityChanged() { apply() }
|
||||||
|
function onAutoScheduleChanged() { apply() }
|
||||||
|
function onStartTimeChanged() { apply() }
|
||||||
|
function onStopTimeChanged() { apply() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: LocationService.data
|
||||||
|
function onCoordinatesReadyChanged() { if (params.enabled && params.autoSchedule) apply() }
|
||||||
|
function onStableLatitudeChanged() { if (params.enabled && params.autoSchedule) apply() }
|
||||||
|
function onStableLongitudeChanged() { if (params.enabled && params.autoSchedule) apply() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreground process runner
|
||||||
|
Process {
|
||||||
|
id: runner
|
||||||
|
running: false
|
||||||
|
onStarted: { isRunning = true; Logger.log("NightLight", "Started wlsunset:", root.lastCommand) }
|
||||||
|
onExited: function (code, status) {
|
||||||
|
isRunning = false
|
||||||
|
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 {
|
Timer {
|
||||||
interval: 60000 // Check every minute
|
id: debounceStart
|
||||||
running: params.enabled && params.autoSchedule
|
interval: 300
|
||||||
repeat: true
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
isActive = isWithinSchedule()
|
if (params.enabled && nextCommand.length > 0) {
|
||||||
|
runner.command = nextCommand
|
||||||
|
runner.running = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ RowLayout {
|
||||||
property bool checked: false
|
property bool checked: false
|
||||||
property bool hovering: false
|
property bool hovering: false
|
||||||
// Smaller default footprint than NToggle
|
// Smaller default footprint than NToggle
|
||||||
property int baseSize: Math.max(Style.baseWidgetSize * 0.8, Math.round(14 / scaling))
|
property int baseSize: Math.max(Style.baseWidgetSize * 0.8, 14)
|
||||||
|
|
||||||
signal toggled(bool checked)
|
signal toggled(bool checked)
|
||||||
signal entered
|
signal entered
|
||||||
|
|
@ -47,26 +47,14 @@ RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {
|
onEntered: { hovering = true; root.entered() }
|
||||||
hovering = true
|
onExited: { hovering = false; root.exited() }
|
||||||
root.entered()
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
hovering = false
|
|
||||||
root.exited()
|
|
||||||
}
|
|
||||||
onClicked: root.toggled(!root.checked)
|
onClicked: root.toggled(!root.checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color { ColorAnimation { duration: Style.animationFast } }
|
||||||
ColorAnimation {
|
Behavior on border.color { ColorAnimation { duration: Style.animationFast } }
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Item {
|
||||||
|
|
||||||
property alias text: input.text
|
property alias text: input.text
|
||||||
property alias placeholderText: input.placeholderText
|
property alias placeholderText: input.placeholderText
|
||||||
|
property alias inputMethodHints: input.inputMethodHints
|
||||||
|
|
||||||
signal editingFinished
|
signal editingFinished
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import qs.Modules.Calendar
|
||||||
import qs.Modules.Dock
|
import qs.Modules.Dock
|
||||||
import qs.Modules.IPC
|
import qs.Modules.IPC
|
||||||
import qs.Modules.LockScreen
|
import qs.Modules.LockScreen
|
||||||
import qs.Modules.NightLight
|
|
||||||
import qs.Modules.Notification
|
import qs.Modules.Notification
|
||||||
import qs.Modules.SettingsPanel
|
import qs.Modules.SettingsPanel
|
||||||
import qs.Modules.PowerPanel
|
import qs.Modules.PowerPanel
|
||||||
|
|
@ -51,7 +50,7 @@ ShellRoot {
|
||||||
|
|
||||||
ToastOverlay {}
|
ToastOverlay {}
|
||||||
|
|
||||||
NightLightOverlay {}
|
// Night light handled by wlsunset via NightLightService; no overlay needed
|
||||||
|
|
||||||
IPCManager {}
|
IPCManager {}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue