diff --git a/.envrc.sample b/.envrc.sample new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc.sample @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index a6da7ef..550f945 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -9,7 +9,7 @@ jobs: # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners runs-on: macos-12 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e9f1656..d11d2cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: DoozyX/clang-format-lint-action@v0.13 with: source: '.' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4c77c3a..c82af85 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,16 +13,20 @@ jobs: - fedora - opensuse - gentoo + cpp_std: [c++17] + include: + - distro: fedora + cpp_std: c++20 runs-on: ubuntu-latest container: image: alexays/waybar:${{ matrix.distro }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: configure - run: meson -Dman-pages=enabled build + run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build - name: build run: ninja -C build - name: test - run: meson test -C build --no-rebuild --print-errorlogs --suite waybar + run: meson test -C build --no-rebuild --verbose --suite waybar diff --git a/.gitignore b/.gitignore index bf2bdbf..4d7babf 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ packagecache *.exe *.out *.app +/.direnv/ diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index e7cbba6..cab4146 100644 --- a/Dockerfiles/archlinux +++ b/Dockerfiles/archlinux @@ -3,4 +3,5 @@ FROM archlinux:base-devel RUN pacman -Syu --noconfirm && \ - pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl + pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl && \ + sed -Ei 's/#(en_(US|GB)\.UTF)/\1/' /etc/locale.gen && locale-gen diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora index e1abd44..5892159 100644 --- a/Dockerfiles/fedora +++ b/Dockerfiles/fedora @@ -2,12 +2,33 @@ FROM fedora:latest -RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ - 'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ - 'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ - 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ - 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ - 'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ - 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \ - 'pkgconfig(playerctl)' && \ +RUN dnf install -y @c-development \ + git-core glibc-langpack-en meson scdoc \ + 'pkgconfig(catch2)' \ + 'pkgconfig(date)' \ + 'pkgconfig(dbusmenu-gtk3-0.4)' \ + 'pkgconfig(fmt)' \ + 'pkgconfig(gdk-pixbuf-2.0)' \ + 'pkgconfig(gio-unix-2.0)' \ + 'pkgconfig(gtk-layer-shell-0)' \ + 'pkgconfig(gtkmm-3.0)' \ + 'pkgconfig(jack)' \ + 'pkgconfig(jsoncpp)' \ + 'pkgconfig(libevdev)' \ + 'pkgconfig(libinput)' \ + 'pkgconfig(libmpdclient)' \ + 'pkgconfig(libnl-3.0)' \ + 'pkgconfig(libnl-genl-3.0)' \ + 'pkgconfig(libpulse)' \ + 'pkgconfig(libudev)' \ + 'pkgconfig(playerctl)' \ + 'pkgconfig(pugixml)' \ + 'pkgconfig(sigc++-2.0)' \ + 'pkgconfig(spdlog)' \ + 'pkgconfig(upower-glib)' \ + 'pkgconfig(wayland-client)' \ + 'pkgconfig(wayland-cursor)' \ + 'pkgconfig(wayland-protocols)' \ + 'pkgconfig(wireplumber-0.4)' \ + 'pkgconfig(xkbregistry)' && \ dnf clean all -y diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6d3b6b8 --- /dev/null +++ b/flake.lock @@ -0,0 +1,94 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1667210711, + "narHash": "sha256-IoErjXZAkzYWHEpQqwu/DeRNJGFdR7X2OGbkhMqMrpw=", + "owner": "numtide", + "repo": "devshell", + "rev": "96a9dd12b8a447840cc246e17a47b81a4268bba7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1642700792, + "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1643381941, + "narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1670152712, + "narHash": "sha256-LJttwIvJqsZIj8u1LxVRv82vwUtkzVqQVi7Wb8gxPS4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "14ddeaebcbe9a25748221d1d7ecdf98e20e2325e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d909186 --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "Highly customizable Wayland bar for Sway and Wlroots based compositors."; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + devshell.url = "github:numtide/devshell"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, flake-utils, devshell, nixpkgs }: + let + inherit (nixpkgs) lib; + genSystems = lib.genAttrs [ + "x86_64-linux" + ]; + + pkgsFor = genSystems (system: + import nixpkgs { + inherit system; + }); + + mkDate = longDate: (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); + in + { + overlays.default = _: prev: rec { + waybar = prev.callPackage ./nix/default.nix { + version = "0.9.16" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + }; + }; + packages = genSystems + (system: + (self.overlays.default null pkgsFor.${system}) + // { + default = self.packages.${system}.waybar; + }); + } // + flake-utils.lib.eachDefaultSystem (system: { + devShell = + let pkgs = import nixpkgs { + inherit system; + + overlays = [ devshell.overlay ]; + }; + in + pkgs.devshell.mkShell { + imports = [ "${pkgs.devshell.extraModulesDir}/language/c.nix" ]; + commands = [ + { + package = pkgs.devshell.cli; + help = "Per project developer environments"; + } + ]; + devshell.packages = with pkgs; [ + clang-tools + gdb + ]; + language.c.libraries = with pkgs; [ + ]; + }; + }); +} diff --git a/include/AModule.hpp b/include/AModule.hpp index 357f70e..35625cd 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -15,6 +15,7 @@ class AModule : public IModule { bool enable_scroll = false); virtual ~AModule(); virtual auto update() -> void; + virtual auto refresh(int) -> void{}; virtual operator Gtk::Widget &(); Glib::Dispatcher dp; diff --git a/include/factory.hpp b/include/factory.hpp index f2ddf18..b2b242f 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -1,7 +1,7 @@ #pragma once #include -#ifdef HAVE_LIBDATE +#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE) #include "modules/clock.hpp" #else #include "modules/simpleclock.hpp" diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp index 81e2516..6597d39 100644 --- a/include/modules/backlight.hpp +++ b/include/modules/backlight.hpp @@ -6,6 +6,7 @@ #include #include "ALabel.hpp" +#include "giomm/dbusproxy.h" #include "util/json.hpp" #include "util/sleeper_thread.hpp" @@ -50,6 +51,8 @@ class Backlight : public ALabel { template static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev); + bool handleScroll(GdkEventScroll *e); + const std::string preferred_device_; static constexpr int EPOLL_MAX_EVENTS = 16; @@ -60,5 +63,7 @@ class Backlight : public ALabel { std::vector devices_; // thread must destruct before shared data util::SleeperThread udev_thread_; + + Glib::RefPtr login_proxy_; }; } // namespace waybar::modules diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index ef129fb..c318e8b 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -1,19 +1,22 @@ #pragma once -#include - #include "ALabel.hpp" +#include "util/date.hpp" #include "util/sleeper_thread.hpp" -namespace waybar { - -struct waybar_time; - -namespace modules { +namespace waybar::modules { const std::string kCalendarPlaceholder = "calendar"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; +enum class WeeksSide { + LEFT, + RIGHT, + HIDDEN, +}; + +enum class CldMode { MONTH, YEAR }; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -22,26 +25,37 @@ class Clock : public ALabel { private: util::SleeperThread thread_; + std::map, void (waybar::modules::Clock::*)()> eventMap_; std::locale locale_; std::vector time_zones_; int current_time_zone_idx_; - date::year_month_day calendar_cached_ymd_{date::January / 1 / 0}; - date::months calendar_shift_{0}, calendar_shift_init_{0}; - std::string calendar_cached_text_; bool is_calendar_in_tooltip_; bool is_timezoned_list_in_tooltip_; bool handleScroll(GdkEventScroll* e); + bool handleToggle(GdkEventButton* const& e); - std::string fmt_str_weeks_; - std::string fmt_str_calendar_; - int fmt_weeks_left_pad_{0}; - 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; const date::time_zone* current_timezone(); bool is_timezone_fixed(); auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; + + /*Calendar properties*/ + WeeksSide cldWPos_{WeeksSide::HIDDEN}; + std::map fmtMap_; + CldMode cldMode_{CldMode::MONTH}; + uint cldMonCols_{3}; // Count of the month in the row + int cldMonColLen_{20}; // Length of the month column + int cldWnLen_{3}; // Length of the week number + date::year_month_day cldYearShift_; + date::year_month cldMonShift_; + date::months cldCurrShift_{0}; + date::months cldShift_{0}; + std::string cldYearCached_{}; + std::string cldMonCached_{}; + /*Calendar functions*/ + auto get_calendar(const date::zoned_seconds& now, const date::zoned_seconds& wtime) + -> std::string; + void cldModeSwitch(); }; -} // namespace modules -} // namespace waybar +} // namespace waybar::modules diff --git a/include/modules/image.hpp b/include/modules/image.hpp index 06b61ee..b2c8946 100644 --- a/include/modules/image.hpp +++ b/include/modules/image.hpp @@ -7,6 +7,7 @@ #include #include "ALabel.hpp" +#include "gtkmm/box.h" #include "util/command.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" @@ -15,7 +16,7 @@ namespace waybar::modules { class Image : public AModule { public: - Image(const std::string&, const std::string&, const Json::Value&); + Image(const std::string&, const Json::Value&); auto update() -> void; void refresh(int /*signal*/); @@ -23,6 +24,7 @@ class Image : public AModule { void delayWorker(); void handleEvent(); + Gtk::Box box_; Gtk::Image image_; std::string path_; int size_; diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 77dab08..a6705ea 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "ipc.hpp" #include "util/sleeper_thread.hpp" diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index c13d5ce..e99e94f 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable { auto update() -> void; private: + void setClass(std::string classname, bool enable); void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); - std::tuple getFocusedNode( - const Json::Value& nodes, std::string& output); + std::tuple + getFocusedNode(const Json::Value& nodes, std::string& output); void getTree(); void updateAppIconName(); void updateAppIcon(); @@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable { int windowId_; std::string app_id_; std::string app_class_; + std::string layout_; std::string old_app_id_; std::size_t app_nb_; std::string shell_; unsigned app_icon_size_{24}; bool update_app_icon_{true}; std::string app_icon_name_; + int floating_count_; util::JsonParser parser_; std::mutex mutex_; Ipc ipc_; diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index e6df067..f8a55fa 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "AModule.hpp" @@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable { auto update() -> void; private: - static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view persistent_workspace_switch_cmd_ = + R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; static int convertWorkspaceNameToNum(std::string name); diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index c0ee7f0..fa988fc 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -20,15 +20,19 @@ class Wireplumber : public ALabel { void loadRequiredApiModules(); void prepare(); void activatePlugins(); - static void updateVolume(waybar::modules::Wireplumber* self); - static void updateNodeName(waybar::modules::Wireplumber* self); - static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self); + static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); + static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onObjectManagerInstalled(waybar::modules::Wireplumber* self); + static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id); + static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self); WpCore* wp_core_; GPtrArray* apis_; WpObjectManager* om_; + WpPlugin* mixer_api_; + WpPlugin* def_nodes_api_; + gchar* default_node_name_; uint32_t pending_plugins_; bool muted_; double volume_; diff --git a/include/util/date.hpp b/include/util/date.hpp new file mode 100644 index 0000000..380bb6e --- /dev/null +++ b/include/util/date.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#if HAVE_CHRONO_TIMEZONES +#include +#include + +/* Compatibility layer for on top of C++20 */ +namespace date { + +using namespace std::chrono; + +namespace literals { +using std::chrono::last; +} + +inline auto format(const std::string& spec, const auto& ztime) { + return spec.empty() ? "" : std::vformat("{:L" + spec + "}", std::make_format_args(ztime)); +} + +inline auto format(const std::locale& loc, const std::string& spec, const auto& ztime) { + return spec.empty() ? "" : std::vformat(loc, "{:L" + spec + "}", std::make_format_args(ztime)); +} + +} // namespace date + +#else +#include +#endif + +template +struct fmt::formatter> { + std::string_view specs; + + template + constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(); + if (it != ctx.end() && *it == ':') { + ++it; + } + auto end = it; + while (end != ctx.end() && *end != '}') { + ++end; + } + if (end != it) { + specs = {it, std::string_view::size_type(end - it)}; + } + return end; + } + + template + auto format(const date::zoned_time& ztime, FormatContext& ctx) { + if (ctx.locale()) { + const auto loc = ctx.locale().template get(); + return fmt::format_to(ctx.out(), "{}", date::format(loc, fmt::to_string(specs), ztime)); + } + return fmt::format_to(ctx.out(), "{}", date::format(fmt::to_string(specs), ztime)); + } +}; diff --git a/include/util/format.hpp b/include/util/format.hpp index fac0377..00b6a31 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -66,9 +66,9 @@ struct formatter { std::string string; switch (spec) { case '>': - return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); case '<': - return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); + return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); case '=': format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; break; @@ -77,8 +77,8 @@ struct formatter { format = "{coefficient:.1f}{prefix}{unit}"; break; } - return format_to( - ctx.out(), format, fmt::arg("coefficient", fraction), + return fmt::format_to( + ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction), fmt::arg("number_width", number_width), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("unit", s.unit_), diff --git a/include/util/waybar_time.hpp b/include/util/waybar_time.hpp deleted file mode 100644 index b9f9ea9..0000000 --- a/include/util/waybar_time.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -namespace waybar { - -struct waybar_time { - std::locale locale; - date::zoned_seconds ztime; -}; - -} // namespace waybar - -template <> -struct fmt::formatter { - std::string_view specs; - - template - constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') { - ++it; - } - auto end = it; - while (end != ctx.end() && *end != '}') { - ++end; - } - if (end != it) { - specs = {it, std::string_view::size_type(end - it)}; - } - return end; - } - - template - auto format(const waybar::waybar_time& t, FormatContext& ctx) { - return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime)); - } -}; diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index 9c8ba79..ca3d922 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -58,16 +58,25 @@ The *backlight* module displays the current backlight level. *on-scroll-up*: ++ typeof: string ++ - Command to execute when performing a scroll up on the module. + Command to execute when performing a scroll up on the module. This replaces the default behaviour of brightness control. *on-scroll-down*: ++ typeof: string - Command to execute when performing a scroll down on the module. + Command to execute when performing a scroll down on the module. This replaces the default behaviour of brightness control. *smooth-scrolling-threshold*: ++ typeof: double Threshold to be used when scrolling. +*reverse-scrolling*: ++ + typeof: bool ++ + Option to reverse the scroll direction. + +*scroll-step*: ++ + typeof: float ++ + default: 1.0 ++ + The speed in which to change the brightness when scrolling. + # EXAMPLE: ``` diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index feff9f6..09e871e 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -1,4 +1,4 @@ -waybar-custom(5) +waybar-image(5) # NAME @@ -10,12 +10,13 @@ The *image* module displays an image from a path. # CONFIGURATION -Addressed by *custom/* - *path*: ++ typeof: string ++ The path to the image. - +*exec*: ++ + typeof: string ++ + The path to the script, which should return image path file + it will only execute if the path is not set *size*: ++ typeof: integer ++ The width/height to render the image. @@ -58,15 +59,15 @@ Addressed by *custom/* # EXAMPLES -## Spotify: - -## mpd: - ``` -"image/album-art": { +"image#album-art": { "path": "/tmp/mpd_art", "size": 32, "interval": 5, "on-click": "mpc toggle" } -``` \ No newline at end of file +``` + +# STYLE + +- *#image* diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 6e5ebdb..19e0cd2 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -66,6 +66,25 @@ Addressed by *sway/window* default: true ++ Option to disable tooltip on hover. +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + Option to show the focused window along with its workspace styles on all outputs. + +*offscreen-css*: ++ + typeof: bool ++ + default: false ++ + Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style. + +*offscreen-css-text*: ++ + typeof: string ++ + Only effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspaces styles. + +*show-focused-workspace-name*: ++ + typeof: bool ++ + default: false ++ + If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied. + *rewrite*: ++ typeof: object ++ Rules to rewrite window title. See *rewrite rules*. @@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped. # STYLE - *#window* -- *window#waybar.empty* When no windows is in the workspace -- *window#waybar.solo* When one window is in the workspace +- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-css option is not set +- *window#waybar.solo* When one tiled window is in the workspace +- *window#waybar.floating* When there are only floating windows in the workspace +- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked +- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed +- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv - *window#waybar.* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 74407bd..9532b64 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -263,6 +263,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-custom(5)* - *waybar-disk(5)* - *waybar-idle-inhibitor(5)* +- *waybar-image(5)* - *waybar-keyboard-state(5)* - *waybar-memory(5)* - *waybar-mpd(5)* @@ -278,6 +279,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-sway-scratchpad(5)* - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* +- *waybar-wireplumber(5)* - *waybar-wlr-taskbar(5)* - *waybar-wlr-workspaces(5)* - *waybar-temperature(5)* diff --git a/meson.build b/meson.build index a4b4c7a..ad6541e 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.16', + version: '0.9.17', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ @@ -123,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) -tz_dep = dependency('date', - required: false, - default_options : [ 'use_system_tzdb=true' ], - modules : [ 'date::date', 'date::date-tz' ], - fallback: [ 'date', 'tz_dep' ]) + +cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ') +have_chrono_timezones = cpp_lib_chrono >= 201907 +if have_chrono_timezones + tz_dep = declare_dependency() +else + tz_dep = dependency('date', + required: false, + default_options : [ 'use_system_tzdb=true' ], + modules : [ 'date::date', 'date::date-tz' ], + fallback: [ 'date', 'tz_dep' ]) +endif prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') @@ -313,7 +320,10 @@ if get_option('rfkill').enabled() and is_linux ) endif -if tz_dep.found() +if have_chrono_timezones + add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp') + src_files += 'src/modules/clock.cpp' +elif tz_dep.found() add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') src_files += 'src/modules/clock.cpp' else @@ -396,6 +406,7 @@ if scdoc.found() 'waybar-disk.5.scd', 'waybar-gamemode.5.scd', 'waybar-idle-inhibitor.5.scd', + 'waybar-image.5.scd', 'waybar-keyboard-state.5.scd', 'waybar-memory.5.scd', 'waybar-mpd.5.scd', @@ -449,7 +460,7 @@ endif catch2 = dependency( 'catch2', - version: '>=3.0.0', + version: '>=2.0.0', fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..2665446 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,139 @@ +{ lib +, stdenv +, fetchFromGitHub +, meson +, pkg-config +, ninja +, wrapGAppsHook +, wayland +, wlroots +, gtkmm3 +, libsigcxx +, jsoncpp +, scdoc +, spdlog +, gtk-layer-shell +, howard-hinnant-date +, libinotify-kqueue +, libxkbcommon +, evdevSupport ? true +, libevdev +, inputSupport ? true +, libinput +, jackSupport ? true +, libjack2 +, mpdSupport ? true +, libmpdclient +, nlSupport ? true +, libnl +, pulseSupport ? true +, libpulseaudio +, rfkillSupport ? true +, runTests ? true +, catch2_3 +, sndioSupport ? true +, sndio +, swaySupport ? true +, sway +, traySupport ? true +, libdbusmenu-gtk3 +, udevSupport ? true +, udev +, upowerSupport ? true +, upower +, wireplumberSupport ? true +, wireplumber +, withMediaPlayer ? false +, glib +, gobject-introspection +, python3 +, playerctl +, version +}: + +stdenv.mkDerivation rec { + pname = "waybar"; + inherit version; + # version = "0.9.16"; + + src = lib.cleanSourceWith { + filter = name: type: + let + baseName = baseNameOf (toString name); + in + ! ( + lib.hasSuffix ".nix" baseName + ); + src = lib.cleanSource ../.; + }; + + nativeBuildInputs = [ + meson + ninja + pkg-config + scdoc + wrapGAppsHook + ] ++ lib.optional withMediaPlayer gobject-introspection; + + propagatedBuildInputs = lib.optionals withMediaPlayer [ + glib + playerctl + python3.pkgs.pygobject3 + ]; + strictDeps = false; + + buildInputs = with lib; + [ wayland wlroots gtkmm3 libsigcxx jsoncpp spdlog gtk-layer-shell howard-hinnant-date libxkbcommon ] + ++ optional (!stdenv.isLinux) libinotify-kqueue + ++ optional evdevSupport libevdev + ++ optional inputSupport libinput + ++ optional jackSupport libjack2 + ++ optional mpdSupport libmpdclient + ++ optional nlSupport libnl + ++ optional pulseSupport libpulseaudio + ++ optional sndioSupport sndio + ++ optional swaySupport sway + ++ optional traySupport libdbusmenu-gtk3 + ++ optional udevSupport udev + ++ optional upowerSupport upower + ++ optional wireplumberSupport wireplumber; + + checkInputs = [ catch2_3 ]; + doCheck = runTests; + + mesonFlags = (lib.mapAttrsToList + (option: enable: "-D${option}=${if enable then "enabled" else "disabled"}") + { + dbusmenu-gtk = traySupport; + jack = jackSupport; + libinput = inputSupport; + libnl = nlSupport; + libudev = udevSupport; + mpd = mpdSupport; + pulseaudio = pulseSupport; + rfkill = rfkillSupport; + sndio = sndioSupport; + tests = runTests; + upower_glib = upowerSupport; + wireplumber = wireplumberSupport; + } + ) ++ [ + "-Dsystemd=disabled" + "-Dgtk-layer-shell=enabled" + "-Dman-pages=enabled" + ]; + + preFixup = lib.optionalString withMediaPlayer '' + cp $src/resources/custom_modules/mediaplayer.py $out/bin/waybar-mediaplayer.py + wrapProgram $out/bin/waybar-mediaplayer.py \ + --prefix PYTHONPATH : "$PYTHONPATH:$out/${python3.sitePackages}" + ''; + + meta = with lib; { + description = "Highly customizable Wayland bar for Sway and Wlroots based compositors"; + license = licenses.mit; + maintainers = with maintainers; [ FlorianFranzen minijackson synthetica lovesegfault ]; + platforms = platforms.unix; + homepage = "https://github.com/alexays/waybar"; + }; +} diff --git a/src/bar.cpp b/src/bar.cpp index f46b7d0..62ff80c 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { void waybar::Bar::handleSignal(int signal) { for (auto& module : modules_all_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } + module->refresh(signal); } } diff --git a/src/factory.cpp b/src/factory.cpp index afe5c61..dd5c142 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -101,6 +101,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "disk") { return new waybar::modules::Disk(id, config_[name]); } + if (ref == "image") { + return new waybar::modules::Image(id, config_[name]); + } #ifdef HAVE_DBUSMENU if (ref == "tray") { return new waybar::modules::SNI::Tray(id, bar_, config_[name]); @@ -159,8 +162,6 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { } if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name]); - } else if (ref.compare(0, 6, "image/") == 0 && ref.size() > 6) { - return new waybar::modules::Image(ref.substr(6), id, config_[name]); } } catch (const std::exception& e) { auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); diff --git a/src/main.cpp b/src/main.cpp index e06774b..ff446ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,6 +85,12 @@ int main(int argc, char* argv[]) { waybar::Client::inst()->reset(); }); + std::signal(SIGINT, [](int /*signal*/) { + spdlog::info("Quitting."); + reload = false; + waybar::Client::inst()->reset(); + }); + for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { std::signal(sig, [](int sig) { for (auto& bar : waybar::Client::inst()->bars) { diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aa734a4..a46b296 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -48,13 +48,13 @@ struct UdevMonitorDeleter { void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") { if (rc == bad_rc) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa void check_gte(int rc, int gte, const char *message = "rc was: ") { if (rc < gte) { - throw std::runtime_error(fmt::format(message, rc)); + throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } @@ -106,6 +106,15 @@ waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value & dp.emit(); } + // Set up scroll handler + event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll)); + + // Connect to the login interface + login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( + Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", + "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); + udev_thread_ = [this] { std::unique_ptr udev{udev_new()}; check_nn(udev.get(), "Udev new failed"); @@ -181,7 +190,8 @@ auto waybar::modules::Backlight::update() -> void { event_box_.show(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); - label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), + label_.set_markup(fmt::format(fmt::runtime(format_), + fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); getState(percent); } else { @@ -263,3 +273,71 @@ void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt la upsert_device(first, last, inserter, dev.get()); } } + +bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { + // Check if the user has set a custom command for scrolling + if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { + return AModule::handleScroll(e); + } + + // Fail fast if the proxy could not be initialized + if (!login_proxy_) { + return true; + } + + // Check scroll direction + auto dir = AModule::getScrollDir(e); + if (dir == SCROLL_DIR::NONE) { + return true; + } + + if (config_["reverse-scrolling"].asBool()) { + if (dir == SCROLL_DIR::UP) { + dir = SCROLL_DIR::DOWN; + } else if (dir == SCROLL_DIR::DOWN) { + dir = SCROLL_DIR::UP; + } + } + + // Get scroll step + double step = 1; + + if (config_["scroll-step"].isDouble()) { + step = config_["scroll-step"].asDouble(); + } + + // Get the best device + decltype(devices_) devices; + { + std::scoped_lock lock(udev_thread_mutex_); + devices = devices_; + } + const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_); + + if (best == nullptr) { + return true; + } + + // Compute the absolute step + const auto abs_step = static_cast(round(step * best->get_max() / 100.0f)); + + // Compute the new value + int new_value = best->get_actual(); + + if (dir == SCROLL_DIR::UP) { + new_value += abs_step; + } else if (dir == SCROLL_DIR::DOWN) { + new_value -= abs_step; + } + + // Clamp the value + new_value = std::clamp(new_value, 0, best->get_max()); + + // Set the new value + auto call_args = Glib::VariantContainerBase( + g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value)); + + login_proxy_->call_sync("SetBrightness", call_args); + + return true; +} diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 97ff0a5..abd1240 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -505,12 +505,12 @@ const std::tuple waybar::modules::Battery::g float time_remaining{0.0f}; if (status == "Discharging" && time_to_empty_now_exists) { - if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f; + if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f; } else if (status == "Discharging" && total_power_exists && total_energy_exists) { if (total_power != 0) time_remaining = (float)total_energy / total_power; } else if (status == "Charging" && time_to_full_now_exists) { if (time_to_full_now_exists && (time_to_full_now != 0)) - time_remaining = -(float)time_to_full_now / 1000.0f; + time_remaining = -(float)time_to_full_now / 3600.0f; // If we've turned positive it means the battery is past 100% and so just report that as no // time remaining if (time_remaining > 0.0f) time_remaining = 0.0f; @@ -604,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai format = config_["format-time"].asString(); } std::string zero_pad_minutes = fmt::format("{:02d}", minutes); - return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), + return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes), fmt::arg("m", zero_pad_minutes)); } @@ -644,7 +644,8 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), + label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("timeTo", tooltip_text_default), fmt::arg("power", power), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted))); } @@ -665,9 +666,9 @@ auto waybar::modules::Battery::update() -> void { } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; - label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), - fmt::arg("icon", getIcon(capacity, icons)), - fmt::arg("time", time_remaining_formatted))); + label_.set_markup(fmt::format( + fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power), + fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted))); } // Call parent update ALabel::update(); diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index e6a1fe3..c3a2547 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void { state_ = state; label_.set_markup(fmt::format( - format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), + fmt::runtime(format_), fmt::arg("status", state_), + fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_alias", cur_controller_.alias), @@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void { enumerate_format = config_["tooltip-format-enumerate-connected"].asString(); } ss << fmt::format( - enumerate_format, fmt::arg("device_address", dev.address), + fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address), fmt::arg("device_address_type", dev.address_type), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); @@ -247,7 +248,7 @@ auto waybar::modules::Bluetooth::update() -> void { } } label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("status", state_), + fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 55f2c5b..414ee33 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -10,14 +10,11 @@ #include #include "util/ustring_clen.hpp" -#include "util/waybar_time.hpp" #ifdef HAVE_LANGINFO_1STDAY #include #include #endif -using waybar::waybar_time; - waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), current_time_zone_idx_(0), @@ -35,16 +32,10 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) time_zones_.push_back(date::locate_zone(config_["timezone"].asString())); } - // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark - // that local time should be shown. + // If all timezones are parsed and no one is good, add current time zone. nullptr in timezones + // vector means that local time should be shown if (!time_zones_.size()) { - time_zones_.push_back(nullptr); - } - - if (!is_timezone_fixed()) { - spdlog::warn( - "As using a timezone, some format args may be missing as the date library haven't got a " - "release since 2018."); + time_zones_.push_back(date::current_zone()); } // Check if a particular placeholder is present in the tooltip format, to know what to calculate @@ -62,39 +53,94 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) } } + // Calendar configuration if (is_calendar_in_tooltip_) { - if (config_["on-scroll"][kCalendarPlaceholder].isInt()) { - calendar_shift_init_ = - date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()}; + if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) { + if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") { + cldWPos_ = WeeksSide::LEFT; + } else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") { + cldWPos_ = WeeksSide::RIGHT; + } + } + if (config_[kCalendarPlaceholder]["format"]["months"].isString()) + fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()}); + else + fmtMap_.insert({0, "{}"}); + if (config_[kCalendarPlaceholder]["format"]["days"].isString()) + fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()}); + else + fmtMap_.insert({2, "{}"}); + if (config_[kCalendarPlaceholder]["format"]["weeks"].isString() && + cldWPos_ != WeeksSide::HIDDEN) { + fmtMap_.insert( + {4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(), + std::regex("\\{\\}"), + (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")}); + Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("]+>|\\{.*\\}"), "")}; + cldWnLen_ += tmp.size(); + } else { + if (cldWPos_ != WeeksSide::HIDDEN) + fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"}); + else + cldWnLen_ = 0; + } + if (config_[kCalendarPlaceholder]["format"]["weekdays"].isString()) + fmtMap_.insert({1, config_[kCalendarPlaceholder]["format"]["weekdays"].asString()}); + else + fmtMap_.insert({1, "{}"}); + if (config_[kCalendarPlaceholder]["format"]["today"].isString()) + fmtMap_.insert({3, config_[kCalendarPlaceholder]["format"]["today"].asString()}); + else + fmtMap_.insert({3, "{}"}); + if (config_[kCalendarPlaceholder]["mode"].isString()) { + const std::string cfgMode{(config_[kCalendarPlaceholder]["mode"].isString()) + ? config_[kCalendarPlaceholder]["mode"].asString() + : "month"}; + const std::map monthModes{{"month", CldMode::MONTH}, + {"year", CldMode::YEAR}}; + if (monthModes.find(cfgMode) != monthModes.end()) + cldMode_ = monthModes.at(cfgMode); + else + spdlog::warn( + "Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" " + "is using instead", + cfgMode); + } + if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) { + cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt(); + if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) { + cldMonCols_ = 3u; + spdlog::warn( + "Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, " + "12]. Value 3 is using instead", + cldMonCols_); + } + } else + cldMonCols_ = 1; + if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) { + cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()}; event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK); event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) { - calendar_shift_ = date::months{0}; + cldCurrShift_ = date::months{0}; return false; }); } + if (config_[kCalendarPlaceholder]["on-click-left"].isString()) { + if (config_[kCalendarPlaceholder]["on-click-left"].asString() == "mode") + eventMap_.insert({std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), + &waybar::modules::Clock::cldModeSwitch}); + } + if (config_[kCalendarPlaceholder]["on-click-right"].isString()) { + if (config_[kCalendarPlaceholder]["on-click-right"].asString() == "mode") + eventMap_.insert({std::make_pair(3, GdkEventType::GDK_BUTTON_PRESS), + &waybar::modules::Clock::cldModeSwitch}); + } } - if (config_["locale"].isString()) { + if (config_["locale"].isString()) locale_ = std::locale(config_["locale"].asString()); - } else { + else locale_ = std::locale(""); - } - - if (config_["format-calendar-weeks"].isString()) { - fmt_str_weeks_ = - std::regex_replace(config_["format-calendar-weeks"].asString(), std::regex("\\{\\}"), - (first_day_of_week() == date::Monday) ? "{:%V}" : "{:%U}"); - fmt_weeks_left_pad_ = - std::regex_replace(fmt_str_weeks_, std::regex("]+>|\\{.*\\}"), "").length(); - } else { - fmt_str_weeks_ = ""; - } - - if (config_["format-calendar"].isString()) { - fmt_str_calendar_ = config_["format-calendar"].asString(); - } else { - fmt_str_calendar_ = "{}"; - } thread_ = [this] { dp.emit(); @@ -116,24 +162,22 @@ bool waybar::modules::Clock::is_timezone_fixed() { } auto waybar::modules::Clock::update() -> void { - auto time_zone = current_timezone(); + const auto* time_zone = current_timezone(); auto now = std::chrono::system_clock::now(); - waybar_time wtime = {locale_, - date::make_zoned(time_zone, date::floor(now))}; + auto ztime = date::zoned_time{time_zone, date::floor(now)}; - auto shifted_date = date::year_month_day{date::floor(now)} + calendar_shift_; + auto shifted_date = date::year_month_day{date::floor(now)} + cldCurrShift_; auto now_shifted = date::sys_days{shifted_date} + (now - date::floor(now)); - waybar_time shifted_wtime = { - locale_, date::make_zoned(time_zone, date::floor(now_shifted))}; + auto shifted_ztime = date::zoned_time{time_zone, date::floor(now_shifted)}; - std::string text = ""; + std::string text{""}; if (!is_timezone_fixed()) { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - text = fmt::format(locale_, format_, localtime); + text = fmt::format(locale_, fmt::runtime(format_), localtime); } else { - text = fmt::format(format_, wtime); + text = fmt::format(locale_, fmt::runtime(format_), ztime); } label_.set_markup(text); @@ -142,13 +186,13 @@ auto waybar::modules::Clock::update() -> void { std::string calendar_lines{""}; std::string timezoned_time_lines{""}; if (is_calendar_in_tooltip_) { - calendar_lines = calendar_text(shifted_wtime); + calendar_lines = get_calendar(ztime, shifted_ztime); } if (is_timezoned_list_in_tooltip_) { timezoned_time_lines = timezones_text(&now); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(tooltip_format, shifted_wtime, + text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); label_.set_tooltip_markup(text); @@ -159,6 +203,21 @@ auto waybar::modules::Clock::update() -> void { ALabel::update(); } +bool waybar::modules::Clock::handleToggle(GdkEventButton* const& e) { + const std::map, void (waybar::modules::Clock::*)()>::const_iterator& + rec{eventMap_.find(std::pair(e->button, e->type))}; + + const auto callMethod{(rec != eventMap_.cend()) ? rec->second : nullptr}; + + if (callMethod) { + (this->*callMethod)(); + } else + return ALabel::handleToggle(e); + + update(); + return true; +} + bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { // defer to user commands if set if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -168,11 +227,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { auto dir = AModule::getScrollDir(e); // Shift calendar date - if (calendar_shift_init_.count() != 0) { + if (cldShift_.count() != 0) { if (dir == SCROLL_DIR::UP) - calendar_shift_ += calendar_shift_init_; + cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_; else - calendar_shift_ -= calendar_shift_init_; + cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_; } else { // Change time zone if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { @@ -196,126 +255,214 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { return true; } -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 (calendar_cached_ymd_ == ymd) { - return calendar_cached_text_; - } - - const auto curr_day{(calendar_shift_init_.count() != 0 && calendar_shift_.count() != 0) - ? date::day{0} - : ymd.day()}; - const date::year_month ym{ymd.year(), ymd.month()}; - const auto first_dow = first_day_of_week(); - - std::stringstream os; - - enum class WeeksSide { - LEFT, - RIGHT, - HIDDEN, - }; - WeeksSide weeks_pos = WeeksSide::HIDDEN; - - if (config_["calendar-weeks-pos"].isString()) { - if (config_["calendar-weeks-pos"].asString() == "left") { - weeks_pos = WeeksSide::LEFT; - // Add paddings before the header - os << std::string(3 + fmt_weeks_left_pad_, ' '); - } else if (config_["calendar-weeks-pos"].asString() == "right") { - weeks_pos = WeeksSide::RIGHT; - } - } - - weekdays_header(first_dow, os); - - // First week day prefixed with spaces if needed. - date::sys_days print_wd{ym / 1}; - auto wd{date::weekday{print_wd}}; - auto empty_days = (wd - first_dow).count(); - - /* Print weeknumber on the left for the first row*/ - if (weeks_pos == WeeksSide::LEFT) { - os << fmt::format(fmt_str_weeks_, print_wd) << ' '; - } - - if (empty_days > 0) { - os << std::string(empty_days * 3 - 1, ' '); - } - - const 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) { - if (weeks_pos == WeeksSide::RIGHT) { - os << ' ' << fmt::format(fmt_str_weeks_, print_wd); - } - - os << '\n'; - - print_wd = (ym / d); - - if (weeks_pos == WeeksSide::LEFT) { - os << fmt::format(fmt_str_weeks_, print_wd) << ' '; - } - } - - if (d == curr_day) { - if (config_["today-format"].isString()) { - auto today_format = config_["today-format"].asString(); - os << fmt::format(today_format, date::format("%e", d)); - } else { - os << "" << date::format("%e", d) << ""; - } - } else { - os << fmt::format(fmt_str_calendar_, date::format("%e", d)); - } - /*Print weeks on the right when the endings with spaces*/ - if (weeks_pos == WeeksSide::RIGHT && d == last_day) { - empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding()); - if (empty_days > 0 && empty_days < 7) { - os << std::string(empty_days * 3, ' '); - } - - os << ' ' << fmt::format(fmt_str_weeks_, print_wd); - } - } - - auto result = os.str(); - calendar_cached_ymd_ = ymd; - calendar_cached_text_ = result; - return result; +// The number of weeks in calendar month layout plus 1 more for calendar titles +unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) { + using namespace date; + return static_cast( + ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) + + 2; } -auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os) - -> void { - std::stringstream res; - auto wd = first_week_day; - do { - if (wd != first_week_day) { - res << ' '; - } - Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); - auto clen = ustring_clen(wd_ustring); - auto wd_len = wd_ustring.length(); - while (clen > 2) { - wd_ustring = wd_ustring.substr(0, wd_len - 1); - wd_len--; - clen = ustring_clen(wd_ustring); - } - const std::string pad(2 - clen, ' '); - res << pad << wd_ustring; - } while (++wd != first_week_day); - res << '\n'; +auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line) + -> const date::year_month_weekday { + unsigned index = line - 2; + auto sd = date::sys_days{ym / 1}; + if (date::weekday{sd} == firstdow) ++index; + auto ymdw = ym / firstdow[index]; + return ymdw; +} - if (config_["format-calendar-weekdays"].isString()) { - os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str()); - } else - os << res.str(); +auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym, + unsigned const line, date::weekday const firstdow, + const std::locale* const locale_) -> std::string { + using namespace date::literals; + std::ostringstream res; + + switch (line) { + case 0: { + // Output month and year title + res << date::format(*locale_, "%B %Y", ym); + break; + } + case 1: { + // Output weekday names title + auto wd{firstdow}; + do { + Glib::ustring wd_ustring{date::format(*locale_, "%a", wd)}; + auto clen{ustring_clen(wd_ustring)}; + auto wd_len{wd_ustring.length()}; + while (clen > 2) { + wd_ustring = wd_ustring.substr(0, wd_len - 1); + --wd_len; + clen = ustring_clen(wd_ustring); + } + const std::string pad(2 - clen, ' '); + + if (wd != firstdow) res << ' '; + + res << pad << wd_ustring; + } while (++wd != firstdow); + break; + } + case 2: { + // Output first week prefixed with spaces if necessary + auto wd = date::weekday{ym / 1}; + res << std::string(static_cast((wd - firstdow).count()) * 3, ' '); + + if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / 1_d) + res << date::format("%e", 1_d); + else + res << "{today}"; + + auto d = 2_d; + + while (++wd != firstdow) { + if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d) + res << date::format(" %e", d); + else + res << " {today}"; + + ++d; + } + break; + } + default: { + // Output a non-first week: + auto ymdw{cldGetWeekForLine(ym, firstdow, line)}; + if (ymdw.ok()) { + auto d = date::year_month_day{ymdw}.day(); + auto const e = (ym / last).day(); + auto wd = firstdow; + + if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d) + res << date::format("%e", d); + else + res << "{today}"; + + while (++wd != firstdow && ++d <= e) { + if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d) + res << date::format(" %e", d); + else + res << " {today}"; + } + // Append row with spaces if the week did not complete + res << std::string(static_cast((firstdow - wd).count()) * 3, ' '); + } + break; + } + } + + return res.str(); +} + +auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now, + const date::zoned_seconds& wtime) -> std::string { + auto daypoint = date::floor(wtime.get_local_time()); + const auto ymd{date::year_month_day{daypoint}}; + const auto ym{ymd.year() / ymd.month()}; + const auto y{ymd.year()}; + const auto firstdow = first_day_of_week(); + const auto maxRows{12 / cldMonCols_}; + std::ostringstream os; + std::ostringstream tmp; + // get currdate + daypoint = date::floor(now.get_local_time()); + const auto currDate{date::year_month_day{daypoint}}; + + if (cldMode_ == CldMode::YEAR) { + if (y / date::month{1} / 1 == cldYearShift_) + return cldYearCached_; + else + cldYearShift_ = y / date::month{1} / 1; + } + if (cldMode_ == CldMode::MONTH) { + if (ym == cldMonShift_) + return cldMonCached_; + else + cldMonShift_ = ym; + } + + // Compute number of lines needed for each calendar month + unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + for (auto& m : ml) { + if (cldMode_ == CldMode::YEAR || m == static_cast(ymd.month())) + m = cldRowsInMonth(y / date::month{m}, firstdow); + else + m = 0u; + } + for (auto row{0u}; row < maxRows; ++row) { + const auto lines = *std::max_element(std::begin(ml) + (row * cldMonCols_), + std::begin(ml) + ((row + 1) * cldMonCols_)); + for (auto line{0u}; line < lines; ++line) { + for (auto col{0u}; col < cldMonCols_; ++col) { + const auto mon{date::month{row * cldMonCols_ + col + 1}}; + if (cldMode_ == CldMode::YEAR || y / mon == ym) { + date::year_month ymTmp{y / mon}; + if (col != 0 && cldMode_ == CldMode::YEAR) os << " "; + + // Week numbers on the left + if (cldWPos_ == WeeksSide::LEFT && line > 0) { + if (line > 1) { + if (line < ml[static_cast(ymTmp.month()) - 1u]) + os << fmt::format(fmt::runtime(fmtMap_[4]), + (line == 2) + ? date::sys_days{ymTmp / 1} + : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)}) + << ' '; + else + os << std::string(cldWnLen_, ' '); + } + } + + os << fmt::format( + fmt::runtime((cldWPos_ != WeeksSide::LEFT || line == 0) ? "{:<{}}" : "{:>{}}"), + getCalendarLine(currDate, ymTmp, line, firstdow, &locale_), + (cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0))); + + // Week numbers on the right + if (cldWPos_ == WeeksSide ::RIGHT && line > 0) { + if (line > 1) { + if (line < ml[static_cast(ymTmp.month()) - 1u]) + os << ' ' + << fmt::format(fmt::runtime(fmtMap_[4]), + (line == 2) + ? date::sys_days{ymTmp / 1} + : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)}); + else + os << std::string(cldWnLen_, ' '); + } + } + } + } + + // Apply user formats to calendar + if (line < 2) + tmp << fmt::format(fmt::runtime(fmtMap_[line]), os.str()); + else + tmp << os.str(); + // Clear ostringstream + std::ostringstream().swap(os); + if (line + 1u != lines || (row + 1u != maxRows && cldMode_ == CldMode::YEAR)) tmp << '\n'; + } + if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\n'; + } + + os << fmt::format( // Apply days format + fmt::runtime(fmt::format(fmt::runtime(fmtMap_[2]), tmp.str())), + // Apply today format + fmt::arg("today", fmt::format(fmt::runtime(fmtMap_[3]), date::format("%e", ymd.day())))); + + if (cldMode_ == CldMode::YEAR) + cldYearCached_ = os.str(); + else + cldMonCached_ = os.str(); + + return os.str(); +} + +void waybar::modules::Clock::cldModeSwitch() { + cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR; } auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now) @@ -324,7 +471,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin return ""; } std::stringstream os; - waybar_time wtime; for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) { if (static_cast(time_zone_idx) == current_time_zone_idx_) { continue; @@ -333,8 +479,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin if (!timezone) { timezone = date::current_zone(); } - wtime = {locale_, date::make_zoned(timezone, date::floor(*now))}; - os << fmt::format(format_, wtime) << '\n'; + auto ztime = date::zoned_time{timezone, date::floor(*now)}; + os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n'; } return os.str(); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 23dba38..b7e1d2d 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -126,7 +126,7 @@ auto waybar::modules::Custom::update() -> void { } else { parseOutputRaw(); } - auto str = fmt::format(format_, text_, fmt::arg("alt", alt_), + auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); if (str.empty()) { @@ -209,8 +209,8 @@ void waybar::modules::Custom::parseOutputJson() { class_.push_back(c.asString()); } } - if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { - percentage_ = parsed["percentage"].asUInt(); + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { + percentage_ = (int)lround(parsed["percentage"].asFloat()); } else { percentage_ = 0; } diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 5578dc2..eb4d902 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup( - fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_markup(fmt::format( + fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } if (tooltipEnabled()) { @@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text( - fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), - fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), - fmt::arg("total", total), fmt::arg("path", path_))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), + fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), + fmt::arg("path", path_))); } // Call parent update ALabel::update(); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 7129297..1b8d7fc 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -213,14 +213,14 @@ auto Gamemode::update() -> void { // Tooltip if (tooltip) { - std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount)); + std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); box_.set_tooltip_text(text); } // Label format - std::string str = - fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), - fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); + std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("glyph", useIcon ? "" : glyph), + fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); label_.set_markup(str); if (useIcon) { diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 6cadd62..622c28d 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -49,23 +49,23 @@ auto Language::update() -> void { void Language::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); - auto kbName = ev.substr(ev.find_last_of('>') + 1, ev.find_first_of(',')); + std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); auto layoutName = ev.substr(ev.find_first_of(',') + 1); if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore + layoutName = waybar::util::sanitize_string(layoutName); + const auto briefName = getShortFrom(layoutName); if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, layoutName); + layoutName = fmt::format(fmt::runtime(format_), layoutName); } - layoutName = waybar::util::sanitize_string(layoutName); - if (layoutName == layoutName_) return; layoutName_ = layoutName; @@ -87,18 +87,18 @@ void Language::initLanguage() { searcher = searcher.substr(searcher.find("keymap:") + 8); searcher = searcher.substr(0, searcher.find_first_of("\n\t")); + searcher = waybar::util::sanitize_string(searcher); + auto layoutName = std::string{}; const auto briefName = getShortFrom(searcher); if (config_.isMember("format-" + briefName)) { const auto propName = "format-" + briefName; - layoutName = fmt::format(format_, config_[propName].asString()); + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); } else { - layoutName = fmt::format(format_, searcher); + layoutName = fmt::format(fmt::runtime(format_), searcher); } - layoutName = waybar::util::sanitize_string(layoutName); - layoutName_ = layoutName; spdlog::debug("hyprland language initLanguage found {}", layoutName_); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 6eb0942..22acbf3 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -19,6 +19,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) // register for hyprland ipc gIPC->registerForIPC("submap", this); + dp.emit(); } Submap::~Submap() { @@ -33,7 +34,7 @@ auto Submap::update() -> void { if (submap_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, submap_)); + label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); if (tooltipEnabled()) { label_.set_tooltip_text(submap_); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index d3d06cc..47daae9 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -40,8 +40,8 @@ auto Window::update() -> void { if (!format_.empty()) { label_.show(); - label_.set_markup( - fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); + label_.set_markup(fmt::format(fmt::runtime(format_), + waybar::util::rewriteTitle(lastView, config_["rewrite"]))); } else { label_.hide(); } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index c4109b0..a5fc9ac 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -63,21 +63,15 @@ auto waybar::modules::IdleInhibitor::update() -> void { } std::string status_text = status ? "activated" : "deactivated"; - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_markup( - status ? fmt::format(config_["tooltip-format-activated"].isString() - ? config_["tooltip-format-activated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text))) - : fmt::format(config_["tooltip-format-deactivated"].isString() - ? config_["tooltip-format-deactivated"].asString() - : "{status}", - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text)))); + auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"]; + auto tooltip_format = config.isString() ? config.asString() : "{status}"; + label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); } // Call parent update ALabel::update(); diff --git a/src/modules/image.cpp b/src/modules/image.cpp index eed19ae..a938617 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -1,15 +1,16 @@ #include "modules/image.hpp" -#include - -waybar::modules::Image::Image(const std::string& name, const std::string& id, - const Json::Value& config) - : AModule(config, "image-" + name, id, "{}") { - event_box_.add(image_); +waybar::modules::Image::Image(const std::string& id, const Json::Value& config) + : AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { + box_.pack_start(image_); + box_.set_name("image"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + event_box_.add(box_); dp.emit(); - path_ = config["path"].asString(); size_ = config["size"].asInt(); interval_ = config_["interval"].asInt(); @@ -40,8 +41,17 @@ void waybar::modules::Image::refresh(int sig) { } auto waybar::modules::Image::update() -> void { - Glib::RefPtr pixbuf; + util::command::res output_; + Glib::RefPtr pixbuf; + if (config_["path"].isString()) { + path_ = config_["path"].asString(); + } else if (config_["exec"].isString()) { + output_ = util::command::exec(config_["exec"].asString()); + path_ = output_.out; + } else { + path_ = ""; + } if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_); else diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index e4340b1..fe2a4be 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -118,7 +118,7 @@ auto Inhibitor::update() -> void { std::string status_text = activated() ? "activated" : "deactivated"; label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated"); - label_.set_markup(fmt::format(format_, fmt::arg("status", status_text), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 3a92110..9bd6fcd 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -72,7 +72,7 @@ auto JACK::update() -> void { } else format = "{load}%"; - label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); @@ -81,9 +81,9 @@ auto JACK::update() -> void { std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), - fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), - fmt::arg("xruns", xruns_))); + fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), + fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); } // Call parent update diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index b2750b6..4c081d6 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void { }; for (auto& label_state : label_states) { std::string text; - text = fmt::format(label_state.format, + text = fmt::format(fmt::runtime(label_state.format), fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_), fmt::arg("name", label_state.name)); label_state.label.set_markup(text); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 4a0e634..544d781 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void { event_box_.show(); auto icons = std::vector{state}; label_.set_markup(fmt::format( - format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), + fmt::runtime(format), used_ram_percentage, + fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), @@ -68,8 +69,8 @@ auto waybar::modules::Memory::update() -> void { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format( - tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), - fmt::arg("swapTotal", total_swap_gigabytes), + fmt::runtime(tooltip_format), used_ram_percentage, + fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 401b759..e728897 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -174,14 +174,14 @@ void waybar::modules::MPD::setLabel() { try { auto text = fmt::format( - format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), - fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), - fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), - fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), - fmt::arg("filename", filename)); + fmt::runtime(format), fmt::arg("artist", artist.raw()), + fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), + fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)); if (text.empty()) { label_.hide(); } else { @@ -198,7 +198,7 @@ void waybar::modules::MPD::setLabel() { : "MPD (connected)"; try { auto tooltip_text = - fmt::format(tooltip_format, fmt::arg("artist", artist.raw()), + fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 651dfd5..f11821f 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -378,10 +378,10 @@ auto Mpris::update() -> void { break; } auto label_format = - fmt::format(formatstr, fmt::arg("player", info.name), fmt::arg("status", info.status_string), - fmt::arg("artist", *info.artist), fmt::arg("title", *info.title), - fmt::arg("album", *info.album), fmt::arg("length", *info.length), - fmt::arg("dynamic", dynamic.str()), + fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name), + fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist), + fmt::arg("title", *info.title), fmt::arg("album", *info.album), + fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); label_.set_markup(label_format); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a4797ee..8409311 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void { getState(signal_strength_); auto text = fmt::format( - format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), + fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), @@ -363,8 +363,8 @@ auto waybar::modules::Network::update() -> void { } if (!tooltip_format.empty()) { auto tooltip_text = fmt::format( - tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), - fmt::arg("signalStrength", signal_strength_), + fmt::runtime(tooltip_format), fmt::arg("essid", essid_), + fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index c797997..0630710 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void { format_source = config_["format-source"].asString(); } } - format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); + format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_)); auto text = fmt::format( - format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))); if (text.empty()) { @@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void { } if (!tooltip_format.empty()) { label_.set_tooltip_text(fmt::format( - tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), + fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 4a51c83..1f788e0 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) { } label_.get_style_context()->add_class(mode); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw())); label_.show(); } diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index d0f492f..d93938c 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) { label_.hide(); // hide empty labels or labels with empty format } else { label_.show(); - label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw())); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw())); } ALabel::update(); diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index e6f1bd0..72e7207 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -110,7 +110,8 @@ auto Sndio::update() -> void { label_.get_style_context()->remove_class("muted"); } - auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); + auto text = + fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); if (text.empty()) { label_.hide(); } else { diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 4d6495c..5c3df7b 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -2,6 +2,8 @@ #include +#include + namespace waybar::modules::sway { Ipc::Ipc() { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index d3730a1..a5860bd 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { auto Language::update() -> void { std::lock_guard lock(mutex_); auto display_layout = trim(fmt::format( - format_, fmt::arg("short", layout_.short_name), + fmt::runtime(format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim( - fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name), + fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index 7eaa523..b81735e 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -42,7 +42,7 @@ auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, mode_)); + label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); if (tooltipEnabled()) { label_.set_tooltip_text(mode_); } diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp index 59e3053..17dc270 100644 --- a/src/modules/sway/scratchpad.cpp +++ b/src/modules/sway/scratchpad.cpp @@ -32,7 +32,8 @@ auto Scratchpad::update() -> void { if (count_ || show_empty_) { event_box_.show(); label_.set_markup( - fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), + fmt::format(fmt::runtime(format_), + fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), fmt::arg("count", count_))); if (tooltip_enabled_) { label_.set_tooltip_markup(tooltip_text_); @@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void { if (tooltip_enabled_) { tooltip_text_.clear(); for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { - tooltip_text_.append(fmt::format(tooltip_format_ + '\n', + tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'), fmt::arg("app", window["app_id"].asString()), fmt::arg("title", window["name"].asString()))); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 5da7d3d..7d60d29 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -17,7 +17,7 @@ namespace waybar::modules::sway { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) - : AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) { + : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { // Icon size if (config_["icon-size"].isUInt()) { app_icon_size_ = config["icon-size"].asUInt(); @@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::Window exception"); } }); } @@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); auto output = payload["output"].isString() ? payload["output"].asString() : ""; - std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) = + std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) = getFocusedNode(payload["nodes"], output); updateAppIconName(); dp.emit(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::onCmd exception"); } } @@ -156,30 +158,56 @@ void Window::updateAppIcon() { } auto Window::update() -> void { - if (!old_app_id_.empty()) { - bar_.window.get_style_context()->remove_class(old_app_id_); - } + spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_, + floating_count_); + + int mode = 0; if (app_nb_ == 0) { - bar_.window.get_style_context()->remove_class("solo"); - if (!bar_.window.get_style_context()->has_class("empty")) { - bar_.window.get_style_context()->add_class("empty"); + if (floating_count_ == 0) { + mode += 1; + } else { + mode += 4; } } else if (app_nb_ == 1) { - bar_.window.get_style_context()->remove_class("empty"); - if (!bar_.window.get_style_context()->has_class("solo")) { - bar_.window.get_style_context()->add_class("solo"); + mode += 2; + } else { + if (layout_ == "tabbed") { + mode += 8; + } else if (layout_ == "stacked") { + mode += 16; + } else { + mode += 32; } if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { bar_.window.get_style_context()->add_class(app_id_); old_app_id_ = app_id_; } - } else { - bar_.window.get_style_context()->remove_class("solo"); - bar_.window.get_style_context()->remove_class("empty"); } - label_.set_markup(fmt::format( - format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), - fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); + + if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) && + bar_.window.get_style_context()->has_class(old_app_id_)) { + spdlog::trace("Removing app_id class: {}", old_app_id_); + bar_.window.get_style_context()->remove_class(old_app_id_); + old_app_id_ = ""; + } + + setClass("empty", ((mode & 1) > 0)); + setClass("solo", ((mode & 2) > 0)); + setClass("floating", ((mode & 4) > 0)); + setClass("tabbed", ((mode & 8) > 0)); + setClass("stacked", ((mode & 16) > 0)); + setClass("tiled", ((mode & 32) > 0)); + + if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { + spdlog::trace("Adding app_id class: {}", app_id_); + bar_.window.get_style_context()->add_class(app_id_); + old_app_id_ = app_id_; + } + + label_.set_markup( + fmt::format(fmt::runtime(format_), + fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), + fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); if (tooltipEnabled()) { label_.set_tooltip_text(window_); } @@ -190,71 +218,143 @@ auto Window::update() -> void { AIconLabel::update(); } -int leafNodesInWorkspace(const Json::Value& node) { +void Window::setClass(std::string classname, bool enable) { + if (enable) { + if (!bar_.window.get_style_context()->has_class(classname)) { + bar_.window.get_style_context()->add_class(classname); + } + } else { + bar_.window.get_style_context()->remove_class(classname); + } +} + +std::pair leafNodesInWorkspace(const Json::Value& node) { auto const& nodes = node["nodes"]; auto const& floating_nodes = node["floating_nodes"]; if (nodes.empty() && floating_nodes.empty()) { - if (node["type"] == "workspace") - return 0; - else - return 1; + if (node["type"].asString() == "workspace") + return {0, 0}; + else if (node["type"].asString() == "floating_con") { + return {0, 1}; + } else { + return {1, 0}; + } } int sum = 0; - if (!nodes.empty()) { - for (auto const& node : nodes) sum += leafNodesInWorkspace(node); - } - if (!floating_nodes.empty()) { - for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node); - } - return sum; -} - -std::tuple gfnWithWorkspace( - const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_, - Json::Value& parentWorkspace) { + int floating_sum = 0; for (auto const& node : nodes) { - if (node["output"].isString()) { - output = node["output"].asString(); - } - // found node - if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) { - if ((!config_["all-outputs"].asBool() && output == bar_.output->name) || - config_["all-outputs"].asBool()) { - auto app_id = node["app_id"].isString() ? node["app_id"].asString() - : node["window_properties"]["instance"].asString(); - const auto app_class = node["window_properties"]["class"].isString() - ? node["window_properties"]["class"].asString() - : ""; - - const auto shell = node["shell"].isString() ? node["shell"].asString() : ""; - - int nb = node.size(); - if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace); - return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()), - app_id, app_class, shell}; - } - } - // iterate - if (node["type"] == "workspace") parentWorkspace = node; - auto [nb, id, name, app_id, app_class, shell] = - gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace); - if (id > -1 && !name.empty()) { - return {nb, id, name, app_id, app_class, shell}; - } - // Search for floating node - std::tie(nb, id, name, app_id, app_class, shell) = - gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace); - if (id > -1 && !name.empty()) { - return {nb, id, name, app_id, app_class, shell}; - } + std::pair all_leaf_nodes = leafNodesInWorkspace(node); + sum += all_leaf_nodes.first; + floating_sum += all_leaf_nodes.second; } - return {0, -1, "", "", "", ""}; + for (auto const& node : floating_nodes) { + std::pair all_leaf_nodes = leafNodesInWorkspace(node); + sum += all_leaf_nodes.first; + floating_sum += all_leaf_nodes.second; + } + return {sum, floating_sum}; } -std::tuple +std::tuple +gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_, + const Bar& bar_, Json::Value& parentWorkspace, + const Json::Value& immediateParent) { + for (auto const& node : nodes) { + if (node["type"].asString() == "output") { + if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) && + (node["name"].asString() != bar_.output->name)) { + continue; + } + output = node["name"].asString(); + } else if (node["type"].asString() == "workspace") { + // needs to be a string comparison, because filterWorkspace is the current_workspace + if (node["name"].asString() != immediateParent["current_workspace"].asString()) { + continue; + } + if (node["focused"].asBool()) { + std::pair all_leaf_nodes = leafNodesInWorkspace(node); + return {all_leaf_nodes.first, + all_leaf_nodes.second, + node["id"].asInt(), + (((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) && + (config_["show-focused-workspace-name"].asBool())) + ? node["name"].asString() + : "", + "", + "", + "", + node["layout"].asString()}; + } + parentWorkspace = node; + } else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && + (node["focused"].asBool())) { + // found node + spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name, + output, node["name"].asString()); + auto app_id = node["app_id"].isString() ? node["app_id"].asString() + : node["window_properties"]["instance"].asString(); + const auto app_class = node["window_properties"]["class"].isString() + ? node["window_properties"]["class"].asString() + : ""; + const auto shell = node["shell"].isString() ? node["shell"].asString() : ""; + int nb = node.size(); + int floating_count = 0; + std::string workspace_layout = ""; + if (!parentWorkspace.isNull()) { + std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace); + nb = all_leaf_nodes.first; + floating_count = all_leaf_nodes.second; + workspace_layout = parentWorkspace["layout"].asString(); + } + return {nb, + floating_count, + node["id"].asInt(), + Glib::Markup::escape_text(node["name"].asString()), + app_id, + app_class, + shell, + workspace_layout}; + } + + // iterate + auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] = + gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node); + auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] = + gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node); + + // if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) { + if ((id > 0) || (id2 < 0 && id > -1)) { + return {nb, f, id, name, app_id, app_class, shell, workspace_layout}; + } else if (id2 > 0 && !name2.empty()) { + return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2}; + } + } + + // this only comes into effect when no focused children are present + if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() && + immediateParent["type"].asString() == "workspace") { + std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent); + // using an empty string as default ensures that no window depending styles are set due to the + // checks above for !name.empty() + return {all_leaf_nodes.first, + all_leaf_nodes.second, + 0, + (all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0) + ? config_["offscreen-css-text"].asString() + : "", + "", + "", + "", + immediateParent["layout"].asString()}; + } + + return {0, 0, -1, "", "", "", "", ""}; +} + +std::tuple Window::getFocusedNode(const Json::Value& nodes, std::string& output) { - Json::Value placeholder = 0; - return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); + Json::Value placeholder = Json::Value::null; + return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder); } void Window::getTree() { @@ -262,6 +362,7 @@ void Window::getTree() { ipc_.sendCmd(IPC_GET_TREE); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); + spdlog::trace("Window::getTree exception"); } } diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b621b83..08742ae 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -233,7 +233,7 @@ auto Workspaces::update() -> void { std::string output = (*it)["name"].asString(); if (config_["format"].isString()) { auto format = config_["format"].asString(); - output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)), + output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString())); } @@ -259,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { try { if (node["target_output"].isString()) { ipc_.sendCmd(IPC_COMMAND, - fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + - workspace_switch_cmd_, - "--no-auto-back-and-forth", node["name"].asString(), - node["target_output"].asString(), "--no-auto-back-and-forth", - node["name"].asString())); + fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth", + node["name"].asString(), node["target_output"].asString(), + "--no-auto-back-and-forth", node["name"].asString())); } else { ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", config_["disable-auto-back-and-forth"].asBool() diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index eca05a7..ff722d7 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void { } auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; - label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); @@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } // Call parent update ALabel::update(); diff --git a/src/modules/upower/upower.cpp b/src/modules/upower/upower.cpp index eb29913..38c1f7f 100644 --- a/src/modules/upower/upower.cpp +++ b/src/modules/upower/upower.cpp @@ -336,8 +336,8 @@ auto UPower::update() -> void { break; } std::string label_format = - fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString), - fmt::arg("time", time_format)); + fmt::format(fmt::runtime(showAltText ? format_alt : format), + fmt::arg("percentage", percentString), fmt::arg("time", time_format)); // Only set the label text if it doesn't only contain spaces bool onlySpaces = true; for (auto& character : label_format) { diff --git a/src/modules/upower/upower_tooltip.cpp b/src/modules/upower/upower_tooltip.cpp index bec55c7..7dd5d10 100644 --- a/src/modules/upower/upower_tooltip.cpp +++ b/src/modules/upower/upower_tooltip.cpp @@ -29,7 +29,7 @@ UPowerTooltip::~UPowerTooltip() {} uint UPowerTooltip::updateTooltip(Devices& devices) { // Removes all old devices for (auto child : contentBox->get_children()) { - child->~Widget(); + delete child; } uint deviceCount = 0; diff --git a/src/modules/user.cpp b/src/modules/user.cpp index 2f7c6e9..418fc58 100644 --- a/src/modules/user.cpp +++ b/src/modules/user.cpp @@ -127,16 +127,16 @@ auto User::update() -> void { auto startSystemTime = currentSystemTime - workSystemTimeSeconds; long workSystemDays = uptimeSeconds / 86400; - auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), - fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), - fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), - fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), - fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), - fmt::arg("work_d", workSystemDays), - fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), - fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), - fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), - fmt::arg("user", systemUser)); + auto label = fmt::format( + fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), + fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), + fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), + fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), + fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays), + fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), + fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), + fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), + fmt::arg("user", systemUser)); ALabel::label_.set_markup(label); AIconLabel::update(); } diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 9a12a9b..4c7a2d0 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -1,15 +1,22 @@ #include "modules/wireplumber.hpp" +#include + +bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } + waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) : ALabel(config, "wireplumber", id, "{volume}%"), wp_core_(nullptr), apis_(nullptr), om_(nullptr), + mixer_api_(nullptr), + def_nodes_api_(nullptr), + default_node_name_(nullptr), pending_plugins_(0), muted_(false), volume_(0.0), node_id_(0) { - wp_init(WP_INIT_ALL); + wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(NULL, NULL); apis_ = g_ptr_array_new_with_free_func(g_object_unref); om_ = wp_object_manager_new(); @@ -18,10 +25,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val loadRequiredApiModules(); + spdlog::debug("[{}]: connecting to pipewire...", this->name_); + if (!wp_core_connect(wp_core_)) { + spdlog::error("[{}]: Could not connect to PipeWire", this->name_); throw std::runtime_error("Could not connect to PipeWire\n"); } + spdlog::debug("[{}]: connected!", this->name_); + g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); activatePlugins(); @@ -33,33 +45,26 @@ waybar::modules::Wireplumber::~Wireplumber() { g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_object(&om_); g_clear_object(&wp_core_); + g_clear_object(&mixer_api_); + g_clear_object(&def_nodes_api_); + g_free(default_node_name_); } -uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplumber* self) { - uint32_t id; - g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api"); +void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id); - if (!def_nodes_api) { - throw std::runtime_error("Default nodes API is not loaded\n"); + if (!isValidNodeId(id)) { + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id); + return; } - g_signal_emit_by_name(def_nodes_api, "get-default-node", "Audio/Sink", &id); - - if (id <= 0 || id >= G_MAXUINT32) { - auto err = fmt::format("'{}' is not a valid ID (returned by default-nodes-api)\n", id); - throw std::runtime_error(err); - } - - return id; -} - -void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self) { - auto proxy = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, - "bound-id", "=u", self->node_id_, NULL)); + auto proxy = static_cast(wp_object_manager_lookup( + self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL)); if (!proxy) { - throw std::runtime_error(fmt::format("Object '{}' not found\n", self->node_id_)); + auto err = fmt::format("Object '{}' not found\n", id); + spdlog::error("[{}]: {}", self->name_, err); + throw std::runtime_error(err); } g_autoptr(WpProperties) properties = @@ -73,15 +78,24 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* auto description = wp_properties_get(properties, "node.description"); self->node_name_ = nick ? nick : description; + spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_); } -void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self) { +void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating volume", self->name_); double vol; GVariant* variant = NULL; - g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api"); - g_signal_emit_by_name(mixer_api, "get-volume", self->node_id_, &variant); + + if (!isValidNodeId(id)) { + spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id); + return; + } + + g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant); + if (!variant) { - auto err = fmt::format("Node {} does not support volume\n", self->node_id_); + auto err = fmt::format("Node {} does not support volume\n", id); + spdlog::error("[{}]: {}", self->name_, err); throw std::runtime_error(err); } @@ -93,22 +107,121 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se self->dp.emit(); } +void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id); + + g_autoptr(WpNode) node = static_cast(wp_object_manager_lookup( + self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL)); + + if (!node) { + spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id); + return; + } + + const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); + + if (g_strcmp0(self->default_node_name_, name) != 0) { + spdlog::debug( + "[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not " + "the default node: {}", + self->name_, id, name, self->default_node_name_); + return; + } + + spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}", + self->name_, id, name); + updateVolume(self, id); +} + +void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) { + spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); + + uint32_t default_node_id; + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id); + + if (!isValidNodeId(default_node_id)) { + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, + default_node_id); + return; + } + + g_autoptr(WpNode) node = static_cast( + wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", + "=u", default_node_id, NULL)); + + if (!node) { + spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_, + default_node_id); + return; + } + + const gchar* default_node_name = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); + + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})", + self->name_, default_node_name, default_node_id); + + if (g_strcmp0(self->default_node_name_, default_node_name) == 0) { + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). " + "Ignoring.", + self->name_, self->default_node_name_, default_node_id); + return; + } + + spdlog::debug( + "[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})", + self->name_, default_node_name, default_node_id); + + self->default_node_name_ = g_strdup(default_node_name); + updateVolume(self, default_node_id); + updateNodeName(self, default_node_id); +} + void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) { - self->node_id_ = - self->config_["node-id"].isInt() ? self->config_["node-id"].asInt() : getDefaultNodeId(self); + spdlog::debug("[{}]: onObjectManagerInstalled", self->name_); - g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api"); + self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api"); - updateVolume(self); - updateNodeName(self); - g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self); + if (!self->def_nodes_api_) { + spdlog::error("[{}]: default nodes api is not loaded.", self->name_); + throw std::runtime_error("Default nodes API is not loaded\n"); + } + + self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api"); + + if (!self->mixer_api_) { + spdlog::error("[{}]: mixer api is not loaded.", self->name_); + throw std::runtime_error("Mixer api is not loaded\n"); + } + + uint32_t default_node_id; + g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink", + &self->default_node_name_); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id); + + if (self->default_node_name_) { + spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", + self->name_, self->default_node_name_, default_node_id); + } + + updateVolume(self, default_node_id); + updateNodeName(self, default_node_id); + + g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self); + g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged, + self); } void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self) { + auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p)); + spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name); g_autoptr(GError) error = NULL; if (!wp_object_activate_finish(p, res, &error)) { + spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message); throw std::runtime_error(error->message); } @@ -118,6 +231,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* } void waybar::modules::Wireplumber::activatePlugins() { + spdlog::debug("[{}]: activating plugins", name_); for (uint16_t i = 0; i < apis_->len; i++) { WpPlugin* plugin = static_cast(g_ptr_array_index(apis_, i)); pending_plugins_++; @@ -127,13 +241,13 @@ void waybar::modules::Wireplumber::activatePlugins() { } void waybar::modules::Wireplumber::prepare() { - wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL); - wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL); - wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY, - WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL); + spdlog::debug("[{}]: preparing object manager", name_); + wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", + "=s", "Audio/Sink", NULL); } void waybar::modules::Wireplumber::loadRequiredApiModules() { + spdlog::debug("[{}]: loading required modules", name_); g_autoptr(GError) error = NULL; if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL, @@ -165,7 +279,7 @@ auto waybar::modules::Wireplumber::update() -> void { label_.get_style_context()->remove_class("muted"); } - std::string markup = fmt::format(format, fmt::arg("node_name", node_name_), + std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))); label_.set_markup(markup); @@ -177,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void { } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_), - fmt::arg("volume", volume_), - fmt::arg("icon", getIcon(volume_)))); + label_.set_tooltip_text( + fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_), + fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)))); } else { label_.set_tooltip_text(node_name_); } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 97d84bd..427083b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -102,8 +102,11 @@ Glib::RefPtr get_desktop_app_info(const std::string &app_id desktop_file = desktop_list[0][i]; } else { auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); - auto startup_class = tmp_info->get_startup_wm_class(); + if (!tmp_info) + // see https://github.com/Alexays/Waybar/issues/1446 + continue; + auto startup_class = tmp_info->get_startup_wm_class(); if (startup_class == app_id) { desktop_file = desktop_list[0][i]; break; @@ -615,9 +618,10 @@ void Task::update() { app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { - auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_before_.set_markup(txt); else @@ -625,9 +629,10 @@ void Task::update() { text_before_.show(); } if (!format_after_.empty()) { - auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) text_after_.set_markup(txt); else @@ -636,9 +641,10 @@ void Task::update() { } if (!format_tooltip_.empty()) { - auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), - fmt::arg("app_id", app_id), fmt::arg("state", state_string()), - fmt::arg("short_state", state_string(true))); + auto txt = + fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), + fmt::arg("short_state", state_string(true))); if (markup) button_.set_tooltip_markup(txt); else diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index aa80b54..c1b68c8 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -169,7 +169,7 @@ WorkspaceManager::~WorkspaceManager() { wl_display *display = Client::inst()->wl_display; - // Send `stop` request and wait for one roundtrip. This is not quite correct as + // Send `stop` request and wait for one roundtrip. This is not quite correct as // the protocol encourages us to wait for the .finished event, but it should work // with wlroots workspace manager implementation. zext_workspace_manager_v1_stop(workspace_manager_); @@ -379,7 +379,7 @@ Workspace::~Workspace() { } auto Workspace::update() -> void { - label_.set_markup(fmt::format(format_, fmt::arg("name", name_), + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); } diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index 7ff6f2a..f496d7a 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -2,7 +2,11 @@ #include -#include +#if __has_include() +#include +#else +#include +#endif #include #include diff --git a/test/config.cpp b/test/config.cpp index cdc96b0..3d0f007 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -1,6 +1,10 @@ #include "config.hpp" -#include +#if __has_include() +#include +#else +#include +#endif TEST_CASE("Load simple config", "[config]") { waybar::Config conf; diff --git a/test/date.cpp b/test/date.cpp new file mode 100644 index 0000000..aa6d79b --- /dev/null +++ b/test/date.cpp @@ -0,0 +1,162 @@ +#include "util/date.hpp" + +#include +#include +#include +#include +#include + +#if __has_include() +#include +#include +#else +#include +#endif + +#ifndef SKIP +#define SKIP(...) \ + WARN(__VA_ARGS__); \ + return +#endif + +using namespace std::literals::chrono_literals; + +/* + * Check that the date/time formatter with locale and timezone support is working as expected. + */ + +const date::zoned_time TEST_TIME = date::zoned_time{ + "UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s}; + +/* + * Check if the date formatted with LC_TIME=en_US is within expectations. + * + * The check expects Glibc output style and will fail with FreeBSD (different implementation) + * or musl (no implementation). + */ +static const bool LC_TIME_is_sane = []() { + try { + std::stringstream ss; + ss.imbue(std::locale("en_US.UTF-8")); + + time_t t = 1641211200; + std::tm tm = *std::gmtime(&t); + + ss << std::put_time(&tm, "%x %X"); + return ss.str() == "01/03/2022 12:00:00 PM"; + } catch (std::exception &) { + return false; + } +}(); + +TEST_CASE("Format UTC time", "[clock][util]") { + const auto loc = std::locale("C"); + const auto tm = TEST_TIME; + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); + + if (!LC_TIME_is_sane) { + SKIP("Locale support check failed, skip tests"); + } + + /* Test a few locales that are most likely to be present */ + SECTION("US locale") { + try { + const auto loc = std::locale("en_US.UTF-8"); + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); + CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); + } catch (const std::runtime_error &) { + WARN("Locale en_US not found, skip tests"); + } + } + SECTION("GB locale") { + try { + const auto loc = std::locale("en_GB.UTF-8"); + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05")); + CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); + } catch (const std::runtime_error &) { + WARN("Locale en_GB not found, skip tests"); + } + } + SECTION("Global locale") { + try { + const auto loc = std::locale::global(std::locale("en_US.UTF-8")); + + CHECK(fmt::format("{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); + CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); + CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); + + std::locale::global(loc); + } catch (const std::runtime_error &) { + WARN("Locale en_US not found, skip tests"); + } + } +} + +TEST_CASE("Format zoned time", "[clock][util]") { + const auto loc = std::locale("C"); + const auto tm = date::zoned_time{"America/New_York", TEST_TIME}; + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + + if (!LC_TIME_is_sane) { + SKIP("Locale support check failed, skip tests"); + } + + /* Test a few locales that are most likely to be present */ + SECTION("US locale") { + try { + const auto loc = std::locale("en_US.UTF-8"); + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); + CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + } catch (const std::runtime_error &) { + WARN("Locale en_US not found, skip tests"); + } + } + SECTION("GB locale") { + try { + const auto loc = std::locale("en_GB.UTF-8"); + + CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05")); + CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05"); + CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + } catch (const std::runtime_error &) { + WARN("Locale en_GB not found, skip tests"); + } + } + SECTION("Global locale") { + try { + const auto loc = std::locale::global(std::locale("en_US.UTF-8")); + + CHECK(fmt::format("{}", tm).empty()); // no format specified + CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 + Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); + CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); + CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); + + std::locale::global(loc); + } catch (const std::runtime_error &) { + WARN("Locale en_US not found, skip tests"); + } + } +} diff --git a/test/main.cpp b/test/main.cpp index 7970c26..daeee69 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,8 +3,13 @@ #include #include +#if __has_include() #include #include +#else +#include +#include +#endif #include int main(int argc, char* argv[]) { @@ -13,10 +18,16 @@ int main(int argc, char* argv[]) { session.applyCommandLine(argc, argv); const auto logger = spdlog::default_logger(); +#if CATCH_VERSION_MAJOR >= 3 for (const auto& spec : session.config().getReporterSpecs()) { - if (spec.name() == "tap") { + const auto& reporter_name = spec.name(); +#else + { + const auto& reporter_name = session.config().getReporterName(); +#endif + if (reporter_name == "tap") { spdlog::set_pattern("# [%l] %v"); - } else if (spec.name() == "compact") { + } else if (reporter_name == "compact") { logger->sinks().clear(); } else { logger->sinks().assign({std::make_shared()}); diff --git a/test/meson.build b/test/meson.build index b1e1123..02cbb2a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -15,7 +15,7 @@ test_src = files( if tz_dep.found() test_dep += tz_dep - test_src += files('waybar_time.cpp') + test_src += files('date.cpp') endif waybar_test = executable( diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp deleted file mode 100644 index 79469d4..0000000 --- a/test/waybar_time.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "util/waybar_time.hpp" - -#include -#include - -#include -#include -#include - -using namespace std::literals::chrono_literals; - -/* - * Check that the date/time formatter with locale and timezone support is working as expected. - */ - -const date::zoned_time TEST_TIME = date::make_zoned( - "UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s); - -TEST_CASE("Format UTC time", "[clock][util]") { - waybar::waybar_time tm{std::locale("C"), TEST_TIME}; - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); - - /* Test a few locales that are most likely to be present */ - SECTION("US locale") { - try { - tm.locale = std::locale("en_US"); - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); - REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); - } catch (const std::runtime_error&) { - // locale not found; ignore - } - } - SECTION("GB locale") { - try { - tm.locale = std::locale("en_GB"); - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05")); - REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 13:04:05"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); - } catch (const std::runtime_error&) { - // locale not found; ignore - } - } -} - -TEST_CASE("Format zoned time", "[clock][util]") { - waybar::waybar_time tm{std::locale("C"), date::make_zoned("America/New_York", TEST_TIME)}; - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); - - /* Test a few locales that are most likely to be present */ - SECTION("US locale") { - try { - tm.locale = std::locale("en_US"); - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); - REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); - } catch (const std::runtime_error&) { - // locale not found; ignore - } - } - - SECTION("GB locale") { - try { - tm.locale = std::locale("en_GB"); - - REQUIRE(fmt::format("{}", tm).empty()); // no format specified - REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 - Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05")); - REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 08:04:05"); - REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); - } catch (const std::runtime_error&) { - // locale not found; ignore - } - } -}