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

This commit is contained in:
2025-10-09 22:05:52 -07:00
parent 559079e9a6
commit c5a7fae1d9
14 changed files with 138 additions and 20 deletions

2
.gitignore vendored
View File

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

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,6 +28,7 @@ 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_;
private:

View File

@ -16,7 +16,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);

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

@ -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);
@ -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,37 @@ 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) {
// Intentionally empty
}
static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *output) {
// Intentionally empty
}
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 void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *mode) {
// Intentionally empty
}
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");
}
@ -107,6 +146,9 @@ 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);
box_.set_name("tags");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
@ -162,6 +204,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 +233,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 +316,24 @@ void Tags::handle_urgent_tags(uint32_t tags) {
}
}
void Tags::handle_focused_view(const char *title, uint32_t tags) {
auto hide_vacant = config_["hide-vacant"].asBool();
for (size_t i = 0; i < buttons_.size(); ++i) {
bool visible = buttons_[i].is_visible();
bool occupied = buttons_[i].get_style_context()->has_class("occupied");
bool focused = buttons_[i].get_style_context()->has_class("focused");
if ((1 << i) & tags) {
if (hide_vacant && !visible) {
buttons_[i].set_visible(true);
}
buttons_[i].get_style_context()->add_class("current-view");
} else {
if (hide_vacant && !(occupied || focused)) {
buttons_[i].set_visible(false);
}
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,7 +60,7 @@ 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),
@ -95,7 +96,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