1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

night mode repairs

This commit is contained in:
bbedward
2025-08-29 14:23:37 -04:00
parent 3746b1cfad
commit 108fdd9b7f
9 changed files with 651 additions and 947 deletions

View File

@@ -19,11 +19,14 @@ Singleton {
property bool nightModeEnabled: false property bool nightModeEnabled: false
property int nightModeTemperature: 4500 property int nightModeTemperature: 4500
property bool nightModeAutoEnabled: false property bool nightModeAutoEnabled: false
property string nightModeAutoMode: "manual" property string nightModeAutoMode: "time"
property string nightModeStartTime: "20:00" property int nightModeStartHour: 18
property string nightModeEndTime: "06:00" property int nightModeStartMinute: 0
property int nightModeEndHour: 6
property int nightModeEndMinute: 0
property real latitude: 0.0 property real latitude: 0.0
property real longitude: 0.0 property real longitude: 0.0
property string nightModeLocationProvider: ""
property var pinnedApps: [] property var pinnedApps: []
property int selectedGpuIndex: 0 property int selectedGpuIndex: 0
property bool nvidiaGpuTempEnabled: false property bool nvidiaGpuTempEnabled: false
@@ -61,13 +64,27 @@ Singleton {
nightModeAutoEnabled = settings.nightModeAutoEnabled nightModeAutoEnabled = settings.nightModeAutoEnabled
!== undefined ? settings.nightModeAutoEnabled : false !== undefined ? settings.nightModeAutoEnabled : false
nightModeAutoMode = settings.nightModeAutoMode nightModeAutoMode = settings.nightModeAutoMode
!== undefined ? settings.nightModeAutoMode : "manual" !== undefined ? settings.nightModeAutoMode : "time"
nightModeStartTime = settings.nightModeStartTime // Handle legacy time format
!== undefined ? settings.nightModeStartTime : "20:00" if (settings.nightModeStartTime !== undefined) {
nightModeEndTime = settings.nightModeEndTime const parts = settings.nightModeStartTime.split(":")
!== undefined ? settings.nightModeEndTime : "06:00" nightModeStartHour = parseInt(parts[0]) || 18
nightModeStartMinute = parseInt(parts[1]) || 0
} else {
nightModeStartHour = settings.nightModeStartHour !== undefined ? settings.nightModeStartHour : 18
nightModeStartMinute = settings.nightModeStartMinute !== undefined ? settings.nightModeStartMinute : 0
}
if (settings.nightModeEndTime !== undefined) {
const parts = settings.nightModeEndTime.split(":")
nightModeEndHour = parseInt(parts[0]) || 6
nightModeEndMinute = parseInt(parts[1]) || 0
} else {
nightModeEndHour = settings.nightModeEndHour !== undefined ? settings.nightModeEndHour : 6
nightModeEndMinute = settings.nightModeEndMinute !== undefined ? settings.nightModeEndMinute : 0
}
latitude = settings.latitude !== undefined ? settings.latitude : 0.0 latitude = settings.latitude !== undefined ? settings.latitude : 0.0
longitude = settings.longitude !== undefined ? settings.longitude : 0.0 longitude = settings.longitude !== undefined ? settings.longitude : 0.0
nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : ""
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
selectedGpuIndex = settings.selectedGpuIndex selectedGpuIndex = settings.selectedGpuIndex
!== undefined ? settings.selectedGpuIndex : 0 !== undefined ? settings.selectedGpuIndex : 0
@@ -104,10 +121,13 @@ Singleton {
"nightModeTemperature": nightModeTemperature, "nightModeTemperature": nightModeTemperature,
"nightModeAutoEnabled": nightModeAutoEnabled, "nightModeAutoEnabled": nightModeAutoEnabled,
"nightModeAutoMode": nightModeAutoMode, "nightModeAutoMode": nightModeAutoMode,
"nightModeStartTime": nightModeStartTime, "nightModeStartHour": nightModeStartHour,
"nightModeEndTime": nightModeEndTime, "nightModeStartMinute": nightModeStartMinute,
"nightModeEndHour": nightModeEndHour,
"nightModeEndMinute": nightModeEndMinute,
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
"nightModeLocationProvider": nightModeLocationProvider,
"pinnedApps": pinnedApps, "pinnedApps": pinnedApps,
"selectedGpuIndex": selectedGpuIndex, "selectedGpuIndex": selectedGpuIndex,
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled, "nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
@@ -152,13 +172,23 @@ Singleton {
saveSettings() saveSettings()
} }
function setNightModeStartTime(time) { function setNightModeStartHour(hour) {
nightModeStartTime = time nightModeStartHour = hour
saveSettings() saveSettings()
} }
function setNightModeEndTime(time) { function setNightModeStartMinute(minute) {
nightModeEndTime = time nightModeStartMinute = minute
saveSettings()
}
function setNightModeEndHour(hour) {
nightModeEndHour = hour
saveSettings()
}
function setNightModeEndMinute(minute) {
nightModeEndMinute = minute
saveSettings() saveSettings()
} }
@@ -174,6 +204,11 @@ Singleton {
saveSettings() saveSettings()
} }
function setNightModeLocationProvider(provider) {
nightModeLocationProvider = provider
saveSettings()
}
function setWallpaperPath(path) { function setWallpaperPath(path) {
wallpaperPath = path wallpaperPath = path
saveSettings() saveSettings()

View File

@@ -117,7 +117,7 @@ DankPopout {
width: parent.width - Theme.spacingL * 2 width: parent.width - Theme.spacingL * 2
x: Theme.spacingL x: Theme.spacingL
y: Theme.spacingL y: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingS
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -630,16 +630,30 @@ DankPopout {
} }
Row { Item {
width: parent.width width: parent.width
spacing: Theme.spacingM height: audioSliderRow.implicitHeight
Row {
id: audioSliderRow
x: -Theme.spacingS
width: parent.width + Theme.spacingS * 2
spacing: Theme.spacingM
AudioSliderRow { AudioSliderRow {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
} }
BrightnessSliderRow { Item {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: parent.height
BrightnessSliderRow {
width: parent.width
height: parent.height
x: -Theme.spacingS
}
}
} }
} }
@@ -776,11 +790,11 @@ DankPopout {
ToggleButton { ToggleButton {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
iconName: DisplayService.nightModeActive ? "nightlight" : "dark_mode" iconName: DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
text: "Night Mode" text: "Night Mode"
secondaryText: DisplayService.nightModeActive ? "On" : "Off" secondaryText: DisplayService.nightModeEnabled ? "On" : "Off"
isActive: true isActive: DisplayService.nightModeEnabled
enabled: DisplayService.brightnessAvailable enabled: DisplayService.automationAvailable
onClicked: DisplayService.toggleNightMode() onClicked: DisplayService.toggleNightMode()
} }

View File

@@ -11,8 +11,8 @@ Row {
property var defaultSink: AudioService.sink property var defaultSink: AudioService.sink
height: 60 height: 40
spacing: Theme.spacingM spacing: Theme.spacingS
Rectangle { Rectangle {
width: Theme.iconSize + Theme.spacingS * 2 width: Theme.iconSize + Theme.spacingS * 2
@@ -58,10 +58,7 @@ Row {
DankSlider { DankSlider {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: { width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
if (parent.width <= 0) return 80
return Math.max(80, Math.min(400, parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM))
}
enabled: defaultSink !== null enabled: defaultSink !== null
minimum: 0 minimum: 0
maximum: 100 maximum: 100

View File

@@ -8,15 +8,15 @@ import qs.Widgets
Row { Row {
id: root id: root
height: 60 height: 40
spacing: Theme.spacingM spacing: Theme.spacingS
Rectangle { Rectangle {
width: Theme.iconSize + Theme.spacingS * 2 width: Theme.iconSize + Theme.spacingS * 2
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse && DisplayService.devices.length > 1 color: iconArea.containsMouse
? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
: "transparent" : "transparent"
@@ -27,8 +27,8 @@ Row {
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: DisplayService.devices.length > 1 hoverEnabled: true
cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: Qt.PointingHandCursor
onClicked: function(event) { onClicked: function(event) {
if (DisplayService.devices.length > 1) { if (DisplayService.devices.length > 1) {
@@ -57,55 +57,27 @@ Row {
} }
} }
Column { DankSlider {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
spacing: 0 enabled: DisplayService.brightnessAvailable
minimum: 1
DankSlider { maximum: 100
width: parent.width value: {
enabled: DisplayService.brightnessAvailable let level = DisplayService.brightnessLevel
minimum: 1 if (level > 100) {
maximum: 100 let deviceInfo = DisplayService.getCurrentDeviceInfo()
value: { if (deviceInfo && deviceInfo.max > 0) {
let level = DisplayService.brightnessLevel return Math.round((level / deviceInfo.max) * 100)
if (level > 100) {
let deviceInfo = DisplayService.getCurrentDeviceInfo()
if (deviceInfo && deviceInfo.max > 0) {
return Math.round((level / deviceInfo.max) * 100)
}
return 50
}
return level
}
onSliderValueChanged: function(newValue) {
if (DisplayService.brightnessAvailable) {
DisplayService.setBrightness(newValue)
} }
return 50
} }
return level
} }
onSliderValueChanged: function(newValue) {
StyledText { if (DisplayService.brightnessAvailable) {
visible: { DisplayService.setBrightness(newValue)
if (DisplayService.devices.length <= 1) return false
if (!DisplayService.currentDevice) return false
let currentIndex = -1
for (let i = 0; i < DisplayService.devices.length; i++) {
if (DisplayService.devices[i].name === DisplayService.currentDevice) {
currentIndex = i
break
}
}
return currentIndex !== 0
} }
width: parent.width
text: DisplayService.currentDevice || ""
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
topPadding: 2
} }
} }

View File

@@ -608,35 +608,26 @@ Item {
id: nightModeToggle id: nightModeToggle
width: parent.width width: parent.width
text: "Night Mode (Manual)" text: "Night Mode"
description: SessionData.nightModeAutoEnabled ? "Manual control - automation will override when scheduled" : "Apply warm color temperature to reduce eye strain" description: "Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates."
checked: DisplayService.nightModeActive checked: DisplayService.nightModeEnabled
opacity: SessionData.nightModeAutoEnabled ? 0.7 : 1
onToggled: (checked) => { onToggled: (checked) => {
if (checked !== DisplayService.nightModeActive) { DisplayService.toggleNightMode();
if (checked)
DisplayService.enableNightMode();
else
DisplayService.disableNightMode();
}
} }
Connections { Connections {
function onNightModeActiveChanged() { function onNightModeEnabledChanged() {
nightModeToggle.checked = DisplayService.nightModeActive; nightModeToggle.checked = DisplayService.nightModeEnabled;
} }
target: DisplayService target: DisplayService
} }
} }
DankDropdown { DankDropdown {
width: parent.width width: parent.width
text: "Night Mode Temperature" text: "Temperature"
description: DisplayService.nightModeActive ? "Disable night mode to adjust" : "Set temperature for night mode" description: "Color temperature for night mode"
enabled: !DisplayService.nightModeActive
opacity: !DisplayService.nightModeActive ? 1 : 0.6
currentValue: SessionData.nightModeTemperature + "K" currentValue: SessionData.nightModeTemperature + "K"
options: { options: {
var temps = []; var temps = [];
@@ -659,287 +650,274 @@ Item {
} }
DankToggle { DankToggle {
id: automaticToggle
width: parent.width width: parent.width
text: "Night Mode Automation" text: "Automatic Control"
description: "Automatically enable/disable night mode based on time or location, independent of the manual toggle." description: "Let the system automatically turn night mode on and off"
checked: SessionData.nightModeAutoEnabled checked: SessionData.nightModeAutoEnabled
onToggled: (checked) => { onToggled: (checked) => {
SessionData.setNightModeAutoEnabled(checked); SessionData.setNightModeAutoEnabled(checked);
} }
Connections {
target: SessionData
function onNightModeAutoEnabledChanged() {
automaticToggle.checked = SessionData.nightModeAutoEnabled;
}
}
} }
Column { Column {
id: automaticSettings
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingS
visible: SessionData.nightModeAutoEnabled visible: SessionData.nightModeAutoEnabled
leftPadding: Theme.spacingM leftPadding: Theme.spacingM
Row { Connections {
spacing: Theme.spacingL target: SessionData
width: parent.width - parent.leftPadding function onNightModeAutoEnabledChanged() {
automaticSettings.visible = SessionData.nightModeAutoEnabled;
StyledText {
text: "Mode:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
} }
DankTabBar {
width: 200
height: 32
model: [{
"text": "Time"
}, {
"text": "Location"
}]
currentIndex: SessionData.nightModeAutoMode === "location" ? 1 : 0
onTabClicked: (index) => {
SessionData.setNightModeAutoMode(index === 1 ? "location" : "time");
}
}
} }
Row { DankTabBar {
spacing: Theme.spacingM id: modeTabBarNight
visible: SessionData.nightModeAutoMode === "time" width: 200
width: parent.width - parent.leftPadding height: 32
topPadding: Theme.spacingL model: [{
"text": "Time"
StyledText { }, {
text: "Start:" "text": "Location"
font.pixelSize: Theme.fontSizeMedium }]
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter Component.onCompleted: {
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
} }
DankTextField { onTabClicked: (index) => {
id: startTimeField console.log("Tab clicked:", index, "Setting mode to:", index === 1 ? "location" : "time");
width: 80 DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time");
height: 32 currentIndex = index;
text: SessionData.nightModeStartTime
placeholderText: "20:00"
maximumLength: 5
topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS
normalBorderColor: {
if (text.length === 0) return Theme.outlineStrong;
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
return isValid ? Theme.success : Theme.error;
}
onAccepted: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
if (isValid) {
SessionData.setNightModeStartTime(text);
} else {
text = SessionData.nightModeStartTime;
}
}
onEditingFinished: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
if (isValid) {
SessionData.setNightModeStartTime(text);
} else {
text = SessionData.nightModeStartTime;
}
}
anchors.verticalCenter: parent.verticalCenter
validator: RegularExpressionValidator {
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
}
} }
StyledText {
text: "End:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: endTimeField
width: 80
height: 32
text: SessionData.nightModeEndTime
placeholderText: "06:00"
maximumLength: 5
topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS
normalBorderColor: {
if (text.length === 0) return Theme.outlineStrong;
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
return isValid ? Theme.success : Theme.error;
}
onAccepted: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
if (isValid) {
SessionData.setNightModeEndTime(text);
} else {
text = SessionData.nightModeEndTime;
}
}
onEditingFinished: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
if (isValid) {
SessionData.setNightModeEndTime(text);
} else {
text = SessionData.nightModeEndTime;
}
}
anchors.verticalCenter: parent.verticalCenter
validator: RegularExpressionValidator {
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
}
}
} }
Column { Column {
width: parent.width - parent.leftPadding property bool isTimeMode: SessionData.nightModeAutoMode === "time"
spacing: Theme.spacingXS visible: isTimeMode
visible: SessionData.nightModeAutoMode === "location" spacing: Theme.spacingM
// Header row
Row {
spacing: Theme.spacingM
height: 20
leftPadding: 45
StyledText {
text: "Hour"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 50
horizontalAlignment: Text.AlignHCenter
anchors.bottom: parent.bottom
}
StyledText {
text: "Minute"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 50
horizontalAlignment: Text.AlignHCenter
anchors.bottom: parent.bottom
}
}
// Start time row
Row {
spacing: Theme.spacingM
height: 32
StyledText {
id: startLabel
text: "Start"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: 50
anchors.verticalCenter: parent.verticalCenter
}
DankDropdown {
width: 60
height: 32
text: ""
currentValue: SessionData.nightModeStartHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++) {
hours.push(i.toString());
}
return hours;
}
onValueChanged: (value) => {
SessionData.setNightModeStartHour(parseInt(value));
}
}
DankDropdown {
width: 60
height: 32
text: ""
currentValue: SessionData.nightModeStartMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: (value) => {
SessionData.setNightModeStartMinute(parseInt(value));
}
}
}
// End time row
Row {
spacing: Theme.spacingM
height: 32
StyledText {
text: "End"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: startLabel.width
anchors.verticalCenter: parent.verticalCenter
}
DankDropdown {
width: 60
height: 32
text: ""
currentValue: SessionData.nightModeEndHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++) {
hours.push(i.toString());
}
return hours;
}
onValueChanged: (value) => {
SessionData.setNightModeEndHour(parseInt(value));
}
}
DankDropdown {
width: 60
height: 32
text: ""
currentValue: SessionData.nightModeEndMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: (value) => {
SessionData.setNightModeEndMinute(parseInt(value));
}
}
}
}
Column {
property bool isLocationMode: SessionData.nightModeAutoMode === "location"
visible: isLocationMode
spacing: Theme.spacingM
width: parent.width
DankToggle {
width: parent.width
text: "Auto-location"
description: DisplayService.geoclueAvailable ? "Use automatic location detection (geoclue2)" : "Geoclue service not running - cannot auto-detect location"
checked: SessionData.nightModeLocationProvider === "geoclue2"
enabled: DisplayService.geoclueAvailable
onToggled: (checked) => {
if (checked && DisplayService.geoclueAvailable) {
SessionData.setNightModeLocationProvider("geoclue2")
SessionData.setLatitude(0.0)
SessionData.setLongitude(0.0)
} else {
SessionData.setNightModeLocationProvider("")
}
}
}
StyledText { StyledText {
text: "Uses automatic location detection for sunrise/sunset times. If automatic detection fails, enter your coordinates manually below (e.g., NYC: 40.7128, -74.0060)." text: "Manual Coordinates"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
visible: SessionData.nightModeLocationProvider !== "geoclue2"
}
Row {
spacing: Theme.spacingM
visible: SessionData.nightModeLocationProvider !== "geoclue2"
Column {
spacing: Theme.spacingXS
StyledText {
text: "Latitude"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.latitude.toString()
placeholderText: "0.0"
onTextChanged: {
const lat = parseFloat(text) || 0.0
if (lat >= -90 && lat <= 90) {
SessionData.setLatitude(lat)
}
}
}
}
Column {
spacing: Theme.spacingXS
StyledText {
text: "Longitude"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.longitude.toString()
placeholderText: "0.0"
onTextChanged: {
const lon = parseFloat(text) || 0.0
if (lon >= -180 && lon <= 180) {
SessionData.setLongitude(lon)
}
}
}
}
}
StyledText {
text: "Uses sunrise/sunset times to automatically adjust night mode based on your location."
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
}
Row {
spacing: Theme.spacingM
visible: SessionData.nightModeAutoMode === "location"
width: parent.width - parent.leftPadding
StyledText {
text: "Coordinates:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: latitudeField
width: 90
height: 32
text: SessionData.latitude ? SessionData.latitude.toString() : ""
placeholderText: "40.7128"
maximumLength: 10
topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS
normalBorderColor: {
if (text.length === 0) return Theme.outlineStrong;
var lat = parseFloat(text);
return (!isNaN(lat) && lat >= -90 && lat <= 90) ? Theme.success : Theme.error;
}
onAccepted: {
var lat = parseFloat(text);
if (!isNaN(lat) && lat >= -90 && lat <= 90) {
SessionData.setLatitude(lat);
} else {
text = SessionData.latitude ? SessionData.latitude.toString() : "";
}
}
onEditingFinished: {
var lat = parseFloat(text);
if (!isNaN(lat) && lat >= -90 && lat <= 90) {
SessionData.setLatitude(lat);
} else {
text = SessionData.latitude ? SessionData.latitude.toString() : "";
}
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: ","
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: longitudeField
width: 90
height: 32
text: SessionData.longitude ? SessionData.longitude.toString() : ""
placeholderText: "-74.0060"
maximumLength: 11
topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS
normalBorderColor: {
if (text.length === 0) return Theme.outlineStrong;
var lon = parseFloat(text);
return (!isNaN(lon) && lon >= -180 && lon <= 180) ? Theme.success : Theme.error;
}
onAccepted: {
var lon = parseFloat(text);
if (!isNaN(lon) && lon >= -180 && lon <= 180) {
SessionData.setLongitude(lon);
} else {
text = SessionData.longitude ? SessionData.longitude.toString() : "";
}
}
onEditingFinished: {
var lon = parseFloat(text);
if (!isNaN(lon) && lon >= -180 && lon <= 180) {
SessionData.setLongitude(lon);
} else {
text = SessionData.longitude ? SessionData.longitude.toString() : "";
}
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "(lat, lng)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledRect {
width: 60
height: 32
radius: Theme.cornerRadius
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
StyledText {
text: "Clear"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.centerIn: parent
}
StateLayer {
stateColor: Theme.error
cornerRadius: parent.radius
onClicked: {
console.log("PersonalizationTab: Clearing location coordinates via UI")
SessionData.setLatitude(0.0);
SessionData.setLongitude(0.0);
latitudeField.text = "";
longitudeField.text = "";
// Also call the service clear function
if (typeof globalThis !== 'undefined' && globalThis.clearNightModeLocation) {
globalThis.clearNightModeLocation();
}
}
}
anchors.verticalCenter: parent.verticalCenter
}
} }

View File

@@ -5,7 +5,6 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Services
Singleton { Singleton {
id: root id: root
@@ -33,7 +32,18 @@ Singleton {
signal brightnessChanged signal brightnessChanged
signal deviceSwitched signal deviceSwitched
property bool nightModeActive: false property bool nightModeActive: nightModeEnabled
// Night Mode Properties
property bool nightModeEnabled: false
property bool automationAvailable: false
property bool geoclueAvailable: false
property bool isAutomaticNightTime: false
function buildGammastepCommand(gammastepArgs) {
const commandStr = "pkill gammastep; " + ["gammastep"].concat(gammastepArgs).join(" ")
return ["sh", "-c", commandStr]
}
function setBrightnessInternal(percentage, device) { function setBrightnessInternal(percentage, device) {
const clampedValue = Math.max(1, Math.min(100, percentage)) const clampedValue = Math.max(1, Math.min(100, percentage))
@@ -214,58 +224,211 @@ Singleton {
ddcInitialBrightnessProcess.running = true ddcInitialBrightnessProcess.running = true
} }
// Night Mode Functions - Simplified
function enableNightMode() { function enableNightMode() {
if (nightModeActive) if (!automationAvailable) {
gammaStepTestProcess.running = true
return return
}
// Test if gammastep exists before enabling nightModeEnabled = true
gammaStepTestProcess.running = true SessionData.setNightModeEnabled(true)
}
// Apply immediately or start automation
function updateNightModeTemperature(temperature) { if (SessionData.nightModeAutoEnabled) {
SessionData.setNightModeTemperature(temperature) startAutomation()
} else {
// If night mode is active, restart it with new temperature applyNightModeDirectly()
if (nightModeActive) {
// Temporarily disable and re-enable to restart with new temp
nightModeActive = false
Qt.callLater(() => {
if (SessionData.nightModeEnabled) {
nightModeActive = true
}
})
} }
} }
function disableNightMode() { function disableNightMode() {
nightModeActive = false nightModeEnabled = false
SessionData.setNightModeEnabled(false) SessionData.setNightModeEnabled(false)
stopAutomation()
// Also kill any stray gammastep processes // Nuclear approach - kill ALL gammastep processes multiple times
Quickshell.execDetached(["pkill", "gammastep"]) Quickshell.execDetached(["pkill", "-f", "gammastep"])
Quickshell.execDetached(["pkill", "-9", "gammastep"])
Quickshell.execDetached(["killall", "gammastep"])
// Also stop all related processes
gammaStepProcess.running = false
automationProcess.running = false
gammaStepTestProcess.running = false
} }
function toggleNightMode() { function toggleNightMode() {
// Check if automation is active - show warning if trying to manually toggle if (nightModeEnabled) {
if (SessionData.nightModeAutoEnabled) {
ToastService.showWarning("Night mode is in automatic mode. Disable automation in settings to control manually.")
return
}
if (nightModeActive) {
disableNightMode() disableNightMode()
} else { } else {
enableNightMode() enableNightMode()
} }
} }
function applyNightModeDirectly() {
const temperature = SessionData.nightModeTemperature || 4500
gammaStepProcess.command = buildGammastepCommand([
"-m", "wayland",
"-O", String(temperature)
])
gammaStepProcess.running = true
}
function resetToNormalMode() {
// Just kill gammastep to return to normal display temperature
Quickshell.execDetached(["pkill", "gammastep"])
}
function startAutomation() {
if (!automationAvailable) return
const mode = SessionData.nightModeAutoMode || "time"
switch (mode) {
case "time":
startTimeBasedMode()
break
case "location":
startLocationBasedMode()
break
}
}
function stopAutomation() {
automationProcess.running = false
gammaStepProcess.running = false
isAutomaticNightTime = false
// Nuclear approach - kill ALL gammastep processes multiple times
Quickshell.execDetached(["pkill", "-f", "gammastep"])
Quickshell.execDetached(["pkill", "-9", "gammastep"])
Quickshell.execDetached(["killall", "gammastep"])
}
function startTimeBasedMode() {
checkTimeBasedMode()
}
function startLocationBasedMode() {
const temperature = SessionData.nightModeTemperature || 4500
const dayTemp = 6500
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
automationProcess.command = buildGammastepCommand([
"-m", "wayland",
"-l", `${SessionData.latitude.toFixed(6)}:${SessionData.longitude.toFixed(6)}`,
"-t", `${dayTemp}:${temperature}`,
"-v"
])
automationProcess.running = true
return
}
if (SessionData.nightModeLocationProvider === "geoclue2") {
automationProcess.command = buildGammastepCommand([
"-m", "wayland",
"-l", "geoclue2",
"-t", `${dayTemp}:${temperature}`,
"-v"
])
automationProcess.running = true
return
}
console.warn("DisplayService: Location mode selected but no coordinates or geoclue provider set")
}
function checkTimeBasedMode() {
if (!nightModeEnabled || !SessionData.nightModeAutoEnabled || SessionData.nightModeAutoMode !== "time") {
return
}
const now = new Date()
const currentTime = now.getHours() * 60 + now.getMinutes()
const startMinutes = SessionData.nightModeStartHour * 60 + SessionData.nightModeStartMinute
const endMinutes = SessionData.nightModeEndHour * 60 + SessionData.nightModeEndMinute
let shouldBeNight = false
if (startMinutes > endMinutes) {
shouldBeNight = (currentTime >= startMinutes) || (currentTime < endMinutes)
} else {
shouldBeNight = (currentTime >= startMinutes) && (currentTime < endMinutes)
}
if (shouldBeNight !== isAutomaticNightTime) {
isAutomaticNightTime = shouldBeNight
if (shouldBeNight) {
applyNightModeDirectly()
} else {
resetToNormalMode()
}
}
}
function detectLocationProviders() {
geoclueDetectionProcess.running = true
}
function setNightModeAutomationMode(mode) {
console.log("DisplayService: Setting night mode automation mode to:", mode)
SessionData.setNightModeAutoMode(mode)
}
function evaluateNightMode() {
console.log("DisplayService: Evaluating night mode state - enabled:", nightModeEnabled, "auto:", SessionData.nightModeAutoEnabled)
// Always stop all processes first to clean slate
stopAutomation()
if (!nightModeEnabled) {
return
}
if (SessionData.nightModeAutoEnabled) {
restartTimer.nextAction = "automation"
restartTimer.start()
} else {
restartTimer.nextAction = "direct"
restartTimer.start()
}
}
function checkNightModeAvailability() {
gammastepAvailabilityProcess.running = true
}
Timer {
id: restartTimer
property string nextAction: ""
interval: 100
repeat: false
onTriggered: {
if (nextAction === "automation") {
startAutomation()
} else if (nextAction === "direct") {
applyNightModeDirectly()
}
nextAction = ""
}
}
Component.onCompleted: { Component.onCompleted: {
ddcDetectionProcess.running = true ddcDetectionProcess.running = true
refreshDevices() refreshDevices()
checkNightModeAvailability()
// Check if night mode was enabled on startup // Initialize night mode state from session
if (SessionData.nightModeEnabled) { nightModeEnabled = SessionData.nightModeEnabled
enableNightMode() }
SystemClock {
precision: SystemClock.Minutes
onDateChanged: {
if (nightModeEnabled && SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
checkTimeBasedMode()
}
} }
} }
@@ -393,27 +556,11 @@ Singleton {
"max": parseInt(parts[4]) "max": parseInt(parts[4])
}) })
} }
// Store brightnessctl devices separately, will be combined with DDC // Store brightnessctl devices separately
const brightnessCtlDevices = newDevices devices = newDevices
devices = brightnessCtlDevices
// Always refresh to combine with DDC devices and set up device selection
// If we have DDC devices, combine them refreshDevicesInternal()
if (ddcDevices.length > 0) {
refreshDevicesInternal()
} else if (devices.length > 0 && !currentDevice) {
// Try to restore last selected device, fallback to first device
const lastDevice = SessionData.lastBrightnessDevice || ""
const deviceExists = devices.some(
d => d.name === lastDevice)
if (deviceExists) {
setCurrentDevice(lastDevice, false)
} else {
const nonKbdDevice = devices.find(
d => !d.name.includes("kbd"))
|| devices[0]
setCurrentDevice(nonKbdDevice.name, false)
}
}
} }
} }
} }
@@ -564,46 +711,108 @@ Singleton {
} }
Process { Process {
id: gammaStepTestProcess id: gammastepAvailabilityProcess
command: ["which", "gammastep"]
running: false
onExited: function(exitCode) {
automationAvailable = (exitCode === 0)
if (automationAvailable) {
console.log("DisplayService: gammastep available")
detectLocationProviders()
// If night mode should be enabled on startup
if (nightModeEnabled && SessionData.nightModeAutoEnabled) {
startAutomation()
} else if (nightModeEnabled) {
applyNightModeDirectly()
}
} else {
console.log("DisplayService: gammastep not available")
}
}
}
Process {
id: geoclueDetectionProcess
command: ["sh", "-c", "busctl --system list | grep -qF org.freedesktop.GeoClue2"]
running: false
onExited: function (exitCode) {
geoclueAvailable = (exitCode === 0)
console.log("DisplayService: geoclue available:", geoclueAvailable)
}
}
Process {
id: gammaStepTestProcess
command: ["which", "gammastep"] command: ["which", "gammastep"]
running: false running: false
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode === 0) { if (exitCode === 0) {
// gammastep exists, enable night mode automationAvailable = true
nightModeActive = true nightModeEnabled = true
SessionData.setNightModeEnabled(true) SessionData.setNightModeEnabled(true)
if (SessionData.nightModeAutoEnabled) {
startAutomation()
} else {
applyNightModeDirectly()
}
} else { } else {
// gammastep not found
console.warn("DisplayService: gammastep not found") console.warn("DisplayService: gammastep not found")
ToastService.showWarning( ToastService.showWarning("Night mode failed: gammastep not found")
"Night mode failed: gammastep not found")
} }
} }
} }
Process { Process {
id: gammaStepProcess id: gammaStepProcess
running: false
command: {
const temperature = SessionData.nightModeTemperature || 4500
return ["gammastep", "-m", "wayland", "-O", String(temperature)]
}
running: nightModeActive
onExited: function (exitCode) { onExited: function (exitCode) {
// If process exits with non-zero code while we think it should be running if (nightModeEnabled && exitCode !== 0 && exitCode !== 15) {
if (nightModeActive && exitCode !== 0) { console.warn("DisplayService: Night mode process failed:", exitCode)
console.warn("DisplayService: Night mode process crashed with exit code:",
exitCode)
nightModeActive = false
SessionData.setNightModeEnabled(false)
ToastService.showWarning("Night mode failed: process crashed")
} }
} }
} }
Process {
id: automationProcess
running: false
property string processType: "automation"
onExited: function (exitCode) {
if (nightModeEnabled && SessionData.nightModeAutoEnabled && exitCode !== 0 && exitCode !== 15) {
console.warn("DisplayService: Night mode automation failed:", exitCode)
// Location mode failed
console.warn("DisplayService: Location-based night mode failed")
}
}
}
// 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 onLatitudeChanged() { evaluateNightMode() }
function onLongitudeChanged() { evaluateNightMode() }
function onNightModeLocationProviderChanged() { evaluateNightMode() }
}
// IPC Handler for external control // IPC Handler for external control
IpcHandler { IpcHandler {
function set(percentage: string, device: string): string { function set(percentage: string, device: string): string {
@@ -702,9 +911,9 @@ Singleton {
if (!root.brightnessAvailable) if (!root.brightnessAvailable)
return "No brightness devices available" return "No brightness devices available"
let result = "Available devices:\n" let result = "Available devices:\\n"
for (const device of root.devices) { for (const device of root.devices) {
result += device.name + " (" + device.class + ")\n" result += device.name + " (" + device.class + ")\\n"
} }
return result return result
} }
@@ -716,7 +925,7 @@ Singleton {
IpcHandler { IpcHandler {
function toggle(): string { function toggle(): string {
root.toggleNightMode() root.toggleNightMode()
return root.nightModeActive ? "Night mode enabled" : "Night mode disabled" return root.nightModeEnabled ? "Night mode enabled" : "Night mode disabled"
} }
function enable(): string { function enable(): string {
@@ -730,7 +939,7 @@ Singleton {
} }
function status(): string { function status(): string {
return root.nightModeActive ? "Night mode is enabled" : "Night mode is disabled" return root.nightModeEnabled ? "Night mode is enabled" : "Night mode is disabled"
} }
function temperature(value: string): string { function temperature(value: string): string {
@@ -753,12 +962,13 @@ Singleton {
SessionData.setNightModeTemperature(rounded) SessionData.setNightModeTemperature(rounded)
// If night mode is active, restart it with new temperature // Restart night mode with new temperature if active
if (root.nightModeActive) { if (root.nightModeEnabled) {
root.nightModeActive = false if (SessionData.nightModeAutoEnabled) {
Qt.callLater(() => { root.startAutomation()
root.nightModeActive = true } else {
}) root.applyNightModeDirectly()
}
} }
if (rounded !== temp) { if (rounded !== temp) {
@@ -770,4 +980,4 @@ Singleton {
target: "night" target: "night"
} }
} }

View File

@@ -1,505 +0,0 @@
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()
}
}
}

View File

@@ -49,6 +49,7 @@ Rectangle {
} }
} }
Column { Column {
anchors.left: parent.left anchors.left: parent.left
anchors.right: dropdown.left anchors.right: dropdown.left
@@ -77,7 +78,7 @@ Rectangle {
Rectangle { Rectangle {
id: dropdown id: dropdown
width: 180 width: root.width <= 60 ? root.width : 180
height: 36 height: 36
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
@@ -123,16 +124,18 @@ Rectangle {
} }
size: 18 size: 18
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: name !== "" visible: name !== "" && root.width > 60
} }
StyledText { StyledText {
text: root.currentValue text: root.currentValue
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
width: dropdown.width - contentRow.x - expandIcon.width width: root.width <= 60 ?
- Theme.spacingM - Theme.spacingS (dropdown.width - expandIcon.width - Theme.spacingS * 2) :
elide: Text.ElideRight (dropdown.width - contentRow.x - expandIcon.width - Theme.spacingM - Theme.spacingS)
elide: root.width <= 60 ? Text.ElideNone : Text.ElideRight
horizontalAlignment: root.width <= 60 ? Text.AlignHCenter : Text.AlignLeft
} }
} }

View File

@@ -24,8 +24,8 @@ ShellRoot {
Component.onCompleted: { Component.onCompleted: {
PortalService.init() PortalService.init()
// Initialize NightModeAutomationService to trigger its Component.onCompleted // Initialize DisplayService night mode functionality
NightModeAutomationService.automationAvailable DisplayService.nightModeEnabled
} }
WallpaperBackground { WallpaperBackground {