diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index d5064fe4..f3c107ac 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Test in FreeBSD VM - uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 + uses: vmactions/freebsd-vm@v0.1.6 # aka FreeBSD 13.0 with: mem: 2048 usesh: true @@ -21,7 +21,7 @@ jobs: pkg install -y git # subprojects/date pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ - pkgconf pulseaudio scdoc sndio spdlog + pkgconf pulseaudio scdoc sndio spdlog wayland-protocols run: | meson build -Dman-pages=enabled ninja -C build diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine index c0e032ff..03836aaa 100644 --- a/Dockerfiles/alpine +++ b/Dockerfiles/alpine @@ -2,4 +2,4 @@ FROM alpine:latest -RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon +RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata diff --git a/README.md b/README.md index 98b99a2d..c5806e3b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Highly customizable Wayland bar for Sway and Wlroots based compositors.
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or -[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
+[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* #### Current features @@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] libsndio [sndio module] libevdev [KeyboardState module] +xkbregistry ``` **Build dependencies** @@ -101,7 +102,8 @@ sudo apt install \ libsigc++-2.0-dev \ libspdlog-dev \ libwayland-dev \ - scdoc + scdoc \ + libxkbregistry-dev ``` diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp new file mode 100644 index 00000000..3eac7dd7 --- /dev/null +++ b/include/AIconLabel.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "ALabel.hpp" + +namespace waybar { + +class AIconLabel : public ALabel { + public: + AIconLabel(const Json::Value &config, const std::string &name, const std::string &id, + const std::string &format, uint16_t interval = 0, bool ellipsize = false, + bool enable_click = false, bool enable_scroll = false); + virtual ~AIconLabel() = default; + virtual auto update() -> void; + + protected: + Gtk::Image image_; + Gtk::Box box_; + + bool iconEnabled() const; +}; + +} // namespace waybar diff --git a/include/bar.hpp b/include/bar.hpp index 4aa17c17..01a9d034 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -112,14 +112,14 @@ class Bar { Gtk::Box center_; Gtk::Box right_; Gtk::Box box_; - std::vector> modules_left_; - std::vector> modules_center_; - std::vector> modules_right_; + std::vector> modules_left_; + std::vector> modules_center_; + std::vector> modules_right_; #ifdef HAVE_SWAY using BarIpcClient = modules::sway::BarIpcClient; std::unique_ptr _ipc_client; #endif - std::vector> modules_all_; + std::vector> modules_all_; }; } // namespace waybar diff --git a/include/factory.hpp b/include/factory.hpp index 43dd2cfd..3855ce22 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -51,6 +51,9 @@ #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" #endif +#ifdef HAVE_GIO_UNIX +#include "modules/inhibitor.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 1f262e91..5a9c01e4 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -1,21 +1,14 @@ #pragma once -#include -#if FMT_VERSION < 60000 -#include -#else -#include -#endif #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" -namespace waybar::modules { +namespace waybar { -struct waybar_time { - std::locale locale; - date::zoned_seconds ztime; -}; +struct waybar_time; + +namespace modules { const std::string kCalendarPlaceholder = "calendar"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; @@ -46,4 +39,5 @@ class Clock : public ALabel { auto timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string; }; -} // namespace waybar::modules +} // namespace modules +} // namespace waybar diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp new file mode 100644 index 00000000..aa2f97d4 --- /dev/null +++ b/include/modules/inhibitor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" + +namespace waybar::modules { + +class Inhibitor : public ALabel { + public: + Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); + ~Inhibitor() override; + auto update() -> void; + auto activated() -> bool; + + private: + auto handleToggle(::GdkEventButton* const& e) -> bool; + + const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; + const std::string inhibitors_; + int handle_ = -1; +}; + +} // namespace waybar::modules diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index 1793bfe8..ed137c14 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -24,8 +24,6 @@ class KeyboardState : public AModule { auto update() -> void; private: - static auto openDevice(const std::string&) -> std::pair; - Gtk::Box box_; Gtk::Label numlock_label_; Gtk::Label capslock_label_; diff --git a/include/modules/network.hpp b/include/modules/network.hpp index c91b598b..7b8281b1 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -73,7 +73,8 @@ class Network : public ALabel { int cidr_; int32_t signal_strength_dbm_; uint8_t signal_strength_; - uint32_t frequency_; + std::string signal_strength_app_; + float frequency_; uint32_t route_priority; util::SleeperThread thread_; diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index 0f7ae317..0ab129e67 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -2,7 +2,8 @@ #include #include -#include "ALabel.hpp" + +#include "AIconLabel.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" @@ -10,7 +11,7 @@ namespace waybar::modules::sway { -class Window : public ALabel, public sigc::trackable { +class Window : public AIconLabel, public sigc::trackable { public: Window(const std::string&, const waybar::Bar&, const Json::Value&); ~Window() = default; @@ -23,6 +24,7 @@ class Window : public ALabel, public sigc::trackable { std::string& output); void getTree(); std::string rewriteTitle(const std::string& title); + void updateAppIcon(); const Bar& bar_; std::string window_; diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 3fe032f8..973d15db 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -3,11 +3,13 @@ #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" +#include "giomm/desktopappinfo.h" #include "util/json.hpp" #include #include #include +#include #include #include @@ -61,15 +63,18 @@ class Task Gtk::Image icon_; Gtk::Label text_before_; Gtk::Label text_after_; + Glib::RefPtr app_info_; bool button_visible_ = false; bool ignored_ = false; - bool with_icon_; + bool with_icon_ = false; + bool with_name_ = false; std::string format_before_; std::string format_after_; std::string format_tooltip_; + std::string name_; std::string title_; std::string app_id_; uint32_t state_ = 0; @@ -77,6 +82,8 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; + void set_app_info_from_app_id_list(const std::string& app_id_list); + bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); void hide_if_ignored(); public: @@ -136,6 +143,7 @@ class Taskbar : public waybar::AModule std::vector> icon_themes_; std::unordered_set ignore_list_; + std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; @@ -158,8 +166,9 @@ class Taskbar : public waybar::AModule bool show_output(struct wl_output *) const; bool all_outputs() const; - std::vector> icon_themes() const; + const std::vector>& icon_themes() const; const std::unordered_set& ignore_list() const; + const std::map& app_ids_replace_map() const; }; } /* namespace waybar::modules::wlr */ diff --git a/include/util/command.hpp b/include/util/command.hpp index 3a38da36..5f398e19 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -68,7 +68,10 @@ inline int close(FILE* fp, pid_t pid) { inline FILE* open(const std::string& cmd, int& pid) { if (cmd == "") return nullptr; int fd[2]; - pipe(fd); + if (pipe(fd) != 0){ + spdlog::error("Unable to pipe fd"); + return nullptr; + } pid_t child_pid = fork(); diff --git a/include/util/format.hpp b/include/util/format.hpp index 543a100f..48f0f516 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include class pow_format { public: @@ -84,5 +85,15 @@ namespace fmt { ); } }; + + + // Glib ustirng support + template <> + struct formatter : formatter { + template + auto format(const Glib::ustring& value, FormatContext& ctx) { + return formatter::format(value, ctx); + } + }; } diff --git a/include/util/string.hpp b/include/util/string.hpp index d644b4c4..24a9b2b9 100644 --- a/include/util/string.hpp +++ b/include/util/string.hpp @@ -1,15 +1,17 @@ +#pragma once + #include const std::string WHITESPACE = " \n\r\t\f\v"; -std::string ltrim(const std::string s) { +inline std::string ltrim(const std::string& s) { size_t begin = s.find_first_not_of(WHITESPACE); return (begin == std::string::npos) ? "" : s.substr(begin); } -std::string rtrim(const std::string s) { +inline std::string rtrim(const std::string& s) { size_t end = s.find_last_not_of(WHITESPACE); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } -std::string trim(const std::string& s) { return rtrim(ltrim(s)); } +inline std::string trim(const std::string& s) { return rtrim(ltrim(s)); } diff --git a/include/util/waybar_time.hpp b/include/util/waybar_time.hpp new file mode 100644 index 00000000..c74e58c3 --- /dev/null +++ b/include/util/waybar_time.hpp @@ -0,0 +1,39 @@ +#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-clock.5.scd b/man/waybar-clock.5.scd index a8fc1877..fb470e03 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -86,6 +86,11 @@ The *clock* module displays the current date and time. typeof: double ++ Threshold to be used when scrolling. +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + View all valid format options in *strftime(3)*. # FORMAT REPLACEMENTS diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 8f9dcfaf..3d30859d 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -151,7 +151,8 @@ $text\\n$tooltip\\n$class* "max-length": 40, "interval": 30, // Remove this if your script is endless and write in loop "exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder - "exec-if": "pgrep spotify" + "exec-if": "pgrep spotify", + "return-type": "json" } ``` diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd new file mode 100644 index 00000000..0838f4d6 --- /dev/null +++ b/man/waybar-inhibitor.5.scd @@ -0,0 +1,92 @@ +waybar-inhibitor(5) + +# NAME + +waybar - inhibitor module + +# DESCRIPTION + +The *inhibitor* module allows to take an inhibitor lock that logind provides. +See *systemd-inhibit*(1) for more information. + +# CONFIGURATION + +*what*: ++ + typeof: string or array ++ + The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*. + +*format*: ++ + typeof: string ++ + The format, how the state should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the current state, the corresponding icon gets selected. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label. + +*max-length*: ++ + typeof: integer ++ + The maximum length in character the module should display. + +*min-length*: ++ + typeof: integer ++ + The minimum length in characters the module should take up. + +*align*: ++ + typeof: float ++ + The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. A click also toggles the state + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right clicked on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +# FORMAT REPLACEMENTS + +*{status}*: status (*activated* or *deactivated*) + +*{icon}*: Icon, as defined in *format-icons* + +# EXAMPLES + +``` +"inhibitor": { + "what": "handle-lid-switch", + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + } +} +``` diff --git a/man/waybar-keyboard-state.5.scd b/man/waybar-keyboard-state.5.scd index 1d7c3a83..94db07d4 100644 --- a/man/waybar-keyboard-state.5.scd +++ b/man/waybar-keyboard-state.5.scd @@ -8,6 +8,8 @@ waybar - keyboard-state module The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock. +You must be a member of the input group to use this module. + # CONFIGURATION *interval*: ++ diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index 0639c07c..d960ffd2 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -84,12 +84,20 @@ Addressed by *memory* *{percentage}*: Percentage of memory in use. +*{swapPercentage}*: Percentage of swap in use. + *{total}*: Amount of total memory available in GiB. +*{swapTotal}*: Amount of total swap available in GiB. + *{used}*: Amount of used memory in GiB. +*{swapUsed}*: Amount of used swap in GiB. + *{avail}*: Amount of available memory in GiB. +*{swapAvail}*: Amount of available swap in GiB. + # EXAMPLES ``` diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index b8b59cd3..29eed4f5 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -69,7 +69,7 @@ Addressed by *sway/mode* # EXAMPLES ``` -"sway/window": { +"sway/mode": { "format": " {}", "max-length": 50 } diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index ea060696..df138077 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -70,6 +70,11 @@ Addressed by *sway/window* typeof: object ++ Rules to rewrite window title. See *rewrite rules*. +*icon*: ++ + typeof: bool ++ + default: true ++ + Option to hide the application icon. + # REWRITE RULES *rewrite* is an object where keys are regular expressions and values are diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index f2808b90..b575e098 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -69,10 +69,6 @@ Addressed by *sway/workspaces* typeof: string ++ Command to execute when the module is updated. -*numeric-first*: ++ - typeof: bool ++ - Whether to put workspaces starting with numbers before workspaces that do not start with a number. - *disable-auto-back-and-forth*: ++ typeof: bool ++ Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. @@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge "sway/workspaces": { "disable-scroll": true, "all-outputs": true, - "numeric-first": false, "format": "{name}: {icon}", "format-icons": { "1": "", diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index c664594d..bfd1f296 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -29,6 +29,10 @@ Addressed by *tray* typeof: integer ++ Defines the spacing between the tray icons. +*reverse-direction*: ++ + typeof: bool ++ + Defines if new app icons should be added in a reverse order + *on-update*: ++ typeof: string ++ Command to execute when the module is updated. diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index 6645cc24..b2946ac5 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -72,10 +72,16 @@ Addressed by *wlr/taskbar* typeof: array ++ List of app_id/titles to be invisible. +*app_ids-mapping*: ++ + typeof: object ++ + Dictionary of app_id to be replaced with + # FORMAT REPLACEMENTS *{icon}*: The icon of the application. +*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id} + *{title}*: The title of the application. *{app_id}*: The app_id (== application name) of the application. @@ -87,10 +93,15 @@ Addressed by *wlr/taskbar* # CLICK ACTIONS *activate*: Bring the application into foreground. + *minimize*: Toggle application's minimized state. + *minimize-raise*: Bring the application into foreground or toggle its minimized state. + *maximize*: Toggle application's maximized state. + *fullscreen*: Toggle application's fullscreen state. + *close*: Close the application. # EXAMPLES @@ -105,7 +116,10 @@ Addressed by *wlr/taskbar* "on-click-middle": "close", "ignore-list": [ "Alacritty" - ] + ], + "app_ids-mapping": { + "firefoxdeveloperedition": "firefox-developer-edition" + } } ``` diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index f0df5e94..5d7b2acd 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -52,6 +52,7 @@ Addressed by *wlr/workspaces* # CLICK ACTIONS *activate*: Switch to workspace. + *close*: Close the workspace. # ICONS diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index c42f6eb8..249ca7e4 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -82,6 +82,12 @@ Also a minimal example configuration can be found on the at the bottom of this m default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. +*fixed-center* ++ + typeof: bool ++ + default: *true* + Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space. + When false, the center block is centered in the space between the left and right block. + *passthrough* ++ typeof: bool ++ default: *false* ++ diff --git a/meson.build b/meson.build index 7f2d9562..17a6bd51 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.8', + version: '0.9.10', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ @@ -79,14 +79,14 @@ is_netbsd = host_machine.system() == 'netbsd' is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') -fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep']) +fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep']) spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) +giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) @@ -142,6 +142,7 @@ src_files = files( 'src/factory.cpp', 'src/AModule.cpp', 'src/ALabel.cpp', + 'src/AIconLabel.cpp', 'src/modules/custom.cpp', 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', @@ -242,6 +243,11 @@ if libsndio.found() src_files += 'src/modules/sndio.cpp' endif +if (giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') + src_files += 'src/modules/inhibitor.cpp' +endif + if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') @@ -290,7 +296,7 @@ executable( gtk_layer_shell, libsndio, tz_dep, - xkbregistry + xkbregistry ], include_directories: [include_directories('include')], install: true, @@ -347,6 +353,10 @@ if scdoc.found() 'waybar-sndio.5.scd', ] + if (giounix.found() and not get_option('logind').disabled()) + man_files += 'waybar-inhibitor.5.scd' + endif + foreach file : man_files path = '@0@'.format(file) basename = path.split('/')[-1] diff --git a/meson_options.txt b/meson_options.txt index f4f60d09..230a53d6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,5 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') +option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') diff --git a/resources/style.css b/resources/style.css index 0235942b..563ee0dd 100644 --- a/resources/style.css +++ b/resources/style.css @@ -1,10 +1,7 @@ * { - border: none; - border-radius: 0; /* `otf-font-awesome` is required to be installed for icons */ - font-family: Roboto, Helvetica, Arial, sans-serif; + font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; - min-height: 0; } window#waybar { @@ -43,6 +40,9 @@ window#waybar.chromium { color: #ffffff; /* Use box-shadow instead of border so the text isn't offset */ box-shadow: inset 0 -3px transparent; + /* Avoid rounded borders under each workspace name */ + border: none; + border-radius: 0; } /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp new file mode 100644 index 00000000..5bc36e4e --- /dev/null +++ b/src/AIconLabel.cpp @@ -0,0 +1,28 @@ +#include "AIconLabel.hpp" + +#include + +namespace waybar { + +AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const std::string &id, + const std::string &format, uint16_t interval, bool ellipsize, + bool enable_click, bool enable_scroll) + : ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) { + event_box_.remove(); + box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL); + box_.set_spacing(8); + box_.add(image_); + box_.add(label_); + event_box_.add(box_); +} + +auto AIconLabel::update() -> void { + image_.set_visible(image_.get_visible() && iconEnabled()); + ALabel::update(); +} + +bool AIconLabel::iconEnabled() const { + return config_["icon"].isBool() ? config_["icon"].asBool() : true; +} + +} // namespace waybar diff --git a/src/AModule.cpp b/src/AModule.cpp index 7da942e0..0e0f00ec 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -6,7 +6,9 @@ namespace waybar { AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id, bool enable_click, bool enable_scroll) - : name_(std::move(name)), config_(std::move(config)) { + : name_(std::move(name)), config_(std::move(config)) + , distance_scrolled_y_(0.0) + , distance_scrolled_x_(0.0) { // configure events' user commands if (config_["on-click"].isString() || config_["on-click-middle"].isString() || config_["on-click-backward"].isString() || config_["on-click-forward"].isString() || diff --git a/src/bar.cpp b/src/bar.cpp index fbd4623f..eb00dd15 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -735,21 +735,22 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk module = factory.makeModule(ref); } - modules_all_.emplace_back(module); + std::shared_ptr module_sp(module); + modules_all_.emplace_back(module_sp); if (group) { group->pack_start(*module, false, false); } else { if (pos == "modules-left") { - modules_left_.emplace_back(module); + modules_left_.emplace_back(module_sp); } if (pos == "modules-center") { - modules_center_.emplace_back(module); + modules_center_.emplace_back(module_sp); } if (pos == "modules-right") { - modules_right_.emplace_back(module); + modules_right_.emplace_back(module_sp); } } - module->dp.connect([module, &name] { + module->dp.connect([module, name] { try { module->update(); } catch (const std::exception& e) { @@ -766,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk auto waybar::Bar::setupWidgets() -> void { window.add(box_); box_.pack_start(left_, false, false); - box_.set_center_widget(center_); + if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { + box_.set_center_widget(center_); + } else { + box_.pack_start(center_, true, false); + } box_.pack_end(right_, false, false); // Convert to button code for every module that is used. diff --git a/src/factory.cpp b/src/factory.cpp index 351aa332..900653b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -94,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sndio") { return new waybar::modules::Sndio(id, config_[name]); } +#endif +#ifdef HAVE_GIO_UNIX + if (ref == "inhibitor") { + return new waybar::modules::Inhibitor(id, bar_, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 26567690..c0075a48 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -161,7 +161,7 @@ const std::tuple waybar::modules::Battery::g uint32_t energy_now; uint32_t energy_full_design; std::string _status; - std::ifstream(bat / "status") >> _status; + std::getline(std::ifstream(bat / "status"), _status); // Some battery will report current and charge in μA/μAh. // Scale these by the voltage to get μW/μWh. diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index be8f2e25..87cd58aa 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -1,17 +1,24 @@ #include "modules/clock.hpp" -#include #include +#if FMT_VERSION < 60000 +#include +#else +#include +#endif +#include #include #include + #include "util/ustring_clen.hpp" +#include "util/waybar_time.hpp" #ifdef HAVE_LANGINFO_1STDAY #include #include #endif -using waybar::modules::waybar_time; +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), @@ -96,7 +103,7 @@ auto waybar::modules::Clock::update() -> void { // 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(format_, localtime); + text = fmt::format(locale_, format_, localtime); } else { text = fmt::format(format_, wtime); } @@ -114,10 +121,10 @@ auto waybar::modules::Clock::update() -> void { } auto tooltip_format = config_["tooltip-format"].asString(); text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); + label_.set_tooltip_markup(text); } } - label_.set_tooltip_markup(text); // Call parent update ALabel::update(); } @@ -255,14 +262,3 @@ auto waybar::modules::Clock::first_day_of_week() -> date::weekday { #endif return date::Sunday; } - -template <> -struct fmt::formatter : fmt::formatter { - template - auto format(const waybar_time& t, FormatContext& ctx) { -#if FMT_VERSION >= 80000 - auto& tm_format = specs; -#endif - return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime)); - } -}; diff --git a/src/modules/cpu/common.cpp b/src/modules/cpu/common.cpp index 4cf67eba..903b3bdc 100644 --- a/src/modules/cpu/common.cpp +++ b/src/modules/cpu/common.cpp @@ -62,7 +62,7 @@ auto waybar::modules::Cpu::update() -> void { double waybar::modules::Cpu::getCpuLoad() { double load[1]; if (getloadavg(load, 1) != -1) { - return load[0]; + return std::ceil(load[0] * 100.0) / 100.0; } throw std::runtime_error("Can't get Cpu load"); } diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp new file mode 100644 index 00000000..1e3f2d35 --- /dev/null +++ b/src/modules/inhibitor.cpp @@ -0,0 +1,175 @@ +#include "modules/inhibitor.hpp" + +#include +#include +#include + +namespace { + +using DBus = std::unique_ptr; + +auto dbus() -> DBus { + GError *error = nullptr; + GDBusConnection* connection = + g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + + if (error) { + spdlog::error("g_bus_get_sync() failed: {}", error->message); + g_error_free(error); + connection = nullptr; + } + + auto destructor = [](GDBusConnection* connection) { + GError *error = nullptr; + g_dbus_connection_close_sync(connection, nullptr, &error); + if (error) { + spdlog::error( + "g_bus_connection_close_sync failed(): {}", + error->message); + g_error_free(error); + } + }; + + return DBus{connection, destructor}; +} + +auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { + GError *error = nullptr; + GUnixFDList* fd_list; + int handle; + + auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit", + g_variant_new( + "(ssss)", + inhibitors.c_str(), + "waybar", + "Asked by user", + "block"), + G_VARIANT_TYPE("(h)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &fd_list, + nullptr, + &error); + if (error) { + spdlog::error( + "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", + error->message); + g_error_free(error); + handle = -1; + } else { + gint index; + g_variant_get(reply, "(h)", &index); + g_variant_unref(reply); + handle = g_unix_fd_list_get(fd_list, index, nullptr); + g_object_unref(fd_list); + } + + return handle; +} + +auto checkInhibitor(const std::string& inhibitor) -> const std::string& { + static const auto inhibitors = std::array{ + "idle", + "shutdown", + "sleep", + "handle-power-key", + "handle-suspend-key", + "handle-hibernate-key", + "handle-lid-switch" + }; + + if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) + == inhibitors.end()) { + throw std::runtime_error("invalid logind inhibitor " + inhibitor); + } + + return inhibitor; +} + +auto getInhibitors(const Json::Value& config) -> std::string { + std::string inhibitors = "idle"; + + if (config["what"].empty()) { + return inhibitors; + } + + if (config["what"].isString()) { + return checkInhibitor(config["what"].asString()); + } + + if (config["what"].isArray()) { + inhibitors = checkInhibitor(config["what"][0].asString()); + for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { + inhibitors += ":" + checkInhibitor(config["what"][i].asString()); + } + return inhibitors; + } + + return inhibitors; +} + +} + +namespace waybar::modules { + +Inhibitor::Inhibitor(const std::string& id, const Bar& bar, + const Json::Value& config) + : ALabel(config, "inhibitor", id, "{status}", true), + dbus_(::dbus()), + inhibitors_(::getInhibitors(config)) { + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &Inhibitor::handleToggle)); + dp.emit(); +} + +Inhibitor::~Inhibitor() { + if (handle_ != -1) { + ::close(handle_); + } +} + +auto Inhibitor::activated() -> bool { + return handle_ != -1; +} + +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), + fmt::arg("icon", getIcon(0, status_text)))); + label_.get_style_context()->add_class(status_text); + + if (tooltipEnabled()) { + label_.set_tooltip_text(status_text); + } + + return ALabel::update(); +} + +auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { + if (e->button == 1) { + if (activated()) { + ::close(handle_); + handle_ = -1; + } else { + handle_ = ::getLocks(dbus_, inhibitors_); + if (handle_ == -1) { + spdlog::error("cannot get inhibitor locks"); + } + } + } + + return ALabel::handleToggle(e); +} + +} // waybar::modules diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 2b6eb2d9..1ac075d6 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -1,6 +1,8 @@ #include "modules/keyboard_state.hpp" +#include #include #include +#include extern "C" { #include @@ -8,6 +10,69 @@ extern "C" { #include } +class errno_error : public std::runtime_error { + public: + int code; + errno_error(int code, const std::string& msg) + : std::runtime_error(getErrorMsg(code, msg.c_str())), + code(code) {} + errno_error(int code, const char* msg) + : std::runtime_error(getErrorMsg(code, msg)), + code(code) {} + private: + static auto getErrorMsg(int err, const char* msg) -> std::string { + std::string error_msg{msg}; + error_msg += ": "; + +#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 32) + // strerrorname_np gets the error code's name; it's nice to have, but it's a recent GNU extension + const auto errno_name = strerrorname_np(err); + error_msg += errno_name; + error_msg += " "; +#endif + + const auto errno_str = strerror(err); + error_msg += errno_str; + + return error_msg; + } +}; + +auto openFile(const std::string& path, int flags) -> int { + int fd = open(path.c_str(), flags); + if (fd < 0) { + if (errno == EACCES) { + throw errno_error(errno, "Can't open " + path + " (are you in the input group?)"); + } else { + throw errno_error(errno, "Can't open " + path); + } + } + return fd; +} + +auto closeFile(int fd) -> void { + int res = close(fd); + if (res < 0) { + throw errno_error(errno, "Can't close file"); + } +} + +auto openDevice(int fd) -> libevdev* { + libevdev* dev; + int err = libevdev_new_from_fd(fd, &dev); + if (err < 0) { + throw errno_error(-err, "Can't create libevdev device"); + } + return dev; +} + +auto supportsLockStates(const libevdev* dev) -> bool { + return libevdev_has_event_type(dev, EV_LED) + && libevdev_has_event_code(dev, EV_LED, LED_NUML) + && libevdev_has_event_code(dev, EV_LED, LED_CAPSL) + && libevdev_has_event_code(dev, EV_LED, LED_SCROLLL); +} + waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), @@ -48,26 +113,36 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& if (config_["device-path"].isString()) { std::string dev_path = config_["device-path"].asString(); - std::tie(fd_, dev_) = openDevice(dev_path); + fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + dev_ = openDevice(fd_); } else { DIR* dev_dir = opendir("/dev/input"); if (dev_dir == nullptr) { - throw std::runtime_error("Failed to open /dev/input"); + throw errno_error(errno, "Failed to open /dev/input"); } dirent *ep; while ((ep = readdir(dev_dir))) { if (ep->d_type != DT_CHR) continue; std::string dev_path = std::string("/dev/input/") + ep->d_name; + int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY); try { - std::tie(fd_, dev_) = openDevice(dev_path); - spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path); - break; - } catch (const std::runtime_error& e) { - continue; + auto dev = openDevice(fd); + if (supportsLockStates(dev)) { + spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); + fd_ = fd; + dev_ = dev; + break; + } + } catch (const errno_error& e) { + // ENOTTY just means the device isn't an evdev device, skip it + if (e.code != ENOTTY) { + spdlog::warn(e.what()); + } } + closeFile(fd); } if (dev_ == nullptr) { - throw std::runtime_error("Failed to find keyboard device"); + throw errno_error(errno, "Failed to find keyboard device"); } } @@ -79,35 +154,13 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& waybar::modules::KeyboardState::~KeyboardState() { libevdev_free(dev_); - int err = close(fd_); - if (err < 0) { - // Not much we can do, so ignore it. + try { + closeFile(fd_); + } catch (const std::runtime_error& e) { + spdlog::warn(e.what()); } } -auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair { - int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY); - if (fd < 0) { - throw std::runtime_error("Can't open " + path); - } - - libevdev* dev; - int err = libevdev_new_from_fd(fd, &dev); - if (err < 0) { - throw std::runtime_error("Can't create libevdev device"); - } - if (!libevdev_has_event_type(dev, EV_LED)) { - throw std::runtime_error("Device doesn't support LED events"); - } - if (!libevdev_has_event_code(dev, EV_LED, LED_NUML) - || !libevdev_has_event_code(dev, EV_LED, LED_CAPSL) - || !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) { - throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events"); - } - - return std::make_pair(fd, dev); -} - auto waybar::modules::KeyboardState::update() -> void { int err = LIBEVDEV_READ_STATUS_SUCCESS; while (err == LIBEVDEV_READ_STATUS_SUCCESS) { @@ -117,8 +170,8 @@ auto waybar::modules::KeyboardState::update() -> void { err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev); } } - if (err != -EAGAIN) { - throw std::runtime_error("Failed to sync evdev device"); + if (-err != EAGAIN) { + throw errno_error(-err, "Failed to sync evdev device"); } int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 75e05302..a0bf13fa 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -12,7 +12,15 @@ auto waybar::modules::Memory::update() -> void { parseMeminfo(); unsigned long memtotal = meminfo_["MemTotal"]; + unsigned long swaptotal = 0; + if (meminfo_.count("SwapTotal")) { + swaptotal = meminfo_["SwapTotal"]; + } unsigned long memfree; + unsigned long swapfree = 0; + if (meminfo_.count("SwapFree")) { + swapfree = meminfo_["SwapFree"]; + } if (meminfo_.count("MemAvailable")) { // New kernels (3.4+) have an accurate available memory field. memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; @@ -24,9 +32,16 @@ auto waybar::modules::Memory::update() -> void { if (memtotal > 0 && memfree >= 0) { auto total_ram_gigabytes = memtotal / std::pow(1024, 2); + auto total_swap_gigabytes = swaptotal / std::pow(1024, 2); int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; + int used_swap_percentage = 0; + if (swaptotal && swapfree) { + used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; + } auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); + auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2); auto available_ram_gigabytes = memfree / std::pow(1024, 2); + auto available_swap_gigabytes = swapfree / std::pow(1024, 2); auto format = format_; auto state = getState(used_ram_percentage); @@ -43,9 +58,13 @@ auto waybar::modules::Memory::update() -> void { 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), - fmt::arg("avail", available_ram_gigabytes))); + fmt::arg("swapUsed", used_swap_gigabytes), + fmt::arg("avail", available_ram_gigabytes), + fmt::arg("swapAvail", available_swap_gigabytes))); } if (tooltipEnabled()) { @@ -54,9 +73,13 @@ auto waybar::modules::Memory::update() -> void { label_.set_tooltip_text(fmt::format(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("avail", available_ram_gigabytes))); + fmt::arg("swapUsed", used_swap_gigabytes), + fmt::arg("avail", available_ram_gigabytes), + fmt::arg("swapAvail", available_swap_gigabytes))); } else { label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); } diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 6d272867..e5cafc02 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -129,7 +129,7 @@ void waybar::modules::MPD::setLabel() { album = getTag(MPD_TAG_ALBUM); title = getTag(MPD_TAG_TITLE); date = getTag(MPD_TAG_DATE); - song_pos = mpd_status_get_song_pos(status_.get()); + song_pos = mpd_status_get_song_pos(status_.get()) + 1; volume = mpd_status_get_volume(status_.get()); if (volume < 0) { volume = 0; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e7b20ab5..4fd8e3ec 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -78,7 +78,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), - want_route_dump_(false), + want_route_dump_(true), want_link_dump_(false), want_addr_dump_(false), dump_in_progress_(false), @@ -88,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_WLAN}, #endif - frequency_(0) { + frequency_(0.0) { // Start with some "text" in the module's label_, update() will then // update it. Since the text should be different, update() will be able @@ -106,7 +106,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf } if (!config_["interface"].isString()) { - // "interface" isn't configure, then try to guess the external + // "interface" isn't configured, then try to guess the external // interface currently used for internet. want_route_dump_ = true; } else { @@ -331,12 +331,13 @@ auto waybar::modules::Network::update() -> void { 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", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), @@ -360,12 +361,13 @@ auto waybar::modules::Network::update() -> void { 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", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -403,7 +405,8 @@ void waybar::modules::Network::clearIface() { cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; - frequency_ = 0; + signal_strength_app_.clear(); + frequency_ = 0.0; } int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { @@ -470,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->essid_.clear(); net->signal_strength_dbm_ = 0; net->signal_strength_ = 0; - net->frequency_ = 0; + net->signal_strength_app_.clear(); + net->frequency_ = 0.0; } } net->carrier_ = carrier.value(); @@ -788,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; + // WiFi-hardware usually operates in the range -90 to -30dBm. - // WiFi-hardware usually operates in the range -90 to -20dBm. - const int hardwareMax = -20; + // If a signal is too strong, it can overwhelm receiving circuity that is designed + // to pick up and process a certain signal level. The following percentage is scaled to + // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm). + const int hardwareOptimum = -45; const int hardwareMin = -90; const int strength = - ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; - signal_strength_ = std::clamp(strength, 0, 100); + 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); + signal_strength_ = std::clamp(strength, 0, 100); + + if (signal_strength_dbm_ >= -50) { + signal_strength_app_ = "Great Connectivity"; + } else if (signal_strength_dbm_ >= -60) { + signal_strength_app_ = "Good Connectivity"; + } else if (signal_strength_dbm_ >= -67) { + signal_strength_app_ = "Streaming"; + } else if (signal_strength_dbm_ >= -70) { + signal_strength_app_ = "Web Surfing"; + } else if (signal_strength_dbm_ >= -80) { + signal_strength_app_ = "Basic Connectivity"; + } else { + signal_strength_app_ = "Poor Connectivity"; + } } if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); @@ -803,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { void waybar::modules::Network::parseFreq(struct nlattr **bss) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) { - // in MHz - frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + // in GHz + frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; } } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 8f2b25de..b1f78302 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -54,7 +54,9 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) { c, static_cast(static_cast(PA_SUBSCRIPTION_MASK_SERVER) | static_cast(PA_SUBSCRIPTION_MASK_SINK) | - static_cast(PA_SUBSCRIPTION_MASK_SOURCE)), + static_cast(PA_SUBSCRIPTION_MASK_SINK_INPUT) | + static_cast(PA_SUBSCRIPTION_MASK_SOURCE) | + static_cast(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)), nullptr, nullptr); break; @@ -79,6 +81,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { if (dir == SCROLL_DIR::NONE) { return true; } + if (config_["reverse-scrolling"].asInt() == 1){ + if (dir == SCROLL_DIR::UP) { + dir = SCROLL_DIR::DOWN; + } else if (dir == SCROLL_DIR::DOWN) { + dir = SCROLL_DIR::UP; + } + } double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; @@ -114,8 +123,12 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context * conte pa_context_get_server_info(context, serverInfoCb, data); } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); + } else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + pa_context_get_sink_info_list(context, sinkInfoCb, data); } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); + } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { + pa_context_get_source_info_list(context, sourceInfoCb, data); } } @@ -272,7 +285,7 @@ auto waybar::modules::Pulseaudio::update() -> void { fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); getState(volume_); - + if (tooltipEnabled()) { if (tooltip_format.empty() && config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index b504c8d0..74987ca4 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -8,13 +8,7 @@ #include #include -template <> -struct fmt::formatter : formatter { - template - auto format(const Glib::ustring& value, FormatContext& ctx) { - return formatter::format(value, ctx); - } -}; +#include "util/format.hpp" template <> struct fmt::formatter : formatter { diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index c32c0d6a..94120be6 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -25,7 +25,11 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) } void Tray::onAdd(std::unique_ptr& item) { - box_.pack_start(item->event_box); + if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) { + box_.pack_end(item->event_box); + } else { + box_.pack_start(item->event_box); + } dp.emit(); } diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 73a64c3b..f64c7d05 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -154,7 +154,10 @@ auto Language::init_layouts_map(const std::vector& used_layouts) -> std::map short_name_to_number_map; for (const auto& used_layout_name : used_layouts) { - auto used_layout = &layouts_map_.find(used_layout_name)->second; + auto found = layouts_map_.find(used_layout_name); + if (found == layouts_map_.end()) + continue; + auto used_layout = &found->second; auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name]; if (layouts_with_same_name_list.size() < 2) { continue; diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index fc81b2cf..00418a16 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -1,11 +1,20 @@ #include "modules/sway/window.hpp" + +#include +#include +#include +#include +#include #include + +#include #include +#include namespace waybar::modules::sway { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { + : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { ipc_.subscribe(R"(["window","workspace"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd)); @@ -29,12 +38,60 @@ void Window::onCmd(const struct Ipc::ipc_response& res) { auto payload = parser_.parse(res.payload); auto output = payload["output"].isString() ? payload["output"].asString() : ""; std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output); + updateAppIcon(); dp.emit(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); } } +std::optional getDesktopFilePath(const std::string& app_id) { + const auto data_dirs = Glib::get_system_data_dirs(); + for (const auto& data_dir : data_dirs) { + const auto desktop_file_path = data_dir + "applications/" + app_id + ".desktop"; + if (std::filesystem::exists(desktop_file_path)) { + return desktop_file_path; + } + } + return {}; +} + +std::optional getIconName(const std::string& app_id) { + const auto desktop_file_path = getDesktopFilePath(app_id); + if (!desktop_file_path.has_value()) { + return {}; + } + try { + Glib::KeyFile desktop_file; + desktop_file.load_from_file(desktop_file_path.value()); + const auto icon_name = desktop_file.get_string("Desktop Entry", "Icon"); + if (icon_name.empty()) { + return {}; + } + return icon_name; + } catch (Glib::FileError& error) { + spdlog::warn( + "Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str()); + } catch (Glib::KeyFileError& error) { + spdlog::warn( + "Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str()); + } + return {}; +} + +void Window::updateAppIcon() { + if (!iconEnabled()) { + return; + } + const auto icon_name = getIconName(app_id_); + if (icon_name.has_value()) { + image_.set_from_icon_name(icon_name.value(), Gtk::ICON_SIZE_LARGE_TOOLBAR); + image_.set_visible(true); + return; + } + image_.set_visible(false); +} + auto Window::update() -> void { if (!old_app_id_.empty()) { bar_.window.get_style_context()->remove_class(old_app_id_); @@ -63,7 +120,7 @@ auto Window::update() -> void { label_.set_tooltip_text(window_); } // Call parent update - ALabel::update(); + AIconLabel::update(); } int leafNodesInWorkspace(const Json::Value& node) { diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 43dcf33c..074d145f 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { Json::Value v; v["name"] = p_w_name; v["target_output"] = bar_.output->name; + v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); break; } @@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { Json::Value v; v["name"] = p_w_name; v["target_output"] = ""; + v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); } } } - // config option to sort numeric workspace names before others - bool config_numeric_first = config_["numeric-first"].asBool(); - + // sway has a defined ordering of workspaces that should be preserved in + // the representation displayed by waybar to ensure that commands such + // as "workspace prev" or "workspace next" make sense when looking at + // the workspace representation in the bar. + // Due to waybar's own feature of persistent workspaces unknown to sway, + // custom sorting logic is necessary to make these workspaces appear + // naturally in the list of workspaces without messing up sway's + // sorting. For this purpose, a custom numbering property is created + // that preserves the order provided by sway while inserting numbered + // persistent workspaces at their natural positions. + // + // All of this code assumes that sway provides numbered workspaces first + // and other workspaces are sorted by their creation time. + // + // In a first pass, the maximum "num" value is computed to enqueue + // unnumbered workspaces behind numbered ones when computing the sort + // attribute. + int max_num = -1; + for (auto & workspace : workspaces_) { + max_num = std::max(workspace["num"].asInt(), max_num); + } + for (auto & workspace : workspaces_) { + auto workspace_num = workspace["num"].asInt(); + if (workspace_num > -1) { + workspace["sort"] = workspace_num; + } else { + workspace["sort"] = ++max_num; + } + } std::sort(workspaces_.begin(), workspaces_.end(), - [config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) { - // the "num" property (integer type): - // The workspace number or -1 for workspaces that do - // not start with a number. - // We could rely on sway providing this property: - // - // auto l = lhs["num"].asInt(); - // auto r = rhs["num"].asInt(); - // - // We cannot rely on the "num" property as provided by sway - // via IPC, because persistent workspace might not exist in - // sway's view. However, we need this property also for - // not-yet created persistent workspace. As such, we simply - // duplicate sway's logic of assigning the "num" property - // into waybar (see convertWorkspaceNameToNum). This way the - // sorting should work out even when we include workspaces - // that do not currently exist. + [](const Json::Value &lhs, const Json::Value &rhs) { auto lname = lhs["name"].asString(); auto rname = rhs["name"].asString(); - int l = convertWorkspaceNameToNum(lname); - int r = convertWorkspaceNameToNum(rname); + int l = lhs["sort"].asInt(); + int r = rhs["sort"].asInt(); if (l == r) { - // in case both integers are the same, lexicographical - // sort. This also covers the case when both don't have a - // number (i.e., l == r == -1). + // In case both integers are the same, lexicographical + // sort. The code above already ensure that this will only + // happend in case of explicitly numbered workspaces. return lname < rname; } - // one of the workspaces doesn't begin with a number, so - // num is -1. - if (l < 0 || r < 0) { - if (config_numeric_first) { - return r < 0; - } - return l < 0; - } - - // both workspaces have a "num" so let's just compare those return l < r; }); + } dp.emit(); } catch (const std::exception &e) { diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index af1b0da0..55c9431e 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -4,6 +4,7 @@ #include "glibmm/fileutils.h" #include "glibmm/refptr.h" #include "util/format.hpp" +#include "util/string.hpp" #include #include @@ -11,7 +12,9 @@ #include #include #include +#include +#include #include #include @@ -24,27 +27,6 @@ namespace waybar::modules::wlr { -/* String manipulation methods */ -const std::string WHITESPACE = " \n\r\t\f\v"; - -static std::string ltrim(const std::string& s) -{ - size_t start = s.find_first_not_of(WHITESPACE); - return (start == std::string::npos) ? "" : s.substr(start); -} - -static std::string rtrim(const std::string& s) -{ - size_t end = s.find_last_not_of(WHITESPACE); - return (end == std::string::npos) ? "" : s.substr(0, end + 1); -} - -static std::string trim(const std::string& s) -{ - return rtrim(ltrim(s)); -} - - /* Icon loading functions */ static std::vector search_prefix() { @@ -86,8 +68,7 @@ static Glib::RefPtr load_icon_from_file(std::string icon_path, int } } -/* Method 1 - get the correct icon name from the desktop file */ -static std::string get_from_desktop_app_info(const std::string &app_id) +static Glib::RefPtr get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); @@ -103,33 +84,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id) ".desktop" }; - Glib::RefPtr app_info; + for (auto& prefix : prefixes) { + for (auto& folder : app_folders) { + for (auto& suffix : suffixes) { + auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + if (!app_info_) { + continue; + } - for (auto& prefix : prefixes) - for (auto& folder : app_folders) - for (auto& suffix : suffixes) - if (!app_info) - app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + return app_info_; + } + } + } - if (app_info && app_info->get_icon()) - return app_info->get_icon()->to_string(); - - return ""; + return {}; } -/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ -static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, - const std::string &app_id) +Glib::RefPtr get_desktop_app_info(const std::string &app_id) { - if (icon_theme->lookup_icon(app_id, 24)) - return app_id; + auto app_info = get_app_info_by_name(app_id); + if (app_info) { + return app_info; + } - return ""; -} - -/* Method 3 - as last resort perform a search for most appropriate desktop info file */ -static std::string get_from_desktop_app_info_search(const std::string &app_id) -{ std::string desktop_file = ""; gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); @@ -151,65 +128,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id) } g_free(desktop_list); - return get_from_desktop_app_info(desktop_file); + return get_app_info_by_name(desktop_file); } -static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, - const std::string &app_id_list, int size) -{ +void Task::set_app_info_from_app_id_list(const std::string& app_id_list) { std::string app_id; std::istringstream stream(app_id_list); - bool found = false; /* Wayfire sends a list of app-id's in space separated format, other compositors * send a single app-id, but in any case this works fine */ while (stream >> app_id) { + app_info_ = get_desktop_app_info(app_id); + if (app_info_) { + return; + } + + auto lower_app_id = app_id; + std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), + [](char c){ return std::tolower(c); }); + app_info_ = get_desktop_app_info(lower_app_id); + if (app_info_) { + return; + } + size_t start = 0, end = app_id.size(); start = app_id.rfind(".", end); std::string app_name = app_id.substr(start+1, app_id.size()); + app_info_ = get_desktop_app_info(app_name); + if (app_info_) { + return; + } - auto lower_app_id = app_id; - std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), - [](char c){ return std::tolower(c); }); + start = app_id.find("-"); + app_name = app_id.substr(0, start); + app_info_ = get_desktop_app_info(app_name); + } +} - std::string icon_name = get_from_icon_theme(icon_theme, app_id); +static std::string get_icon_name_from_icon_theme(const Glib::RefPtr& icon_theme, + const std::string &app_id) +{ + if (icon_theme->lookup_icon(app_id, 24)) + return app_id; - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, lower_app_id); - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(lower_app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info_search(app_id); + return ""; +} - if (icon_name.empty()) - icon_name = "unknown"; +bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size) +{ + std::string ret_icon_name = "unknown"; + if (app_info) { + std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); + if (!icon_name.empty()) { + ret_icon_name = icon_name; + } else { + if (app_info->get_icon()) { + ret_icon_name = app_info->get_icon()->to_string(); + } + } + } - Glib::RefPtr pixbuf; + Glib::RefPtr pixbuf; - try { - pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); - } catch(...) { - if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) - pixbuf = load_icon_from_file(icon_name, size); - else - pixbuf = {}; - } + try { + pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); + } catch(...) { + if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) + pixbuf = load_icon_from_file(ret_icon_name, size); + else + pixbuf = {}; + } - if (pixbuf) { - image.set(pixbuf); - found = true; - break; - } - } + if (pixbuf) { + image.set(pixbuf); + return true; + } - return found; + return false; } /* Task class implementation */ @@ -289,13 +285,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, content_.show(); button_.add(content_); - with_icon_ = false; format_before_.clear(); format_after_.clear(); if (config_["format"].isString()) { /* The user defined a format string, use it */ auto format = config_["format"].asString(); + if (format.find("{name}") != std::string::npos) { + with_name_ = true; + } auto icon_pos = format.find("{icon}"); if (icon_pos == 0) { @@ -402,13 +400,28 @@ void Task::handle_app_id(const char *app_id) app_id_ = app_id; hide_if_ignored(); - if (!with_icon_) + auto ids_replace_map = tbar_->app_ids_replace_map(); + if (ids_replace_map.count(app_id_)) { + auto replaced_id = ids_replace_map[app_id_]; + spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id)); + app_id_ = replaced_id; + } + + if (!with_icon_ && !with_name_) { return; + } + + set_app_info_from_app_id_list(app_id_); + name_ = app_info_ ? app_info_->get_display_name() : app_id; + + if (!with_icon_) { + return; + } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; bool found = false; for (auto& icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { found = true; break; } @@ -564,14 +577,17 @@ void Task::update() { bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; std::string title = title_; + std::string name = name_; std::string app_id = app_id_; if (markup) { title = Glib::Markup::escape_text(title); + name = Glib::Markup::escape_text(name); 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)) @@ -585,6 +601,7 @@ void Task::update() 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)) @@ -599,6 +616,7 @@ 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)) @@ -726,6 +744,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu } } + // Load app_id remappings + if (config_["app_ids-mapping"].isObject()) { + const Json::Value& mapping = config_["app_ids-mapping"]; + const std::vector app_ids = config_["app_ids-mapping"].getMemberNames(); + for (auto& app_id : app_ids) { + app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); + } + } + icon_themes_.push_back(Gtk::IconTheme::get_default()); } @@ -857,10 +884,10 @@ bool Taskbar::all_outputs() const return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } -std::vector> Taskbar::icon_themes() const -{ - return icon_themes_; -} -const std::unordered_set &Taskbar::ignore_list() const { return ignore_list_; } +const std::vector>& Taskbar::icon_themes() const { return icon_themes_; } + +const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } + +const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } } /* namespace waybar::modules::wlr */ diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index 2c67317b..244487f2 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -1,4 +1,3 @@ -#define CATCH_CONFIG_RUNNER #include "util/SafeSignal.hpp" #include @@ -138,8 +137,3 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr producer.join(); REQUIRE(count == NUM_EVENTS); } - -int main(int argc, char* argv[]) { - Glib::init(); - return Catch::Session().run(argc, argv); -} diff --git a/test/config.cpp b/test/config.cpp index edd6d6b8..29b0502a 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -1,4 +1,3 @@ -#define CATCH_CONFIG_MAIN #include "config.hpp" #include diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 00000000..bbfcc9f2 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,26 @@ +#define CATCH_CONFIG_RUNNER +#include +#include +#include + +#include +#include +#include + +int main(int argc, char* argv[]) { + Catch::Session session; + Glib::init(); + + session.applyCommandLine(argc, argv); + const auto logger = spdlog::default_logger(); + const auto& reporter_name = session.config().getReporterName(); + if (reporter_name == "tap") { + spdlog::set_pattern("# [%l] %v"); + } else if (reporter_name == "compact") { + logger->sinks().clear(); + } else { + logger->sinks().assign({std::make_shared()}); + } + + return session.run(); +} diff --git a/test/meson.build b/test/meson.build index bbef21e7..b1e11237 100644 --- a/test/meson.build +++ b/test/meson.build @@ -6,30 +6,27 @@ test_dep = [ jsoncpp, spdlog, ] - -config_test = executable( - 'config_test', +test_src = files( + 'main.cpp', + 'SafeSignal.cpp', 'config.cpp', '../src/config.cpp', - dependencies: test_dep, - include_directories: test_inc, ) -safesignal_test = executable( - 'safesignal_test', - 'SafeSignal.cpp', +if tz_dep.found() + test_dep += tz_dep + test_src += files('waybar_time.cpp') +endif + +waybar_test = executable( + 'waybar_test', + test_src, dependencies: test_dep, include_directories: test_inc, ) test( - 'Configuration test', - config_test, - workdir: meson.source_root(), -) - -test( - 'SafeSignal test', - safesignal_test, + 'waybar', + waybar_test, workdir: meson.source_root(), ) diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp new file mode 100644 index 00000000..5fc3312d --- /dev/null +++ b/test/waybar_time.cpp @@ -0,0 +1,90 @@ +#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 + } + } +}