diff --git a/.gitignore b/.gitignore index b486237e..c75a9e79 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ result result-* .ccls-cache + +.wraplock diff --git a/include/group.hpp b/include/group.hpp index 5ce331a8..4376e3fa 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -4,6 +4,8 @@ #include #include +#include + #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 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(); }; diff --git a/include/modules/river/mode.hpp b/include/modules/river/mode.hpp index 246cecae..35b1c3c6 100644 --- a/include/modules/river/mode.hpp +++ b/include/modules/river/mode.hpp @@ -21,6 +21,7 @@ class Mode : public waybar::ALabel { private: const waybar::Bar &bar_; + std::set hidden_modes_; std::string mode_; struct zriver_seat_status_v1 *seat_status_; }; diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index fd867346..047ee086 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -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: diff --git a/include/modules/river/window.hpp b/include/modules/river/window.hpp index bf29ebbf..32542555 100644 --- a/include/modules/river/window.hpp +++ b/include/modules/river/window.hpp @@ -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); diff --git a/man/waybar-river-mode.5.scd b/man/waybar-river-mode.5.scd index 5837411d..b6b65846 100644 --- a/man/waybar-river-mode.5.scd +++ b/man/waybar-river-mode.5.scd @@ -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). diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index 64621229..077f8b04 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -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) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 1b799275..2ee63bc4 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -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", diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml index e9629dde..8ba02dc0 100644 --- a/protocol/river-status-unstable-v1.xml +++ b/protocol/river-status-unstable-v1.xml @@ -100,7 +100,7 @@ - + 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 @@ - + 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. + diff --git a/src/group.cpp b/src/group.cpp index 50841efd..91b60578 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -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) { diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index 308a0901..7e71ccd6 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -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 } diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 1f788e09..619c432a 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -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(version, 3); + version = std::min(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()) { diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 33be0e6f..146387bb 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -27,10 +27,22 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive static_cast(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(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 */ diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index dc7a6459..5c2b5ddd 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -4,14 +4,15 @@ #include #include +#include #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(data)->handle_focused_view(title); + const char *title, uint32_t tags) { + static_cast(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(version, 2); + version = std::min(version, 4); static_cast(data)->status_manager_ = static_cast( 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