Merge branch 'master' into list_of_times
This commit is contained in:
		
						commit
						b6655e475b
					
				|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> | ||||
| > 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)<br> | ||||
| [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)<br> | ||||
| > *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 | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,25 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <gtkmm/box.h> | ||||
| #include <gtkmm/image.h> | ||||
| 
 | ||||
| #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
 | ||||
|  | @ -112,14 +112,14 @@ class Bar { | |||
|   Gtk::Box                                      center_; | ||||
|   Gtk::Box                                      right_; | ||||
|   Gtk::Box                                      box_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_left_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_center_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_right_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_left_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_center_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_right_; | ||||
| #ifdef HAVE_SWAY | ||||
|   using BarIpcClient = modules::sway::BarIpcClient; | ||||
|   std::unique_ptr<BarIpcClient> _ipc_client; | ||||
| #endif | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_all_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_all_; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar
 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -1,21 +1,14 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #if FMT_VERSION < 60000 | ||||
| #include <fmt/time.h> | ||||
| #else | ||||
| #include <fmt/chrono.h> | ||||
| #endif | ||||
| #include <date/tz.h> | ||||
| #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
 | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| #include <gio/gio.h> | ||||
| 
 | ||||
| #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
 | ||||
|  | @ -24,8 +24,6 @@ class KeyboardState : public AModule { | |||
|   auto update() -> void; | ||||
| 
 | ||||
|  private: | ||||
|   static auto openDevice(const std::string&) -> std::pair<int, libevdev*>; | ||||
| 
 | ||||
|   Gtk::Box    box_; | ||||
|   Gtk::Label  numlock_label_; | ||||
|   Gtk::Label  capslock_label_; | ||||
|  |  | |||
|  | @ -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_; | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ | |||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include <tuple> | ||||
| #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_; | ||||
|  |  | |||
|  | @ -3,11 +3,13 @@ | |||
| #include "AModule.hpp" | ||||
| #include "bar.hpp" | ||||
| #include "client.hpp" | ||||
| #include "giomm/desktopappinfo.h" | ||||
| #include "util/json.hpp" | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| #include <gdk/gdk.h> | ||||
|  | @ -61,15 +63,18 @@ class Task | |||
|     Gtk::Image icon_; | ||||
|     Gtk::Label text_before_; | ||||
|     Gtk::Label text_after_; | ||||
|     Glib::RefPtr<Gio::DesktopAppInfo> 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<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size); | ||||
|     void hide_if_ignored(); | ||||
| 
 | ||||
|    public: | ||||
|  | @ -136,6 +143,7 @@ class Taskbar : public waybar::AModule | |||
| 
 | ||||
|     std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_; | ||||
|     std::unordered_set<std::string> ignore_list_; | ||||
|     std::map<std::string, std::string> 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<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const; | ||||
|     const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const; | ||||
|     const std::unordered_set<std::string>& ignore_list() const; | ||||
|     const std::map<std::string, std::string>& app_ids_replace_map() const; | ||||
| }; | ||||
| 
 | ||||
| } /* namespace waybar::modules::wlr */ | ||||
|  |  | |||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include <glibmm/ustring.h> | ||||
| 
 | ||||
