From 05494377767de56b67e69824d19f142841990ce9 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Mon, 4 May 2026 16:24:48 +0200 Subject: [PATCH] quic --- CMakeLists.txt | 6 +- include/components/weather.hpp | 129 +++++++++++++++ include/widgets/weather.hpp | 48 +++++- resources/bar.css | 27 +++- resources/weatherCodes.json | 282 +++++++++++++++++++++++++++++++++ src/services/hyprland.cpp | 7 +- src/widgets/weather.cpp | 171 ++++++++++++++------ 7 files changed, 610 insertions(+), 60 deletions(-) create mode 100644 include/components/weather.hpp create mode 100644 resources/weatherCodes.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2de0215..2f16f80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,8 +106,8 @@ include(Catch) catch_discover_tests(bar_tests) # Copy all CSS files in resources/ into build and user config directories -file(GLOB RES_CSS_FILES CONFIGURE_DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/resources/*.css" +file(GLOB RES_FILES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/resources/*" ) set(RES_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") set(RES_USER_DIR "$ENV{HOME}/.config/bar") @@ -115,7 +115,7 @@ set(RES_USER_DIR "$ENV{HOME}/.config/bar") set(RES_DST_FILES "") set(USER_DST_FILES "") -foreach(RES_FILE IN LISTS RES_CSS_FILES) +foreach(RES_FILE IN LISTS RES_FILES) get_filename_component(RES_NAME "${RES_FILE}" NAME) set(RES_DST "${RES_BUILD_DIR}/${RES_NAME}") set(USER_DST "${RES_USER_DIR}/${RES_NAME}") diff --git a/include/components/weather.hpp b/include/components/weather.hpp new file mode 100644 index 0000000..cfae408 --- /dev/null +++ b/include/components/weather.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include + +#include "services/textureCache.hpp" +#include "widgets/weather.hpp" + +#include "gtkmm/box.h" +#include "gtkmm/enums.h" +#include "gtkmm/image.h" + +std::string formatTemperature(double temp) { + return std::to_string(static_cast(std::round(temp))) + "°C"; +} + +class WeatherInfo : public Gtk::Box { + private: + Gtk::Image getImageFromWeatherCode(int weather_code, bool is_daytime = true) { + Gtk::Image pic; + + + auto file = std::ifstream("resources/weatherCodes.json"); + if (!file.is_open()) { + auto homeDir = std::getenv("HOME"); + std::cout << "Failed to open local weatherCodes.json, trying " << std::string(homeDir) + "/.config/bar/weatherCodes.json" << std::endl; + file = std::ifstream(std::string(homeDir) + "/.config/bar/weatherCodes.json"); + + if (!file.is_open()) { + std::cerr << "Failed to open weatherCodes.json" << std::endl; + return pic; + } + } + + auto json = nlohmann::json::parse(file); + file.close(); + + std::string image_path = json[std::to_string(weather_code)][is_daytime ? "day" : "night"]["image"].get(); + + auto texture = TextureCacheService::getInstance()->getTexture(image_path); + + if (texture) { + pic.set(texture); + } + + pic.set_pixel_size(64); + + return pic; + } + + public: + WeatherInfo(WeatherWidget::CurrentWeather current_weather) { + set_orientation(Gtk::Orientation::HORIZONTAL); + this->set_valign(Gtk::Align::CENTER); + this->set_halign(Gtk::Align::FILL); + + auto image = getImageFromWeatherCode(current_weather.weather_code); + auto temp = Gtk::Label(); + temp.set_markup("" + formatTemperature(current_weather.temperature) + "" + " (" + formatTemperature(current_weather.apparent_temperature) + ")"); + temp.set_halign(Gtk::Align::START); + + + auto temp_box = Gtk::Box(Gtk::Orientation::VERTICAL); + temp_box.set_valign(Gtk::Align::CENTER); + temp_box.set_halign(Gtk::Align::START); + temp_box.append(temp); + + + append(image); + append(temp_box); + } + + WeatherInfo(WeatherWidget::HourlyWeather hourly_weather) { + set_orientation(Gtk::Orientation::HORIZONTAL); + this->set_valign(Gtk::Align::CENTER); + add_css_class("weather-info"); + + auto image = getImageFromWeatherCode(hourly_weather.weather_code); + + auto temp = Gtk::Label(); + temp.set_markup("" + formatTemperature(hourly_weather.temperature_2m) + "" + " (" + formatTemperature(hourly_weather.apparent_temperature) + ")"); + temp.set_halign(Gtk::Align::START); + + + Gtk::Box temp_box(Gtk::Orientation::VERTICAL); + temp_box.set_halign(Gtk::Align::START); + temp_box.set_valign(Gtk::Align::CENTER); + temp_box.append(temp); + + auto timeLabel = Gtk::Label(); + timeLabel.set_markup("" + hourly_weather.time + ""); + timeLabel.set_halign(Gtk::Align::START); + temp_box.append(timeLabel); + + append(image); + append(temp_box); + } + + WeatherInfo(WeatherWidget::DailyWeather daily_weather) { + set_orientation(Gtk::Orientation::HORIZONTAL); + this->set_valign(Gtk::Align::CENTER); + add_css_class("weather-info"); + + auto image = getImageFromWeatherCode(daily_weather.weather_code); + + auto max_label = Gtk::Label(); + max_label.set_markup("H: " + formatTemperature(daily_weather.temperature_2m_max) + "" + " (" + formatTemperature(daily_weather.apparent_temperature_max) + ")"); + max_label.set_halign(Gtk::Align::START); + + auto min_label = Gtk::Label(); + min_label.set_markup("L: " + formatTemperature(daily_weather.temperature_2m_min) + "" + " (" + formatTemperature(daily_weather.apparent_temperature_min) + ")"); + min_label.set_halign(Gtk::Align::START); + + Gtk::Box temp_box(Gtk::Orientation::VERTICAL); + temp_box.set_halign(Gtk::Align::START); + temp_box.set_valign(Gtk::Align::CENTER); + temp_box.append(max_label); + temp_box.append(min_label); + + auto timeLabel = Gtk::Label(); + timeLabel.set_markup("" + daily_weather.time + ""); + timeLabel.set_halign(Gtk::Align::START); + temp_box.append(timeLabel); + + append(image); + append(temp_box); + } +}; \ No newline at end of file diff --git a/include/widgets/weather.hpp b/include/widgets/weather.hpp index af0f76a..f6655bf 100644 --- a/include/widgets/weather.hpp +++ b/include/widgets/weather.hpp @@ -1,20 +1,58 @@ #pragma once +#include +#include #include #include #include +#include "gtkmm/scrolledwindow.h" + class WeatherWidget : public Gtk::Box { public: + struct CurrentWeather { + double temperature; + double apparent_temperature; + double precipitation; + int weather_code; + }; + + struct DailyWeather { + double temperature_2m_max; + double temperature_2m_min; + double apparent_temperature_min; + double apparent_temperature_max; + double precipitation_sum; + int weather_code; + std::string time; + }; + + struct HourlyWeather { + double temperature_2m; + double apparent_temperature; + double precipitation_probability; + double precipitation; + int weather_code; + std::string time; + }; + WeatherWidget(); private: - Gtk::Label titleLabel; - Gtk::Label currentLabel; - Gtk::Label todayLabel; + std::array daily_weather{}; + std::array hourly_weather{}; + CurrentWeather current_weather{}; + + Gtk::ScrolledWindow currentScroll; + Gtk::ScrolledWindow hourlyScroll; + Gtk::ScrolledWindow dailyScroll; + + Glib::Dispatcher m_dispatcher; bool onRefreshTick(); void fetchWeather(); - void applyWeatherText(const std::string ¤t_text, - const std::string &today_text); + + void refreshCurrentWeather(); + void refreshHourlyWeather(); + void refreshDailyWeather(); }; diff --git a/resources/bar.css b/resources/bar.css index d7a8b7b..6552e14 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -1,4 +1,6 @@ /* Custom Scrollbar Styling */ +/** biome-ignore-all lint/correctness/noUnknownTypeSelector: gtk css has more valid identifiers */ + scrollbar, scrollbar * { min-width: 8px; background: transparent; @@ -22,7 +24,6 @@ scrollbar trough { scrollbar button { background: transparent; } -/** biome-ignore-all lint/correctness/noUnknownTypeSelector: gtk css has more valid identifiers */ * { all: unset; } @@ -295,4 +296,26 @@ tooltip { .available-devices { background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; -} \ No newline at end of file +} + +.current-weather-scroll { + background-color: rgba(255, 255, 255, 0.1); + + border-radius: 8px; +} + +.hourly-weather-scroll { + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; +} + +.daily-weather-scroll { + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; +} + +.weather-info { + padding: 4px 8px; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.1); +} diff --git a/resources/weatherCodes.json b/resources/weatherCodes.json new file mode 100644 index 0000000..8ca1587 --- /dev/null +++ b/resources/weatherCodes.json @@ -0,0 +1,282 @@ +{ + "0":{ + "day":{ + "description":"Sunny", + "image":"http://openweathermap.org/img/wn/01d@2x.png" + }, + "night":{ + "description":"Clear", + "image":"http://openweathermap.org/img/wn/01n@2x.png" + } + }, + "1":{ + "day":{ + "description":"Mainly Sunny", + "image":"http://openweathermap.org/img/wn/01d@2x.png" + }, + "night":{ + "description":"Mainly Clear", + "image":"http://openweathermap.org/img/wn/01n@2x.png" + } + }, + "2":{ + "day":{ + "description":"Partly Cloudy", + "image":"http://openweathermap.org/img/wn/02d@2x.png" + }, + "night":{ + "description":"Partly Cloudy", + "image":"http://openweathermap.org/img/wn/02n@2x.png" + } + }, + "3":{ + "day":{ + "description":"Cloudy", + "image":"http://openweathermap.org/img/wn/03d@2x.png" + }, + "night":{ + "description":"Cloudy", + "image":"http://openweathermap.org/img/wn/03n@2x.png" + } + }, + "45":{ + "day":{ + "description":"Foggy", + "image":"http://openweathermap.org/img/wn/50d@2x.png" + }, + "night":{ + "description":"Foggy", + "image":"http://openweathermap.org/img/wn/50n@2x.png" + } + }, + "48":{ + "day":{ + "description":"Rime Fog", + "image":"http://openweathermap.org/img/wn/50d@2x.png" + }, + "night":{ + "description":"Rime Fog", + "image":"http://openweathermap.org/img/wn/50n@2x.png" + } + }, + "51":{ + "day":{ + "description":"Light Drizzle", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Light Drizzle", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "53":{ + "day":{ + "description":"Drizzle", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Drizzle", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "55":{ + "day":{ + "description":"Heavy Drizzle", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Heavy Drizzle", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "56":{ + "day":{ + "description":"Light Freezing Drizzle", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Light Freezing Drizzle", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "57":{ + "day":{ + "description":"Freezing Drizzle", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Freezing Drizzle", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "61":{ + "day":{ + "description":"Light Rain", + "image":"http://openweathermap.org/img/wn/10d@2x.png" + }, + "night":{ + "description":"Light Rain", + "image":"http://openweathermap.org/img/wn/10n@2x.png" + } + }, + "63":{ + "day":{ + "description":"Rain", + "image":"http://openweathermap.org/img/wn/10d@2x.png" + }, + "night":{ + "description":"Rain", + "image":"http://openweathermap.org/img/wn/10n@2x.png" + } + }, + "65":{ + "day":{ + "description":"Heavy Rain", + "image":"http://openweathermap.org/img/wn/10d@2x.png" + }, + "night":{ + "description":"Heavy Rain", + "image":"http://openweathermap.org/img/wn/10n@2x.png" + } + }, + "66":{ + "day":{ + "description":"Light Freezing Rain", + "image":"http://openweathermap.org/img/wn/10d@2x.png" + }, + "night":{ + "description":"Light Freezing Rain", + "image":"http://openweathermap.org/img/wn/10n@2x.png" + } + }, + "67":{ + "day":{ + "description":"Freezing Rain", + "image":"http://openweathermap.org/img/wn/10d@2x.png" + }, + "night":{ + "description":"Freezing Rain", + "image":"http://openweathermap.org/img/wn/10n@2x.png" + } + }, + "71":{ + "day":{ + "description":"Light Snow", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Light Snow", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "73":{ + "day":{ + "description":"Snow", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Snow", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "75":{ + "day":{ + "description":"Heavy Snow", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Heavy Snow", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "77":{ + "day":{ + "description":"Snow Grains", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Snow Grains", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "80":{ + "day":{ + "description":"Light Showers", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Light Showers", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "81":{ + "day":{ + "description":"Showers", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Showers", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "82":{ + "day":{ + "description":"Heavy Showers", + "image":"http://openweathermap.org/img/wn/09d@2x.png" + }, + "night":{ + "description":"Heavy Showers", + "image":"http://openweathermap.org/img/wn/09n@2x.png" + } + }, + "85":{ + "day":{ + "description":"Light Snow Showers", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Light Snow Showers", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "86":{ + "day":{ + "description":"Snow Showers", + "image":"http://openweathermap.org/img/wn/13d@2x.png" + }, + "night":{ + "description":"Snow Showers", + "image":"http://openweathermap.org/img/wn/13n@2x.png" + } + }, + "95":{ + "day":{ + "description":"Thunderstorm", + "image":"http://openweathermap.org/img/wn/11d@2x.png" + }, + "night":{ + "description":"Thunderstorm", + "image":"http://openweathermap.org/img/wn/11n@2x.png" + } + }, + "96":{ + "day":{ + "description":"Light Thunderstorms With Hail", + "image":"http://openweathermap.org/img/wn/11d@2x.png" + }, + "night":{ + "description":"Light Thunderstorms With Hail", + "image":"http://openweathermap.org/img/wn/11n@2x.png" + } + }, + "99":{ + "day":{ + "description":"Thunderstorm With Hail", + "image":"http://openweathermap.org/img/wn/11d@2x.png" + }, + "night":{ + "description":"Thunderstorm With Hail", + "image":"http://openweathermap.org/img/wn/11n@2x.png" + } + } +} \ No newline at end of file diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index e2706c5..23c51c7 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -225,7 +226,11 @@ void HyprlandService::onOpenWindow(std::string windowData) { auto parts = StringHelper::split(windowData, ','); std::string addr = "0x" + parts[0]; int workspaceId = std::stoi(parts[1]); - std::string title = parts[2]; + std::string title = ""; + + if (parts.size() > 2) { + std::string title = parts[2]; + } auto clientPtr = std::make_shared(); clientPtr->address = addr; diff --git a/src/widgets/weather.cpp b/src/widgets/weather.cpp index b6a13d7..5c89259 100644 --- a/src/widgets/weather.cpp +++ b/src/widgets/weather.cpp @@ -1,16 +1,21 @@ #include "widgets/weather.hpp" +#include #include #include -#include +#include #include #include -#include #include +#include + +#include "components/weather.hpp" + +#include "gtkmm/object.h" namespace { constexpr const char *kWeatherUrl = - "https://api.open-meteo.com/v1/forecast?latitude=49.0094&longitude=8.4044&daily=temperature_2m_max,temperature_2m_min,weather_code&hourly=temperature_2m,rain¤t=temperature_2m&timezone=Europe%2FBerlin"; + "https://api.open-meteo.com/v1/forecast?latitude=49.0094&longitude=8.4044&daily=temperature_2m_max,temperature_2m_min,apparent_temperature_min,apparent_temperature_max,precipitation_sum,weather_code&hourly=temperature_2m,apparent_temperature,weather_code,precipitation_probability,precipitation¤t=temperature_2m,apparent_temperature,weather_code,precipitation&timezone=Europe%2FBerlin&past_days=0&forecast_days=7"; size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { size_t total = size * nmemb; @@ -19,32 +24,42 @@ size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { return total; } -std::string formatTemp(double value) { - std::ostringstream ss; - ss.setf(std::ios::fixed); - ss.precision(1); - ss << value << "°C"; - return ss.str(); -} } // namespace -WeatherWidget::WeatherWidget() - : Gtk::Box(Gtk::Orientation::VERTICAL) { - this->set_orientation(Gtk::Orientation::VERTICAL); - this->set_spacing(6); +WeatherWidget::WeatherWidget() : Gtk::Box(Gtk::Orientation::VERTICAL) { + set_spacing(10); - this->titleLabel.set_text("Weather"); - this->currentLabel.set_text("Now: --"); - this->todayLabel.set_text("Today: -- / --"); + currentScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER); + currentScroll.set_propagate_natural_height(true); + currentScroll.add_css_class("current-weather-scroll"); + currentScroll.set_valign(Gtk::Align::START); + currentScroll.set_halign(Gtk::Align::FILL); - this->append(this->titleLabel); - this->append(this->currentLabel); - this->append(this->todayLabel); + hourlyScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER); + hourlyScroll.set_propagate_natural_height(true); + hourlyScroll.add_css_class("hourly-weather-scroll"); + hourlyScroll.set_valign(Gtk::Align::START); + hourlyScroll.set_halign(Gtk::Align::FILL); - this->fetchWeather(); - Glib::signal_timeout().connect_seconds( - sigc::mem_fun(*this, &WeatherWidget::onRefreshTick), - 3600); + dailyScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER); + dailyScroll.set_propagate_natural_height(true); + dailyScroll.add_css_class("daily-weather-scroll"); + dailyScroll.set_valign(Gtk::Align::START); + dailyScroll.set_halign(Gtk::Align::FILL); + + + this->append(currentScroll); + this->append(hourlyScroll); + this->append(dailyScroll); + + m_dispatcher.connect([this]() { + this->refreshCurrentWeather(); + this->refreshHourlyWeather(); + this->refreshDailyWeather(); + }); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &WeatherWidget::onRefreshTick), 15 * 60 * 1000); + this->onRefreshTick(); } bool WeatherWidget::onRefreshTick() { @@ -52,15 +67,45 @@ bool WeatherWidget::onRefreshTick() { return true; } +void WeatherWidget::refreshCurrentWeather() { + auto current_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + current_box->set_halign(Gtk::Align::CENTER); + current_box->set_valign(Gtk::Align::CENTER); + this->currentScroll.set_child(*current_box); + + auto &cw = this->current_weather; + auto weatherPill = Gtk::make_managed(cw); + current_box->append(*weatherPill); +} + +void WeatherWidget::refreshHourlyWeather() { + auto hourly_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + this->hourlyScroll.set_child(*hourly_box); + hourly_box->set_spacing(5); + + for (auto &hw : this->hourly_weather) { + auto weatherPill = Gtk::make_managed(hw); + hourly_box->append(*weatherPill); + } +} + +void WeatherWidget::refreshDailyWeather() { + auto daily_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + this->dailyScroll.set_child(*daily_box); + daily_box->set_spacing(5); + + for (auto &dw : this->daily_weather) { + auto weatherPill = Gtk::make_managed(dw); + daily_box->append(*weatherPill); + } +} + void WeatherWidget::fetchWeather() { std::thread([this]() { std::string buffer; CURL *curl = curl_easy_init(); if (!curl) { - Glib::signal_idle().connect_once([this]() { - this->applyWeatherText("Now: --", "Today: -- / --"); - }); return; } @@ -68,41 +113,69 @@ void WeatherWidget::fetchWeather() { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_buffer); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "bar/1.0"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"); auto res = curl_easy_perform(curl); curl_easy_cleanup(curl); if (res != CURLE_OK || buffer.empty()) { - Glib::signal_idle().connect_once([this]() { - this->applyWeatherText("Now: --", "Today: -- / --"); - }); + return; } try { - auto json = nlohmann::json::parse(buffer); - double current = json.at("current").at("temperature_2m").get(); - double minTemp = json.at("daily").at("temperature_2m_min").at(0).get(); - double maxTemp = json.at("daily").at("temperature_2m_max").at(0).get(); + auto json = nlohmann::json::parse(buffer); - std::string currentText = "Now: " + formatTemp(current); - std::string todayText = "Today: " + formatTemp(minTemp) + " / " + formatTemp(maxTemp); + auto current = json["current"]; + + this->current_weather.temperature = current["temperature_2m"].get(); + this->current_weather.apparent_temperature = current["apparent_temperature"].get(); + this->current_weather.weather_code = current["weather_code"].get(); + this->current_weather.precipitation = current["precipitation"].get(); + + auto daily = json["daily"]; + + for (size_t i = 0; i < daily["time"].size(); i++) { + DailyWeather dw{}; + dw.temperature_2m_max = daily["temperature_2m_max"][i].get(); + dw.temperature_2m_min = daily["temperature_2m_min"][i].get(); + dw.apparent_temperature_min = daily["apparent_temperature_min"][i].get(); + dw.apparent_temperature_max = daily["apparent_temperature_max"][i].get(); + dw.precipitation_sum = daily["precipitation_sum"][i].get(); + dw.weather_code = daily["weather_code"][i].get(); + auto time_str = daily["time"][i].get(); + std::tm tm = {}; + std::istringstream ss(time_str); + ss >> std::get_time(&tm, "%Y-%m-%d"); + + std::mktime(&tm); + char buffer[10]; + std::strftime(buffer, sizeof(buffer), "%a", &tm); + dw.time = buffer; + + this->daily_weather[i] = dw; + } + + auto hourly = json["hourly"]; + auto current_time = std::time(nullptr); + auto nextHour = std::localtime(¤t_time)->tm_hour + 1; + + for (int i = nextHour; i < nextHour + 24; i ++) { + HourlyWeather hw{}; + hw.temperature_2m = hourly["temperature_2m"][i].get(); + hw.apparent_temperature = hourly["apparent_temperature"][i].get(); + hw.weather_code = hourly["weather_code"][i].get(); + hw.precipitation_probability = hourly["precipitation_probability"][i].get(); + hw.precipitation = hourly["precipitation"][i].get(); + hw.time = hourly["time"][i].get().substr(11, 5); + + this->hourly_weather[i - nextHour] = hw; + } - Glib::signal_idle().connect_once([this, currentText, todayText]() { - this->applyWeatherText(currentText, todayText); - }); } catch (const std::exception &ex) { spdlog::error("Weather parse error: {}", ex.what()); - Glib::signal_idle().connect_once([this]() { - this->applyWeatherText("Now: --", "Today: -- / --"); - }); } + + m_dispatcher.emit(); }).detach(); } - -void WeatherWidget::applyWeatherText(const std::string ¤t_text, - const std::string &today_text) { - this->currentLabel.set_text(current_text); - this->todayLabel.set_text(today_text); -}