1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-15 10:12:07 -04:00

Initial commit for nightMode automation

This commit is contained in:
purian23
2025-08-28 17:18:59 -04:00
parent 6f11891b1c
commit 48a78c39e2
7 changed files with 1035 additions and 338 deletions

View File

@@ -18,6 +18,12 @@ Singleton {
property bool doNotDisturb: false property bool doNotDisturb: false
property bool nightModeEnabled: false property bool nightModeEnabled: false
property int nightModeTemperature: 4500 property int nightModeTemperature: 4500
property bool nightModeAutoEnabled: false
property string nightModeAutoMode: "manual"
property string nightModeStartTime: "20:00"
property string nightModeEndTime: "06:00"
property real latitude: 0.0
property real longitude: 0.0
property var pinnedApps: [] property var pinnedApps: []
property int selectedGpuIndex: 0 property int selectedGpuIndex: 0
property bool nvidiaGpuTempEnabled: false property bool nvidiaGpuTempEnabled: false
@@ -52,6 +58,16 @@ Singleton {
!== undefined ? settings.nightModeEnabled : false !== undefined ? settings.nightModeEnabled : false
nightModeTemperature = settings.nightModeTemperature nightModeTemperature = settings.nightModeTemperature
!== undefined ? settings.nightModeTemperature : 4500 !== undefined ? settings.nightModeTemperature : 4500
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"
latitude = settings.latitude !== undefined ? settings.latitude : 0.0
longitude = settings.longitude !== undefined ? settings.longitude : 0.0
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
@@ -86,6 +102,12 @@ Singleton {
"doNotDisturb": doNotDisturb, "doNotDisturb": doNotDisturb,
"nightModeEnabled": nightModeEnabled, "nightModeEnabled": nightModeEnabled,
"nightModeTemperature": nightModeTemperature, "nightModeTemperature": nightModeTemperature,
"nightModeAutoEnabled": nightModeAutoEnabled,
"nightModeAutoMode": nightModeAutoMode,
"nightModeStartTime": nightModeStartTime,
"nightModeEndTime": nightModeEndTime,
"latitude": latitude,
"longitude": longitude,
"pinnedApps": pinnedApps, "pinnedApps": pinnedApps,
"selectedGpuIndex": selectedGpuIndex, "selectedGpuIndex": selectedGpuIndex,
"nvidiaGpuTempEnabled": nvidiaGpuTempEnabled, "nvidiaGpuTempEnabled": nvidiaGpuTempEnabled,
@@ -119,6 +141,39 @@ Singleton {
saveSettings() saveSettings()
} }
function setNightModeAutoEnabled(enabled) {
console.log("SessionData: Setting nightModeAutoEnabled to", enabled)
nightModeAutoEnabled = enabled
saveSettings()
}
function setNightModeAutoMode(mode) {
nightModeAutoMode = mode
saveSettings()
}
function setNightModeStartTime(time) {
nightModeStartTime = time
saveSettings()
}
function setNightModeEndTime(time) {
nightModeEndTime = time
saveSettings()
}
function setLatitude(lat) {
console.log("SessionData: Setting latitude to", lat)
latitude = lat
saveSettings()
}
function setLongitude(lng) {
console.log("SessionData: Setting longitude to", lng)
longitude = lng
saveSettings()
}
function setWallpaperPath(path) { function setWallpaperPath(path) {
wallpaperPath = path wallpaperPath = path
saveSettings() saveSettings()

View File

@@ -762,9 +762,9 @@ DankPopout {
implicitHeight: { implicitHeight: {
let height = Theme.spacingL let height = Theme.spacingL
if (BrightnessService.brightnessAvailable) { if (DisplayService.brightnessAvailable) {
height += 80 height += 80
if (BrightnessService.devices.length > 1) { if (DisplayService.devices.length > 1) {
height += 40 height += 40
} }
} }

View File

@@ -34,7 +34,9 @@ Item {
width: parent.width width: parent.width
sourceComponent: settingsComponent sourceComponent: settingsComponent
} }
} }
} }
Component { Component {
@@ -43,7 +45,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
visible: BrightnessService.brightnessAvailable visible: DisplayService.brightnessAvailable
StyledText { StyledText {
text: "Brightness" text: "Brightness"
@@ -54,102 +56,105 @@ Item {
DankDropdown { DankDropdown {
id: deviceDropdown id: deviceDropdown
width: parent.width width: parent.width
height: 40 height: 40
visible: BrightnessService.devices.length > 1 visible: DisplayService.devices.length > 1
text: "Device" text: "Device"
description: { description: {
const deviceInfo = BrightnessService.getCurrentDeviceInfo() const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (deviceInfo && deviceInfo.class === "ddc") { if (deviceInfo && deviceInfo.class === "ddc")
return "DDC changes can be slow and unreliable" return "DDC changes can be slow and unreliable";
return "";
} }
return "" currentValue: DisplayService.currentDevice
} options: DisplayService.devices.map(function(d) {
currentValue: BrightnessService.currentDevice return d.name;
options: BrightnessService.devices.map(function (d) {
return d.name
}) })
optionIcons: BrightnessService.devices.map(function (d) { optionIcons: DisplayService.devices.map(function(d) {
if (d.class === "backlight") if (d.class === "backlight")
return "desktop_windows" return "desktop_windows";
if (d.class === "ddc") if (d.class === "ddc")
return "tv" return "tv";
if (d.name.includes("kbd")) if (d.name.includes("kbd"))
return "keyboard" return "keyboard";
return "lightbulb" return "lightbulb";
}) })
onValueChanged: function (value) { onValueChanged: function(value) {
BrightnessService.setCurrentDevice(value, true) DisplayService.setCurrentDevice(value, true);
} }
Connections { Connections {
target: BrightnessService
function onDevicesChanged() { function onDevicesChanged() {
if (BrightnessService.currentDevice) { if (DisplayService.currentDevice)
deviceDropdown.currentValue = BrightnessService.currentDevice deviceDropdown.currentValue = DisplayService.currentDevice;
}
// Check if saved device is now available // Check if saved device is now available
const lastDevice = SessionData.lastBrightnessDevice const lastDevice = SessionData.lastBrightnessDevice || "";
|| ""
if (lastDevice) { if (lastDevice) {
const deviceExists = BrightnessService.devices.some( const deviceExists = DisplayService.devices.some((d) => {
d => d.name === lastDevice) return d.name === lastDevice;
if (deviceExists });
&& (!BrightnessService.currentDevice if (deviceExists && (!DisplayService.currentDevice || DisplayService.currentDevice !== lastDevice))
|| BrightnessService.currentDevice !== lastDevice)) { DisplayService.setCurrentDevice(lastDevice, false);
BrightnessService.setCurrentDevice(lastDevice,
false)
}
} }
} }
function onDeviceSwitched() { function onDeviceSwitched() {
// Force update the description when device switches // Force update the description when device switches
deviceDropdown.description = Qt.binding(function () { deviceDropdown.description = Qt.binding(function() {
const deviceInfo = BrightnessService.getCurrentDeviceInfo() const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (deviceInfo && deviceInfo.class === "ddc") { if (deviceInfo && deviceInfo.class === "ddc")
return "DDC changes can be slow and unreliable" return "DDC changes can be slow and unreliable";
}
return "" return "";
}) });
} }
target: DisplayService
} }
} }
DankSlider { DankSlider {
id: brightnessSlider id: brightnessSlider
width: parent.width width: parent.width
value: BrightnessService.brightnessLevel value: DisplayService.brightnessLevel
leftIcon: "brightness_low" leftIcon: "brightness_low"
rightIcon: "brightness_high" rightIcon: "brightness_high"
enabled: BrightnessService.brightnessAvailable enabled: DisplayService.brightnessAvailable && DisplayService.isCurrentDeviceReady()
&& BrightnessService.isCurrentDeviceReady() opacity: DisplayService.isCurrentDeviceReady() ? 1 : 0.5
opacity: BrightnessService.isCurrentDeviceReady() ? 1.0 : 0.5 onSliderValueChanged: function(newValue) {
onSliderValueChanged: function (newValue) { brightnessDebounceTimer.pendingValue = newValue;
brightnessDebounceTimer.pendingValue = newValue brightnessDebounceTimer.restart();
brightnessDebounceTimer.restart()
} }
onSliderDragFinished: function (finalValue) { onSliderDragFinished: function(finalValue) {
brightnessDebounceTimer.stop() brightnessDebounceTimer.stop();
BrightnessService.setBrightnessInternal( DisplayService.setBrightnessInternal(finalValue, DisplayService.currentDevice);
finalValue, BrightnessService.currentDevice)
} }
Connections { Connections {
target: BrightnessService
function onBrightnessChanged() { function onBrightnessChanged() {
brightnessSlider.value = BrightnessService.brightnessLevel brightnessSlider.value = DisplayService.brightnessLevel;
} }
function onDeviceSwitched() { function onDeviceSwitched() {
brightnessSlider.value = BrightnessService.brightnessLevel brightnessSlider.value = DisplayService.brightnessLevel;
} }
target: DisplayService
} }
} }
} }
} }
Component { Component {
@@ -174,32 +179,40 @@ Item {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: BrightnessService.nightModeActive ? Qt.rgba( color: DisplayService.nightModeActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
Theme.primary.r, border.color: DisplayService.nightModeActive ? Theme.primary : "transparent"
Theme.primary.g, border.width: DisplayService.nightModeActive ? 1 : 0
Theme.primary.b, opacity: SessionData.nightModeAutoEnabled ? 0.6 : 1
0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
border.color: BrightnessService.nightModeActive ? Theme.primary : "transparent"
border.width: BrightnessService.nightModeActive ? 1 : 0
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: BrightnessService.nightModeActive ? "nightlight" : "dark_mode" name: DisplayService.nightModeActive ? "nightlight" : "dark_mode"
size: Theme.iconSizeLarge size: Theme.iconSizeLarge
color: BrightnessService.nightModeActive ? Theme.primary : Theme.surfaceText color: DisplayService.nightModeActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "Night Mode" text: SessionData.nightModeAutoEnabled ? "Night Mode (Auto)" : "Night Mode"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: BrightnessService.nightModeActive ? Theme.primary : Theme.surfaceText color: DisplayService.nightModeActive ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
}
DankIcon {
name: "schedule"
size: 16
color: Theme.primary
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Theme.spacingS
visible: SessionData.nightModeAutoEnabled
} }
MouseArea { MouseArea {
@@ -209,20 +222,17 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
BrightnessService.toggleNightMode() DisplayService.toggleNightMode();
} }
} }
} }
Rectangle { Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: SessionData.isLightMode ? Qt.rgba( color: SessionData.isLightMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : (lightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
border.color: SessionData.isLightMode ? Theme.primary : "transparent" border.color: SessionData.isLightMode ? Theme.primary : "transparent"
border.width: SessionData.isLightMode ? 1 : 0 border.width: SessionData.isLightMode ? 1 : 0
@@ -244,6 +254,7 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
MouseArea { MouseArea {
@@ -253,7 +264,7 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
Theme.toggleLightMode() Theme.toggleLightMode();
} }
} }
@@ -262,10 +273,15 @@ Item {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
brightnessDebounceTimer: Timer { brightnessDebounceTimer: Timer {
@@ -273,13 +289,13 @@ Item {
interval: { interval: {
// Use longer interval for DDC devices since ddcutil is slow // Use longer interval for DDC devices since ddcutil is slow
const deviceInfo = BrightnessService.getCurrentDeviceInfo() const deviceInfo = DisplayService.getCurrentDeviceInfo();
return (deviceInfo && deviceInfo.class === "ddc") ? 100 : 50 return (deviceInfo && deviceInfo.class === "ddc") ? 100 : 50;
} }
repeat: false repeat: false
onTriggered: { onTriggered: {
BrightnessService.setBrightnessInternal( DisplayService.setBrightnessInternal(pendingValue, DisplayService.currentDevice);
pendingValue, BrightnessService.currentDevice)
} }
} }
} }

View File

@@ -15,18 +15,18 @@ DankOSD {
property int pendingValue: 0 property int pendingValue: 0
interval: { interval: {
const deviceInfo = BrightnessService.getCurrentDeviceInfo() const deviceInfo = DisplayService.getCurrentDeviceInfo()
return (deviceInfo && deviceInfo.class === "ddc") ? 200 : 50 return (deviceInfo && deviceInfo.class === "ddc") ? 200 : 50
} }
repeat: false repeat: false
onTriggered: { onTriggered: {
BrightnessService.setBrightnessInternal(pendingValue, BrightnessService.lastIpcDevice) DisplayService.setBrightnessInternal(pendingValue, DisplayService.lastIpcDevice)
} }
} }
Connections { Connections {
target: BrightnessService target: DisplayService
function onBrightnessChanged() { function onBrightnessChanged() {
root.show() root.show()
} }
@@ -53,7 +53,7 @@ DankOSD {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
const deviceInfo = BrightnessService.getCurrentDeviceInfo() const deviceInfo = DisplayService.getCurrentDeviceInfo()
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc")
return "brightness_medium" return "brightness_medium"
else if (deviceInfo.name.includes("kbd")) else if (deviceInfo.name.includes("kbd"))
@@ -75,17 +75,17 @@ DankOSD {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
minimum: 1 minimum: 1
maximum: 100 maximum: 100
enabled: BrightnessService.brightnessAvailable enabled: DisplayService.brightnessAvailable
showValue: true showValue: true
unit: "%" unit: "%"
Component.onCompleted: { Component.onCompleted: {
if (BrightnessService.brightnessAvailable) if (DisplayService.brightnessAvailable)
value = BrightnessService.brightnessLevel value = DisplayService.brightnessLevel
} }
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (BrightnessService.brightnessAvailable) { if (DisplayService.brightnessAvailable) {
root.brightnessDebounceTimer.pendingValue = newValue root.brightnessDebounceTimer.pendingValue = newValue
root.brightnessDebounceTimer.restart() root.brightnessDebounceTimer.restart()
resetHideTimer() resetHideTimer()
@@ -97,23 +97,23 @@ DankOSD {
} }
onSliderDragFinished: function(finalValue) { onSliderDragFinished: function(finalValue) {
if (BrightnessService.brightnessAvailable) { if (DisplayService.brightnessAvailable) {
root.brightnessDebounceTimer.stop() root.brightnessDebounceTimer.stop()
BrightnessService.setBrightnessInternal(finalValue, BrightnessService.lastIpcDevice) DisplayService.setBrightnessInternal(finalValue, DisplayService.lastIpcDevice)
} }
} }
Connections { Connections {
target: BrightnessService target: DisplayService
function onBrightnessChanged() { function onBrightnessChanged() {
if (!brightnessSlider.pressed) if (!brightnessSlider.pressed)
brightnessSlider.value = BrightnessService.brightnessLevel brightnessSlider.value = DisplayService.brightnessLevel
} }
function onDeviceSwitched() { function onDeviceSwitched() {
if (!brightnessSlider.pressed) if (!brightnessSlider.pressed)
brightnessSlider.value = BrightnessService.brightnessLevel brightnessSlider.value = DisplayService.brightnessLevel
} }
} }
} }
@@ -121,10 +121,10 @@ DankOSD {
} }
onOsdShown: { onOsdShown: {
if (BrightnessService.brightnessAvailable && contentLoader.item) { if (DisplayService.brightnessAvailable && contentLoader.item) {
let slider = contentLoader.item.children[0].children[1] let slider = contentLoader.item.children[0].children[1]
if (slider) if (slider)
slider.value = BrightnessService.brightnessLevel slider.value = DisplayService.brightnessLevel
} }
} }
} }

View File

@@ -17,75 +17,56 @@ Item {
property bool fontsEnumerated: false property bool fontsEnumerated: false
function enumerateFonts() { function enumerateFonts() {
var fonts = ["Default"] var fonts = ["Default"];
var availableFonts = Qt.fontFamilies() var availableFonts = Qt.fontFamilies();
var rootFamilies = [] var rootFamilies = [];
var seenFamilies = new Set() var seenFamilies = new Set();
for (var i = 0; i < availableFonts.length; i++) { for (var i = 0; i < availableFonts.length; i++) {
var fontName = availableFonts[i] var fontName = availableFonts[i];
if (fontName.startsWith(".")) if (fontName.startsWith("."))
continue continue;
if (fontName === SettingsData.defaultFontFamily) if (fontName === SettingsData.defaultFontFamily)
continue continue;
var rootName = fontName.replace( var rootName = fontName.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i, function(match, suffix) {
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, return match;
"").replace( }).trim();
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
"").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i,
function (match, suffix) {
return match
}).trim()
if (!seenFamilies.has(rootName) && rootName !== "") { if (!seenFamilies.has(rootName) && rootName !== "") {
seenFamilies.add(rootName) seenFamilies.add(rootName);
rootFamilies.push(rootName) rootFamilies.push(rootName);
} }
} }
cachedFontFamilies = fonts.concat(rootFamilies.sort()) cachedFontFamilies = fonts.concat(rootFamilies.sort());
var monoFonts = ["Default"] var monoFonts = ["Default"];
var monoFamilies = [] var monoFamilies = [];
var seenMonoFamilies = new Set() var seenMonoFamilies = new Set();
for (var j = 0; j < availableFonts.length; j++) { for (var j = 0; j < availableFonts.length; j++) {
var fontName2 = availableFonts[j] var fontName2 = availableFonts[j];
if (fontName2.startsWith(".")) if (fontName2.startsWith("."))
continue continue;
if (fontName2 === SettingsData.defaultMonoFontFamily) if (fontName2 === SettingsData.defaultMonoFontFamily)
continue continue;
var lowerName = fontName2.toLowerCase() var lowerName = fontName2.toLowerCase();
if (lowerName.includes("mono") || lowerName.includes( if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
"code") || lowerName.includes( var rootName2 = fontName2.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").trim();
"console") || lowerName.includes(
"terminal") || lowerName.includes(
"courier") || lowerName.includes(
"dejavu sans mono") || lowerName.includes(
"jetbrains") || lowerName.includes(
"fira") || lowerName.includes(
"hack") || lowerName.includes(
"source code") || lowerName.includes(
"ubuntu mono") || lowerName.includes("cascadia")) {
var rootName2 = fontName2.replace(
/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i,
"").replace(
/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i,
"").trim()
if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") { if (!seenMonoFamilies.has(rootName2) && rootName2 !== "") {
seenMonoFamilies.add(rootName2) seenMonoFamilies.add(rootName2);
monoFamilies.push(rootName2) monoFamilies.push(rootName2);
} }
} }
} }
cachedMonoFamilies = monoFonts.concat(monoFamilies.sort()) cachedMonoFamilies = monoFonts.concat(monoFamilies.sort());
} }
Component.onCompleted: { Component.onCompleted: {
// Access WallpaperCyclingService to ensure it's initialized // Access WallpaperCyclingService to ensure it's initialized
WallpaperCyclingService.cyclingActive WallpaperCyclingService.cyclingActive;
if (!fontsEnumerated) { if (!fontsEnumerated) {
enumerateFonts() enumerateFonts();
fontsEnumerated = true fontsEnumerated = true;
} }
} }
@@ -107,10 +88,8 @@ Item {
width: parent.width width: parent.width
height: wallpaperSection.implicitHeight + Theme.spacingL * 2 height: wallpaperSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Column { Column {
@@ -138,6 +117,7 @@ Item {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
@@ -167,6 +147,7 @@ Item {
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1 maskSpreadAtMin: 1
} }
} }
Rectangle { Rectangle {
@@ -217,12 +198,13 @@ Item {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (parentModal) { if (parentModal) {
parentModal.allowFocusOverride = true parentModal.allowFocusOverride = true;
parentModal.shouldHaveFocus = false parentModal.shouldHaveFocus = false;
} }
wallpaperBrowser.open() wallpaperBrowser.open();
} }
} }
} }
Rectangle { Rectangle {
@@ -243,24 +225,29 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (Theme.currentTheme === Theme.dynamic) { if (Theme.currentTheme === Theme.dynamic)
Theme.switchTheme("blue") Theme.switchTheme("blue");
}
SessionData.setWallpaper("") SessionData.setWallpaper("");
} }
} }
} }
} }
} }
MouseArea { MouseArea {
id: wallpaperMouseArea id: wallpaperMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
} }
} }
Column { Column {
@@ -269,9 +256,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: SessionData.wallpaperPath ? SessionData.wallpaperPath.split( text: SessionData.wallpaperPath ? SessionData.wallpaperPath.split('/').pop() : "No wallpaper selected"
'/').pop(
) : "No wallpaper selected"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
elide: Text.ElideMiddle elide: Text.ElideMiddle
@@ -299,15 +284,11 @@ Item {
iconSize: Theme.iconSizeSmall iconSize: Theme.iconSizeSmall
enabled: SessionData.wallpaperPath enabled: SessionData.wallpaperPath
opacity: SessionData.wallpaperPath ? 1 : 0.5 opacity: SessionData.wallpaperPath ? 1 : 0.5
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
Theme.surfaceVariant.g, hoverColor: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
Theme.surfaceVariant.b, 0.5)
hoverColor: Qt.rgba(Theme.primary.r,
Theme.primary.g,
Theme.primary.b, 0.12)
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
onClicked: { onClicked: {
WallpaperCyclingService.cyclePrevManually() WallpaperCyclingService.cyclePrevManually();
} }
} }
@@ -317,19 +298,18 @@ Item {
iconSize: Theme.iconSizeSmall iconSize: Theme.iconSizeSmall
enabled: SessionData.wallpaperPath enabled: SessionData.wallpaperPath
opacity: SessionData.wallpaperPath ? 1 : 0.5 opacity: SessionData.wallpaperPath ? 1 : 0.5
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
Theme.surfaceVariant.g, hoverColor: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
Theme.surfaceVariant.b, 0.5)
hoverColor: Qt.rgba(Theme.primary.r,
Theme.primary.g,
Theme.primary.b, 0.12)
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
onClicked: { onClicked: {
WallpaperCyclingService.cycleNextManually() WallpaperCyclingService.cycleNextManually();
} }
} }
} }
} }
} }
// Wallpaper Cycling Section - Full Width // Wallpaper Cycling Section - Full Width
@@ -375,6 +355,7 @@ Item {
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
width: parent.width width: parent.width
} }
} }
DankToggle { DankToggle {
@@ -382,11 +363,11 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
checked: SessionData.wallpaperCyclingEnabled checked: SessionData.wallpaperCyclingEnabled
onToggled: toggled => { onToggled: (toggled) => {
return SessionData.setWallpaperCyclingEnabled( return SessionData.setWallpaperCyclingEnabled(toggled);
toggled)
} }
} }
} }
// Cycling mode and settings // Cycling mode and settings
@@ -417,13 +398,12 @@ Item {
}, { }, {
"text": "Time" "text": "Time"
}] }]
currentIndex: SessionData.wallpaperCyclingMode currentIndex: SessionData.wallpaperCyclingMode === "time" ? 1 : 0
=== "time" ? 1 : 0 onTabClicked: (index) => {
onTabClicked: index => { SessionData.setWallpaperCyclingMode(index === 1 ? "time" : "interval");
SessionData.setWallpaperCyclingMode(
index === 1 ? "time" : "interval")
} }
} }
} }
// Interval settings // Interval settings
@@ -437,17 +417,15 @@ Item {
description: "How often to change wallpaper" description: "How often to change wallpaper"
options: intervalOptions options: intervalOptions
currentValue: { currentValue: {
const currentSeconds = SessionData.wallpaperCyclingInterval const currentSeconds = SessionData.wallpaperCyclingInterval;
const index = intervalValues.indexOf( const index = intervalValues.indexOf(currentSeconds);
currentSeconds) return index >= 0 ? intervalOptions[index] : "5 minutes";
return index >= 0 ? intervalOptions[index] : "5 minutes"
} }
onValueChanged: value => { onValueChanged: (value) => {
const index = intervalOptions.indexOf( const index = intervalOptions.indexOf(value);
value)
if (index >= 0) if (index >= 0)
SessionData.setWallpaperCyclingInterval( SessionData.setWallpaperCyclingInterval(intervalValues[index]);
intervalValues[index])
} }
} }
@@ -473,28 +451,25 @@ Item {
topPadding: Theme.spacingS topPadding: Theme.spacingS
bottomPadding: Theme.spacingS bottomPadding: Theme.spacingS
onAccepted: { onAccepted: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test( var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
text)
if (isValid) if (isValid)
SessionData.setWallpaperCyclingTime( SessionData.setWallpaperCyclingTime(text);
text)
else else
text = SessionData.wallpaperCyclingTime text = SessionData.wallpaperCyclingTime;
} }
onEditingFinished: { onEditingFinished: {
var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test( var isValid = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/.test(text);
text)
if (isValid) if (isValid)
SessionData.setWallpaperCyclingTime( SessionData.setWallpaperCyclingTime(text);
text)
else else
text = SessionData.wallpaperCyclingTime text = SessionData.wallpaperCyclingTime;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
validator: RegularExpressionValidator { validator: RegularExpressionValidator {
regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/ regularExpression: /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/
} }
} }
StyledText { StyledText {
@@ -503,10 +478,15 @@ Item {
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
} }
} }
} }
} }
// Dynamic Theme Section // Dynamic Theme Section
@@ -514,10 +494,8 @@ Item {
width: parent.width width: parent.width
height: dynamicThemeSection.implicitHeight + Theme.spacingL * 2 height: dynamicThemeSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Column { Column {
@@ -539,8 +517,7 @@ Item {
} }
Column { Column {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM - toggle.width - Theme.spacingM
- toggle.width - Theme.spacingM
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -558,6 +535,7 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
DankToggle { DankToggle {
@@ -566,13 +544,14 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
checked: Theme.wallpaperPath !== "" && Theme.currentTheme === Theme.dynamic checked: Theme.wallpaperPath !== "" && Theme.currentTheme === Theme.dynamic
enabled: ToastService.wallpaperErrorStatus !== "matugen_missing" && Theme.wallpaperPath !== "" enabled: ToastService.wallpaperErrorStatus !== "matugen_missing" && Theme.wallpaperPath !== ""
onToggled: toggled => { onToggled: (toggled) => {
if (toggled) if (toggled)
Theme.switchTheme(Theme.dynamic) Theme.switchTheme(Theme.dynamic);
else else
Theme.switchTheme("blue") Theme.switchTheme("blue");
} }
} }
} }
StyledText { StyledText {
@@ -583,7 +562,9 @@ Item {
width: parent.width width: parent.width
leftPadding: Theme.iconSize + Theme.spacingM leftPadding: Theme.iconSize + Theme.spacingM
} }
} }
} }
// Display Settings // Display Settings
@@ -591,10 +572,8 @@ Item {
width: parent.width width: parent.width
height: displaySection.implicitHeight + Theme.spacingL * 2 height: displaySection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Column { Column {
@@ -622,65 +601,342 @@ Item {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
DankToggle { DankToggle {
id: nightModeToggle id: nightModeToggle
width: parent.width width: parent.width
text: "Night Mode" text: "Night Mode (Manual)"
description: "Apply warm color temperature to reduce eye strain" description: SessionData.nightModeAutoEnabled ? "Manual control - automation will override when scheduled" : "Apply warm color temperature to reduce eye strain"
checked: BrightnessService.nightModeActive checked: DisplayService.nightModeActive
onToggled: checked => { opacity: SessionData.nightModeAutoEnabled ? 0.7 : 1
if (checked !== BrightnessService.nightModeActive) { onToggled: (checked) => {
if (checked !== DisplayService.nightModeActive) {
if (checked) if (checked)
BrightnessService.enableNightMode() DisplayService.enableNightMode();
else else
BrightnessService.disableNightMode() DisplayService.disableNightMode();
} }
} }
Connections { Connections {
function onNightModeActiveChanged() { function onNightModeActiveChanged() {
nightModeToggle.checked = BrightnessService.nightModeActive nightModeToggle.checked = DisplayService.nightModeActive;
} }
target: BrightnessService target: DisplayService
} }
} }
DankDropdown { DankDropdown {
width: parent.width width: parent.width
text: "Night Mode Temperature" text: "Night Mode Temperature"
description: BrightnessService.nightModeActive ? "Disable night mode to adjust" : "Set temperature for night mode" description: DisplayService.nightModeActive ? "Disable night mode to adjust" : "Set temperature for night mode"
enabled: !BrightnessService.nightModeActive enabled: !DisplayService.nightModeActive
opacity: !BrightnessService.nightModeActive ? 1 : 0.6 opacity: !DisplayService.nightModeActive ? 1 : 0.6
currentValue: SessionData.nightModeTemperature + "K" currentValue: SessionData.nightModeTemperature + "K"
options: { options: {
var temps = [] var temps = [];
for (var i = 2500; i <= 6000; i += 500) { for (var i = 2500; i <= 6000; i += 500) {
temps.push(i + "K") temps.push(i + "K");
} }
return temps return temps;
} }
onValueChanged: value => { onValueChanged: (value) => {
var temp = parseInt( var temp = parseInt(value.replace("K", ""));
value.replace("K", "")) SessionData.setNightModeTemperature(temp);
SessionData.setNightModeTemperature(
temp)
} }
} }
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.2
}
DankToggle {
width: parent.width
text: "Night Mode Automation"
description: "Automatically enable/disable night mode based on time schedule or location. Works independently of manual toggle above."
checked: SessionData.nightModeAutoEnabled
onToggled: (checked) => {
SessionData.setNightModeAutoEnabled(checked);
}
}
Column {
width: parent.width
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
}
DankTabBar {
width: 200
height: 32
model: [{
"text": "Time"
}, {
"text": "Location"
}]
currentIndex: SessionData.nightModeAutoMode === "location" ? 1 : 0
onTabClicked: (index) => {
SessionData.setNightModeAutoMode(index === 1 ? "location" : "time");
}
}
}
StyledText {
text: "Press Enter or click away to save time changes. Border turns green when valid (HH:MM format)."
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
visible: SessionData.nightModeAutoMode === "time"
width: parent.width - parent.leftPadding
wrapMode: Text.WordWrap
}
Row {
spacing: Theme.spacingM
visible: SessionData.nightModeAutoMode === "time"
width: parent.width - parent.leftPadding
StyledText {
text: "Start:"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
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]$/
}
}
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"
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)."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
}
StyledText {
text: "Press Enter or click away to save changes. Border turns green when valid."
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
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
}
}
}
DankToggle { DankToggle {
width: parent.width width: parent.width
text: "Light Mode" text: "Light Mode"
description: "Use light theme instead of dark theme" description: "Use light theme instead of dark theme"
checked: SessionData.isLightMode checked: SessionData.isLightMode
onToggled: checked => { onToggled: (checked) => {
Theme.setLightMode(checked) Theme.setLightMode(checked);
} }
} }
} }
} }
// Font Settings // Font Settings
@@ -688,10 +944,8 @@ Item {
width: parent.width width: parent.width
height: fontSection.implicitHeight + Theme.spacingL * 2 height: fontSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Column { Column {
@@ -719,6 +973,7 @@ Item {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
DankDropdown { DankDropdown {
@@ -727,20 +982,19 @@ Item {
description: "Select system font family" description: "Select system font family"
currentValue: { currentValue: {
if (SettingsData.fontFamily === SettingsData.defaultFontFamily) if (SettingsData.fontFamily === SettingsData.defaultFontFamily)
return "Default" return "Default";
else else
return SettingsData.fontFamily || "Default" return SettingsData.fontFamily || "Default";
} }
enableFuzzySearch: true enableFuzzySearch: true
popupWidthOffset: 100 popupWidthOffset: 100
maxPopupHeight: 400 maxPopupHeight: 400
options: cachedFontFamilies options: cachedFontFamilies
onValueChanged: value => { onValueChanged: (value) => {
if (value.startsWith("Default")) if (value.startsWith("Default"))
SettingsData.setFontFamily( SettingsData.setFontFamily(SettingsData.defaultFontFamily);
SettingsData.defaultFontFamily)
else else
SettingsData.setFontFamily(value) SettingsData.setFontFamily(value);
} }
} }
@@ -751,63 +1005,63 @@ Item {
currentValue: { currentValue: {
switch (SettingsData.fontWeight) { switch (SettingsData.fontWeight) {
case Font.Thin: case Font.Thin:
return "Thin" return "Thin";
case Font.ExtraLight: case Font.ExtraLight:
return "Extra Light" return "Extra Light";
case Font.Light: case Font.Light:
return "Light" return "Light";
case Font.Normal: case Font.Normal:
return "Regular" return "Regular";
case Font.Medium: case Font.Medium:
return "Medium" return "Medium";
case Font.DemiBold: case Font.DemiBold:
return "Demi Bold" return "Demi Bold";
case Font.Bold: case Font.Bold:
return "Bold" return "Bold";
case Font.ExtraBold: case Font.ExtraBold:
return "Extra Bold" return "Extra Bold";
case Font.Black: case Font.Black:
return "Black" return "Black";
default: default:
return "Regular" return "Regular";
} }
} }
options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"] options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"]
onValueChanged: value => { onValueChanged: (value) => {
var weight var weight;
switch (value) { switch (value) {
case "Thin": case "Thin":
weight = Font.Thin weight = Font.Thin;
break break;
case "Extra Light": case "Extra Light":
weight = Font.ExtraLight weight = Font.ExtraLight;
break break;
case "Light": case "Light":
weight = Font.Light weight = Font.Light;
break break;
case "Regular": case "Regular":
weight = Font.Normal weight = Font.Normal;
break break;
case "Medium": case "Medium":
weight = Font.Medium weight = Font.Medium;
break break;
case "Demi Bold": case "Demi Bold":
weight = Font.DemiBold weight = Font.DemiBold;
break break;
case "Bold": case "Bold":
weight = Font.Bold weight = Font.Bold;
break break;
case "Extra Bold": case "Extra Bold":
weight = Font.ExtraBold weight = Font.ExtraBold;
break break;
case "Black": case "Black":
weight = Font.Black weight = Font.Black;
break break;
default: default:
weight = Font.Normal weight = Font.Normal;
break break;
} }
SettingsData.setFontWeight(weight) SettingsData.setFontWeight(weight);
} }
} }
@@ -817,27 +1071,28 @@ Item {
description: "Select monospace font for process list and technical displays" description: "Select monospace font for process list and technical displays"
currentValue: { currentValue: {
if (SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily) if (SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily)
return "Default" return "Default";
return SettingsData.monoFontFamily || "Default" return SettingsData.monoFontFamily || "Default";
} }
enableFuzzySearch: true enableFuzzySearch: true
popupWidthOffset: 100 popupWidthOffset: 100
maxPopupHeight: 400 maxPopupHeight: 400
options: cachedMonoFamilies options: cachedMonoFamilies
onValueChanged: value => { onValueChanged: (value) => {
if (value === "Default") if (value === "Default")
SettingsData.setMonoFontFamily( SettingsData.setMonoFontFamily(SettingsData.defaultMonoFontFamily);
SettingsData.defaultMonoFontFamily)
else else
SettingsData.setMonoFontFamily( SettingsData.setMonoFontFamily(value);
value)
}
}
} }
} }
} }
}
}
} }
FileBrowserModal { FileBrowserModal {
@@ -847,17 +1102,18 @@ Item {
browserIcon: "wallpaper" browserIcon: "wallpaper"
browserType: "wallpaper" browserType: "wallpaper"
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => { onFileSelected: (path) => {
SessionData.setWallpaper(path) SessionData.setWallpaper(path);
close() close();
} }
onDialogClosed: { onDialogClosed: {
if (parentModal) { if (parentModal) {
parentModal.allowFocusOverride = false parentModal.allowFocusOverride = false;
parentModal.shouldHaveFocus = Qt.binding(() => { parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible return parentModal.shouldBeVisible;
}) });
} }
} }
} }
} }

View File

@@ -5,6 +5,7 @@ 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
@@ -245,6 +246,12 @@ Singleton {
} }
function toggleNightMode() { 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 (nightModeActive) {
disableNightMode() disableNightMode()
} else { } else {
@@ -271,10 +278,10 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
ddcAvailable = (exitCode === 0) ddcAvailable = (exitCode === 0)
if (ddcAvailable) { if (ddcAvailable) {
console.log("BrightnessService: ddcutil detected") console.log("DisplayService: ddcutil detected")
ddcDisplayDetectionProcess.running = true ddcDisplayDetectionProcess.running = true
} else { } else {
console.log("BrightnessService: ddcutil not available") console.log("DisplayService: ddcutil not available")
} }
} }
} }
@@ -288,7 +295,7 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (!text.trim()) { if (!text.trim()) {
console.log("BrightnessService: No DDC displays found") console.log("DisplayService: No DDC displays found")
ddcDevices = [] ddcDevices = []
return return
} }
@@ -311,7 +318,7 @@ Singleton {
} }
ddcDevices = newDdcDevices ddcDevices = newDdcDevices
console.log("BrightnessService: Found", ddcDevices.length, console.log("DisplayService: Found", ddcDevices.length,
"DDC displays") "DDC displays")
// Queue initial brightness readings for DDC devices // Queue initial brightness readings for DDC devices
@@ -339,7 +346,7 @@ Singleton {
} }
} }
} catch (error) { } catch (error) {
console.warn("BrightnessService: Failed to parse DDC devices:", console.warn("DisplayService: Failed to parse DDC devices:",
error) error)
ddcDevices = [] ddcDevices = []
} }
@@ -348,7 +355,7 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("BrightnessService: Failed to detect DDC displays:", console.warn("DisplayService: Failed to detect DDC displays:",
exitCode) exitCode)
ddcDevices = [] ddcDevices = []
} }
@@ -361,7 +368,7 @@ Singleton {
command: ["brightnessctl", "-m", "-l"] command: ["brightnessctl", "-m", "-l"]
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("BrightnessService: Failed to list devices:", console.warn("DisplayService: Failed to list devices:",
exitCode) exitCode)
brightnessAvailable = false brightnessAvailable = false
} }
@@ -370,7 +377,7 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (!text.trim()) { if (!text.trim()) {
console.warn("BrightnessService: No devices found") console.warn("DisplayService: No devices found")
return return
} }
const lines = text.trim().split("\n") const lines = text.trim().split("\n")
@@ -417,7 +424,7 @@ Singleton {
running: false running: false
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) if (exitCode !== 0)
console.warn("BrightnessService: Failed to set brightness:", console.warn("DisplayService: Failed to set brightness:",
exitCode) exitCode)
} }
} }
@@ -429,7 +436,7 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) if (exitCode !== 0)
console.warn( console.warn(
"BrightnessService: Failed to set DDC brightness:", "DisplayService: Failed to set DDC brightness:",
exitCode) exitCode)
} }
} }
@@ -440,7 +447,7 @@ Singleton {
running: false running: false
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) if (exitCode !== 0)
console.warn("BrightnessService: Failed to get initial DDC brightness:", console.warn("DisplayService: Failed to get initial DDC brightness:",
exitCode) exitCode)
processNextDdcInit() processNextDdcInit()
@@ -470,7 +477,7 @@ Singleton {
delete newPending[deviceName] delete newPending[deviceName]
ddcPendingInit = newPending ddcPendingInit = newPending
console.log("BrightnessService: Initial DDC Device", console.log("DisplayService: Initial DDC Device",
deviceName, "brightness:", brightness + "%") deviceName, "brightness:", brightness + "%")
} }
} }
@@ -484,7 +491,7 @@ Singleton {
running: false running: false
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) if (exitCode !== 0)
console.warn("BrightnessService: Failed to get brightness:", console.warn("DisplayService: Failed to get brightness:",
exitCode) exitCode)
} }
@@ -508,7 +515,7 @@ Singleton {
} }
brightnessInitialized = true brightnessInitialized = true
console.log("BrightnessService: Device", currentDevice, console.log("DisplayService: Device", currentDevice,
"brightness:", brightness + "%") "brightness:", brightness + "%")
brightnessChanged() brightnessChanged()
} }
@@ -523,7 +530,7 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0) if (exitCode !== 0)
console.warn( console.warn(
"BrightnessService: Failed to get DDC brightness:", "DisplayService: Failed to get DDC brightness:",
exitCode) exitCode)
} }
@@ -548,7 +555,7 @@ Singleton {
} }
brightnessInitialized = true brightnessInitialized = true
console.log("BrightnessService: DDC Device", currentDevice, console.log("DisplayService: DDC Device", currentDevice,
"brightness:", brightness + "%") "brightness:", brightness + "%")
brightnessChanged() brightnessChanged()
} }
@@ -569,7 +576,7 @@ Singleton {
SessionData.setNightModeEnabled(true) SessionData.setNightModeEnabled(true)
} else { } else {
// gammastep not found // gammastep not found
console.warn("BrightnessService: 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")
} }
@@ -588,7 +595,7 @@ Singleton {
onExited: function (exitCode) { onExited: function (exitCode) {
// If process exits with non-zero code while we think it should be running // If process exits with non-zero code while we think it should be running
if (nightModeActive && exitCode !== 0) { if (nightModeActive && exitCode !== 0) {
console.warn("BrightnessService: Night mode process crashed with exit code:", console.warn("DisplayService: Night mode process crashed with exit code:",
exitCode) exitCode)
nightModeActive = false nightModeActive = false
SessionData.setNightModeEnabled(false) SessionData.setNightModeEnabled(false)

View File

@@ -0,0 +1,363 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
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
Component.onCompleted: {
console.log("NightModeAutomationService: Component completed")
checkAvailability()
updateFromSessionData()
if (SessionData.nightModeAutoEnabled) {
console.log("NightModeAutomationService: Auto-starting automation on init")
startAutomation()
}
}
function checkAvailability() {
gammaStepTestProcess.running = true
}
function startAutomation() {
if (!automationAvailable) {
console.warn("NightModeAutomationService: Gammastep not available")
return
}
const mode = SessionData.nightModeAutoMode || "manual"
switch (mode) {
case "time":
startTimeBasedMode()
break
case "location":
startLocationBasedMode()
break
case "manual":
default:
stopAutomation()
break
}
}
function stopAutomation() {
automationTimer.stop()
locationTimer.stop()
if (gammaStepAutomationProcess.running) {
gammaStepAutomationProcess.kill()
}
isAutomaticNightTime = false
}
function startTimeBasedMode() {
console.log("NightModeAutomationService: Starting time-based automation")
automationTimer.start()
checkTimeBasedMode()
}
function startLocationBasedMode() {
if (!locationProviderAvailable) {
console.warn("NightModeAutomationService: No location provider available, falling back to time-based mode")
startTimeBasedMode()
return
}
console.log("NightModeAutomationService: Starting location-based automation")
const temperature = SessionData.nightModeTemperature || 4500
const dayTemp = 6500
if (latitude !== 0.0 && longitude !== 0.0) {
gammaStepAutomationProcess.command = [
"gammastep",
"-m", "wayland",
"-l", `${latitude.toFixed(6)}:${longitude.toFixed(6)}`,
"-t", `${dayTemp}:${temperature}`,
"-v"
]
} else {
gammaStepAutomationProcess.command = [
"gammastep",
"-m", "wayland",
"-l", currentProvider || "manual",
"-t", `${dayTemp}:${temperature}`,
"-v"
]
}
gammaStepAutomationProcess.running = true
locationTimer.start()
}
function checkTimeBasedMode() {
if (!SessionData.nightModeAutoEnabled || SessionData.nightModeAutoMode !== "time") {
console.log("NightModeAutomationService: checkTimeBasedMode - not enabled or wrong mode")
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)
}
console.log(`NightModeAutomationService: Time check - Current: ${currentHour}:${currentMinute.toString().padStart(2, '0')} (${currentTime}), Range: ${startTime}-${endTime} (${startMinutes}-${endMinutes}), Should be night: ${shouldBeNight}`)
if (shouldBeNight !== isAutomaticNightTime) {
isAutomaticNightTime = shouldBeNight
console.log("NightModeAutomationService: Automatic night time status changed to:", shouldBeNight)
if (shouldBeNight) {
requestNightModeActivation()
} else {
requestNightModeDeactivation()
}
} else {
console.log("NightModeAutomationService: No change needed, isAutomaticNightTime already:", isAutomaticNightTime)
}
}
function requestNightModeActivation() {
console.log("NightModeAutomationService: Requesting night mode activation")
const temperature = SessionData.nightModeTemperature || 4500
console.log("NightModeAutomationService: Using temperature:", temperature + "K")
gammaStepOneTimeProcess.command = [
"gammastep",
"-m", "wayland",
"-O", String(temperature),
"-P"
]
console.log("NightModeAutomationService: Running gamma command:", gammaStepOneTimeProcess.command.join(" "))
gammaStepOneTimeProcess.running = true
SessionData.setNightModeEnabled(true)
}
function requestNightModeDeactivation() {
console.log("NightModeAutomationService: Requesting night mode deactivation")
gammaStepResetProcess.command = [
"gammastep",
"-m", "wayland",
"-O", "6500",
"-P"
]
gammaStepResetProcess.running = true
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 updateFromSessionData() {
console.log("NightModeAutomationService: Updating from SessionData - lat:", SessionData.latitude, "lng:", SessionData.longitude)
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
setLocation(SessionData.latitude, SessionData.longitude)
}
}
function detectLocationProviders() {
locationProviderDetectionProcess.running = true
}
function testAutomationNow() {
console.log("NightModeAutomationService: Manual test triggered")
console.log("NightModeAutomationService: Current settings - autoEnabled:", SessionData.nightModeAutoEnabled, "mode:", SessionData.nightModeAutoMode)
if (SessionData.nightModeAutoMode === "time") {
checkTimeBasedMode()
} else if (SessionData.nightModeAutoMode === "location") {
console.log("NightModeAutomationService: Location mode - coordinates:", latitude, longitude)
}
}
Timer {
id: automationTimer
interval: 60000
running: false
repeat: true
onTriggered: {
checkTimeBasedMode()
}
}
Timer {
id: locationTimer
interval: 300000
running: false
repeat: true
onTriggered: {
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
detectLocationProviders()
}
}
}
Process {
id: gammaStepTestProcess
command: ["which", "gammastep"]
running: false
onExited: function(exitCode) {
automationAvailable = (exitCode === 0)
if (automationAvailable) {
console.log("NightModeAutomationService: Gammastep available")
detectLocationProviders()
} else {
console.warn("NightModeAutomationService: Gammastep not available")
}
}
}
Process {
id: locationProviderDetectionProcess
command: ["gammastep", "-l", "list"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
availableProviders = text.trim().split('\n').filter(line => line.trim().length > 0)
locationProviderAvailable = availableProviders.length > 0
if (locationProviderAvailable && !currentProvider) {
currentProvider = availableProviders[0]
}
console.log("NightModeAutomationService: Available providers:", availableProviders)
}
}
}
onExited: function(exitCode) {
if (exitCode !== 0) {
console.warn("NightModeAutomationService: Failed to detect location providers")
locationProviderAvailable = false
}
}
}
Process {
id: gammaStepAutomationProcess
running: false
onExited: function(exitCode) {
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location" && exitCode !== 0) {
console.warn("NightModeAutomationService: Location-based automation failed, exit code:", exitCode)
restartTimer.start()
}
}
}
Process {
id: gammaStepOneTimeProcess
running: false
onExited: function(exitCode) {
if (exitCode !== 0) {
console.warn("NightModeAutomationService: Failed to enable night mode, exit code:", exitCode)
}
}
}
Process {
id: gammaStepResetProcess
running: false
onExited: function(exitCode) {
if (exitCode !== 0) {
console.warn("NightModeAutomationService: Failed to reset gamma, exit code:", exitCode)
}
}
}
Timer {
id: restartTimer
interval: 10000
running: false
repeat: false
onTriggered: {
if (SessionData.nightModeAutoEnabled && SessionData.nightModeAutoMode === "location") {
startLocationBasedMode()
}
}
}
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()
}
}
}