| class pow_format { | ||||
|   public: | ||||
|  | @ -84,5 +85,15 @@ namespace fmt { | |||
|             ); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     // Glib ustirng support
 | ||||
|     template <> | ||||
|     struct formatter<Glib::ustring> : formatter<std::string> { | ||||
|       template <typename FormatContext> | ||||
|       auto format(const Glib::ustring& value, FormatContext& ctx) { | ||||
|         return formatter<std::string>::format(value, ctx); | ||||
|       } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,17 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| 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)); } | ||||
|  |  | |||
|  | @ -0,0 +1,39 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <date/tz.h> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| namespace waybar { | ||||
| 
 | ||||
| struct waybar_time { | ||||
|   std::locale         locale; | ||||
|   date::zoned_seconds ztime; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar
 | ||||
| 
 | ||||
| template <> | ||||
| struct fmt::formatter<waybar::waybar_time> { | ||||
|   std::string_view specs; | ||||
| 
 | ||||
|   template <typename ParseContext> | ||||
|   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 <typename FormatContext> | ||||
|   auto format(const waybar::waybar_time& t, FormatContext& ctx) { | ||||
|     return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime)); | ||||
|   } | ||||
| }; | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  |  | |||
|  | @ -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": "" | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | @ -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*: ++ | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
| ``` | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ Addressed by *sway/mode* | |||
| # EXAMPLES | ||||
| 
 | ||||
| ``` | ||||
| "sway/window": { | ||||
| "sway/mode": { | ||||
|     "format": " {}", | ||||
|     "max-length": 50 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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": "", | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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" | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ Addressed by *wlr/workspaces* | |||
| # CLICK ACTIONS | ||||
| 
 | ||||
| *activate*: Switch to workspace. | ||||
| 
 | ||||
| *close*: Close the workspace. | ||||
| 
 | ||||
| # ICONS | ||||
|  |  | |||
|  | @ -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* ++ | ||||
|  |  | |||
							
								
								
									
										18
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										18
									
								
								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] | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
|  | @ -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 */ | ||||
|  |  | |||
|  | @ -0,0 +1,28 @@ | |||
| #include "AIconLabel.hpp" | ||||
| 
 | ||||
| #include <gdkmm/pixbuf.h> | ||||
| 
 | ||||
| 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
 | ||||
|  | @ -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() || | ||||
|  |  | |||
							
								
								
									
										17
									
								
								src/bar.cpp
								
								
								
								
							
							
						
						
									
										17
									
								
								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<AModule> 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.
 | ||||
|  |  | |||
|  | @ -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]); | ||||
|  |  | |||
|  | @ -161,7 +161,7 @@ const std::tuple<uint8_t, float, std::string, float> 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.
 | ||||
|  |  | |||
|  | @ -1,17 +1,24 @@ | |||
| #include "modules/clock.hpp" | ||||
| 
 | ||||
| #include <time.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #if FMT_VERSION < 60000 | ||||
| #include <fmt/time.h> | ||||
| #else | ||||
| #include <fmt/chrono.h> | ||||
| #endif | ||||
| 
 | ||||
| #include <ctime> | ||||
| #include <sstream> | ||||
| #include <type_traits> | ||||
| 
 | ||||
| #include "util/ustring_clen.hpp" | ||||
| #include "util/waybar_time.hpp" | ||||
| #ifdef HAVE_LANGINFO_1STDAY | ||||
| #include <langinfo.h> | ||||
| #include <locale.h> | ||||
| #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<waybar_time> : fmt::formatter<std::tm> { | ||||
|   template <typename FormatContext> | ||||
|   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)); | ||||
|   } | ||||
| }; | ||||
|  |  | |||
|  | @ -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"); | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,175 @@ | |||
| #include "modules/inhibitor.hpp" | ||||
| 
 | ||||
| #include <gio/gio.h> | ||||
| #include <gio/gunixfdlist.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>; | ||||
| 
 | ||||
| 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
 | ||||
|  | @ -1,6 +1,8 @@ | |||
| #include "modules/keyboard_state.hpp" | ||||
| #include <errno.h> | ||||
| #include <filesystem> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <sys/types.h> | ||||
|  | @ -8,6 +10,69 @@ extern "C" { | |||
| #include <fcntl.h> | ||||
| } | ||||
| 
 | ||||
