Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .config/hypr/scripts/quickshell/Config.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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()]);
}
}
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
47 changes: 21 additions & 26 deletions .config/hypr/scripts/quickshell/calendar/weather.sh
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand All @@ -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" ;;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
148 changes: 147 additions & 1 deletion .config/hypr/scripts/quickshell/settings/SettingsPopup.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) }
}
}
}
}
}
}
}
}
Expand Down