Compare commits

..

18 Commits

Author SHA1 Message Date
8e5f7fec42 Make signal-triggered groups always click-to-activate
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2025-10-10 04:21:01 -07:00
cb6bc2f261 Fix some bugs
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2025-10-09 23:02:31 -07:00
3db4d5b788 Update README.md
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2025-10-09 22:07:54 -07:00
c5a7fae1d9 Fork
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2025-10-09 22:05:52 -07:00
559079e9a6 fix: lint
Some checks failed
clang-format / lint (push) Has been cancelled
freebsd / build (push) Has been cancelled
linux / build (c++20, alpine) (push) Has been cancelled
linux / build (c++20, archlinux) (push) Has been cancelled
linux / build (c++20, debian) (push) Has been cancelled
linux / build (c++20, fedora) (push) Has been cancelled
linux / build (c++20, gentoo) (push) Has been cancelled
linux / build (c++20, opensuse) (push) Has been cancelled
Nix-Tests / nix-flake-check (push) Has been cancelled
2025-10-05 10:56:24 +02:00
0c41cf47c2 Merge pull request #4359 from zjeffer/fix/zjeffer/thread-sanitizer-warning
Fix Hyprland IPC thread sanitizer warning, other IPC & general fixes
2025-10-05 10:56:02 +02:00
a16d53b30d Merge branch 'master' into fix/zjeffer/thread-sanitizer-warning 2025-10-05 10:51:47 +02:00
151cf54532 fix: lint 2025-10-05 09:58:34 +02:00
b3f1d02b16 Merge pull request #4516 from DreamMaoMao/fix-ext-ws
fix: right and middle button not work in ext/workspace module
2025-10-05 09:57:50 +02:00
bea012d06d Merge pull request #4518 from DreamMaoMao/fix-network
fix: Correct the error in converting network speed units
2025-10-05 09:57:29 +02:00
197ee78080 Merge pull request #4525 from lairez/makepkg
Fixes #4521 and #4522
2025-10-05 09:57:06 +02:00
d8e2392410 Fixes #4521 and #4522
The problem is commit 2b552f7 which introduces a minimum interval time
of 1ms. But then, in modules/custom.cpp, the constructor tests if the
interval is nonzero to distinguish continuous workers from delay workers.
2025-10-03 11:24:18 +02:00
801319f024 fix: Correct the error in converting network speed units 2025-10-02 08:55:40 +08:00
6f308d8ea1 fix: right and middle button not work in ext/workspace module 2025-10-01 22:30:23 +08:00
9720d80524 add asan.supp 2025-08-23 18:25:45 +02:00
3c3164eb8e Fix warning if swap-icon-label is not defined in config 2025-08-19 23:56:10 +02:00
556c5f5a30 Add tsan.supp file to easily ignore common tsan issues from external libraries 2025-08-19 23:56:08 +02:00
5079884b78 Hyprland IPC improvements, fix tsan warning, WindowCount shouldn't create a separate IPC 2025-08-12 19:39:36 +02:00
32 changed files with 252 additions and 95 deletions

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ result
result-* result-*
.ccls-cache .ccls-cache
.wraplock

View File

@ -1,3 +1,9 @@
# This is a fork!
This is my fork of [Waybar](https://github.com/Alexays/Waybar). It has a few
changes with the biggest being that it works with my fork or river (the Wayland
compositor). In fact, it will probably **not** work with vanilla river.
# Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png) # Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>

5
asan.supp Normal file
View File

@ -0,0 +1,5 @@
# Suppress common address sanitizer issues in dependencies, these are often non-fixable or not an issue.
# Use it like this (when in repo root): ASAN_OPTIONS="suppressions=./asan.supp" ./build/waybar
leak:libpangoft2-1.0.so.0
leak:libgtk-3.so.0
leak:libfontconfig.so.1

View File

@ -4,6 +4,8 @@
#include <gtkmm/widget.h> #include <gtkmm/widget.h>
#include <json/json.h> #include <json/json.h>
#include <optional>
#include "AModule.hpp" #include "AModule.hpp"
#include "gtkmm/revealer.h" #include "gtkmm/revealer.h"
@ -15,6 +17,7 @@ class Group : public AModule {
~Group() override = default; ~Group() override = default;
auto update() -> void override; auto update() -> void override;
operator Gtk::Widget &() override; operator Gtk::Widget &() override;
auto refresh(int sig) -> void override;
virtual Gtk::Box &getBox(); virtual Gtk::Box &getBox();
void addWidget(Gtk::Widget &widget); void addWidget(Gtk::Widget &widget);
@ -26,10 +29,12 @@ class Group : public AModule {
bool is_first_widget = true; bool is_first_widget = true;
bool is_drawer = false; bool is_drawer = false;
bool click_to_reveal = false; bool click_to_reveal = false;
std::optional<int> toggle_signal;
std::string add_class_to_drawer_children; std::string add_class_to_drawer_children;
bool handleMouseEnter(GdkEventCrossing *const &ev) override; bool handleMouseEnter(GdkEventCrossing *const &ev) override;
bool handleMouseLeave(GdkEventCrossing *const &ev) override; bool handleMouseLeave(GdkEventCrossing *const &ev) override;
bool handleToggle(GdkEventButton *const &ev) override; bool handleToggle(GdkEventButton *const &ev) override;
void toggle();
void show_group(); void show_group();
void hide_group(); void hide_group();
}; };

View File

@ -17,9 +17,13 @@ class EventHandler {
virtual ~EventHandler() = default; virtual ~EventHandler() = default;
}; };
/// If you want to use the Hyprland IPC, simply use IPC::inst() to get the singleton instance.
/// Do not create multiple instances.
class IPC { class IPC {
protected:
IPC(); // use IPC::inst() instead.
public: public:
IPC();
~IPC(); ~IPC();
static IPC& inst(); static IPC& inst();
@ -43,9 +47,6 @@ class IPC {
std::list<std::pair<std::string, EventHandler*>> callbacks_; std::list<std::pair<std::string, EventHandler*>> callbacks_;
int socketfd_; // the hyprland socket file descriptor int socketfd_; // the hyprland socket file descriptor
pid_t socketOwnerPid_; pid_t socketOwnerPid_;
bool running_ = true; bool running_ = true; // the ipcThread will stop running when this is false
}; };
inline bool modulesReady = false;
inline std::unique_ptr<IPC> gIPC;
}; // namespace waybar::modules::hyprland }; // namespace waybar::modules::hyprland

View File

@ -7,7 +7,6 @@
#include "AAppIconLabel.hpp" #include "AAppIconLabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "modules/hyprland/backend.hpp" #include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
@ -26,8 +25,8 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler {
static auto parse(const Json::Value& value) -> Workspace; static auto parse(const Json::Value& value) -> Workspace;
}; };
static auto getActiveWorkspace(const std::string&) -> Workspace; auto getActiveWorkspace(const std::string&) -> Workspace;
static auto getActiveWorkspace() -> Workspace; auto getActiveWorkspace() -> Workspace;
void onEvent(const std::string& ev) override; void onEvent(const std::string& ev) override;
void queryActiveWorkspace(); void queryActiveWorkspace();
void setClass(const std::string&, bool enable); void setClass(const std::string&, bool enable);
@ -36,6 +35,7 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler {
std::mutex mutex_; std::mutex mutex_;
const Bar& bar_; const Bar& bar_;
Workspace workspace_; Workspace workspace_;
IPC& m_ipc;
}; };
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland

View File

@ -21,6 +21,7 @@ class Mode : public waybar::ALabel {
private: private:
const waybar::Bar &bar_; const waybar::Bar &bar_;
std::set<std::string> hidden_modes_;
std::string mode_; std::string mode_;
struct zriver_seat_status_v1 *seat_status_; struct zriver_seat_status_v1 *seat_status_;
}; };

View File

@ -20,6 +20,7 @@ class Tags : public waybar::AModule {
void handle_focused_tags(uint32_t tags); void handle_focused_tags(uint32_t tags);
void handle_view_tags(struct wl_array *tags); void handle_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags); void handle_urgent_tags(uint32_t tags);
void handle_focused_view(const char *title, uint32_t tags);
void handle_show(); void handle_show();
void handle_primary_clicked(uint32_t tag); void handle_primary_clicked(uint32_t tag);
@ -27,7 +28,11 @@ class Tags : public waybar::AModule {
struct zriver_status_manager_v1 *status_manager_; struct zriver_status_manager_v1 *status_manager_;
struct zriver_control_v1 *control_; struct zriver_control_v1 *control_;
struct zriver_seat_status_v1 *seat_status_;
struct wl_seat *seat_; struct wl_seat *seat_;
// used to make sure the focused view tags are on the correct output
const wl_output *output_;
const wl_output *focused_output_;
private: private:
const waybar::Bar &bar_; const waybar::Bar &bar_;

View File

@ -3,6 +3,8 @@
#include <gtkmm/button.h> #include <gtkmm/button.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <optional>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h" #include "river-status-unstable-v1-client-protocol.h"
@ -16,7 +18,7 @@ class Window : public waybar::ALabel {
virtual ~Window(); virtual ~Window();
// Handlers for wayland events // Handlers for wayland events
void handle_focused_view(const char *title); void handle_focused_view(const char *title, uint32_t);
void handle_focused_output(struct wl_output *output); void handle_focused_output(struct wl_output *output);
void handle_unfocused_output(struct wl_output *output); void handle_unfocused_output(struct wl_output *output);
@ -25,6 +27,7 @@ class Window : public waybar::ALabel {
private: private:
const waybar::Bar &bar_; const waybar::Bar &bar_;
std::optional<std::string> default_format_; // format when there is no window
struct wl_output *output_; // stores the output this module belongs to struct wl_output *output_; // stores the output this module belongs to
struct wl_output *focused_output_; // stores the currently focused output struct wl_output *focused_output_; // stores the currently focused output
struct zriver_seat_status_v1 *seat_status_; struct zriver_seat_status_v1 *seat_status_;

View File

@ -17,6 +17,10 @@ Addressed by *river/mode*
default: {} ++ default: {} ++
The format, how information should be displayed. On {} data gets inserted. The format, how information should be displayed. On {} data gets inserted.
*hidden-modes*: ++
typeof: array ++
List of modes that should result in this module being hidden. Useful if you want to hide the default mode, for example.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label (in 90 degree increments). Positive value to rotate the text label (in 90 degree increments).

View File

@ -50,10 +50,13 @@ Addressed by *river/tags*
- *#tags button.occupied* - *#tags button.occupied*
- *#tags button.focused* - *#tags button.focused*
- *#tags button.urgent* - *#tags button.urgent*
- *#tags button.current-view*
Note that occupied/focused/urgent status may overlap. That is, a tag may be Note that occupied/focused/urgent status may overlap. That is, a tag may be
both occupied and focused at the same time. both occupied and focused at the same time.
The current view is set on all tags on which the current view is visible.
# SEE ALSO # SEE ALSO
waybar(5), river(1) waybar(5), river(1)

View File

@ -17,6 +17,10 @@ Addressed by *river/window*
default: {} ++ default: {} ++
The format, how information should be displayed. On {} data gets inserted. The format, how information should be displayed. On {} data gets inserted.
*default-format*: ++
typeof: string ++
A string to be show if no window is focused. No formatting is done but markup is supported.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label (in 90 degree increments). Positive value to rotate the text label (in 90 degree increments).

View File

@ -369,6 +369,10 @@ A group may hide all but one element, showing them only on mouse hover. In order
Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left. Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.
When the bar is vertical, it reads as top-to-bottom. When the bar is vertical, it reads as top-to-bottom.
*toggle-signal*: ++
typeof: integer ++
If set, when waybar recives SIGRTMIN+N (where N is this value) it will toggle the visibility of the drawer.
``` ```
"group/power": { "group/power": {
"orientation": "inherit", "orientation": "inherit",

View File

@ -100,7 +100,7 @@
</event> </event>
</interface> </interface>
<interface name="zriver_seat_status_v1" version="3"> <interface name="zriver_seat_status_v1" version="4">
<description summary="track seat focus"> <description summary="track seat focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent focus of a seat. Note that (un)focused_output events will only be sent
@ -128,13 +128,14 @@
<arg name="output" type="object" interface="wl_output"/> <arg name="output" type="object" interface="wl_output"/>
</event> </event>
<event name="focused_view"> <event name="focused_view" since="4">
<description summary="information on the focused view"> <description summary="information on the focused view">
Sent once on binding the interface and again whenever the focused Sent once on binding the interface and again whenever the focused
view or a property thereof changes. The title may be an empty string view or a property thereof changes. The title may be an empty string
if no view is focused or the focused view did not set a title. if no view is focused or the focused view did not set a title.
</description> </description>
<arg name="title" type="string" summary="title of the focused view"/> <arg name="title" type="string" summary="title of the focused view"/>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event> </event>
<event name="mode" since="3"> <event name="mode" since="3">

View File

@ -17,12 +17,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
config["format-alt"].isString() || config["menu"].isString() || enable_click, config["format-alt"].isString() || config["menu"].isString() || enable_click,
enable_scroll), enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format), format_(config_["format"].isString() ? config_["format"].asString() : format),
// Leave the default option outside of the std::max(1L, ...), because the zero value
// (default) is used in modules/custom.cpp to make the difference between
// two types of custom scripts. Fixes #4521.
interval_(config_["interval"] == "once" interval_(config_["interval"] == "once"
? std::chrono::milliseconds::max() ? std::chrono::milliseconds::max()
: std::chrono::milliseconds( : std::chrono::milliseconds(
std::max(1L, // Minimum 1ms due to millisecond precision (config_["interval"].isNumeric()
static_cast<long>( ? std::max(1L, // Minimum 1ms due to millisecond precision
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000)))), static_cast<long>(config_["interval"].asDouble()) * 1000)
: 1000 * (long)interval))),
default_format_(format_) { default_format_(format_) {
label_.set_name(name); label_.set_name(name);
if (!id.empty()) { if (!id.empty()) {

View File

@ -51,8 +51,12 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
if (config_["drawer"].isObject()) { if (config_["drawer"].isObject()) {
is_drawer = true; is_drawer = true;
const auto& drawer_config = config_["drawer"]; const auto& drawer_config = config_["drawer"];
if (drawer_config["toggle-signal"].isInt()) {
toggle_signal = std::make_optional(drawer_config["toggle-signal"].asInt());
}
const int transition_duration = const int transition_duration =
(drawer_config["transition-duration"].isInt() ? drawer_config["transition-duration"].asInt() (drawer_config["transition-duration"].isInt() ? drawer_config["transition-duration"].asInt()
: 500); : 500);
@ -62,7 +66,7 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
const bool left_to_right = (drawer_config["transition-left-to-right"].isBool() const bool left_to_right = (drawer_config["transition-left-to-right"].isBool()
? drawer_config["transition-left-to-right"].asBool() ? drawer_config["transition-left-to-right"].asBool()
: true); : true);
click_to_reveal = drawer_config["click-to-reveal"].asBool(); click_to_reveal = drawer_config["click-to-reveal"].asBool() || toggle_signal.has_value();
auto transition_type = getPreferredTransitionType(vertical); auto transition_type = getPreferredTransitionType(vertical);
@ -84,6 +88,14 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
event_box_.add(box); event_box_.add(box);
} }
void Group::toggle() {
if ((box.get_state_flags() & Gtk::StateFlags::STATE_FLAG_PRELIGHT) != 0U) {
hide_group();
} else {
show_group();
}
}
void Group::show_group() { void Group::show_group() {
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
revealer.set_reveal_child(true); revealer.set_reveal_child(true);
@ -112,11 +124,7 @@ bool Group::handleToggle(GdkEventButton* const& e) {
if (!click_to_reveal || e->button != 1) { if (!click_to_reveal || e->button != 1) {
return false; return false;
} }
if ((box.get_state_flags() & Gtk::StateFlags::STATE_FLAG_PRELIGHT) != 0U) { toggle();
hide_group();
} else {
show_group();
}
return true; return true;
} }
@ -124,6 +132,12 @@ auto Group::update() -> void {
// noop // noop
} }
void Group::refresh(int sig) {
if (toggle_signal.has_value() && sig == SIGRTMIN + toggle_signal.value()) {
toggle();
}
}
Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; } Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }
void Group::addWidget(Gtk::Widget& widget) { void Group::addWidget(Gtk::Widget& widget) {

View File

@ -30,7 +30,9 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
cldMonShift_{year(1900) / January}, cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0}, tzCurrIdx_{0},
tzTooltipFormat_{config_["timezone-tooltip-format"].isString() ? config_["timezone-tooltip-format"].asString() : ""}, tzTooltipFormat_{config_["timezone-tooltip-format"].isString()
? config_["timezone-tooltip-format"].asString()
: ""},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
m_tlpText_ = m_tlpFmt_; m_tlpText_ = m_tlpFmt_;

View File

@ -349,11 +349,11 @@ Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager,
} }
const bool config_on_click_middle = config["on-click-middle"].isString(); const bool config_on_click_middle = config["on-click-middle"].isString();
if (config_on_click_middle) { if (config_on_click_middle) {
on_click_middle_action_ = config["on-click"].asString(); on_click_middle_action_ = config["on-click-middle"].asString();
} }
const bool config_on_click_right = config["on-click-right"].isString(); const bool config_on_click_right = config["on-click-right"].isString();
if (config_on_click_right) { if (config_on_click_right) {
on_click_right_action_ = config["on-click"].asString(); on_click_right_action_ = config["on-click-right"].asString();
} }
// setup UI // setup UI

View File

@ -83,8 +83,6 @@ void IPC::socketListener() {
return; return;
} }
if (!modulesReady) return;
spdlog::info("Hyprland IPC starting"); spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr; struct sockaddr_un addr;

View File

@ -11,8 +11,6 @@ namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { : ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true;
// get the active layout when open // get the active layout when open
initLanguage(); initLanguage();

View File

@ -2,14 +2,10 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "util/sanitize_str.hpp"
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { : ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true;
parseConfig(config); parseConfig(config);
label_.hide(); label_.hide();

View File

@ -21,7 +21,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) { : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx); std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
modulesReady = true;
separateOutputs_ = config["separate-outputs"].asBool(); separateOutputs_ = config["separate-outputs"].asBool();
// register for hyprland ipc // register for hyprland ipc

View File

@ -9,35 +9,29 @@
#include <vector> #include <vector>
#include "modules/hyprland/backend.hpp" #include "modules/hyprland/backend.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::hyprland { namespace waybar::modules::hyprland {
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) { : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true;
separateOutputs_ = separateOutputs_ =
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true; config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
queryActiveWorkspace(); queryActiveWorkspace();
update(); update();
dp.emit(); dp.emit();
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("fullscreen", this); m_ipc.registerForIPC("fullscreen", this);
gIPC->registerForIPC("workspace", this); m_ipc.registerForIPC("workspace", this);
gIPC->registerForIPC("focusedmon", this); m_ipc.registerForIPC("focusedmon", this);
gIPC->registerForIPC("openwindow", this); m_ipc.registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindow", this);
} }
WindowCount::~WindowCount() { WindowCount::~WindowCount() {
gIPC->unregisterForIPC(this); m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish // wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_); std::lock_guard<std::mutex> lg(mutex_);
} }
@ -70,7 +64,7 @@ auto WindowCount::update() -> void {
} }
auto WindowCount::getActiveWorkspace() -> Workspace { auto WindowCount::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); const auto workspace = m_ipc.getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) { if (workspace.isObject()) {
return Workspace::parse(workspace); return Workspace::parse(workspace);
@ -80,24 +74,31 @@ auto WindowCount::getActiveWorkspace() -> Workspace {
} }
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace { auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors"); const auto monitors = m_ipc.getSocket1JsonReply("monitors");
if (monitors.isArray()) { if (monitors.isArray()) {
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { auto monitor = std::ranges::find_if(
return monitor["name"] == monitorName; monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
});
if (monitor == std::end(monitors)) { if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName); spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{-1, 0, false}; return Workspace{
.id = -1,
.windows = 0,
.hasfullscreen = false,
};
} }
const int id = (*monitor)["activeWorkspace"]["id"].asInt(); const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
if (workspaces.isArray()) { if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(), auto workspace = std::ranges::find_if(
[&](Json::Value workspace) { return workspace["id"] == id; }); workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) { if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id); spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, false}; return Workspace{
.id = -1,
.windows = 0,
.hasfullscreen = false,
};
} }
return Workspace::parse(*workspace); return Workspace::parse(*workspace);
}; };
@ -108,9 +109,9 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace { auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
return Workspace{ return Workspace{
value["id"].asInt(), .id = value["id"].asInt(),
value["windows"].asInt(), .windows = value["windows"].asInt(),
value["hasfullscreen"].asBool(), .hasfullscreen = value["hasfullscreen"].asBool(),
}; };
} }

View File

@ -19,7 +19,6 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
m_bar(bar), m_bar(bar),
m_box(bar.orientation, 0), m_box(bar.orientation, 0),
m_ipc(IPC::inst()) { m_ipc(IPC::inst()) {
modulesReady = true;
parseConfig(config); parseConfig(config);
m_box.set_name("workspaces"); m_box.set_name("workspaces");

View File

@ -16,10 +16,11 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
interval_ = config_["interval"] == "once" interval_ = config_["interval"] == "once"
? std::chrono::milliseconds::max() ? std::chrono::milliseconds::max()
: std::chrono::milliseconds( : std::chrono::milliseconds(std::max(
std::max(1L, // Minimum 1ms due to millisecond precision 1L, // Minimum 1ms due to millisecond precision
static_cast<long>( static_cast<long>(
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * 1000))); (config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) *
1000)));
if (size_ == 0) { if (size_ == 0) {
size_ = 16; size_ = 16;

View File

@ -333,18 +333,23 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits",
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthTotalBits", fmt::arg("bandwidthUpBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg(
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), "bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthTotalOctets", fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes",
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthTotalBytes", fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
if (text.compare(label_.get_label()) != 0) { if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text); label_.set_markup(text);
if (text.empty()) { if (text.empty()) {

View File

@ -43,7 +43,7 @@ static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zr
} }
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title) { const char *title, uint32_t tags) {
// Intentionally empty // Intentionally empty
} }

View File

@ -18,7 +18,7 @@ static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *se
} }
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *seat_status, static void listen_focused_view(void *data, struct zriver_seat_status_v1 *seat_status,
const char *title) { const char *title, uint32_t tags) {
// Intentionally empty // Intentionally empty
} }
@ -36,7 +36,7 @@ static const zriver_seat_status_v1_listener seat_status_listener_impl = {
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 3); version = std::min<uint32_t>(version, 4);
if (version < ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) { if (version < ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) {
spdlog::error( spdlog::error(
"river server does not support the \"mode\" event; the module will be disabled"); "river server does not support the \"mode\" event; the module will be disabled");
@ -79,6 +79,14 @@ Mode::Mode(const std::string &id, const waybar::Bar &bar, const Json::Value &con
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
} }
if (config_["hidden-modes"].isArray()) {
for (const auto &mode : config["hidden-modes"]) {
if (mode.isString()) {
hidden_modes_.emplace(mode.asString());
}
}
}
label_.hide(); label_.hide();
ALabel::update(); ALabel::update();
@ -95,7 +103,7 @@ Mode::~Mode() {
} }
void Mode::handle_mode(const char *mode) { void Mode::handle_mode(const char *mode) {
if (format_.empty()) { if (format_.empty() || hidden_modes_.contains(mode)) {
label_.hide(); label_.hide();
} else { } else {
if (!mode_.empty()) { if (!mode_.empty()) {

View File

@ -27,10 +27,22 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive
static_cast<Tags *>(data)->handle_urgent_tags(tags); static_cast<Tags *>(data)->handle_urgent_tags(tags);
} }
static void listen_layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
const char *layout) {
// unused here
}
static void listen_layout_name_clear(void *data,
struct zriver_output_status_v1 *zriver_output_status_v1) {
// unused here
}
static const zriver_output_status_v1_listener output_status_listener_impl{ static const zriver_output_status_v1_listener output_status_listener_impl{
.focused_tags = listen_focused_tags, .focused_tags = listen_focused_tags,
.view_tags = listen_view_tags, .view_tags = listen_view_tags,
.urgent_tags = listen_urgent_tags, .urgent_tags = listen_urgent_tags,
.layout_name = listen_layout_name,
.layout_name_clear = listen_layout_name_clear,
}; };
static void listen_command_success(void *data, static void listen_command_success(void *data,
@ -50,10 +62,36 @@ static const zriver_command_callback_v1_listener command_callback_listener_impl{
.failure = listen_command_failure, .failure = listen_command_failure,
}; };
static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *output) {
static_cast<Tags *>(data)->focused_output_ = output;
}
static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *output) {}
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title, uint32_t tags) {
static_cast<Tags *>(data)->handle_focused_view(title, tags);
static_cast<Tags *>(data)->AModule::update();
}
static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *mode) {
static_cast<Tags *>(data)->AModule::update();
}
static const zriver_seat_status_v1_listener seat_status_listener_impl{
.focused_output = listen_focused_output,
.unfocused_output = listen_unfocused_output,
.focused_view = listen_focused_view,
.mode = listen_mode,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min(version, 2u); version = std::min(version, 4u);
if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) { if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {
spdlog::warn("river server does not support urgent tags"); spdlog::warn("river server does not support urgent tags");
} }
@ -86,6 +124,8 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
status_manager_{nullptr}, status_manager_{nullptr},
control_{nullptr}, control_{nullptr},
seat_{nullptr}, seat_{nullptr},
output_{nullptr},
focused_output_{nullptr},
bar_(bar), bar_(bar),
box_{bar.orientation, 0}, box_{bar.orientation, 0},
output_status_{nullptr} { output_status_{nullptr} {
@ -107,6 +147,11 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
spdlog::error("wl_seat not advertised"); spdlog::error("wl_seat not advertised");
} }
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);
output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
box_.set_name("tags"); box_.set_name("tags");
if (!id.empty()) { if (!id.empty()) {
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
@ -162,6 +207,10 @@ Tags::~Tags() {
zriver_control_v1_destroy(control_); zriver_control_v1_destroy(control_);
} }
if (seat_status_) {
zriver_seat_status_v1_destroy(seat_status_);
}
if (status_manager_) { if (status_manager_) {
zriver_status_manager_v1_destroy(status_manager_); zriver_status_manager_v1_destroy(status_manager_);
} }
@ -187,7 +236,14 @@ void Tags::handle_primary_clicked(uint32_t tag) {
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
// Send river command to toggle tag on right mouse click // Send river command to move view to tag on right mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "set-view-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
} else if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 2) {
// Send river command to toggle tag on middle mouse click
zriver_command_callback_v1 *callback; zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "toggle-focused-tags"); zriver_control_v1_add_argument(control_, "toggle-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
@ -263,4 +319,14 @@ void Tags::handle_urgent_tags(uint32_t tags) {
} }
} }
void Tags::handle_focused_view(const char *title, uint32_t tags) {
for (size_t i = 0; i < buttons_.size(); ++i) {
if ((1 << i) & tags && output_ == focused_output_) {
buttons_[i].get_style_context()->add_class("current-view");
} else {
buttons_[i].get_style_context()->remove_class("current-view");
}
}
}
} /* namespace waybar::modules::river */ } /* namespace waybar::modules::river */

View File

@ -4,14 +4,15 @@
#include <wayland-client.h> #include <wayland-client.h>
#include <algorithm> #include <algorithm>
#include <iostream>
#include "client.hpp" #include "client.hpp"
namespace waybar::modules::river { namespace waybar::modules::river {
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title) { const char *title, uint32_t tags) {
static_cast<Window *>(data)->handle_focused_view(title); static_cast<Window *>(data)->handle_focused_view(title, tags);
} }
static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
@ -39,7 +40,7 @@ static const zriver_seat_status_v1_listener seat_status_listener_impl{
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 2); version = std::min<uint32_t>(version, 4);
static_cast<Window *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( static_cast<Window *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
} }
@ -59,11 +60,15 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo
.global_remove = handle_global_remove}; .global_remove = handle_global_remove};
Window::Window(const std::string &id, const waybar::Bar &bar, const Json::Value &config) Window::Window(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::ALabel(config, "window", id, "{}", 30), : waybar::ALabel(config, "window", id, "{}", 30, true),
status_manager_{nullptr}, status_manager_{nullptr},
seat_{nullptr}, seat_{nullptr},
bar_(bar), bar_(bar),
seat_status_{nullptr} { seat_status_{nullptr} {
if (config_["default-format"].isString()) {
default_format_ = config_["default-format"].asString();
}
struct wl_display *display = Client::inst()->wl_display; struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display); struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this); wl_registry_add_listener(registry, &registry_listener_impl, this);
@ -95,7 +100,7 @@ Window::~Window() {
} }
} }
void Window::handle_focused_view(const char *title) { void Window::handle_focused_view(const char *title, uint32_t tags) {
// don't change the label on unfocused outputs. // don't change the label on unfocused outputs.
// this makes the current output report its currently focused view, and unfocused outputs will // this makes the current output report its currently focused view, and unfocused outputs will
// report their last focused views. when freshly starting the bar, unfocused outputs don't have a // report their last focused views. when freshly starting the bar, unfocused outputs don't have a
@ -103,7 +108,16 @@ void Window::handle_focused_view(const char *title) {
if (focused_output_ != output_) return; if (focused_output_ != output_) return;
if (std::strcmp(title, "") == 0 || format_.empty()) { if (std::strcmp(title, "") == 0 || format_.empty()) {
if (default_format_.has_value()) {
label_.show();
const std::string &default_format = default_format_.value();
label_.set_markup(default_format);
if (tooltipEnabled()) {
label_.set_tooltip_markup(default_format);
}
} else {
label_.hide(); // hide empty labels or labels with empty format label_.hide(); // hide empty labels or labels with empty format
}
} else { } else {
label_.show(); label_.show();
auto text = fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw()); auto text = fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw());

7
tsan.supp Normal file
View File

@ -0,0 +1,7 @@
# Suppress common thread issues in dependencies, these are often non-fixable or not an issue.
# Use it like this (when in repo root): TSAN_OPTIONS="suppressions=./tsan.supp" ./build/waybar
race:libfontconfig.so
race:libglib-2.0.so
race:libpango-1.0.so
race:libc.so.6
race:libgio-2.0.so