From 21f18ad4e9a37df40a8dd7da83619ad8889130f7 Mon Sep 17 00:00:00 2001 From: Ecys <73040912+Eccys@users.noreply.github.com> Date: Sat, 23 May 2026 19:04:38 +0000 Subject: [PATCH] feat(quickshell): add weather auto update toggle and interval setting --- .config/hypr/scripts/quickshell/Config.qml | 12 +- .../scripts/quickshell/calendar/weather.sh | 47 +++--- .../quickshell/settings/SettingsPopup.qml | 148 +++++++++++++++++- 3 files changed, 177 insertions(+), 30 deletions(-) diff --git a/.config/hypr/scripts/quickshell/Config.qml b/.config/hypr/scripts/quickshell/Config.qml index f3a720a..d476f88 100644 --- a/.config/hypr/scripts/quickshell/Config.qml +++ b/.config/hypr/scripts/quickshell/Config.qml @@ -93,6 +93,8 @@ Item { property string weatherUnit: "metric" property string weatherApiKey: "" property string weatherCityId: "" + property bool weatherAutoUpdate: true + property int weatherUpdateInterval: 15 property var keybindsData: [] signal keybindsLoaded() @@ -127,7 +129,9 @@ Item { let envs = { "OPENWEATHER_KEY": config.weatherApiKey, "OPENWEATHER_CITY_ID": config.weatherCityId, - "OPENWEATHER_UNIT": config.weatherUnit + "OPENWEATHER_UNIT": config.weatherUnit, + "OPENWEATHER_AUTO_UPDATE": config.weatherAutoUpdate.toString(), + "OPENWEATHER_UPDATE_INTERVAL": config.weatherUpdateInterval.toString() }; config.updateEnvBulk(config.weatherEnvPath, envs); @@ -227,7 +231,7 @@ Item { if (m.transform !== 0) monitorStr += ",transform," + m.transform; let jsonArr = [{ name: m.name, resW: m.resW, resH: m.resH, rate: parseInt(m.rate), x: 0, y: 0, scale: m.sysScale, transform: m.transform }]; config.setSetting("monitors", jsonArr); - config.sh("hyprctl keyword monitor " + monitorStr + " ; swww kill ; sleep 0.2 ; swww-daemon &"); + config.sh("hyprctl keyword monitor " + monitorStr + " ; awww kill ; sleep 0.2 ; awww-daemon &"); Quickshell.execDetached(["notify-send", "Display Update", "Applied: " + m.resW + "x" + m.resH + " @ " + m.rate + "Hz"]); } else { let rects = []; @@ -277,7 +281,7 @@ Item { jsonArr.push({ name: r.name, resW: r.resW, resH: r.resH, rate: parseInt(r.rate), x: r.x, y: r.y, scale: r.sysScale, transform: r.transform }); } config.setSetting("monitors", jsonArr); - config.sh("hyprctl --batch '" + batchCmds.join(" ; ") + "' ; swww kill ; sleep 0.2 ; swww-daemon &"); + config.sh("hyprctl --batch '" + batchCmds.join(" ; ") + "' ; awww kill ; sleep 0.2 ; awww-daemon &"); Quickshell.execDetached(["notify-send", "Display Update", "Applied layout for: " + summaryString.trim()]); } } @@ -351,6 +355,8 @@ Item { if (key === "OPENWEATHER_KEY") config.weatherApiKey = val; else if (key === "OPENWEATHER_CITY_ID") config.weatherCityId = val; else if (key === "OPENWEATHER_UNIT") config.weatherUnit = val; + else if (key === "OPENWEATHER_AUTO_UPDATE") config.weatherAutoUpdate = (val === "true"); + else if (key === "OPENWEATHER_UPDATE_INTERVAL") config.weatherUpdateInterval = parseInt(val) || 15; } } } diff --git a/.config/hypr/scripts/quickshell/calendar/weather.sh b/.config/hypr/scripts/quickshell/calendar/weather.sh index 56582cf..a4688ae 100755 --- a/.config/hypr/scripts/quickshell/calendar/weather.sh +++ b/.config/hypr/scripts/quickshell/calendar/weather.sh @@ -1,16 +1,10 @@ #!/usr/bin/env bash -# ----------------------------------------------------------------------------- -# CACHING & MIGRATION -# ----------------------------------------------------------------------------- -source "$(dirname "${BASH_SOURCE[0]}")/../../caching.sh" -qs_ensure_cache "weather" - -# Force standard C locale for number formatting and date parsing (fixes printf and date command issues on varying OS locales) -export LC_ALL=C +# Force standard C locale for number formatting (fixes printf decimal/comma issues on varying OS locales) +export LC_NUMERIC=C # Paths -cache_dir="$QS_CACHE_WEATHER" +cache_dir="$HOME/.cache/quickshell/weather" json_file="${cache_dir}/weather.json" view_file="${cache_dir}/view_id" daily_cache_file="${cache_dir}/daily_weather_cache.json" @@ -27,6 +21,8 @@ fi KEY="$OPENWEATHER_KEY" ID="$OPENWEATHER_CITY_ID" UNIT="${OPENWEATHER_UNIT:-metric}" # Default to metric if not set +AUTO_UPDATE="${OPENWEATHER_AUTO_UPDATE:-true}" +INTERVAL_MINS="${OPENWEATHER_UPDATE_INTERVAL:-15}" # Determine temperature symbol based on unit case "$UNIT" in @@ -39,11 +35,11 @@ mkdir -p "${cache_dir}" get_icon() { case $1 in - "50d"|"50n") icon="󰖑"; quote="Mist" ;; + "50d"|"50n") icon=""; quote="Mist" ;; "01d") icon=""; quote="Sunny" ;; "01n") icon=""; quote="Clear" ;; "02d"|"02n"|"03d"|"03n"|"04d"|"04n") icon=""; quote="Cloudy" ;; - "09d"|"09n"|"10d"|"10n") icon="󰖗"; quote="Rainy" ;; + "09d"|"09n"|"10d"|"10n") icon=""; quote="Rainy" ;; "11d"|"11n") icon=""; quote="Storm" ;; "13d"|"13n") icon=""; quote="Snow" ;; *) icon=""; quote="Unknown" ;; @@ -115,12 +111,7 @@ get_data() { api_cod=$(echo "$raw_api" | jq -r '.cod' 2>/dev/null) if [ -z "$raw_api" ] || [ -z "$raw_weather" ] || [[ "$api_cod" != "200" ]]; then - # If curl failed (network glitch, rate limit, API downtime), don't destroy - # the existing working cache. Just abort the update. - # If there is NO cache at all, then fall back to dummy data. - if [ ! -f "$json_file" ]; then - write_dummy_data - fi + write_dummy_data return fi @@ -249,7 +240,7 @@ if [[ "$1" == "--getdata" ]]; then get_data elif [[ "$1" == "--json" ]]; then - CACHE_LIMIT=900 # 15 minutes for valid working data + CACHE_LIMIT=$((INTERVAL_MINS * 60)) PENDING_RETRY_LIMIT=3600 # 1 hour for invalid/activating keys if [ -f "$json_file" ]; then @@ -258,16 +249,20 @@ elif [[ "$1" == "--json" ]]; then diff=$((current_time - file_time)) if grep -q '"desc": "No API Key"' "$json_file"; then - # Key is pending/invalid. Check once an hour. - if [ $diff -gt $PENDING_RETRY_LIMIT ]; then - touch "$json_file" # Bump file timestamp slightly to avoid spamming processes - get_data & + # Key is pending/invalid. Check once an hour if auto update is enabled. + if [[ "$AUTO_UPDATE" == "true" ]]; then + if [ $diff -gt $PENDING_RETRY_LIMIT ]; then + touch "$json_file" # Bump file timestamp slightly to avoid spamming processes + get_data & + fi fi else - # Normal working API key. Check every 15 mins. - if [ $diff -gt $CACHE_LIMIT ]; then - touch "$json_file" - get_data & + # Normal working API key. Check every CACHE_LIMIT seconds if auto update is enabled. + if [[ "$AUTO_UPDATE" == "true" ]]; then + if [ $diff -gt $CACHE_LIMIT ]; then + touch "$json_file" + get_data & + fi fi fi cat "$json_file" diff --git a/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml b/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml index c53a63f..557cb27 100644 --- a/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml +++ b/.config/hypr/scripts/quickshell/settings/SettingsPopup.qml @@ -125,7 +125,7 @@ Item { function maxHighlightForTab(tab) { if (tab === 0) return 6; - if (tab === 1) return 3; + if (tab === 1) return 4; if (tab === 2) return dynamicKeybindsModel.count - 1; if (tab === 4) return dynamicStartupModel.count - 1; return -1; @@ -153,6 +153,8 @@ Item { } else if (root.highlightedBox === 2) { if (weatherLoader.item) weatherLoader.item.focusCityId(); } else if (root.highlightedBox === 3) { + } else if (root.highlightedBox === 4) { + Config.weatherAutoUpdate = !Config.weatherAutoUpdate; } } else if (root.currentTab === 2) { if (root.highlightedBox >= 0 && root.highlightedBox < dynamicKeybindsModel.count) { @@ -189,6 +191,7 @@ Item { else if (box === 1) approxY = root.s(140); else if (box === 2) approxY = root.s(240); else if (box === 3) approxY = root.s(340); + else if (box === 4) approxY = root.s(440); weatherLoader.item.scrollToBox(approxY); } else if (root.currentTab === 2 && keybindLoader.item) { let approxY = box * root.s(56) + root.s(120); @@ -309,6 +312,10 @@ Item { Config.workspaceCount = Math.max(2, Config.workspaceCount - 1); event.accepted = true; return; + } else if (root.currentTab === 1 && root.highlightedBox === 4) { + Config.weatherUpdateInterval = Math.max(5, Config.weatherUpdateInterval - 5); + event.accepted = true; + return; } } if (event.key === Qt.Key_Right) { @@ -320,6 +327,10 @@ Item { Config.workspaceCount = Math.min(10, Config.workspaceCount + 1); event.accepted = true; return; + } else if (root.currentTab === 1 && root.highlightedBox === 4) { + Config.weatherUpdateInterval = Math.min(120, Config.weatherUpdateInterval + 5); + event.accepted = true; + return; } } @@ -626,6 +637,7 @@ Item { if (targetBox === 1) approxY = root.s(140); else if (targetBox === 2) approxY = root.s(240); else if (targetBox === 3) approxY = root.s(340); + else if (targetBox === 4) approxY = root.s(440); weatherLoader.item.scrollTo(approxY); } else if (targetTab === 2 && keybindLoader.item) { approxY = targetBox * (root.s(56)) + root.s(120); @@ -2264,6 +2276,140 @@ Item { } } } + + // ── Box 4: Auto Update & Interval ─────────────────────── + Rectangle { + id: wBox4 + Layout.fillWidth: true + Layout.preferredHeight: autoUpdateRow.implicitHeight + root.s(28) + radius: root.s(12) + + property bool isActive: root.highlightedBox === 4 + color: isActive ? root.blue : root.surface0 + border.color: isActive ? root.blue : root.surface1 + border.width: 1 + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + + MouseArea { anchors.fill: parent; onClicked: root.highlightedBox = 4; z: -1 } + + ColumnLayout { + id: autoUpdateRow + anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; anchors.margins: root.s(16) + spacing: root.s(10) + + RowLayout { + Layout.fillWidth: true; spacing: root.s(14) + Item { + Layout.preferredWidth: root.s(22); Layout.alignment: Qt.AlignVCenter + Text { + anchors.centerIn: parent; text: "󰚰"; font.family: "Iosevka Nerd Font"; font.pixelSize: root.s(18) + color: wBox4.isActive ? root.base : root.blue + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + ColumnLayout { + Layout.fillWidth: true; spacing: root.s(3) + Text { + text: "Auto Update"; font.family: "Inter"; font.weight: Font.Medium; font.pixelSize: root.s(14) + color: wBox4.isActive ? root.base : root.text; Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Text { + text: "Update weather periodically"; font.family: "Inter"; font.pixelSize: root.s(11) + color: wBox4.isActive ? Qt.alpha(root.base, 0.75) : Qt.alpha(root.subtext0, 0.7); Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + Rectangle { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.preferredWidth: root.s(40) + Layout.preferredHeight: root.s(22) + radius: root.s(11) + scale: autoUpdateToggleMa.containsMouse ? 1.05 : 1.0 + Behavior on scale { NumberAnimation { duration: 150; easing.type: Easing.OutBack } } + color: Config.weatherAutoUpdate + ? (wBox4.isActive ? root.base : root.peach) + : Qt.alpha(root.surface2, wBox4.isActive ? 0.4 : 1.0) + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + Rectangle { + width: root.s(16); height: root.s(16); radius: root.s(8) + color: Config.weatherAutoUpdate + ? (wBox4.isActive ? root.peach : root.base) + : (wBox4.isActive ? root.peach : root.surface0) + y: root.s(3); x: Config.weatherAutoUpdate ? root.s(21) : root.s(3) + Behavior on x { NumberAnimation { duration: 250; easing.type: Easing.OutBack } } + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + MouseArea { id: autoUpdateToggleMa; anchors.fill: parent; hoverEnabled: true; onClicked: Config.weatherAutoUpdate = !Config.weatherAutoUpdate; cursorShape: Qt.PointingHandCursor } + } + } + + RowLayout { + Layout.fillWidth: true; spacing: root.s(14) + visible: Config.weatherAutoUpdate + + Item { + Layout.preferredWidth: root.s(22); Layout.alignment: Qt.AlignVCenter + Text { + anchors.centerIn: parent; text: "󰔚"; font.family: "Iosevka Nerd Font"; font.pixelSize: root.s(18) + color: wBox4.isActive ? root.base : root.blue + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + ColumnLayout { + Layout.fillWidth: true; spacing: root.s(3) + Text { + text: "Update Interval"; font.family: "Inter"; font.weight: Font.Medium; font.pixelSize: root.s(14) + color: wBox4.isActive ? root.base : root.text; Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Text { + text: "Interval in minutes"; font.family: "Inter"; font.pixelSize: root.s(11) + color: wBox4.isActive ? Qt.alpha(root.base, 0.75) : Qt.alpha(root.subtext0, 0.7); Layout.fillWidth: true + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + } + RowLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight; spacing: root.s(10) + Rectangle { + width: root.s(28); height: root.s(28); radius: root.s(6) + color: intervalMinusMa.pressed ? Qt.alpha(root.base, 0.3) : (intervalMinusMa.containsMouse ? Qt.alpha(root.base, 0.2) : Qt.alpha(root.base, 0.15)) + scale: intervalMinusMa.pressed ? 0.90 : (intervalMinusMa.containsMouse ? 1.08 : 1.0) + Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.OutQuart } } + Behavior on color { ColorAnimation { duration: 200 } } + Text { + anchors.centerIn: parent; text: "-" + font.family: "JetBrains Mono"; font.weight: Font.Bold; font.pixelSize: root.s(15) + color: wBox4.isActive ? root.base : root.blue + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + MouseArea { id: intervalMinusMa; anchors.fill: parent; hoverEnabled: true; onClicked: Config.weatherUpdateInterval = Math.max(5, Config.weatherUpdateInterval - 5) } + } + Text { + text: Config.weatherUpdateInterval.toString() + font.family: "JetBrains Mono"; font.weight: Font.Black; font.pixelSize: root.s(14) + color: wBox4.isActive ? root.base : root.blue + Layout.minimumWidth: root.s(36); horizontalAlignment: Text.AlignHCenter + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + Rectangle { + width: root.s(28); height: root.s(28); radius: root.s(6) + color: intervalPlusMa.pressed ? Qt.alpha(root.base, 0.3) : (intervalPlusMa.containsMouse ? Qt.alpha(root.base, 0.2) : Qt.alpha(root.base, 0.15)) + scale: intervalPlusMa.pressed ? 0.90 : (intervalPlusMa.containsMouse ? 1.08 : 1.0) + Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.OutQuart } } + Behavior on color { ColorAnimation { duration: 200 } } + Text { + anchors.centerIn: parent; text: "+" + font.family: "JetBrains Mono"; font.weight: Font.Bold; font.pixelSize: root.s(15) + color: wBox4.isActive ? root.base : root.blue + Behavior on color { ColorAnimation { duration: 220; easing.type: Easing.OutExpo } } + } + MouseArea { id: intervalPlusMa; anchors.fill: parent; hoverEnabled: true; onClicked: Config.weatherUpdateInterval = Math.min(120, Config.weatherUpdateInterval + 5) } + } + } + } + } + } } } }