diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index e54f4d2..bb63241 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -12,6 +12,11 @@ namespace waybar::modules { +struct waybar_time { + std::locale locale; + date::zoned_time ztime; +}; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -23,6 +28,12 @@ class Clock : public ALabel { std::locale locale_; const date::time_zone* time_zone_; bool fixed_time_zone_; + date::year_month_day cached_calendar_ymd_; + std::string cached_calendar_text_; + + auto calendar_text(const waybar_time& wtime) -> std::string; + auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; + auto first_day_of_week() -> date::weekday; }; } // namespace waybar::modules diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index e3213f3..6684d89 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -65,6 +65,10 @@ The *clock* module displays the current date and time. View all valid format options in *strftime(3)*. +# FORMAT REPLACEMENTS + +*{calendar}*: Current month calendar + # EXAMPLES ``` diff --git a/meson.build b/meson.build index 69439ac..8cfe5d8 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,22 @@ if not compiler.has_header('filesystem') endif endif +code = ''' +#include +#include +int main(int argc, char** argv) { + locale_t locale = newlocale(LC_ALL, "en_US.UTF-8", nullptr); + char* str; + str = nl_langinfo_l(_NL_TIME_WEEK_1STDAY, locale); + str = nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, locale); + freelocale(locale); + return 0; +} +''' +if compiler.links(code, name : 'nl_langinfo with _NL_TIME_WEEK_1STDAY, _NL_TIME_FIRST_WEEKDAY') + add_project_arguments('-DHAVE_LANGINFO_1STDAY', language: 'cpp') +endif + add_global_arguments(cpp_args, language : 'cpp') add_global_link_arguments(cpp_link_args, language : 'cpp') diff --git a/resources/config b/resources/config index fcc365e..237c787 100644 --- a/resources/config +++ b/resources/config @@ -65,7 +65,7 @@ }, "clock": { // "timezone": "America/New_York", - "tooltip-format": "{:%Y-%m-%d | %H:%M}", + "tooltip-format": "{:%Y %B}\n{calendar}", "format-alt": "{:%Y-%m-%d}" }, "cpu": { diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 38af115..5655168 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -1,4 +1,12 @@ #include "modules/clock.hpp" +#include +#include +#ifdef HAVE_LANGINFO_1STDAY +#include +#include +#endif + +using waybar::modules::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60) @@ -24,13 +32,6 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) }; } -using zoned_time = date::zoned_time; - -struct waybar_time { - std::locale locale; - zoned_time ztime; -}; - auto waybar::modules::Clock::update() -> void { if (!fixed_time_zone_) { // Time zone can change. Be sure to pick that. @@ -44,15 +45,96 @@ auto waybar::modules::Clock::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { + const auto calendar = calendar_text(wtime); auto tooltip_format = config_["tooltip-format"].asString(); - auto tooltip_text = fmt::format(tooltip_format, wtime); - label_.set_tooltip_text(tooltip_text); + auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); + label_.set_tooltip_markup(tooltip_text); } else { - label_.set_tooltip_text(text); + label_.set_tooltip_markup(text); } } } +auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { + const auto daypoint = date::floor(wtime.ztime.get_local_time()); + const auto ymd = date::year_month_day(daypoint); + if (cached_calendar_ymd_ == ymd) { + return cached_calendar_text_; + } + + const date::year_month ym(ymd.year(), ymd.month()); + const auto curr_day = ymd.day(); + + std::stringstream os; + const auto first_dow = first_day_of_week(); + weekdays_header(first_dow, os); + + // First week prefixed with spaces if needed. + auto wd = date::weekday(ym/1); + auto empty_days = (wd - first_dow).count(); + if (empty_days > 0) { + os << std::string(empty_days * 3 - 1, ' '); + } + auto last_day = (ym/date::literals::last).day(); + for (auto d = date::day(1); d <= last_day; ++d, ++wd) { + if (wd != first_dow) { + os << ' '; + } else if (unsigned(d) != 1) { + os << '\n'; + } + if (d == curr_day) { + os << "" << date::format("%e", d) << ""; + } else { + os << date::format("%e", d); + } + } + + auto result = os.str(); + cached_calendar_ymd_ = ymd; + cached_calendar_text_ = result; + return result; +} + +auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void { + auto wd = first_dow; + do { + if (wd != first_dow) os << ' '; + Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); + auto wd_len = wd_ustring.length(); + if (wd_len > 2) { + wd_ustring = wd_ustring.substr(0, 2); + wd_len = 2; + } + const std::string pad(2 - wd_len, ' '); + os << pad << wd_ustring; + } while (++wd != first_dow); + os << "\n"; +} + +#ifdef HAVE_LANGINFO_1STDAY +template +using deleter_from_fn = std::integral_constant; + +template +using deleting_unique_ptr = std::unique_ptr>; +#endif + +// Computations done similarly to Linux cal utility. +auto waybar::modules::Clock::first_day_of_week() -> date::weekday { +#ifdef HAVE_LANGINFO_1STDAY + deleting_unique_ptr::type, freelocale> + posix_locale{newlocale(LC_ALL, locale_.name().c_str(), nullptr)}; + if (posix_locale) { + const int i = (std::intptr_t) nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get()); + auto ymd = date::year(i / 10000)/(i / 100 % 100)/(i % 100); + auto wd = date::weekday(ymd); + uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get()); + return wd + date::days(j - 1); + } +#endif + return date::Sunday; +} + template <> struct fmt::formatter : fmt::formatter { template