From 205a76b2f30fe054fa68fa7e2154a5423d3646f3 Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 22 Mar 2026 17:08:48 +0100 Subject: [PATCH] hyprsunset schedule and toggle --- Config/BarConfig.qml | 4 + Config/Config.qml | 5 + Config/General.qml | 4 + Helpers/Hyprsunset.qml | 84 ++++++++ Modules/Bar/Bar.qml | 9 + Modules/HyprsunsetWidget.qml | 29 +++ Modules/Settings/Categories/General.qml | 9 + .../Settings/Controls/SettingHyprSpinner.qml | 180 ++++++++++++++++++ 8 files changed, 324 insertions(+) create mode 100644 Helpers/Hyprsunset.qml create mode 100644 Modules/HyprsunsetWidget.qml create mode 100644 Modules/Settings/Controls/SettingHyprSpinner.qml diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index 7b39915..087991c 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -40,6 +40,10 @@ JsonObject { id: "spacer", enabled: true }, + { + id: "hyprsunset", + enabled: true + }, { id: "tray", enabled: true diff --git a/Config/Config.qml b/Config/Config.qml index 6e4206c..069da6a 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -177,6 +177,10 @@ Singleton { mode: general.color.mode, smart: general.color.smart, scheduleDark: general.color.scheduleDark, + scheduleHyprsunset: general.color.scheduleHyprsunset, + scheduleHyprsunsetStart: general.color.scheduleHyprsunsetStart, + hyprsunsetTemp: general.color.hyprsunsetTemp, + scheduleHyprsunsetEnd: general.color.scheduleHyprsunsetEnd, schemeGeneration: general.color.schemeGeneration, scheduleDarkStart: general.color.scheduleDarkStart, scheduleDarkEnd: general.color.scheduleDarkEnd, @@ -374,6 +378,7 @@ Singleton { } onLoaded: { ModeScheduler.checkStartup(); + Hyprsunset.checkStartup(); try { JSON.parse(text()); const elapsed = timer.elapsedMs(); diff --git a/Config/General.qml b/Config/General.qml index ff1e21e..a823f6d 100644 --- a/Config/General.qml +++ b/Config/General.qml @@ -19,11 +19,15 @@ JsonObject { property list terminal: ["kitty"] } component Color: JsonObject { + property int hyprsunsetTemp: 5000 property string mode: "dark" property bool neovimColors: false property bool scheduleDark: false property int scheduleDarkEnd: 0 property int scheduleDarkStart: 0 + property bool scheduleHyprsunset: false + property int scheduleHyprsunsetEnd: 0 + property int scheduleHyprsunsetStart: 0 property bool schemeGeneration: true property bool smart: false } diff --git a/Helpers/Hyprsunset.qml b/Helpers/Hyprsunset.qml new file mode 100644 index 0000000..1812eb8 --- /dev/null +++ b/Helpers/Hyprsunset.qml @@ -0,0 +1,84 @@ +pragma Singleton + +import Quickshell +import QtQuick +import qs.Config + +Singleton { + id: root + + property bool enabled + readonly property int end: Config.general.color.scheduleHyprsunsetEnd + property bool manualToggle: false + readonly property int start: Config.general.color.scheduleHyprsunsetStart + readonly property int temp: Config.general.color.hyprsunsetTemp + + function checkStartup(): void { + if (!Config.general.color.scheduleHyprsunset) + return; + + var now = new Date(); + if (now.getHours() >= root.start || now.getHours() < root.end) { + root.startNightLight(root.temp); + } else { + root.stopNightLight(); + } + } + + function startNightLight(temp: int): void { + Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${temp}`]); + root.enabled = true; + } + + function stopNightLight(): void { + Quickshell.execDetached(["hyprctl", "hyprsunset", "identity"]); + root.enabled = false; + } + + function toggleNightLight(): void { + if (enabled) + stopNightLight(); + else + startNightLight(temp); + } + + onManualToggleChanged: { + if (root.manualToggle) + manualTimer.start(); + } + + Timer { + id: manualTimer + + interval: 60000 * 60 + repeat: false + running: false + + onTriggered: { + root.manualToggle = false; + } + } + + Timer { + interval: 5000 + repeat: true + running: true + triggeredOnStart: true + + onTriggered: { + console.log("start"); + if (!Config.general.color.scheduleHyprsunset) + return; + + if (root.manualToggle) + return; + + var now = new Date(); + if (now.getHours() >= root.start || now.getHours() < root.end) { + root.startNightLight(root.temp); + } else { + root.stopNightLight(); + } + } + } +} diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index df30e99..8115e8e 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -66,6 +66,15 @@ RowLayout { DelegateChooser { role: "id" + DelegateChoice { + roleValue: "hyprsunset" + + delegate: WrappedLoader { + sourceComponent: HyprsunsetWidget { + } + } + } + DelegateChoice { roleValue: "spacer" diff --git a/Modules/HyprsunsetWidget.qml b/Modules/HyprsunsetWidget.qml new file mode 100644 index 0000000..620b73a --- /dev/null +++ b/Modules/HyprsunsetWidget.qml @@ -0,0 +1,29 @@ +import QtQuick +import qs.Components +import qs.Helpers +import qs.Config + +CustomRect { + id: root + + property bool tempEnabled: Hyprsunset.enabled + + color: root.tempEnabled ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer + implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitWidth: implicitHeight + radius: Appearance.rounding.full + + StateLayer { + onClicked: { + Hyprsunset.toggleNightLight(); + Hyprsunset.manualToggle = true; + } + } + + MaterialIcon { + anchors.centerIn: parent + animate: true + color: root.tempEnabled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface + text: root.tempEnabled ? "lightbulb" : "light_off" + } +} diff --git a/Modules/Settings/Categories/General.qml b/Modules/Settings/Categories/General.qml index bf5e962..5ec62dc 100644 --- a/Modules/Settings/Categories/General.qml +++ b/Modules/Settings/Categories/General.qml @@ -163,6 +163,15 @@ SettingsPage { object: Config.general.color settings: ["scheduleDarkStart", "scheduleDarkEnd", "scheduleDark"] } + + Separator { + } + + SettingHyprSpinner { + name: "Schedule Hyprsunset" + object: Config.general.color + settings: ["scheduleHyprsunsetStart", "scheduleHyprsunsetEnd", "scheduleHyprsunset", "hyprsunsetTemp"] + } } SettingsSection { diff --git a/Modules/Settings/Controls/SettingHyprSpinner.qml b/Modules/Settings/Controls/SettingHyprSpinner.qml new file mode 100644 index 0000000..d876e07 --- /dev/null +++ b/Modules/Settings/Controls/SettingHyprSpinner.qml @@ -0,0 +1,180 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +Item { + id: root + + required property string name + required property var object + required property list settings + + function commitChoice(choice: int, setting: string): void { + root.object[setting] = choice; + Config.save(); + } + + function formattedValue(setting: string): string { + const value = root.object[setting]; + + if (value === null || value === undefined) + return ""; + + return String(value); + } + + function hourToAmPm(hour) { + var h = Number(hour) % 24; + var d = new Date(2000, 0, 1, h, 0, 0); + return Qt.formatTime(d, "h AP"); + } + + Layout.fillWidth: true + Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomText { + id: text + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.name + } + + CustomText { + Layout.alignment: Qt.AlignLeft + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.normal + text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]])) + } + } + + ColumnLayout { + id: optionLayout + + Layout.fillHeight: true + Layout.preferredWidth: 100 + + RowLayout { + Layout.preferredWidth: optionLayout.width + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Enabled: ") + } + + CustomSwitch { + id: enabledSwitch + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + checked: root.object[root.settings[2]] + + onToggled: { + root.object[root.settings[2]] = checked; + Config.save(); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: setting2.expanded ? -1 : 1 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Start: ") + } + + SpinnerButton { + id: setting1 + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 + Layout.preferredWidth: height * 2 + currentIndex: root.object[root.settings[0]] + enabled: enabledSwitch.checked + text: root.formattedValue(root.settings[0]) + + menu.onItemSelected: item => { + root.commitChoice(item, root.settings[0]); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: setting1.expanded ? -1 : 1 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("End: ") + } + + SpinnerButton { + id: setting2 + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 + Layout.preferredWidth: height * 2 + currentIndex: root.object[root.settings[1]] + enabled: enabledSwitch.checked + text: root.formattedValue(root.settings[1]) + + menu.onItemSelected: item => { + root.commitChoice(item, root.settings[1]); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: -2 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Temp: ") + } + + CustomRect { + id: rect + + Layout.preferredHeight: 33 + Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 200), 50) + color: DynamicColors.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.full + + CustomTextField { + id: textField + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 200) + text: root.formattedValue(root.settings[3]) + + onEditingFinished: { + root.object[root.settings[3]] = textField.text; + Config.save(); + } + } + } + } + } + } +}