| 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, libevdev*> { | ||||
|     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); | ||||
|  |  | |||
|  | @ -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)); | ||||
|       } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,7 +54,9 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) { | |||
|           c, | ||||
|           static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) | | ||||
|                                                  static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) | | ||||
|                                                  static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE)), | ||||
|                                                  static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) | | ||||
|                                                  static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) | | ||||
|                                                  static_cast<int>(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<double>(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(); | ||||
|  |  | |||
|  | @ -8,13 +8,7 @@ | |||
| #include <fstream> | ||||
| #include <map> | ||||
| 
 | ||||
| template <> | ||||
| struct fmt::formatter<Glib::ustring> : formatter<std::string> { | ||||
|   template <typename FormatContext> | ||||
|   auto format(const Glib::ustring& value, FormatContext& ctx) { | ||||
|     return formatter<std::string>::format(value, ctx); | ||||
|   } | ||||
| }; | ||||
| #include "util/format.hpp" | ||||
| 
 | ||||
| template <> | ||||
| struct fmt::formatter<Glib::VariantBase> : formatter<std::string> { | ||||
|  |  | |||
|  | @ -25,7 +25,11 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) | |||
| } | ||||
| 
 | ||||
| void Tray::onAdd(std::unique_ptr<Item>& 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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -154,7 +154,10 @@ auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> | |||
| 
 | ||||
|   std::map<std::string, int> 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; | ||||
|  |  | |||
|  | @ -1,11 +1,20 @@ | |||
| #include "modules/sway/window.hpp" | ||||
| 
 | ||||
| #include <gdkmm/pixbuf.h> | ||||
| #include <glibmm/fileutils.h> | ||||
| #include <glibmm/keyfile.h> | ||||
| #include <glibmm/miscutils.h> | ||||
| #include <gtkmm/enums.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <filesystem> | ||||
| #include <regex> | ||||
| #include <string> | ||||
| 
 | ||||
| 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<std::string> 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<Glib::ustring> 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) { | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cctype> | ||||
| #include <string> | ||||
| 
 | ||||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include "glibmm/fileutils.h" | ||||
| #include "glibmm/refptr.h" | ||||
| #include "util/format.hpp" | ||||
| #include "util/string.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cctype> | ||||
|  | @ -11,7 +12,9 @@ | |||
| #include <cstring> | ||||
| #include <memory> | ||||
| #include <sstream> | ||||
| #include <utility> | ||||
| 
 | ||||
| #include <fmt/core.h> | ||||
| #include <gdkmm/monitor.h> | ||||
| 
 | ||||
| #include <gtkmm/icontheme.h> | ||||
|  | @ -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<std::string> search_prefix() | ||||
| { | ||||
|  | @ -86,8 +68,7 @@ static Glib::RefPtr<Gdk::Pixbuf> 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<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id) | ||||
| { | ||||
|     static std::vector<std::string> prefixes = search_prefix(); | ||||
| 
 | ||||
|  | @ -103,33 +84,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id) | |||
|         ".desktop" | ||||
|     }; | ||||
| 
 | ||||
|     Glib::RefPtr<Gio::DesktopAppInfo> 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<Gtk::IconTheme>& icon_theme, | ||||
|         const std::string &app_id) | ||||
| Glib::RefPtr<Gio::DesktopAppInfo> 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<Gtk::IconTheme>& 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<Gtk::IconTheme>& 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<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> 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<Gdk::Pixbuf> pixbuf; | ||||
| 	Glib::RefPtr<Gdk::Pixbuf> 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<std::string> 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<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const | ||||
| { | ||||
|     return icon_themes_; | ||||
| } | ||||
| const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; } | ||||
| const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; } | ||||
| 
 | ||||
| const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; } | ||||
| 
 | ||||
| const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } | ||||
| 
 | ||||
| } /* namespace waybar::modules::wlr */ | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| #define CATCH_CONFIG_RUNNER | ||||
| #include "util/SafeSignal.hpp" | ||||
| 
 | ||||
| #include <glibmm.h> | ||||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| #define CATCH_CONFIG_MAIN | ||||
| #include "config.hpp" | ||||
| 
 | ||||
| #include <catch2/catch.hpp> | ||||
|  |  | |||
|  | @ -0,0 +1,26 @@ | |||
| #define CATCH_CONFIG_RUNNER | ||||
| #include <glibmm.h> | ||||
| #include <spdlog/sinks/stdout_sinks.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <catch2/catch.hpp> | ||||
| #include <catch2/catch_reporter_tap.hpp> | ||||
| #include <memory> | ||||
| 
 | ||||
| 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<spdlog::sinks::stderr_sink_st>()}); | ||||
|   } | ||||
| 
 | ||||
|   return session.run(); | ||||
| } | ||||
|  | @ -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(), | ||||
| ) | ||||
|  |  | |||
|  | @ -0,0 +1,90 @@ | |||
| #include "util/waybar_time.hpp" | ||||
| 
 | ||||
| #include <date/date.h> | ||||
| #include <date/tz.h> | ||||
| 
 | ||||
| #include <catch2/catch.hpp> | ||||
| #include <chrono> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| 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<std::chrono::seconds> 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
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue