diff --git a/include/modules/ext/workspace_manager.hpp b/include/modules/ext/workspace_manager.hpp new file mode 100644 index 00000000..0607b5ba --- /dev/null +++ b/include/modules/ext/workspace_manager.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "ext-workspace-v1-client-protocol.h" + +namespace waybar::modules::ext { + +class WorkspaceGroup; +class Workspace; + +class WorkspaceManager final : public AModule { + public: + WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); + ~WorkspaceManager() override; + void register_manager(wl_registry *registry, uint32_t name, uint32_t version); + void remove_workspace_group(uint32_t id); + void remove_workspace(uint32_t id); + void set_needs_sorting() { needs_sorting_ = true; } + + // wl events + void handle_workspace_group(ext_workspace_group_handle_v1 *handle); + void handle_workspace(ext_workspace_handle_v1 *handle); + void handle_done(); + void handle_finished(); + + // wl requests + void commit() const; + + private: + void update() override; + bool has_button(const Gtk::Button *button); + void sort_workspaces(); + void clear_buttons(); + void update_buttons(); + + static uint32_t group_global_id; + static uint32_t workspace_global_id; + uint32_t workspace_name = 0; + + bool sort_by_id_ = false; + bool sort_by_name_ = true; + bool sort_by_coordinates_ = false; + bool all_outputs_ = false; + + const waybar::Bar &bar_; + Gtk::Box box_; + + ext_workspace_manager_v1 *ext_manager_ = nullptr; + std::vector> groups_; + std::vector> workspaces_; + + bool needs_sorting_ = false; +}; + +class WorkspaceGroup { + public: + WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, uint32_t id); + ~WorkspaceGroup(); + + u_int32_t id() const { return id_; } + bool has_output(const wl_output *output); + bool has_workspace(const ext_workspace_handle_v1 *workspace); + + // wl events + void handle_capabilities(uint32_t capabilities); + void handle_output_enter(wl_output *output); + void handle_output_leave(wl_output *output); + void handle_workspace_enter(ext_workspace_handle_v1 *handle); + void handle_workspace_leave(ext_workspace_handle_v1 *handle); + void handle_removed(); + + private: + WorkspaceManager &workspaces_manager_; + ext_workspace_group_handle_v1 *ext_handle_; + uint32_t id_; + std::vector outputs_; + std::vector workspaces_; +}; + +class Workspace { + public: + Workspace(const Json::Value &config, WorkspaceManager &manager, ext_workspace_handle_v1 *handle, + uint32_t id, const std::string &name); + ~Workspace(); + + ext_workspace_handle_v1 *handle() const { return ext_handle_; } + u_int32_t id() const { return id_; } + std::string &workspace_id() { return workspace_id_; } + std::string &name() { return name_; } + std::vector &coordinates() { return coordinates_; } + Gtk::Button &button() { return button_; } + void update(); + + // wl events + void handle_id(const std::string &id); + void handle_name(const std::string &name); + void handle_coordinates(const std::vector &coordinates); + void handle_state(uint32_t state); + void handle_capabilities(uint32_t capabilities); + void handle_removed(); + + // gdk events + bool handle_clicked(const GdkEventButton *button) const; + + private: + bool has_state(uint32_t state) const { return (state_ & state) == state; } + std::string icon(); + + WorkspaceManager &workspace_manager_; + ext_workspace_handle_v1 *ext_handle_ = nullptr; + uint32_t id_; + uint32_t state_ = 0; + std::string workspace_id_; + std::string name_; + std::vector coordinates_; + + bool active_only_ = false; + bool ignore_hidden_ = true; + std::string format_; + bool with_icon_ = false; + static std::map icon_map_; + std::string on_click_action_; + std::string on_click_middle_action_; + std::string on_click_right_action_; + + Gtk::Button button_; + Gtk::Box content_; + Gtk::Label label_; + + bool needs_updating_ = false; +}; + +} // namespace waybar::modules::ext diff --git a/include/modules/ext/workspace_manager_binding.hpp b/include/modules/ext/workspace_manager_binding.hpp new file mode 100644 index 00000000..b41f207c --- /dev/null +++ b/include/modules/ext/workspace_manager_binding.hpp @@ -0,0 +1,10 @@ +#include "ext-workspace-v1-client-protocol.h" + +namespace waybar::modules::ext { +void add_registry_listener(void *data); +void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data); +void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, + void *data); +ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, + uint32_t version, void *data); +} // namespace waybar::modules::ext diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp deleted file mode 100644 index f7cc759e..00000000 --- a/include/modules/wlr/workspace_manager.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "AModule.hpp" -#include "bar.hpp" -#include "ext-workspace-unstable-v1-client-protocol.h" - -namespace waybar::modules::wlr { - -class WorkspaceManager; -class WorkspaceGroup; - -class Workspace { - public: - Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zext_workspace_handle_v1 *workspace, uint32_t id, std::string name); - ~Workspace(); - auto update() -> void; - - auto id() const -> uint32_t { return id_; } - auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); } - auto is_urgent() const -> bool { return state_ & static_cast(State::URGENT); } - auto is_hidden() const -> bool { return state_ & static_cast(State::HIDDEN); } - auto is_empty() const -> bool { return state_ & static_cast(State::EMPTY); } - auto is_persistent() const -> bool { return persistent_; } - // wlr stuff - auto handle_name(const std::string &name) -> void; - auto handle_coordinates(const std::vector &coordinates) -> void; - auto handle_state(const std::vector &state) -> void; - auto handle_remove() -> void; - auto make_persistent() -> void; - auto handle_duplicate() -> void; - - auto handle_done() -> void; - auto handle_clicked(GdkEventButton *bt) -> bool; - auto show() -> void; - auto hide() -> void; - auto get_button_ref() -> Gtk::Button & { return button_; } - auto get_name() -> std::string & { return name_; } - auto get_coords() -> std::vector & { return coordinates_; } - - enum class State { - ACTIVE = (1 << 0), - URGENT = (1 << 1), - HIDDEN = (1 << 2), - EMPTY = (1 << 3), - }; - - private: - auto get_icon() -> std::string; - - const Bar &bar_; - const Json::Value &config_; - WorkspaceGroup &workspace_group_; - - // wlr stuff - zext_workspace_handle_v1 *workspace_handle_; - uint32_t state_ = 0; - - uint32_t id_; - std::string name_; - std::vector coordinates_; - static std::map icons_map_; - std::string format_; - bool with_icon_ = false; - bool persistent_ = false; - - Gtk::Button button_; - Gtk::Box content_; - Gtk::Label label_; -}; - -class WorkspaceGroup { - public: - WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, - WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, - uint32_t id); - ~WorkspaceGroup(); - auto update() -> void; - - auto id() const -> uint32_t { return id_; } - auto is_visible() const -> bool; - auto remove_workspace(uint32_t id_) -> void; - auto active_only() const -> bool; - auto creation_delayed() const -> bool; - auto workspaces() -> std::vector> & { return workspaces_; } - auto persistent_workspaces() -> std::vector & { return persistent_workspaces_; } - - auto sort_workspaces() -> void; - auto set_need_to_sort() -> void { need_to_sort = true; } - auto add_button(Gtk::Button &button) -> void; - auto remove_button(Gtk::Button &button) -> void; - auto fill_persistent_workspaces() -> void; - auto create_persistent_workspaces() -> void; - - // wlr stuff - auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void; - auto handle_remove() -> void; - auto handle_output_enter(wl_output *output) -> void; - auto handle_output_leave() -> void; - auto handle_done() -> void; - auto commit() -> void; - - private: - static uint32_t workspace_global_id; - const waybar::Bar &bar_; - Gtk::Box &box_; - const Json::Value &config_; - WorkspaceManager &workspace_manager_; - - // wlr stuff - zext_workspace_group_handle_v1 *workspace_group_handle_; - wl_output *output_ = nullptr; - - uint32_t id_; - std::vector> workspaces_; - bool need_to_sort = false; - std::vector persistent_workspaces_; - bool persistent_created_ = false; -}; - -class WorkspaceManager : public AModule { - public: - WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); - ~WorkspaceManager() override; - auto update() -> void override; - - auto all_outputs() const -> bool { return all_outputs_; } - auto active_only() const -> bool { return active_only_; } - auto workspace_comparator() const - -> std::function &, std::unique_ptr &)>; - auto creation_delayed() const -> bool { return creation_delayed_; } - - auto sort_workspaces() -> void; - auto remove_workspace_group(uint32_t id_) -> void; - - // wlr stuff - auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; - auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle) - -> void; - auto handle_done() -> void; - auto handle_finished() -> void; - auto commit() -> void; - - private: - const waybar::Bar &bar_; - Gtk::Box box_; - std::vector> groups_; - - // wlr stuff - zext_workspace_manager_v1 *workspace_manager_ = nullptr; - - static uint32_t group_global_id; - - bool sort_by_name_ = true; - bool sort_by_coordinates_ = true; - bool sort_by_number_ = false; - bool all_outputs_ = false; - bool active_only_ = false; - bool creation_delayed_ = false; -}; - -} // namespace waybar::modules::wlr diff --git a/include/modules/wlr/workspace_manager_binding.hpp b/include/modules/wlr/workspace_manager_binding.hpp deleted file mode 100644 index cc242c94..00000000 --- a/include/modules/wlr/workspace_manager_binding.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "ext-workspace-unstable-v1-client-protocol.h" - -namespace waybar::modules::wlr { -void add_registry_listener(void *data); -void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data); -void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, - void *data); -zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data); -} // namespace waybar::modules::wlr diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-ext-workspaces.5.scd similarity index 71% rename from man/waybar-wlr-workspaces.5.scd rename to man/waybar-ext-workspaces.5.scd index 62d3f636..54c67be2 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-ext-workspaces.5.scd @@ -10,7 +10,7 @@ The *workspaces* module displays the currently used workspaces in wayland compos # CONFIGURATION -Addressed by *wlr/workspaces* +Addressed by *ext/workspaces* *format*: ++ typeof: string ++ @@ -24,18 +24,18 @@ Addressed by *wlr/workspaces* *sort-by-name*: ++ typeof: bool ++ default: true ++ - Should workspaces be sorted by name. + Should workspaces be sorted by name. Workspace names will be sorted numerically when all names are numbers. *sort-by-coordinates*: ++ typeof: bool ++ - default: true ++ + default: false ++ Should workspaces be sorted by coordinates. ++ Note that if both *sort-by-name* and *sort-by-coordinates* are true sort-by name will be first. If both are false - sort by id will be performed. -*sort-by-number*: ++ +*sort-by-id*: ++ typeof: bool ++ default: false ++ - If set to true, workspace names will be sorted numerically. Takes precedence over any other sort-by option. + Should workspaces be sorted by ID. Workspace ID will be sorted numerically when all ID are numbers. Takes precedence over any other sort-by option. *all-outputs*: ++ typeof: bool ++ @@ -47,9 +47,16 @@ Addressed by *wlr/workspaces* default: false ++ If set to true only active or urgent workspaces will be shown. +*ignore-hidden*: ++ + typeof: bool ++ + default: true ++ + If set to false hidden workspaces will be shown. + # FORMAT REPLACEMENTS -*{name}*: Name of workspace assigned by compositor +*{name}*: Name of workspace assigned by compositor. + +*{id}*: ID of workspace assigned by compositor. *{icon}*: Icon, as defined in *format-icons*. @@ -69,18 +76,18 @@ In addition to workspace name matching, the following *format-icons* can be set. # EXAMPLES ``` -"wlr/workspaces": { +"ext/workspaces": { "format": "{name}: {icon}", + "on-click": "activate", "format-icons": { - "1": "", - "2": "", - "3": "", - "4": "", - "5": "", + "Workspace 1": "", + "Workspace 2": "", + "Workspace 3": "", + "Workspace 4": "", "active": "", "default": "" }, - "sort-by-number": true + "sort-by-id": true } ``` diff --git a/meson.build b/meson.build index a6eabd69..6f404b29 100644 --- a/meson.build +++ b/meson.build @@ -277,6 +277,17 @@ if true man_files += files('man/waybar-wlr-taskbar.5.scd') endif +if wayland_protos.version().version_compare('>=1.39') + add_project_arguments('-DHAVE_EXT_WORKSPACES', language: 'cpp') + src_files += files( + 'src/modules/ext/workspace_manager.cpp', + 'src/modules/ext/workspace_manager_binding.cpp', + ) + man_files += files( + 'man/waybar-ext-workspaces.5.scd', + ) +endif + if true add_project_arguments('-DHAVE_RIVER', language: 'cpp') src_files += files( @@ -486,17 +497,6 @@ else man_files += files('man/waybar-clock.5.scd') endif -if get_option('experimental') - add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp') - src_files += files( - 'src/modules/wlr/workspace_manager.cpp', - 'src/modules/wlr/workspace_manager_binding.cpp', - ) - man_files += files( - 'man/waybar-wlr-workspaces.5.scd', - ) -endif - cava = dependency('cava', version : '>=0.10.4', required: get_option('cava'), diff --git a/protocol/ext-workspace-unstable-v1.xml b/protocol/ext-workspace-unstable-v1.xml deleted file mode 100644 index 24410b62..00000000 --- a/protocol/ext-workspace-unstable-v1.xml +++ /dev/null @@ -1,306 +0,0 @@ - - - - Copyright © 2019 Christopher Billington - Copyright © 2020 Ilia Bozhinov - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, 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. - - - - - Workspaces, also called virtual desktops, are groups of surfaces. A - compositor with a concept of workspaces may only show some such groups of - surfaces (those of 'active' workspaces) at a time. 'Activating' a - workspace is a request for the compositor to display that workspace's - surfaces as normal, whereas the compositor may hide or otherwise - de-emphasise surfaces that are associated only with 'inactive' workspaces. - Workspaces are grouped by which sets of outputs they correspond to, and - may contain surfaces only from those outputs. In this way, it is possible - for each output to have its own set of workspaces, or for all outputs (or - any other arbitrary grouping) to share workspaces. Compositors may - optionally conceptually arrange each group of workspaces in an - N-dimensional grid. - - The purpose of this protocol is to enable the creation of taskbars and - docks by providing them with a list of workspaces and their properties, - and allowing them to activate and deactivate workspaces. - - After a client binds the zext_workspace_manager_v1, each workspace will be - sent via the workspace event. - - - - - This event is emitted whenever a new workspace group has been created. - - All initial details of the workspace group (workspaces, outputs) will be - sent immediately after this event via the corresponding events in - zext_workspace_group_handle_v1. - - - - - - - The client must send this request after it has finished sending other - requests. The compositor must process a series of requests preceding a - commit request atomically. - - This allows changes to the workspace properties to be seen as atomic, - even if they happen via multiple events, and even if they involve - multiple zext_workspace_handle_v1 objects, for example, deactivating one - workspace and activating another. - - - - - - This event is sent after all changes in all workspace groups have been - sent. - - This allows changes to one or more zext_workspace_group_handle_v1 - properties to be seen as atomic, even if they happen via multiple - events. In particular, an output moving from one workspace group to - another sends an output_enter event and an output_leave event to the two - zext_workspace_group_handle_v1 objects in question. The compositor sends - the done event only after updating the output information in both - workspace groups. - - - - - - This event indicates that the compositor is done sending events to the - zext_workspace_manager_v1. The server will destroy the object - immediately after sending this request, so it will become invalid and - the client should free any resources associated with it. - - - - - - Indicates the client no longer wishes to receive events for new - workspace groups. However the compositor may emit further workspace - events, until the finished event is emitted. - - The client must not send any more requests after this one. - - - - - - - A zext_workspace_group_handle_v1 object represents a a workspace group - that is assigned a set of outputs and contains a number of workspaces. - - The set of outputs assigned to the workspace group is conveyed to the client via - output_enter and output_leave events, and its workspaces are conveyed with - workspace events. - - For example, a compositor which has a set of workspaces for each output may - advertise a workspace group (and its workspaces) per output, whereas a compositor - where a workspace spans all outputs may advertise a single workspace group for all - outputs. - - - - - This event is emitted whenever an output is assigned to the workspace - group. - - - - - - - This event is emitted whenever an output is removed from the workspace - group. - - - - - - - This event is emitted whenever a new workspace has been created. - - All initial details of the workspace (name, coordinates, state) will - be sent immediately after this event via the corresponding events in - zext_workspace_handle_v1. - - - - - - - This event means the zext_workspace_group_handle_v1 has been destroyed. - It is guaranteed there won't be any more events for this - zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes - inert so any requests will be ignored except the destroy request. - - The compositor must remove all workspaces belonging to a workspace group - before removing the workspace group. - - - - - - Request that the compositor create a new workspace with the given name. - - There is no guarantee that the compositor will create a new workspace, - or that the created workspace will have the provided name. - - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - - A zext_workspace_handle_v1 object represents a a workspace that handles a - group of surfaces. - - Each workspace has a name, conveyed to the client with the name event; a - list of states, conveyed to the client with the state event; and - optionally a set of coordinates, conveyed to the client with the - coordinates event. The client may request that the compositor activate or - deactivate the workspace. - - Each workspace can belong to only a single workspace group. - Depepending on the compositor policy, there might be workspaces with - the same name in different workspace groups, but these workspaces are still - separate (e.g. one of them might be active while the other is not). - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and whenever the name of the workspace changes. - - - - - - - This event is used to organize workspaces into an N-dimensional grid - within a workspace group, and if supported, is emitted immediately after - the zext_workspace_handle_v1 is created and whenever the coordinates of - the workspace change. Compositors may not send this event if they do not - conceptually arrange workspaces in this way. If compositors simply - number workspaces, without any geometric interpretation, they may send - 1D coordinates, which clients should not interpret as implying any - geometry. Sending an empty array means that the compositor no longer - orders the workspace geometrically. - - Coordinates have an arbitrary number of dimensions N with an uint32 - position along each dimension. By convention if N > 1, the first - dimension is X, the second Y, the third Z, and so on. The compositor may - chose to utilize these events for a more novel workspace layout - convention, however. No guarantee is made about the grid being filled or - bounded; there may be a workspace at coordinate 1 and another at - coordinate 1000 and none in between. Within a workspace group, however, - workspaces must have unique coordinates of equal dimensionality. - - - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and each time the workspace state changes, either because of a - compositor action or because of a request in this protocol. - - - - - - - The different states that a workspace can have. - - - - - - - The workspace is not visible in its workspace group, and clients - attempting to visualize the compositor workspace state should not - display such workspaces. - - - - - - - This event means the zext_workspace_handle_v1 has been destroyed. It is - guaranteed there won't be any more events for this - zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so - any requests will be ignored except the destroy request. - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - Request that this workspace be activated. - - There is no guarantee the workspace will be actually activated, and - behaviour may be compositor-dependent. For example, activating a - workspace may or may not deactivate all other workspaces in the same - group. - - - - - - Request that this workspace be deactivated. - - There is no guarantee the workspace will be actually deactivated. - - - - - - Request that this workspace be removed. - - There is no guarantee the workspace will be actually removed. - - - - diff --git a/protocol/meson.build b/protocol/meson.build index cd9a77b1..b16113b2 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -26,12 +26,17 @@ client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], - ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ['dwl-ipc-unstable-v2.xml'], ] +if wayland_protos.version().version_compare('>=1.39') + client_protocols += [ + [wl_protocol_dir, 'staging/ext-workspace/ext-workspace-v1.xml'] + ] +endif + client_protos_src = [] client_protos_headers = [] diff --git a/src/factory.cpp b/src/factory.cpp index f7aa2d30..20408106 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -17,8 +17,8 @@ #ifdef HAVE_WLR_TASKBAR #include "modules/wlr/taskbar.hpp" #endif -#ifdef HAVE_WLR_WORKSPACES -#include "modules/wlr/workspace_manager.hpp" +#ifdef HAVE_EXT_WORKSPACES +#include "modules/ext/workspace_manager.hpp" #endif #ifdef HAVE_RIVER #include "modules/river/layout.hpp" @@ -178,9 +178,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } #endif -#ifdef HAVE_WLR_WORKSPACES - if (ref == "wlr/workspaces") { - return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); +#ifdef HAVE_EXT_WORKSPACES + if (ref == "ext/workspaces") { + return new waybar::modules::ext::WorkspaceManager(id, bar_, config_[name]); } #endif #ifdef HAVE_RIVER diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp new file mode 100644 index 00000000..5fec3cdb --- /dev/null +++ b/src/modules/ext/workspace_manager.cpp @@ -0,0 +1,508 @@ +#include "modules/ext/workspace_manager.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "client.hpp" +#include "gtkmm/widget.h" +#include "modules/ext/workspace_manager_binding.hpp" + +namespace waybar::modules::ext { + +// WorkspaceManager + +uint32_t WorkspaceManager::group_global_id = 0; +uint32_t WorkspaceManager::workspace_global_id = 0; +std::map Workspace::icon_map_; + +WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, + const Json::Value &config) + : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { + add_registry_listener(this); + + // parse configuration + + const auto config_sort_by_number = config_["sort-by-number"]; + if (config_sort_by_number.isBool()) { + spdlog::warn("[ext/workspaces]: Prefer sort-by-id instead of sort-by-number"); + sort_by_id_ = config_sort_by_number.asBool(); + } + + const auto config_sort_by_id = config_["sort-by-id"]; + if (config_sort_by_id.isBool()) { + sort_by_id_ = config_sort_by_id.asBool(); + } + + const auto config_sort_by_name = config_["sort-by-name"]; + if (config_sort_by_name.isBool()) { + sort_by_name_ = config_sort_by_name.asBool(); + } + + const auto config_sort_by_coordinates = config_["sort-by-coordinates"]; + if (config_sort_by_coordinates.isBool()) { + sort_by_coordinates_ = config_sort_by_coordinates.asBool(); + } + + const auto config_all_outputs = config_["all-outputs"]; + if (config_all_outputs.isBool()) { + all_outputs_ = config_all_outputs.asBool(); + } + + // setup UI + + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + box_.get_style_context()->add_class(MODULE_CLASS); + event_box_.add(box_); + + spdlog::debug("[ext/workspaces]: Workspace manager created"); +} + +WorkspaceManager::~WorkspaceManager() { + workspaces_.clear(); + groups_.clear(); + + if (ext_manager_ != nullptr) { + auto *display = Client::inst()->wl_display; + // Send `stop` request and wait for one roundtrip. + ext_workspace_manager_v1_stop(ext_manager_); + wl_display_roundtrip(display); + } + + if (ext_manager_ != nullptr) { + spdlog::warn("[ext/workspaces]: Destroying workspace manager before .finished event"); + ext_workspace_manager_v1_destroy(ext_manager_); + } + + spdlog::debug("[ext/workspaces]: Workspace manager destroyed"); +} + +void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) { + if (ext_manager_ != nullptr) { + spdlog::warn("[ext/workspaces]: Register workspace manager again although already registered!"); + return; + } + if (version != 1) { + spdlog::warn("[ext/workspaces]: Using different workspace manager protocol version: {}", + version); + } + + ext_manager_ = workspace_manager_bind(registry, name, version, this); +} + +void WorkspaceManager::remove_workspace_group(uint32_t id) { + const auto it = + std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; }); + + if (it == groups_.end()) { + spdlog::warn("[ext/workspaces]: Can't find workspace group with id {}", id); + return; + } + + groups_.erase(it); +} + +void WorkspaceManager::remove_workspace(uint32_t id) { + const auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [id](const auto &w) { return w->id() == id; }); + + if (it == workspaces_.end()) { + spdlog::warn("[ext/workspaces]: Can't find workspace with id {}", id); + return; + } + + workspaces_.erase(it); +} + +void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) { + const auto new_id = ++group_global_id; + groups_.push_back(std::make_unique(*this, handle, new_id)); + spdlog::debug("[ext/workspaces]: Workspace group {} created", new_id); +} + +void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) { + const auto new_id = ++workspace_global_id; + const auto new_name = std::to_string(++workspace_name); + workspaces_.push_back(std::make_unique(config_, *this, handle, new_id, new_name)); + set_needs_sorting(); + spdlog::debug("[ext/workspaces]: Workspace {} created", new_id); +} + +void WorkspaceManager::handle_done() { dp.emit(); } + +void WorkspaceManager::handle_finished() { + spdlog::debug("[ext/workspaces]: Finishing workspace manager"); + ext_workspace_manager_v1_destroy(ext_manager_); + ext_manager_ = nullptr; +} + +void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } + +void WorkspaceManager::update() { + spdlog::debug("[ext/workspaces]: Updating state"); + + if (needs_sorting_) { + clear_buttons(); + sort_workspaces(); + needs_sorting_ = false; + } + + update_buttons(); + AModule::update(); +} + +bool WorkspaceManager::has_button(const Gtk::Button *button) { + const auto buttons = box_.get_children(); + return std::find(buttons.begin(), buttons.end(), button) != buttons.end(); +} + +void WorkspaceManager::sort_workspaces() { + // determine if workspace ID's and names can be sort numerically or literally + + auto is_numeric = [](const std::string &s) { + return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); + }; + + auto sort_by_workspace_id_numerically = + std::all_of(workspaces_.begin(), workspaces_.end(), + [&](const auto &w) { return is_numeric(w->workspace_id()); }); + + auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(), + [&](const auto &w) { return is_numeric(w->name()); }); + + // sort based on configuration setting with sort-by-id as fallback + + std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto &w1, const auto &w2) { + if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) { + if (w1->workspace_id() == w2->workspace_id()) { + return w1->id() < w2->id(); + } + if (sort_by_workspace_id_numerically) { + // the idea is that phonetic compare can be applied just to numbers + // with same number of digits + return w1->workspace_id().size() < w2->workspace_id().size() || + (w1->workspace_id().size() == w2->workspace_id().size() && + w1->workspace_id() < w2->workspace_id()); + } + return w1->workspace_id() < w2->workspace_id(); + } + + if (sort_by_name_) { + if (w1->name() == w2->name()) { + return w1->id() < w2->id(); + } + if (sort_by_name_numerically) { + // see above about numeric sorting + return w1->name().size() < w2->name().size() || + (w1->name().size() == w2->name().size() && w1->name() < w2->name()); + } + return w1->name() < w2->name(); + } + + if (sort_by_coordinates_) { + if (w1->coordinates() == w2->coordinates()) { + return w1->id() < w2->id(); + } + return w1->coordinates() < w2->coordinates(); + } + + return w1->id() < w2->id(); + }); +} + +void WorkspaceManager::clear_buttons() { + for (const auto &workspace : workspaces_) { + if (has_button(&workspace->button())) { + box_.remove(workspace->button()); + } + } +} + +void WorkspaceManager::update_buttons() { + const auto *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + + // go through all workspace + + for (const auto &workspace : workspaces_) { + const bool workspace_on_any_group_for_output = + std::any_of(groups_.begin(), groups_.end(), [&](const auto &group) { + const bool group_on_output = group->has_output(output) || all_outputs_; + const bool workspace_on_group = group->has_workspace(workspace->handle()); + return group_on_output && workspace_on_group; + }); + const bool bar_contains_button = has_button(&workspace->button()); + + // add or remove buttons if needed, update button state + + if (workspace_on_any_group_for_output) { + if (!bar_contains_button) { + // add button to bar + box_.pack_start(workspace->button(), false, false); + workspace->button().show_all(); + } + workspace->update(); + } else { + if (bar_contains_button) { + // remove button from bar + box_.remove(workspace->button()); + } + } + } +} + +// WorkspaceGroup + +WorkspaceGroup::WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, + uint32_t id) + : workspaces_manager_(manager), ext_handle_(handle), id_(id) { + add_workspace_group_listener(ext_handle_, this); +} + +WorkspaceGroup::~WorkspaceGroup() { + if (ext_handle_ != nullptr) { + ext_workspace_group_handle_v1_destroy(ext_handle_); + } + spdlog::debug("[ext/workspaces]: Workspace group {} destroyed", id_); +} + +bool WorkspaceGroup::has_output(const wl_output *output) { + return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end(); +} + +bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) { + return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end(); +} + +void WorkspaceGroup::handle_capabilities(uint32_t capabilities) { + spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_); + if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) == + capabilities) { + spdlog::debug("[ext/workspaces]: - create-workspace"); + } +} + +void WorkspaceGroup::handle_output_enter(wl_output *output) { outputs_.push_back(output); } + +void WorkspaceGroup::handle_output_leave(wl_output *output) { + const auto it = std::find(outputs_.begin(), outputs_.end(), output); + if (it != outputs_.end()) { + outputs_.erase(it); + } +} + +void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1 *handle) { + workspaces_.push_back(handle); +} + +void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) { + const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle); + if (it != workspaces_.end()) { + workspaces_.erase(it); + } +} + +void WorkspaceGroup::handle_removed() { + spdlog::debug("[ext/workspaces]: Removing workspace group {}", id_); + workspaces_manager_.remove_workspace_group(id_); +} + +// Workspace + +Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager, + ext_workspace_handle_v1 *handle, uint32_t id, const std::string &name) + : workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) { + add_workspace_listener(ext_handle_, this); + + // parse configuration + + const auto &config_active_only = config["active-only"]; + if (config_active_only.isBool()) { + active_only_ = config_active_only.asBool(); + } + + const auto &config_ignore_hidden = config["ignore-hidden"]; + if (config_ignore_hidden.isBool()) { + ignore_hidden_ = config_ignore_hidden.asBool(); + } + + const auto &config_format = config["format"]; + format_ = config_format.isString() ? config_format.asString() : "{name}"; + with_icon_ = format_.find("{icon}") != std::string::npos; + + if (with_icon_ && icon_map_.empty()) { + const auto &format_icons = config["format-icons"]; + for (auto &n : format_icons.getMemberNames()) { + icon_map_.emplace(n, format_icons[n].asString()); + } + } + + const bool config_on_click = config["on-click"].isString(); + if (config_on_click) { + on_click_action_ = config["on-click"].asString(); + } + const bool config_on_click_middle = config["on-click-middle"].isString(); + if (config_on_click_middle) { + on_click_middle_action_ = config["on-click"].asString(); + } + const bool config_on_click_right = config["on-click-right"].isString(); + if (config_on_click_right) { + on_click_right_action_ = config["on-click"].asString(); + } + + // setup UI + + if (config_on_click || config_on_click_middle || config_on_click_right) { + button_.add_events(Gdk::BUTTON_PRESS_MASK); + button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), + false); + } + + button_.set_relief(Gtk::RELIEF_NONE); + content_.set_center_widget(label_); + button_.add(content_); +} + +Workspace::~Workspace() { + if (ext_handle_ != nullptr) { + ext_workspace_handle_v1_destroy(ext_handle_); + } + spdlog::debug("[ext/workspaces]: Workspace {} destroyed", id_); +} + +void Workspace::update() { + if (!needs_updating_) { + return; + } + + // update style and visibility + + const auto style_context = button_.get_style_context(); + style_context->remove_class("active"); + style_context->remove_class("urgent"); + style_context->remove_class("hidden"); + + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { + button_.set_visible(true); + style_context->add_class("active"); + } + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) { + button_.set_visible(true); + style_context->add_class("urgent"); + } + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) { + button_.set_visible(!active_only_ && !ignore_hidden_); + style_context->add_class("hidden"); + } + if (state_ == 0) { + button_.set_visible(!active_only_); + } + + // update label + label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), + fmt::arg("id", workspace_id_), + fmt::arg("icon", with_icon_ ? icon() : ""))); + + needs_updating_ = false; +} + +void Workspace::handle_id(const std::string &id) { + spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id); + workspace_id_ = id; + needs_updating_ = true; + workspace_manager_.set_needs_sorting(); +} + +void Workspace::handle_name(const std::string &name) { + spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name); + name_ = name; + needs_updating_ = true; + workspace_manager_.set_needs_sorting(); +} + +void Workspace::handle_coordinates(const std::vector &coordinates) { + coordinates_ = coordinates; + needs_updating_ = true; + workspace_manager_.set_needs_sorting(); +} + +void Workspace::handle_state(uint32_t state) { + state_ = state; + needs_updating_ = true; +} + +void Workspace::handle_capabilities(uint32_t capabilities) { + spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) { + spdlog::debug("[ext/workspaces]: - activate"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) { + spdlog::debug("[ext/workspaces]: - deactivate"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) { + spdlog::debug("[ext/workspaces]: - remove"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { + spdlog::debug("[ext/workspaces]: - assign"); + } + needs_updating_ = true; +} + +void Workspace::handle_removed() { + spdlog::debug("[ext/workspaces]: Removing workspace {}", id_); + workspace_manager_.remove_workspace(id_); +} + +bool Workspace::handle_clicked(const GdkEventButton *button) const { + std::string action; + if (button->button == GDK_BUTTON_PRIMARY) { + action = on_click_action_; + } else if (button->button == GDK_BUTTON_MIDDLE) { + action = on_click_middle_action_; + } else if (button->button == GDK_BUTTON_SECONDARY) { + action = on_click_right_action_; + } + + if (action.empty()) { + return true; + } + + if (action == "activate") { + ext_workspace_handle_v1_activate(ext_handle_); + } else if (action == "close") { + ext_workspace_handle_v1_remove(ext_handle_); + } else { + spdlog::warn("[ext/workspaces]: Unknown action {}", action); + } + workspace_manager_.commit(); + return true; +} + +std::string Workspace::icon() { + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { + const auto active_icon_it = icon_map_.find("active"); + if (active_icon_it != icon_map_.end()) { + return active_icon_it->second; + } + } + + const auto named_icon_it = icon_map_.find(name_); + if (named_icon_it != icon_map_.end()) { + return named_icon_it->second; + } + + const auto default_icon_it = icon_map_.find("default"); + if (default_icon_it != icon_map_.end()) { + return default_icon_it->second; + } + + return name_; +} + +} // namespace waybar::modules::ext diff --git a/src/modules/ext/workspace_manager_binding.cpp b/src/modules/ext/workspace_manager_binding.cpp new file mode 100644 index 00000000..2d9c6b48 --- /dev/null +++ b/src/modules/ext/workspace_manager_binding.cpp @@ -0,0 +1,159 @@ +#include "modules/ext/workspace_manager_binding.hpp" + +#include + +#include + +#include "client.hpp" +#include "modules/ext/workspace_manager.hpp" + +namespace waybar::modules::ext { + +static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, + uint32_t version) { + if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) { + static_cast(data)->register_manager(registry, name, version); + } +} + +static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) { + /* Nothing to do here */ +} + +static const wl_registry_listener registry_listener_impl = {.global = handle_global, + .global_remove = handle_global_remove}; + +void add_registry_listener(void *data) { + wl_display *display = Client::inst()->wl_display; + wl_registry *registry = wl_display_get_registry(display); + + wl_registry_add_listener(registry, ®istry_listener_impl, data); + wl_display_roundtrip(display); +} + +static void workspace_manager_handle_workspace_group( + void *data, ext_workspace_manager_v1 *_, ext_workspace_group_handle_v1 *workspace_group) { + static_cast(data)->handle_workspace_group(workspace_group); +} + +static void workspace_manager_handle_workspace(void *data, ext_workspace_manager_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace(workspace); +} + +static void workspace_manager_handle_done(void *data, ext_workspace_manager_v1 *_) { + static_cast(data)->handle_done(); +} + +static void workspace_manager_handle_finished(void *data, ext_workspace_manager_v1 *_) { + static_cast(data)->handle_finished(); +} + +static const ext_workspace_manager_v1_listener workspace_manager_impl = { + .workspace_group = workspace_manager_handle_workspace_group, + .workspace = workspace_manager_handle_workspace, + .done = workspace_manager_handle_done, + .finished = workspace_manager_handle_finished, +}; + +ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, + uint32_t version, void *data) { + auto *workspace_manager = static_cast( + wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version)); + + if (workspace_manager) + ext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); + else + spdlog::error("Failed to register manager"); + + return workspace_manager; +} + +static void workspace_group_handle_capabilities(void *data, ext_workspace_group_handle_v1 *_, + uint32_t capabilities) { + static_cast(data)->handle_capabilities(capabilities); +} + +static void workspace_group_handle_output_enter(void *data, ext_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_enter(output); +} + +static void workspace_group_handle_output_leave(void *data, ext_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_leave(output); +} + +static void workspace_group_handle_workspace_enter(void *data, ext_workspace_group_handle_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_enter(workspace); +} + +static void workspace_group_handle_workspace_leave(void *data, ext_workspace_group_handle_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_leave(workspace); +} + +static void workspace_group_handle_removed(void *data, ext_workspace_group_handle_v1 *_) { + static_cast(data)->handle_removed(); +} + +static const ext_workspace_group_handle_v1_listener workspace_group_impl = { + .capabilities = workspace_group_handle_capabilities, + .output_enter = workspace_group_handle_output_enter, + .output_leave = workspace_group_handle_output_leave, + .workspace_enter = workspace_group_handle_workspace_enter, + .workspace_leave = workspace_group_handle_workspace_leave, + .removed = workspace_group_handle_removed}; + +void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, + void *data) { + ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); +} + +void workspace_handle_name(void *data, struct ext_workspace_handle_v1 *_, const char *name) { + static_cast(data)->handle_name(name); +} + +void workspace_handle_id(void *data, struct ext_workspace_handle_v1 *_, const char *id) { + static_cast(data)->handle_id(id); +} + +void workspace_handle_coordinates(void *data, struct ext_workspace_handle_v1 *_, + struct wl_array *coordinates) { + std::vector coords_vec; + auto coords = static_cast(coordinates->data); + for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { + coords_vec.push_back(coords[i]); + } + + static_cast(data)->handle_coordinates(coords_vec); +} + +void workspace_handle_state(void *data, struct ext_workspace_handle_v1 *workspace_handle, + uint32_t state) { + static_cast(data)->handle_state(state); +} + +static void workspace_handle_capabilities(void *data, + struct ext_workspace_handle_v1 *workspace_handle, + uint32_t capabilities) { + static_cast(data)->handle_capabilities(capabilities); +} + +void workspace_handle_removed(void *data, struct ext_workspace_handle_v1 *workspace_handle) { + static_cast(data)->handle_removed(); +} + +static const ext_workspace_handle_v1_listener workspace_impl = { + .id = workspace_handle_id, + .name = workspace_handle_name, + .coordinates = workspace_handle_coordinates, + .state = workspace_handle_state, + .capabilities = workspace_handle_capabilities, + .removed = workspace_handle_removed}; + +void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) { + ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); +} +} // namespace waybar::modules::ext diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp deleted file mode 100644 index f556a161..00000000 --- a/src/modules/wlr/workspace_manager.cpp +++ /dev/null @@ -1,585 +0,0 @@ -#include "modules/wlr/workspace_manager.hpp" - -#include -#include -#include - -#include -#include -#include -#include - -#include "client.hpp" -#include "gtkmm/widget.h" -#include "modules/wlr/workspace_manager_binding.hpp" - -namespace waybar::modules::wlr { - -uint32_t WorkspaceGroup::workspace_global_id = 0; -uint32_t WorkspaceManager::group_global_id = 0; -std::map Workspace::icons_map_; - -WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, - const Json::Value &config) - : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { - auto config_sort_by_name = config_["sort-by-name"]; - if (config_sort_by_name.isBool()) { - sort_by_name_ = config_sort_by_name.asBool(); - } - - auto config_sort_by_coordinates = config_["sort-by-coordinates"]; - if (config_sort_by_coordinates.isBool()) { - sort_by_coordinates_ = config_sort_by_coordinates.asBool(); - } - - auto config_sort_by_number = config_["sort-by-number"]; - if (config_sort_by_number.isBool()) { - sort_by_number_ = config_sort_by_number.asBool(); - } - - auto config_all_outputs = config_["all-outputs"]; - if (config_all_outputs.isBool()) { - all_outputs_ = config_all_outputs.asBool(); - } - - auto config_active_only = config_["active-only"]; - if (config_active_only.isBool()) { - active_only_ = config_active_only.asBool(); - creation_delayed_ = active_only_; - } - - box_.set_name("workspaces"); - if (!id.empty()) { - box_.get_style_context()->add_class(id); - } - box_.get_style_context()->add_class(MODULE_CLASS); - event_box_.add(box_); - - add_registry_listener(this); - if (!workspace_manager_) { - return; - } -} - -auto WorkspaceManager::workspace_comparator() const - -> std::function &, std::unique_ptr &)> { - return [=, this](std::unique_ptr &lhs, std::unique_ptr &rhs) { - auto is_name_less = lhs->get_name() < rhs->get_name(); - auto is_name_eq = lhs->get_name() == rhs->get_name(); - auto is_coords_less = lhs->get_coords() < rhs->get_coords(); - - if (sort_by_number_) { - try { - auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name()); - return is_number_less; - } catch (const std::invalid_argument &) { - } - } - - if (sort_by_name_) { - if (sort_by_coordinates_) { - return is_name_eq ? is_coords_less : is_name_less; - } else { - return is_name_less; - } - } - - if (sort_by_coordinates_) { - return is_coords_less; - } - - return lhs->id() < rhs->id(); - }; -} - -auto WorkspaceManager::sort_workspaces() -> void { - std::vector>> all_workspaces; - for (auto &group : groups_) { - auto &group_workspaces = group->workspaces(); - all_workspaces.reserve(all_workspaces.size() + - std::distance(group_workspaces.begin(), group_workspaces.end())); - if (!active_only()) { - all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end()); - continue; - } - - for (auto &workspace : group_workspaces) { - if (!workspace->is_active()) { - continue; - } - - all_workspaces.push_back(workspace); - } - } - - std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator()); - for (size_t i = 0; i < all_workspaces.size(); ++i) { - box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i); - } -} - -auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) - -> void { - if (workspace_manager_) { - spdlog::warn("Register workspace manager again although already registered!"); - return; - } - if (version != 1) { - spdlog::warn("Using different workspace manager protocol version: {}", version); - } - workspace_manager_ = workspace_manager_bind(registry, name, version, this); -} - -auto WorkspaceManager::handle_workspace_group_create( - zext_workspace_group_handle_v1 *workspace_group_handle) -> void { - auto new_id = ++group_global_id; - groups_.push_back( - std::make_unique(bar_, box_, config_, *this, workspace_group_handle, new_id)); - spdlog::debug("Workspace group {} created", new_id); -} - -auto WorkspaceManager::handle_finished() -> void { - zext_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; -} - -auto WorkspaceManager::handle_done() -> void { - for (auto &group : groups_) { - group->handle_done(); - } - dp.emit(); -} - -auto WorkspaceManager::update() -> void { - for (auto &group : groups_) { - group->update(); - } - if (creation_delayed()) { - creation_delayed_ = false; - sort_workspaces(); - } - AModule::update(); -} - -WorkspaceManager::~WorkspaceManager() { - if (!workspace_manager_) { - return; - } - - wl_display *display = Client::inst()->wl_display; - - // Send `stop` request and wait for one roundtrip. This is not quite correct as - // the protocol encourages us to wait for the .finished event, but it should work - // with wlroots workspace manager implementation. - zext_workspace_manager_v1_stop(workspace_manager_); - wl_display_roundtrip(display); - - // If the .finished handler is still not executed, destroy the workspace manager here. - if (workspace_manager_) { - spdlog::warn("Foreign toplevel manager destroyed before .finished event"); - zext_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; - } -} - -auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { - auto it = std::find_if(groups_.begin(), groups_.end(), - [id](const std::unique_ptr &g) { return g->id() == id; }); - - if (it == groups_.end()) { - spdlog::warn("Can't find group with id {}", id); - return; - } - - groups_.erase(it); -} -auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); } - -WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config, - WorkspaceManager &manager, - zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) - : bar_(bar), - box_(box), - config_(config), - workspace_manager_(manager), - workspace_group_handle_(workspace_group_handle), - id_(id) { - add_workspace_group_listener(workspace_group_handle, this); -} - -auto WorkspaceGroup::fill_persistent_workspaces() -> void { - if (config_["persistent_workspaces"].isObject()) { - spdlog::warn( - "persistent_workspaces is deprecated. Please change config to use persistent-workspaces."); - } - - if ((config_["persistent-workspaces"].isObject() || - config_["persistent_workspaces"].isObject()) && - !workspace_manager_.all_outputs()) { - const Json::Value &p_workspaces = config_["persistent-workspaces"].isObject() - ? config_["persistent-workspaces"] - : config_["persistent_workspaces"]; - const std::vector p_workspaces_names = p_workspaces.getMemberNames(); - - for (const std::string &p_w_name : p_workspaces_names) { - const Json::Value &p_w = p_workspaces[p_w_name]; - if (p_w.isArray() && !p_w.empty()) { - // Adding to target outputs - for (const Json::Value &output : p_w) { - if (output.asString() == bar_.output->name) { - persistent_workspaces_.push_back(p_w_name); - break; - } - } - } else { - // Adding to all outputs - persistent_workspaces_.push_back(p_w_name); - } - } - } -} - -auto WorkspaceGroup::create_persistent_workspaces() -> void { - for (const std::string &p_w_name : persistent_workspaces_) { - auto new_id = ++workspace_global_id; - workspaces_.push_back( - std::make_unique(bar_, config_, *this, nullptr, new_id, p_w_name)); - spdlog::debug("Workspace {} created", new_id); - } -} - -auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); } -auto WorkspaceGroup::creation_delayed() const -> bool { - return workspace_manager_.creation_delayed(); -} - -auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { - box_.pack_start(button, false, false); -} - -WorkspaceGroup::~WorkspaceGroup() { - if (!workspace_group_handle_) { - return; - } - - zext_workspace_group_handle_v1_destroy(workspace_group_handle_); - workspace_group_handle_ = nullptr; -} - -auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void { - auto new_id = ++workspace_global_id; - workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace, new_id, "")); - spdlog::debug("Workspace {} created", new_id); - if (!persistent_created_) { - fill_persistent_workspaces(); - create_persistent_workspaces(); - persistent_created_ = true; - } -} - -auto WorkspaceGroup::handle_remove() -> void { - zext_workspace_group_handle_v1_destroy(workspace_group_handle_); - workspace_group_handle_ = nullptr; - workspace_manager_.remove_workspace_group(id_); -} - -auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { - spdlog::debug("Output {} assigned to {} group", (void *)output, id_); - output_ = output; - - if (!is_visible() || workspace_manager_.creation_delayed()) { - return; - } - - for (auto &workspace : workspaces_) { - add_button(workspace->get_button_ref()); - } -} - -auto WorkspaceGroup::is_visible() const -> bool { - return output_ != nullptr && - (workspace_manager_.all_outputs() || - output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())); -} - -auto WorkspaceGroup::handle_output_leave() -> void { - spdlog::debug("Output {} remove from {} group", (void *)output_, id_); - output_ = nullptr; - - if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { - return; - } - - for (auto &workspace : workspaces_) { - remove_button(workspace->get_button_ref()); - } -} - -auto WorkspaceGroup::update() -> void { - for (auto &workspace : workspaces_) { - if (workspace_manager_.creation_delayed()) { - add_button(workspace->get_button_ref()); - if (is_visible() && (workspace->is_active() || workspace->is_urgent())) { - workspace->show(); - } - } - - workspace->update(); - } -} - -auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { - auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [id](const std::unique_ptr &w) { return w->id() == id; }); - - if (it == workspaces_.end()) { - spdlog::warn("Can't find workspace with id {}", id); - return; - } - - workspaces_.erase(it); -} - -auto WorkspaceGroup::handle_done() -> void { - need_to_sort = false; - if (!is_visible()) { - return; - } - - for (auto &workspace : workspaces_) { - workspace->handle_done(); - } - - if (creation_delayed()) { - return; - } - - if (!workspace_manager_.all_outputs()) { - sort_workspaces(); - } else { - workspace_manager_.sort_workspaces(); - } -} - -auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } - -auto WorkspaceGroup::sort_workspaces() -> void { - std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator()); - for (size_t i = 0; i < workspaces_.size(); ++i) { - box_.reorder_child(workspaces_[i]->get_button_ref(), i); - } -} - -auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); } - -Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zext_workspace_handle_v1 *workspace, uint32_t id, std::string name) - : bar_(bar), - config_(config), - workspace_group_(workspace_group), - workspace_handle_(workspace), - id_(id), - name_(name) { - if (workspace) { - add_workspace_listener(workspace, this); - } else { - state_ = (uint32_t)State::EMPTY; - } - - auto config_format = config["format"]; - - format_ = config_format.isString() ? config_format.asString() : "{name}"; - with_icon_ = format_.find("{icon}") != std::string::npos; - - if (with_icon_ && icons_map_.empty()) { - auto format_icons = config["format-icons"]; - for (auto &name : format_icons.getMemberNames()) { - icons_map_.emplace(name, format_icons[name].asString()); - } - } - - /* Handle click events if configured */ - if (config_["on-click"].isString() || config_["on-click-middle"].isString() || - config_["on-click-right"].isString()) { - button_.add_events(Gdk::BUTTON_PRESS_MASK); - button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), - false); - } - - button_.set_relief(Gtk::RELIEF_NONE); - content_.set_center_widget(label_); - button_.add(content_); - - if (!workspace_group.is_visible()) { - return; - } - - workspace_group.add_button(button_); - button_.show_all(); -} - -Workspace::~Workspace() { - workspace_group_.remove_button(button_); - if (!workspace_handle_) { - return; - } - - zext_workspace_handle_v1_destroy(workspace_handle_); - workspace_handle_ = nullptr; -} - -auto Workspace::update() -> void { - label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), - fmt::arg("icon", with_icon_ ? get_icon() : ""))); -} - -auto Workspace::handle_state(const std::vector &state) -> void { - state_ = 0; - for (auto state_entry : state) { - switch (state_entry) { - case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE: - state_ |= (uint32_t)State::ACTIVE; - break; - case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT: - state_ |= (uint32_t)State::URGENT; - break; - case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN: - state_ |= (uint32_t)State::HIDDEN; - break; - } - } -} - -auto Workspace::handle_remove() -> void { - if (workspace_handle_) { - zext_workspace_handle_v1_destroy(workspace_handle_); - workspace_handle_ = nullptr; - } - if (!persistent_) { - workspace_group_.remove_workspace(id_); - } else { - state_ = (uint32_t)State::EMPTY; - } -} - -auto add_or_remove_class(Glib::RefPtr context, bool condition, - const std::string &class_name) { - if (condition) { - context->add_class(class_name); - } else { - context->remove_class(class_name); - } -} - -auto Workspace::handle_done() -> void { - spdlog::debug("Workspace {} changed to state {}", id_, state_); - auto style_context = button_.get_style_context(); - add_or_remove_class(style_context, is_active(), "active"); - add_or_remove_class(style_context, is_urgent(), "urgent"); - add_or_remove_class(style_context, is_hidden(), "hidden"); - add_or_remove_class(style_context, is_empty(), "persistent"); - - if (workspace_group_.creation_delayed()) { - return; - } - - if (workspace_group_.active_only() && (is_active() || is_urgent())) { - button_.show_all(); - } else if (workspace_group_.active_only() && !(is_active() || is_urgent())) { - button_.hide(); - } -} - -auto Workspace::get_icon() -> std::string { - if (is_active()) { - auto active_icon_it = icons_map_.find("active"); - if (active_icon_it != icons_map_.end()) { - return active_icon_it->second; - } - } - - auto named_icon_it = icons_map_.find(name_); - if (named_icon_it != icons_map_.end()) { - return named_icon_it->second; - } - - if (is_empty()) { - auto persistent_icon_it = icons_map_.find("persistent"); - if (persistent_icon_it != icons_map_.end()) { - return persistent_icon_it->second; - } - } - - auto default_icon_it = icons_map_.find("default"); - if (default_icon_it != icons_map_.end()) { - return default_icon_it->second; - } - - return name_; -} - -auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { - std::string action; - if (config_["on-click"].isString() && bt->button == 1) { - action = config_["on-click"].asString(); - } else if (config_["on-click-middle"].isString() && bt->button == 2) { - action = config_["on-click-middle"].asString(); - } else if (config_["on-click-right"].isString() && bt->button == 3) { - action = config_["on-click-right"].asString(); - } - - if (action.empty()) - return true; - else if (action == "activate") { - zext_workspace_handle_v1_activate(workspace_handle_); - } else if (action == "close") { - zext_workspace_handle_v1_remove(workspace_handle_); - } else { - spdlog::warn("Unknown action {}", action); - } - - workspace_group_.commit(); - - return true; -} - -auto Workspace::show() -> void { button_.show_all(); } -auto Workspace::hide() -> void { button_.hide(); } - -auto Workspace::handle_name(const std::string &name) -> void { - if (name_ != name) { - workspace_group_.set_need_to_sort(); - } - name_ = name; - spdlog::debug("Workspace {} added to group {}", name, workspace_group_.id()); - - make_persistent(); - handle_duplicate(); -} - -auto Workspace::make_persistent() -> void { - auto p_workspaces = workspace_group_.persistent_workspaces(); - - if (std::find(p_workspaces.begin(), p_workspaces.end(), name_) != p_workspaces.end()) { - persistent_ = true; - } -} - -auto Workspace::handle_duplicate() -> void { - auto duplicate = - std::find_if(workspace_group_.workspaces().begin(), workspace_group_.workspaces().end(), - [this](const std::unique_ptr &g) { - return g->get_name() == name_ && g->id() != id_; - }); - if (duplicate != workspace_group_.workspaces().end()) { - workspace_group_.remove_workspace(duplicate->get()->id()); - } -} - -auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { - if (coordinates_ != coordinates) { - workspace_group_.set_need_to_sort(); - } - coordinates_ = coordinates; -} -} // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp deleted file mode 100644 index 22e68fbf..00000000 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "modules/wlr/workspace_manager_binding.hpp" - -#include - -#include - -#include "client.hpp" -#include "modules/wlr/workspace_manager.hpp" - -namespace waybar::modules::wlr { - -static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, - uint32_t version) { - if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) { - static_cast(data)->register_manager(registry, name, version); - } -} - -static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) { - /* Nothing to do here */ -} - -static const wl_registry_listener registry_listener_impl = {.global = handle_global, - .global_remove = handle_global_remove}; - -void add_registry_listener(void *data) { - wl_display *display = Client::inst()->wl_display; - wl_registry *registry = wl_display_get_registry(display); - - wl_registry_add_listener(registry, ®istry_listener_impl, data); - wl_display_roundtrip(display); - wl_display_roundtrip(display); -} - -static void workspace_manager_handle_workspace_group( - void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) { - static_cast(data)->handle_workspace_group_create(workspace_group); -} - -static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) { - static_cast(data)->handle_done(); -} - -static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) { - static_cast(data)->handle_finished(); -} - -static const zext_workspace_manager_v1_listener workspace_manager_impl = { - .workspace_group = workspace_manager_handle_workspace_group, - .done = workspace_manager_handle_done, - .finished = workspace_manager_handle_finished, -}; - -zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data) { - auto *workspace_manager = static_cast( - wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version)); - - if (workspace_manager) - zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); - else - spdlog::error("Failed to register manager"); - - return workspace_manager; -} - -static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_enter(output); -} - -static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_leave(); -} - -static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_, - zext_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace_create(workspace); -} - -static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) { - static_cast(data)->handle_remove(); -} - -static const zext_workspace_group_handle_v1_listener workspace_group_impl = { - .output_enter = workspace_group_handle_output_enter, - .output_leave = workspace_group_handle_output_leave, - .workspace = workspace_group_handle_workspace, - .remove = workspace_group_handle_remove}; - -void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, - void *data) { - zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); -} - -void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) { - static_cast(data)->handle_name(name); -} - -void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_, - struct wl_array *coordinates) { - std::vector coords_vec; - auto coords = static_cast(coordinates->data); - for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { - coords_vec.push_back(coords[i]); - } - - static_cast(data)->handle_coordinates(coords_vec); -} - -void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle, - struct wl_array *state) { - std::vector state_vec; - auto states = static_cast(state->data); - for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) { - state_vec.push_back(states[i]); - } - - static_cast(data)->handle_state(state_vec); -} - -void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) { - static_cast(data)->handle_remove(); -} - -static const zext_workspace_handle_v1_listener workspace_impl = { - .name = workspace_handle_name, - .coordinates = workspace_handle_coordinates, - .state = workspace_handle_state, - .remove = workspace_handle_remove}; - -void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) { - zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); -} -} // namespace waybar::modules::wlr