quick commit

This commit is contained in:
2026-02-04 15:52:31 +01:00
parent 0463c37543
commit ce0643b6ac
26 changed files with 929 additions and 45 deletions

View File

@@ -38,6 +38,11 @@ target_sources(bar_lib
src/app.cpp
src/bar/bar.cpp
src/connection/httpConnection.cpp
src/connection/dbus/notification.cpp
src/connection/dbus/mpris.cpp
src/connection/dbus/tray.cpp
src/widgets/clock.cpp
src/widgets/date.cpp
src/widgets/notification/baseNotification.cpp
@@ -45,14 +50,12 @@ target_sources(bar_lib
src/widgets/notification/notificationWindow.cpp
src/widgets/notification/spotifyNotification.cpp
src/widgets/volumeWidget.cpp
src/widgets/weather.cpp
src/widgets/webWidget.cpp
src/services/hyprland.cpp
src/services/notificationController.cpp
src/services/textureCache.cpp
src/services/tray.cpp
src/services/dbus/notification.cpp
src/services/dbus/mpris.cpp
src/widgets/tray.cpp
src/widgets/controlCenter/controlCenter.cpp

View File

@@ -4,7 +4,8 @@
#include "bar/bar.hpp"
#include "services/hyprland.hpp"
#include "services/dbus/notification.hpp"
#include "connection/dbus/notification.hpp"
#include "connection/dbus/tray.hpp"
#include "glibmm/refptr.h"
#include "gtkmm/application.h"

View File

@@ -0,0 +1,24 @@
#pragma once
#include <giomm.h>
#include <sigc++/sigc++.h>
class DbusConnection {
public:
virtual ~DbusConnection() = default;
protected:
Glib::RefPtr<Gio::DBus::Connection> connection;
void connect_session_async(const sigc::slot<void(const Glib::RefPtr<Gio::AsyncResult> &)> &callback) {
Gio::DBus::Connection::get(Gio::DBus::BusType::SESSION, callback);
}
static void ensure_gio_init() {
try {
Gio::init();
} catch (const Glib::Error &) {
// Already initialized.
}
}
};

View File

@@ -10,8 +10,6 @@
#include "gdkmm/pixbuf.h"
#include "glibmm/variant.h"
struct MprisPlayer2Message {
std::string title;
std::vector<std::string> artist;

View File

@@ -5,9 +5,10 @@
#include <sigc++/sigc++.h>
#include <set>
#include <vector>
#include "services/dbus/messages.hpp"
#include "connection/dbus/dbus.hpp"
#include "connection/dbus/messages.hpp"
class MprisController {
class MprisController : public DbusConnection {
public:
struct PlayerState {
std::string title;
@@ -51,7 +52,6 @@ class MprisController {
PlaybackStatus currentPlaybackStatus = PlaybackStatus::Stopped;
Glib::RefPtr<Gio::DBus::Connection> m_connection;
Glib::RefPtr<Gio::DBus::Proxy> m_proxy;
Glib::RefPtr<Gio::DBus::Proxy> m_dbus_proxy;
std::string m_player_bus_name = "org.mpris.MediaPlayer2.spotify";

View File

@@ -5,6 +5,7 @@
#include <gtkmm.h>
#include <sigc++/sigc++.h>
#include "connection/dbus/dbus.hpp"
#include "giomm/dbusconnection.h"
#include "giomm/dbusownname.h"
#include "glib.h"
@@ -39,7 +40,7 @@ const Glib::ustring introspection_xml = R"(
</node>
)";
class NotificationService {
class NotificationService : public DbusConnection {
public:
NotificationService() : notificationIdCounter(1) {
Gio::DBus::own_name(

View File

@@ -15,7 +15,9 @@
#include <string>
#include <vector>
class TrayService {
#include "connection/dbus/dbus.hpp"
class TrayService : public DbusConnection {
inline static TrayService *instance = nullptr;
public:
struct Item {
@@ -85,7 +87,6 @@ class TrayService {
bool addSignalPending = false;
};
Glib::RefPtr<Gio::DBus::Connection> connection;
Glib::RefPtr<Gio::DBus::NodeInfo> nodeInfo;
Gio::DBus::InterfaceVTable vtable;

View File

@@ -0,0 +1,36 @@
#pragma once
#include <map>
#include <string>
struct HttpResponse {
long status_code = 0;
std::string body;
std::map<std::string, std::string> headers;
std::string error;
bool ok() const {
return error.empty();
}
};
class HttpConnection {
public:
static HttpResponse get(const std::string &url,
const std::map<std::string, std::string> &headers = {},
long timeout_ms = 0);
static HttpResponse post(const std::string &url,
const std::string &body,
const std::map<std::string, std::string> &headers = {},
const std::string &content_type = "application/json",
long timeout_ms = 0);
private:
static HttpResponse performRequest(const std::string &method,
const std::string &url,
const std::string &body,
const std::map<std::string, std::string> &headers,
const std::string &content_type,
long timeout_ms);
};

View File

@@ -6,7 +6,7 @@
#include <sys/types.h>
#include <vector>
#include "services/dbus/messages.hpp"
#include "connection/dbus/messages.hpp"
#include "widgets/notification/baseNotification.hpp"
#include "gdkmm/monitor.h"

View File

@@ -6,6 +6,7 @@
#include "gtkmm/label.h"
#include "gtkmm/stack.h"
#include "widgets/controlCenter/mediaControl.hpp"
#include "widgets/weather.hpp"
#include <unordered_map>
@@ -18,7 +19,7 @@ class ControlCenter : public Popover {
Gtk::Box tabRow;
Gtk::Stack contentStack;
Gtk::Box controlCenterContainer;
Gtk::Label testLabel;
WeatherWidget weatherWidget;
Gtk::Button mediaControl;
Gtk::Button testTabButton;
std::shared_ptr<MprisController> mprisController = MprisController::getInstance();

View File

@@ -7,7 +7,7 @@
#include "gtkmm/scale.h"
#include "gtkmm/scrolledwindow.h"
#include "services/dbus/mpris.hpp"
#include "connection/dbus/mpris.hpp"
class MediaControlWidget : public Gtk::Box {
public:

View File

@@ -2,7 +2,7 @@
#include <spdlog/spdlog.h>
#include "services/dbus/messages.hpp"
#include "connection/dbus/messages.hpp"
#include "widgets/notification/baseNotification.hpp"
#include "gtkmm/box.h"

View File

@@ -2,7 +2,7 @@
#pragma once
#include <cstdint>
#include "services/dbus/messages.hpp"
#include "connection/dbus/messages.hpp"
#include "widgets/notification/baseNotification.hpp"
class NotificationWindow : public BaseNotification {

View File

@@ -3,7 +3,7 @@
#include <cstdint>
#include <memory>
#include "services/dbus/messages.hpp"
#include "connection/dbus/messages.hpp"
#include "widgets/notification/baseNotification.hpp"
#include "gtkmm/centerbox.h"

View File

@@ -17,7 +17,7 @@
#include <string>
#include <vector>
#include "services/tray.hpp"
#include "connection/dbus/tray.hpp"
#include "components/base/button.hpp"
class TrayIconWidget : public Button {

View File

@@ -0,0 +1,20 @@
#pragma once
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <sigc++/sigc++.h>
class WeatherWidget : public Gtk::Box {
public:
WeatherWidget();
private:
Gtk::Label titleLabel;
Gtk::Label currentLabel;
Gtk::Label todayLabel;
bool onRefreshTick();
void fetchWeather();
void applyWeatherText(const std::string &current_text,
const std::string &today_text);
};

View File

@@ -2,8 +2,8 @@
#include <sigc++/sigc++.h>
#include <vector>
#include "services/dbus/notification.hpp"
#include "services/dbus/mpris.hpp"
#include "connection/dbus/notification.hpp"
#include "connection/dbus/mpris.hpp"
#include "services/notificationController.hpp"
#include "services/textureCache.hpp"

View File

View File

@@ -1,4 +1,4 @@
#include "services/dbus/mpris.hpp"
#include "connection/dbus/mpris.hpp"
#include <map>
#include <spdlog/spdlog.h>
@@ -19,16 +19,12 @@ std::shared_ptr<MprisController> MprisController::createForPlayer(const std::str
}
MprisController::MprisController() {
Gio::DBus::Connection::get(
Gio::DBus::BusType::SESSION,
sigc::mem_fun(*this, &MprisController::on_bus_connected));
connect_session_async(sigc::mem_fun(*this, &MprisController::on_bus_connected));
}
MprisController::MprisController(const std::string &bus_name)
: m_player_bus_name(bus_name) {
Gio::DBus::Connection::get(
Gio::DBus::BusType::SESSION,
sigc::mem_fun(*this, &MprisController::on_bus_connected));
connect_session_async(sigc::mem_fun(*this, &MprisController::on_bus_connected));
}
std::vector<std::string> MprisController::get_registered_players() const {
@@ -65,11 +61,11 @@ void MprisController::on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &res
return;
}
try {
m_connection = Gio::DBus::Connection::get_finish(result);
connection = Gio::DBus::Connection::get_finish(result);
if (!m_dbus_proxy) {
m_dbus_proxy = Gio::DBus::Proxy::create_sync(
m_connection,
connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus");
@@ -131,10 +127,10 @@ void MprisController::handle_player_registered(const std::string &bus_name) {
registeredPlayers.insert(bus_name);
if (bus_name == m_player_bus_name && !m_proxy && m_connection) {
if (bus_name == m_player_bus_name && !m_proxy && connection) {
try {
m_proxy = Gio::DBus::Proxy::create_sync(
m_connection,
connection,
m_player_bus_name,
"/org/mpris/MediaPlayer2",
"org.mpris.MediaPlayer2.Player");

View File

@@ -1,11 +1,9 @@
#include "services/dbus/notification.hpp"
#include "connection/dbus/notification.hpp"
#include <spdlog/spdlog.h>
#include <sys/types.h>
#include "helpers/string.hpp"
#include "services/notificationController.hpp"
#include "widgets/notification/copyNotification.hpp"
#include "glib.h"
#include "glibconfig.h"

View File

@@ -1,4 +1,4 @@
#include "services/tray.hpp"
#include "connection/dbus/tray.hpp"
#include <algorithm>
#include <cstring>
@@ -277,11 +277,7 @@ void TrayService::start() {
return;
}
try {
Gio::init();
} catch (const Glib::Error &) {
// Already initialised; ignore.
}
ensure_gio_init();
if (!nodeInfo) {
try {

View File

@@ -0,0 +1,119 @@
#include "connection/httpConnection.hpp"
#include <algorithm>
#include <cctype>
#include <curl/curl.h>
#include <string>
#include <utility>
namespace {
size_t write_to_string(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total = size * nmemb;
auto *buffer = static_cast<std::string *>(userp);
buffer->append(static_cast<char *>(contents), total);
return total;
}
std::string trim(std::string value) {
auto not_space = [](unsigned char c) { return std::isspace(c) == 0; };
value.erase(value.begin(),
std::find_if(value.begin(), value.end(), not_space));
value.erase(std::find_if(value.rbegin(), value.rend(), not_space).base(),
value.end());
return value;
}
size_t header_to_map(char *buffer, size_t size, size_t nitems, void *userdata) {
size_t total = size * nitems;
auto *header_map = static_cast<std::map<std::string, std::string> *>(userdata);
std::string line(buffer, total);
auto colon = line.find(':');
if (colon != std::string::npos) {
auto key = trim(line.substr(0, colon));
auto value = trim(line.substr(colon + 1));
if (!key.empty()) {
header_map->insert_or_assign(std::move(key), std::move(value));
}
}
return total;
}
}
HttpResponse HttpConnection::get(const std::string &url,
const std::map<std::string, std::string> &headers,
long timeout_ms) {
return performRequest("GET", url, std::string(), headers, std::string(), timeout_ms);
}
HttpResponse HttpConnection::post(const std::string &url,
const std::string &body,
const std::map<std::string, std::string> &headers,
const std::string &content_type,
long timeout_ms) {
return performRequest("POST", url, body, headers, content_type, timeout_ms);
}
HttpResponse HttpConnection::performRequest(const std::string &method,
const std::string &url,
const std::string &body,
const std::map<std::string, std::string> &headers,
const std::string &content_type,
long timeout_ms) {
HttpResponse response;
CURL *curl = curl_easy_init();
if (!curl) {
response.error = "curl_easy_init failed";
return response;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_string);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_to_map);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response.headers);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "bar/1.0");
if (timeout_ms > 0) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_ms);
}
if (method == "POST") {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
} else {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
}
struct curl_slist *header_list = nullptr;
for (const auto &pair : headers) {
std::string header = pair.first + ": " + pair.second;
header_list = curl_slist_append(header_list, header.c_str());
}
if (method == "POST" && !content_type.empty()) {
std::string content_header = "Content-Type: " + content_type;
header_list = curl_slist_append(header_list, content_header.c_str());
}
if (header_list) {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
}
auto result = curl_easy_perform(curl);
if (result != CURLE_OK) {
response.error = curl_easy_strerror(result);
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.status_code);
if (header_list) {
curl_slist_free_all(header_list);
}
curl_easy_cleanup(curl);
return response;
}

View File

@@ -3,7 +3,7 @@
#include <algorithm>
#include <memory>
#include "services/dbus/messages.hpp"
#include "connection/dbus/messages.hpp"
#include "widgets/notification/baseNotification.hpp"
#include "widgets/notification/copyNotification.hpp"
#include "widgets/notification/notificationWindow.hpp"

View File

@@ -34,10 +34,8 @@ ControlCenter::ControlCenter(std::string icon, std::string name)
this->controlCenterContainer.set_orientation(Gtk::Orientation::VERTICAL);
this->controlCenterContainer.set_spacing(4);
this->testLabel.set_text("Test tab");
this->contentStack.add(this->controlCenterContainer, "controls", "Controls");
this->contentStack.add(this->testLabel, "test", "Test");
this->contentStack.add(this->weatherWidget, "test", "Test");
this->contentStack.set_visible_child("controls");
this->setActiveTab("controls");

112
src/widgets/weather.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "widgets/weather.hpp"
#include <curl/curl.h>
#include <glibmm/main.h>
#include <ios>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <sstream>
#include <thread>
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&current=temperature_2m&timezone=Europe%2FBerlin";
size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total = size * nmemb;
auto *buffer = static_cast<std::string *>(userp);
buffer->append(static_cast<char *>(contents), total);
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);
this->set_margin_top(4);
this->set_margin_bottom(4);
this->set_margin_start(4);
this->set_margin_end(4);
this->titleLabel.set_text("Weather");
this->currentLabel.set_text("Now: --");
this->todayLabel.set_text("Today: -- / --");
this->append(this->titleLabel);
this->append(this->currentLabel);
this->append(this->todayLabel);
this->fetchWeather();
Glib::signal_timeout().connect_seconds(
sigc::mem_fun(*this, &WeatherWidget::onRefreshTick),
3600);
}
bool WeatherWidget::onRefreshTick() {
this->fetchWeather();
return true;
}
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;
}
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, "bar/1.0");
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>();
double minTemp = json.at("daily").at("temperature_2m_min").at(0).get<double>();
double maxTemp = json.at("daily").at("temperature_2m_max").at(0).get<double>();
std::string currentText = "Now: " + formatTemp(current);
std::string todayText = "Today: " + formatTemp(minTemp) + " / " + formatTemp(maxTemp);
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: -- / --");
});
}
}).detach();
}
void WeatherWidget::applyWeatherText(const std::string &current_text,
const std::string &today_text) {
this->currentLabel.set_text(current_text);
this->todayLabel.set_text(today_text);
}

580
weather.json Normal file
View File

@@ -0,0 +1,580 @@
{
"latitude": 49.0,
"longitude": 8.4,
"generationtime_ms": 0.23317337036132812,
"utc_offset_seconds": 3600,
"timezone": "Europe/Berlin",
"timezone_abbreviation": "GMT+1",
"elevation": 122.0,
"current_units": {
"time": "iso8601",
"interval": "seconds",
"temperature_2m": "°C"
},
"current": {
"time": "2026-02-03T23:00",
"interval": 900,
"temperature_2m": -0.7
},
"hourly_units": {
"time": "iso8601",
"temperature_2m": "°C",
"rain": "mm"
},
"hourly": {
"time": [
"2026-02-03T00:00",
"2026-02-03T01:00",
"2026-02-03T02:00",
"2026-02-03T03:00",
"2026-02-03T04:00",
"2026-02-03T05:00",
"2026-02-03T06:00",
"2026-02-03T07:00",
"2026-02-03T08:00",
"2026-02-03T09:00",
"2026-02-03T10:00",
"2026-02-03T11:00",
"2026-02-03T12:00",
"2026-02-03T13:00",
"2026-02-03T14:00",
"2026-02-03T15:00",
"2026-02-03T16:00",
"2026-02-03T17:00",
"2026-02-03T18:00",
"2026-02-03T19:00",
"2026-02-03T20:00",
"2026-02-03T21:00",
"2026-02-03T22:00",
"2026-02-03T23:00",
"2026-02-04T00:00",
"2026-02-04T01:00",
"2026-02-04T02:00",
"2026-02-04T03:00",
"2026-02-04T04:00",
"2026-02-04T05:00",
"2026-02-04T06:00",
"2026-02-04T07:00",
"2026-02-04T08:00",
"2026-02-04T09:00",
"2026-02-04T10:00",
"2026-02-04T11:00",
"2026-02-04T12:00",
"2026-02-04T13:00",
"2026-02-04T14:00",
"2026-02-04T15:00",
"2026-02-04T16:00",
"2026-02-04T17:00",
"2026-02-04T18:00",
"2026-02-04T19:00",
"2026-02-04T20:00",
"2026-02-04T21:00",
"2026-02-04T22:00",
"2026-02-04T23:00",
"2026-02-05T00:00",
"2026-02-05T01:00",
"2026-02-05T02:00",
"2026-02-05T03:00",
"2026-02-05T04:00",
"2026-02-05T05:00",
"2026-02-05T06:00",
"2026-02-05T07:00",
"2026-02-05T08:00",
"2026-02-05T09:00",
"2026-02-05T10:00",
"2026-02-05T11:00",
"2026-02-05T12:00",
"2026-02-05T13:00",
"2026-02-05T14:00",
"2026-02-05T15:00",
"2026-02-05T16:00",
"2026-02-05T17:00",
"2026-02-05T18:00",
"2026-02-05T19:00",
"2026-02-05T20:00",
"2026-02-05T21:00",
"2026-02-05T22:00",
"2026-02-05T23:00",
"2026-02-06T00:00",
"2026-02-06T01:00",
"2026-02-06T02:00",
"2026-02-06T03:00",
"2026-02-06T04:00",
"2026-02-06T05:00",
"2026-02-06T06:00",
"2026-02-06T07:00",
"2026-02-06T08:00",
"2026-02-06T09:00",
"2026-02-06T10:00",
"2026-02-06T11:00",
"2026-02-06T12:00",
"2026-02-06T13:00",
"2026-02-06T14:00",
"2026-02-06T15:00",
"2026-02-06T16:00",
"2026-02-06T17:00",
"2026-02-06T18:00",
"2026-02-06T19:00",
"2026-02-06T20:00",
"2026-02-06T21:00",
"2026-02-06T22:00",
"2026-02-06T23:00",
"2026-02-07T00:00",
"2026-02-07T01:00",
"2026-02-07T02:00",
"2026-02-07T03:00",
"2026-02-07T04:00",
"2026-02-07T05:00",
"2026-02-07T06:00",
"2026-02-07T07:00",
"2026-02-07T08:00",
"2026-02-07T09:00",
"2026-02-07T10:00",
"2026-02-07T11:00",
"2026-02-07T12:00",
"2026-02-07T13:00",
"2026-02-07T14:00",
"2026-02-07T15:00",
"2026-02-07T16:00",
"2026-02-07T17:00",
"2026-02-07T18:00",
"2026-02-07T19:00",
"2026-02-07T20:00",
"2026-02-07T21:00",
"2026-02-07T22:00",
"2026-02-07T23:00",
"2026-02-08T00:00",
"2026-02-08T01:00",
"2026-02-08T02:00",
"2026-02-08T03:00",
"2026-02-08T04:00",
"2026-02-08T05:00",
"2026-02-08T06:00",
"2026-02-08T07:00",
"2026-02-08T08:00",
"2026-02-08T09:00",
"2026-02-08T10:00",
"2026-02-08T11:00",
"2026-02-08T12:00",
"2026-02-08T13:00",
"2026-02-08T14:00",
"2026-02-08T15:00",
"2026-02-08T16:00",
"2026-02-08T17:00",
"2026-02-08T18:00",
"2026-02-08T19:00",
"2026-02-08T20:00",
"2026-02-08T21:00",
"2026-02-08T22:00",
"2026-02-08T23:00",
"2026-02-09T00:00",
"2026-02-09T01:00",
"2026-02-09T02:00",
"2026-02-09T03:00",
"2026-02-09T04:00",
"2026-02-09T05:00",
"2026-02-09T06:00",
"2026-02-09T07:00",
"2026-02-09T08:00",
"2026-02-09T09:00",
"2026-02-09T10:00",
"2026-02-09T11:00",
"2026-02-09T12:00",
"2026-02-09T13:00",
"2026-02-09T14:00",
"2026-02-09T15:00",
"2026-02-09T16:00",
"2026-02-09T17:00",
"2026-02-09T18:00",
"2026-02-09T19:00",
"2026-02-09T20:00",
"2026-02-09T21:00",
"2026-02-09T22:00",
"2026-02-09T23:00"
],
"temperature_2m": [
2.2,
2.2,
2.2,
2.2,
2.0,
1.9,
1.8,
1.5,
1.2,
1.4,
1.8,
2.2,
2.5,
1.9,
2.6,
3.0,
2.7,
3.0,
3.0,
2.4,
1.7,
1.3,
0.9,
-0.7,
-0.2,
-0.1,
-0.5,
-0.7,
-1.4,
-1.1,
-1.0,
-1.0,
-1.1,
-0.9,
1.7,
3.7,
5.0,
5.8,
6.5,
6.9,
6.6,
5.9,
4.8,
3.6,
2.7,
2.0,
1.5,
1.1,
0.5,
0.2,
0.4,
0.9,
1.2,
1.3,
1.5,
1.7,
1.8,
2.0,
2.2,
2.7,
3.4,
4.2,
5.2,
6.1,
6.4,
5.9,
4.5,
3.2,
2.4,
1.9,
1.4,
1.1,
1.0,
0.8,
0.5,
0.3,
0.1,
-0.1,
-0.2,
-0.3,
-0.3,
-0.1,
0.7,
1.7,
2.9,
4.1,
5.0,
5.2,
5.1,
4.8,
4.3,
4.1,
4.2,
4.1,
3.8,
3.5,
3.3,
3.4,
3.4,
3.5,
3.5,
3.5,
3.4,
3.4,
3.5,
3.6,
4.1,
5.2,
6.6,
7.8,
8.4,
8.8,
8.8,
8.1,
6.9,
5.9,
5.3,
4.9,
4.6,
4.3,
4.2,
4.0,
3.9,
3.9,
3.8,
3.8,
3.8,
3.9,
4.3,
4.7,
5.3,
6.0,
6.9,
7.5,
7.6,
7.5,
7.2,
6.6,
5.8,
5.0,
4.4,
3.9,
3.5,
3.1,
2.9,
2.8,
3.0,
3.3,
3.6,
3.5,
3.4,
3.4,
3.7,
4.2,
4.7,
5.3,
6.0,
6.5,
6.8,
6.8,
6.6,
5.9,
4.8,
3.9,
3.5,
3.3,
3.1,
2.9
],
"rain": [
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.10,
0.40,
0.00,
0.00,
0.20,
0.60,
0.30,
1.30,
0.00,
0.00,
0.40,
0.10,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.10,
0.20,
0.00,
0.00,
0.00,
0.10,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00,
0.00
]
},
"daily_units": {
"time": "iso8601",
"temperature_2m_max": "°C",
"temperature_2m_min": "°C",
"weather_code": "wmo code"
},
"daily": {
"time": [
"2026-02-03",
"2026-02-04",
"2026-02-05",
"2026-02-06",
"2026-02-07",
"2026-02-08",
"2026-02-09"
],
"temperature_2m_max": [
3.0,
6.9,
6.4,
5.2,
8.8,
7.6,
6.8
],
"temperature_2m_min": [
-0.7,
-1.4,
0.2,
-0.3,
3.3,
3.1,
2.8
],
"weather_code": [
61,
45,
45,
61,
61,
3,
3
]
}
}