diff --git a/include/factory.hpp b/include/factory.hpp index 28273eb0..fcbf3a2d 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -10,6 +10,9 @@ #ifdef HAVE_WLR #include "modules/wlr/taskbar.hpp" #endif +#ifdef HAVE_RIVER +#include "modules/river/tags.hpp" +#endif #if defined(__linux__) && !defined(NO_FILESYSTEM) #include "modules/battery.hpp" #endif diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp new file mode 100644 index 00000000..f80b3c59 --- /dev/null +++ b/include/modules/river/tags.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "river-status-unstable-v1-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +namespace waybar::modules::river { + +class Tags : public waybar::AModule { + public: + Tags(const std::string &, const waybar::Bar &, const Json::Value &); + ~Tags(); + + // Handlers for wayland events + void handle_focused_tags(uint32_t tags); + void handle_view_tags(struct wl_array *tags); + + struct zriver_status_manager_v1 *status_manager_; + + private: + const waybar::Bar & bar_; + Gtk::Box box_; + std::vector buttons_; + struct zriver_output_status_v1 *output_status_; +}; + +} /* namespace waybar::modules::river */ diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd new file mode 100644 index 00000000..a02ddeb3 --- /dev/null +++ b/man/waybar-river-tags.5.scd @@ -0,0 +1,38 @@ +waybar-river-tags(5) + +# NAME + +waybar - river tags module + +# DESCRIPTION + +The *tags* module displays the current state of tags in river. + +# CONFIGURATION + +Addressed by *river/tags* + +*num-tags*: ++ + typeof: uint ++ + default: 9 ++ + The number of tags that should be displayed. + +# EXAMPLE + +``` +"river/tags": { + "num-tags": 5 +} +``` + +# STYLE + +- *#tags button* +- *#tags button.occupied* +- *#tags button.focused* + +Note that a tag can be both occupied and focused at the same time. + +# SEE ALSO + +waybar(5), river(1) diff --git a/meson.build b/meson.build index 496fc3a8..8d6dc168 100644 --- a/meson.build +++ b/meson.build @@ -170,6 +170,11 @@ if true src_files += 'src/modules/wlr/taskbar.cpp' endif +if true + add_project_arguments('-DHAVE_RIVER', language: 'cpp') + src_files += 'src/modules/river/tags.cpp' +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += 'src/modules/network.cpp' @@ -259,6 +264,7 @@ if scdoc.found() 'waybar-mpd.5.scd', 'waybar-network.5.scd', 'waybar-pulseaudio.5.scd', + 'waybar-river-tags.5.scd', 'waybar-sway-mode.5.scd', 'waybar-sway-window.5.scd', 'waybar-sway-workspaces.5.scd', diff --git a/protocol/meson.build b/protocol/meson.build index f4146aeb..07d524ae 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,6 +27,7 @@ client_protocols = [ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], + ['river-status-unstable-v1.xml'], ] client_protos_src = [] diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml new file mode 100644 index 00000000..a4d6f4e5 --- /dev/null +++ b/protocol/river-status-unstable-v1.xml @@ -0,0 +1,116 @@ + + + + Copyright 2020 Isaac Freund + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + 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. + + + + + This request indicates that the client will not use the + river_status_manager object any more. Objects that have been created + through this instance are not affected. + + + + + + This creates a new river_output_status object for the given wl_output. + + + + + + + + This creates a new river_seat_status object for the given wl_seat. + + + + + + + + + This interface allows clients to receive information about the current + windowing state of an output. + + + + + This request indicates that the client will not use the + river_output_status object any more. + + + + + + Sent once binding the interface and again whenever the tag focus of + the output changes. + + + + + + + Sent once on binding the interface and again whenever the tag state + of the output changes. + + + + + + + + This interface allows clients to receive information about the current + focus of a seat. + + + + + This request indicates that the client will not use the + river_seat_status object any more. + + + + + + Sent on binding the interface and again whenever an output gains focus. + + + + + + + Sent whenever an output loses focus. + + + + + + + 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/factory.cpp b/src/factory.cpp index f09ea31a..5a01d523 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -25,7 +25,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { #endif #ifdef HAVE_WLR if (ref == "wlr/taskbar") { - return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); + return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); + } +#endif +#ifdef HAVE_RIVER + if (ref == "river/tags") { + return new waybar::modules::river::Tags(id, bar_, config_[name]); } #endif if (ref == "idle_inhibitor") { diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp new file mode 100644 index 00000000..804ea090 --- /dev/null +++ b/src/modules/river/tags.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include + +#include "client.hpp" +#include "modules/river/tags.hpp" +#include "river-status-unstable-v1-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +namespace waybar::modules::river { + +static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + uint32_t tags) { + static_cast(data)->handle_focused_tags(tags); +} + +static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, + struct wl_array *tags) { + static_cast(data)->handle_view_tags(tags); +} + +static const zriver_output_status_v1_listener output_status_listener_impl{ + .focused_tags = listen_focused_tags, + .view_tags = listen_view_tags, +}; + +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) { + static_cast(data)->status_manager_ = static_cast( + wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + /* Ignore event */ +} + +static const wl_registry_listener registry_listener_impl = {.global = handle_global, + .global_remove = handle_global_remove}; + +Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) + : waybar::AModule(config, "tags", id, false, false), + status_manager_{nullptr}, + bar_(bar), + box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, + 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); + + if (!status_manager_) { + spdlog::error("river_status_manager_v1 not advertised"); + return; + } + + box_.set_name("tags"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + event_box_.add(box_); + + // Default to 9 tags + const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9; + for (uint32_t tag = 1; tag <= num_tags; ++tag) { + Gtk::Button &button = buttons_.emplace_back(std::to_string(tag)); + button.set_relief(Gtk::RELIEF_NONE); + box_.pack_start(button, false, false, 0); + button.show(); + } + + struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + 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_); +} + +Tags::~Tags() { + if (output_status_) { + zriver_output_status_v1_destroy(output_status_); + } +} + +void Tags::handle_focused_tags(uint32_t tags) { + uint32_t i = 0; + for (auto &button : buttons_) { + if ((1 << i) & tags) { + button.get_style_context()->add_class("focused"); + } else { + button.get_style_context()->remove_class("focused"); + } + ++i; + } +} + +void Tags::handle_view_tags(struct wl_array *view_tags) { + // First clear all occupied state + for (auto &button : buttons_) { + button.get_style_context()->remove_class("occupied"); + } + + // Set tags with a view to occupied + uint32_t *start = static_cast(view_tags->data); + for (uint32_t *tags = start; tags < start + view_tags->size / sizeof(uint32_t); ++tags) { + uint32_t i = 0; + for (auto &button : buttons_) { + if (*tags & (1 << i)) { + button.get_style_context()->add_class("occupied"); + } + ++i; + } + } +} + +} /* namespace waybar::modules::river */