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:
@@ -19,11 +19,14 @@ Singleton {
|
||||
property bool nightModeEnabled: false
|
||||
property int nightModeTemperature: 4500
|
||||
property bool nightModeAutoEnabled: false
|
||||
property string nightModeAutoMode: "manual"
|
||||
property string nightModeStartTime: "20:00"
|
||||
property string nightModeEndTime: "06:00"
|
||||
property string nightModeAutoMode: "time"
|
||||
property int nightModeStartHour: 18
|
||||
property int nightModeStartMinute: 0
|
||||
property int nightModeEndHour: 6
|
||||
property int nightModeEndMinute: 0
|
||||
property real latitude: 0.0
|
||||
property real longitude: 0.0
|
||||
property string nightModeLocationProvider: ""
|
||||
property var pinnedApps: []
|
||||
property int selectedGpuIndex: 0
|
||||
property bool nvidiaGpuTempEnabled: false
|
||||
@@ -61,13 +64,27 @@ Singleton {
|
||||
nightModeAutoEnabled = settings.nightModeAutoEnabled
|
||||
!== undefined ? settings.nightModeAutoEnabled : false
|
||||
nightModeAutoMode = settings.nightModeAutoMode
|
||||
!== undefined ? settings.nightModeAutoMode : "manual"
|
||||
nightModeStartTime = settings.nightModeStartTime
|
||||
!== undefined ? settings.nightModeStartTime : "20:00"
|
||||
nightModeEndTime = settings.nightModeEndTime
|
||||
!== undefined ? settings.nightModeEndTime : "06:00"
|
||||
!== undefined ? settings.nightModeAutoMode : "time"
|
||||
// Handle legacy time format
|
||||
if (settings.nightModeStartTime !== undefined) {
|
||||
const parts = settings.nightModeStartTime.split(":")
|
||||
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
|
||||
longitude = settings.longitude !== undefined ? settings.longitude : 0.0
|
||||
nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : ""
|
||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : []
|
||||
selectedGpuIndex = settings.selectedGpuIndex
|
||||
!== undefined ? settings.selectedGpuIndex : 0
|
||||
@@ -104,10 +121,13 @@ Singleton {
|
||||
"nightModeTemperature": nightModeTemperature,
|
||||
"nightModeAutoEnabled": nightModeAutoEnabled,
|
||||
"nightModeAutoMode": nightModeAutoMode,
|
||||
"nightModeStartTime": nightModeStartTime,
|
||||
"nightModeEndTime": nightModeEndTime,
|
||||
"nightModeStartHour": nightModeStartHour,
|
||||
"nightModeStartMinute": nightModeStartMinute,
|
||||
"nightModeEndHour": nightModeEndHour,
|
||||
"nightModeEndMinute": nightModeEndMinute,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"nightModeLocationProvider": nightModeLocationProvider,
|
||||
"pinnedApps": pinnedApps,
|
||||
"selectedGpuIndex": selectedGpuIndex,
|
||||
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
|
||||
@@ -152,13 +172,23 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeStartTime(time) {
|
||||
nightModeStartTime = time
|
||||
function setNightModeStartHour(hour) {
|
||||
nightModeStartHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndTime(time) {
|
||||
nightModeEndTime = time
|
||||
function setNightModeStartMinute(minute) {
|
||||
nightModeStartMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndHour(hour) {
|
||||
nightModeEndHour = hour
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeEndMinute(minute) {
|
||||
nightModeEndMinute = minute
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
@@ -174,6 +204,11 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setNightModeLocationProvider(provider) {
|
||||
nightModeLocationProvider = provider
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setWallpaperPath(path) {
|
||||
wallpaperPath = path
|
||||
saveSettings()
|
||||
|
||||
@@ -117,7 +117,7 @@ DankPopout {
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
x: Theme.spacingL
|
||||
y: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
@@ -630,16 +630,30 @@ DankPopout {
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
Item {
|
||||
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 {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
}
|
||||
AudioSliderRow {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
}
|
||||
|
||||
BrightnessSliderRow {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
Item {
|
||||
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 {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
iconName: DisplayService.nightModeActive ? "nightlight" : "dark_mode"
|
||||
iconName: DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
text: "Night Mode"
|
||||
secondaryText: DisplayService.nightModeActive ? "On" : "Off"
|
||||
isActive: true
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
secondaryText: DisplayService.nightModeEnabled ? "On" : "Off"
|
||||
isActive: DisplayService.nightModeEnabled
|
||||
enabled: DisplayService.automationAvailable
|
||||
onClicked: DisplayService.toggleNightMode()
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ Row {
|
||||
|
||||
property var defaultSink: AudioService.sink
|
||||
|
||||
height: 60
|
||||
spacing: Theme.spacingM
|
||||
height: 40
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
@@ -58,10 +58,7 @@ Row {
|
||||
|
||||
DankSlider {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: {
|
||||
if (parent.width <= 0) return 80
|
||||
return Math.max(80, Math.min(400, parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM))
|
||||
}
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
|
||||
enabled: defaultSink !== null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
|
||||
@@ -8,15 +8,15 @@ import qs.Widgets
|
||||
Row {
|
||||
id: root
|
||||
|
||||
height: 60
|
||||
spacing: Theme.spacingM
|
||||
height: 40
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize + Theme.spacingS * 2
|
||||
height: Theme.iconSize + Theme.spacingS * 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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)
|
||||
: "transparent"
|
||||
|
||||
@@ -27,8 +27,8 @@ Row {
|
||||
MouseArea {
|
||||
id: iconArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: DisplayService.devices.length > 1
|
||||
cursorShape: DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: function(event) {
|
||||
if (DisplayService.devices.length > 1) {
|
||||
@@ -57,55 +57,27 @@ Row {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
DankSlider {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) - Theme.spacingM
|
||||
spacing: 0
|
||||
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
value: {
|
||||
let level = DisplayService.brightnessLevel
|
||||
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)
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
value: {
|
||||
let level = DisplayService.brightnessLevel
|
||||
if (level > 100) {
|
||||
let deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (deviceInfo && deviceInfo.max > 0) {
|
||||
return Math.round((level / deviceInfo.max) * 100)
|
||||
}
|
||||
return 50
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: {
|
||||
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
|
||||
onSliderValueChanged: function(newValue) {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(newValue)
|
||||
}
|
||||
width: parent.width
|
||||
text: DisplayService.currentDevice || ""
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
topPadding: 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -608,35 +608,26 @@ Item {
|
||||
id: nightModeToggle
|
||||
|
||||
width: parent.width
|
||||
text: "Night Mode (Manual)"
|
||||
description: SessionData.nightModeAutoEnabled ? "Manual control - automation will override when scheduled" : "Apply warm color temperature to reduce eye strain"
|
||||
checked: DisplayService.nightModeActive
|
||||
opacity: SessionData.nightModeAutoEnabled ? 0.7 : 1
|
||||
text: "Night Mode"
|
||||
description: "Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates."
|
||||
checked: DisplayService.nightModeEnabled
|
||||
onToggled: (checked) => {
|
||||
if (checked !== DisplayService.nightModeActive) {
|
||||
if (checked)
|
||||
DisplayService.enableNightMode();
|
||||
else
|
||||
DisplayService.disableNightMode();
|
||||
}
|
||||
DisplayService.toggleNightMode();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNightModeActiveChanged() {
|
||||
nightModeToggle.checked = DisplayService.nightModeActive;
|
||||
function onNightModeEnabledChanged() {
|
||||
nightModeToggle.checked = DisplayService.nightModeEnabled;
|
||||
}
|
||||
|
||||
target: DisplayService
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: "Night Mode Temperature"
|
||||
description: DisplayService.nightModeActive ? "Disable night mode to adjust" : "Set temperature for night mode"
|
||||
enabled: !DisplayService.nightModeActive
|
||||
opacity: !DisplayService.nightModeActive ? 1 : 0.6
|
||||
text: "Temperature"
|
||||
description: "Color temperature for night mode"
|
||||
currentValue: SessionData.nightModeTemperature + "K"
|
||||
options: {
|
||||
var temps = [];
|
||||
@@ -659,287 +650,274 @@ Item {
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: automaticToggle
|
||||
width: parent.width
|
||||
text: "Night Mode Automation"
|
||||
description: "Automatically enable/disable night mode based on time or location, independent of the manual toggle."
|
||||
text: "Automatic Control"
|
||||
description: "Let the system automatically turn night mode on and off"
|
||||
checked: SessionData.nightModeAutoEnabled
|
||||
onToggled: (checked) => {
|
||||
SessionData.setNightModeAutoEnabled(checked);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticToggle.checked = SessionData.nightModeAutoEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: automaticSettings
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
visible: SessionData.nightModeAutoEnabled
|
||||
leftPadding: Theme.spacingM
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingL
|
||||
width: parent.width - parent.leftPadding
|
||||
|
||||
StyledText {
|
||||
text: "Mode:"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onNightModeAutoEnabledChanged() {
|
||||
automaticSettings.visible = SessionData.nightModeAutoEnabled;
|
||||
}
|
||||
|
||||
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 {
|
||||
spacing: Theme.spacingM
|
||||
visible: SessionData.nightModeAutoMode === "time"
|
||||
width: parent.width - parent.leftPadding
|
||||
topPadding: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: "Start:"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
DankTabBar {
|
||||
id: modeTabBarNight
|
||||
width: 200
|
||||
height: 32
|
||||
model: [{
|
||||
"text": "Time"
|
||||
}, {
|
||||
"text": "Location"
|
||||
}]
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = SessionData.nightModeAutoMode === "location" ? 1 : 0;
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: startTimeField
|
||||
width: 80
|
||||
height: 32
|
||||
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]$/
|
||||
}
|
||||
|
||||
|
||||
onTabClicked: (index) => {
|
||||
console.log("Tab clicked:", index, "Setting mode to:", index === 1 ? "location" : "time");
|
||||
DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time");
|
||||
currentIndex = index;
|
||||
}
|
||||
|
||||
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 {
|
||||
width: parent.width - parent.leftPadding
|
||||
spacing: Theme.spacingXS
|
||||
visible: SessionData.nightModeAutoMode === "location"
|
||||
property bool isTimeMode: SessionData.nightModeAutoMode === "time"
|
||||
visible: isTimeMode
|
||||
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 {
|
||||
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
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -33,7 +32,18 @@ Singleton {
|
||||
signal brightnessChanged
|
||||
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) {
|
||||
const clampedValue = Math.max(1, Math.min(100, percentage))
|
||||
@@ -214,58 +224,211 @@ Singleton {
|
||||
ddcInitialBrightnessProcess.running = true
|
||||
}
|
||||
|
||||
// Night Mode Functions - Simplified
|
||||
function enableNightMode() {
|
||||
if (nightModeActive)
|
||||
if (!automationAvailable) {
|
||||
gammaStepTestProcess.running = true
|
||||
return
|
||||
}
|
||||
|
||||
// Test if gammastep exists before enabling
|
||||
gammaStepTestProcess.running = true
|
||||
}
|
||||
|
||||
function updateNightModeTemperature(temperature) {
|
||||
SessionData.setNightModeTemperature(temperature)
|
||||
|
||||
// If night mode is active, restart it with new temperature
|
||||
if (nightModeActive) {
|
||||
// Temporarily disable and re-enable to restart with new temp
|
||||
nightModeActive = false
|
||||
Qt.callLater(() => {
|
||||
if (SessionData.nightModeEnabled) {
|
||||
nightModeActive = true
|
||||
}
|
||||
})
|
||||
nightModeEnabled = true
|
||||
SessionData.setNightModeEnabled(true)
|
||||
|
||||
// Apply immediately or start automation
|
||||
if (SessionData.nightModeAutoEnabled) {
|
||||
startAutomation()
|
||||
} else {
|
||||
applyNightModeDirectly()
|
||||
}
|
||||
}
|
||||
|
||||
function disableNightMode() {
|
||||
nightModeActive = false
|
||||
nightModeEnabled = false
|
||||
SessionData.setNightModeEnabled(false)
|
||||
|
||||
// Also kill any stray gammastep processes
|
||||
Quickshell.execDetached(["pkill", "gammastep"])
|
||||
stopAutomation()
|
||||
// Nuclear approach - kill ALL gammastep processes multiple times
|
||||
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() {
|
||||
// Check if automation is active - show warning if trying to manually toggle
|
||||
if (SessionData.nightModeAutoEnabled) {
|
||||
ToastService.showWarning("Night mode is in automatic mode. Disable automation in settings to control manually.")
|
||||
return
|
||||
}
|
||||
|
||||
if (nightModeActive) {
|
||||
if (nightModeEnabled) {
|
||||
disableNightMode()
|
||||
} else {
|
||||
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: {
|
||||
ddcDetectionProcess.running = true
|
||||
refreshDevices()
|
||||
checkNightModeAvailability()
|
||||
|
||||
// Check if night mode was enabled on startup
|
||||
if (SessionData.nightModeEnabled) {
|
||||
enableNightMode()
|
||||
// Initialize night mode state from session
|
||||
nightModeEnabled = SessionData.nightModeEnabled
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
precision: SystemClock.Minutes
|
||||
onDateChanged: {
|
||||
if (nightModeEnabled && SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "time") {
|
||||
checkTimeBasedMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,27 +556,11 @@ Singleton {
|
||||
"max": parseInt(parts[4])
|
||||
})
|
||||
}
|
||||
// Store brightnessctl devices separately, will be combined with DDC
|
||||
const brightnessCtlDevices = newDevices
|
||||
devices = brightnessCtlDevices
|
||||
|
||||
// If we have DDC devices, combine them
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Store brightnessctl devices separately
|
||||
devices = newDevices
|
||||
|
||||
// Always refresh to combine with DDC devices and set up device selection
|
||||
refreshDevicesInternal()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -564,46 +711,108 @@ Singleton {
|
||||
}
|
||||
|
||||
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"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
// gammastep exists, enable night mode
|
||||
nightModeActive = true
|
||||
automationAvailable = true
|
||||
nightModeEnabled = true
|
||||
SessionData.setNightModeEnabled(true)
|
||||
|
||||
if (SessionData.nightModeAutoEnabled) {
|
||||
startAutomation()
|
||||
} else {
|
||||
applyNightModeDirectly()
|
||||
}
|
||||
} else {
|
||||
// gammastep not found
|
||||
console.warn("DisplayService: gammastep not found")
|
||||
ToastService.showWarning(
|
||||
"Night mode failed: gammastep not found")
|
||||
ToastService.showWarning("Night mode failed: gammastep not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gammaStepProcess
|
||||
|
||||
command: {
|
||||
const temperature = SessionData.nightModeTemperature || 4500
|
||||
return ["gammastep", "-m", "wayland", "-O", String(temperature)]
|
||||
}
|
||||
running: nightModeActive
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
// If process exits with non-zero code while we think it should be running
|
||||
if (nightModeActive && exitCode !== 0) {
|
||||
console.warn("DisplayService: Night mode process crashed with exit code:",
|
||||
exitCode)
|
||||
nightModeActive = false
|
||||
SessionData.setNightModeEnabled(false)
|
||||
ToastService.showWarning("Night mode failed: process crashed")
|
||||
if (nightModeEnabled && exitCode !== 0 && exitCode !== 15) {
|
||||
console.warn("DisplayService: Night mode process failed:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
IpcHandler {
|
||||
function set(percentage: string, device: string): string {
|
||||
@@ -702,9 +911,9 @@ Singleton {
|
||||
if (!root.brightnessAvailable)
|
||||
return "No brightness devices available"
|
||||
|
||||
let result = "Available devices:\n"
|
||||
let result = "Available devices:\\n"
|
||||
for (const device of root.devices) {
|
||||
result += device.name + " (" + device.class + ")\n"
|
||||
result += device.name + " (" + device.class + ")\\n"
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -716,7 +925,7 @@ Singleton {
|
||||
IpcHandler {
|
||||
function toggle(): string {
|
||||
root.toggleNightMode()
|
||||
return root.nightModeActive ? "Night mode enabled" : "Night mode disabled"
|
||||
return root.nightModeEnabled ? "Night mode enabled" : "Night mode disabled"
|
||||
}
|
||||
|
||||
function enable(): string {
|
||||
@@ -730,7 +939,7 @@ Singleton {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -753,12 +962,13 @@ Singleton {
|
||||
|
||||
SessionData.setNightModeTemperature(rounded)
|
||||
|
||||
// If night mode is active, restart it with new temperature
|
||||
if (root.nightModeActive) {
|
||||
root.nightModeActive = false
|
||||
Qt.callLater(() => {
|
||||
root.nightModeActive = true
|
||||
})
|
||||
// Restart night mode with new temperature if active
|
||||
if (root.nightModeEnabled) {
|
||||
if (SessionData.nightModeAutoEnabled) {
|
||||
root.startAutomation()
|
||||
} else {
|
||||
root.applyNightModeDirectly()
|
||||
}
|
||||
}
|
||||
|
||||
if (rounded !== temp) {
|
||||
@@ -770,4 +980,4 @@ Singleton {
|
||||
|
||||
target: "night"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: dropdown.left
|
||||
@@ -77,7 +78,7 @@ Rectangle {
|
||||
Rectangle {
|
||||
id: dropdown
|
||||
|
||||
width: 180
|
||||
width: root.width <= 60 ? root.width : 180
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
@@ -123,16 +124,18 @@ Rectangle {
|
||||
}
|
||||
size: 18
|
||||
color: Theme.surfaceVariantText
|
||||
visible: name !== ""
|
||||
visible: name !== "" && root.width > 60
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.currentValue
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: dropdown.width - contentRow.x - expandIcon.width
|
||||
- Theme.spacingM - Theme.spacingS
|
||||
elide: Text.ElideRight
|
||||
width: root.width <= 60 ?
|
||||
(dropdown.width - expandIcon.width - Theme.spacingS * 2) :
|
||||
(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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ ShellRoot {
|
||||
|
||||
Component.onCompleted: {
|
||||
PortalService.init()
|
||||
// Initialize NightModeAutomationService to trigger its Component.onCompleted
|
||||
NightModeAutomationService.automationAvailable
|
||||
// Initialize DisplayService night mode functionality
|
||||
DisplayService.nightModeEnabled
|
||||
}
|
||||
|
||||
WallpaperBackground {
|
||||
|
||||
Reference in New Issue
Block a user