diff --git a/include/factory.hpp b/include/factory.hpp index 558a8d4e..b2b242fa 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -21,6 +21,7 @@ #include "modules/river/mode.hpp" #include "modules/river/tags.hpp" #include "modules/river/window.hpp" +#include "modules/river/layout.hpp" #endif #ifdef HAVE_HYPRLAND #include "modules/hyprland/backend.hpp" diff --git a/include/modules/river/layout.hpp b/include/modules/river/layout.hpp new file mode 100644 index 00000000..ffa5e21e --- /dev/null +++ b/include/modules/river/layout.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "river-status-unstable-v1-client-protocol.h" + +namespace waybar::modules::river { + +class Layout : public waybar::ALabel { + public: + Layout(const std::string &, const waybar::Bar &, const Json::Value &); + ~Layout(); + + // Handlers for wayland events + void handle_name(const char *name); + void handle_clear(); + void handle_focused_output(struct wl_output *output); + void handle_unfocused_output(struct wl_output *output); + + struct zriver_status_manager_v1 *status_manager_; + struct wl_seat *seat_; + + private: + const waybar::Bar &bar_; + struct wl_output *output_; // stores the output this module belongs to + struct wl_output *focused_output_; // stores the currently focused output + struct zriver_output_status_v1 *output_status_; + struct zriver_seat_status_v1 *seat_status_; +}; + +} /* namespace waybar::modules::river */ diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd new file mode 100644 index 00000000..5b18eee8 --- /dev/null +++ b/man/waybar-river-layout.5.scd @@ -0,0 +1,67 @@ +waybar-river-layout(5) + +# NAME + +waybar - river layout module + +# DESCRIPTION + +The *layout* module displays the current layout in river. + +It may not be set until a layout is first applied. + +# CONFIGURATION + +Addressed by *river/layout* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how information should be displayed. On {} data gets inserted. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label. + +*max-length*: ++ + typeof: integer ++ + The maximum length in character the module should display. + +*min-length*: ++ + typeof: integer ++ + The minimum length in characters the module should take up. + +*align*: ++ + typeof: float ++ + The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right clicked on the module. + +# EXAMPLE + +``` +"river/layout": { + "format": "{}", + "min-length": 4, + "align": "right" +} +``` + +# STYLE + +- *#layout* +- *#layout.focused* Applied when the output this module's bar belongs to is focused. + +# SEE ALSO + +waybar(5), river(1) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 704d666b..9532b647 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -273,6 +273,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-river-mode(5)* - *waybar-river-tags(5)* - *waybar-river-window(5)* +- *waybar-river-layout(5)* - *waybar-states(5)* - *waybar-sway-mode(5)* - *waybar-sway-scratchpad(5)* diff --git a/meson.build b/meson.build index 77b292a7..ad6541e5 100644 --- a/meson.build +++ b/meson.build @@ -224,6 +224,7 @@ if true src_files += 'src/modules/river/mode.cpp' src_files += 'src/modules/river/tags.cpp' src_files += 'src/modules/river/window.cpp' + src_files += 'src/modules/river/layout.cpp' endif if true @@ -415,6 +416,7 @@ if scdoc.found() 'waybar-river-mode.5.scd', 'waybar-river-tags.5.scd', 'waybar-river-window.5.scd', + 'waybar-river-layout.5.scd', 'waybar-sway-language.5.scd', 'waybar-sway-mode.5.scd', 'waybar-sway-scratchpad.5.scd', diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml index 6a742563..e9629dde 100644 --- a/protocol/river-status-unstable-v1.xml +++ b/protocol/river-status-unstable-v1.xml @@ -16,7 +16,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - + A global factory for objects that receive status information specific to river. It could be used to implement, for example, a status bar. @@ -47,7 +47,7 @@ - + This interface allows clients to receive information about the current windowing state of an output. @@ -83,6 +83,21 @@ + + + + Sent once on binding the interface should a layout name exist and again + whenever the name changes. + + + + + + + Sent when the current layout name has been removed without a new one + being set, for example when the active layout generator disconnects. + + diff --git a/src/factory.cpp b/src/factory.cpp index 4f196f5d..dd5c142d 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -64,6 +64,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "river/window") { return new waybar::modules::river::Window(id, bar_, config_[name]); } + if (ref == "river/layout") { + return new waybar::modules::river::Layout(id, bar_, config_[name]); + } #endif #ifdef HAVE_HYPRLAND if (ref == "hyprland/window") { diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp new file mode 100644 index 00000000..e938400f --- /dev/null +++ b/src/modules/river/layout.cpp @@ -0,0 +1,174 @@ +#include "modules/river/layout.hpp" + +#include +#include + +#include "client.hpp" + +namespace waybar::modules::river { + +static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + uint32_t tags) { + // Intentionally empty +} + +static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + struct wl_array *tags) { + // Intentionally empty +} + +static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + uint32_t tags) { + // Intentionally empty +} + +static void listen_layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + const char *layout) { + static_cast(data)->handle_name(layout); +} + +static void listen_layout_name_clear(void *data, + struct zriver_output_status_v1 *zriver_output_status_v1) { + static_cast(data)->handle_clear(); +} + +static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_focused_output(output); +} + +static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_unfocused_output(output); +} + +static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *title) { + // Intentionally empty +} + +static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *mode) { + // Intentionally empty +} + +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 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, 4); + + // implies ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION + if (version < ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) { + spdlog::error( + "river server does not support the \"layout_name\" and \"layout_clear\" events; the " + "module will be disabled" + + std::to_string(version)); + return; + } + static_cast(data)->status_manager_ = static_cast( + wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); + } + + if (std::strcmp(interface, wl_seat_interface.name) == 0) { + version = std::min(version, 1); + static_cast(data)->seat_ = static_cast( + wl_registry_bind(registry, name, &wl_seat_interface, version)); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + // Nobody cares +} + +static const wl_registry_listener registry_listener_impl = {.global = handle_global, + .global_remove = handle_global_remove}; + +Layout::Layout(const std::string &id, const waybar::Bar &bar, const Json::Value &config) + : waybar::ALabel(config, "layout", id, "{}"), + status_manager_{nullptr}, + seat_{nullptr}, + bar_(bar), + output_status_{nullptr} { + struct wl_display *display = Client::inst()->wl_display; + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener_impl, this); + wl_display_roundtrip(display); + + output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + + if (!status_manager_) { + spdlog::error("river_status_manager_v1 not advertised"); + return; + } + + if (!seat_) { + spdlog::error("wl_seat not advertised"); + } + + label_.hide(); + ALabel::update(); + + 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_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); + zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); + + zriver_status_manager_v1_destroy(status_manager_); +} + +Layout::~Layout() { + if (output_status_) { + zriver_output_status_v1_destroy(output_status_); + } + if (seat_status_) { + zriver_seat_status_v1_destroy(seat_status_); + } +} + +void Layout::handle_name(const char *name) { + if (std::strcmp(name, "") == 0 || format_.empty()) { + label_.hide(); // hide empty labels or labels with empty format + } else { + label_.show(); + label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw())); + } + ALabel::update(); +} + +void Layout::handle_clear() { + label_.hide(); + ALabel::update(); +} + +void Layout::handle_focused_output(struct wl_output *output) { + if (output_ == output) { // if we focused the output this bar belongs to + label_.get_style_context()->add_class("focused"); + ALabel::update(); + } + focused_output_ = output; +} + +void Layout::handle_unfocused_output(struct wl_output *output) { + if (output_ == output) { // if we unfocused the output this bar belongs to + label_.get_style_context()->remove_class("focused"); + ALabel::update(); + } +} + +} /* namespace waybar::modules::river */