// 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 #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) { // // NOTE: There does not seem to be weather emojis with the moon in unicode. const std::map> icons{ {{1, {"🌞", "🌝"}}, // clear sky {2, {"🌤", "🌤"}}, // few clouds {3, {"⛅", "⛅"}}, // scattered clouds {4, {"☁", "☁"}}, // broken clouds {9, {"🌧", "🌧"}}, // shower rain {10, {"🌧", "🌧"}}, // rain {10, {"🌩", "🌩"}}, // thunderstorm {13, {"🌨", "🌨"}}, // snow {50, {"🌫", "🌫"}}} }; // mist const auto icon{icons.find(std::stoul(icon_id.data()))}; if (icon != icons.end()) { if (*icon_id.rbegin() == 'n') // Night { return icon->second.second; } return icon->second.first; } 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; std::ifstream configfile( helpers::get_config_file_path("statusweather.cfg")); po::store(po::parse_config_file(configfile, options, true), vm); configfile.close(); po::notify(vm); 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(); const std::lock_guard guard(mutex_weather); if (response.code == 200) { 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 > 28.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} )" R"({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 (const std::exception &e) { std::cout << R"()" << e.what() << "" << std::endl; return 1; } } // Local Variables: // eval: (rainbow-mode 1) // End: