diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index cc1c4b4e..a6da7ef7 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,7 +21,8 @@ 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 wayland-protocols upower + pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower \ + libinotify run: | meson build -Dman-pages=enabled ninja -C build diff --git a/include/AButton.hpp b/include/AButton.hpp new file mode 100644 index 00000000..ce29a09f --- /dev/null +++ b/include/AButton.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "AModule.hpp" + +namespace waybar { + +class AButton : public AModule { + public: + AButton(const Json::Value &, const std::string &, const std::string &, const std::string &format, + uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, + bool enable_scroll = false); + virtual ~AButton() = default; + virtual auto update() -> void; + virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0); + virtual std::string getIcon(uint16_t, const std::vector &alts, uint16_t max = 0); + + protected: + Gtk::Button button_ = Gtk::Button(name_); + Gtk::Label *label_ = (Gtk::Label *)button_.get_child(); + std::string format_; + const std::chrono::seconds interval_; + bool alt_ = false; + std::string default_format_; + + virtual bool handleToggle(GdkEventButton *const &e); + virtual std::string getState(uint8_t value, bool lesser = false); +}; + +} // namespace waybar diff --git a/include/factory.hpp b/include/factory.hpp index fd741ce6..5ffee4fc 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -9,6 +9,7 @@ #ifdef HAVE_SWAY #include "modules/sway/language.hpp" #include "modules/sway/mode.hpp" +#include "modules/sway/scratchpad.hpp" #include "modules/sway/window.hpp" #include "modules/sway/workspaces.hpp" #endif @@ -23,10 +24,10 @@ #endif #ifdef HAVE_HYPRLAND #include "modules/hyprland/backend.hpp" -#include "modules/hyprland/window.hpp" #include "modules/hyprland/language.hpp" +#include "modules/hyprland/window.hpp" #endif -#if defined(__linux__) && !defined(NO_FILESYSTEM) +#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) #include "modules/battery.hpp" #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) @@ -75,6 +76,7 @@ #include "modules/custom.hpp" #include "modules/custom_list.hpp" #include "modules/temperature.hpp" +#include "modules/user.hpp" namespace waybar { diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp index b7499b8f..90567716 100644 --- a/include/modules/backlight.hpp +++ b/include/modules/backlight.hpp @@ -5,7 +5,7 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" @@ -14,16 +14,18 @@ struct udev_device; namespace waybar::modules { -class Backlight : public ALabel { +class Backlight : public AButton { class BacklightDev { public: BacklightDev() = default; - BacklightDev(std::string name, int actual, int max); + BacklightDev(std::string name, int actual, int max, bool powered); std::string_view name() const; int get_actual() const; void set_actual(int actual); int get_max() const; void set_max(int max); + bool get_powered() const; + void set_powered(bool powered); friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) { return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_; } @@ -32,6 +34,7 @@ class Backlight : public ALabel { std::string name_; int actual_ = 1; int max_ = 1; + bool powered_ = true; }; public: diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index 99950ae3..614b36ab 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -6,14 +6,16 @@ #include #endif #include +#if defined(__linux__) #include +#endif #include #include #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { @@ -24,7 +26,7 @@ namespace fs = std::experimental::filesystem; namespace fs = std::filesystem; #endif -class Battery : public ALabel { +class Battery : public AButton { public: Battery(const std::string&, const Json::Value&); ~Battery(); diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp index bd9737b1..6aac0747 100644 --- a/include/modules/bluetooth.hpp +++ b/include/modules/bluetooth.hpp @@ -1,6 +1,6 @@ #pragma once -#include "ALabel.hpp" +#include "AButton.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" #endif @@ -12,7 +12,7 @@ namespace waybar::modules { -class Bluetooth : public ALabel { +class Bluetooth : public AButton { struct ControllerInfo { std::string path; std::string address; diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 08ab05e0..b3e49888 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -2,7 +2,7 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar { @@ -14,7 +14,7 @@ namespace modules { const std::string kCalendarPlaceholder = "calendar"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; -class Clock : public ALabel { +class Clock : public AButton { public: Clock(const std::string&, const Json::Value&); ~Clock() = default; diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp index 539f926c..4ad5771f 100644 --- a/include/modules/cpu.hpp +++ b/include/modules/cpu.hpp @@ -9,12 +9,12 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Cpu : public ALabel { +class Cpu : public AButton { public: Cpu(const std::string&, const Json::Value&); ~Cpu() = default; diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index 711d07e2..e4a81bb4 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -5,14 +5,14 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/command.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Custom : public ALabel { +class Custom : public AButton { public: Custom(const std::string&, const std::string&, const Json::Value&); ~Custom(); diff --git a/include/modules/disk.hpp b/include/modules/disk.hpp index ec386b21..761314e2 100644 --- a/include/modules/disk.hpp +++ b/include/modules/disk.hpp @@ -5,13 +5,13 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/format.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Disk : public ALabel { +class Disk : public AButton { public: Disk(const std::string&, const Json::Value&); ~Disk() = default; diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index b9d1c99e..9401bf5a 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -1,30 +1,36 @@ #pragma once -#include +#include +#include #include #include -#include -#include +#include #include namespace waybar::modules::hyprland { -class IPC { + +class EventHandler { public: + virtual void onEvent(const std::string& ev) = 0; + virtual ~EventHandler() = default; +}; + +class IPC { + public: IPC() { startIPC(); } - void registerForIPC(const std::string&, std::function); + void registerForIPC(const std::string&, EventHandler*); + void unregisterForIPC(EventHandler*); std::string getSocket1Reply(const std::string& rq); -private: + private: + void startIPC(); + void parseIPC(const std::string&); - void startIPC(); - void parseIPC(const std::string&); - - std::mutex callbackMutex; - std::deque>> callbacks; + std::mutex callbackMutex; + std::list> callbacks; }; inline std::unique_ptr gIPC; inline bool modulesReady = false; -}; - +}; // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index 9e7193e2..04fe3825 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -1,20 +1,21 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "bar.hpp" #include "modules/hyprland/backend.hpp" #include "util/json.hpp" namespace waybar::modules::hyprland { -class Language : public waybar::ALabel { -public: +class Language : public waybar::AButton, +public EventHandler { + public: Language(const std::string&, const waybar::Bar&, const Json::Value&); - ~Language() = default; + ~Language(); auto update() -> void; -private: + private: void onEvent(const std::string&); void initLanguage(); @@ -26,4 +27,4 @@ private: std::string layoutName_; }; -} \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp index e8446551..4b697cf3 100644 --- a/include/modules/hyprland/window.hpp +++ b/include/modules/hyprland/window.hpp @@ -9,20 +9,24 @@ namespace waybar::modules::hyprland { -class Window : public waybar::ALabel { -public: +class Window : public waybar::ALabel, + public EventHandler { + public: Window(const std::string&, const waybar::Bar&, const Json::Value&); - ~Window() = default; + ~Window(); auto update() -> void; -private: + private: + uint getActiveWorkspaceID(std::string); + std::string getLastWindowTitle(uint); void onEvent(const std::string&); + bool separate_outputs; std::mutex mutex_; const Bar& bar_; util::JsonParser parser_; std::string lastView; }; -} \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index ac0bcf02..80d370c4 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -2,13 +2,13 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "bar.hpp" #include "client.hpp" namespace waybar::modules { -class IdleInhibitor : public ALabel { +class IdleInhibitor : public AButton { sigc::connection timeout_; public: @@ -20,6 +20,7 @@ class IdleInhibitor : public ALabel { private: bool handleToggle(GdkEventButton* const& e); + void toggleStatus(); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp index a5f300d6..9a012035 100644 --- a/include/modules/inhibitor.hpp +++ b/include/modules/inhibitor.hpp @@ -4,12 +4,12 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "bar.hpp" namespace waybar::modules { -class Inhibitor : public ALabel { +class Inhibitor : public AButton { public: Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~Inhibitor() override; diff --git a/include/modules/jack.hpp b/include/modules/jack.hpp index a3555be6..fbab0623 100644 --- a/include/modules/jack.hpp +++ b/include/modules/jack.hpp @@ -1,9 +1,11 @@ #pragma once #include -#include #include #include + +#include + #include "ALabel.hpp" #include "util/sleeper_thread.hpp" @@ -11,26 +13,26 @@ namespace waybar::modules { class JACK : public ALabel { public: - JACK(const std::string&, const Json::Value&); + JACK(const std::string &, const Json::Value &); ~JACK() = default; auto update() -> void; - int bufSize(jack_nframes_t size); - int sampleRate(jack_nframes_t rate); - int xrun(); - void shutdown(); + int bufSize(jack_nframes_t size); + int sampleRate(jack_nframes_t rate); + int xrun(); + void shutdown(); private: - std::string JACKState(); + std::string JACKState(); - jack_client_t* client_; - jack_nframes_t bufsize_; - jack_nframes_t samplerate_; - unsigned int xruns_; - float load_; - bool running_; - std::mutex mutex_; - std::string state_; + jack_client_t *client_; + jack_nframes_t bufsize_; + jack_nframes_t samplerate_; + unsigned int xruns_; + float load_; + bool running_; + std::mutex mutex_; + std::string state_; util::SleeperThread thread_; }; diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index 05fbec13..ce9faba0 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -3,12 +3,15 @@ #include #include +#include + #include "AModule.hpp" #include "bar.hpp" #include "util/sleeper_thread.hpp" extern "C" { #include +#include } namespace waybar::modules { @@ -20,6 +23,8 @@ class KeyboardState : public AModule { auto update() -> void; private: + auto tryAddDevice(const std::string&) -> void; + Gtk::Box box_; Gtk::Label numlock_label_; Gtk::Label capslock_label_; @@ -31,11 +36,12 @@ class KeyboardState : public AModule { const std::chrono::seconds interval_; std::string icon_locked_; std::string icon_unlocked_; + std::string devices_path_; - int fd_; - libevdev* dev_; + struct libinput* libinput_; + std::unordered_map libinput_devices_; - util::SleeperThread thread_; + util::SleeperThread libinput_thread_, hotplug_thread_; }; } // namespace waybar::modules diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp index e23ed841..a5887e3d 100644 --- a/include/modules/memory.hpp +++ b/include/modules/memory.hpp @@ -5,12 +5,12 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Memory : public ALabel { +class Memory : public AButton { public: Memory(const std::string&, const Json::Value&); ~Memory() = default; diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index ae3f9152..b85906d1 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -7,12 +7,12 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "modules/mpd/state.hpp" namespace waybar::modules { -class MPD : public ALabel { +class MPD : public AButton { friend class detail::Context; // State machine diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp index 1276e3c3..28ca6410 100644 --- a/include/modules/mpd/state.hpp +++ b/include/modules/mpd/state.hpp @@ -7,7 +7,7 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" namespace waybar::modules { class MPD; diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 8ec6b6df..9f13da59 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -10,7 +10,7 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" @@ -18,7 +18,7 @@ namespace waybar::modules { -class Network : public ALabel { +class Network : public AButton { public: Network(const std::string&, const Json::Value&); ~Network(); diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp index a8e4890a..f222f9e7 100644 --- a/include/modules/pulseaudio.hpp +++ b/include/modules/pulseaudio.hpp @@ -7,11 +7,11 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" namespace waybar::modules { -class Pulseaudio : public ALabel { +class Pulseaudio : public AButton { public: Pulseaudio(const std::string&, const Json::Value&); ~Pulseaudio(); diff --git a/include/modules/simpleclock.hpp b/include/modules/simpleclock.hpp index 5cbee4c6..2945d86e 100644 --- a/include/modules/simpleclock.hpp +++ b/include/modules/simpleclock.hpp @@ -2,12 +2,12 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Clock : public ALabel { +class Clock : public AButton { public: Clock(const std::string&, const Json::Value&); ~Clock() = default; diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp index eb9b218e..6d7350cf 100644 --- a/include/modules/sndio.hpp +++ b/include/modules/sndio.hpp @@ -4,12 +4,12 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Sndio : public ALabel { +class Sndio : public AButton { public: Sndio(const std::string &, const Json::Value &); ~Sndio(); diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index f7602765..8673067b 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -6,7 +6,7 @@ #include #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" @@ -14,7 +14,7 @@ namespace waybar::modules::sway { -class Language : public ALabel, public sigc::trackable { +class Language : public AButton, public sigc::trackable { public: Language(const std::string& id, const Json::Value& config); ~Language() = default; diff --git a/include/modules/sway/mode.hpp b/include/modules/sway/mode.hpp index 5543c4eb..041f68ac 100644 --- a/include/modules/sway/mode.hpp +++ b/include/modules/sway/mode.hpp @@ -2,7 +2,7 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" @@ -10,7 +10,7 @@ namespace waybar::modules::sway { -class Mode : public ALabel, public sigc::trackable { +class Mode : public AButton, public sigc::trackable { public: Mode(const std::string&, const Json::Value&); ~Mode() = default; diff --git a/include/modules/sway/scratchpad.hpp b/include/modules/sway/scratchpad.hpp new file mode 100644 index 00000000..e68e7726 --- /dev/null +++ b/include/modules/sway/scratchpad.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "client.hpp" +#include "modules/sway/ipc/client.hpp" +#include "util/json.hpp" + +namespace waybar::modules::sway { +class Scratchpad : public ALabel { + public: + Scratchpad(const std::string&, const Json::Value&); + ~Scratchpad() = default; + auto update() -> void; + + private: + auto getTree() -> void; + auto onCmd(const struct Ipc::ipc_response&) -> void; + auto onEvent(const struct Ipc::ipc_response&) -> void; + + std::string tooltip_format_; + bool show_empty_; + bool tooltip_enabled_; + std::string tooltip_text_; + int count_; + std::mutex mutex_; + Ipc ipc_; + util::JsonParser parser_; +}; +} // namespace waybar::modules::sway \ No newline at end of file diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index c99ba7f7..c13d5cee 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -24,7 +24,6 @@ class Window : public AIconLabel, public sigc::trackable { std::tuple getFocusedNode( const Json::Value& nodes, std::string& output); void getTree(); - std::string rewriteTitle(const std::string& title); void updateAppIconName(); void updateAppIcon(); diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp index 04caafc5..47898015 100644 --- a/include/modules/temperature.hpp +++ b/include/modules/temperature.hpp @@ -4,12 +4,12 @@ #include -#include "ALabel.hpp" +#include "AButton.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Temperature : public ALabel { +class Temperature : public AButton { public: Temperature(const std::string&, const Json::Value&); ~Temperature() = default; diff --git a/include/modules/user.hpp b/include/modules/user.hpp new file mode 100644 index 00000000..38b09c41 --- /dev/null +++ b/include/modules/user.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#include "AIconLabel.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { +class User : public AIconLabel { + public: + User(const std::string&, const Json::Value&); + ~User() = default; + auto update() -> void; + + private: + util::SleeperThread thread_; + + Glib::RefPtr pixbuf_; + + static constexpr inline int defaultUserImageWidth_ = 20; + static constexpr inline int defaultUserImageHeight_ = 20; + + bool signal_label(GdkEventButton* button) const; + + long uptime_as_seconds(); + std::string get_user_login() const; + std::string get_user_home_dir() const; + std::string get_default_user_avatar_path() const; + void init_default_user_avatar(int width, int height); + void init_user_avatar(const std::string& path, int width, int height); + void init_avatar(const Json::Value& config); + void init_update_worker(); +}; +} // namespace waybar::modules diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 74b0109d..7a1f17a6 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -74,6 +74,10 @@ class Task { std::string app_id_; uint32_t state_ = 0; + int32_t drag_start_x; + int32_t drag_start_y; + int32_t drag_start_button = -1; + private: std::string repr() const; std::string state_string(bool = false) const; @@ -105,6 +109,12 @@ class Task { /* Callbacks for Gtk events */ bool handle_clicked(GdkEventButton *); + bool handle_button_release(GdkEventButton *); + bool handle_motion_notify(GdkEventMotion *); + void handle_drag_data_get(const Glib::RefPtr &context, + Gtk::SelectionData &selection_data, guint info, guint time); + void handle_drag_data_received(const Glib::RefPtr &context, int x, int y, + Gtk::SelectionData selection_data, guint info, guint time); public: bool operator==(const Task &) const; diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index e65594af..963d610f 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -152,6 +152,7 @@ class WorkspaceManager : public AModule { bool sort_by_name_ = true; bool sort_by_coordinates_ = true; + bool sort_by_number_ = false; bool all_outputs_ = false; bool active_only_ = false; bool creation_delayed_ = false; diff --git a/include/util/format.hpp b/include/util/format.hpp index e9904210..fac0377d 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -56,9 +56,10 @@ struct formatter { fraction /= base; } - auto max_width = 4 // coeff in {:.3g} format - + 1 // prefix from units array - + s.binary_ // for the 'i' in GiB. + auto number_width = 5 // coeff in {:.1f} format + + s.binary_; // potential 4th digit before the decimal point + auto max_width = number_width + 1 // prefix from units array + + s.binary_ // for the 'i' in GiB. + s.unit_.length(); const char* format; @@ -69,15 +70,16 @@ struct formatter { case '<': return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); case '=': - format = "{coefficient:<4.3g}{padding}{prefix}{unit}"; + format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; break; case 0: default: - format = "{coefficient:.3g}{prefix}{unit}"; + format = "{coefficient:.1f}{prefix}{unit}"; break; } return format_to( ctx.out(), format, fmt::arg("coefficient", fraction), + fmt::arg("number_width", number_width), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("unit", s.unit_), fmt::arg("padding", pow ? "" diff --git a/include/util/rewrite_title.hpp b/include/util/rewrite_title.hpp new file mode 100644 index 00000000..c477339e --- /dev/null +++ b/include/util/rewrite_title.hpp @@ -0,0 +1,8 @@ +#pragma once +#include + +#include + +namespace waybar::util { +std::string rewriteTitle(const std::string&, const Json::Value&); +} diff --git a/include/util/sanitize_str.hpp b/include/util/sanitize_str.hpp new file mode 100644 index 00000000..bb45c353 --- /dev/null +++ b/include/util/sanitize_str.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace waybar::util { +std::string sanitize_string(std::string str); +} // namespace waybar::util diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index d14e4f24..9c8ba791 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -37,8 +37,8 @@ The *backlight* module displays the current backlight level. Positive value to rotate the text label. *states*: ++ - typeof: array ++ - A number of backlight states which get activated on certain brightness levels. + typeof: object ++ + A number of backlight states which get activated on certain brightness levels. See *waybar-states(5)*. *on-click*: ++ typeof: string ++ diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 1ddd4d6d..fabde59a 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -33,7 +33,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y The interval in which the information gets polled. *states*: ++ - typeof: array ++ + typeof: object ++ A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*. *format*: ++ diff --git a/man/waybar-bluetooth.5.scd b/man/waybar-bluetooth.5.scd index fd01dd97..6a5e71a7 100644 --- a/man/waybar-bluetooth.5.scd +++ b/man/waybar-bluetooth.5.scd @@ -137,7 +137,7 @@ Addressed by *bluetooth* *{device_alias}*: Alias of the displayed device. -*{device_enumerate}*: Show a list of all connected devices, each on a seperate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++ +*{device_enumerate}*: Show a list of all connected devices, each on a separate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++ and/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip related format options. # EXPERIMENTAL BATTERY PERCENTAGE FEATURE diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index 2e0d6c71..e3545536 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -42,7 +42,7 @@ The *cpu* module displays the current cpu utilization. Positive value to rotate the text label. *states*: ++ - typeof: array ++ + typeof: object ++ A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*. *on-click*: ++ diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 58797141..586c9dbd 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -32,7 +32,7 @@ Addressed by *disk* Positive value to rotate the text label. *states*: ++ - typeof: array ++ + typeof: object ++ A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*. *max-length*: ++ diff --git a/man/waybar-gamemode.5.scd b/man/waybar-gamemode.5.scd index f7847bd9..257c9c91 100644 --- a/man/waybar-gamemode.5.scd +++ b/man/waybar-gamemode.5.scd @@ -23,7 +23,7 @@ Feral Gamemode optimizations. *tooltip*: ++ typeof: bool ++ - defualt: true ++ + default: true ++ Option to disable tooltip on hover. *tooltip-format*: ++ diff --git a/man/waybar-hyprland-window.5.scd b/man/waybar-hyprland-window.5.scd index 4be137d0..0135d7c9 100644 --- a/man/waybar-hyprland-window.5.scd +++ b/man/waybar-hyprland-window.5.scd @@ -17,12 +17,31 @@ Addressed by *hyprland/window* default: {} ++ The format, how information should be displayed. On {} the current window title is displayed. +*rewrite*: ++ + typeof: object ++ + Rules to rewrite window title. See *rewrite rules*. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the title is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. # EXAMPLES ``` "hyprland/window": { - "format": "{}" + "format": "{}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } } ``` diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index b341a494..f85456d8 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -63,6 +63,11 @@ screensaving, also known as "presentation mode". typeof: double ++ Threshold to be used when scrolling. +*start-activated*: ++ + typeof: bool ++ + default: *false* ++ + Whether the inhibit should be activated when starting waybar. + *timeout*: ++ typeof: double ++ The number of minutes the inhibit should last. diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd index 0838f4d6..10d41bd5 100644 --- a/man/waybar-inhibitor.5.scd +++ b/man/waybar-inhibitor.5.scd @@ -6,7 +6,7 @@ waybar - inhibitor module # DESCRIPTION -The *inhibitor* module allows to take an inhibitor lock that logind provides. +The *inhibitor* module allows one to take an inhibitor lock that logind provides. See *systemd-inhibit*(1) for more information. # CONFIGURATION diff --git a/man/waybar-keyboard-state.5.scd b/man/waybar-keyboard-state.5.scd index 905a321f..fef46709 100644 --- a/man/waybar-keyboard-state.5.scd +++ b/man/waybar-keyboard-state.5.scd @@ -13,6 +13,7 @@ You must be a member of the input group to use this module. # CONFIGURATION *interval*: ++ + Deprecated, this module use event loop now, the interval has no effect. typeof: integer ++ default: 1 ++ The interval, in seconds, to poll the keyboard state. diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index d960ffd2..34e342f3 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -32,7 +32,7 @@ Addressed by *memory* Positive value to rotate the text label. *states*: ++ - typeof: array ++ + typeof: object ++ A number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*. *max-length*: ++ diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 07a0de80..b941c22c 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -43,8 +43,8 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu Positive value to rotate the text label. *states*: ++ - typeof: array ++ - A number of volume states which get activated on certain volume levels. See *waybar-states(5)* + typeof: object ++ + A number of volume states which get activated on certain volume levels. See *waybar-states(5)*. *max-length*: ++ typeof: integer ++ @@ -101,6 +101,10 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu default: 100 ++ The maximum volume that can be set, in percentage. +*ignored-sinks*: ++ + typeof: array ++ + Sinks in this list will not be shown as the active sink by Waybar. Entries should be the sink's description field. + # FORMAT REPLACEMENTS *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. diff --git a/man/waybar-states.5.scd b/man/waybar-states.5.scd index ae2df1b9..c1b78391 100644 --- a/man/waybar-states.5.scd +++ b/man/waybar-states.5.scd @@ -7,14 +7,13 @@ apply a class when the value matches the declared state value. # STATES -- Every entry (*state*) consists of a ** (typeof: *string*) and a ** (typeof: *integer*). +Every entry (*state*) consists of a ** (typeof: *string*) and a ** (typeof: *integer*). - - The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the ** of the state. - Each class gets activated when the current capacity is equal or below the configured **. +- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the ** of the state. + Each class gets activated when the current value is equal to or less than the configured ** for the *battery* module, or equal to or greater than the configured ** for all other modules. - - Also each state can have its own *format*. - Those can be configured via *format-*. - Or if you want to differentiate a bit more even as *format--*. +- Also, each state can have its own *format*. + Those can be configured via *format-*, or if you want to differentiate a bit more, as *format--*. # EXAMPLE diff --git a/man/waybar-sway-scratchpad.5.scd b/man/waybar-sway-scratchpad.5.scd new file mode 100644 index 00000000..11fc32c0 --- /dev/null +++ b/man/waybar-sway-scratchpad.5.scd @@ -0,0 +1,64 @@ +waybar-sway-scratchpad(5) + +# NAME + +waybar - sway scratchpad module + +# DESCRIPTION + +The *scratchpad* module displays the scratchpad status in Sway + +# CONFIGURATION + +Addressed by *sway/scratchpad* + +*format*: ++ + typeof: string ++ + default: {icon} {count} ++ + The format, how information should be displayed. + +*show-empty*: ++ + typeof: bool ++ + default: false ++ + Option to show module when scratchpad is empty. + +*format-icons*: ++ + typeof: array/object ++ + Based on the current scratchpad window counts, the corresponding icon gets selected. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + default: {app}: {title} ++ + The format, how information in the tooltip should be displayed. + +# FORMAT REPLACEMENTS + +*{icon}*: Icon, as defined in *format-icons*. + +*{count}*: Number of windows in the scratchpad. + +*{app}*: Name of the application in the scratchpad. + +*{title}*: Title of the application in the scratchpad. + +# EXAMPLES + +``` +"sway/scratchpad": { + "format": "{icon} {count}", + "show-empty": false, + "format-icons": ["", ""], + "tooltip": true, + "tooltip-format": "{app}: {title}" +} +``` + +# STYLE + +- *#scratchpad* +- *#scratchpad.empty* diff --git a/man/waybar-upower.5.scd b/man/waybar-upower.5.scd index 99c015a5..dae974dd 100644 --- a/man/waybar-upower.5.scd +++ b/man/waybar-upower.5.scd @@ -33,7 +33,7 @@ compatible devices in the tooltip. *tooltip*: ++ typeof: bool ++ - defualt: true ++ + default: true ++ Option to disable tooltip on hover. *tooltip-spacing*: ++ diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index 5d7b2acd..169112fc 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -33,6 +33,11 @@ Addressed by *wlr/workspaces* Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first. If both are false - sort by id will be performed. +*sort-by-number*: ++ + typeof: bool ++ + default: false ++ + If set to true, workspace names will be sorted numerically. Takes presedence over any other sort-by option. + *all-outputs*: ++ typeof: bool ++ default: false ++ @@ -75,7 +80,8 @@ Additional to workspace name matching, the following *format-icons* can be set. "5": "", "focused": "", "default": "" - } + }, + "sort-by-number": true } ``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index fafc2b36..54340f21 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -273,6 +273,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-river-window(5)* - *waybar-states(5)* - *waybar-sway-mode(5)* +- *waybar-sway-scratchpad(5)* - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* - *waybar-wlr-taskbar(5)* diff --git a/meson.build b/meson.build index 743d291d..8de9ae02 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.13', + version: '0.9.15', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ @@ -87,9 +87,11 @@ 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').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled())) -jsoncpp = dependency('jsoncpp') +jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep']) sigcpp = dependency('sigc++-2.0') +libinotify = dependency('libinotify', required: false) libepoll = dependency('epoll-shim', required: false) +libinput = dependency('libinput', required: get_option('libinput')) libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) upower_glib = dependency('upower-glib', required: get_option('upower_glib')) @@ -143,6 +145,7 @@ endif src_files = files( 'src/factory.cpp', 'src/AModule.cpp', + 'src/AButton.cpp', 'src/ALabel.cpp', 'src/AIconLabel.cpp', 'src/modules/custom.cpp', @@ -150,12 +153,15 @@ src_files = files( 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', 'src/modules/temperature.cpp', + 'src/modules/user.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp', 'src/config.cpp', 'src/group.cpp', - 'src/util/ustring_clen.cpp' + 'src/util/ustring_clen.cpp', + 'src/util/sanitize_str.cpp', + 'src/util/rewrite_title.cpp' ) if is_linux @@ -177,6 +183,11 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd 'src/modules/memory/bsd.cpp', 'src/modules/memory/common.cpp', ) + if is_freebsd + src_files += files( + 'src/modules/battery.cpp', + ) + endif endif add_project_arguments('-DHAVE_SWAY', language: 'cpp') @@ -186,7 +197,8 @@ src_files += [ 'src/modules/sway/mode.cpp', 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', - 'src/modules/sway/workspaces.cpp' + 'src/modules/sway/workspaces.cpp', + 'src/modules/sway/scratchpad.cpp' ] if true @@ -251,8 +263,9 @@ if libudev.found() and (is_linux or libepoll.found()) src_files += 'src/modules/backlight.cpp' endif -if libevdev.found() and (is_linux or libepoll.found()) +if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found()) add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') + add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp') src_files += 'src/modules/keyboard_state.cpp' endif @@ -312,12 +325,14 @@ executable( gtkmm, dbusmenu_gtk, giounix, + libinput, libnl, libnlgen, upower_glib, libpulse, libjack, libudev, + libinotify, libepoll, libmpdclient, libevdev, @@ -373,6 +388,7 @@ if scdoc.found() 'waybar-river-window.5.scd', 'waybar-sway-language.5.scd', 'waybar-sway-mode.5.scd', + 'waybar-sway-scratchpad.5.scd', 'waybar-sway-window.5.scd', 'waybar-sway-workspaces.5.scd', 'waybar-temperature.5.scd', @@ -413,6 +429,7 @@ endif catch2 = dependency( 'catch2', + version: '>=3.0.0', fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) diff --git a/meson_options.txt b/meson_options.txt index b8eb9213..bd5eb819 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,5 @@ option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') +option('libinput', type: 'feature', value: 'auto', description: 'Enable libinput support for libinput related features') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') diff --git a/resources/config b/resources/config index 6a753acf..ad76e937 100644 --- a/resources/config +++ b/resources/config @@ -5,7 +5,7 @@ // "width": 1280, // Waybar width "spacing": 4, // Gaps between modules (4px) // Choose the order of the modules - "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], + "modules-left": ["sway/workspaces", "sway/mode", "sway/scratchpad", "custom/media"], "modules-center": ["sway/window"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"], // Modules configuration @@ -36,6 +36,13 @@ "sway/mode": { "format": "{}" }, + "sway/scratchpad": { + "format": "{icon} {count}", + "show-empty": false, + "format-icons": ["", ""], + "tooltip": true, + "tooltip-format": "{app}: {title}" + }, "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ", "format-disconnected": "Disconnected ", diff --git a/resources/style.css b/resources/style.css index 563ee0dd..40d870af 100644 --- a/resources/style.css +++ b/resources/style.css @@ -34,21 +34,28 @@ window#waybar.chromium { border: none; } -#workspaces button { - padding: 0 5px; - background-color: transparent; - color: #ffffff; +button { /* 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 */ + /* Avoid rounded borders under each button name */ border: none; border-radius: 0; } /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ +button:hover { + background: inherit; + box-shadow: inset 0 -3px #ffffff; +} + +#workspaces button { + padding: 0 5px; + background-color: transparent; + color: #ffffff; +} + #workspaces button:hover { background: rgba(0, 0, 0, 0.2); - box-shadow: inset 0 -3px #ffffff; } #workspaces button.focused { @@ -78,6 +85,7 @@ window#waybar.chromium { #tray, #mode, #idle_inhibitor, +#scratchpad, #mpd { padding: 0 10px; color: #ffffff; @@ -252,3 +260,11 @@ label:focus { #keyboard-state > label.locked { background: rgba(0, 0, 0, 0.2); } + +#scratchpad { + background: rgba(0, 0, 0, 0.2); +} + +#scratchpad.empty { + background-color: transparent; +} diff --git a/src/AButton.cpp b/src/AButton.cpp new file mode 100644 index 00000000..3457bcfa --- /dev/null +++ b/src/AButton.cpp @@ -0,0 +1,162 @@ +#include "AButton.hpp" + +#include + +#include + +namespace waybar { + +AButton::AButton(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) + : AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll), + format_(config_["format"].isString() ? config_["format"].asString() : format), + interval_(config_["interval"] == "once" + ? std::chrono::seconds(100000000) + : std::chrono::seconds( + config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)), + default_format_(format_) { + button_.set_name(name); + button_.set_relief(Gtk::RELIEF_NONE); + + /* https://github.com/Alexays/Waybar/issues/1731 */ + auto css = Gtk::CssProvider::create(); + css->load_from_data( + "button { all: unset; min-width: 0; } label:disabled,button:disabled { all: inherit; } label " + "{ all: unset; }"); + button_.get_style_context()->add_provider(css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + if (!id.empty()) { + button_.get_style_context()->add_class(id); + } + event_box_.add(button_); + if (config_["max-length"].isUInt()) { + label_->set_max_width_chars(config_["max-length"].asInt()); + label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); + label_->set_single_line_mode(true); + } else if (ellipsize && label_->get_max_width_chars() == -1) { + label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); + label_->set_single_line_mode(true); + } + + if (config_["min-length"].isUInt()) { + label_->set_width_chars(config_["min-length"].asUInt()); + } + + uint rotate = 0; + + if (config_["rotate"].isUInt()) { + rotate = config["rotate"].asUInt(); + label_->set_angle(rotate); + } + + if (config_["align"].isDouble()) { + auto align = config_["align"].asFloat(); + if (rotate == 90 || rotate == 270) { + label_->set_yalign(align); + } else { + label_->set_xalign(align); + } + } + + if (!(config_["on-click"].isString() || config_["on-click-middle"].isString() || + config_["on-click-backward"].isString() || config_["on-click-forward"].isString() || + config_["on-click-right"].isString() || config_["format-alt"].isString() || enable_click)) { + button_.set_sensitive(false); + } else { + button_.signal_pressed().connect([this] { + GdkEventButton* e = (GdkEventButton*)gdk_event_new(GDK_BUTTON_PRESS); + e->button = 1; + handleToggle(e); + }); + } +} + +auto AButton::update() -> void { AModule::update(); } + +std::string AButton::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) { + auto format_icons = config_["format-icons"]; + if (format_icons.isObject()) { + if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { + format_icons = format_icons[alt]; + } else { + format_icons = format_icons["default"]; + } + } + if (format_icons.isArray()) { + auto size = format_icons.size(); + auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + format_icons = format_icons[idx]; + } + if (format_icons.isString()) { + return format_icons.asString(); + } + return ""; +} + +std::string AButton::getIcon(uint16_t percentage, const std::vector& alts, + uint16_t max) { + auto format_icons = config_["format-icons"]; + if (format_icons.isObject()) { + std::string _alt = "default"; + for (const auto& alt : alts) { + if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { + _alt = alt; + break; + } + } + format_icons = format_icons[_alt]; + } + if (format_icons.isArray()) { + auto size = format_icons.size(); + auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + format_icons = format_icons[idx]; + } + if (format_icons.isString()) { + return format_icons.asString(); + } + return ""; +} + +bool waybar::AButton::handleToggle(GdkEventButton* const& e) { + if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) { + alt_ = !alt_; + if (alt_ && config_["format-alt"].isString()) { + format_ = config_["format-alt"].asString(); + } else { + format_ = default_format_; + } + } + return AModule::handleToggle(e); +} + +std::string AButton::getState(uint8_t value, bool lesser) { + if (!config_["states"].isObject()) { + return ""; + } + // Get current state + std::vector> states; + if (config_["states"].isObject()) { + for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) { + if (it->isUInt() && it.key().isString()) { + states.emplace_back(it.key().asString(), it->asUInt()); + } + } + } + // Sort states + std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) { + return lesser ? a.second < b.second : a.second > b.second; + }); + std::string valid_state; + for (auto const& state : states) { + if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) { + button_.get_style_context()->add_class(state.first); + valid_state = state.first; + } else { + button_.get_style_context()->remove_class(state.first); + } + } + return valid_state; +} + +} // namespace waybar diff --git a/src/factory.cpp b/src/factory.cpp index 6cfa35d6..8a311d88 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -7,7 +7,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { auto hash_pos = name.find('#'); auto ref = name.substr(0, hash_pos); auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : ""; -#if defined(__linux__) && !defined(NO_FILESYSTEM) +#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) if (ref == "battery") { return new waybar::modules::Battery(id, config_[name]); } @@ -35,6 +35,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sway/language") { return new waybar::modules::sway::Language(id, config_[name]); } + if (ref == "sway/scratchpad") { + return new waybar::modules::sway::Scratchpad(id, config_[name]); + } #endif #ifdef HAVE_WLR if (ref == "wlr/taskbar") { @@ -81,6 +84,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "clock") { return new waybar::modules::Clock(id, config_[name]); } + if (ref == "user") { + return new waybar::modules::User(id, config_[name]); + } if (ref == "disk") { return new waybar::modules::Disk(id, config_[name]); } diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index 4b34824f..a3c612ca 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -73,8 +73,9 @@ void check_nn(const void *ptr, const char *message = "ptr was null") { } } // namespace -waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max) - : name_(std::move(name)), actual_(actual), max_(max) {} +waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max, + bool powered) + : name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {} std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; } @@ -86,8 +87,12 @@ int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; } void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; } +bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; } + +void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; } + waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config) - : ALabel(config, "backlight", id, "{percent}%", 2), + : AButton(config, "backlight", id, "{percent}%", 2), preferred_device_(config["device"].isString() ? config["device"].asString() : "") { // Get initial state { @@ -172,21 +177,26 @@ auto waybar::modules::Backlight::update() -> void { return; } - const uint8_t percent = - best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); - label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), - fmt::arg("icon", getIcon(percent)))); - getState(percent); + if (best->get_powered()) { + event_box_.show(); + const uint8_t percent = + best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); + label_->set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), + fmt::arg("icon", getIcon(percent)))); + getState(percent); + } else { + event_box_.hide(); + } } else { if (!previous_best_.has_value()) { return; } - label_.set_markup(""); + label_->set_markup(""); } previous_best_ = best == nullptr ? std::nullopt : std::optional{*best}; previous_format_ = format_; // Call parent update - ALabel::update(); + AButton::update(); } template @@ -215,6 +225,7 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr); const char *max = udev_device_get_sysattr_value(dev, "max_brightness"); + const char *power = udev_device_get_sysattr_value(dev, "bl_power"); auto found = std::find_if(first, last, [name](const auto &device) { return device.name() == name; }); @@ -225,10 +236,14 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, if (max != nullptr) { found->set_max(std::stoi(max)); } + if (power != nullptr) { + found->set_powered(std::stoi(power) == 0); + } } else { const int actual_int = actual == nullptr ? 0 : std::stoi(actual); const int max_int = max == nullptr ? 0 : std::stoi(max); - *inserter = BacklightDev{name, actual_int, max_int}; + const bool power_bool = power == nullptr ? true : std::stoi(power) == 0; + *inserter = BacklightDev{name, actual_int, max_int, power_bool}; ++inserter; } } diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index bd8583c5..0ddd8246 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -1,9 +1,13 @@ #include "modules/battery.hpp" - +#if defined(__FreeBSD__) +#include +#endif #include +#include waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) - : ALabel(config, "battery", id, "{capacity}%", 60) { + : AButton(config, "battery", id, "{capacity}%", 60) { +#if defined(__linux__) battery_watch_fd_ = inotify_init1(IN_CLOEXEC); if (battery_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); @@ -19,11 +23,12 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& conf if (global_watch < 0) { throw std::runtime_error("Could not watch for battery plug/unplug"); } - +#endif worker(); } waybar::modules::Battery::~Battery() { +#if defined(__linux__) std::lock_guard guard(battery_list_mutex_); if (global_watch >= 0) { @@ -39,9 +44,16 @@ waybar::modules::Battery::~Battery() { batteries_.erase(it); } close(battery_watch_fd_); +#endif } void waybar::modules::Battery::worker() { +#if defined(__FreeBSD__) + thread_timer_ = [this] { + dp.emit(); + thread_timer_.sleep_for(interval_); + }; +#else thread_timer_ = [this] { // Make sure we eventually update the list of batteries even if we miss an // inotify event for some reason @@ -68,9 +80,11 @@ void waybar::modules::Battery::worker() { refreshBatteries(); dp.emit(); }; +#endif } void waybar::modules::Battery::refreshBatteries() { +#if defined(__linux__) std::lock_guard guard(battery_list_mutex_); // Mark existing list of batteries as not necessarily found std::map check_map; @@ -108,7 +122,7 @@ void waybar::modules::Battery::refreshBatteries() { } auto adap_defined = config_["adapter"].isString(); if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) && - fs::exists(node.path() / "online")) { + (fs::exists(node.path() / "online") || fs::exists(node.path() / "status"))) { adapter_ = node.path(); } } @@ -135,6 +149,7 @@ void waybar::modules::Battery::refreshBatteries() { batteries_.erase(check.first); } } +#endif } // Unknown > Full > Not charging > Discharging > Charging @@ -156,105 +171,35 @@ const std::tuple waybar::modules::Battery::g std::lock_guard guard(battery_list_mutex_); try { - uint32_t total_power = 0; // μW - uint32_t total_energy = 0; // μWh - uint32_t total_energy_full = 0; - uint32_t total_energy_full_design = 0; - uint32_t total_capacity{0}; - std::string status = "Unknown"; - for (auto const& item : batteries_) { - auto bat = item.first; - uint32_t power_now; - uint32_t energy_full; - uint32_t energy_now; - uint32_t energy_full_design; - uint32_t capacity{0}; - std::string _status; - std::getline(std::ifstream(bat / "status"), _status); +#if defined(__FreeBSD__) + /* Allocate state of battery units reported via ACPI. */ + int battery_units = 0; + size_t battery_units_size = sizeof battery_units; + if (sysctlbyname("hw.acpi.battery.units", &battery_units, &battery_units_size, NULL, 0) != 0) { + throw std::runtime_error("sysctl hw.acpi.battery.units failed"); + } - // Some battery will report current and charge in μA/μAh. - // Scale these by the voltage to get μW/μWh. - if (fs::exists(bat / "current_now") || fs::exists(bat / "current_avg")) { - uint32_t voltage_now; - uint32_t current_now; - uint32_t charge_now; - uint32_t charge_full; - uint32_t charge_full_design; - // Some batteries have only *_avg, not *_now - if (fs::exists(bat / "voltage_now")) - std::ifstream(bat / "voltage_now") >> voltage_now; - else - std::ifstream(bat / "voltage_avg") >> voltage_now; - if (fs::exists(bat / "current_now")) - std::ifstream(bat / "current_now") >> current_now; - else - std::ifstream(bat / "current_avg") >> current_now; - std::ifstream(bat / "charge_full") >> charge_full; - std::ifstream(bat / "charge_full_design") >> charge_full_design; - if (fs::exists(bat / "charge_now")) - std::ifstream(bat / "charge_now") >> charge_now; - else { - // charge_now is missing on some systems, estimate using capacity. - uint32_t capacity; - std::ifstream(bat / "capacity") >> capacity; - charge_now = (capacity * charge_full) / 100; - } - power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000; - energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000; - energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000; - energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000; - } // Gamepads such as PS Dualshock provide the only capacity - else if (fs::exists(bat / "energy_now") && fs::exists(bat / "energy_full")) { - std::ifstream(bat / "power_now") >> power_now; - std::ifstream(bat / "energy_now") >> energy_now; - std::ifstream(bat / "energy_full") >> energy_full; - std::ifstream(bat / "energy_full_design") >> energy_full_design; - } else { - std::ifstream(bat / "capacity") >> capacity; - power_now = 0; - energy_now = 0; - energy_full = 0; - energy_full_design = 0; - } + if (battery_units < 0) { + throw std::runtime_error("No battery units"); + } - // Show the "smallest" status among all batteries - if (status_gt(status, _status)) { - status = _status; - } - total_power += power_now; - total_energy += energy_now; - total_energy_full += energy_full; - total_energy_full_design += energy_full_design; - total_capacity += capacity; + int capacity; + size_t size_capacity = sizeof capacity; + if (sysctlbyname("hw.acpi.battery.life", &capacity, &size_capacity, NULL, 0) != 0) { + throw std::runtime_error("sysctl hw.acpi.battery.life failed"); } - if (!adapter_.empty() && status == "Discharging") { - bool online; - std::ifstream(adapter_ / "online") >> online; - if (online) { - status = "Plugged"; - } + int time; + size_t size_time = sizeof time; + if (sysctlbyname("hw.acpi.battery.time", &time, &size_time, NULL, 0) != 0) { + throw std::runtime_error("sysctl hw.acpi.battery.time failed"); } - float time_remaining = 0; - if (status == "Discharging" && total_power != 0) { - time_remaining = (float)total_energy / total_power; - } else if (status == "Charging" && total_power != 0) { - time_remaining = -(float)(total_energy_full - total_energy) / total_power; - if (time_remaining > 0.0f) { - // If we've turned positive it means the battery is past 100% and so - // just report that as no time remaining - time_remaining = 0.0f; - } - } - float capacity{0.0f}; - if (total_energy_full > 0.0f) { - capacity = ((float)total_energy * 100.0f / (float)total_energy_full); - } else { - capacity = (float)total_capacity; - } - // Handle design-capacity - if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) { - capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design); + int rate; + size_t size_rate = sizeof rate; + if (sysctlbyname("hw.acpi.battery.rate", &rate, &size_rate, NULL, 0) != 0) { + throw std::runtime_error("sysctl hw.acpi.battery.rate failed"); } + + auto status = getAdapterStatus(capacity); // Handle full-at if (config_["full-at"].isUInt()) { auto full_at = config_["full-at"].asUInt(); @@ -268,13 +213,319 @@ const std::tuple waybar::modules::Battery::g capacity = 100.f; } uint8_t cap = round(capacity); - if (cap == 100 && status == "Charging") { + if (cap == 100 && status == "Plugged") { // If we've reached 100% just mark as full as some batteries can stay // stuck reporting they're still charging but not yet done status = "Full"; } + // spdlog::info("{} {} {} {}", capacity,time,status,rate); + return {capacity, time / 60.0, status, rate}; + +#elif defined(__linux__) + uint32_t total_power = 0; // μW + bool total_power_exists = false; + uint32_t total_energy = 0; // μWh + bool total_energy_exists = false; + uint32_t total_energy_full = 0; + bool total_energy_full_exists = false; + uint32_t total_energy_full_design = 0; + bool total_energy_full_design_exists = false; + uint32_t total_capacity = 0; + bool total_capacity_exists = false; + + std::string status = "Unknown"; + for (auto const& item : batteries_) { + auto bat = item.first; + std::string _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. + + uint32_t capacity = 0; + bool capacity_exists = false; + if (fs::exists(bat / "capacity")) { + capacity_exists = true; + std::ifstream(bat / "capacity") >> capacity; + } + + uint32_t current_now = 0; + bool current_now_exists = false; + if (fs::exists(bat / "current_now")) { + current_now_exists = true; + std::ifstream(bat / "current_now") >> current_now; + } else if (fs::exists(bat / "current_avg")) { + current_now_exists = true; + std::ifstream(bat / "current_avg") >> current_now; + } + + uint32_t voltage_now = 0; + bool voltage_now_exists = false; + if (fs::exists(bat / "voltage_now")) { + voltage_now_exists = true; + std::ifstream(bat / "voltage_now") >> voltage_now; + } else if (fs::exists(bat / "voltage_avg")) { + voltage_now_exists = true; + std::ifstream(bat / "voltage_avg") >> voltage_now; + } + + uint32_t charge_full = 0; + bool charge_full_exists = false; + if (fs::exists(bat / "charge_full")) { + charge_full_exists = true; + std::ifstream(bat / "charge_full") >> charge_full; + } + + uint32_t charge_full_design = 0; + bool charge_full_design_exists = false; + if (fs::exists(bat / "charge_full_design")) { + charge_full_design_exists = true; + std::ifstream(bat / "charge_full_design") >> charge_full_design; + } + + uint32_t charge_now = 0; + bool charge_now_exists = false; + if (fs::exists(bat / "charge_now")) { + charge_now_exists = true; + std::ifstream(bat / "charge_now") >> charge_now; + } + + uint32_t power_now = 0; + bool power_now_exists = false; + if (fs::exists(bat / "power_now")) { + power_now_exists = true; + std::ifstream(bat / "power_now") >> power_now; + } + + uint32_t energy_now = 0; + bool energy_now_exists = false; + if (fs::exists(bat / "energy_now")) { + energy_now_exists = true; + std::ifstream(bat / "energy_now") >> energy_now; + } + + uint32_t energy_full = 0; + bool energy_full_exists = false; + if (fs::exists(bat / "energy_full")) { + energy_full_exists = true; + std::ifstream(bat / "energy_full") >> energy_full; + } + + uint32_t energy_full_design = 0; + bool energy_full_design_exists = false; + if (fs::exists(bat / "energy_full_design")) { + energy_full_design_exists = true; + std::ifstream(bat / "energy_full_design") >> energy_full_design; + } + + if (!voltage_now_exists) { + if (power_now_exists && current_now_exists && current_now != 0) { + voltage_now_exists = true; + voltage_now = 1000000 * (uint64_t)power_now / (uint64_t)current_now; + } else if (energy_full_design_exists && charge_full_design_exists && + charge_full_design != 0) { + voltage_now_exists = true; + voltage_now = 1000000 * (uint64_t)energy_full_design / (uint64_t)charge_full_design; + } else if (energy_now_exists) { + if (charge_now_exists && charge_now != 0) { + voltage_now_exists = true; + voltage_now = 1000000 * (uint64_t)energy_now / (uint64_t)charge_now; + } else if (capacity_exists && charge_full_exists) { + charge_now_exists = true; + charge_now = (uint64_t)charge_full * (uint64_t)capacity / 100; + if (charge_full != 0 && capacity != 0) { + voltage_now_exists = true; + voltage_now = + 1000000 * (uint64_t)energy_now * 100 / (uint64_t)charge_full / (uint64_t)capacity; + } + } + } else if (energy_full_exists) { + if (charge_full_exists && charge_full != 0) { + voltage_now_exists = true; + voltage_now = 1000000 * (uint64_t)energy_full / (uint64_t)charge_full; + } else if (charge_now_exists && capacity_exists) { + if (capacity != 0) { + charge_full_exists = true; + charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity; + } + if (charge_now != 0) { + voltage_now_exists = true; + voltage_now = + 10000 * (uint64_t)energy_full * (uint64_t)capacity / (uint64_t)charge_now; + } + } + } + } + + if (!capacity_exists) { + if (charge_now_exists && charge_full_exists && charge_full != 0) { + capacity_exists = true; + capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full; + } else if (energy_now_exists && energy_full_exists && energy_full != 0) { + capacity_exists = true; + capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full; + } else if (charge_now_exists && energy_full_exists && voltage_now_exists) { + if (!charge_full_exists && voltage_now != 0) { + charge_full_exists = true; + charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now; + } + if (energy_full != 0) { + capacity_exists = true; + capacity = (uint64_t)charge_now * (uint64_t)voltage_now / 10000 / (uint64_t)energy_full; + } + } else if (charge_full_exists && energy_now_exists && voltage_now_exists) { + if (!charge_now_exists && voltage_now != 0) { + charge_now_exists = true; + charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now; + } + if (voltage_now != 0 && charge_full != 0) { + capacity_exists = true; + capacity = 100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now / + (uint64_t)charge_full; + } + } + } + + if (!energy_now_exists && voltage_now_exists) { + if (charge_now_exists) { + energy_now_exists = true; + energy_now = (uint64_t)charge_now * (uint64_t)voltage_now / 1000000; + } else if (capacity_exists && charge_full_exists) { + charge_now_exists = true; + charge_now = (uint64_t)capacity * (uint64_t)charge_full / 100; + energy_now_exists = true; + energy_now = + (uint64_t)voltage_now * (uint64_t)capacity * (uint64_t)charge_full / 1000000 / 100; + } else if (capacity_exists && energy_full) { + if (voltage_now != 0) { + charge_full_exists = true; + charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now; + charge_now_exists = true; + charge_now = (uint64_t)capacity * 10000 * (uint64_t)energy_full / (uint64_t)voltage_now; + } + energy_now_exists = true; + energy_now = (uint64_t)capacity * (uint64_t)energy_full / 100; + } + } + + if (!energy_full_exists && voltage_now_exists) { + if (charge_full_exists) { + energy_full_exists = true; + energy_full = (uint64_t)charge_full * (uint64_t)voltage_now / 1000000; + } else if (charge_now_exists && capacity_exists && capacity != 0) { + charge_full_exists = true; + charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity; + energy_full_exists = true; + energy_full = (uint64_t)charge_now * (uint64_t)voltage_now / (uint64_t)capacity / 10000; + } else if (capacity_exists && energy_now) { + if (voltage_now != 0) { + charge_now_exists = true; + charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now; + } + if (capacity != 0) { + energy_full_exists = true; + energy_full = 100 * (uint64_t)energy_now / (uint64_t)capacity; + if (voltage_now != 0) { + charge_full_exists = true; + charge_full = + 100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now / (uint64_t)capacity; + } + } + } + } + + if (!power_now_exists && voltage_now_exists && current_now_exists) { + power_now_exists = true; + power_now = (uint64_t)voltage_now * (uint64_t)current_now / 1000000; + } + + if (!energy_full_design_exists && voltage_now_exists && charge_full_design_exists) { + energy_full_design_exists = true; + energy_full_design = (uint64_t)voltage_now * (uint64_t)charge_full_design / 1000000; + } + + // Show the "smallest" status among all batteries + if (status_gt(status, _status)) status = _status; + + if (power_now_exists) { + total_power_exists = true; + total_power += power_now; + } + if (energy_now_exists) { + total_energy_exists = true; + total_energy += energy_now; + } + if (energy_full_exists) { + total_energy_full_exists = true; + total_energy_full += energy_full; + } + if (energy_full_design_exists) { + total_energy_full_design_exists = true; + total_energy_full_design += energy_full_design; + } + if (capacity_exists) { + total_capacity_exists = true; + total_capacity += capacity; + } + } + + // Give `Plugged` higher priority over `Not charging`. + // So in a setting where TLP is used, `Plugged` is shown when the threshold is reached + if (!adapter_.empty() && (status == "Discharging" || status == "Not charging")) { + bool online; + std::string current_status; + std::ifstream(adapter_ / "online") >> online; + std::getline(std::ifstream(adapter_ / "status"), current_status); + if (online && current_status != "Discharging") status = "Plugged"; + } + + float time_remaining{0.0f}; + if (status == "Discharging" && total_power_exists && total_energy_exists) { + if (total_power != 0) time_remaining = (float)total_energy / total_power; + } else if (status == "Charging" && total_energy_exists && total_energy_full_exists && + total_power_exists) { + if (total_power != 0) + time_remaining = -(float)(total_energy_full - total_energy) / total_power; + // If we've turned positive it means the battery is past 100% and so just report that as no + // time remaining + if (time_remaining > 0.0f) time_remaining = 0.0f; + } + + float calculated_capacity{0.0f}; + if (total_capacity_exists) { + if (total_capacity > 0.0f) + calculated_capacity = (float)total_capacity / batteries_.size(); + else if (total_energy_full_exists && total_energy_exists) { + if (total_energy_full > 0.0f) + calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); + } + } + + // Handle design-capacity + if ((config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) && + total_energy_exists && total_energy_full_design_exists) { + if (total_energy_full_design > 0.0f) + calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design); + } + + // Handle full-at + if (config_["full-at"].isUInt()) { + auto full_at = config_["full-at"].asUInt(); + if (full_at < 100) calculated_capacity = 100.f * calculated_capacity / full_at; + } + + // Handle it gracefully by clamping at 100% + // This can happen when the battery is calibrating and goes above 100% + if (calculated_capacity > 100.f) calculated_capacity = 100.f; + + uint8_t cap = round(calculated_capacity); + // If we've reached 100% just mark as full as some batteries can stay stuck reporting they're + // still charging but not yet done + if (cap == 100 && status == "Charging") status = "Full"; + return {cap, time_remaining, status, total_power / 1e6}; +#endif } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); return {0, 0, "Unknown", 0}; @@ -282,13 +533,26 @@ const std::tuple waybar::modules::Battery::g } const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const { +#if defined(__FreeBSD__) + int state; + size_t size_state = sizeof state; + if (sysctlbyname("hw.acpi.battery.state", &state, &size_state, NULL, 0) != 0) { + throw std::runtime_error("sysctl hw.acpi.battery.state failed"); + } + bool online = state == 2; + std::string status{"Unknown"}; // TODO: add status in FreeBSD + { +#else if (!adapter_.empty()) { bool online; + std::string status; std::ifstream(adapter_ / "online") >> online; + std::getline(std::ifstream(adapter_ / "status"), status); +#endif if (capacity == 100) { return "Full"; } - if (online) { + if (online && status != "Discharging") { return "Plugged"; } return "Discharging"; @@ -309,14 +573,17 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai format = config_["format-time"].asString(); } std::string zero_pad_minutes = fmt::format("{:02d}", minutes); - return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), fmt::arg("m", zero_pad_minutes)); + return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes), + fmt::arg("m", zero_pad_minutes)); } auto waybar::modules::Battery::update() -> void { +#if defined(__linux__) if (batteries_.empty()) { event_box_.hide(); return; } +#endif auto [capacity, time_remaining, status, power] = getInfos(); if (status == "Unknown") { status = getAdapterStatus(capacity); @@ -346,14 +613,14 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), - fmt::arg("capacity", capacity), - fmt::arg("time", time_remaining_formatted))); + button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), + fmt::arg("power", power), fmt::arg("capacity", capacity), + fmt::arg("time", time_remaining_formatted))); } if (!old_status_.empty()) { - label_.get_style_context()->remove_class(old_status_); + button_.get_style_context()->remove_class(old_status_); } - label_.get_style_context()->add_class(status); + button_.get_style_context()->add_class(status); old_status_ = status; if (!state.empty() && config_["format-" + status + "-" + state].isString()) { format = config_["format-" + status + "-" + state].asString(); @@ -367,10 +634,10 @@ auto waybar::modules::Battery::update() -> void { } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; - label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), - fmt::arg("icon", getIcon(capacity, icons)), - fmt::arg("time", time_remaining_formatted))); + label_->set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), + fmt::arg("icon", getIcon(capacity, icons)), + fmt::arg("time", time_remaining_formatted))); } // Call parent update - ALabel::update(); + AButton::update(); } diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 7a640f09..a1233361 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -80,7 +80,7 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned } // namespace waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config) - : ALabel(config, "bluetooth", id, " {status}", 10), + : AButton(config, "bluetooth", id, " {status}", 10), #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_BLUETOOTH}, #endif @@ -101,6 +101,7 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& } else { spdlog::error("findCurController() failed: no bluetooth controller found"); } + event_box_.hide(); return; } findConnectedDevices(cur_controller_.path, connected_devices_); @@ -189,10 +190,10 @@ auto waybar::modules::Bluetooth::update() -> void { format_.empty() ? event_box_.hide() : event_box_.show(); auto update_style_context = [this](const std::string& style_class, bool in_next_state) { - if (in_next_state && !label_.get_style_context()->has_class(style_class)) { - label_.get_style_context()->add_class(style_class); - } else if (!in_next_state && label_.get_style_context()->has_class(style_class)) { - label_.get_style_context()->remove_class(style_class); + if (in_next_state && !button_.get_style_context()->has_class(style_class)) { + button_.get_style_context()->add_class(style_class); + } else if (!in_next_state && button_.get_style_context()->has_class(style_class)) { + button_.get_style_context()->remove_class(style_class); } }; update_style_context("discoverable", cur_controller_.discoverable); @@ -204,7 +205,7 @@ auto waybar::modules::Bluetooth::update() -> void { update_style_context(state, true); state_ = state; - label_.set_markup(fmt::format( + label_->set_markup(fmt::format( format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address_type", cur_controller_.address_type), @@ -245,7 +246,7 @@ auto waybar::modules::Bluetooth::update() -> void { device_enumerate_.erase(0, 1); } } - label_.set_tooltip_text(fmt::format( + button_.set_tooltip_text(fmt::format( tooltip_format, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_.address), @@ -259,7 +260,7 @@ auto waybar::modules::Bluetooth::update() -> void { } // Call parent update - ALabel::update(); + AButton::update(); } // NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 71b24c19..80c02a45 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -18,7 +18,7 @@ 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), + : AButton(config, "clock", id, "{:%H:%M}", 60, false, false, true), current_time_zone_idx_(0), is_calendar_in_tooltip_(false), is_timezoned_list_in_tooltip_(false) { @@ -107,7 +107,7 @@ auto waybar::modules::Clock::update() -> void { } else { text = fmt::format(format_, wtime); } - label_.set_markup(text); + label_->set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { @@ -119,12 +119,12 @@ auto waybar::modules::Clock::update() -> void { 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); + button_.set_tooltip_markup(text); } } // Call parent update - ALabel::update(); + AButton::update(); } bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { diff --git a/src/modules/cpu/common.cpp b/src/modules/cpu/common.cpp index cdbbc3d4..8201ed09 100644 --- a/src/modules/cpu/common.cpp +++ b/src/modules/cpu/common.cpp @@ -10,7 +10,7 @@ #endif waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) - : ALabel(config, "cpu", id, "{usage}%", 10) { + : AButton(config, "cpu", id, "{usage}%", 10) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -23,7 +23,7 @@ auto waybar::modules::Cpu::update() -> void { auto [cpu_usage, tooltip] = getCpuUsage(); auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency(); if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); + button_.set_tooltip_text(tooltip); } auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; @@ -52,11 +52,11 @@ auto waybar::modules::Cpu::update() -> void { auto icon_format = fmt::format("icon{}", core_i); store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); } - label_.set_markup(fmt::vformat(format, store)); + label_->set_markup(fmt::vformat(format, store)); } // Call parent update - ALabel::update(); + AButton::update(); } double waybar::modules::Cpu::getCpuLoad() { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 6fc01675..397298b2 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -4,7 +4,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, const Json::Value& config) - : ALabel(config, "custom-" + name, id, "{}"), + : AButton(config, "custom-" + name, id, "{}"), name_(name), id_(id), percentage_(0), @@ -103,13 +103,13 @@ void waybar::modules::Custom::handleEvent() { } bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) { - auto ret = ALabel::handleScroll(e); + auto ret = AButton::handleScroll(e); handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { - auto ret = ALabel::handleToggle(e); + auto ret = AButton::handleToggle(e); handleEvent(); return ret; } @@ -131,31 +131,33 @@ auto waybar::modules::Custom::update() -> void { if (str.empty()) { event_box_.hide(); } else { - label_.set_markup(str); + label_->set_markup(str); if (tooltipEnabled()) { if (text_ == tooltip_) { - if (label_.get_tooltip_markup() != str) { - label_.set_tooltip_markup(str); + if (button_.get_tooltip_markup() != str) { + button_.set_tooltip_markup(str); } } else { - if (label_.get_tooltip_markup() != tooltip_) { - label_.set_tooltip_markup(tooltip_); + if (button_.get_tooltip_markup() != tooltip_) { + button_.set_tooltip_markup(tooltip_); } } } - auto classes = label_.get_style_context()->list_classes(); + auto classes = button_.get_style_context()->list_classes(); for (auto const& c : classes) { if (c == id_) continue; - label_.get_style_context()->remove_class(c); + button_.get_style_context()->remove_class(c); } for (auto const& c : class_) { - label_.get_style_context()->add_class(c); + button_.get_style_context()->add_class(c); } + button_.get_style_context()->add_class("flat"); + button_.get_style_context()->add_class("text-button"); event_box_.show(); } } // Call parent update - ALabel::update(); + AButton::update(); } void waybar::modules::Custom::parseOutputRaw() { diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 5578dc2f..626378ca 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -3,7 +3,7 @@ using namespace waybar::util; waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config) - : ALabel(config, "disk", id, "{}%", 30), path_("/") { + : AButton(config, "disk", id, "{}%", 30), path_("/") { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -58,7 +58,7 @@ auto waybar::modules::Disk::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup( + label_->set_markup( fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), @@ -70,12 +70,12 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text( + button_.set_tooltip_text( fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), fmt::arg("path", path_))); } // Call parent update - ALabel::update(); + AButton::update(); } diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index c7accd58..7129297d 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -109,7 +109,7 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config) } Gamemode::~Gamemode() { - if (gamemode_proxy) gamemode_proxy->unreference(); + if (gamemode_proxy) gamemode_proxy.reset(); if (gamemodeWatcher_id > 0) { Gio::DBus::unwatch_name(gamemodeWatcher_id); gamemodeWatcher_id = 0; diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index ae73a252..76c071c0 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -95,15 +95,37 @@ void IPC::parseIPC(const std::string& ev) { for (auto& [eventname, handler] : callbacks) { if (eventname == request) { - handler(ev); + handler->onEvent(ev); } } } -void IPC::registerForIPC(const std::string& ev, std::function fn) { +void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { + if (!ev_handler) { + return; + } callbackMutex.lock(); - callbacks.emplace_back(std::make_pair(ev, fn)); + callbacks.emplace_back(std::make_pair(ev, ev_handler)); + + callbackMutex.unlock(); +} + +void IPC::unregisterForIPC(EventHandler* ev_handler) { + if (!ev_handler) { + return; + } + + callbackMutex.lock(); + + for (auto it = callbacks.begin(); it != callbacks.end();) { + auto it_current = it; + it++; + auto& [eventname, handler] = *it_current; + if (handler == ev_handler) { + callbacks.erase(it_current); + } + } callbackMutex.unlock(); } @@ -168,4 +190,4 @@ std::string IPC::getSocket1Reply(const std::string& rq) { return std::string(buffer); } -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 5463508b..b6d7c0fa 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -4,12 +4,14 @@ #include #include +#include + #include "modules/hyprland/backend.hpp" namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + : AButton(config, "language", id, "{}", 0, true), bar_(bar) { modulesReady = true; if (!gIPC.get()) { @@ -19,24 +21,30 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con // get the active layout when open initLanguage(); - label_.hide(); - ALabel::update(); + button_.hide(); + AButton::update(); // register for hyprland ipc - gIPC->registerForIPC("activelayout", [&](const std::string& ev) { this->onEvent(ev); }); + gIPC->registerForIPC("activelayout", this); +} + +Language::~Language() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lg(mutex_); } auto Language::update() -> void { std::lock_guard lg(mutex_); if (!format_.empty()) { - label_.show(); - label_.set_markup(layoutName_); + button_.show(); + label_->set_markup(layoutName_); } else { - label_.hide(); + button_.hide(); } - ALabel::update(); + AButton::update(); } void Language::onEvent(const std::string& ev) { @@ -48,16 +56,6 @@ void Language::onEvent(const std::string& ev) { if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString()) return; // ignore - auto replaceAll = [](std::string str, const std::string& from, - const std::string& to) -> std::string { - size_t start_pos = 0; - while ((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - return str; - }; - const auto BRIEFNAME = getShortFrom(layoutName); if (config_.isMember("format-" + BRIEFNAME)) { @@ -67,7 +65,7 @@ void Language::onEvent(const std::string& ev) { layoutName = fmt::format(format_, layoutName); } - layoutName = replaceAll(layoutName, "&", "&"); + layoutName = waybar::util::sanitize_string(layoutName); if (layoutName == layoutName_) return; @@ -128,4 +126,4 @@ std::string Language::getShortFrom(const std::string& fullName) { return ""; } -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index c2923335..c3bebe49 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -2,13 +2,20 @@ #include +#include +#include + #include "modules/hyprland/backend.hpp" +#include "util/command.hpp" +#include "util/json.hpp" +#include "util/rewrite_title.hpp" namespace waybar::modules::hyprland { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "window", id, "{}", 0, true), bar_(bar) { modulesReady = true; + separate_outputs = config["separate-outputs"].as(); if (!gIPC.get()) { gIPC = std::make_unique(); @@ -18,7 +25,13 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) ALabel::update(); // register for hyprland ipc - gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); }); + gIPC->registerForIPC("activewindow", this); +} + +Window::~Window() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lg(mutex_); } auto Window::update() -> void { @@ -27,7 +40,8 @@ auto Window::update() -> void { if (!format_.empty()) { label_.show(); - label_.set_markup(fmt::format(format_, lastView)); + label_.set_markup( + fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); } else { label_.hide(); } @@ -35,21 +49,45 @@ auto Window::update() -> void { ALabel::update(); } +uint Window::getActiveWorkspaceID(std::string monitorName) { + auto cmd = waybar::util::command::exec("hyprctl monitors -j"); + assert(cmd.exit_code == 0); + Json::Value json = parser_.parse(cmd.out); + assert(json.isArray()); + auto monitor = std::find_if(json.begin(), json.end(), + [&](Json::Value monitor) { return monitor["name"] == monitorName; }); + if (monitor == std::end(json)) { + return 0; + } + return (*monitor)["activeWorkspace"]["id"].as(); +} + +std::string Window::getLastWindowTitle(uint workspaceID) { + auto cmd = waybar::util::command::exec("hyprctl workspaces -j"); + assert(cmd.exit_code == 0); + Json::Value json = parser_.parse(cmd.out); + assert(json.isArray()); + auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) { + return workspace["id"].as() == workspaceID; + }); + + if (workspace == std::end(json)) { + return ""; + } + return (*workspace)["lastwindowtitle"].as(); +} + void Window::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); - auto windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256); - auto replaceAll = [](std::string str, const std::string& from, - const std::string& to) -> std::string { - size_t start_pos = 0; - while ((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - return str; - }; + std::string windowName; + if (separate_outputs) { + windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name)); + } else { + windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256); + } - windowName = replaceAll(windowName, "&", "&"); + windowName = waybar::util::sanitize_string(windowName); if (windowName == lastView) return; @@ -59,5 +97,4 @@ void Window::onEvent(const std::string& ev) { dp.emit(); } - -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 3302abb6..0c82a08e 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -8,7 +8,7 @@ bool waybar::modules::IdleInhibitor::status = false; waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "idle_inhibitor", id, "{status}"), + : AButton(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), pid_(-1) { @@ -16,6 +16,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& throw std::runtime_error("idle-inhibit not available"); } + if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() && + config_["start-activated"].asBool() != status) { + toggleStatus(); + } + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); @@ -44,13 +49,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status if (status) { - label_.get_style_context()->remove_class("deactivated"); + button_.get_style_context()->remove_class("deactivated"); if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( waybar::Client::inst()->idle_inhibit_manager, bar_.surface); } } else { - label_.get_style_context()->remove_class("activated"); + button_.get_style_context()->remove_class("activated"); if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; @@ -58,11 +63,11 @@ auto waybar::modules::IdleInhibitor::update() -> void { } std::string status_text = status ? "activated" : "deactivated"; - 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); + label_->set_markup(fmt::format(format_, fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); + button_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_markup( + button_.set_tooltip_markup( status ? fmt::format(config_["tooltip-format-activated"].isString() ? config_["tooltip-format-activated"].asString() : "{status}", @@ -75,37 +80,41 @@ auto waybar::modules::IdleInhibitor::update() -> void { fmt::arg("icon", getIcon(0, status_text)))); } // Call parent update - ALabel::update(); + AButton::update(); +} + +void waybar::modules::IdleInhibitor::toggleStatus() { + status = !status; + + if (timeout_.connected()) { + /* cancel any already active timeout handler */ + timeout_.disconnect(); + } + + if (status && config_["timeout"].isNumeric()) { + auto timeoutMins = config_["timeout"].asDouble(); + int timeoutSecs = timeoutMins * 60; + + timeout_ = Glib::signal_timeout().connect_seconds( + []() { + /* intentionally not tied to a module instance lifetime + * as the output with `this` can be disconnected + */ + spdlog::info("deactivating idle_inhibitor by timeout"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + /* disconnect */ + return false; + }, + timeoutSecs); + } } bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - status = !status; - - if (timeout_.connected()) { - /* cancel any already active timeout handler */ - timeout_.disconnect(); - } - - if (status && config_["timeout"].isNumeric()) { - auto timeoutMins = config_["timeout"].asDouble(); - int timeoutSecs = timeoutMins * 60; - - timeout_ = Glib::signal_timeout().connect_seconds( - []() { - /* intentionally not tied to a module instance lifetime - * as the output with `this` can be disconnected - */ - spdlog::info("deactivating idle_inhibitor by timeout"); - status = false; - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - module->update(); - } - /* disconnect */ - return false; - }, - timeoutSecs); - } + toggleStatus(); // Make all other idle inhibitor modules update for (auto const& module : waybar::modules::IdleInhibitor::modules) { @@ -115,6 +124,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { } } - ALabel::handleToggle(e); + AButton::handleToggle(e); return true; } diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index e4340b14..73af5037 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -98,7 +98,7 @@ auto getInhibitors(const Json::Value& config) -> std::string { namespace waybar::modules { Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "inhibitor", id, "{status}", true), + : AButton(config, "inhibitor", id, "{status}", true), dbus_(::dbus()), inhibitors_(::getInhibitors(config)) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); @@ -117,16 +117,16 @@ 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); + button_.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)))); + button_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_text(status_text); + button_.set_tooltip_text(status_text); } - return ALabel::update(); + return AButton::update(); } auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { @@ -142,7 +142,7 @@ auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { } } - return ALabel::handleToggle(e); + return AButton::handleToggle(e); } } // namespace waybar::modules diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 38faacd6..b2750b68 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -8,8 +8,13 @@ extern "C" { #include +#include +#include +#include +#include #include #include +#include } class errno_error : public std::runtime_error { @@ -99,8 +104,18 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& icon_unlocked_(config_["format-icons"]["unlocked"].isString() ? config_["format-icons"]["unlocked"].asString() : "unlocked"), - fd_(0), - dev_(nullptr) { + devices_path_("/dev/input/"), + libinput_(nullptr), + libinput_devices_({}) { + static struct libinput_interface interface = { + [](const char* path, int flags, void* user_data) { return open(path, flags); }, + [](int fd, void* user_data) { close(fd); }}; + if (config_["interval"].isUInt()) { + spdlog::warn("keyboard-state: interval is deprecated"); + } + + libinput_ = libinput_path_create_context(&interface, NULL); + box_.set_name("keyboard-state"); if (config_["numlock"].asBool()) { numlock_label_.get_style_context()->add_class("numlock"); @@ -121,70 +136,135 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& if (config_["device-path"].isString()) { std::string dev_path = config_["device-path"].asString(); - 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 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 { - 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 errno_error(errno, "Failed to find keyboard device"); + tryAddDevice(dev_path); + if (libinput_devices_.empty()) { + spdlog::error("keyboard-state: Cannot find device {}", dev_path); } } - thread_ = [this] { + DIR* dev_dir = opendir(devices_path_.c_str()); + if (dev_dir == nullptr) { + throw errno_error(errno, "Failed to open " + devices_path_); + } + dirent* ep; + while ((ep = readdir(dev_dir))) { + if (ep->d_type == DT_DIR) continue; + std::string dev_path = devices_path_ + ep->d_name; + tryAddDevice(dev_path); + } + + if (libinput_devices_.empty()) { + throw errno_error(errno, "Failed to find keyboard device"); + } + + libinput_thread_ = [this] { dp.emit(); - thread_.sleep_for(interval_); + while (1) { + struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0}; + poll(&fd, 1, -1); + libinput_dispatch(libinput_); + struct libinput_event* event; + while ((event = libinput_get_event(libinput_))) { + auto type = libinput_event_get_type(event); + if (type == LIBINPUT_EVENT_KEYBOARD_KEY) { + auto keyboard_event = libinput_event_get_keyboard_event(event); + auto state = libinput_event_keyboard_get_key_state(keyboard_event); + if (state == LIBINPUT_KEY_STATE_RELEASED) { + uint32_t key = libinput_event_keyboard_get_key(keyboard_event); + switch (key) { + case KEY_CAPSLOCK: + case KEY_NUMLOCK: + case KEY_SCROLLLOCK: + dp.emit(); + break; + default: + break; + } + } + } + libinput_event_destroy(event); + } + } + }; + + hotplug_thread_ = [this] { + int fd; + fd = inotify_init(); + if (fd < 0) { + spdlog::error("Failed to initialize inotify: {}", strerror(errno)); + return; + } + inotify_add_watch(fd, devices_path_.c_str(), IN_CREATE | IN_DELETE); + while (1) { + int BUF_LEN = 1024 * (sizeof(struct inotify_event) + 16); + char buf[BUF_LEN]; + int length = read(fd, buf, 1024); + if (length < 0) { + spdlog::error("Failed to read inotify: {}", strerror(errno)); + return; + } + for (int i = 0; i < length;) { + struct inotify_event* event = (struct inotify_event*)&buf[i]; + std::string dev_path = devices_path_ + event->name; + if (event->mask & IN_CREATE) { + // Wait for device setup + int timeout = 10; + while (timeout--) { + try { + int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + closeFile(fd); + break; + } catch (const errno_error& e) { + if (e.code == EACCES) { + sleep(1); + } + } + } + tryAddDevice(dev_path); + } else if (event->mask & IN_DELETE) { + auto it = libinput_devices_.find(dev_path); + if (it != libinput_devices_.end()) { + spdlog::info("Keyboard {} has been removed.", dev_path); + libinput_devices_.erase(it); + } + } + i += sizeof(struct inotify_event) + event->len; + } + } }; } waybar::modules::KeyboardState::~KeyboardState() { - libevdev_free(dev_); - try { - closeFile(fd_); - } catch (const std::runtime_error& e) { - spdlog::warn(e.what()); + for (const auto& [_, dev_ptr] : libinput_devices_) { + libinput_path_remove_device(dev_ptr); } } auto waybar::modules::KeyboardState::update() -> void { - int err = LIBEVDEV_READ_STATUS_SUCCESS; - while (err == LIBEVDEV_READ_STATUS_SUCCESS) { - input_event ev; - err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev); - while (err == LIBEVDEV_READ_STATUS_SYNC) { - err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev); + sleep(0); // Wait for keyboard status change + int numl = 0, capsl = 0, scrolll = 0; + + try { + std::string dev_path; + if (config_["device-path"].isString() && + libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { + dev_path = config_["device-path"].asString(); + } else { + dev_path = libinput_devices_.begin()->first; + } + int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + auto dev = openDevice(fd); + numl = libevdev_get_event_value(dev, EV_LED, LED_NUML); + capsl = libevdev_get_event_value(dev, EV_LED, LED_CAPSL); + scrolll = libevdev_get_event_value(dev, EV_LED, LED_SCROLLL); + libevdev_free(dev); + closeFile(fd); + } 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()); } } - if (-err != EAGAIN) { - throw errno_error(-err, "Failed to sync evdev device"); - } - - int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML); - int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL); - int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL); struct { bool state; @@ -211,3 +291,25 @@ auto waybar::modules::KeyboardState::update() -> void { AModule::update(); } + +auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) -> void { + try { + int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + auto dev = openDevice(fd); + if (supportsLockStates(dev)) { + spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); + if (libinput_devices_.find(dev_path) == libinput_devices_.end()) { + auto device = libinput_path_add_device(libinput_, dev_path.c_str()); + libinput_device_ref(device); + libinput_devices_[dev_path] = device; + } + } + libevdev_free(dev); + closeFile(fd); + } 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()); + } + } +} diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 6bf84e86..8f190d2d 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -1,7 +1,7 @@ #include "modules/memory.hpp" waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) - : ALabel(config, "memory", id, "{}%", 30) { + : AButton(config, "memory", id, "{}%", 30) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -31,17 +31,18 @@ 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); + float total_ram_gigabytes = + 0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB + float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76); 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); + float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76); + float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76); + float available_ram_gigabytes = 0.01 * round(memfree / 10485.76); + float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76); auto format = format_; auto state = getState(used_ram_percentage); @@ -54,7 +55,7 @@ auto waybar::modules::Memory::update() -> void { } else { event_box_.show(); auto icons = std::vector{state}; - label_.set_markup(fmt::format( + label_->set_markup(fmt::format( format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), @@ -66,7 +67,7 @@ auto waybar::modules::Memory::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_text(fmt::format( + button_.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), @@ -74,12 +75,12 @@ auto waybar::modules::Memory::update() -> void { 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)); + button_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); } } } else { event_box_.hide(); } // Call parent update - ALabel::update(); + AButton::update(); } diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 0f58343d..04d7a46d 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -4,6 +4,9 @@ #include #include +#include +using namespace waybar::util; + #include "modules/mpd/state.hpp" #if defined(MPD_NOINLINE) namespace waybar::modules { @@ -12,7 +15,7 @@ namespace waybar::modules { #endif waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) - : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), + : AButton(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true), module_name_(id.empty() ? "mpd" : "mpd#" + id), server_(nullptr), port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), @@ -44,7 +47,7 @@ auto waybar::modules::MPD::update() -> void { context_.update(); // Call parent update - ALabel::update(); + AButton::update(); } void waybar::modules::MPD::queryMPD() { @@ -85,15 +88,15 @@ std::string waybar::modules::MPD::getFilename() const { void waybar::modules::MPD::setLabel() { if (connection_ == nullptr) { - label_.get_style_context()->add_class("disconnected"); - label_.get_style_context()->remove_class("stopped"); - label_.get_style_context()->remove_class("playing"); - label_.get_style_context()->remove_class("paused"); + button_.get_style_context()->add_class("disconnected"); + button_.get_style_context()->remove_class("stopped"); + button_.get_style_context()->remove_class("playing"); + button_.get_style_context()->remove_class("paused"); auto format = config_["format-disconnected"].isString() ? config_["format-disconnected"].asString() : "disconnected"; - label_.set_markup(format); + label_->set_markup(format); if (tooltipEnabled()) { std::string tooltip_format; @@ -101,11 +104,11 @@ void waybar::modules::MPD::setLabel() { ? config_["tooltip-format-disconnected"].asString() : "MPD (disconnected)"; // Nothing to format - label_.set_tooltip_text(tooltip_format); + button_.set_tooltip_text(tooltip_format); } return; } else { - label_.get_style_context()->remove_class("disconnected"); + button_.get_style_context()->remove_class("disconnected"); } auto format = format_; @@ -118,29 +121,29 @@ void waybar::modules::MPD::setLabel() { if (stopped()) { format = config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped"; - label_.get_style_context()->add_class("stopped"); - label_.get_style_context()->remove_class("playing"); - label_.get_style_context()->remove_class("paused"); + button_.get_style_context()->add_class("stopped"); + button_.get_style_context()->remove_class("playing"); + button_.get_style_context()->remove_class("paused"); } else { - label_.get_style_context()->remove_class("stopped"); + button_.get_style_context()->remove_class("stopped"); if (playing()) { - label_.get_style_context()->add_class("playing"); - label_.get_style_context()->remove_class("paused"); + button_.get_style_context()->add_class("playing"); + button_.get_style_context()->remove_class("paused"); } else if (paused()) { format = config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); - label_.get_style_context()->add_class("paused"); - label_.get_style_context()->remove_class("playing"); + button_.get_style_context()->add_class("paused"); + button_.get_style_context()->remove_class("playing"); } stateIcon = getStateIcon(); - artist = getTag(MPD_TAG_ARTIST); - album_artist = getTag(MPD_TAG_ALBUM_ARTIST); - album = getTag(MPD_TAG_ALBUM); - title = getTag(MPD_TAG_TITLE); - date = getTag(MPD_TAG_DATE); - filename = getFilename(); + artist = sanitize_string(getTag(MPD_TAG_ARTIST)); + album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST)); + album = sanitize_string(getTag(MPD_TAG_ALBUM)); + title = sanitize_string(getTag(MPD_TAG_TITLE)); + date = sanitize_string(getTag(MPD_TAG_DATE)); + filename = sanitize_string(getFilename()); song_pos = mpd_status_get_song_pos(status_.get()) + 1; volume = mpd_status_get_volume(status_.get()); if (volume < 0) { @@ -166,17 +169,15 @@ void waybar::modules::MPD::setLabel() { if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt()); try { - label_.set_markup(fmt::format( - format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()), - fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()), - fmt::arg("album", Glib::Markup::escape_text(album).raw()), - fmt::arg("title", Glib::Markup::escape_text(title).raw()), - fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("volume", volume), - fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), - fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), - fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), - fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), - fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename))); + label_->set_markup(fmt::format( + format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), + fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), + fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), + fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), + fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), + fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), + fmt::arg("filename", filename))); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error: {}", e.what()); } @@ -195,7 +196,7 @@ void waybar::modules::MPD::setLabel() { fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon)); - label_.set_tooltip_text(tooltip_text); + button_.set_tooltip_text(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 3fe4a8b0..c787d5bd 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -78,7 +78,7 @@ waybar::modules::Network::readBandwidthUsage() { } waybar::modules::Network::Network(const std::string &id, const Json::Value &config) - : ALabel(config, "network", id, DEFAULT_FORMAT, 60), + : AButton(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), @@ -95,11 +95,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf #endif frequency_(0.0) { - // Start with some "text" in the module's label_, update() will then + // Start with some "text" in the module's label_-> update() will then // update it. Since the text should be different, update() will be able // to show or hide the event_box_. This is to work around the case where // the module start with no text, but the the event_box_ is shown. - label_.set_markup(""); + label_->set_markup(""); auto bandwidth = readBandwidthUsage(); if (bandwidth.has_value()) { @@ -309,8 +309,8 @@ auto waybar::modules::Network::update() -> void { if (!alt_) { auto state = getNetworkState(); - if (!state_.empty() && label_.get_style_context()->has_class(state_)) { - label_.get_style_context()->remove_class(state_); + if (!state_.empty() && button_.get_style_context()->has_class(state_)) { + button_.get_style_context()->remove_class(state_); } if (config_["format-" + state].isString()) { default_format_ = config_["format-" + state].asString(); @@ -322,8 +322,8 @@ auto waybar::modules::Network::update() -> void { if (config_["tooltip-format-" + state].isString()) { tooltip_format = config_["tooltip-format-" + state].asString(); } - if (!label_.get_style_context()->has_class(state)) { - label_.get_style_context()->add_class(state); + if (!button_.get_style_context()->has_class(state)) { + button_.get_style_context()->add_class(state); } format_ = default_format_; state_ = state; @@ -349,8 +349,8 @@ auto waybar::modules::Network::update() -> void { fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), fmt::arg("bandwidthTotalBytes", pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); - if (text.compare(label_.get_label()) != 0) { - label_.set_markup(text); + if (text.compare(label_->get_label()) != 0) { + label_->set_markup(text); if (text.empty()) { event_box_.hide(); } else { @@ -382,16 +382,16 @@ auto waybar::modules::Network::update() -> void { fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), fmt::arg("bandwidthTotalBytes", pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); - if (label_.get_tooltip_text() != tooltip_text) { - label_.set_tooltip_markup(tooltip_text); + if (button_.get_tooltip_text() != tooltip_text) { + button_.set_tooltip_markup(tooltip_text); } - } else if (label_.get_tooltip_text() != text) { - label_.set_tooltip_markup(text); + } else if (button_.get_tooltip_text() != text) { + button_.set_tooltip_markup(text); } } // Call parent update - ALabel::update(); + AButton::update(); } bool waybar::modules::Network::checkInterface(std::string name) { @@ -646,7 +646,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { if (has_gateway && !has_destination && temp_idx != -1) { // Check if this is the first default route we see, or if this new // route have a higher priority. - if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) { + /** Module doesn`t update state, because RTA_GATEWAY call before enable new router and set + higher priority. Disable router -> RTA_GATEWAY -> up new router -> set higher priority added + checking route id + **/ + if (!is_del_event && + ((net->ifid_ == -1) || (priority < net->route_priority) || (net->ifid_ != temp_idx))) { // Clear if's state for the case were there is a higher priority // route on a different interface. net->clearIface(); diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 24b858d3..95f5ee53 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -1,7 +1,7 @@ #include "modules/pulseaudio.hpp" waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config) - : ALabel(config, "pulseaudio", id, "{volume}%"), + : AButton(config, "pulseaudio", id, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr), context_(nullptr), @@ -36,6 +36,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value } waybar::modules::Pulseaudio::~Pulseaudio() { + pa_context_disconnect(context_); mainloop_api_->quit(mainloop_api_, 0); pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_free(mainloop_); @@ -91,19 +92,31 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; int max_volume = 100; + double step = 1; // isDouble returns true for integers as well, just in case if (config_["scroll-step"].isDouble()) { - change = round(config_["scroll-step"].asDouble() * volume_tick); + step = config_["scroll-step"].asDouble(); } if (config_["max-volume"].isInt()) { - max_volume = std::min(0, config_["max-volume"].asInt()); + max_volume = std::min(config_["max-volume"].asInt(), static_cast(PA_VOLUME_UI_MAX)); } + if (dir == SCROLL_DIR::UP) { - if (volume_ + 1 <= max_volume) { + if (volume_ < max_volume) { + if (volume_ + step > max_volume) { + change = round((max_volume - volume_) * volume_tick); + } else { + change = round(step * volume_tick); + } pa_cvolume_inc(&pa_volume, change); } } else if (dir == SCROLL_DIR::DOWN) { - if (volume_ - 1 >= 0) { + if (volume_ > 0) { + if (volume_ - step < 0) { + change = round(volume_ * volume_tick); + } else { + change = round(step * volume_tick); + } pa_cvolume_dec(&pa_volume, change); } } @@ -170,6 +183,15 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_ if (i == nullptr) return; auto pa = static_cast(data); + + if (pa->config_["ignored-sinks"].isArray()) { + for (const auto &ignored_sink : pa->config_["ignored-sinks"]) { + if (ignored_sink.asString() == i->description) { + return; + } + } + } + if (pa->current_sink_name_ == i->name) { if (i->state != PA_SINK_RUNNING) { pa->current_sink_running_ = false; @@ -240,9 +262,9 @@ auto waybar::modules::Pulseaudio::update() -> void { if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire format_name = format_name + "-bluetooth"; - label_.get_style_context()->add_class("bluetooth"); + button_.get_style_context()->add_class("bluetooth"); } else { - label_.get_style_context()->remove_class("bluetooth"); + button_.get_style_context()->remove_class("bluetooth"); } if (muted_) { // Check muted bluetooth format exist, otherwise fallback to default muted format @@ -250,29 +272,29 @@ auto waybar::modules::Pulseaudio::update() -> void { format_name = "format"; } format_name = format_name + "-muted"; - label_.get_style_context()->add_class("muted"); - label_.get_style_context()->add_class("sink-muted"); + button_.get_style_context()->add_class("muted"); + button_.get_style_context()->add_class("sink-muted"); } else { - label_.get_style_context()->remove_class("muted"); - label_.get_style_context()->remove_class("sink-muted"); + button_.get_style_context()->remove_class("muted"); + button_.get_style_context()->remove_class("sink-muted"); } format = config_[format_name].isString() ? config_[format_name].asString() : format; } // TODO: find a better way to split source/sink std::string format_source = "{volume}%"; if (source_muted_) { - label_.get_style_context()->add_class("source-muted"); + button_.get_style_context()->add_class("source-muted"); if (config_["format-source-muted"].isString()) { format_source = config_["format-source-muted"].asString(); } } else { - label_.get_style_context()->remove_class("source-muted"); + button_.get_style_context()->remove_class("source-muted"); if (config_["format-source-muted"].isString()) { format_source = config_["format-source"].asString(); } } format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); - label_.set_markup(fmt::format( + label_->set_markup(fmt::format( format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); @@ -283,16 +305,16 @@ auto waybar::modules::Pulseaudio::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format( + button_.set_tooltip_text(fmt::format( tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); } else { - label_.set_tooltip_text(desc_); + button_.set_tooltip_text(desc_); } } // Call parent update - ALabel::update(); + AButton::update(); } diff --git a/src/modules/simpleclock.cpp b/src/modules/simpleclock.cpp index 27c7ac77..f6fc17fb 100644 --- a/src/modules/simpleclock.cpp +++ b/src/modules/simpleclock.cpp @@ -3,7 +3,7 @@ #include waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) - : ALabel(config, "clock", id, "{:%H:%M}", 60) { + : AButton(config, "clock", id, "{:%H:%M}", 60) { thread_ = [this] { dp.emit(); auto now = std::chrono::system_clock::now(); @@ -19,17 +19,17 @@ auto waybar::modules::Clock::update() -> void { auto now = std::chrono::system_clock::now(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); auto text = fmt::format(format_, localtime); - label_.set_markup(text); + label_->set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_text = fmt::format(tooltip_format, localtime); - label_.set_tooltip_text(tooltip_text); + button_.set_tooltip_text(tooltip_text); } else { - label_.set_tooltip_text(text); + button_.set_tooltip_text(text); } } // Call parent update - ALabel::update(); + AButton::update(); } diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index 63e9eac3..dfab0d2a 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -41,7 +41,7 @@ auto Sndio::connect_to_sndio() -> void { } Sndio::Sndio(const std::string &id, const Json::Value &config) - : ALabel(config, "sndio", id, "{volume}%", 1), + : AButton(config, "sndio", id, "{volume}%", 1, false, true), hdl_(nullptr), pfds_(0), addr_(0), @@ -105,14 +105,14 @@ auto Sndio::update() -> void { unsigned int vol = 100. * static_cast(volume_) / static_cast(maxval_); if (volume_ == 0) { - label_.get_style_context()->add_class("muted"); + button_.get_style_context()->add_class("muted"); } else { - label_.get_style_context()->remove_class("muted"); + button_.get_style_context()->remove_class("muted"); } - label_.set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_))); + label_->set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_))); - ALabel::update(); + AButton::update(); } auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index d3730a11..8a6af618 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -18,7 +18,7 @@ const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names"; const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name"; Language::Language(const std::string& id, const Json::Value& config) - : ALabel(config, "language", id, "{}", 0, true) { + : AButton(config, "language", id, "{}", 0, true) { is_variant_displayed = format_.find("{variant}") != std::string::npos; if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) { displayed_short_flag |= static_cast(DispayedShortFlag::ShortName); @@ -99,7 +99,7 @@ auto Language::update() -> void { format_, fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); - label_.set_markup(display_layout); + label_->set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim( @@ -107,22 +107,22 @@ auto Language::update() -> void { fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); - label_.set_tooltip_markup(tooltip_display_layout); + button_.set_tooltip_markup(tooltip_display_layout); } else { - label_.set_tooltip_markup(display_layout); + button_.set_tooltip_markup(display_layout); } } event_box_.show(); // Call parent update - ALabel::update(); + AButton::update(); } auto Language::set_current_layout(std::string current_layout) -> void { - label_.get_style_context()->remove_class(layout_.short_name); + button_.get_style_context()->remove_class(layout_.short_name); layout_ = layouts_map_[current_layout]; - label_.get_style_context()->add_class(layout_.short_name); + button_.get_style_context()->add_class(layout_.short_name); } auto Language::init_layouts_map(const std::vector& used_layouts) -> void { diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index 7eaa523a..b6c9fc2b 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -5,7 +5,7 @@ namespace waybar::modules::sway { Mode::Mode(const std::string& id, const Json::Value& config) - : ALabel(config, "mode", id, "{}", 0, true) { + : AButton(config, "mode", id, "{}", 0, true) { ipc_.subscribe(R"(["mode"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent)); // Launch worker @@ -42,14 +42,14 @@ auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(format_, mode_)); + label_->set_markup(fmt::format(format_, mode_)); if (tooltipEnabled()) { - label_.set_tooltip_text(mode_); + button_.set_tooltip_text(mode_); } event_box_.show(); } // Call parent update - ALabel::update(); + AButton::update(); } } // namespace waybar::modules::sway diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp new file mode 100644 index 00000000..59e30530 --- /dev/null +++ b/src/modules/sway/scratchpad.cpp @@ -0,0 +1,82 @@ +#include "modules/sway/scratchpad.hpp" + +#include + +#include + +namespace waybar::modules::sway { +Scratchpad::Scratchpad(const std::string& id, const Json::Value& config) + : ALabel(config, "scratchpad", id, + config["format"].isString() ? config["format"].asString() : "{icon} {count}"), + tooltip_format_(config_["tooltip-format"].isString() ? config_["tooltip-format"].asString() + : "{app}: {title}"), + show_empty_(config_["show-empty"].isBool() ? config_["show-empty"].asBool() : false), + tooltip_enabled_(config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true), + tooltip_text_(""), + count_(0) { + ipc_.subscribe(R"(["window"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Scratchpad::onEvent)); + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Scratchpad::onCmd)); + + getTree(); + + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } + }); +} +auto Scratchpad::update() -> void { + if (count_ || show_empty_) { + event_box_.show(); + label_.set_markup( + fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), + fmt::arg("count", count_))); + if (tooltip_enabled_) { + label_.set_tooltip_markup(tooltip_text_); + } + } else { + event_box_.hide(); + } + if (count_) { + label_.get_style_context()->remove_class("empty"); + } else { + label_.get_style_context()->add_class("empty"); + } + ALabel::update(); +} + +auto Scratchpad::getTree() -> void { + try { + ipc_.sendCmd(IPC_GET_TREE); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void { + try { + std::lock_guard lock(mutex_); + auto tree = parser_.parse(res.payload); + count_ = tree["nodes"][0]["nodes"][0]["floating_nodes"].size(); + if (tooltip_enabled_) { + tooltip_text_.clear(); + for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { + tooltip_text_.append(fmt::format(tooltip_format_ + '\n', + fmt::arg("app", window["app_id"].asString()), + fmt::arg("title", window["name"].asString()))); + } + if (!tooltip_text_.empty()) { + tooltip_text_.pop_back(); + } + } + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Scratchpad::onEvent(const struct Ipc::ipc_response& res) -> void { getTree(); } +} // namespace waybar::modules::sway \ No newline at end of file diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 3d63743f..5da7d3d4 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -12,6 +12,8 @@ #include #include +#include "util/rewrite_title.hpp" + namespace waybar::modules::sway { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) @@ -175,8 +177,9 @@ auto Window::update() -> void { bar_.window.get_style_context()->remove_class("solo"); bar_.window.get_style_context()->remove_class("empty"); } - label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)), - fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); + label_.set_markup(fmt::format( + format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), + fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); if (tooltipEnabled()) { label_.set_tooltip_text(window_); } @@ -262,30 +265,4 @@ void Window::getTree() { } } -std::string Window::rewriteTitle(const std::string& title) { - const auto& rules = config_["rewrite"]; - if (!rules.isObject()) { - return title; - } - - std::string res = title; - - for (auto it = rules.begin(); it != rules.end(); ++it) { - if (it.key().isString() && it->isString()) { - try { - // malformated regexes will cause an exception. - // in this case, log error and try the next rule. - const std::regex rule{it.key().asString()}; - if (std::regex_match(title, rule)) { - res = std::regex_replace(res, rule, it->asString()); - } - } catch (const std::regex_error& e) { - spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what()); - } - } - } - - return res; -} - } // namespace waybar::modules::sway diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index eea1198e..935fea30 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -3,15 +3,11 @@ #include #if defined(__FreeBSD__) -// clang-format off -#include #include -// clang-format on #endif waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config) - : ALabel(config, "temperature", id, "{temperatureC}°C", 10) { - + : AButton(config, "temperature", id, "{temperatureC}°C", 10) { #if defined(__FreeBSD__) // try to read sysctl? #else @@ -46,9 +42,9 @@ auto waybar::modules::Temperature::update() -> void { auto format = format_; if (critical) { format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format; - label_.get_style_context()->add_class("critical"); + button_.get_style_context()->add_class("critical"); } else { - label_.get_style_context()->remove_class("critical"); + button_.get_style_context()->remove_class("critical"); } if (format.empty()) { @@ -59,21 +55,21 @@ auto waybar::modules::Temperature::update() -> void { } auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; - label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k), - fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); + label_->set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), + fmt::arg("temperatureK", temperature_k), + fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); if (tooltipEnabled()) { std::string tooltip_format = "{temperatureC}°C"; if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k))); + button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), + fmt::arg("temperatureK", temperature_k))); } // Call parent update - ALabel::update(); + AButton::update(); } float waybar::modules::Temperature::getTemperature() { @@ -81,13 +77,17 @@ float waybar::modules::Temperature::getTemperature() { int temp; size_t size = sizeof temp; + auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; + auto sysctl_thermal = fmt::format("hw.acpi.thermal.tz{}.temperature", zone); + if (sysctlbyname("hw.acpi.thermal.tz0.temperature", &temp, &size, NULL, 0) != 0) { - throw std::runtime_error("sysctl hw.acpi.thermal.tz0.temperature or dev.cpu.0.temperature failed"); + throw std::runtime_error( + "sysctl hw.acpi.thermal.tz0.temperature or dev.cpu.0.temperature failed"); } - auto temperature_c = ((float)temp-2732)/10; + auto temperature_c = ((float)temp - 2732) / 10; return temperature_c; -#else // Linux +#else // Linux std::ifstream temp(file_path_); if (!temp.is_open()) { throw std::runtime_error("Can't open " + file_path_); diff --git a/src/modules/user.cpp b/src/modules/user.cpp new file mode 100644 index 00000000..7e3223c9 --- /dev/null +++ b/src/modules/user.cpp @@ -0,0 +1,130 @@ +#include "modules/user.hpp" + +#include +#include +#include + +#include +#include + +#include "gdkmm/event.h" +#include "gdkmm/types.h" +#include "sigc++/functors/mem_fun.h" +#include "sigc++/functors/ptr_fun.h" + +#if HAVE_CPU_LINUX +#include +#endif + +#if HAVE_CPU_BSD +#include +#endif + +#define LEFT_MOUSE_BUTTON 1 + +namespace waybar::modules { +User::User(const std::string& id, const Json::Value& config) + : AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, false, true) { + if (AIconLabel::iconEnabled()) { + this->init_avatar(AIconLabel::config_); + } + this->init_update_worker(); + AModule::event_box_.signal_button_press_event().connect(sigc::mem_fun(this, &User::signal_label)); +} + +bool User::signal_label(GdkEventButton* button) const { + if (button->type != GDK_BUTTON_PRESS) return true; + + if (button->button == LEFT_MOUSE_BUTTON) { + Gio::AppInfo::launch_default_for_uri("file:///" + this->get_user_home_dir()); + } + return false; +} + +long User::uptime_as_seconds() { + long uptime = 0; + +#if HAVE_CPU_LINUX + struct sysinfo s_info; + if (0 == sysinfo(&s_info)) { + uptime = s_info.uptime; + } +#endif + +#if HAVE_CPU_BSD + struct timespec s_info; + if (0 == clock_gettime(CLOCK_UPTIME_PRECISE, &s_info)) { + uptime = s_info.tv_sec; + } +#endif + + return uptime; +} + +std::string User::get_user_login() const { return Glib::get_user_name(); } + +std::string User::get_user_home_dir() const { return Glib::get_home_dir(); } + +void User::init_update_worker() { + this->thread_ = [this] { + ALabel::dp.emit(); + auto now = std::chrono::system_clock::now(); + auto diff = now.time_since_epoch() % ALabel::interval_; + this->thread_.sleep_for(ALabel::interval_ - diff); + }; +} + +void User::init_avatar(const Json::Value& config) { + int height = + config["height"].isUInt() ? config["height"].asUInt() : this->defaultUserImageHeight_; + int width = config["width"].isUInt() ? config["width"].asUInt() : this->defaultUserImageWidth_; + + if (config["avatar"].isString()) { + std::string userAvatar = config["avatar"].asString(); + if (!userAvatar.empty()) { + this->init_user_avatar(userAvatar, width, height); + return; + } + } + + this->init_default_user_avatar(width, width); +} + +std::string User::get_default_user_avatar_path() const { + return this->get_user_home_dir() + "/" + ".face"; +} + +void User::init_default_user_avatar(int width, int height) { + this->init_user_avatar(this->get_default_user_avatar_path(), width, height); +} + +void User::init_user_avatar(const std::string& path, int width, int height) { + this->pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height); + AIconLabel::image_.set(this->pixbuf_); +} + +auto User::update() -> void { + std::string systemUser = this->get_user_login(); + std::transform(systemUser.cbegin(), systemUser.cend(), systemUser.begin(), + [](unsigned char c) { return std::toupper(c); }); + + long uptimeSeconds = this->uptime_as_seconds(); + auto workSystemTimeSeconds = std::chrono::seconds(uptimeSeconds); + auto currentSystemTime = std::chrono::system_clock::now(); + auto startSystemTime = currentSystemTime - workSystemTimeSeconds; + long workSystemDays = uptimeSeconds / 86400; + + auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), + fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), + fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), + fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), + fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), + fmt::arg("work_d", workSystemDays), + fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), + fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), + fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), + fmt::arg("user", systemUser)); + ALabel::label_.set_markup(label); + ALabel::update(); +} +}; // namespace waybar::modules diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 156624d5..97d84bd0 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -251,6 +251,9 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_imp .parent = tl_handle_parent, }; +static const std::vector target_entries = { + Gtk::TargetEntry("WAYBAR_TOPLEVEL", Gtk::TARGET_SAME_APP, 0)}; + Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : bar_{bar}, @@ -309,9 +312,22 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, /* Handle click events if configured */ if (config_["on-click"].isString() || config_["on-click-middle"].isString() || config_["on-click-right"].isString()) { - button_.add_events(Gdk::BUTTON_PRESS_MASK); - button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false); } + + button_.add_events(Gdk::BUTTON_PRESS_MASK); + button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false); + button_.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_button_release), + false); + + button_.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify), + false); + + button_.drag_source_set(target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE); + button_.drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE); + + button_.signal_drag_data_get().connect(sigc::mem_fun(*this, &Task::handle_drag_data_get), false); + button_.signal_drag_data_received().connect( + sigc::mem_fun(*this, &Task::handle_drag_data_received), false); } Task::~Task() { @@ -495,6 +511,14 @@ void Task::handle_closed() { } bool Task::handle_clicked(GdkEventButton *bt) { + /* filter out additional events for double/triple clicks */ + if (bt->type == GDK_BUTTON_PRESS) { + /* save where the button press ocurred in case it becomes a drag */ + drag_start_button = bt->button; + drag_start_x = bt->x; + drag_start_y = bt->y; + } + std::string action; if (config_["on-click"].isString() && bt->button == 1) action = config_["on-click"].asString(); @@ -528,6 +552,54 @@ bool Task::handle_clicked(GdkEventButton *bt) { return true; } +bool Task::handle_button_release(GdkEventButton *bt) { + drag_start_button = -1; + return false; +} + +bool Task::handle_motion_notify(GdkEventMotion *mn) { + if (drag_start_button == -1) return false; + + if (button_.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) { + /* start drag in addition to other assigned action */ + auto target_list = Gtk::TargetList::create(target_entries); + auto refptr = Glib::RefPtr(target_list); + auto drag_context = + button_.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent *)mn); + } + + return false; +} + +void Task::handle_drag_data_get(const Glib::RefPtr &context, + Gtk::SelectionData &selection_data, guint info, guint time) { + spdlog::debug("drag_data_get"); + void *button_addr = (void *)&this->button_; + + selection_data.set("WAYBAR_TOPLEVEL", 32, (const guchar *)&button_addr, sizeof(gpointer)); +} + +void Task::handle_drag_data_received(const Glib::RefPtr &context, int x, int y, + Gtk::SelectionData selection_data, guint info, guint time) { + spdlog::debug("drag_data_received"); + gpointer handle = *(gpointer *)selection_data.get_data(); + auto dragged_button = (Gtk::Button *)handle; + + if (dragged_button == &this->button_) return; + + auto parent_of_dragged = dragged_button->get_parent(); + auto parent_of_dest = this->button_.get_parent(); + + if (parent_of_dragged != parent_of_dest) return; + + auto box = (Gtk::Box *)parent_of_dragged; + + auto position_prop = box->child_property_position(this->button_); + auto position = position_prop.get_value(); + + box->reorder_child(*dragged_button, position); +} + bool Task::operator==(const Task &o) const { return o.id_ == id_; } bool Task::operator!=(const Task &o) const { return o.id_ != id_; } diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index cc3c8151..da83cb79 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -32,6 +32,11 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar sort_by_coordinates_ = config_sort_by_coordinates.asBool(); } + auto config_sort_by_number = config_["sort-by-number"]; + if (config_sort_by_number.isBool()) { + sort_by_number_ = config_sort_by_number.asBool(); + } + auto config_all_outputs = config_["all-outputs"]; if (config_all_outputs.isBool()) { all_outputs_ = config_all_outputs.asBool(); @@ -61,6 +66,12 @@ auto WorkspaceManager::workspace_comparator() const auto is_name_less = lhs->get_name() < rhs->get_name(); auto is_name_eq = lhs->get_name() == rhs->get_name(); auto is_coords_less = lhs->get_coords() < rhs->get_coords(); + auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name()); + + if (sort_by_number_) { + return is_number_less; + } + if (sort_by_name_) { if (sort_by_coordinates_) { return is_name_eq ? is_coords_less : is_name_less; diff --git a/src/util/rewrite_title.cpp b/src/util/rewrite_title.cpp new file mode 100644 index 00000000..fae59bb1 --- /dev/null +++ b/src/util/rewrite_title.cpp @@ -0,0 +1,32 @@ +#include "util/rewrite_title.hpp" + +#include + +#include + +namespace waybar::util { +std::string rewriteTitle(const std::string& title, const Json::Value& rules) { + if (!rules.isObject()) { + return title; + } + + std::string res = title; + + for (auto it = rules.begin(); it != rules.end(); ++it) { + if (it.key().isString() && it->isString()) { + try { + // malformated regexes will cause an exception. + // in this case, log error and try the next rule. + const std::regex rule{it.key().asString()}; + if (std::regex_match(title, rule)) { + res = std::regex_replace(res, rule, it->asString()); + } + } catch (const std::regex_error& e) { + spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what()); + } + } + } + + return res; +} +} // namespace waybar::util diff --git a/src/util/sanitize_str.cpp b/src/util/sanitize_str.cpp new file mode 100644 index 00000000..72c72f84 --- /dev/null +++ b/src/util/sanitize_str.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +namespace waybar::util { +// replaces ``<>&"'`` with their encoded counterparts +std::string sanitize_string(std::string str) { + // note: it's important that '&' is replaced first; therefor we *can't* use std::map + const std::pair replacement_table[] = { + {'&', "&"}, {'<', "<"}, {'>', ">"}, {'"', """}, {'\'', "'"}}; + size_t startpoint; + for (size_t i = 0; i < (sizeof(replacement_table) / sizeof(replacement_table[0])); ++i) { + startpoint = 0; + std::pair pair = replacement_table[i]; + while ((startpoint = str.find(pair.first, startpoint)) != std::string::npos) { + str.replace(startpoint, 1, pair.second); + startpoint += pair.second.length(); + } + } + + return str; +} +} // namespace waybar::util diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap index c82b310f..ea61b942 100644 --- a/subprojects/catch2.wrap +++ b/subprojects/catch2.wrap @@ -1,12 +1,13 @@ [wrap-file] -directory = Catch2-2.13.7 -source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip -source_filename = Catch2-2.13.7.zip -source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9 -patch_filename = catch2_2.13.7-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch -patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158 +directory = Catch2-3.1.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.1.0.tar.gz +source_filename = Catch2-3.1.0.tar.gz +source_hash = c252b2d9537e18046d8b82535069d2567f77043f8e644acf9a9fffc22ea6e6f7 +patch_filename = catch2_3.1.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/catch2_3.1.0-1/get_patch +patch_hash = 4ebf4277aed574a9912a79f4817a310d837798e099bbafa6097be23a7f5e3ae4 +wrapdb_version = 3.1.0-1 [provide] catch2 = catch2_dep - +catch2-with-main = catch2_with_main_dep diff --git a/subprojects/date.wrap b/subprojects/date.wrap index 4d4067c9..d18c3ebf 100644 --- a/subprojects/date.wrap +++ b/subprojects/date.wrap @@ -1,9 +1,14 @@ [wrap-file] -source_url=https://github.com/HowardHinnant/date/archive/v3.0.0.tar.gz -source_filename=date-3.0.0.tar.gz -source_hash=87bba2eaf0ebc7ec539e5e62fc317cb80671a337c1fb1b84cb9e4d42c6dbebe3 -directory=date-3.0.0 +source_url = https://github.com/HowardHinnant/date/archive/v3.0.1.tar.gz +source_filename = date-3.0.1.tar.gz +source_hash = 7a390f200f0ccd207e8cff6757e04817c1a0aec3e327b006b7eb451c57ee3538 +directory = date-3.0.1 +patch_filename = hinnant-date_3.0.1-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/hinnant-date_3.0.1-2/get_patch +patch_hash = 11b715b792609117a63310eeefc2939cc2ca26ecd4e996108335e504db58a41d +wrapdb_version = 3.0.1-2 + +[provide] +tz = tz_dep +date = date_dep -patch_url = https://github.com/mesonbuild/hinnant-date/releases/download/3.0.0-1/hinnant-date.zip -patch_filename = hinnant-date-3.0.0-1-wrap.zip -patch_hash = 6ccaf70732d8bdbd1b6d5fdf3e1b935c23bf269bda12fdfd0e561276f63432fe diff --git a/subprojects/jsoncpp.wrap b/subprojects/jsoncpp.wrap new file mode 100644 index 00000000..ee3eb2e1 --- /dev/null +++ b/subprojects/jsoncpp.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = jsoncpp-1.9.5 +source_url = https://github.com/open-source-parsers/jsoncpp/archive/1.9.5.tar.gz +source_filename = jsoncpp-1.9.5.tar.gz +source_hash = f409856e5920c18d0c2fb85276e24ee607d2a09b5e7d5f0a371368903c275da2 + +[provide] +jsoncpp = jsoncpp_dep + diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index 6a1e17a5..7ff6f2ae 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/test/config.cpp b/test/config.cpp index 29b0502a..cdc96b0c 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -1,6 +1,6 @@ #include "config.hpp" -#include +#include TEST_CASE("Load simple config", "[config]") { waybar::Config conf; diff --git a/test/main.cpp b/test/main.cpp index 5eb060d9..7970c262 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include int main(int argc, char* argv[]) { @@ -13,13 +13,14 @@ int main(int argc, char* argv[]) { 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()}); + for (const auto& spec : session.config().getReporterSpecs()) { + if (spec.name() == "tap") { + spdlog::set_pattern("# [%l] %v"); + } else if (spec.name() == "compact") { + logger->sinks().clear(); + } else { + logger->sinks().assign({std::make_shared()}); + } } return session.run(); diff --git a/test/waybar_time.cpp b/test/waybar_time.cpp index 5fc3312d..79469d41 100644 --- a/test/waybar_time.cpp +++ b/test/waybar_time.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include