Compare commits

...

4 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
16 changed files with 164 additions and 24 deletions

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ result
result-*
.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)
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>

View File

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

View File

@ -21,6 +21,7 @@ class Mode : public waybar::ALabel {
private:
const waybar::Bar &bar_;
std::set<std::string> hidden_modes_;
std::string mode_;
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_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags);
void handle_focused_view(const char *title, uint32_t tags);
void handle_show();
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_control_v1 *control_;
struct zriver_seat_status_v1 *seat_status_;
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:
const waybar::Bar &bar_;

View File

@ -3,6 +3,8 @@
#include <gtkmm/button.h>
#include <wayland-client.h>
#include <optional>
#include "ALabel.hpp"
#include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h"
@ -16,7 +18,7 @@ class Window : public waybar::ALabel {
virtual ~Window();
// 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_unfocused_output(struct wl_output *output);
@ -25,6 +27,7 @@ class Window : public waybar::ALabel {
private:
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 *focused_output_; // stores the currently focused output
struct zriver_seat_status_v1 *seat_status_;

View File

@ -17,6 +17,10 @@ Addressed by *river/mode*
default: {} ++
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*: ++
typeof: integer ++
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.focused*
- *#tags button.urgent*
- *#tags button.current-view*
Note that occupied/focused/urgent status may overlap. That is, a tag may be
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
waybar(5), river(1)

View File

@ -17,6 +17,10 @@ Addressed by *river/window*
default: {} ++
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*: ++
typeof: integer ++
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.
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": {
"orientation": "inherit",

View File

@ -100,7 +100,7 @@
</event>
</interface>
<interface name="zriver_seat_status_v1" version="3">
<interface name="zriver_seat_status_v1" version="4">
<description summary="track seat focus">
This interface allows clients to receive information about the current
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"/>
</event>
<event name="focused_view">
<event name="focused_view" since="4">
<description summary="information on the focused view">
Sent once on binding the interface and again whenever the focused
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.
</description>
<arg name="title" type="string" summary="title of the focused view"/>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="mode" since="3">

View File

@ -51,8 +51,12 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
if (config_["drawer"].isObject()) {
is_drawer = true;
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 =
(drawer_config["transition-duration"].isInt() ? drawer_config["transition-duration"].asInt()
: 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()
? drawer_config["transition-left-to-right"].asBool()
: 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);
@ -84,6 +88,14 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value&
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() {
box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
revealer.set_reveal_child(true);
@ -112,11 +124,7 @@ bool Group::handleToggle(GdkEventButton* const& e) {
if (!click_to_reveal || e->button != 1) {
return false;
}
if ((box.get_state_flags() & Gtk::StateFlags::STATE_FLAG_PRELIGHT) != 0U) {
hide_group();
} else {
show_group();
}
toggle();
return true;
}
@ -124,6 +132,12 @@ auto Group::update() -> void {
// 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; }
void Group::addWidget(Gtk::Widget& widget) {

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,
const char *title) {
const char *title, uint32_t tags) {
// 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,
const char *title) {
const char *title, uint32_t tags) {
// 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,
const char *interface, uint32_t version) {
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) {
spdlog::error(
"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");
}
if (config_["hidden-modes"].isArray()) {
for (const auto &mode : config["hidden-modes"]) {
if (mode.isString()) {
hidden_modes_.emplace(mode.asString());
}
}
}
label_.hide();
ALabel::update();
@ -95,7 +103,7 @@ Mode::~Mode() {
}
void Mode::handle_mode(const char *mode) {
if (format_.empty()) {
if (format_.empty() || hidden_modes_.contains(mode)) {
label_.hide();
} else {
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 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{
.focused_tags = listen_focused_tags,
.view_tags = listen_view_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,
@ -50,10 +62,36 @@ static const zriver_command_callback_v1_listener command_callback_listener_impl{
.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,
const char *interface, uint32_t version) {
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) {
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},
control_{nullptr},
seat_{nullptr},
output_{nullptr},
focused_output_{nullptr},
bar_(bar),
box_{bar.orientation, 0},
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");
}
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");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
@ -162,6 +207,10 @@ Tags::~Tags() {
zriver_control_v1_destroy(control_);
}
if (seat_status_) {
zriver_seat_status_v1_destroy(seat_status_);
}
if (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) {
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_control_v1_add_argument(control_, "toggle-focused-tags");
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 */

View File

@ -4,14 +4,15 @@
#include <wayland-client.h>
#include <algorithm>
#include <iostream>
#include "client.hpp"
namespace waybar::modules::river {
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title) {
static_cast<Window *>(data)->handle_focused_view(title);
const char *title, uint32_t tags) {
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,
@ -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,
const char *interface, uint32_t version) {
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 *>(
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};
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},
seat_{nullptr},
bar_(bar),
seat_status_{nullptr} {
if (config_["default-format"].isString()) {
default_format_ = config_["default-format"].asString();
}
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
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.
// 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
@ -103,7 +108,16 @@ void Window::handle_focused_view(const char *title) {
if (focused_output_ != output_) return;
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
}
} else {
label_.show();
auto text = fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw());