diff --git a/README.md b/README.md index 32fbfa69..5781ea3a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - Sway (Workspaces, Binding mode, Focused window name) - River (Mapping mode, Tags, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name) -- DWL (Tags) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) +- DWL (Tags, Focused window name) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time - Battery diff --git a/include/modules/dwl/window.hpp b/include/modules/dwl/window.hpp new file mode 100644 index 00000000..e4c96404 --- /dev/null +++ b/include/modules/dwl/window.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "util/json.hpp" +#include "dwl-ipc-unstable-v2-client-protocol.h" + +namespace waybar::modules::dwl { + +class Window : public AAppIconLabel, public sigc::trackable { + public: + Window(const std::string&, const waybar::Bar&, const Json::Value&); + virtual ~Window() = default; + + void handle_layout(const uint32_t layout); + void handle_title(const char *title); + void handle_appid(const char *ppid); + void handle_layout_symbol(const char *layout_symbol); + void handle_frame(); + + struct zdwl_ipc_manager_v2 *status_manager_; + private: + const Bar& bar_; + + std::string title_; + std::string appid_; + std::string layout_symbol_; + uint32_t layout_; + + struct zdwl_ipc_output_v2 *output_status_; +}; + +} // namespace waybar::modules::dwl diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd new file mode 100644 index 00000000..c2f5b93e --- /dev/null +++ b/man/waybar-dwl-window.5.scd @@ -0,0 +1,118 @@ +waybar-dwl-window(5) + +# NAME + +waybar - dwl window module + +# DESCRIPTION + +The *window* module displays the title of the currently focused window in DWL + +# CONFIGURATION + +Addressed by *dwl/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format, how information should be displayed. + +*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 accept. + +*align*: ++ + typeof: float ++ + The alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + +*justify*: ++ + typeof: string ++ + The alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning. + +*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-click on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite the module format output. See *rewrite rules*. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Option to hide the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Option to change the size of the application icon. + +# FORMAT REPLACEMENTS + +*{title}*: The title of the focused window. + +*{app_id}*: The app_id of the focused window. + +*{layout}*: The layout of the focused window. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the format output is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. + +# EXAMPLES + +``` +"dwl/window": { + "format": "{}", + "max-length": 50, + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } +} +``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 2d4de0c9..5fe30ca8 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -310,6 +310,7 @@ A group may hide all but one element, showing them only on mouse hover. In order - *waybar-custom(5)* - *waybar-disk(5)* - *waybar-dwl-tags(5)* +- *waybar-dwl-window(5)* - *waybar-gamemode(5)* - *waybar-hyprland-language(5)* - *waybar-hyprland-submap(5)* diff --git a/meson.build b/meson.build index 12097608..bbcde7f0 100644 --- a/meson.build +++ b/meson.build @@ -293,7 +293,9 @@ endif if true add_project_arguments('-DHAVE_DWL', language: 'cpp') src_files += files('src/modules/dwl/tags.cpp') + src_files += files('src/modules/dwl/window.cpp') man_files += files('man/waybar-dwl-tags.5.scd') + man_files += files('man/waybar-dwl-window.5.scd') endif if true diff --git a/src/factory.cpp b/src/factory.cpp index 94076201..0549fe09 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -28,6 +28,7 @@ #endif #ifdef HAVE_DWL #include "modules/dwl/tags.hpp" +#include "modules/dwl/window.hpp" #endif #ifdef HAVE_HYPRLAND #include "modules/hyprland/language.hpp" @@ -187,6 +188,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "dwl/tags") { return new waybar::modules::dwl::Tags(id, bar_, config_[name]); } + if (ref == "dwl/window") { + return new waybar::modules::dwl::Window(id, bar_, config_[name]); + } #endif #ifdef HAVE_HYPRLAND if (ref == "hyprland/window") { diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 036cb1b8..085b8224 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -21,11 +21,11 @@ wl_array tags, layouts; static uint num_tags = 0; -void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { +static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { // Intentionally empty } -void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { +static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { // Intentionally empty } @@ -37,15 +37,15 @@ static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag : num_tags & ~(1 << tag); } -void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) { +static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) { // Intentionally empty } -void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) { +static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) { // Intentionally empty } -void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { +static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { // Intentionally empty } @@ -97,6 +97,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con 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); diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp new file mode 100644 index 00000000..cfd47a05 --- /dev/null +++ b/src/modules/dwl/window.cpp @@ -0,0 +1,126 @@ +#include "modules/dwl/window.hpp" + +#include +#include +#include +#include +#include +#include + +#include "client.hpp" +#include "dwl-ipc-unstable-v2-client-protocol.h" + +#include "util/rewrite_string.hpp" + +namespace waybar::modules::dwl { + +static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { + // Intentionally empty +} + +static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { + // Intentionally empty +} + +static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state, + uint32_t clients, uint32_t focused) { + // Intentionally empty +} + +static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) { + static_cast(data)->handle_layout_symbol(layout); +} + +static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) { + static_cast(data)->handle_title(title); +} + +static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { + static_cast(data)->handle_frame(); +} + +static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) { + static_cast(data)->handle_layout(layout); +} + +static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){ + static_cast(data)->handle_appid(appid); +}; + +static const zdwl_ipc_output_v2_listener output_status_listener_impl{ + .toggle_visibility = toggle_visibility, + .active = active, + .tag = set_tag, + .layout = set_layout, + .title = title, + .appid = appid, + .layout_symbol = set_layout_symbol, + .frame = dwl_frame, +}; + +static void handle_global(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version) { + if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { + static_cast(data)->status_manager_ = static_cast( + (zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); + } +} + +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}; + +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) + : AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar) { + 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("dwl_status_manager_v2 not advertised"); + return; + } + + struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output); + zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this); + zdwl_ipc_manager_v2_destroy(status_manager_); +} + +void Window::handle_title(const char *title) { + title_ = title; +} + +void Window::handle_appid(const char *appid) { + appid_ = appid; +} + +void Window::handle_layout_symbol(const char *layout_symbol) { + layout_symbol_ = layout_symbol; +} + +void Window::handle_layout(const uint32_t layout) { + layout_ = layout; +} + +void Window::handle_frame() { + label_.set_markup(waybar::util::rewriteString( + fmt::format( + fmt::runtime(format_), + fmt::arg("title", title_), + fmt::arg("layout", layout_symbol_), + fmt::arg("app_id", appid_)), + config_["rewrite"])); + updateAppIconName(appid_, ""); + updateAppIcon(); + if (tooltipEnabled()) { + label_.set_tooltip_text(title_); + } +} + +} // namespace waybar::modules::dwl