#include "widgets/weather.hpp" #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,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; auto *buffer = static_cast(userp); buffer->append(static_cast(contents), total); return total; } } // namespace WeatherWidget::WeatherWidget() : Gtk::Box(Gtk::Orientation::VERTICAL) { set_spacing(10); 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); 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); 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() { this->fetchWeather(); 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) { return; } curl_easy_setopt(curl, CURLOPT_URL, kWeatherUrl); 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, "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()) { return; } try { auto json = nlohmann::json::parse(buffer); 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 currentHour = std::localtime(¤t_time)->tm_hour; for (int i = currentHour; i < currentHour + 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 - currentHour] = hw; } } catch (const std::exception &ex) { spdlog::error("Weather parse error: {}", ex.what()); } m_dispatcher.emit(); }).detach(); }