// Print weather information for i3 status bar. /* i3blocks config: * [weather] * command=statusweather * interval=persist * markup=pango */ /* ~/.config/statusweather.cfg: * api_key = abc123 * city = Hamburg,de */ #include "helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct weather { float temperature{-273.15}; std::string icon{"❗"}; bool old{true}; } __attribute__((aligned(64))) weather; // NOLINT(cert-err58-cpp) std::mutex mutex_weather; std::string map_icon(const std::string_view icon_id) { // const std::map icons{{{1, "🌞"}, {2, "⛅"}, {3, "☁"}, {4, "☁"}, {9, "🌧"}, {10, "🌧"}, {10, "⛈"}, {13, "🌨"}, {50, "🌫"}}}; // TODO: Differenciate between day and night. const auto icon{icons.find(std::stoul(icon_id.data()))}; if (icon != icons.end()) { return icon->second; } return {}; } std::tuple get_options() { namespace po = boost::program_options; po::options_description options("Options"); // clang-format off options.add_options() ("api_key", po::value()->required()->value_name("API key"), "API key for openweathermap.org.") ("city", po::value()->required()->value_name("City"), "City you want the weather data for.") ; // clang-format on po::variables_map vm; const auto path{[]() { auto path{helpers::get_env("XDG_CONFIG_HOME")}; if (path.empty()) { path = helpers::get_env("HOME"); if (!path.empty()) { path += "/.config"; } } return path += "/statusweather.cfg"; }()}; std::ifstream configfile(path); po::store(po::parse_config_file(configfile, options, true), vm); configfile.close(); if ((vm.count("api_key") == 0) || (vm.count("city") == 0)) { throw std::runtime_error{"api_key or city not configured."}; } return std::make_tuple(vm["api_key"].as(), vm["city"].as()); } bool fetch_weather() { using fmt::format; std::string api_key; std::string city; try { std::tie(api_key, city) = get_options(); } catch (std::runtime_error &e) { std::cout << R"()" << e.what() << "" << std::endl; return false; } RestClient::init(); RestClient::Connection conn( "http://api.openweathermap.org/data/2.5/weather"); conn.FollowRedirects(true, 5); conn.SetTimeout(120); // auto response{ conn.get(format("?appid={0:s}&q={1:s}&units=metric", api_key, city))}; RestClient::disable(); if (response.code == 200) { const std::lock_guard guard(mutex_weather); const auto json{nlohmann::json::parse(response.body)}; weather.temperature = json[0]["main"]["temp"].get(); weather.icon = map_icon( json[0]["weather"][0]["icon"].get()); weather.old = false; } else { weather.old = true; } return true; } void print_weather() { using fmt::format; const std::lock_guard guard(mutex_weather); const std::string color{[] { if (weather.temperature > 25.0) { return "#ff2200"; } if (weather.temperature < 0.0) { return "#aaffff"; } if (weather.temperature < 10.0) { return "#44ddff"; } return "#66ff66"; }()}; std::cout << format(R"({0:s} {2:.1Lf}°C{3:s})", weather.icon, color, weather.temperature, weather.old ? R"( )" : "") << std::endl; } void update(std::atomic &cancelled) { using clock = std::chrono::system_clock; using namespace std::chrono_literals; while (!cancelled) { if (fetch_weather()) { print_weather(); } std::this_thread::sleep_until(clock::now() + 30min); } } int main() { try { std::locale::global(std::locale("")); // TODO: Implement clean shutdown. std::atomic cancelled{false}; auto future{ std::async(std::launch::async, update, std::ref(cancelled))}; std::string line; while (std::getline(std::cin, line)) // Button click is sent to stdin. { if (line == "1") // Left mouse button. { if (fetch_weather()) { print_weather(); } } } } catch (std::exception &e) { std::cout << R"()" << e.what() << "" << std::endl; return 33; } }