quic
This commit is contained in:
@@ -106,8 +106,8 @@ include(Catch)
|
|||||||
catch_discover_tests(bar_tests)
|
catch_discover_tests(bar_tests)
|
||||||
|
|
||||||
# Copy all CSS files in resources/ into build and user config directories
|
# Copy all CSS files in resources/ into build and user config directories
|
||||||
file(GLOB RES_CSS_FILES CONFIGURE_DEPENDS
|
file(GLOB RES_FILES CONFIGURE_DEPENDS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/resources/*.css"
|
"${CMAKE_CURRENT_SOURCE_DIR}/resources/*"
|
||||||
)
|
)
|
||||||
set(RES_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
|
set(RES_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
|
||||||
set(RES_USER_DIR "$ENV{HOME}/.config/bar")
|
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(RES_DST_FILES "")
|
||||||
set(USER_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)
|
get_filename_component(RES_NAME "${RES_FILE}" NAME)
|
||||||
set(RES_DST "${RES_BUILD_DIR}/${RES_NAME}")
|
set(RES_DST "${RES_BUILD_DIR}/${RES_NAME}")
|
||||||
set(USER_DST "${RES_USER_DIR}/${RES_NAME}")
|
set(USER_DST "${RES_USER_DIR}/${RES_NAME}")
|
||||||
|
|||||||
129
include/components/weather.hpp
Normal file
129
include/components/weather.hpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#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<int>(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<std::string>();
|
||||||
|
|
||||||
|
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("<span weight='bold'>" + formatTemperature(current_weather.temperature) + "</span>" + "<span color='gray' size='small'> (" + formatTemperature(current_weather.apparent_temperature) + ")</span>");
|
||||||
|
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("<span weight='bold'>" + formatTemperature(hourly_weather.temperature_2m) + "</span>" + "<span color='gray' size='small'> (" + formatTemperature(hourly_weather.apparent_temperature) + ")</span>");
|
||||||
|
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("<span size='small'>" + hourly_weather.time + "</span>");
|
||||||
|
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("<span weight='bold'>H: " + formatTemperature(daily_weather.temperature_2m_max) + "</span>" + "<span color='gray' size='small'> (" + formatTemperature(daily_weather.apparent_temperature_max) + ")</span>");
|
||||||
|
max_label.set_halign(Gtk::Align::START);
|
||||||
|
|
||||||
|
auto min_label = Gtk::Label();
|
||||||
|
min_label.set_markup("<span size='small'>L: " + formatTemperature(daily_weather.temperature_2m_min) + "</span>" + "<span color='gray' size='small'> (" + formatTemperature(daily_weather.apparent_temperature_min) + ")</span>");
|
||||||
|
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("<span size='small'>" + daily_weather.time + "</span>");
|
||||||
|
timeLabel.set_halign(Gtk::Align::START);
|
||||||
|
temp_box.append(timeLabel);
|
||||||
|
|
||||||
|
append(image);
|
||||||
|
append(temp_box);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,20 +1,58 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <glibmm/dispatcher.h>
|
||||||
#include <gtkmm/box.h>
|
#include <gtkmm/box.h>
|
||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <sigc++/sigc++.h>
|
#include <sigc++/sigc++.h>
|
||||||
|
|
||||||
|
#include "gtkmm/scrolledwindow.h"
|
||||||
|
|
||||||
class WeatherWidget : public Gtk::Box {
|
class WeatherWidget : public Gtk::Box {
|
||||||
public:
|
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();
|
WeatherWidget();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Gtk::Label titleLabel;
|
std::array<DailyWeather, 7> daily_weather{};
|
||||||
Gtk::Label currentLabel;
|
std::array<HourlyWeather, 24> hourly_weather{};
|
||||||
Gtk::Label todayLabel;
|
CurrentWeather current_weather{};
|
||||||
|
|
||||||
|
Gtk::ScrolledWindow currentScroll;
|
||||||
|
Gtk::ScrolledWindow hourlyScroll;
|
||||||
|
Gtk::ScrolledWindow dailyScroll;
|
||||||
|
|
||||||
|
Glib::Dispatcher m_dispatcher;
|
||||||
|
|
||||||
bool onRefreshTick();
|
bool onRefreshTick();
|
||||||
void fetchWeather();
|
void fetchWeather();
|
||||||
void applyWeatherText(const std::string ¤t_text,
|
|
||||||
const std::string &today_text);
|
void refreshCurrentWeather();
|
||||||
|
void refreshHourlyWeather();
|
||||||
|
void refreshDailyWeather();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/* Custom Scrollbar Styling */
|
/* Custom Scrollbar Styling */
|
||||||
|
/** biome-ignore-all lint/correctness/noUnknownTypeSelector: gtk css has more valid identifiers */
|
||||||
|
|
||||||
scrollbar, scrollbar * {
|
scrollbar, scrollbar * {
|
||||||
min-width: 8px;
|
min-width: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -22,7 +24,6 @@ scrollbar trough {
|
|||||||
scrollbar button {
|
scrollbar button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
/** biome-ignore-all lint/correctness/noUnknownTypeSelector: gtk css has more valid identifiers */
|
|
||||||
* {
|
* {
|
||||||
all: unset;
|
all: unset;
|
||||||
}
|
}
|
||||||
@@ -295,4 +296,26 @@ tooltip {
|
|||||||
.available-devices {
|
.available-devices {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|||||||
282
resources/weatherCodes.json
Normal file
282
resources/weatherCodes.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <glib-unix.h>
|
#include <glib-unix.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -225,7 +226,11 @@ void HyprlandService::onOpenWindow(std::string windowData) {
|
|||||||
auto parts = StringHelper::split(windowData, ',');
|
auto parts = StringHelper::split(windowData, ',');
|
||||||
std::string addr = "0x" + parts[0];
|
std::string addr = "0x" + parts[0];
|
||||||
int workspaceId = std::stoi(parts[1]);
|
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<Client>();
|
auto clientPtr = std::make_shared<Client>();
|
||||||
clientPtr->address = addr;
|
clientPtr->address = addr;
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
#include "widgets/weather.hpp"
|
#include "widgets/weather.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <glibmm/main.h>
|
#include <glibmm/main.h>
|
||||||
#include <ios>
|
#include <iomanip>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "components/weather.hpp"
|
||||||
|
|
||||||
|
#include "gtkmm/object.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr const char *kWeatherUrl =
|
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 write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||||
size_t total = size * nmemb;
|
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;
|
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
|
} // namespace
|
||||||
|
|
||||||
WeatherWidget::WeatherWidget()
|
WeatherWidget::WeatherWidget() : Gtk::Box(Gtk::Orientation::VERTICAL) {
|
||||||
: Gtk::Box(Gtk::Orientation::VERTICAL) {
|
set_spacing(10);
|
||||||
this->set_orientation(Gtk::Orientation::VERTICAL);
|
|
||||||
this->set_spacing(6);
|
|
||||||
|
|
||||||
this->titleLabel.set_text("Weather");
|
currentScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER);
|
||||||
this->currentLabel.set_text("Now: --");
|
currentScroll.set_propagate_natural_height(true);
|
||||||
this->todayLabel.set_text("Today: -- / --");
|
currentScroll.add_css_class("current-weather-scroll");
|
||||||
|
currentScroll.set_valign(Gtk::Align::START);
|
||||||
|
currentScroll.set_halign(Gtk::Align::FILL);
|
||||||
|
|
||||||
this->append(this->titleLabel);
|
hourlyScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER);
|
||||||
this->append(this->currentLabel);
|
hourlyScroll.set_propagate_natural_height(true);
|
||||||
this->append(this->todayLabel);
|
hourlyScroll.add_css_class("hourly-weather-scroll");
|
||||||
|
hourlyScroll.set_valign(Gtk::Align::START);
|
||||||
|
hourlyScroll.set_halign(Gtk::Align::FILL);
|
||||||
|
|
||||||
this->fetchWeather();
|
dailyScroll.set_policy(Gtk::PolicyType::ALWAYS, Gtk::PolicyType::NEVER);
|
||||||
Glib::signal_timeout().connect_seconds(
|
dailyScroll.set_propagate_natural_height(true);
|
||||||
sigc::mem_fun(*this, &WeatherWidget::onRefreshTick),
|
dailyScroll.add_css_class("daily-weather-scroll");
|
||||||
3600);
|
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() {
|
bool WeatherWidget::onRefreshTick() {
|
||||||
@@ -52,15 +67,45 @@ bool WeatherWidget::onRefreshTick() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WeatherWidget::refreshCurrentWeather() {
|
||||||
|
auto current_box = Gtk::make_managed<Gtk::Box>(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<WeatherInfo>(cw);
|
||||||
|
current_box->append(*weatherPill);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherWidget::refreshHourlyWeather() {
|
||||||
|
auto hourly_box = Gtk::make_managed<Gtk::Box>(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<WeatherInfo>(hw);
|
||||||
|
hourly_box->append(*weatherPill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherWidget::refreshDailyWeather() {
|
||||||
|
auto daily_box = Gtk::make_managed<Gtk::Box>(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<WeatherInfo>(dw);
|
||||||
|
daily_box->append(*weatherPill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WeatherWidget::fetchWeather() {
|
void WeatherWidget::fetchWeather() {
|
||||||
std::thread([this]() {
|
std::thread([this]() {
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
|
|
||||||
CURL *curl = curl_easy_init();
|
CURL *curl = curl_easy_init();
|
||||||
if (!curl) {
|
if (!curl) {
|
||||||
Glib::signal_idle().connect_once([this]() {
|
|
||||||
this->applyWeatherText("Now: --", "Today: -- / --");
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,41 +113,69 @@ void WeatherWidget::fetchWeather() {
|
|||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_buffer);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_buffer);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &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);
|
auto res = curl_easy_perform(curl);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK || buffer.empty()) {
|
if (res != CURLE_OK || buffer.empty()) {
|
||||||
Glib::signal_idle().connect_once([this]() {
|
|
||||||
this->applyWeatherText("Now: --", "Today: -- / --");
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto json = nlohmann::json::parse(buffer);
|
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);
|
auto current = json["current"];
|
||||||
std::string todayText = "Today: " + formatTemp(minTemp) + " / " + formatTemp(maxTemp);
|
|
||||||
|
this->current_weather.temperature = current["temperature_2m"].get<double>();
|
||||||
|
this->current_weather.apparent_temperature = current["apparent_temperature"].get<double>();
|
||||||
|
this->current_weather.weather_code = current["weather_code"].get<int>();
|
||||||
|
this->current_weather.precipitation = current["precipitation"].get<double>();
|
||||||
|
|
||||||
|
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<double>();
|
||||||
|
dw.temperature_2m_min = daily["temperature_2m_min"][i].get<double>();
|
||||||
|
dw.apparent_temperature_min = daily["apparent_temperature_min"][i].get<double>();
|
||||||
|
dw.apparent_temperature_max = daily["apparent_temperature_max"][i].get<double>();
|
||||||
|
dw.precipitation_sum = daily["precipitation_sum"][i].get<double>();
|
||||||
|
dw.weather_code = daily["weather_code"][i].get<int>();
|
||||||
|
auto time_str = daily["time"][i].get<std::string>();
|
||||||
|
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<double>();
|
||||||
|
hw.apparent_temperature = hourly["apparent_temperature"][i].get<double>();
|
||||||
|
hw.weather_code = hourly["weather_code"][i].get<int>();
|
||||||
|
hw.precipitation_probability = hourly["precipitation_probability"][i].get<double>();
|
||||||
|
hw.precipitation = hourly["precipitation"][i].get<double>();
|
||||||
|
hw.time = hourly["time"][i].get<std::string>().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) {
|
} catch (const std::exception &ex) {
|
||||||
spdlog::error("Weather parse error: {}", ex.what());
|
spdlog::error("Weather parse error: {}", ex.what());
|
||||||
Glib::signal_idle().connect_once([this]() {
|
|
||||||
this->applyWeatherText("Now: --", "Today: -- / --");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_dispatcher.emit();
|
||||||
}).detach();
|
}).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);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user