From 94a4d41a65c98b6c95e2f520a57911321ae256ec Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Tue, 26 May 2020 07:29:54 +0000 Subject: [PATCH 01/78] disk: make used calculation match df(1) output Substract space only available to superuser as well. --- src/modules/disk.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 59ffea67..658fcd90 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -45,7 +45,7 @@ auto waybar::modules::Disk::update() -> void { } auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); - auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); + auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); label_.set_markup(fmt::format(format_ @@ -53,7 +53,7 @@ auto waybar::modules::Disk::update() -> void { , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) - , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks) , fmt::arg("total", total) , fmt::arg("path", path_) )); @@ -67,7 +67,7 @@ auto waybar::modules::Disk::update() -> void { , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) - , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks) , fmt::arg("total", total) , fmt::arg("path", path_) )); From 2b11b7ef8c9b66e9f00ec573fa3de3f14e724e0d Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 5 Aug 2020 02:17:38 +0300 Subject: [PATCH 02/78] Base wlr impl (Manager, Group) --- include/factory.hpp | 1 + include/modules/wlr/workspaces.hpp | 82 ++++++++ meson.build | 1 + protocol/meson.build | 1 + protocol/wlr-workspace-unstable-v1.xml | 270 +++++++++++++++++++++++++ src/factory.cpp | 3 + src/modules/wlr/workspaces.cpp | 152 ++++++++++++++ 7 files changed, 510 insertions(+) create mode 100644 include/modules/wlr/workspaces.hpp create mode 100644 protocol/wlr-workspace-unstable-v1.xml create mode 100644 src/modules/wlr/workspaces.cpp diff --git a/include/factory.hpp b/include/factory.hpp index fcbf3a2d..db6fd875 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -9,6 +9,7 @@ #endif #ifdef HAVE_WLR #include "modules/wlr/taskbar.hpp" +#include "modules/wlr/workspaces.hpp" #endif #ifdef HAVE_RIVER #include "modules/river/tags.hpp" diff --git a/include/modules/wlr/workspaces.hpp b/include/modules/wlr/workspaces.hpp new file mode 100644 index 00000000..351ddc45 --- /dev/null +++ b/include/modules/wlr/workspaces.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "wlr-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, + zwlr_workspace_handle_v1 &workspace); + ~Workspace() = default; + auto update() -> void; + + enum class State { ACTIVE = 1 << 1 }; + + private: + const Bar & bar_; + const Gtk::Box box_; + const Gtk::Image icon_; + const Json::Value & config_; + const WorkspaceGroup &workspace_group_; + + // wlr stuff + zwlr_workspace_handle_v1 &workspace_handle_; + uint32_t state_; +}; + +class WorkspaceGroup { + public: + WorkspaceGroup(const waybar::Bar &bar, const Json::Value &config, WorkspaceManager &manager, + zwlr_workspace_group_handle_v1 &workspace_group_handle); + auto update() -> void; + + // wlr stuff + auto handle_workspace_create(zwlr_workspace_handle_v1 &workspace_handle) -> void; + auto handle_remove() -> void; + auto handle_output_enter(wl_output &output) -> void; + auto handle_output_leave(wl_output &output) -> void; + + private: + const waybar::Bar & bar_; + const Json::Value & config_; + const WorkspaceManager & workspace_manager_; + std::vector> workspaces_; + + // wlr stuff + zwlr_workspace_group_handle_v1 &workspace_group_handle_; +}; + +class WorkspaceManager : public AModule { + public: + WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); + auto update() -> void override; + + // wlr stuff + auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; + auto handle_workspace_group_create(zwlr_workspace_group_handle_v1 *workspace_group_handle) + -> void; + auto handle_done() -> void; + auto handle_finished() -> void; + + private: + const waybar::Bar & bar_; + Gtk::Box box_; + std::vector> groups_; + + // wlr stuff + zwlr_workspace_manager_v1 *workspace_manager_ = nullptr; +}; + +} // namespace waybar::modules::wlr diff --git a/meson.build b/meson.build index dd56c290..d0ba1e81 100644 --- a/meson.build +++ b/meson.build @@ -166,6 +166,7 @@ src_files += [ if true add_project_arguments('-DHAVE_WLR', language: 'cpp') src_files += 'src/modules/wlr/taskbar.cpp' + src_files += 'src/modules/wlr/workspaces.cpp' endif if true diff --git a/protocol/meson.build b/protocol/meson.build index 07d524ae..467260bc 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'], + ['wlr-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ] diff --git a/protocol/wlr-workspace-unstable-v1.xml b/protocol/wlr-workspace-unstable-v1.xml new file mode 100644 index 00000000..ae0e44db --- /dev/null +++ b/protocol/wlr-workspace-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + 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 zwlr_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 + zwlr_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 zwlr_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 zwlr_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 + zwlr_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 + zwlr_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 zwlr_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. + + + + + 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 + zwlr_workspace_handle_v1. + + + + + + + This event means the zwlr_workspace_group_handle_v1 has been destroyed. + It is guaranteed there won't be any more events for this + zwlr_workspace_group_handle_v1. The zwlr_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. + + + + + + Destroys the zwlr_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 zwlr_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. + + + + + This event is emitted immediately after the zwlr_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 zwlr_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 zwlr_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. + + + + + + + + This event means the zwlr_workspace_handle_v1 has been destroyed. It is + guaranteed there won't be any more events for this + zwlr_workspace_handle_v1. The zwlr_workspace_handle_v1 becomes inert so + any requests will be ignored except the destroy request. + + + + + + Destroys the zwlr_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. + + + + diff --git a/src/factory.cpp b/src/factory.cpp index 5a01d523..804378e2 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -27,6 +27,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "wlr/taskbar") { return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } + if (ref == "wlr/workspaces") { + return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); + } #endif #ifdef HAVE_RIVER if (ref == "river/tags") { diff --git a/src/modules/wlr/workspaces.cpp b/src/modules/wlr/workspaces.cpp new file mode 100644 index 00000000..0d9ae0b3 --- /dev/null +++ b/src/modules/wlr/workspaces.cpp @@ -0,0 +1,152 @@ +#include "modules/wlr/workspaces.hpp" + +#include +#include + +#include + +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, zwlr_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}; + +WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, + const Json::Value &config) + : waybar::AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), + bar_(bar), + box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + event_box_.add(box_); + + // wlr stuff + wl_display * display = Client::inst()->wl_display; + wl_registry *registry = wl_display_get_registry(display); + + wl_registry_add_listener(registry, ®istry_listener_impl, this); + wl_display_roundtrip(display); + + if (!workspace_manager_) { + return; + } +} + +static void workspace_manager_handle_workspace_group( + void *data, zwlr_workspace_manager_v1 *_, + zwlr_workspace_group_handle_v1 *workspace_group) { + static_cast(data)->handle_workspace_group_create(workspace_group); +} + +static void workspace_manager_handle_done(void *data, zwlr_workspace_manager_v1 *_) { + static_cast(data)->handle_done(); +} + +static void workspace_manager_handle_finished(void *data, zwlr_workspace_manager_v1 *_) { + static_cast(data)->handle_finished(); +} + +static const zwlr_workspace_manager_v1_listener workspace_manager_impl = { + .workspace_group = workspace_manager_handle_workspace_group, + .done = workspace_manager_handle_done, + .finished = workspace_manager_handle_finished, +}; + +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_ = static_cast( + wl_registry_bind(registry, name, &zwlr_workspace_manager_v1_interface, version)); + + if (workspace_manager_) + zwlr_workspace_manager_v1_add_listener(workspace_manager_, &workspace_manager_impl, this); + else + spdlog::debug("Failed to register manager"); +} +auto WorkspaceManager::handle_workspace_group_create( + zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void { + groups_.push_back(std::make_unique(bar_, config_, *this, *workspace_group_handle)); +} + +auto WorkspaceManager::handle_finished() -> void { + zwlr_workspace_manager_v1_destroy(workspace_manager_); + workspace_manager_ = nullptr; +} +auto WorkspaceManager::handle_done() -> void {} +auto WorkspaceManager::update() -> void { + for (auto &group : groups_) { + group->update(); + } + AModule::update(); +} + +static void workspace_group_handle_output_enter(void *data, + zwlr_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_enter(*output); +} + +static void workspace_group_handle_output_leave(void *data, + zwlr_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_leave(*output); +} + +static void workspace_group_handle_workspace(void *data, + zwlr_workspace_group_handle_v1 *_, + zwlr_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_create(*workspace); +} + +static void workspace_group_handle_remove(void *data, + zwlr_workspace_group_handle_v1 *_) { + static_cast(data)->handle_remove(); +} + +static const zwlr_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 +}; + +WorkspaceGroup::WorkspaceGroup(const Bar & bar, const Json::Value &config, WorkspaceManager &manager, + zwlr_workspace_group_handle_v1 &workspace_group_handle) + : bar_(bar), config_(config), workspace_manager_(manager), workspace_group_handle_(workspace_group_handle) +{ + zwlr_workspace_group_handle_v1_add_listener(&workspace_group_handle, &workspace_group_impl, this); +} +auto WorkspaceGroup::handle_workspace_create(zwlr_workspace_handle_v1 &workspace) -> void { + workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace)); +} +auto WorkspaceGroup::handle_remove() -> void {} +auto WorkspaceGroup::handle_output_enter(wl_output &output) -> void {} +auto WorkspaceGroup::handle_output_leave(wl_output &output) -> void {} +auto WorkspaceGroup::update() -> void { + +} +Workspace::Workspace(const Bar &bar, const Json::Value &config, + WorkspaceGroup &workspace_group, zwlr_workspace_handle_v1 &workspace) + : bar_(bar), config_(config), workspace_group_(workspace_group), workspace_handle_(workspace) +{ +} +} // namespace waybar::modules::wlr \ No newline at end of file From edd4d8ee119f1ebeb51ecbac814784e8980e08ca Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 5 Aug 2020 23:10:08 +0300 Subject: [PATCH 03/78] Finish implement wlr --- include/factory.hpp | 2 +- include/modules/wlr/workspace_manager.hpp | 109 +++++++++++ .../modules/wlr/workspace_manager_binding.hpp | 8 + include/modules/wlr/workspaces.hpp | 82 -------- meson.build | 3 +- src/modules/wlr/workspace_manager.cpp | 182 ++++++++++++++++++ src/modules/wlr/workspace_manager_binding.cpp | 134 +++++++++++++ src/modules/wlr/workspaces.cpp | 152 --------------- 8 files changed, 436 insertions(+), 236 deletions(-) create mode 100644 include/modules/wlr/workspace_manager.hpp create mode 100644 include/modules/wlr/workspace_manager_binding.hpp delete mode 100644 include/modules/wlr/workspaces.hpp create mode 100644 src/modules/wlr/workspace_manager.cpp create mode 100644 src/modules/wlr/workspace_manager_binding.cpp delete mode 100644 src/modules/wlr/workspaces.cpp diff --git a/include/factory.hpp b/include/factory.hpp index db6fd875..a34e3612 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -9,7 +9,7 @@ #endif #ifdef HAVE_WLR #include "modules/wlr/taskbar.hpp" -#include "modules/wlr/workspaces.hpp" +#include "modules/wlr/workspace_manager.hpp" #endif #ifdef HAVE_RIVER #include "modules/river/tags.hpp" diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp new file mode 100644 index 00000000..3c8fe99d --- /dev/null +++ b/include/modules/wlr/workspace_manager.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "wlr-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, + zwlr_workspace_handle_v1 *workspace); + ~Workspace(); + auto update() -> void; + + auto id() const -> uint32_t { return id_; } + auto handle_name(const std::string &name) -> void { name_ = name; } + auto handle_coordinates(const std::vector &coordinates) -> void { + coordinates_ = coordinates; + } + auto handle_state(const std::vector &state) -> void; + auto handle_remove() -> void; + + enum class State { ACTIVE = 1 << 0 }; + + private: + static uint32_t workspace_global_id; + const Bar & bar_; + const Json::Value &config_; + WorkspaceGroup & workspace_group_; + + // wlr stuff + zwlr_workspace_handle_v1 *workspace_handle_; + uint32_t state_ = 0; + + uint32_t id_; + std::string name_; + std::vector coordinates_; + + const Gtk::Box box_; + const Gtk::Image icon_; +}; + +class WorkspaceGroup { + public: + WorkspaceGroup(const waybar::Bar &bar, const Json::Value &config, WorkspaceManager &manager, + zwlr_workspace_group_handle_v1 *workspace_group_handle); + ~WorkspaceGroup(); + auto update() -> void; + + auto id() const -> uint32_t { return id_; } + auto remove_workspace(uint32_t id_) -> void; + + // wlr stuff + auto handle_workspace_create(zwlr_workspace_handle_v1 *workspace_handle) -> void; + + auto handle_remove() -> void; + auto handle_output_enter(wl_output *output) -> void; + auto handle_output_leave() -> void; + + private: + static uint32_t group_global_id; + const waybar::Bar &bar_; + const Json::Value &config_; + WorkspaceManager & workspace_manager_; + + // wlr stuff + zwlr_workspace_group_handle_v1 *workspace_group_handle_; + wl_output * output_ = nullptr; + + uint32_t id_; + std::vector> workspaces_; +}; + +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 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(zwlr_workspace_group_handle_v1 *workspace_group_handle) + -> void; + + auto handle_done() -> void; + auto handle_finished() -> void; + + private: + const waybar::Bar & bar_; + Gtk::Box box_; + std::vector> groups_; + + // wlr stuff + zwlr_workspace_manager_v1 *workspace_manager_ = nullptr; +}; + +} // namespace waybar::modules::wlr diff --git a/include/modules/wlr/workspace_manager_binding.hpp b/include/modules/wlr/workspace_manager_binding.hpp new file mode 100644 index 00000000..1cef652f --- /dev/null +++ b/include/modules/wlr/workspace_manager_binding.hpp @@ -0,0 +1,8 @@ +#include "wlr-workspace-unstable-v1-client-protocol.h" + +namespace waybar::modules::wlr { + void add_registry_listener(void *data); + void add_workspace_listener(zwlr_workspace_handle_v1 *workspace_handle, void *data); + void add_workspace_group_listener(zwlr_workspace_group_handle_v1 *workspace_group_handle, void *data); + zwlr_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data); +} \ No newline at end of file diff --git a/include/modules/wlr/workspaces.hpp b/include/modules/wlr/workspaces.hpp deleted file mode 100644 index 351ddc45..00000000 --- a/include/modules/wlr/workspaces.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -#include "AModule.hpp" -#include "bar.hpp" -#include "wlr-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, - zwlr_workspace_handle_v1 &workspace); - ~Workspace() = default; - auto update() -> void; - - enum class State { ACTIVE = 1 << 1 }; - - private: - const Bar & bar_; - const Gtk::Box box_; - const Gtk::Image icon_; - const Json::Value & config_; - const WorkspaceGroup &workspace_group_; - - // wlr stuff - zwlr_workspace_handle_v1 &workspace_handle_; - uint32_t state_; -}; - -class WorkspaceGroup { - public: - WorkspaceGroup(const waybar::Bar &bar, const Json::Value &config, WorkspaceManager &manager, - zwlr_workspace_group_handle_v1 &workspace_group_handle); - auto update() -> void; - - // wlr stuff - auto handle_workspace_create(zwlr_workspace_handle_v1 &workspace_handle) -> void; - auto handle_remove() -> void; - auto handle_output_enter(wl_output &output) -> void; - auto handle_output_leave(wl_output &output) -> void; - - private: - const waybar::Bar & bar_; - const Json::Value & config_; - const WorkspaceManager & workspace_manager_; - std::vector> workspaces_; - - // wlr stuff - zwlr_workspace_group_handle_v1 &workspace_group_handle_; -}; - -class WorkspaceManager : public AModule { - public: - WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); - auto update() -> void override; - - // wlr stuff - auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; - auto handle_workspace_group_create(zwlr_workspace_group_handle_v1 *workspace_group_handle) - -> void; - auto handle_done() -> void; - auto handle_finished() -> void; - - private: - const waybar::Bar & bar_; - Gtk::Box box_; - std::vector> groups_; - - // wlr stuff - zwlr_workspace_manager_v1 *workspace_manager_ = nullptr; -}; - -} // namespace waybar::modules::wlr diff --git a/meson.build b/meson.build index d0ba1e81..638a766d 100644 --- a/meson.build +++ b/meson.build @@ -166,7 +166,8 @@ src_files += [ if true add_project_arguments('-DHAVE_WLR', language: 'cpp') src_files += 'src/modules/wlr/taskbar.cpp' - src_files += 'src/modules/wlr/workspaces.cpp' + src_files += 'src/modules/wlr/workspace_manager.cpp' + src_files += 'src/modules/wlr/workspace_manager_binding.cpp' endif if true diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp new file mode 100644 index 00000000..04a31888 --- /dev/null +++ b/src/modules/wlr/workspace_manager.cpp @@ -0,0 +1,182 @@ +#include "modules/wlr/workspace_manager.hpp" + +#include +#include + +#include + +#include "modules/wlr/workspace_manager_binding.hpp" + +namespace waybar::modules::wlr { + +uint32_t Workspace::workspace_global_id = 0; +uint32_t WorkspaceGroup::group_global_id = 0; + +WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, + const Json::Value &config) + : waybar::AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), + bar_(bar), + box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + event_box_.add(box_); + + add_registry_listener(this); + if (!workspace_manager_) { + return; + } +} + +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( + zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void { + groups_.push_back(std::make_unique(bar_, config_, *this, workspace_group_handle)); + spdlog::debug("Workspace group {} created", groups_.back()->id()); +} + +auto WorkspaceManager::handle_finished() -> void { + zwlr_workspace_manager_v1_destroy(workspace_manager_); + workspace_manager_ = nullptr; +} + +auto WorkspaceManager::handle_done() -> void { +} + +auto WorkspaceManager::update() -> void { + for (auto &group : groups_) { + group->update(); + } + AModule::update(); +} + +WorkspaceManager::~WorkspaceManager() { + if (!workspace_manager_) { + return; + } + + zwlr_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); +} + +WorkspaceGroup::WorkspaceGroup(const Bar &bar, const Json::Value &config, WorkspaceManager &manager, + zwlr_workspace_group_handle_v1 *workspace_group_handle) + : bar_(bar), + config_(config), + workspace_manager_(manager), + workspace_group_handle_(workspace_group_handle), + id_(++group_global_id) { + add_workspace_group_listener(workspace_group_handle, this); +} + +WorkspaceGroup::~WorkspaceGroup()->void { + if (!workspace_group_handle_) { + return; + } + + zwlr_workspace_group_handle_v1_destroy(workspace_group_handle_); + workspace_group_handle_ = nullptr; +} + +auto WorkspaceGroup::handle_workspace_create(zwlr_workspace_handle_v1 *workspace) -> void { + workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace)); + spdlog::debug("Workspace {} created", workspaces_.back()->id()); +} + +auto WorkspaceGroup::handle_remove() -> void { + zwlr_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; +} + +auto WorkspaceGroup::handle_output_leave() -> void { + spdlog::debug("Output {} remove from {} group", (void *)output_, id_); + output_ = nullptr; +} + +auto WorkspaceGroup::update() -> void { + for (auto &workspace : workspaces_) { + 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 group with id {}", id); + return; + } + + workspaces_.erase(it); +} + +Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, + zwlr_workspace_handle_v1 *workspace) + : bar_(bar), + config_(config), + workspace_group_(workspace_group), + workspace_handle_(workspace), + id_(++workspace_global_id) { + add_workspace_listener(workspace, this); +} + +Workspace::~Workspace() { + if (!workspace_handle_) { + return; + } + + zwlr_workspace_handle_v1_destroy(workspace_handle_); + workspace_handle_ = nullptr; +} + +auto Workspace::update() -> void {} + +auto Workspace::handle_state(const std::vector &state) -> void { + state_ = 0; + for (auto state_entry : state) { + switch (state_entry) { + case ZWLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE: + state_ |= (uint32_t)State::ACTIVE; + break; + } + } +} + +auto Workspace::handle_remove() -> void { + zwlr_workspace_handle_v1_destroy(workspace_handle_); + workspace_handle_ = nullptr; + workspace_group_.remove_workspace(id_); +} +} // namespace waybar::modules::wlr \ No newline at end of file diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp new file mode 100644 index 00000000..f6db711b --- /dev/null +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -0,0 +1,134 @@ +#include "modules/wlr/workspace_manager_binding.hpp" + +#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, zwlr_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, zwlr_workspace_manager_v1 *_, zwlr_workspace_group_handle_v1 *workspace_group) { + static_cast(data)->handle_workspace_group_create(workspace_group); +} + +static void workspace_manager_handle_done(void *data, zwlr_workspace_manager_v1 *_) { + static_cast(data)->handle_done(); +} + +static void workspace_manager_handle_finished(void *data, zwlr_workspace_manager_v1 *_) { + static_cast(data)->handle_finished(); +} + +static const zwlr_workspace_manager_v1_listener workspace_manager_impl = { + .workspace_group = workspace_manager_handle_workspace_group, + .done = workspace_manager_handle_done, + .finished = workspace_manager_handle_finished, +}; + +zwlr_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, &zwlr_workspace_manager_v1_interface, version)); + + if (workspace_manager) + zwlr_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, zwlr_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_enter(output); +} + +static void workspace_group_handle_output_leave(void *data, zwlr_workspace_group_handle_v1 *_, + wl_output *output) { + static_cast(data)->handle_output_leave(); +} + +static void workspace_group_handle_workspace(void *data, zwlr_workspace_group_handle_v1 *_, + zwlr_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_create(workspace); +} + +static void workspace_group_handle_remove(void *data, zwlr_workspace_group_handle_v1 *_) { + static_cast(data)->handle_remove(); +} + +static const zwlr_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(zwlr_workspace_group_handle_v1 *workspace_group_handle, + void * data) { + zwlr_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); +} + +void workspace_handle_name(void *data, struct zwlr_workspace_handle_v1 *_, const char *name) { + static_cast(data)->handle_name(name); +} + +void workspace_handle_coordinates(void *data, struct zwlr_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; ++i) { + coords_vec.push_back(coords[i]); + } + + static_cast(data)->handle_coordinates(coords_vec); +} + +void workspace_handle_state(void *data, struct zwlr_workspace_handle_v1 *_, + struct wl_array *state) { + std::vector state_vec; + auto coords = static_cast(state->data); + for (size_t i = 0; i < state->size; ++i) { + state_vec.push_back(coords[i]); + } + + static_cast(data)->handle_state(state_vec); +} + +void workspace_handle_remove(void *data, struct zwlr_workspace_handle_v1 *_) { + static_cast(data)->handle_remove(); +} + +static const zwlr_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(zwlr_workspace_handle_v1 *workspace_handle, void *data) { + zwlr_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); +} +} // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspaces.cpp b/src/modules/wlr/workspaces.cpp deleted file mode 100644 index 0d9ae0b3..00000000 --- a/src/modules/wlr/workspaces.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "modules/wlr/workspaces.hpp" - -#include -#include - -#include - -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, zwlr_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}; - -WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, - const Json::Value &config) - : waybar::AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), - bar_(bar), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { - box_.set_name("workspaces"); - if (!id.empty()) { - box_.get_style_context()->add_class(id); - } - event_box_.add(box_); - - // wlr stuff - wl_display * display = Client::inst()->wl_display; - wl_registry *registry = wl_display_get_registry(display); - - wl_registry_add_listener(registry, ®istry_listener_impl, this); - wl_display_roundtrip(display); - - if (!workspace_manager_) { - return; - } -} - -static void workspace_manager_handle_workspace_group( - void *data, zwlr_workspace_manager_v1 *_, - zwlr_workspace_group_handle_v1 *workspace_group) { - static_cast(data)->handle_workspace_group_create(workspace_group); -} - -static void workspace_manager_handle_done(void *data, zwlr_workspace_manager_v1 *_) { - static_cast(data)->handle_done(); -} - -static void workspace_manager_handle_finished(void *data, zwlr_workspace_manager_v1 *_) { - static_cast(data)->handle_finished(); -} - -static const zwlr_workspace_manager_v1_listener workspace_manager_impl = { - .workspace_group = workspace_manager_handle_workspace_group, - .done = workspace_manager_handle_done, - .finished = workspace_manager_handle_finished, -}; - -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_ = static_cast( - wl_registry_bind(registry, name, &zwlr_workspace_manager_v1_interface, version)); - - if (workspace_manager_) - zwlr_workspace_manager_v1_add_listener(workspace_manager_, &workspace_manager_impl, this); - else - spdlog::debug("Failed to register manager"); -} -auto WorkspaceManager::handle_workspace_group_create( - zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void { - groups_.push_back(std::make_unique(bar_, config_, *this, *workspace_group_handle)); -} - -auto WorkspaceManager::handle_finished() -> void { - zwlr_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; -} -auto WorkspaceManager::handle_done() -> void {} -auto WorkspaceManager::update() -> void { - for (auto &group : groups_) { - group->update(); - } - AModule::update(); -} - -static void workspace_group_handle_output_enter(void *data, - zwlr_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_enter(*output); -} - -static void workspace_group_handle_output_leave(void *data, - zwlr_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_leave(*output); -} - -static void workspace_group_handle_workspace(void *data, - zwlr_workspace_group_handle_v1 *_, - zwlr_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace_create(*workspace); -} - -static void workspace_group_handle_remove(void *data, - zwlr_workspace_group_handle_v1 *_) { - static_cast(data)->handle_remove(); -} - -static const zwlr_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 -}; - -WorkspaceGroup::WorkspaceGroup(const Bar & bar, const Json::Value &config, WorkspaceManager &manager, - zwlr_workspace_group_handle_v1 &workspace_group_handle) - : bar_(bar), config_(config), workspace_manager_(manager), workspace_group_handle_(workspace_group_handle) -{ - zwlr_workspace_group_handle_v1_add_listener(&workspace_group_handle, &workspace_group_impl, this); -} -auto WorkspaceGroup::handle_workspace_create(zwlr_workspace_handle_v1 &workspace) -> void { - workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace)); -} -auto WorkspaceGroup::handle_remove() -> void {} -auto WorkspaceGroup::handle_output_enter(wl_output &output) -> void {} -auto WorkspaceGroup::handle_output_leave(wl_output &output) -> void {} -auto WorkspaceGroup::update() -> void { - -} -Workspace::Workspace(const Bar &bar, const Json::Value &config, - WorkspaceGroup &workspace_group, zwlr_workspace_handle_v1 &workspace) - : bar_(bar), config_(config), workspace_group_(workspace_group), workspace_handle_(workspace) -{ -} -} // namespace waybar::modules::wlr \ No newline at end of file From 7638f5c5958d0f9243c8b71569b9cb048f439a7a Mon Sep 17 00:00:00 2001 From: dmitry Date: Thu, 6 Aug 2020 01:42:57 +0300 Subject: [PATCH 04/78] Add base name representation --- include/modules/wlr/workspace_manager.hpp | 14 ++++++++---- src/modules/wlr/workspace_manager.cpp | 26 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index 3c8fe99d..f606331d 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include @@ -23,6 +25,7 @@ class Workspace { auto update() -> void; auto id() const -> uint32_t { return id_; } + // wlr stuff auto handle_name(const std::string &name) -> void { name_ = name; } auto handle_coordinates(const std::vector &coordinates) -> void { coordinates_ = coordinates; @@ -46,8 +49,9 @@ class Workspace { std::string name_; std::vector coordinates_; - const Gtk::Box box_; - const Gtk::Image icon_; + Gtk::Button button_; + Gtk::Box content_; + Gtk::Label label_; }; class WorkspaceGroup { @@ -62,11 +66,12 @@ class WorkspaceGroup { // wlr stuff auto handle_workspace_create(zwlr_workspace_handle_v1 *workspace_handle) -> void; - auto handle_remove() -> void; auto handle_output_enter(wl_output *output) -> void; auto handle_output_leave() -> void; + auto add_button(Gtk::Button &button) -> void; + private: static uint32_t group_global_id; const waybar::Bar &bar_; @@ -93,10 +98,11 @@ class WorkspaceManager : public AModule { auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; auto handle_workspace_group_create(zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void; - auto handle_done() -> void; auto handle_finished() -> void; + auto add_button(Gtk::Button &button) -> void { box_.pack_start(button, false, false); } + private: const waybar::Bar & bar_; Gtk::Box box_; diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 04a31888..25ce5253 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -14,7 +14,7 @@ uint32_t WorkspaceGroup::group_global_id = 0; WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config) - : waybar::AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), + : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { box_.set_name("workspaces"); @@ -53,6 +53,7 @@ auto WorkspaceManager::handle_finished() -> void { } auto WorkspaceManager::handle_done() -> void { + dp.emit(); } auto WorkspaceManager::update() -> void { @@ -92,8 +93,11 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, const Json::Value &config, Worksp id_(++group_global_id) { add_workspace_group_listener(workspace_group_handle, this); } +auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { + workspace_manager_.add_button(button); +} -WorkspaceGroup::~WorkspaceGroup()->void { +WorkspaceGroup::~WorkspaceGroup() { if (!workspace_group_handle_) { return; } @@ -135,7 +139,7 @@ auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { [id](const std::unique_ptr &w) { return w->id() == id; }); if (it == workspaces_.end()) { - spdlog::warn("Can't find group with id {}", id); + spdlog::warn("Can't find workspace with id {}", id); return; } @@ -148,8 +152,17 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & config_(config), workspace_group_(workspace_group), workspace_handle_(workspace), - id_(++workspace_global_id) { + id_(++workspace_global_id), + content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { add_workspace_listener(workspace, this); + workspace_group.add_button(button_); + button_.set_relief(Gtk::RELIEF_NONE); + label_.set_label(fmt::format("{name}", fmt::arg("name", "1"))); + label_.show(); + content_.add(label_); + content_.show(); + button_.add(content_); + button_.show(); } Workspace::~Workspace() { @@ -161,7 +174,10 @@ Workspace::~Workspace() { workspace_handle_ = nullptr; } -auto Workspace::update() -> void {} +auto Workspace::update() -> void { + label_.set_label(fmt::format("{name}", fmt::arg("name", name_))); + label_.show(); +} auto Workspace::handle_state(const std::vector &state) -> void { state_ = 0; From 4c251578e923760372bf51f2d9540d7abd9b204b Mon Sep 17 00:00:00 2001 From: dmitry Date: Fri, 7 Aug 2020 02:45:08 +0300 Subject: [PATCH 05/78] Add formatting and states handling --- include/modules/wlr/workspace_manager.hpp | 20 +++++- src/modules/wlr/workspace_manager.cpp | 80 ++++++++++++++++++++--- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index f606331d..8cd065f1 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -25,6 +26,7 @@ class Workspace { auto update() -> void; auto id() const -> uint32_t { return id_; } + auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); } // wlr stuff auto handle_name(const std::string &name) -> void { name_ = name; } auto handle_coordinates(const std::vector &coordinates) -> void { @@ -33,9 +35,14 @@ class Workspace { auto handle_state(const std::vector &state) -> void; auto handle_remove() -> void; + auto handle_done() -> void; + auto handle_clicked() -> void; + enum class State { ACTIVE = 1 << 0 }; private: + auto get_icon() -> std::string; + static uint32_t workspace_global_id; const Bar & bar_; const Json::Value &config_; @@ -45,9 +52,12 @@ class Workspace { zwlr_workspace_handle_v1 *workspace_handle_; uint32_t state_ = 0; - uint32_t id_; - std::string name_; - std::vector coordinates_; + uint32_t id_; + std::string name_; + std::vector coordinates_; + static std::map icons_map_; + std::string format_; + bool with_icon_ = false; Gtk::Button button_; Gtk::Box content_; @@ -62,6 +72,7 @@ class WorkspaceGroup { auto update() -> void; auto id() const -> uint32_t { return id_; } + auto is_visible() const -> bool { return output_ != nullptr; } auto remove_workspace(uint32_t id_) -> void; // wlr stuff @@ -71,6 +82,8 @@ class WorkspaceGroup { auto handle_output_leave() -> void; auto add_button(Gtk::Button &button) -> void; + auto handle_done() -> void; + auto commit() -> void; private: static uint32_t group_global_id; @@ -102,6 +115,7 @@ class WorkspaceManager : public AModule { auto handle_finished() -> void; auto add_button(Gtk::Button &button) -> void { box_.pack_start(button, false, false); } + auto commit() -> void; private: const waybar::Bar & bar_; diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 25ce5253..cf0510da 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -9,8 +9,9 @@ namespace waybar::modules::wlr { -uint32_t Workspace::workspace_global_id = 0; -uint32_t WorkspaceGroup::group_global_id = 0; +uint32_t Workspace::workspace_global_id = 0; +uint32_t WorkspaceGroup::group_global_id = 0; +std::map Workspace::icons_map_; WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config) @@ -53,6 +54,9 @@ auto WorkspaceManager::handle_finished() -> void { } auto WorkspaceManager::handle_done() -> void { + for (auto &group : groups_) { + group->handle_done(); + } dp.emit(); } @@ -83,6 +87,7 @@ auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { groups_.erase(it); } +auto WorkspaceManager::commit() -> void { zwlr_workspace_manager_v1_commit(workspace_manager_); } WorkspaceGroup::WorkspaceGroup(const Bar &bar, const Json::Value &config, WorkspaceManager &manager, zwlr_workspace_group_handle_v1 *workspace_group_handle) @@ -145,6 +150,14 @@ auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { workspaces_.erase(it); } +auto WorkspaceGroup::handle_done() -> void { + if (is_visible()) { + for (auto &workspace : workspaces_) { + workspace->handle_done(); + } + } +} +auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, zwlr_workspace_handle_v1 *workspace) @@ -152,17 +165,30 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & config_(config), workspace_group_(workspace_group), workspace_handle_(workspace), - id_(++workspace_global_id), - content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { + id_(++workspace_global_id) { add_workspace_listener(workspace, this); + + 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()); + } + } + + button_.signal_clicked().connect(sigc::mem_fun(this, &Workspace::handle_clicked)); + workspace_group.add_button(button_); button_.set_relief(Gtk::RELIEF_NONE); - label_.set_label(fmt::format("{name}", fmt::arg("name", "1"))); - label_.show(); - content_.add(label_); - content_.show(); + content_.set_center_widget(label_); button_.add(content_); button_.show(); + label_.show(); + content_.show(); } Workspace::~Workspace() { @@ -175,8 +201,8 @@ Workspace::~Workspace() { } auto Workspace::update() -> void { - label_.set_label(fmt::format("{name}", fmt::arg("name", name_))); - label_.show(); + label_.set_markup(fmt::format( + format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); } auto Workspace::handle_state(const std::vector &state) -> void { @@ -195,4 +221,38 @@ auto Workspace::handle_remove() -> void { workspace_handle_ = nullptr; workspace_group_.remove_workspace(id_); } +auto Workspace::handle_done() -> void { + spdlog::debug("Workspace {} changed to state {}", id_, state_); + auto style_context = button_.get_style_context(); + if (is_active()) { + style_context->add_class("focused"); + } else { + style_context->remove_class("focused"); + } +} +auto Workspace::get_icon() -> std::string { + if (is_active()) { + auto focused_icon_it = icons_map_.find("focused"); + if (focused_icon_it != icons_map_.end()) { + return focused_icon_it->second; + } + } + + auto named_icon_it = icons_map_.find(name_); + if (named_icon_it != icons_map_.end()) { + return named_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() -> void { + spdlog::debug("Workspace {} clicked", (void*)workspace_handle_); + zwlr_workspace_handle_v1_activate(workspace_handle_); + workspace_group_.commit(); +} } // namespace waybar::modules::wlr \ No newline at end of file From 0ad29a5960c945fed328d300fce3d897d2a68994 Mon Sep 17 00:00:00 2001 From: dmitry Date: Fri, 7 Aug 2020 23:46:47 +0300 Subject: [PATCH 06/78] Finish base implementation --- include/modules/wlr/workspace_manager.hpp | 28 +++--- src/modules/wlr/workspace_manager.cpp | 86 +++++++++++++++---- src/modules/wlr/workspace_manager_binding.cpp | 12 ++- 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index 8cd065f1..abec5d66 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -21,29 +21,31 @@ class WorkspaceGroup; class Workspace { public: Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zwlr_workspace_handle_v1 *workspace); + zwlr_workspace_handle_v1 *workspace, uint32_t id); ~Workspace(); auto update() -> void; auto id() const -> uint32_t { return id_; } auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); } // wlr stuff - auto handle_name(const std::string &name) -> void { name_ = name; } - auto handle_coordinates(const std::vector &coordinates) -> void { - coordinates_ = coordinates; - } + 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 handle_done() -> void; auto handle_clicked() -> void; + auto show() -> void { button_.show(); } + auto hide() -> void { button_.hide(); } + 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 }; private: auto get_icon() -> std::string; - static uint32_t workspace_global_id; const Bar & bar_; const Json::Value &config_; WorkspaceGroup & workspace_group_; @@ -66,8 +68,9 @@ class Workspace { class WorkspaceGroup { public: - WorkspaceGroup(const waybar::Bar &bar, const Json::Value &config, WorkspaceManager &manager, - zwlr_workspace_group_handle_v1 *workspace_group_handle); + WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, + WorkspaceManager &manager, zwlr_workspace_group_handle_v1 *workspace_group_handle, + uint32_t id); ~WorkspaceGroup(); auto update() -> void; @@ -84,10 +87,12 @@ class WorkspaceGroup { auto add_button(Gtk::Button &button) -> void; auto handle_done() -> void; auto commit() -> void; + auto sort_workspaces() -> void; private: - static uint32_t group_global_id; + static uint32_t workspace_global_id; const waybar::Bar &bar_; + Gtk::Box & box_; const Json::Value &config_; WorkspaceManager & workspace_manager_; @@ -97,6 +102,8 @@ class WorkspaceGroup { uint32_t id_; std::vector> workspaces_; + bool sort_by_name = true; + bool sort_by_coordinates = true; }; class WorkspaceManager : public AModule { @@ -114,7 +121,6 @@ class WorkspaceManager : public AModule { auto handle_done() -> void; auto handle_finished() -> void; - auto add_button(Gtk::Button &button) -> void { box_.pack_start(button, false, false); } auto commit() -> void; private: @@ -124,6 +130,8 @@ class WorkspaceManager : public AModule { // wlr stuff zwlr_workspace_manager_v1 *workspace_manager_ = nullptr; + + static uint32_t group_global_id; }; } // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index cf0510da..2892d550 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -9,8 +9,8 @@ namespace waybar::modules::wlr { -uint32_t Workspace::workspace_global_id = 0; -uint32_t WorkspaceGroup::group_global_id = 0; +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, @@ -44,8 +44,10 @@ auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, ui auto WorkspaceManager::handle_workspace_group_create( zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void { - groups_.push_back(std::make_unique(bar_, config_, *this, workspace_group_handle)); - spdlog::debug("Workspace group {} created", groups_.back()->id()); + 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 { @@ -89,17 +91,28 @@ auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { } auto WorkspaceManager::commit() -> void { zwlr_workspace_manager_v1_commit(workspace_manager_); } -WorkspaceGroup::WorkspaceGroup(const Bar &bar, const Json::Value &config, WorkspaceManager &manager, - zwlr_workspace_group_handle_v1 *workspace_group_handle) +WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config, + WorkspaceManager & manager, + zwlr_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_(++group_global_id) { + id_(id) { add_workspace_group_listener(workspace_group_handle, this); + 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 WorkspaceGroup::add_button(Gtk::Button &button) -> void { - workspace_manager_.add_button(button); + box_.pack_start(button, false, false); } WorkspaceGroup::~WorkspaceGroup() { @@ -112,8 +125,9 @@ WorkspaceGroup::~WorkspaceGroup() { } auto WorkspaceGroup::handle_workspace_create(zwlr_workspace_handle_v1 *workspace) -> void { - workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace)); - spdlog::debug("Workspace {} created", workspaces_.back()->id()); + auto new_id = ++workspace_global_id; + workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace, new_id)); + spdlog::debug("Workspace {} created", new_id); } auto WorkspaceGroup::handle_remove() -> void { @@ -124,12 +138,18 @@ auto WorkspaceGroup::handle_remove() -> void { auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { spdlog::debug("Output {} assigned to {} group", (void *)output, id_); + for (auto &workspace : workspaces_) { + workspace->show(); + } output_ = output; } auto WorkspaceGroup::handle_output_leave() -> void { spdlog::debug("Output {} remove from {} group", (void *)output_, id_); output_ = nullptr; + for (auto &workspace : workspaces_) { + workspace->hide(); + } } auto WorkspaceGroup::update() -> void { @@ -150,22 +170,37 @@ auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { workspaces_.erase(it); } + auto WorkspaceGroup::handle_done() -> void { - if (is_visible()) { - for (auto &workspace : workspaces_) { - workspace->handle_done(); - } + for (auto &workspace : workspaces_) { + workspace->handle_done(); } } auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } +auto WorkspaceGroup::sort_workspaces() -> void { + auto cmp = [=](std::unique_ptr &lhs, std::unique_ptr &rhs) { + if (sort_by_name && lhs->get_name() != rhs->get_name()) { + return lhs->get_name() < rhs->get_name(); + } + + return lhs->get_coords() < rhs->get_coords(); + }; + std::sort(workspaces_.begin(), workspaces_.end(), cmp); + for (size_t i = 0; i < workspaces_.size(); ++i) { + for (auto &workspace : workspaces_) { + box_.reorder_child(workspace->get_button_ref(), i); + } + } +} + Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zwlr_workspace_handle_v1 *workspace) + zwlr_workspace_handle_v1 *workspace, uint32_t id) : bar_(bar), config_(config), workspace_group_(workspace_group), workspace_handle_(workspace), - id_(++workspace_global_id) { + id_(id) { add_workspace_listener(workspace, this); auto config_format = config["format"]; @@ -186,6 +221,10 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & button_.set_relief(Gtk::RELIEF_NONE); content_.set_center_widget(label_); button_.add(content_); + if (!workspace_group.is_visible()) { + return; + } + button_.show(); label_.show(); content_.show(); @@ -221,6 +260,7 @@ auto Workspace::handle_remove() -> void { workspace_handle_ = nullptr; workspace_group_.remove_workspace(id_); } + auto Workspace::handle_done() -> void { spdlog::debug("Workspace {} changed to state {}", id_, state_); auto style_context = button_.get_style_context(); @@ -230,6 +270,7 @@ auto Workspace::handle_done() -> void { style_context->remove_class("focused"); } } + auto Workspace::get_icon() -> std::string { if (is_active()) { auto focused_icon_it = icons_map_.find("focused"); @@ -250,9 +291,20 @@ auto Workspace::get_icon() -> std::string { return name_; } + auto Workspace::handle_clicked() -> void { - spdlog::debug("Workspace {} clicked", (void*)workspace_handle_); + spdlog::debug("Workspace {} clicked", (void *)workspace_handle_); zwlr_workspace_handle_v1_activate(workspace_handle_); workspace_group_.commit(); } + +auto Workspace::handle_name(const std::string &name) -> void { + name_ = name; + workspace_group_.sort_workspaces(); +} + +auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { + coordinates_ = coordinates; + workspace_group_.sort_workspaces(); +} } // namespace waybar::modules::wlr \ No newline at end of file diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp index f6db711b..c7d84bbb 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -107,12 +107,18 @@ void workspace_handle_coordinates(void *data, struct zwlr_workspace_handle_v1 *_ static_cast(data)->handle_coordinates(coords_vec); } -void workspace_handle_state(void *data, struct zwlr_workspace_handle_v1 *_, +void workspace_handle_state(void *data, struct zwlr_workspace_handle_v1 *workspace_handle, struct wl_array *state) { std::vector state_vec; - auto coords = static_cast(state->data); + auto states = static_cast(state->data); for (size_t i = 0; i < state->size; ++i) { - state_vec.push_back(coords[i]); + // To sync server and pending states + if (states[i] == ZWLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE) { + zwlr_workspace_handle_v1_activate(workspace_handle); + } else { + zwlr_workspace_handle_v1_deactivate(workspace_handle); + } + state_vec.push_back(states[i]); } static_cast(data)->handle_state(state_vec); From 42b6c089f34b24cb46a7add4892112a4b15799ca Mon Sep 17 00:00:00 2001 From: dmitry Date: Sat, 8 Aug 2020 00:09:35 +0300 Subject: [PATCH 07/78] Add docs and adjust sorting --- man/waybar-wlr-workspaces.5.scd | 69 +++++++++++++++++++++++++++ man/waybar.5.scd | 1 + meson.build | 1 + src/modules/wlr/workspace_manager.cpp | 23 +++++++-- 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 man/waybar-wlr-workspaces.5.scd diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd new file mode 100644 index 00000000..732c01be --- /dev/null +++ b/man/waybar-wlr-workspaces.5.scd @@ -0,0 +1,69 @@ +waybar-wlr-workspaces(5) + +# NAME + +waybar - wlr workspaces module + +# DESCRIPTION + +The *workspaces* module displays the currently used workspaces in waynland compositor. + +# CONFIGURATION + +Addressed by *wlr/workspaces* + +*format*: ++ + typeof: string ++ + default: {name} ++ + The format, how information should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the workspace name and state, the corresponding icon gets selected. See *icons*. + +*sort-by-name*: ++ + typeof: bool ++ + default: true ++ + Should workspaces be sorted by name. + +*sort-by-coordinates*: ++ + typeof: bool ++ + 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. + +# FORMAT REPLACEMENTS + +*{name}*: Number stripped from workspace value. + +*{icon}*: Icon, as defined in *format-icons*. + +# ICONS + +Additional to workspace name matching, the following *format-icons* can be set. + +- *default*: Will be shown, when no string matches is found. +- *focused*: Will be shown, when workspace is focused + +# EXAMPLES + +``` +"wlr/workspaces": { + "format": "{name}: {icon}", + "format-icons": { + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "focused": "", + "default": "" + } +} +``` + +# Style + +- *#workspaces button* +- *#workspaces button.focused* diff --git a/man/waybar.5.scd b/man/waybar.5.scd index 52671106..a7588219 100644 --- a/man/waybar.5.scd +++ b/man/waybar.5.scd @@ -197,5 +197,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* - *waybar-wlr-taskbar(5)* +- *waybar-wlr-workspaces(5)* - *waybar-temperature(5)* - *waybar-tray(5)* diff --git a/meson.build b/meson.build index 638a766d..19c6b4ac 100644 --- a/meson.build +++ b/meson.build @@ -272,6 +272,7 @@ if scdoc.found() 'waybar-tray.5.scd', 'waybar-states.5.scd', 'waybar-wlr-taskbar.5.scd', + 'waybar-wlr-workspaces.5.scd', 'waybar-bluetooth.5.scd', ] diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 2892d550..d86dc62f 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -101,12 +101,12 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value workspace_group_handle_(workspace_group_handle), id_(id) { add_workspace_group_listener(workspace_group_handle, this); - auto config_sort_by_name = config_["sort_by_name"]; + 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"]; + auto config_sort_by_coordinates = config_["sort-by-coordinates"]; if (config_sort_by_coordinates.isBool()) { sort_by_coordinates = config_sort_by_coordinates.asBool(); } @@ -180,12 +180,25 @@ auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } auto WorkspaceGroup::sort_workspaces() -> void { auto cmp = [=](std::unique_ptr &lhs, std::unique_ptr &rhs) { - if (sort_by_name && lhs->get_name() != rhs->get_name()) { - return lhs->get_name() < rhs->get_name(); + 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_name) { + if (sort_by_coordinates) { + return is_name_eq ? is_coords_less : is_name_less; + } + else { + return is_name_less; + } } - return lhs->get_coords() < rhs->get_coords(); + if (sort_by_coordinates) { + return is_coords_less; + } + + return lhs->id() < rhs->id(); }; + std::sort(workspaces_.begin(), workspaces_.end(), cmp); for (size_t i = 0; i < workspaces_.size(); ++i) { for (auto &workspace : workspaces_) { From 22409d27c5a6307880828841a8b7c4a113b3dc1b Mon Sep 17 00:00:00 2001 From: dmitry Date: Tue, 11 Aug 2020 00:28:56 +0300 Subject: [PATCH 08/78] Fix docs typos Add removing buttons Adjust handling multiple outputs. --- include/modules/wlr/workspace_manager.hpp | 1 + man/waybar-wlr-workspaces.5.scd | 7 ++++--- src/modules/wlr/workspace_manager.cpp | 21 ++++++++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index abec5d66..73c5893f 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -85,6 +85,7 @@ class WorkspaceGroup { auto handle_output_leave() -> void; auto add_button(Gtk::Button &button) -> void; + auto remove_button(Gtk::Button &button) -> void; auto handle_done() -> void; auto commit() -> void; auto sort_workspaces() -> void; diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index 732c01be..c1e827f3 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -6,7 +6,7 @@ waybar - wlr workspaces module # DESCRIPTION -The *workspaces* module displays the currently used workspaces in waynland compositor. +The *workspaces* module displays the currently used workspaces in wayland compositor. # CONFIGURATION @@ -35,7 +35,7 @@ Addressed by *wlr/workspaces* # FORMAT REPLACEMENTS -*{name}*: Number stripped from workspace value. +*{name}*: Name of workspace assigned by compositor *{icon}*: Icon, as defined in *format-icons*. @@ -43,7 +43,7 @@ Addressed by *wlr/workspaces* Additional to workspace name matching, the following *format-icons* can be set. -- *default*: Will be shown, when no string matches is found. +- *default*: Will be shown, when no string match is found. - *focused*: Will be shown, when workspace is focused # EXAMPLES @@ -65,5 +65,6 @@ Additional to workspace name matching, the following *format-icons* can be set. # Style +- *#workspaces* - *#workspaces button* - *#workspaces button.focused* diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index d86dc62f..20294f4a 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -1,5 +1,6 @@ #include "modules/wlr/workspace_manager.hpp" +#include #include #include @@ -77,6 +78,7 @@ WorkspaceManager::~WorkspaceManager() { zwlr_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(), @@ -111,6 +113,7 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value sort_by_coordinates = config_sort_by_coordinates.asBool(); } } + auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { box_.pack_start(button, false, false); } @@ -138,15 +141,25 @@ auto WorkspaceGroup::handle_remove() -> void { auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { spdlog::debug("Output {} assigned to {} group", (void *)output, id_); + output_ = output; + + if (output != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { + return; + } + for (auto &workspace : workspaces_) { workspace->show(); } - output_ = output; } 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_) { workspace->hide(); } @@ -176,6 +189,7 @@ auto WorkspaceGroup::handle_done() -> void { workspace->handle_done(); } } + auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } auto WorkspaceGroup::sort_workspaces() -> void { @@ -207,6 +221,10 @@ auto WorkspaceGroup::sort_workspaces() -> void { } } +auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { + box_.remove(button); +} + Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, zwlr_workspace_handle_v1 *workspace, uint32_t id) : bar_(bar), @@ -244,6 +262,7 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & } Workspace::~Workspace() { + workspace_group_.remove_button(button_); if (!workspace_handle_) { return; } From 285a264aae64780f9c8fbf22f88ebdf7ee55ab86 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 28 Dec 2020 17:26:55 -0800 Subject: [PATCH 09/78] feat(util): SafeSignal class for cross-thread signals with arguments Implement a wrapper over Glib::Dispatcher that passes the arguments to the signal consumer via synchronized `std::queue`. Arguments are always passed by value and the return type of the signal is expected to be `void`. --- include/util/SafeSignal.hpp | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 include/util/SafeSignal.hpp diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp new file mode 100644 index 00000000..b2beff4a --- /dev/null +++ b/include/util/SafeSignal.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace waybar { + +/** + * Thread-safe signal wrapper. + * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. + */ +template +struct SafeSignal : sigc::signal...)> { + public: + SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } + + template + void emit(EmitArgs&&... args) { + { + std::unique_lock lock(mutex_); + queue_.emplace(std::forward(args)...); + } + dp_.emit(); + } + + template + inline void operator()(EmitArgs&&... args) { + emit(std::forward(args)...); + } + + protected: + using signal_t = sigc::signal...)>; + using arg_tuple_t = std::tuple...>; + // ensure that unwrapped methods are not accessible + using signal_t::emit_reverse; + using signal_t::make_slot; + + void handle_event() { + auto fn = signal_t::make_slot(); + for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { + auto args = queue_.front(); + queue_.pop(); + lock.unlock(); + std::apply(fn, args); + } + } + + Glib::Dispatcher dp_; + std::mutex mutex_; + std::queue queue_; +}; + +} // namespace waybar From 8a0e76c8d8e433d78ee5cfe19bec428623fa9a51 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 19 Oct 2020 18:42:25 -0700 Subject: [PATCH 10/78] fix(util): avoid creating temporary functor for each event --- include/util/SafeSignal.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index b2beff4a..15a8d2bd 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace waybar { @@ -36,24 +37,26 @@ struct SafeSignal : sigc::signal...)> { protected: using signal_t = sigc::signal...)>; + using slot_t = decltype(std::declval().make_slot()); using arg_tuple_t = std::tuple...>; // ensure that unwrapped methods are not accessible using signal_t::emit_reverse; using signal_t::make_slot; void handle_event() { - auto fn = signal_t::make_slot(); for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { auto args = queue_.front(); queue_.pop(); lock.unlock(); - std::apply(fn, args); + std::apply(cached_fn_, args); } } Glib::Dispatcher dp_; std::mutex mutex_; std::queue queue_; + // cache functor for signal emission to avoid recreating it on each event + const slot_t cached_fn_ = make_slot(); }; } // namespace waybar From 79883dbce4caf5d8e66ff70af1d59a3a1950fc57 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 28 Dec 2020 17:31:23 -0800 Subject: [PATCH 11/78] feat(util): optimize SafeSignal for events from the main thread --- include/util/SafeSignal.hpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index 15a8d2bd..3b68653c 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -23,11 +24,22 @@ struct SafeSignal : sigc::signal...)> { template void emit(EmitArgs&&... args) { - { - std::unique_lock lock(mutex_); - queue_.emplace(std::forward(args)...); + if (main_tid_ == std::this_thread::get_id()) { + /* + * Bypass the queue if the method is called the main thread. + * Ensures that events emitted from the main thread are processed synchronously and saves a + * few CPU cycles on locking/queuing. + * As a downside, this makes main thread events prioritized over the other threads and + * disrupts chronological order. + */ + signal_t::emit(std::forward(args)...); + } else { + { + std::unique_lock lock(mutex_); + queue_.emplace(std::forward(args)...); + } + dp_.emit(); } - dp_.emit(); } template @@ -55,6 +67,7 @@ struct SafeSignal : sigc::signal...)> { Glib::Dispatcher dp_; std::mutex mutex_; std::queue queue_; + const std::thread::id main_tid_ = std::this_thread::get_id(); // cache functor for signal emission to avoid recreating it on each event const slot_t cached_fn_ = make_slot(); }; From 3e2197a82a9a0f790d484c89bd1655229f97f370 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 28 Dec 2020 17:28:03 -0800 Subject: [PATCH 12/78] test(util): add tests for SafeSignal Add a fixture for writing tests that require interaction with Glib event loop and a very basic test for SafeSignal. --- test/GlibTestsFixture.hpp | 19 ++++++++++++ test/SafeSignal.cpp | 63 +++++++++++++++++++++++++++++++++++++++ test/meson.build | 14 +++++++++ 3 files changed, 96 insertions(+) create mode 100644 test/GlibTestsFixture.hpp create mode 100644 test/SafeSignal.cpp diff --git a/test/GlibTestsFixture.hpp b/test/GlibTestsFixture.hpp new file mode 100644 index 00000000..ffe6fd31 --- /dev/null +++ b/test/GlibTestsFixture.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +/** + * Minimal Glib application to be used for tests that require Glib main loop + */ +class GlibTestsFixture : public sigc::trackable { + public: + GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} + + void run(std::function fn) { + Glib::signal_idle().connect_once(fn); + main_loop_->run(); + } + + void quit() { main_loop_->quit(); } + + protected: + Glib::RefPtr main_loop_; +}; diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp new file mode 100644 index 00000000..b07e9cab --- /dev/null +++ b/test/SafeSignal.cpp @@ -0,0 +1,63 @@ +#define CATCH_CONFIG_RUNNER +#include "util/SafeSignal.hpp" + +#include + +#include +#include + +#include "GlibTestsFixture.hpp" + +using namespace waybar; +/** + * Basic sanity test for SafeSignal: + * check that type deduction works, events are delivered and the order is right + * Running this with -fsanitize=thread should not fail + */ +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") { + const int NUM_EVENTS = 100; + int count = 0; + int last_value = 0; + + SafeSignal test_signal; + + const auto main_tid = std::this_thread::get_id(); + std::thread producer; + + // timeout the test in 500ms + Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, 500); + + test_signal.connect([&](auto val, auto str) { + static_assert(std::is_same::value); + static_assert(std::is_same::value); + // check that we're in the same thread as the main loop + REQUIRE(std::this_thread::get_id() == main_tid); + // check event order + REQUIRE(val == last_value + 1); + + last_value = val; + if (++count >= NUM_EVENTS) { + this->quit(); + }; + }); + + run([&]() { + // check that events from the same thread are delivered and processed synchronously + test_signal.emit(1, "test"); + REQUIRE(count == 1); + + // start another thread and generate events + producer = std::thread([&]() { + for (auto i = 2; i <= NUM_EVENTS; ++i) { + test_signal.emit(i, "test"); + } + }); + }); + producer.join(); + REQUIRE(count == NUM_EVENTS); +} + +int main(int argc, char* argv[]) { + Glib::init(); + return Catch::Session().run(argc, argv); +} diff --git a/test/meson.build b/test/meson.build index 85b9771f..bbef21e7 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ test_inc = include_directories('../include') test_dep = [ catch2, fmt, + gtkmm, jsoncpp, spdlog, ] @@ -14,8 +15,21 @@ config_test = executable( include_directories: test_inc, ) +safesignal_test = executable( + 'safesignal_test', + 'SafeSignal.cpp', + dependencies: test_dep, + include_directories: test_inc, +) + test( 'Configuration test', config_test, workdir: meson.source_root(), ) + +test( + 'SafeSignal test', + safesignal_test, + workdir: meson.source_root(), +) From 03a641ed8343c825635732e9d4c0694160c380e6 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 15 Sep 2021 22:35:50 +0700 Subject: [PATCH 13/78] feat(bar): support swaybar `mode` for configuring window Use `mode` (`waybar::Bar::setMode`) as a shorthand to configure bar visibility, layer, exclusive zones and input event handling in the same way as `swaybar` does. See `sway-bar(5)` for a description of available modes. --- include/bar.hpp | 1 + src/bar.cpp | 77 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 6f3dfcf9..1f90f9a6 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -58,6 +58,7 @@ class Bar { Bar(const Bar &) = delete; ~Bar() = default; + void setMode(const std::string &); void setVisible(bool visible); void toggle(); void handleSignal(int); diff --git a/src/bar.cpp b/src/bar.cpp index a8b230e1..3ce59e3a 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -403,27 +403,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["position"].asString()); - if (config["layer"] == "top") { - layer_ = bar_layer::TOP; - } else if (config["layer"] == "overlay") { - layer_ = bar_layer::OVERLAY; - } - - if (config["exclusive"].isBool()) { - exclusive = config["exclusive"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not reserve an exclusive zone - exclusive = false; - } - - bool passthrough = false; - if (config["passthrough"].isBool()) { - passthrough = config["passthrough"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not accept pointer events. - passthrough = true; - } - auto position = config["position"].asString(); if (position == "right" || position == "left") { @@ -505,13 +484,39 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_ = std::make_unique(window, *output); } - surface_impl_->setLayer(layer_); - surface_impl_->setExclusiveZone(exclusive); surface_impl_->setMargins(margins_); - surface_impl_->setPassThrough(passthrough); surface_impl_->setPosition(position); surface_impl_->setSize(width, height); + if (auto mode = config["mode"]; mode.isString()) { + setMode(mode.asString()); + } else { + if (config["layer"] == "top") { + layer_ = bar_layer::TOP; + } else if (config["layer"] == "overlay") { + layer_ = bar_layer::OVERLAY; + } + + if (config["exclusive"].isBool()) { + exclusive = config["exclusive"].asBool(); + } else if (layer_ == bar_layer::OVERLAY) { + // swaybar defaults: overlay mode does not reserve an exclusive zone + exclusive = false; + } + + bool passthrough = false; + if (config["passthrough"].isBool()) { + passthrough = config["passthrough"].asBool(); + } else if (layer_ == bar_layer::OVERLAY) { + // swaybar defaults: overlay mode does not accept pointer events. + passthrough = true; + } + + surface_impl_->setLayer(layer_); + surface_impl_->setExclusiveZone(exclusive); + surface_impl_->setPassThrough(passthrough); + } + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); setupWidgets(); @@ -528,6 +533,30 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } } +void waybar::Bar::setMode(const std::string& mode) { + bool passthrough = false; + visible = true; + exclusive = true; + layer_ = bar_layer::BOTTOM; + + if (mode == "hide") { + exclusive = false; + layer_ = bar_layer::TOP; + visible = false; + } else if (mode == "invisible") { + visible = false; + } else if (mode == "overlay") { + exclusive = false; + layer_ = bar_layer::TOP; + passthrough = true; + } + + surface_impl_->setLayer(layer_); + surface_impl_->setExclusiveZone(exclusive); + surface_impl_->setPassThrough(passthrough); + setVisible(visible); +} + void waybar::Bar::onMap(GdkEventAny*) { /* * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). From 6d2ba7a75b2399d38c8ee8719f172ec09ec298fc Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 19:29:51 -0800 Subject: [PATCH 14/78] feat(bar): store modes as a map of presets This allows to apply the mode atomically and adds possibility of defining custom modes (to be implemented). --- include/bar.hpp | 21 +++++++++++++++-- src/bar.cpp | 62 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 1f90f9a6..426c55c2 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -36,6 +36,13 @@ struct bar_margins { int left = 0; }; +struct bar_mode { + bar_layer layer; + bool exclusive; + bool passthrough; + bool visible; +}; + class BarSurface { protected: BarSurface() = default; @@ -54,18 +61,23 @@ class BarSurface { class Bar { public: + using bar_mode_map = std::map; + static const bar_mode_map PRESET_MODES; + static const std::string_view MODE_DEFAULT; + static const std::string_view MODE_INVISIBLE; + Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; ~Bar() = default; - void setMode(const std::string &); + void setMode(const std::string_view &); void setVisible(bool visible); void toggle(); void handleSignal(int); struct waybar_output *output; Json::Value config; - struct wl_surface * surface; + struct wl_surface *surface; bool exclusive = true; bool visible = true; bool vertical = false; @@ -77,6 +89,11 @@ class Bar { void getModules(const Factory &, const std::string &); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); + void setMode(const bar_mode &); + + /* Copy initial set of modes to allow customization */ + bar_mode_map configured_modes = PRESET_MODES; + std::string last_mode_{MODE_DEFAULT}; std::unique_ptr surface_impl_; bar_layer layer_; diff --git a/src/bar.cpp b/src/bar.cpp index 3ce59e3a..2f8bc27b 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -23,6 +23,35 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: static constexpr const char* SIZE_DEFINED = "{} size is defined in the config file so it will stay like that"; +const Bar::bar_mode_map Bar::PRESET_MODES = { // + {"dock", + {// Modes supported by the sway config; see man sway-bar(5) + .layer = bar_layer::BOTTOM, + .exclusive = true, + .passthrough = false, + .visible = true}}, + {"hide", + {// + .layer = bar_layer::TOP, + .exclusive = false, + .passthrough = false, + .visible = true}}, + {"invisible", + {// + .layer = bar_layer::BOTTOM, + .exclusive = false, + .passthrough = true, + .visible = false}}, + {"overlay", + {// + .layer = bar_layer::TOP, + .exclusive = false, + .passthrough = true, + .visible = true}}}; + +const std::string_view Bar::MODE_DEFAULT = "dock"; +const std::string_view Bar::MODE_INVISIBLE = "invisible"; + #ifdef HAVE_GTK_LAYER_SHELL struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { @@ -533,28 +562,25 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } } -void waybar::Bar::setMode(const std::string& mode) { - bool passthrough = false; - visible = true; - exclusive = true; - layer_ = bar_layer::BOTTOM; - - if (mode == "hide") { - exclusive = false; - layer_ = bar_layer::TOP; - visible = false; - } else if (mode == "invisible") { - visible = false; - } else if (mode == "overlay") { - exclusive = false; - layer_ = bar_layer::TOP; - passthrough = true; +void waybar::Bar::setMode(const std::string_view& mode) { + auto it = configured_modes.find(mode); + if (it != configured_modes.end()) { + last_mode_ = mode; + setMode(it->second); + } else { + spdlog::warn("Unknown mode \"{}\" requested", mode); + last_mode_ = MODE_DEFAULT; + setMode(configured_modes.at(MODE_DEFAULT)); } +} +void waybar::Bar::setMode(const struct bar_mode& mode) { + layer_ = mode.layer; + exclusive = mode.exclusive; surface_impl_->setLayer(layer_); surface_impl_->setExclusiveZone(exclusive); - surface_impl_->setPassThrough(passthrough); - setVisible(visible); + surface_impl_->setPassThrough(mode.passthrough); + setVisible(mode.visible); } void waybar::Bar::onMap(GdkEventAny*) { From ae88d7d8dcb028aea55d6c2ac675cfd22fb5db89 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 19:31:41 -0800 Subject: [PATCH 15/78] feat(bar): use "default" mode to store global options Read `layer`, `exclusive`, `passthrough` into a special mode "default". Drop `overlay` layer hacks, as it's easier to use `"mode": "overlay"` for the same result. --- src/bar.cpp | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 2f8bc27b..b009110f 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -24,6 +24,12 @@ static constexpr const char* SIZE_DEFINED = "{} size is defined in the config file so it will stay like that"; const Bar::bar_mode_map Bar::PRESET_MODES = { // + {"default", + {// Special mode to hold the global bar configuration + .layer = bar_layer::BOTTOM, + .exclusive = true, + .passthrough = false, + .visible = true}}, {"dock", {// Modes supported by the sway config; see man sway-bar(5) .layer = bar_layer::BOTTOM, @@ -49,7 +55,7 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // .passthrough = true, .visible = true}}}; -const std::string_view Bar::MODE_DEFAULT = "dock"; +const std::string_view Bar::MODE_DEFAULT = "default"; const std::string_view Bar::MODE_INVISIBLE = "invisible"; #ifdef HAVE_GTK_LAYER_SHELL @@ -517,33 +523,26 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_->setPosition(position); surface_impl_->setSize(width, height); + /* Init "default" mode from globals */ + auto& default_mode = configured_modes[MODE_DEFAULT]; + if (config["layer"] == "top") { + default_mode.layer = bar_layer::TOP; + } else if (config["layer"] == "overlay") { + default_mode.layer = bar_layer::OVERLAY; + } + + if (config["exclusive"].isBool()) { + default_mode.exclusive = config["exclusive"].asBool(); + } + + if (config["passthrough"].isBool()) { + default_mode.passthrough = config["passthrough"].asBool(); + } + if (auto mode = config["mode"]; mode.isString()) { - setMode(mode.asString()); + setMode(config["mode"].asString()); } else { - if (config["layer"] == "top") { - layer_ = bar_layer::TOP; - } else if (config["layer"] == "overlay") { - layer_ = bar_layer::OVERLAY; - } - - if (config["exclusive"].isBool()) { - exclusive = config["exclusive"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not reserve an exclusive zone - exclusive = false; - } - - bool passthrough = false; - if (config["passthrough"].isBool()) { - passthrough = config["passthrough"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not accept pointer events. - passthrough = true; - } - - surface_impl_->setLayer(layer_); - surface_impl_->setExclusiveZone(exclusive); - surface_impl_->setPassThrough(passthrough); + setMode(MODE_DEFAULT); } window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); From 87b43c21711e46c97972d5fe6a21aa40f298d626 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:02:57 -0800 Subject: [PATCH 16/78] feat(bar): attach CSS class `mode-{mode}` to window when setting mode --- src/bar.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bar.cpp b/src/bar.cpp index b009110f..43a627ab 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -562,13 +562,21 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } void waybar::Bar::setMode(const std::string_view& mode) { + using namespace std::literals::string_literals; + + auto style = window.get_style_context(); + /* remove styles added by previous setMode calls */ + style->remove_class("mode-"s + last_mode_); + auto it = configured_modes.find(mode); if (it != configured_modes.end()) { last_mode_ = mode; + style->add_class("mode-"s + last_mode_); setMode(it->second); } else { spdlog::warn("Unknown mode \"{}\" requested", mode); last_mode_ = MODE_DEFAULT; + style->add_class("mode-"s + last_mode_); setMode(configured_modes.at(MODE_DEFAULT)); } } From 52361ed3606311e7f47ff6e6a5357dfb43069d1b Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 21 Nov 2021 11:00:57 -0800 Subject: [PATCH 17/78] refactor(bar): make setVisible switch between "default" and "invisible" modes --- include/bar.hpp | 2 -- src/bar.cpp | 29 ++++++++++++----------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 426c55c2..1d7f4a98 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -78,7 +78,6 @@ class Bar { struct waybar_output *output; Json::Value config; struct wl_surface *surface; - bool exclusive = true; bool visible = true; bool vertical = false; Gtk::Window window; @@ -96,7 +95,6 @@ class Bar { std::string last_mode_{MODE_DEFAULT}; std::unique_ptr surface_impl_; - bar_layer layer_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; diff --git a/src/bar.cpp b/src/bar.cpp index 43a627ab..a7b24a6b 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -426,7 +426,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, - layer_{bar_layer::BOTTOM}, left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -582,12 +581,18 @@ void waybar::Bar::setMode(const std::string_view& mode) { } void waybar::Bar::setMode(const struct bar_mode& mode) { - layer_ = mode.layer; - exclusive = mode.exclusive; - surface_impl_->setLayer(layer_); - surface_impl_->setExclusiveZone(exclusive); + surface_impl_->setLayer(mode.layer); + surface_impl_->setExclusiveZone(mode.exclusive); surface_impl_->setPassThrough(mode.passthrough); - setVisible(mode.visible); + + if (mode.visible) { + window.get_style_context()->remove_class("hidden"); + window.set_opacity(1); + } else { + window.get_style_context()->add_class("hidden"); + window.set_opacity(0); + } + surface_impl_->commit(); } void waybar::Bar::onMap(GdkEventAny*) { @@ -600,17 +605,7 @@ void waybar::Bar::onMap(GdkEventAny*) { void waybar::Bar::setVisible(bool value) { visible = value; - if (!visible) { - window.get_style_context()->add_class("hidden"); - window.set_opacity(0); - surface_impl_->setLayer(bar_layer::BOTTOM); - } else { - window.get_style_context()->remove_class("hidden"); - window.set_opacity(1); - surface_impl_->setLayer(layer_); - } - surface_impl_->setExclusiveZone(exclusive && visible); - surface_impl_->commit(); + setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE); } void waybar::Bar::toggle() { setVisible(!visible); } From 5905078e56c3810fa7129049f1689b662fa6704a Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 21:02:29 -0800 Subject: [PATCH 18/78] doc: document `mode` option of the bar config --- man/waybar.5.scd.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 66d5b2eb..8490ee53 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -72,6 +72,11 @@ Also a minimal example configuration can be found on the at the bottom of this m typeof: string ++ Optional name added as a CSS class, for styling multiple waybars. +*mode* ++ + typeof: string ++ + Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++ + Note: *hide* and *invisible* modes may be not as useful without Sway IPC. + *exclusive* ++ typeof: bool ++ default: *true* unless the layer is set to *overlay* ++ From 452dcaa5d3f079cca0bf7be3896d7b9c814671ae Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:28:41 -0800 Subject: [PATCH 19/78] feat(client): store bar_id argument --- include/client.hpp | 1 + src/client.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/client.hpp b/include/client.hpp index bd80d0bd..7fc3dce7 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -29,6 +29,7 @@ class Client { struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; std::vector> bars; Config config; + std::string bar_id; private: Client() = default; diff --git a/src/client.cpp b/src/client.cpp index 95f5a295..8adbeac1 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) { bool show_version = false; std::string config_opt; std::string style_opt; - std::string bar_id; std::string log_level; auto cli = clara::detail::Help(show_help) | clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | From 23e5181cace3c6e6099ce539379fafd653addbbf Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 19 Oct 2020 19:34:48 -0700 Subject: [PATCH 20/78] feat(swaybar-ipc): add swaybar IPC client --- include/bar.hpp | 19 +++++++- include/modules/sway/bar.hpp | 48 +++++++++++++++++++ meson.build | 1 + src/bar.cpp | 17 +++++++ src/modules/sway/bar.cpp | 92 ++++++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 include/modules/sway/bar.hpp create mode 100644 src/modules/sway/bar.cpp diff --git a/include/bar.hpp b/include/bar.hpp index 1d7f4a98..1b9617be 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include "AModule.hpp" #include "xdg-output-unstable-v1-client-protocol.h" @@ -43,6 +46,12 @@ struct bar_mode { bool visible; }; +#ifdef HAVE_SWAY +namespace modules::sway { +class BarIpcClient; +} +#endif // HAVE_SWAY + class BarSurface { protected: BarSurface() = default; @@ -68,7 +77,7 @@ class Bar { Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; - ~Bar() = default; + ~Bar(); void setMode(const std::string_view &); void setVisible(bool visible); @@ -82,6 +91,10 @@ class Bar { bool vertical = false; Gtk::Window window; +#ifdef HAVE_SWAY + std::string bar_id; +#endif + private: void onMap(GdkEventAny *); auto setupWidgets() -> void; @@ -102,6 +115,10 @@ class Bar { std::vector> modules_left_; std::vector> modules_center_; std::vector> modules_right_; +#ifdef HAVE_SWAY + using BarIpcClient = modules::sway::BarIpcClient; + std::unique_ptr _ipc_client; +#endif }; } // namespace waybar diff --git a/include/modules/sway/bar.hpp b/include/modules/sway/bar.hpp new file mode 100644 index 00000000..e32dee4a --- /dev/null +++ b/include/modules/sway/bar.hpp @@ -0,0 +1,48 @@ +#pragma once +#include + +#include "modules/sway/ipc/client.hpp" +#include "util/SafeSignal.hpp" +#include "util/json.hpp" + +namespace waybar { + +class Bar; + +namespace modules::sway { + +/* + * Supported subset of i3/sway IPC barconfig object + */ +struct swaybar_config { + std::string id; + std::string mode; + std::string hidden_state; + std::string position; +}; + +/** + * swaybar IPC client + */ +class BarIpcClient { + public: + BarIpcClient(waybar::Bar& bar); + + private: + void onInitialConfig(const struct Ipc::ipc_response& res); + void onIpcEvent(const struct Ipc::ipc_response&); + void onConfigUpdate(const swaybar_config& config); + void onVisibilityUpdate(bool visible_by_modifier); + + Bar& bar_; + util::JsonParser parser_; + Ipc ipc_; + + swaybar_config bar_config_; + + SafeSignal signal_visible_; + SafeSignal signal_config_; +}; + +} // namespace modules::sway +} // namespace waybar diff --git a/meson.build b/meson.build index 52c9d292..a15ef59d 100644 --- a/meson.build +++ b/meson.build @@ -177,6 +177,7 @@ endif add_project_arguments('-DHAVE_SWAY', language: 'cpp') src_files += [ 'src/modules/sway/ipc/client.cpp', + 'src/modules/sway/bar.cpp', 'src/modules/sway/mode.cpp', 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', diff --git a/src/bar.cpp b/src/bar.cpp index a7b24a6b..8ad81e9c 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -11,6 +11,10 @@ #include "factory.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" +#ifdef HAVE_SWAY +#include "modules/sway/bar.hpp" +#endif + namespace waybar { static constexpr const char* MIN_HEIGHT_MSG = "Requested height: {} is less than the minimum height: {} required by the modules"; @@ -546,6 +550,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); +#if HAVE_SWAY + if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { + bar_id = Client::inst()->bar_id; + if (auto id = config["id"]; id.isString()) { + bar_id = id.asString(); + } + _ipc_client = std::make_unique(*this); + } +#endif + setupWidgets(); window.show_all(); @@ -560,6 +574,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } } +/* Need to define it here because of forward declared members */ +waybar::Bar::~Bar() = default; + void waybar::Bar::setMode(const std::string_view& mode) { using namespace std::literals::string_literals; diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp new file mode 100644 index 00000000..762353cd --- /dev/null +++ b/src/modules/sway/bar.cpp @@ -0,0 +1,92 @@ +#include "modules/sway/bar.hpp" + +#include +#include + +#include "bar.hpp" +#include "modules/sway/ipc/ipc.hpp" + +namespace waybar::modules::sway { + +BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} { + { + sigc::connection handle = + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig)); + ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id); + + handle.disconnect(); + } + + signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate)); + signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate)); + + ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent)); + // Launch worker + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("BarIpcClient::handleEvent {}", e.what()); + } + }); +} + +struct swaybar_config parseConfig(const Json::Value& payload) { + swaybar_config conf; + if (auto id = payload["id"]; id.isString()) { + conf.id = id.asString(); + } + if (auto mode = payload["mode"]; mode.isString()) { + conf.mode = mode.asString(); + } + if (auto hs = payload["hidden_state"]; hs.isString()) { + conf.hidden_state = hs.asString(); + } + if (auto position = payload["position"]; position.isString()) { + conf.position = position.asString(); + } + return conf; +} + +void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) { + try { + auto payload = parser_.parse(res.payload); + auto config = parseConfig(payload); + onConfigUpdate(config); + } catch (const std::exception& e) { + spdlog::error("BarIpcClient::onInitialConfig {}", e.what()); + } +} + +void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { + try { + auto payload = parser_.parse(res.payload); + if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) { + spdlog::trace("swaybar ipc: ignore event for {}", id.asString()); + return; + } + if (payload.isMember("visible_by_modifier")) { + // visibility change for hidden bar + signal_visible_(payload["visible_by_modifier"].asBool()); + } else { + // configuration update + auto config = parseConfig(payload); + signal_config_(config); + } + } catch (const std::exception& e) { + spdlog::error("BarIpcClient::onEvent {}", e.what()); + } +} + +void BarIpcClient::onConfigUpdate(const swaybar_config& config) { + spdlog::info("config update: {} {} {}", config.id, config.mode, config.position); + // TODO: pass config to bars +} + +void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { + spdlog::trace("visiblity update: {}", visible_by_modifier); + // TODO: pass visibility to bars +} + +} // namespace waybar::modules::sway From bc134531552b7429e55337fdfb1734a3e8701551 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 15 Sep 2021 22:39:51 +0700 Subject: [PATCH 21/78] feat(swaybar-ipc): handle mode update --- src/modules/sway/bar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index 762353cd..3179221c 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -81,7 +81,8 @@ void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { void BarIpcClient::onConfigUpdate(const swaybar_config& config) { spdlog::info("config update: {} {} {}", config.id, config.mode, config.position); - // TODO: pass config to bars + bar_config_ = config; + bar_.setMode(bar_config_.mode); } void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { From ebdeb8670310e1e4308a457be04829ba11d2b156 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 19 Oct 2020 19:35:55 -0700 Subject: [PATCH 22/78] feat(swaybar-ipc): handle visibility_by_modifier update --- include/modules/sway/bar.hpp | 2 ++ src/modules/sway/bar.cpp | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/modules/sway/bar.hpp b/include/modules/sway/bar.hpp index e32dee4a..d3a697c3 100644 --- a/include/modules/sway/bar.hpp +++ b/include/modules/sway/bar.hpp @@ -33,12 +33,14 @@ class BarIpcClient { void onIpcEvent(const struct Ipc::ipc_response&); void onConfigUpdate(const swaybar_config& config); void onVisibilityUpdate(bool visible_by_modifier); + void update(); Bar& bar_; util::JsonParser parser_; Ipc ipc_; swaybar_config bar_config_; + bool visible_by_modifier_ = false; SafeSignal signal_visible_; SafeSignal signal_config_; diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index 3179221c..7cae4baf 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -82,12 +82,23 @@ void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { void BarIpcClient::onConfigUpdate(const swaybar_config& config) { spdlog::info("config update: {} {} {}", config.id, config.mode, config.position); bar_config_ = config; - bar_.setMode(bar_config_.mode); + update(); } void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { spdlog::trace("visiblity update: {}", visible_by_modifier); - // TODO: pass visibility to bars + visible_by_modifier_ = visible_by_modifier; + update(); +} + +void BarIpcClient::update() { + bool visible = visible_by_modifier_; + if (bar_config_.mode == "invisible") { + visible = false; + } else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") { + visible = true; + } + bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE); } } // namespace waybar::modules::sway From 61783aafaa351d39074593ec5d3eb2603024b3d2 Mon Sep 17 00:00:00 2001 From: dmitry Date: Tue, 17 Aug 2021 04:31:17 +0300 Subject: [PATCH 23/78] save --- include/modules/wlr/taskbar.hpp | 4 +++ src/modules/wlr/taskbar.cpp | 61 ++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 891ad55b..6ba30ec7 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -3,6 +3,7 @@ #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" +#include "giomm/desktopappinfo.h" #include "util/json.hpp" #include @@ -61,6 +62,7 @@ class Task Gtk::Image icon_; Gtk::Label text_before_; Gtk::Label text_after_; + Glib::RefPtr app_info_; bool button_visible_; bool ignored_; @@ -70,6 +72,7 @@ class Task std::string format_tooltip_; + std::string name_; std::string title_; std::string app_id_; uint32_t state_ = 0; @@ -77,6 +80,7 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; + void set_desktop_app_info(const std::string &app_id); public: /* Getter functions */ diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 932a95e6..12b7c845 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -86,8 +86,7 @@ static Glib::RefPtr load_icon_from_file(std::string icon_path, int } } -/* Method 1 - get the correct icon name from the desktop file */ -static std::string get_from_desktop_app_info(const std::string &app_id) +static Glib::RefPtr get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); @@ -103,33 +102,30 @@ static std::string get_from_desktop_app_info(const std::string &app_id) ".desktop" }; - Glib::RefPtr app_info; + for (auto& prefix : prefixes) { + for (auto& folder : app_folders) { + for (auto& suffix : suffixes) { + auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + if (!app_info_) { + continue; + } - for (auto& prefix : prefixes) - for (auto& folder : app_folders) - for (auto& suffix : suffixes) - if (!app_info) - app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + return app_info_; + } + } + } - if (app_info && app_info->get_icon()) - return app_info->get_icon()->to_string(); - - return ""; + return {}; } -/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ -static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, - const std::string &app_id) +void Task::set_desktop_app_info(const std::string &app_id) { - if (icon_theme->lookup_icon(app_id, 24)) - return app_id; + auto app_info = get_app_info_by_name(app_id); + if (app_info) { + app_info_ = app_info; + return; + } - return ""; -} - -/* Method 3 - as last resort perform a search for most appropriate desktop info file */ -static std::string get_from_desktop_app_info_search(const std::string &app_id) -{ std::string desktop_file = ""; gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); @@ -137,6 +133,7 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id) for (size_t i=0; desktop_list[0][i]; i++) { if (desktop_file == "") { desktop_file = desktop_list[0][i]; + // TODO: debug. Possible error } else { auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); auto startup_class = tmp_info->get_startup_wm_class(); @@ -151,7 +148,18 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id) } g_free(desktop_list); - return get_from_desktop_app_info(desktop_file); + app_info = get_app_info_by_name(desktop_file); + app_info_ = app_info; +} + +/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ +static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, + const std::string &app_id) +{ + if (icon_theme->lookup_icon(app_id, 24)) + return app_id; + + return ""; } static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, @@ -560,14 +568,17 @@ void Task::update() { bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; std::string title = title_; + std::string name = name_; std::string app_id = app_id_; if (markup) { title = Glib::Markup::escape_text(title); + name = Glib::Markup::escape_text(name); app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { auto txt = fmt::format(format_before_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) @@ -581,6 +592,7 @@ void Task::update() if (!format_after_.empty()) { auto txt = fmt::format(format_after_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) @@ -595,6 +607,7 @@ void Task::update() if (!format_tooltip_.empty()) { auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) From 38afa345dd1012f2ff9e66cbb06d16e3190635d3 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 22 Aug 2021 20:18:03 +0300 Subject: [PATCH 24/78] Finish --- include/modules/wlr/taskbar.hpp | 11 ++- man/waybar-wlr-taskbar.5.scd | 11 ++- src/modules/wlr/taskbar.cpp | 170 +++++++++++++++++++------------- 3 files changed, 120 insertions(+), 72 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 6ba30ec7..5d9d93c6 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,8 @@ class Task bool button_visible_; bool ignored_; - bool with_icon_; + bool with_icon_ = false; + bool with_name_ = false; std::string format_before_; std::string format_after_; @@ -80,7 +82,8 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; - void set_desktop_app_info(const std::string &app_id); + void set_app_info_from_app_id_list(const std::string& app_id_list); + bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); public: /* Getter functions */ @@ -139,6 +142,7 @@ class Taskbar : public waybar::AModule std::vector> icon_themes_; std::unordered_set ignore_list_; + std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; @@ -161,8 +165,9 @@ class Taskbar : public waybar::AModule bool show_output(struct wl_output *) const; bool all_outputs() const; - std::vector> icon_themes() const; + const std::vector>& icon_themes() const; const std::unordered_set& ignore_list() const; + const std::map& app_ids_replace_map() const; }; } /* namespace waybar::modules::wlr */ diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index 0e86238a..ebd41fb7 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -72,10 +72,16 @@ Addressed by *wlr/taskbar* typeof: array ++ List of app_id to be invisible. +*app_ids-mapping*: ++ + typeof: object ++ + Directory of app_id to be replaced with + # FORMAT REPLACEMENTS *{icon}*: The icon of the application. +*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id} + *{title}*: The title of the application. *{app_id}*: The app_id (== application name) of the application. @@ -105,7 +111,10 @@ Addressed by *wlr/taskbar* "on-click-middle": "close", "ignore-list": [ "Alacritty" - ] + ], + "app_ids-mapping": { + "firefoxdeveloperedition": "firefox-developer-edition" + } } ``` diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 12b7c845..06ac3676 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -11,7 +11,9 @@ #include #include #include +#include +#include #include #include @@ -86,7 +88,7 @@ static Glib::RefPtr load_icon_from_file(std::string icon_path, int } } -static Glib::RefPtr get_app_info_by_name(const std::string& app_id) +static Glib::RefPtr get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); @@ -118,12 +120,11 @@ static Glib::RefPtr get_app_info_by_name(const std::string& app return {}; } -void Task::set_desktop_app_info(const std::string &app_id) +Glib::RefPtr get_desktop_app_info(const std::string &app_id) { auto app_info = get_app_info_by_name(app_id); if (app_info) { - app_info_ = app_info; - return; + return app_info; } std::string desktop_file = ""; @@ -133,7 +134,6 @@ void Task::set_desktop_app_info(const std::string &app_id) for (size_t i=0; desktop_list[0][i]; i++) { if (desktop_file == "") { desktop_file = desktop_list[0][i]; - // TODO: debug. Possible error } else { auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); auto startup_class = tmp_info->get_startup_wm_class(); @@ -148,12 +148,45 @@ void Task::set_desktop_app_info(const std::string &app_id) } g_free(desktop_list); - app_info = get_app_info_by_name(desktop_file); - app_info_ = app_info; + return get_app_info_by_name(desktop_file); } -/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ -static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, +void Task::set_app_info_from_app_id_list(const std::string& app_id_list) { + std::string app_id; + std::istringstream stream(app_id_list); + + /* Wayfire sends a list of app-id's in space separated format, other compositors + * send a single app-id, but in any case this works fine */ + while (stream >> app_id) + { + app_info_ = get_desktop_app_info(app_id); + if (app_info_) { + return; + } + + auto lower_app_id = app_id; + std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), + [](char c){ return std::tolower(c); }); + app_info_ = get_desktop_app_info(lower_app_id); + if (app_info_) { + return; + } + + size_t start = 0, end = app_id.size(); + start = app_id.rfind(".", end); + std::string app_name = app_id.substr(start+1, app_id.size()); + app_info_ = get_desktop_app_info(app_name); + if (app_info_) { + return; + } + + start = app_id.find("-"); + app_name = app_id.substr(0, start); + app_info_ = get_desktop_app_info(app_name); + } +} + +static std::string get_icon_name_from_icon_theme(const Glib::RefPtr& icon_theme, const std::string &app_id) { if (icon_theme->lookup_icon(app_id, 24)) @@ -162,62 +195,37 @@ static std::string get_from_icon_theme(const Glib::RefPtr& icon_ return ""; } -static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, - const std::string &app_id_list, int size) +bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size) { - std::string app_id; - std::istringstream stream(app_id_list); - bool found = false; + std::string ret_icon_name = "unknown"; + if (app_info) { + std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); + if (!icon_name.empty()) { + ret_icon_name = icon_name; + } else { + if (app_info->get_icon()) { + ret_icon_name = app_info->get_icon()->to_string(); + } + } + } - /* Wayfire sends a list of app-id's in space separated format, other compositors - * send a single app-id, but in any case this works fine */ - while (stream >> app_id) - { - size_t start = 0, end = app_id.size(); - start = app_id.rfind(".", end); - std::string app_name = app_id.substr(start+1, app_id.size()); + Glib::RefPtr pixbuf; - auto lower_app_id = app_id; - std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), - [](char c){ return std::tolower(c); }); + try { + pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); + } catch(...) { + if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) + pixbuf = load_icon_from_file(ret_icon_name, size); + else + pixbuf = {}; + } - std::string icon_name = get_from_icon_theme(icon_theme, app_id); + if (pixbuf) { + image.set(pixbuf); + return true; + } - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, lower_app_id); - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(lower_app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info_search(app_id); - - if (icon_name.empty()) - icon_name = "unknown"; - - Glib::RefPtr pixbuf; - - try { - pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); - } catch(...) { - if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) - pixbuf = load_icon_from_file(icon_name, size); - else - pixbuf = {}; - } - - if (pixbuf) { - image.set(pixbuf); - found = true; - break; - } - } - - return found; + return false; } /* Task class implementation */ @@ -298,13 +306,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, content_.show(); button_.add(content_); - with_icon_ = false; format_before_.clear(); format_after_.clear(); if (config_["format"].isString()) { /* The user defined a format string, use it */ auto format = config_["format"].asString(); + if (format.find("{name}") != std::string::npos) { + with_name_ = true; + } auto icon_pos = format.find("{icon}"); if (icon_pos == 0) { @@ -406,13 +416,28 @@ void Task::handle_app_id(const char *app_id) } } - if (!with_icon_) + auto ids_replace_map = tbar_->app_ids_replace_map(); + if (ids_replace_map.count(app_id_)) { + auto replaced_id = ids_replace_map[app_id_]; + spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id)); + app_id_ = replaced_id; + } + + if (!with_icon_ && !with_name_) { return; + } + + set_app_info_from_app_id_list(app_id_); + name_ = app_info_ ? app_info_->get_display_name() : app_id; + + if (!with_icon_) { + return; + } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; bool found = false; for (auto& icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { found = true; break; } @@ -735,6 +760,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu } } + // Load app_id remappings + if (config_["app_ids-mapping"].isObject()) { + const Json::Value& mapping = config_["app_ids-mapping"]; + const std::vector app_ids = config_["app_ids-mapping"].getMemberNames(); + for (auto& app_id : app_ids) { + app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); + } + } + icon_themes_.push_back(Gtk::IconTheme::get_default()); } @@ -866,10 +900,10 @@ bool Taskbar::all_outputs() const return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } -std::vector> Taskbar::icon_themes() const -{ - return icon_themes_; -} -const std::unordered_set &Taskbar::ignore_list() const { return ignore_list_; } +const std::vector>& Taskbar::icon_themes() const { return icon_themes_; } + +const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } + +const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } } /* namespace waybar::modules::wlr */ From ddfe036f003d18684a4c6f96fbdc4e94e386b387 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 22 Aug 2021 20:42:16 +0300 Subject: [PATCH 25/78] Format --- include/modules/wlr/taskbar.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 5d9d93c6..6680bbf0 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -82,8 +82,8 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; - void set_app_info_from_app_id_list(const std::string& app_id_list); - bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); + void set_app_info_from_app_id_list(const std::string& app_id_list); + bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); public: /* Getter functions */ @@ -142,7 +142,7 @@ class Taskbar : public waybar::AModule std::vector> icon_themes_; std::unordered_set ignore_list_; - std::map app_ids_replace_map_; + std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; From d60bb90b772a63933d5a9813bef926853d76054f Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 22 Aug 2021 20:43:37 +0300 Subject: [PATCH 26/78] Fix typo --- man/waybar-wlr-taskbar.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index ebd41fb7..4ce43da9 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -74,7 +74,7 @@ Addressed by *wlr/taskbar* *app_ids-mapping*: ++ typeof: object ++ - Directory of app_id to be replaced with + Dictionary of app_id to be replaced with # FORMAT REPLACEMENTS From 9d9f95976926d5817837d03344b9506d700cee16 Mon Sep 17 00:00:00 2001 From: Ciaran Downey Date: Wed, 25 Aug 2021 14:47:51 -0700 Subject: [PATCH 27/78] Switch network module to read /proc/net/dev This fixes issue #610 by reading bandwidth usage per-interface from /proc/net/dev instead of globally via /proc/net/netstat. It supports the same matching logic as elsewhere, so setting interface to '*' should display the same sum-total bandwidth usage as the previous implementation. --- include/modules/network.hpp | 1 + src/modules/network.cpp | 168 +++++++++++++++++------------------- 2 files changed, 79 insertions(+), 90 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 009ae5a3..183c9e2c 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -43,6 +43,7 @@ class Network : public ALabel { const std::string getNetworkState() const; void clearIface(); bool wildcardMatch(const std::string& pattern, const std::string& text) const; + std::optional> readBandwidthUsage(); int ifid_; sa_family_t family_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 7d0f6382..22b9f713 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -1,87 +1,79 @@ -#include "modules/network.hpp" +#include #include #include -#include -#include + #include +#include +#include #include + +#include "modules/network.hpp" #include "util/format.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" #endif namespace { - using namespace waybar::util; - -constexpr const char *NETSTAT_FILE = - "/proc/net/netstat"; // std::ifstream does not take std::string_view as param -constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt"; -constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets"; -constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets"; constexpr const char *DEFAULT_FORMAT = "{ifname}"; - -std::ifstream netstat(NETSTAT_FILE); -std::optional read_netstat(std::string_view category, std::string_view key) { - if (!netstat) { - spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE); - return {}; - } - netstat.seekg(std::ios_base::beg); - - // finding corresponding line (category) - // looks into the file for the first line starting by the 'category' string - auto starts_with = [](const std::string &str, std::string_view start) { - return start == std::string_view{str.data(), std::min(str.size(), start.size())}; - }; - - std::string read; - while (std::getline(netstat, read) && !starts_with(read, category)) - ; - if (!starts_with(read, category)) { - spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE); - return {}; - } - - // finding corresponding column (key) - // looks into the fetched line for the first word (space separated) equal to 'key' - int index = 0; - auto r_it = read.begin(); - auto k_it = key.begin(); - while (k_it != key.end() && r_it != read.end()) { - if (*r_it != *k_it) { - r_it = std::find(r_it, read.end(), ' '); - if (r_it != read.end()) { - ++r_it; - } - k_it = key.begin(); - ++index; - } else { - ++r_it; - ++k_it; - } - } - - if (r_it == read.end() && k_it != key.end()) { - spdlog::warn( - "Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE); - return {}; - } - - // finally accessing value - // accesses the line right under the fetched one - std::getline(netstat, read); - assert(starts_with(read, category)); - std::istringstream iss(read); - while (index--) { - std::getline(iss, read, ' '); - } - unsigned long long value; - iss >> value; - return value; -} } // namespace +constexpr const char *NETDEV_FILE = + "/proc/net/dev"; // std::ifstream does not take std::string_view as param +std::optional> +waybar::modules::Network::readBandwidthUsage() { + std::ifstream netdev(NETDEV_FILE); + if (!netdev) { + spdlog::warn("Failed to open netdev file {}", NETDEV_FILE); + return {}; + } + + // skip the headers + std::string line; + std::getline(netdev, line); + std::getline(netdev, line); + if (!netdev) { + spdlog::warn("Unexpectedly short netdev file {}", NETDEV_FILE); + return {}; + } + + unsigned long long receivedBytes = 0ull; + unsigned long long transmittedBytes = 0ull; + for (std::getline(netdev, line); netdev; std::getline(netdev, line)) { + std::istringstream iss(line); + + std::string ifacename; + iss >> ifacename; // ifacename contains "eth0:" + ifacename.pop_back(); // remove trailing ':' + if (!checkInterface(ifacename)) { + continue; + } + + // The rest of the line consists of whitespace separated counts divided + // into two groups (receive and transmit). The first column in each group + // is bytes, which is the only one we care about. + unsigned long long r = 0ull; + unsigned long long t = 0ull; + // Read received bytes + iss >> r; + // Skip all the other columns in the received group + for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) { + // skip whitespace between columns + while (iss.peek() == ' ') { iss.ignore(); } + // skip the irrelevant column + while (iss.peek() != ' ') { iss.ignore(); } + } + // Read transmit bytes + iss >> t; + spdlog::trace("read r={}, t={}, iface={}", r, t, ifacename); + + receivedBytes += r; + transmittedBytes += t; + } + + return {{receivedBytes, transmittedBytes}}; +} + waybar::modules::Network::Network(const std::string &id, const Json::Value &config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), @@ -106,17 +98,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf // the module start with no text, but the the event_box_ is shown. label_.set_markup(""); - auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); - auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); - if (down_octets) { - bandwidth_down_total_ = *down_octets; + auto bandwidth = readBandwidthUsage(); + if (bandwidth.has_value()) { + bandwidth_down_total_ = (*bandwidth).first; + bandwidth_up_total_ = (*bandwidth).second; } else { bandwidth_down_total_ = 0; - } - - if (up_octets) { - bandwidth_up_total_ = *up_octets; - } else { bandwidth_up_total_ = 0; } @@ -303,20 +290,21 @@ const std::string waybar::modules::Network::getNetworkState() const { auto waybar::modules::Network::update() -> void { std::lock_guard lock(mutex_); std::string tooltip_format; - auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); - auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); - unsigned long long bandwidth_down = 0; - if (down_octets) { - bandwidth_down = *down_octets - bandwidth_down_total_; - bandwidth_down_total_ = *down_octets; + auto bandwidth = readBandwidthUsage(); + auto bandwidth_down = 0ull; + auto bandwidth_up = 0ull; + if (bandwidth.has_value()) { + auto down_octets = (*bandwidth).first; + auto up_octets = (*bandwidth).second; + + bandwidth_down = down_octets - bandwidth_down_total_; + bandwidth_down_total_ = down_octets; + + bandwidth_up = up_octets - bandwidth_up_total_; + bandwidth_up_total_ = up_octets; } - unsigned long long bandwidth_up = 0; - if (up_octets) { - bandwidth_up = *up_octets - bandwidth_up_total_; - bandwidth_up_total_ = *up_octets; - } if (!alt_) { auto state = getNetworkState(); if (!state_.empty() && label_.get_style_context()->has_class(state_)) { From 5186dd27e64b00b19736a2fa3bd6b325c4fcd555 Mon Sep 17 00:00:00 2001 From: Ciaran Downey Date: Thu, 26 Aug 2021 11:30:06 -0700 Subject: [PATCH 28/78] Use while (getline) instead of a for loop Also make the comments surrounding the /proc/net/dev parsing clearer and remove the apparently redundant "is the netdev file still good?" check. --- src/modules/network.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 22b9f713..5818f025 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -28,18 +28,14 @@ waybar::modules::Network::readBandwidthUsage() { return {}; } - // skip the headers std::string line; + // skip the headers (first two lines) std::getline(netdev, line); std::getline(netdev, line); - if (!netdev) { - spdlog::warn("Unexpectedly short netdev file {}", NETDEV_FILE); - return {}; - } unsigned long long receivedBytes = 0ull; unsigned long long transmittedBytes = 0ull; - for (std::getline(netdev, line); netdev; std::getline(netdev, line)) { + while (std::getline(netdev, line)) { std::istringstream iss(line); std::string ifacename; @@ -50,8 +46,11 @@ waybar::modules::Network::readBandwidthUsage() { } // The rest of the line consists of whitespace separated counts divided - // into two groups (receive and transmit). The first column in each group - // is bytes, which is the only one we care about. + // into two groups (receive and transmit). Each group has the following + // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast + // + // We only care about the bytes count, so we'll just ignore the 7 other + // columns. unsigned long long r = 0ull; unsigned long long t = 0ull; // Read received bytes @@ -65,7 +64,6 @@ waybar::modules::Network::readBandwidthUsage() { } // Read transmit bytes iss >> t; - spdlog::trace("read r={}, t={}, iface={}", r, t, ifacename); receivedBytes += r; transmittedBytes += t; From 8d04da1551bdfae54d17e16c6e49e7e0819e1f71 Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Tue, 21 Sep 2021 13:15:36 +0200 Subject: [PATCH 29/78] dont escape essid in tooltip just like https://github.com/Alexays/Waybar/pull/834 but for the network name. --- src/modules/network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 3b9fb187..14a6d869 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -342,7 +342,7 @@ auto waybar::modules::Network::update() -> void { auto text = fmt::format( format_, - fmt::arg("essid", essid_), + fmt::arg("essid", Glib::Markup::escape_text(essid_)), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("ifname", ifname_), @@ -793,7 +793,7 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss) { auto essid_end = essid_begin + ies[1]; std::string essid_raw; std::copy(essid_begin, essid_end, std::back_inserter(essid_raw)); - essid_ = Glib::Markup::escape_text(essid_raw); + essid_ = essid_raw; } } } From 110c66dd329427fb29169fe0389f3ee4cbbd8307 Mon Sep 17 00:00:00 2001 From: Sergey Mishin Date: Sun, 3 Oct 2021 16:48:21 +0000 Subject: [PATCH 30/78] Refactor Clock: generalize multi timezones and single timezone cases After this refactoring: 1. Timezones parses only once on start and the we refer to saved values. All time_zone.isString() checks gone to the constructor. 2. Single timezone case handling as case of multi timezoned logic. 3. Scroll event seems more clear now. 4. Tooltip template parses on start to check if there calendar placeholder or not. To do not calculate calendar_text() if not necessary. --- include/modules/clock.hpp | 11 ++-- src/modules/clock.cpp | 108 ++++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 44 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 17752e4d..9f950192 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -17,6 +17,8 @@ struct waybar_time { date::zoned_seconds ztime; }; +const std::string kCalendarPlaceholder = "calendar"; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -26,18 +28,19 @@ class Clock : public ALabel { private: util::SleeperThread thread_; std::locale locale_; - const date::time_zone* time_zone_; - bool fixed_time_zone_; - int time_zone_idx_; + std::vector time_zones_; + int current_time_zone_idx_; date::year_month_day cached_calendar_ymd_ = date::January/1/0; std::string cached_calendar_text_; + bool is_calendar_in_tooltip_; bool handleScroll(GdkEventScroll* e); auto calendar_text(const waybar_time& wtime) -> std::string; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto first_day_of_week() -> date::weekday; - bool setTimeZone(Json::Value zone_name); + const date::time_zone* current_timezone(); + bool is_timezone_fixed(); }; } // namespace waybar::modules diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 7c94c457..d925c2b7 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -14,17 +14,51 @@ using waybar::modules::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) - : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { + : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), + current_time_zone_idx_(0), + is_calendar_in_tooltip_(false) +{ if (config_["timezones"].isArray() && !config_["timezones"].empty()) { - time_zone_idx_ = 0; - setTimeZone(config_["timezones"][time_zone_idx_]); + for (const auto& zone_name: config_["timezones"]) { + if (!zone_name.isString() || zone_name.asString().empty()) { + time_zones_.push_back(nullptr); + continue; + } + time_zones_.push_back( + date::locate_zone( + zone_name.asString() + ) + ); + + } + // If we parse all timezones and no one is good, add nullptr to the tmezones vector, to mark that we need to show localtime + if (!time_zones_.size()) { + time_zones_.push_back(nullptr); + } } else { - setTimeZone(config_["timezone"]); + time_zones_.push_back( + date::locate_zone( + config_["timezone"].asString() + ) + ); } - if (fixed_time_zone_) { + + if (!is_timezone_fixed()) { spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); } + // Check if we have to particular placeholder in tooltip format, to know what to calculate on update + if (config_["tooltip-format"].isString()) { + std::string trimmedFormat = config_["tooltip-format"].asString(); + trimmedFormat.erase(std::remove_if(trimmedFormat.begin(), + trimmedFormat.end(), + [](unsigned char x){return std::isspace(x);}), + trimmedFormat.end()); + if (trimmedFormat.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { + is_calendar_in_tooltip_ = true; + } + } + if (config_["locale"].isString()) { locale_ = std::locale(config_["locale"].asString()); } else { @@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) }; } +const date::time_zone* waybar::modules::Clock::current_timezone() { + return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone(); +} + +bool waybar::modules::Clock::is_timezone_fixed() { + return time_zones_[current_time_zone_idx_] != nullptr; +} + auto waybar::modules::Clock::update() -> void { - if (!fixed_time_zone_) { - // Time zone can change. Be sure to pick that. - time_zone_ = date::current_zone(); - } - - auto now = std::chrono::system_clock::now(); + auto time_zone = current_timezone(); + auto now = std::chrono::system_clock::now(); waybar_time wtime = {locale_, - date::make_zoned(time_zone_, date::floor(now))}; - - std::string text; - if (!fixed_time_zone_) { + date::make_zoned(time_zone, date::floor(now))}; + std::string text = ""; + if (!is_timezone_fixed()) { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); text = fmt::format(format_, localtime); - label_.set_markup(text); } else { text = fmt::format(format_, wtime); - label_.set_markup(text); } + label_.set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { - const auto calendar = calendar_text(wtime); - auto tooltip_format = config_["tooltip-format"].asString(); - auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); - label_.set_tooltip_markup(tooltip_text); - } else { - label_.set_tooltip_markup(text); + std::string calendarText = ""; + if (is_calendar_in_tooltip_) { + calendarText = calendar_text(wtime); + } + auto tooltip_format = config_["tooltip-format"].asString(); + text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendarText)); } } + + label_.set_tooltip_markup(text); // Call parent update ALabel::update(); } -bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) { - if (!zone_name.isString() || zone_name.asString().empty()) { - fixed_time_zone_ = false; - return false; - } - - time_zone_ = date::locate_zone(zone_name.asString()); - fixed_time_zone_ = true; - return true; -} - bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { // defer to user commands if set if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { return true; } - if (!config_["timezones"].isArray() || config_["timezones"].empty()) { + if (time_zones_.size() == 1) { return true; } - auto nr_zones = config_["timezones"].size(); + + auto nr_zones = time_zones_.size(); if (dir == SCROLL_DIR::UP) { - size_t new_idx = time_zone_idx_ + 1; - time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; + size_t new_idx = current_time_zone_idx_ + 1; + current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; } else { - time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; + current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1; } - setTimeZone(config_["timezones"][time_zone_idx_]); + update(); return true; } From d8bc6c92bb49f4255dab1c24c88f1e9c1fecd549 Mon Sep 17 00:00:00 2001 From: Sergey Mishin Date: Tue, 5 Oct 2021 09:55:30 +0000 Subject: [PATCH 31/78] Fix style and spelling --- src/modules/clock.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index d925c2b7..33028b78 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -31,7 +31,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) ); } - // If we parse all timezones and no one is good, add nullptr to the tmezones vector, to mark that we need to show localtime + // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown. if (!time_zones_.size()) { time_zones_.push_back(nullptr); } @@ -47,14 +47,14 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); } - // Check if we have to particular placeholder in tooltip format, to know what to calculate on update + // Check if a particular placeholder is present in the tooltip format, to know what to calculate on update. if (config_["tooltip-format"].isString()) { - std::string trimmedFormat = config_["tooltip-format"].asString(); - trimmedFormat.erase(std::remove_if(trimmedFormat.begin(), - trimmedFormat.end(), + std::string trimmed_format = config_["tooltip-format"].asString(); + trimmed_format.erase(std::remove_if(trimmed_format.begin(), + trimmed_format.end(), [](unsigned char x){return std::isspace(x);}), - trimmedFormat.end()); - if (trimmedFormat.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { + trimmed_format.end()); + if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { is_calendar_in_tooltip_ = true; } } @@ -100,12 +100,12 @@ auto waybar::modules::Clock::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { - std::string calendarText = ""; + std::string calendar_lines = ""; if (is_calendar_in_tooltip_) { - calendarText = calendar_text(wtime); + calendar_lines = calendar_text(wtime); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendarText)); + text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar_lines)); } } From c5e4d2632045a42d620d929a976a0263fbdc047c Mon Sep 17 00:00:00 2001 From: Sergey Mishin Date: Tue, 5 Oct 2021 10:20:06 +0000 Subject: [PATCH 32/78] Fix working without timezone --- src/modules/clock.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 33028b78..7e7d7420 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -29,13 +29,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) zone_name.asString() ) ); - } - // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown. - if (!time_zones_.size()) { - time_zones_.push_back(nullptr); - } - } else { + } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) { time_zones_.push_back( date::locate_zone( config_["timezone"].asString() @@ -43,6 +38,11 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) ); } + // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown. + if (!time_zones_.size()) { + time_zones_.push_back(nullptr); + } + if (!is_timezone_fixed()) { spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); } @@ -105,7 +105,7 @@ auto waybar::modules::Clock::update() -> void { calendar_lines = calendar_text(wtime); } auto tooltip_format = config_["tooltip-format"].asString(); - text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar_lines)); + text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines)); } } From 08b4a8333193097a3b119e38baa745bca5eaf771 Mon Sep 17 00:00:00 2001 From: Ashutosh Malviya Date: Fri, 15 Oct 2021 19:07:25 +0530 Subject: [PATCH 33/78] Add reverse scrolling config option for pulseaudio When natural scrolling is enabled, the behaviour of scrolling on pulseaudio module is reversed, this commit reverses the direction of scroll variable if "reverse-scrolling" is set to 1 in config file. --- src/modules/pulseaudio.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index cf427800..e5b855be 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { if (dir == SCROLL_DIR::NONE) { return true; } + if (config_["reverse-scrolling"].asInt() == 1){ + if (dir == SCROLL_DIR::UP) { + dir = SCROLL_DIR::DOWN; + } else if (dir == SCROLL_DIR::DOWN) { + dir = SCROLL_DIR::UP; + } + } double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; From 75a6dddea5ae5f96d40e0b823ee65bf94ef329f6 Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 20 Oct 2021 01:23:00 +0300 Subject: [PATCH 34/78] Finish --- include/modules/wlr/taskbar.hpp | 1 + man/waybar-wlr-taskbar.5.scd | 2 +- src/modules/wlr/taskbar.cpp | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 891ad55b..f9233baf 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -77,6 +77,7 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; + void hide_if_ignored(); public: /* Getter functions */ diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index 0e86238a..6645cc24 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -70,7 +70,7 @@ Addressed by *wlr/taskbar* *ignore-list*: ++ typeof: array ++ - List of app_id to be invisible. + List of app_id/titles to be invisible. # FORMAT REPLACEMENTS diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 932a95e6..b27e9f8d 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -276,8 +276,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, id_{global_id++}, - content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, - button_visible_{false}, ignored_{false} + content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); @@ -377,13 +376,12 @@ std::string Task::state_string(bool shortened) const void Task::handle_title(const char *title) { title_ = title; + hide_if_ignored(); } -void Task::handle_app_id(const char *app_id) +void Task::hide_if_ignored() { - app_id_ = app_id; - - if (tbar_->ignore_list().count(app_id)) { + if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { ignored_ = true; if (button_visible_) { auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); @@ -397,6 +395,12 @@ void Task::handle_app_id(const char *app_id) handle_output_enter(output); } } +} + +void Task::handle_app_id(const char *app_id) +{ + app_id_ = app_id; + hide_if_ignored(); if (!with_icon_) return; @@ -418,13 +422,13 @@ void Task::handle_app_id(const char *app_id) void Task::handle_output_enter(struct wl_output *output) { - spdlog::debug("{} entered output {}", repr(), (void*)output); - if (ignored_) { spdlog::debug("{} is ignored", repr()); return; } + spdlog::debug("{} entered output {}", repr(), (void*)output); + if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { /* The task entered the output of the current bar make the button visible */ tbar_->add_button(button_); From 769b12f16a05f1c86c9966810dee93185bbc8a15 Mon Sep 17 00:00:00 2001 From: Birger Schacht <1143280+b1rger@users.noreply.github.com> Date: Sat, 6 Nov 2021 09:00:15 +0000 Subject: [PATCH 35/78] Fix typo --- man/waybar-battery.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 48c2ee19..e8053c91 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -120,7 +120,7 @@ The two arguments are: # CUSTOM FORMATS -The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected. +The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected. *format-*: With *states*, a custom format can be set depending on the capacity of your battery. From b24f9ea5690a949f91f367eb90bbe25472f55a74 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 11 Nov 2021 21:42:05 +0100 Subject: [PATCH 36/78] Ensure MPD volume is not negative If the primary output does not support changing volume MPD will report -1. Ensure that negative volume levels will be represented as 0 instead. Signed-off-by: Sefa Eyeoglu --- src/modules/mpd/mpd.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 0a7c9706..6d272867 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() { date = getTag(MPD_TAG_DATE); song_pos = mpd_status_get_song_pos(status_.get()); volume = mpd_status_get_volume(status_.get()); + if (volume < 0) { + volume = 0; + } queue_length = mpd_status_get_queue_length(status_.get()); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); From ad3f46214d791b98364dd95719ee49c9446f20a5 Mon Sep 17 00:00:00 2001 From: John Fredriksson <94405030+jfred9@users.noreply.github.com> Date: Mon, 15 Nov 2021 21:23:36 +0100 Subject: [PATCH 37/78] river/tags: Add possibility for mouse clicks Left mouse click - set-focused-tags Right mouse click - toggle-focused-tags --- include/modules/river/tags.hpp | 6 ++ man/waybar-river-tags.5.scd | 5 ++ protocol/meson.build | 1 + protocol/river-control-unstable-v1.xml | 85 ++++++++++++++++++++++++++ src/modules/river/tags.cpp | 72 +++++++++++++++++++++- 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 protocol/river-control-unstable-v1.xml diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index 9b75fbd3..c49ec60d 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -6,6 +6,7 @@ #include "AModule.hpp" #include "bar.hpp" #include "river-status-unstable-v1-client-protocol.h" +#include "river-control-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar::modules::river { @@ -20,7 +21,12 @@ class Tags : public waybar::AModule { void handle_view_tags(struct wl_array *tags); void handle_urgent_tags(uint32_t tags); + void handle_primary_clicked(uint32_t tag); + bool handle_button_press(GdkEventButton *event_button, uint32_t tag); + struct zriver_status_manager_v1 *status_manager_; + struct zriver_control_v1 *control_; + struct wl_seat *seat_; private: const waybar::Bar & bar_; diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index 65b90332..7814ee49 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -21,6 +21,11 @@ Addressed by *river/tags* typeof: array ++ The label to display for each tag. +*disable-click*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled. + # EXAMPLE ``` diff --git a/protocol/meson.build b/protocol/meson.build index 07d524ae..ea988efc 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -28,6 +28,7 @@ client_protocols = [ ['wlr-layer-shell-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['river-status-unstable-v1.xml'], + ['river-control-unstable-v1.xml'], ] client_protos_src = [] diff --git a/protocol/river-control-unstable-v1.xml b/protocol/river-control-unstable-v1.xml new file mode 100644 index 00000000..b8faa45c --- /dev/null +++ b/protocol/river-control-unstable-v1.xml @@ -0,0 +1,85 @@ + + + + Copyright 2020 The River Developers + + 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. + + + + + This interface allows clients to run compositor commands and receive a + success/failure response with output or a failure message respectively. + + Each command is built up in a series of add_argument requests and + executed with a run_command request. The first argument is the command + to be run. + + A complete list of commands should be made available in the man page of + the compositor. + + + + + This request indicates that the client will not use the + river_control object any more. Objects that have been created + through this instance are not affected. + + + + + + Arguments are stored by the server in the order they were sent until + the run_command request is made. + + + + + + + Execute the command built up using the add_argument request for the + given seat. + + + + + + + + + This object is created by the run_command request. Exactly one of the + success or failure events will be sent. This object will be destroyed + by the compositor after one of the events is sent. + + + + + Sent when the command has been successfully received and executed by + the compositor. Some commands may produce output, in which case the + output argument will be a non-empty string. + + + + + + + Sent when the command could not be carried out. This could be due to + sending a non-existent command, no command, not enough arguments, too + many arguments, invalid arguments, etc. + + + + + diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 2628af2d..87655beb 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -7,7 +7,6 @@ #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 { @@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{ .urgent_tags = listen_urgent_tags, }; +static void listen_command_success(void *data, + struct zriver_command_callback_v1 *zriver_command_callback_v1, + const char *output) { + // Do nothing but keep listener to avoid crashing when command was successful +} + +static void listen_command_failure(void *data, + struct zriver_command_callback_v1 *zriver_command_callback_v1, + const char *output) { + spdlog::error("failure when selecting/toggling tags {}", output); +} + +static const zriver_command_callback_v1_listener command_callback_listener_impl { + .success = listen_command_success, + .failure = listen_command_failure, +}; + 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) { @@ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } + + if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) { + version = std::min(version, 1); + static_cast(data)->control_ = static_cast( + wl_registry_bind(registry, name, &zriver_control_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) { /* 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}, + control_{nullptr}, + seat_{nullptr}, bar_(bar), box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, output_status_{nullptr} { @@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con return; } + if (!control_) { + spdlog::error("river_control_v1 not advertised"); + } + + if (!seat_) { + spdlog::error("wl_seat not advertised"); + } + box_.set_name("tags"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con } } + uint32_t i = 1; for (const auto &tag_label : tag_labels) { Gtk::Button &button = buttons_.emplace_back(tag_label); button.set_relief(Gtk::RELIEF_NONE); box_.pack_start(button, false, false, 0); + if (!config_["disable-click"].asBool()) { + button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i)); + button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i)); + } button.show(); + i <<= 1; } struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); @@ -107,6 +152,31 @@ Tags::~Tags() { if (output_status_) { zriver_output_status_v1_destroy(output_status_); } + + if (control_) { + zriver_control_v1_destroy(control_); + } +} + +void Tags::handle_primary_clicked(uint32_t tag) { + // Send river command to select tag on left mouse click + zriver_command_callback_v1 *callback; + zriver_control_v1_add_argument(control_, "set-focused-tags"); + zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); + callback = zriver_control_v1_run_command(control_, seat_); + zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); +} + +bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { + if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { + // Send river command to toggle tag on right mouse click + zriver_command_callback_v1 *callback; + zriver_control_v1_add_argument(control_, "toggle-focused-tags"); + zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); + callback = zriver_control_v1_run_command(control_, seat_); + zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); + } + return true; } void Tags::handle_focused_tags(uint32_t tags) { From 23955fdcc25ea40cc8d7b4d1386cc83f0ed0ddff Mon Sep 17 00:00:00 2001 From: Anakael Date: Fri, 19 Nov 2021 18:38:59 +0300 Subject: [PATCH 38/78] Update taskbar.hpp --- include/modules/wlr/taskbar.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index f9233baf..cdef9f18 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -77,7 +77,7 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; - void hide_if_ignored(); + void hide_if_ignored(); public: /* Getter functions */ From 18ea53fcbcefe445f98a6f8575bcd1944d2c7710 Mon Sep 17 00:00:00 2001 From: Anakael Date: Fri, 19 Nov 2021 18:39:49 +0300 Subject: [PATCH 39/78] Update taskbar.hpp --- include/modules/wlr/taskbar.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index cdef9f18..c7e66286 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -77,7 +77,7 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; - void hide_if_ignored(); + void hide_if_ignored(); public: /* Getter functions */ From cb6af026f632d4cb56546bbce73ad114c01a9957 Mon Sep 17 00:00:00 2001 From: Anakael Date: Fri, 19 Nov 2021 18:51:28 +0300 Subject: [PATCH 40/78] Update taskbar.hpp --- include/modules/wlr/taskbar.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index c7e66286..3fe032f8 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -61,8 +61,8 @@ class Task Gtk::Image icon_; Gtk::Label text_before_; Gtk::Label text_after_; - bool button_visible_; - bool ignored_; + bool button_visible_ = false; + bool ignored_ = false; bool with_icon_; std::string format_before_; From 60c1706273808937ab460367d350f217e31ebfb0 Mon Sep 17 00:00:00 2001 From: Anakael Date: Fri, 19 Nov 2021 18:54:37 +0300 Subject: [PATCH 41/78] Update taskbar.cpp --- src/modules/wlr/taskbar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index b27e9f8d..af1b0da0 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -376,7 +376,7 @@ std::string Task::state_string(bool shortened) const void Task::handle_title(const char *title) { title_ = title; - hide_if_ignored(); + hide_if_ignored(); } void Task::hide_if_ignored() @@ -400,7 +400,7 @@ void Task::hide_if_ignored() void Task::handle_app_id(const char *app_id) { app_id_ = app_id; - hide_if_ignored(); + hide_if_ignored(); if (!with_icon_) return; From 5baffbf8f8b47b619fc59334caeb0adf6e9473f0 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:28:57 -0800 Subject: [PATCH 42/78] doc: document swaybar ipc options, `ipc` and `id` --- man/waybar.5.scd.in | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 8490ee53..bfc9ab87 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -94,6 +94,16 @@ Also a minimal example configuration can be found on the at the bottom of this m Option to disable the use of gtk-layer-shell for popups. Only functional if compiled with gtk-layer-shell support. +*ipc* ++ + typeof: bool ++ + default: false ++ + Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++ + Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option. + +*id* ++ + typeof: string ++ + *bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance. + *include* ++ typeof: string|array ++ Paths to additional configuration files. From 4154492603ddec3fc8e091cf34bcbc8081fa780a Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:35:51 -0800 Subject: [PATCH 43/78] ci: update FreeBSD configuration Use latest action v0.1.5. Pin runner to macos-10.15. macos-latest will start using macos-11 images without VirtualBox in less than a month[1]. [1] https://github.com/actions/virtual-environments/issues/4060 --- .github/workflows/freebsd.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 8af7a04a..03e5d707 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -4,11 +4,14 @@ on: [ push, pull_request ] jobs: clang: - runs-on: macos-latest # until https://github.com/actions/runner/issues/385 + # Run actions in a FreeBSD vm on the macos-10.15 runner + # https://github.com/actions/runner/issues/385 - for FreeBSD runner support + # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners + runs-on: macos-10.15 steps: - uses: actions/checkout@v2 - name: Test in FreeBSD VM - uses: vmactions/freebsd-vm@v0.1.4 # aka FreeBSD 12.2 + uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 with: usesh: true prepare: | From ffeecf626ceaaa3dfd2ac4f2a9bd6b0c3f5a40ae Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 21 Nov 2021 15:01:25 +0300 Subject: [PATCH 44/78] Update names --- include/modules/wlr/workspace_manager.hpp | 16 +-- .../modules/wlr/workspace_manager_binding.hpp | 10 +- ...e-v1.xml => ext-workspace-unstable-v1.xml} | 104 ++++++++++++------ protocol/meson.build | 2 +- src/modules/wlr/workspace_manager.cpp | 28 ++--- src/modules/wlr/workspace_manager_binding.cpp | 54 ++++----- 6 files changed, 125 insertions(+), 89 deletions(-) rename protocol/{wlr-workspace-unstable-v1.xml => ext-workspace-unstable-v1.xml} (73%) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index 73c5893f..02f4e37f 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -11,7 +11,7 @@ #include "AModule.hpp" #include "bar.hpp" -#include "wlr-workspace-unstable-v1-client-protocol.h" +#include "ext-workspace-unstable-v1-client-protocol.h" namespace waybar::modules::wlr { @@ -21,7 +21,7 @@ class WorkspaceGroup; class Workspace { public: Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zwlr_workspace_handle_v1 *workspace, uint32_t id); + zext_workspace_handle_v1 *workspace, uint32_t id); ~Workspace(); auto update() -> void; @@ -51,7 +51,7 @@ class Workspace { WorkspaceGroup & workspace_group_; // wlr stuff - zwlr_workspace_handle_v1 *workspace_handle_; + zext_workspace_handle_v1 *workspace_handle_; uint32_t state_ = 0; uint32_t id_; @@ -69,7 +69,7 @@ class Workspace { class WorkspaceGroup { public: WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, - WorkspaceManager &manager, zwlr_workspace_group_handle_v1 *workspace_group_handle, + WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id); ~WorkspaceGroup(); auto update() -> void; @@ -79,7 +79,7 @@ class WorkspaceGroup { auto remove_workspace(uint32_t id_) -> void; // wlr stuff - auto handle_workspace_create(zwlr_workspace_handle_v1 *workspace_handle) -> void; + 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; @@ -98,7 +98,7 @@ class WorkspaceGroup { WorkspaceManager & workspace_manager_; // wlr stuff - zwlr_workspace_group_handle_v1 *workspace_group_handle_; + zext_workspace_group_handle_v1 *workspace_group_handle_; wl_output * output_ = nullptr; uint32_t id_; @@ -117,7 +117,7 @@ class WorkspaceManager : public AModule { // wlr stuff auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; - auto handle_workspace_group_create(zwlr_workspace_group_handle_v1 *workspace_group_handle) + auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle) -> void; auto handle_done() -> void; auto handle_finished() -> void; @@ -130,7 +130,7 @@ class WorkspaceManager : public AModule { std::vector> groups_; // wlr stuff - zwlr_workspace_manager_v1 *workspace_manager_ = nullptr; + zext_workspace_manager_v1 *workspace_manager_ = nullptr; static uint32_t group_global_id; }; diff --git a/include/modules/wlr/workspace_manager_binding.hpp b/include/modules/wlr/workspace_manager_binding.hpp index 1cef652f..0bfe663f 100644 --- a/include/modules/wlr/workspace_manager_binding.hpp +++ b/include/modules/wlr/workspace_manager_binding.hpp @@ -1,8 +1,8 @@ -#include "wlr-workspace-unstable-v1-client-protocol.h" +#include "ext-workspace-unstable-v1-client-protocol.h" namespace waybar::modules::wlr { void add_registry_listener(void *data); - void add_workspace_listener(zwlr_workspace_handle_v1 *workspace_handle, void *data); - void add_workspace_group_listener(zwlr_workspace_group_handle_v1 *workspace_group_handle, void *data); - zwlr_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data); -} \ No newline at end of file + 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); +} diff --git a/protocol/wlr-workspace-unstable-v1.xml b/protocol/ext-workspace-unstable-v1.xml similarity index 73% rename from protocol/wlr-workspace-unstable-v1.xml rename to protocol/ext-workspace-unstable-v1.xml index ae0e44db..24410b62 100644 --- a/protocol/wlr-workspace-unstable-v1.xml +++ b/protocol/ext-workspace-unstable-v1.xml @@ -1,5 +1,5 @@ - + Copyright © 2019 Christopher Billington Copyright © 2020 Ilia Bozhinov @@ -26,7 +26,7 @@ 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 @@ -45,7 +45,7 @@ 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 zwlr_workspace_manager_v1, each workspace will be + After a client binds the zext_workspace_manager_v1, each workspace will be sent via the workspace event. @@ -55,9 +55,9 @@ All initial details of the workspace group (workspaces, outputs) will be sent immediately after this event via the corresponding events in - zwlr_workspace_group_handle_v1. + zext_workspace_group_handle_v1. - + @@ -68,21 +68,21 @@ 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 zwlr_workspace_handle_v1 objects, for example, deactivating one + 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 event is sent after all changes in all workspace groups have been + sent. - This allows changes to one or more zwlr_workspace_group_handle_v1 + 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 - zwlr_workspace_group_handle_v1 objects in question. The compositor sends + zext_workspace_group_handle_v1 objects in question. The compositor sends the done event only after updating the output information in both workspace groups. @@ -91,7 +91,7 @@ This event indicates that the compositor is done sending events to the - zwlr_workspace_manager_v1. The server will destroy the object + 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. @@ -108,14 +108,19 @@ - + - A zwlr_workspace_group_handle_v1 object represents a a workspace group + 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. @@ -140,26 +145,36 @@ All initial details of the workspace (name, coordinates, state) will be sent immediately after this event via the corresponding events in - zwlr_workspace_handle_v1. + zext_workspace_handle_v1. - + - This event means the zwlr_workspace_group_handle_v1 has been destroyed. - It is guaranteed there won't be any more events for this - zwlr_workspace_group_handle_v1. The zwlr_workspace_group_handle_v1 becomes - inert so any requests will be ignored except the destroy request. + 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. + 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 zwlr_workspace_handle_v1 object. + + 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 @@ -168,9 +183,9 @@ - + - A zwlr_workspace_handle_v1 object represents a a workspace that handles a + 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 @@ -178,11 +193,16 @@ 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 zwlr_workspace_handle_v1 is + This event is emitted immediately after the zext_workspace_handle_v1 is created and whenever the name of the workspace changes. @@ -192,13 +212,13 @@ This event is used to organize workspaces into an N-dimensional grid within a workspace group, and if supported, is emitted immediately after - the zwlr_workspace_handle_v1 is created and whenever the coordinates of + 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. + 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 @@ -214,7 +234,7 @@ - This event is emitted immediately after the zwlr_workspace_handle_v1 is + 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. @@ -227,20 +247,28 @@ + + + + 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 zwlr_workspace_handle_v1 has been destroyed. It is + This event means the zext_workspace_handle_v1 has been destroyed. It is guaranteed there won't be any more events for this - zwlr_workspace_handle_v1. The zwlr_workspace_handle_v1 becomes inert so + zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so any requests will be ignored except the destroy request. - - Destroys the zwlr_workspace_handle_v1 object. + + 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 @@ -266,5 +294,13 @@ 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 467260bc..f81cb0d4 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,7 +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'], - ['wlr-workspace-unstable-v1.xml'], + ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ] diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 20294f4a..26556ece 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -44,7 +44,7 @@ auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, ui } auto WorkspaceManager::handle_workspace_group_create( - zwlr_workspace_group_handle_v1 *workspace_group_handle) -> void { + 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)); @@ -52,7 +52,7 @@ auto WorkspaceManager::handle_workspace_group_create( } auto WorkspaceManager::handle_finished() -> void { - zwlr_workspace_manager_v1_destroy(workspace_manager_); + zext_workspace_manager_v1_destroy(workspace_manager_); workspace_manager_ = nullptr; } @@ -75,7 +75,7 @@ WorkspaceManager::~WorkspaceManager() { return; } - zwlr_workspace_manager_v1_destroy(workspace_manager_); + zext_workspace_manager_v1_destroy(workspace_manager_); workspace_manager_ = nullptr; } @@ -91,11 +91,11 @@ auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { groups_.erase(it); } -auto WorkspaceManager::commit() -> void { zwlr_workspace_manager_v1_commit(workspace_manager_); } +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, - zwlr_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) + zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) : bar_(bar), box_(box), config_(config), @@ -123,18 +123,18 @@ WorkspaceGroup::~WorkspaceGroup() { return; } - zwlr_workspace_group_handle_v1_destroy(workspace_group_handle_); + zext_workspace_group_handle_v1_destroy(workspace_group_handle_); workspace_group_handle_ = nullptr; } -auto WorkspaceGroup::handle_workspace_create(zwlr_workspace_handle_v1 *workspace) -> void { +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); } auto WorkspaceGroup::handle_remove() -> void { - zwlr_workspace_group_handle_v1_destroy(workspace_group_handle_); + zext_workspace_group_handle_v1_destroy(workspace_group_handle_); workspace_group_handle_ = nullptr; workspace_manager_.remove_workspace_group(id_); } @@ -226,7 +226,7 @@ auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { } Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zwlr_workspace_handle_v1 *workspace, uint32_t id) + zext_workspace_handle_v1 *workspace, uint32_t id) : bar_(bar), config_(config), workspace_group_(workspace_group), @@ -267,7 +267,7 @@ Workspace::~Workspace() { return; } - zwlr_workspace_handle_v1_destroy(workspace_handle_); + zext_workspace_handle_v1_destroy(workspace_handle_); workspace_handle_ = nullptr; } @@ -280,7 +280,7 @@ auto Workspace::handle_state(const std::vector &state) -> void { state_ = 0; for (auto state_entry : state) { switch (state_entry) { - case ZWLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE: + case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE: state_ |= (uint32_t)State::ACTIVE; break; } @@ -288,7 +288,7 @@ auto Workspace::handle_state(const std::vector &state) -> void { } auto Workspace::handle_remove() -> void { - zwlr_workspace_handle_v1_destroy(workspace_handle_); + zext_workspace_handle_v1_destroy(workspace_handle_); workspace_handle_ = nullptr; workspace_group_.remove_workspace(id_); } @@ -326,7 +326,7 @@ auto Workspace::get_icon() -> std::string { auto Workspace::handle_clicked() -> void { spdlog::debug("Workspace {} clicked", (void *)workspace_handle_); - zwlr_workspace_handle_v1_activate(workspace_handle_); + zext_workspace_handle_v1_activate(workspace_handle_); workspace_group_.commit(); } @@ -339,4 +339,4 @@ auto Workspace::handle_coordinates(const std::vector &coordinates) -> coordinates_ = coordinates; workspace_group_.sort_workspaces(); } -} // namespace waybar::modules::wlr \ No newline at end of file +} // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp index c7d84bbb..6a339735 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -9,7 +9,7 @@ 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, zwlr_workspace_manager_v1_interface.name) == 0) { + if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) { static_cast(data)->register_manager(registry, name, version); } } @@ -31,72 +31,72 @@ void add_registry_listener(void *data) { } static void workspace_manager_handle_workspace_group( - void *data, zwlr_workspace_manager_v1 *_, zwlr_workspace_group_handle_v1 *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, zwlr_workspace_manager_v1 *_) { +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, zwlr_workspace_manager_v1 *_) { +static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) { static_cast(data)->handle_finished(); } -static const zwlr_workspace_manager_v1_listener workspace_manager_impl = { +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, }; -zwlr_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, +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, &zwlr_workspace_manager_v1_interface, version)); + auto *workspace_manager = static_cast( + wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version)); if (workspace_manager) - zwlr_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); + 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, zwlr_workspace_group_handle_v1 *_, +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, zwlr_workspace_group_handle_v1 *_, +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, zwlr_workspace_group_handle_v1 *_, - zwlr_workspace_handle_v1 *workspace) { +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, zwlr_workspace_group_handle_v1 *_) { +static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) { static_cast(data)->handle_remove(); } -static const zwlr_workspace_group_handle_v1_listener workspace_group_impl = { +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(zwlr_workspace_group_handle_v1 *workspace_group_handle, +void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void * data) { - zwlr_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); + zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); } -void workspace_handle_name(void *data, struct zwlr_workspace_handle_v1 *_, const char *name) { +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 zwlr_workspace_handle_v1 *_, +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); @@ -107,16 +107,16 @@ void workspace_handle_coordinates(void *data, struct zwlr_workspace_handle_v1 *_ static_cast(data)->handle_coordinates(coords_vec); } -void workspace_handle_state(void *data, struct zwlr_workspace_handle_v1 *workspace_handle, +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; ++i) { // To sync server and pending states - if (states[i] == ZWLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE) { - zwlr_workspace_handle_v1_activate(workspace_handle); + if (states[i] == ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) { + zext_workspace_handle_v1_activate(workspace_handle); } else { - zwlr_workspace_handle_v1_deactivate(workspace_handle); + zext_workspace_handle_v1_deactivate(workspace_handle); } state_vec.push_back(states[i]); } @@ -124,17 +124,17 @@ void workspace_handle_state(void *data, struct zwlr_workspace_handle_v1 *workspa static_cast(data)->handle_state(state_vec); } -void workspace_handle_remove(void *data, struct zwlr_workspace_handle_v1 *_) { +void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) { static_cast(data)->handle_remove(); } -static const zwlr_workspace_handle_v1_listener workspace_impl = { +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(zwlr_workspace_handle_v1 *workspace_handle, void *data) { - zwlr_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); +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 From ef4c6a9ba3c65235e0af0c6456a14f39a4974408 Mon Sep 17 00:00:00 2001 From: dmitry Date: Mon, 22 Nov 2021 01:12:55 +0300 Subject: [PATCH 45/78] Update to proto. Fix displaying. Rename classes. --- include/modules/wlr/workspace_manager.hpp | 15 ++- man/waybar-wlr-workspaces.5.scd | 11 ++- src/modules/wlr/workspace_manager.cpp | 99 ++++++++++++++----- src/modules/wlr/workspace_manager_binding.cpp | 6 -- 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index 02f4e37f..1fcfc229 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -27,6 +27,8 @@ class Workspace { 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); } // wlr stuff auto handle_name(const std::string &name) -> void; auto handle_coordinates(const std::vector &coordinates) -> void; @@ -34,14 +36,19 @@ class Workspace { auto handle_remove() -> void; auto handle_done() -> void; - auto handle_clicked() -> void; + auto handle_clicked(GdkEventButton *bt) -> bool; auto show() -> void { button_.show(); } auto hide() -> void { button_.hide(); } 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 }; + enum class State + { + ACTIVE = 1 << 0, + URGENT = 1 << 1, + HIDDEN = 1 << 2, + }; private: auto get_icon() -> std::string; @@ -75,7 +82,7 @@ class WorkspaceGroup { auto update() -> void; auto id() const -> uint32_t { return id_; } - auto is_visible() const -> bool { return output_ != nullptr; } + auto is_visible() const -> bool; auto remove_workspace(uint32_t id_) -> void; // wlr stuff @@ -89,6 +96,7 @@ class WorkspaceGroup { auto handle_done() -> void; auto commit() -> void; auto sort_workspaces() -> void; + auto set_need_to_sort() -> void { need_to_sort = true; } private: static uint32_t workspace_global_id; @@ -105,6 +113,7 @@ class WorkspaceGroup { std::vector> workspaces_; bool sort_by_name = true; bool sort_by_coordinates = true; + bool need_to_sort = false; }; class WorkspaceManager : public AModule { diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index c1e827f3..344d932c 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -28,7 +28,7 @@ Addressed by *wlr/workspaces* *sort-by-coordinates*: ++ typeof: bool ++ - default: false ++ + default: true ++ 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. @@ -39,6 +39,11 @@ Addressed by *wlr/workspaces* *{icon}*: Icon, as defined in *format-icons*. +# CLICK ACTIONS + +*activate*: Switch to workspace. +*close*: Close the workspace. + # ICONS Additional to workspace name matching, the following *format-icons* can be set. @@ -67,4 +72,6 @@ Additional to workspace name matching, the following *format-icons* can be set. - *#workspaces* - *#workspaces button* -- *#workspaces button.focused* +- *#workspaces button.active* +- *#workspaces button.urgent* +- *#workspaces button.hidden* diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 26556ece..1d67b046 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -143,15 +143,19 @@ auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { spdlog::debug("Output {} assigned to {} group", (void *)output, id_); output_ = output; - if (output != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { - return; + if (!is_visible()) { + return; } for (auto &workspace : workspaces_) { - workspace->show(); + add_button(workspace->get_button_ref()); } } +auto WorkspaceGroup::is_visible() const -> bool { + return output_ != nullptr && 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; @@ -161,7 +165,7 @@ auto WorkspaceGroup::handle_output_leave() -> void { } for (auto &workspace : workspaces_) { - workspace->hide(); + remove_button(workspace->get_button_ref()); } } @@ -185,9 +189,16 @@ auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { } auto WorkspaceGroup::handle_done() -> void { + need_to_sort = false; + if (!is_visible()) { + return; + } + for (auto &workspace : workspaces_) { workspace->handle_done(); } + + sort_workspaces(); } auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } @@ -215,9 +226,7 @@ auto WorkspaceGroup::sort_workspaces() -> void { std::sort(workspaces_.begin(), workspaces_.end(), cmp); for (size_t i = 0; i < workspaces_.size(); ++i) { - for (auto &workspace : workspaces_) { - box_.reorder_child(workspace->get_button_ref(), i); - } + box_.reorder_child(workspaces_[i]->get_button_ref(), i); } } @@ -246,16 +255,23 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & } } - button_.signal_clicked().connect(sigc::mem_fun(this, &Workspace::handle_clicked)); + /* 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); + } - workspace_group.add_button(button_); 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(); label_.show(); content_.show(); @@ -283,6 +299,12 @@ auto Workspace::handle_state(const std::vector &state) -> void { 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::URGENT; + break; } } } @@ -293,21 +315,27 @@ auto Workspace::handle_remove() -> void { workspace_group_.remove_workspace(id_); } +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(); - if (is_active()) { - style_context->add_class("focused"); - } else { - style_context->remove_class("focused"); - } + 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"); } auto Workspace::get_icon() -> std::string { if (is_active()) { - auto focused_icon_it = icons_map_.find("focused"); - if (focused_icon_it != icons_map_.end()) { - return focused_icon_it->second; + auto active_icon_it = icons_map_.find("active"); + if (active_icon_it != icons_map_.end()) { + return active_icon_it->second; } } @@ -324,19 +352,46 @@ auto Workspace::get_icon() -> std::string { return name_; } -auto Workspace::handle_clicked() -> void { - spdlog::debug("Workspace {} clicked", (void *)workspace_handle_); - zext_workspace_handle_v1_activate(workspace_handle_); +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::handle_name(const std::string &name) -> void { + if (name_ != name) { + workspace_group_.set_need_to_sort(); + } name_ = name; - workspace_group_.sort_workspaces(); } auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { + if (coordinates_ != coordinates) { + workspace_group_.set_need_to_sort(); + } coordinates_ = coordinates; - workspace_group_.sort_workspaces(); } } // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp index 6a339735..1005d5c8 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -112,12 +112,6 @@ void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspa std::vector state_vec; auto states = static_cast(state->data); for (size_t i = 0; i < state->size; ++i) { - // To sync server and pending states - if (states[i] == ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) { - zext_workspace_handle_v1_activate(workspace_handle); - } else { - zext_workspace_handle_v1_deactivate(workspace_handle); - } state_vec.push_back(states[i]); } From 6bfb674d1b034a9679c049668714532e2da880c1 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 21 Nov 2021 17:28:47 -0800 Subject: [PATCH 46/78] fix(swaybar-ipc): better logs --- include/modules/sway/bar.hpp | 1 - src/modules/sway/bar.cpp | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/modules/sway/bar.hpp b/include/modules/sway/bar.hpp index d3a697c3..c4381a43 100644 --- a/include/modules/sway/bar.hpp +++ b/include/modules/sway/bar.hpp @@ -18,7 +18,6 @@ struct swaybar_config { std::string id; std::string mode; std::string hidden_state; - std::string position; }; /** diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index 7cae4baf..17382e3b 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -43,9 +43,6 @@ struct swaybar_config parseConfig(const Json::Value& payload) { if (auto hs = payload["hidden_state"]; hs.isString()) { conf.hidden_state = hs.asString(); } - if (auto position = payload["position"]; position.isString()) { - conf.position = position.asString(); - } return conf; } @@ -80,13 +77,17 @@ void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { } void BarIpcClient::onConfigUpdate(const swaybar_config& config) { - spdlog::info("config update: {} {} {}", config.id, config.mode, config.position); + spdlog::info("config update for {}: id {}, mode {}, hidden_state {}", + bar_.bar_id, + config.id, + config.mode, + config.hidden_state); bar_config_ = config; update(); } void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { - spdlog::trace("visiblity update: {}", visible_by_modifier); + spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier); visible_by_modifier_ = visible_by_modifier; update(); } From 23991b65430c8b06e360ef289c0db6d9f3e297ec Mon Sep 17 00:00:00 2001 From: dmitry Date: Tue, 23 Nov 2021 03:10:44 +0300 Subject: [PATCH 47/78] Finish --- include/modules/wlr/workspace_manager.hpp | 55 +++-- man/waybar-wlr-workspaces.5.scd | 10 + src/modules/wlr/workspace_manager.cpp | 198 ++++++++++++------ src/modules/wlr/workspace_manager_binding.cpp | 5 +- 4 files changed, 183 insertions(+), 85 deletions(-) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index 1fcfc229..e4cdb4da 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -37,25 +38,24 @@ class Workspace { auto handle_done() -> void; auto handle_clicked(GdkEventButton *bt) -> bool; - auto show() -> void { button_.show(); } - auto hide() -> void { button_.hide(); } + 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, + enum class State { + ACTIVE = (1 << 0), + URGENT = (1 << 1), + HIDDEN = (1 << 2), }; private: auto get_icon() -> std::string; - const Bar & bar_; + const Bar &bar_; const Json::Value &config_; - WorkspaceGroup & workspace_group_; + WorkspaceGroup &workspace_group_; // wlr stuff zext_workspace_handle_v1 *workspace_handle_; @@ -84,35 +84,36 @@ class WorkspaceGroup { 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 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; // 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 add_button(Gtk::Button &button) -> void; - auto remove_button(Gtk::Button &button) -> void; auto handle_done() -> void; auto commit() -> void; - auto sort_workspaces() -> void; - auto set_need_to_sort() -> void { need_to_sort = true; } private: static uint32_t workspace_global_id; const waybar::Bar &bar_; - Gtk::Box & box_; + Gtk::Box &box_; const Json::Value &config_; - WorkspaceManager & workspace_manager_; + WorkspaceManager &workspace_manager_; // wlr stuff zext_workspace_group_handle_v1 *workspace_group_handle_; - wl_output * output_ = nullptr; + wl_output *output_ = nullptr; uint32_t id_; std::vector> workspaces_; - bool sort_by_name = true; - bool sort_by_coordinates = true; bool need_to_sort = false; }; @@ -122,6 +123,13 @@ class WorkspaceManager : public AModule { ~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 @@ -130,11 +138,10 @@ class WorkspaceManager : public AModule { -> void; auto handle_done() -> void; auto handle_finished() -> void; - auto commit() -> void; private: - const waybar::Bar & bar_; + const waybar::Bar &bar_; Gtk::Box box_; std::vector> groups_; @@ -142,6 +149,12 @@ class WorkspaceManager : public AModule { zext_workspace_manager_v1 *workspace_manager_ = nullptr; static uint32_t group_global_id; + + bool sort_by_name_ = true; + bool sort_by_coordinates_ = true; + bool all_outputs_ = false; + bool active_only_ = false; + bool creation_delayed_ = false; }; } // namespace waybar::modules::wlr diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index 344d932c..f0df5e94 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -33,6 +33,16 @@ Addressed by *wlr/workspaces* 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. +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown. + +*active-only*: ++ + typeof: bool ++ + default: false ++ + If set to true only active or urgent workspaces will be shown. + # FORMAT REPLACEMENTS *{name}*: Name of workspace assigned by compositor diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 1d67b046..9077dc87 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -5,7 +5,10 @@ #include #include +#include +#include +#include "gtkmm/widget.h" #include "modules/wlr/workspace_manager_binding.hpp" namespace waybar::modules::wlr { @@ -19,6 +22,27 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 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_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); @@ -31,6 +55,54 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar } } +auto WorkspaceManager::workspace_comparator() const + -> std::function &, std::unique_ptr &)> { + return [=](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_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_) { @@ -67,6 +139,9 @@ auto WorkspaceManager::update() -> void { for (auto &group : groups_) { group->update(); } + if (creation_delayed()) { + creation_delayed_ = false; + } AModule::update(); } @@ -94,7 +169,7 @@ auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { 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, + WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) : bar_(bar), box_(box), @@ -103,15 +178,11 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value workspace_group_handle_(workspace_group_handle), id_(id) { add_workspace_group_listener(workspace_group_handle, this); - 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 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 { @@ -143,8 +214,8 @@ auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { spdlog::debug("Output {} assigned to {} group", (void *)output, id_); output_ = output; - if (!is_visible()) { - return; + if (!is_visible() || workspace_manager_.creation_delayed()) { + return; } for (auto &workspace : workspaces_) { @@ -153,7 +224,9 @@ auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { } auto WorkspaceGroup::is_visible() const -> bool { - return output_ != nullptr && output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + return output_ != nullptr && + (workspace_manager_.all_outputs() || + output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())); } auto WorkspaceGroup::handle_output_leave() -> void { @@ -171,6 +244,13 @@ auto WorkspaceGroup::handle_output_leave() -> void { 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(); } } @@ -198,41 +278,27 @@ auto WorkspaceGroup::handle_done() -> void { workspace->handle_done(); } - sort_workspaces(); + 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 { - auto cmp = [=](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_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(); - }; - - std::sort(workspaces_.begin(), workspaces_.end(), cmp); + 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); -} +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) @@ -256,11 +322,11 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & } /* Handle click events if configured */ - if (config_["on-click"].isString() || config_["on-click-middle"].isString() - || config_["on-click-right"].isString()) { + 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_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), + false); } button_.set_relief(Gtk::RELIEF_NONE); @@ -272,9 +338,7 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & } workspace_group.add_button(button_); - button_.show(); - label_.show(); - content_.show(); + button_.show_all(); } Workspace::~Workspace() { @@ -303,7 +367,7 @@ auto Workspace::handle_state(const std::vector &state) -> void { state_ |= (uint32_t)State::URGENT; break; case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN: - state_ |= (uint32_t)State::URGENT; + state_ |= (uint32_t)State::HIDDEN; break; } } @@ -315,7 +379,8 @@ auto Workspace::handle_remove() -> void { workspace_group_.remove_workspace(id_); } -auto add_or_remove_class(Glib::RefPtr context, bool condition, const std::string& class_name) { +auto add_or_remove_class(Glib::RefPtr context, bool condition, + const std::string &class_name) { if (condition) { context->add_class(class_name); } else { @@ -329,6 +394,16 @@ auto Workspace::handle_done() -> void { 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"); + + 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 { @@ -355,24 +430,20 @@ auto Workspace::get_icon() -> std::string { 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(); + 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; + 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 { + zext_workspace_handle_v1_activate(workspace_handle_); + } else if (action == "close") { + zext_workspace_handle_v1_remove(workspace_handle_); + } else { spdlog::warn("Unknown action {}", action); } @@ -381,16 +452,19 @@ auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { 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(); + workspace_group_.set_need_to_sort(); } name_ = name; } auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { if (coordinates_ != coordinates) { - workspace_group_.set_need_to_sort(); + workspace_group_.set_need_to_sort(); } coordinates_ = coordinates; } diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp index 1005d5c8..30c60413 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -1,6 +1,7 @@ #include "modules/wlr/workspace_manager_binding.hpp" #include +#include #include "client.hpp" #include "modules/wlr/workspace_manager.hpp" @@ -100,7 +101,7 @@ 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; ++i) { + for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { coords_vec.push_back(coords[i]); } @@ -111,7 +112,7 @@ void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspa struct wl_array *state) { std::vector state_vec; auto states = static_cast(state->data); - for (size_t i = 0; i < state->size; ++i) { + for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) { state_vec.push_back(states[i]); } From 98f7a10a51d6cecc1422edfcb0bc3886e0a1121a Mon Sep 17 00:00:00 2001 From: dmitry Date: Tue, 23 Nov 2021 03:18:00 +0300 Subject: [PATCH 48/78] Fix sort --- src/modules/wlr/workspace_manager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 9077dc87..bacef355 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -141,6 +141,7 @@ auto WorkspaceManager::update() -> void { } if (creation_delayed()) { creation_delayed_ = false; + sort_workspaces(); } AModule::update(); } @@ -283,7 +284,7 @@ auto WorkspaceGroup::handle_done() -> void { } if (!workspace_manager_.all_outputs()) { - //sort_workspaces(); + sort_workspaces(); } else { workspace_manager_.sort_workspaces(); } From c51a973d602fb448aa0e11e37ddd7f3df0d588ee Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 23 Nov 2021 10:10:14 +0100 Subject: [PATCH 49/78] Revert "dont escape essid in tooltip" --- src/modules/network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 020478a6..e7b20ab5 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -328,7 +328,7 @@ auto waybar::modules::Network::update() -> void { auto text = fmt::format( format_, - fmt::arg("essid", Glib::Markup::escape_text(essid_)), + fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("ifname", ifname_), @@ -779,7 +779,7 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss) { auto essid_end = essid_begin + ies[1]; std::string essid_raw; std::copy(essid_begin, essid_end, std::back_inserter(essid_raw)); - essid_ = essid_raw; + essid_ = Glib::Markup::escape_text(essid_raw); } } } From 2290fe10aa35a2c32953361df81a4618d55ee148 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Nov 2021 08:46:58 -0800 Subject: [PATCH 50/78] fix(bar): handle ipc connection errors. Try to use the default bar id (`bar-0`) if none is set. --- src/bar.cpp | 10 +++++++++- src/modules/sway/bar.cpp | 14 ++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 8ad81e9c..26d64ec6 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -61,6 +61,7 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // const std::string_view Bar::MODE_DEFAULT = "default"; const std::string_view Bar::MODE_INVISIBLE = "invisible"; +const std::string_view DEFAULT_BAR_ID = "bar-0"; #ifdef HAVE_GTK_LAYER_SHELL struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { @@ -556,7 +557,14 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) if (auto id = config["id"]; id.isString()) { bar_id = id.asString(); } - _ipc_client = std::make_unique(*this); + if (bar_id.empty()) { + bar_id = DEFAULT_BAR_ID; + } + try { + _ipc_client = std::make_unique(*this); + } catch (const std::exception& exc) { + spdlog::warn("Failed to open bar ipc connection: {}", exc.what()); + } } #endif diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index 17382e3b..78e524ac 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "bar.hpp" #include "modules/sway/ipc/ipc.hpp" @@ -47,13 +49,13 @@ struct swaybar_config parseConfig(const Json::Value& payload) { } void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) { - try { - auto payload = parser_.parse(res.payload); - auto config = parseConfig(payload); - onConfigUpdate(config); - } catch (const std::exception& e) { - spdlog::error("BarIpcClient::onInitialConfig {}", e.what()); + auto payload = parser_.parse(res.payload); + if (auto success = payload.get("success", true); !success.asBool()) { + auto err = payload.get("error", "Unknown error"); + throw std::runtime_error(err.asString()); } + auto config = parseConfig(payload); + onConfigUpdate(config); } void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { From 0472d279e4ecb1ffb5d44e763f7b6a19b3d5e079 Mon Sep 17 00:00:00 2001 From: kraftwerk28 Date: Tue, 23 Nov 2021 14:15:55 +0200 Subject: [PATCH 51/78] Add {flag} format replacement --- include/modules/sway/language.hpp | 8 +++++++- src/modules/sway/language.cpp | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 1faf52b3..19441668 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -32,6 +32,12 @@ class Language : public ALabel, public sigc::trackable { std::string short_name; std::string variant; std::string short_description; + std::string country_flag() const { + static std::string result = "\xf0\x9f\x87\xff\xf0\x9f\x87\xff"; + result[3] = short_name[0] - 0xbb; + result[7] = short_name[1] - 0xbb; + return result; + } }; class XKBContext { @@ -54,7 +60,7 @@ class Language : public ALabel, public sigc::trackable { const static std::string XKB_LAYOUT_NAMES_KEY; const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; - + Layout layout_; std::string tooltip_format_ = ""; std::map layouts_map_; diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 186fa4bb..bdd4b998 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -99,7 +99,8 @@ auto Language::update() -> void { fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), - fmt::arg("variant", layout_.variant))); + fmt::arg("variant", layout_.variant), + fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { From 02560a653776db9c43bb0dbdbfa998e716a6054a Mon Sep 17 00:00:00 2001 From: kraftwerk28 Date: Wed, 25 Aug 2021 23:18:56 +0300 Subject: [PATCH 52/78] Update manpage --- man/waybar-sway-language.5.scd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd index 92a647e6..1c88314c 100644 --- a/man/waybar-sway-language.5.scd +++ b/man/waybar-sway-language.5.scd @@ -37,6 +37,8 @@ Addressed by *sway/language* *{variant}*: Variant of layout (e.g. "dvorak"). +*{flag}*: Country flag of layout. + # EXAMPLES ``` From 59040c53e4ea73bfa654fb28ff8e4644595fa1ab Mon Sep 17 00:00:00 2001 From: kraftwerk28 Date: Wed, 25 Aug 2021 23:26:04 +0300 Subject: [PATCH 53/78] Move definition to .cpp --- include/modules/sway/language.hpp | 7 +------ src/modules/sway/language.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 19441668..92e2bbaa 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -32,12 +32,7 @@ class Language : public ALabel, public sigc::trackable { std::string short_name; std::string variant; std::string short_description; - std::string country_flag() const { - static std::string result = "\xf0\x9f\x87\xff\xf0\x9f\x87\xff"; - result[3] = short_name[0] - 0xbb; - result[7] = short_name[1] - 0xbb; - return result; - } + std::string country_flag() const; }; class XKBContext { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index bdd4b998..1b12ab05 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -213,4 +213,11 @@ Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); delete layout_; } + +std::string Language::Layout::country_flag() const { + static std::string result = "\xf0\x9f\x87\xff\xf0\x9f\x87\xff"; + result[3] = short_name[0] - 0xbb; + result[7] = short_name[1] - 0xbb; + return result; +} } // namespace waybar::modules::sway From 89afa8e149e3ed7d45ef8a93fb1505bf01cd6a17 Mon Sep 17 00:00:00 2001 From: kraftwerk28 Date: Wed, 24 Nov 2021 02:13:40 +0200 Subject: [PATCH 54/78] Checking if emoji byte doesn't get out of bounds --- src/modules/sway/language.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 1b12ab05..73a64c3b 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -108,7 +108,8 @@ auto Language::update() -> void { fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), - fmt::arg("variant", layout_.variant))); + fmt::arg("variant", layout_.variant), + fmt::arg("flag", layout_.country_flag()))); label_.set_tooltip_markup(tooltip_display_layout); } else { label_.set_tooltip_markup(display_layout); @@ -215,9 +216,13 @@ Language::XKBContext::~XKBContext() { } std::string Language::Layout::country_flag() const { - static std::string result = "\xf0\x9f\x87\xff\xf0\x9f\x87\xff"; - result[3] = short_name[0] - 0xbb; - result[7] = short_name[1] - 0xbb; - return result; + if (short_name.size() != 2) return ""; + unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00"; + result[3] = short_name[0] + 0x45; + result[7] = short_name[1] + 0x45; + // Check if both emojis are in A-Z symbol bounds + if (result[3] < 0xa6 || result[3] > 0xbf) return ""; + if (result[7] < 0xa6 || result[7] > 0xbf) return ""; + return std::string{reinterpret_cast(result)}; } } // namespace waybar::modules::sway From 8fe42ebd2eac1b6dbe79a18a5fe4ead9a78bac93 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Nov 2021 19:18:24 -0800 Subject: [PATCH 55/78] doc: update `exclusive` and `passthrough` defaults --- man/waybar.5.scd.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index bfc9ab87..92d0774a 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -79,12 +79,12 @@ Also a minimal example configuration can be found on the at the bottom of this m *exclusive* ++ typeof: bool ++ - default: *true* unless the layer is set to *overlay* ++ + default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. *passthrough* ++ typeof: bool ++ - default: *false* unless the layer is set to *overlay* ++ + default: *false* ++ Option to pass any pointer events to the window under the bar. Intended to be used with either *top* or *overlay* layers and without exclusive zone. From b4e19678b7f0a408c80d1ef89e4eb2d8e2adbf36 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Nov 2021 19:48:31 -0800 Subject: [PATCH 56/78] ci: increase FreeBSD VM memory to 2048MB Intermittent CI failures without any useful diagnostics could be caused by the OOM killer. 1024MB is not really enough to run 3 parallel jobs with a modern C++ compiler. --- .github/workflows/freebsd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 03e5d707..d5064fe4 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -13,6 +13,7 @@ jobs: - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 with: + mem: 2048 usesh: true prepare: | export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio From 0c18e57937376b924203e6dc526fad6fa6f93fe4 Mon Sep 17 00:00:00 2001 From: Nicolas Joyard Date: Sun, 31 Oct 2021 23:55:13 +0100 Subject: [PATCH 57/78] add group feature --- include/bar.hpp | 3 ++- include/group.hpp | 21 ++++++++++++++++++ man/waybar.5.scd.in | 22 +++++++++++++++++++ meson.build | 1 + src/bar.cpp | 53 +++++++++++++++++++++++++-------------------- src/group.cpp | 19 ++++++++++++++++ 6 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 include/group.hpp create mode 100644 src/group.cpp diff --git a/include/bar.hpp b/include/bar.hpp index 6f3dfcf9..4bd5ef39 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -73,7 +73,7 @@ class Bar { private: void onMap(GdkEventAny *); auto setupWidgets() -> void; - void getModules(const Factory &, const std::string &); + void getModules(const Factory &, const std::string &, Gtk::Box*); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); @@ -86,6 +86,7 @@ class Bar { std::vector> modules_left_; std::vector> modules_center_; std::vector> modules_right_; + std::vector> modules_all_; }; } // namespace waybar diff --git a/include/group.hpp b/include/group.hpp new file mode 100644 index 00000000..f282f9c5 --- /dev/null +++ b/include/group.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include "AModule.hpp" +#include "bar.hpp" +#include "factory.hpp" + +namespace waybar { + +class Group : public AModule { + public: + Group(const std::string&, const Bar&, const Json::Value&); + ~Group() = default; + auto update() -> void; + operator Gtk::Widget &(); + Gtk::Box box; +}; + +} // namespace waybar diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 66d5b2eb..f374953c 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -203,6 +203,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's Valid options for the "rotate" property are: 0, 90, 180 and 270. +## Grouping modules + +Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. + +A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example: + +``` +{ + "modules-right": ["group/hardware", "clock"], + + "group/hardware": { + "modules": [ + "cpu", + "memory", + "battery" + ] + }, + + ... +} +``` + # SUPPORTED MODULES - *waybar-backlight(5)* diff --git a/meson.build b/meson.build index 62ac8e36..c499c689 100644 --- a/meson.build +++ b/meson.build @@ -150,6 +150,7 @@ src_files = files( 'src/bar.cpp', 'src/client.cpp', 'src/config.cpp', + 'src/group.cpp', 'src/util/ustring_clen.cpp' ) diff --git a/src/bar.cpp b/src/bar.cpp index a8b230e1..dee81a30 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -9,6 +9,7 @@ #include "bar.hpp" #include "client.hpp" #include "factory.hpp" +#include "group.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" namespace waybar { @@ -594,19 +595,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { } void waybar::Bar::handleSignal(int signal) { - for (auto& module : modules_left_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } - } - for (auto& module : modules_center_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } - } - for (auto& module : modules_right_) { + for (auto& module : modules_all_) { auto* custom = dynamic_cast(module.get()); if (custom != nullptr) { custom->refresh(signal); @@ -614,19 +603,35 @@ void waybar::Bar::handleSignal(int signal) { } } -void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { - if (config[pos].isArray()) { - for (const auto& name : config[pos]) { +void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) { + auto module_list = group ? config[pos]["modules"] : config[pos]; + if (module_list.isArray()) { + for (const auto& name : module_list) { try { - auto module = factory.makeModule(name.asString()); - if (pos == "modules-left") { - modules_left_.emplace_back(module); + auto ref = name.asString(); + AModule* module; + + if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { + auto group_module = new waybar::Group(ref, *this, config[ref]); + getModules(factory, ref, &group_module->box); + module = group_module; + } else { + module = factory.makeModule(ref); } - if (pos == "modules-center") { - modules_center_.emplace_back(module); - } - if (pos == "modules-right") { - modules_right_.emplace_back(module); + + modules_all_.emplace_back(module); + if (group) { + group->pack_start(*module, false, false); + } else { + if (pos == "modules-left") { + modules_left_.emplace_back(module); + } + if (pos == "modules-center") { + modules_center_.emplace_back(module); + } + if (pos == "modules-right") { + modules_right_.emplace_back(module); + } } module->dp.connect([module, &name] { try { diff --git a/src/group.cpp b/src/group.cpp new file mode 100644 index 00000000..9d2188cc --- /dev/null +++ b/src/group.cpp @@ -0,0 +1,19 @@ +#include "group.hpp" +#include +#include + +namespace waybar { + +Group::Group(const std::string& name, const Bar& bar, const Json::Value& config) + : AModule(config, name, "", false, false), + box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0} + { +} + +auto Group::update() -> void { + // noop +} + +Group::operator Gtk::Widget&() { return box; } + +} // namespace waybar From d5112678c357818ac8a357a5d366083cd78ebaee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlys=20Bras=20de=20fer?= Date: Sat, 27 Nov 2021 23:07:26 +0100 Subject: [PATCH 58/78] mediaplayer.py: Exit properly on SIGPIPE --- resources/custom_modules/mediaplayer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index fa9aa58b..1630d97c 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -110,6 +110,7 @@ def main(): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) for player in manager.props.player_names: if arguments.player is not None and arguments.player != player.name: From 3c2fa1625d1fc0c7399219ef1ba2af771b5d0bf6 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 28 Nov 2021 01:12:35 +0300 Subject: [PATCH 59/78] Finish --- meson.build | 5 +++++ meson_options.txt | 1 + protocol/meson.build | 5 ++++- src/factory.cpp | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 62ac8e36..52c9d292 100644 --- a/meson.build +++ b/meson.build @@ -257,6 +257,10 @@ else src_files += 'src/modules/simpleclock.cpp' endif +if get_option('experimental') + add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') +endif + subdir('protocol') executable( @@ -383,3 +387,4 @@ if clangtidy.found() '-p', meson.build_root() ] + src_files) endif + diff --git a/meson_options.txt b/meson_options.txt index 81e44689..f4f60d09 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -11,3 +11,4 @@ option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk- option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') +option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') diff --git a/protocol/meson.build b/protocol/meson.build index 6e82d63d..8aed83df 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,11 +27,14 @@ 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'], - ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ] +if get_option('experimental') + client_protocols += ['ext-workspace-unstable-v1.xml'] +endif + client_protos_src = [] client_protos_headers = [] diff --git a/src/factory.cpp b/src/factory.cpp index a577751a..351aa332 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -30,10 +30,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "wlr/taskbar") { return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } +#ifdef USE_EXPERIMENTAL if (ref == "wlr/workspaces") { return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); } #endif +#endif #ifdef HAVE_RIVER if (ref == "river/tags") { return new waybar::modules::river::Tags(id, bar_, config_[name]); From 2fb671f5fa1aab8fc52aa0208b586cd0824cada2 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 28 Nov 2021 01:19:21 +0300 Subject: [PATCH 60/78] Revert protocol build --- protocol/meson.build | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/protocol/meson.build b/protocol/meson.build index 8aed83df..6e82d63d 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,14 +27,11 @@ 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'], + ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ] -if get_option('experimental') - client_protocols += ['ext-workspace-unstable-v1.xml'] -endif - client_protos_src = [] client_protos_headers = [] From 4b5dc1bb3a5cda181bc0589992fb13076e3ad2b3 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 28 Nov 2021 09:52:18 -0800 Subject: [PATCH 61/78] test: count copies and moves done by SafeSignal --- test/GlibTestsFixture.hpp | 5 +++ test/SafeSignal.cpp | 84 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/test/GlibTestsFixture.hpp b/test/GlibTestsFixture.hpp index ffe6fd31..a21c8e07 100644 --- a/test/GlibTestsFixture.hpp +++ b/test/GlibTestsFixture.hpp @@ -7,6 +7,11 @@ class GlibTestsFixture : public sigc::trackable { public: GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} + void setTimeout(int timeout) { + Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, + timeout); + } + void run(std::function fn) { Glib::signal_idle().connect_once(fn); main_loop_->run(); diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp index b07e9cab..2c67317b 100644 --- a/test/SafeSignal.cpp +++ b/test/SafeSignal.cpp @@ -5,10 +5,15 @@ #include #include +#include #include "GlibTestsFixture.hpp" using namespace waybar; + +template +using remove_cvref_t = typename std::remove_cv::type>::type; + /** * Basic sanity test for SafeSignal: * check that type deduction works, events are delivered and the order is right @@ -25,7 +30,7 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][t std::thread producer; // timeout the test in 500ms - Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, 500); + setTimeout(500); test_signal.connect([&](auto val, auto str) { static_assert(std::is_same::value); @@ -57,6 +62,83 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][t REQUIRE(count == NUM_EVENTS); } +template +struct TestObject { + T value; + unsigned copied = 0; + unsigned moved = 0; + + TestObject(const T& v) : value(v){}; + ~TestObject() = default; + + TestObject(const TestObject& other) + : value(other.value), copied(other.copied + 1), moved(other.moved) {} + + TestObject(TestObject&& other) noexcept + : value(std::move(other.value)), + copied(std::exchange(other.copied, 0)), + moved(std::exchange(other.moved, 0) + 1) {} + + TestObject& operator=(const TestObject& other) { + value = other.value; + copied = other.copied + 1; + moved = other.moved; + return *this; + } + + TestObject& operator=(TestObject&& other) noexcept { + value = std::move(other.value); + copied = std::exchange(other.copied, 0); + moved = std::exchange(other.moved, 0) + 1; + return *this; + } + + bool operator==(T other) const { return value == other; } + operator T() const { return value; } +}; + +/* + * Check the number of copies/moves performed on the object passed through SafeSignal + */ +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") { + const int NUM_EVENTS = 3; + int count = 0; + + SafeSignal> test_signal; + + std::thread producer; + + // timeout the test in 500ms + setTimeout(500); + + test_signal.connect([&](auto& val) { + static_assert(std::is_same, remove_cvref_t>::value); + + /* explicit move in the producer thread */ + REQUIRE(val.moved <= 1); + /* copy within the SafeSignal queuing code */ + REQUIRE(val.copied <= 1); + + if (++count >= NUM_EVENTS) { + this->quit(); + }; + }); + + run([&]() { + test_signal.emit(1); + REQUIRE(count == 1); + producer = std::thread([&]() { + for (auto i = 2; i <= NUM_EVENTS; ++i) { + TestObject t{i}; + // check that signal.emit accepts moved objects + test_signal.emit(std::move(t)); + } + }); + }); + producer.join(); + REQUIRE(count == NUM_EVENTS); +} + int main(int argc, char* argv[]) { Glib::init(); return Catch::Session().run(argc, argv); From cf5ddb2a5e25121cb176c1566e89184c810092e3 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 28 Nov 2021 11:34:21 -0800 Subject: [PATCH 62/78] fix(swaybar-ipc): avoid unnecessary copy of struct swaybar_config --- src/modules/sway/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index 78e524ac..6ad74af1 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -71,7 +71,7 @@ void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { } else { // configuration update auto config = parseConfig(payload); - signal_config_(config); + signal_config_(std::move(config)); } } catch (const std::exception& e) { spdlog::error("BarIpcClient::onEvent {}", e.what()); From b6d0a4b63fb298fe4df24a7679b683b65d64b727 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 28 Nov 2021 12:19:45 -0800 Subject: [PATCH 63/78] feat(bar): allow customization of bar modes Allow changing existing modes and adding new ones via `modes` configuration key. `modes` accepts a JSON object roughly described by the following type ```typescript type BarMode = { layer: 'bottom' | 'top' | 'overlay'; exclusive: bool; passthrough: bool; visible: bool; }; type BarModeList = { [name: string]: BarMode; }; ``` and will be merged with the default modes defined in `bar.cpp`. Note that with absence of other ways to set mode, only those defined in the `sway-bar(5)`[1] documentation could be used right now. [1]: https://github.com/swaywm/sway/blob/master/sway/sway-bar.5.scd --- src/bar.cpp | 62 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 26d64ec6..789bea95 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -63,6 +63,48 @@ const std::string_view Bar::MODE_DEFAULT = "default"; const std::string_view Bar::MODE_INVISIBLE = "invisible"; const std::string_view DEFAULT_BAR_ID = "bar-0"; +/* Deserializer for enum bar_layer */ +void from_json(const Json::Value& j, bar_layer& l) { + if (j == "bottom") { + l = bar_layer::BOTTOM; + } else if (j == "top") { + l = bar_layer::TOP; + } else if (j == "overlay") { + l = bar_layer::OVERLAY; + } +} + +/* Deserializer for struct bar_mode */ +void from_json(const Json::Value& j, bar_mode& m) { + if (j.isObject()) { + if (auto v = j["layer"]; v.isString()) { + from_json(v, m.layer); + } + if (auto v = j["exclusive"]; v.isBool()) { + m.exclusive = v.asBool(); + } + if (auto v = j["passthrough"]; v.isBool()) { + m.passthrough = v.asBool(); + } + if (auto v = j["visible"]; v.isBool()) { + m.visible = v.asBool(); + } + } +} + +/* Deserializer for JSON Object -> map + * Assumes that all the values in the object are deserializable to the same type. + */ +template ::value>> +void from_json(const Json::Value& j, std::map& m) { + if (j.isObject()) { + for (auto it = j.begin(); it != j.end(); ++it) { + from_json(*it, m[it.key().asString()]); + } + } +} + #ifdef HAVE_GTK_LAYER_SHELL struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { @@ -527,23 +569,15 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_->setPosition(position); surface_impl_->setSize(width, height); - /* Init "default" mode from globals */ - auto& default_mode = configured_modes[MODE_DEFAULT]; - if (config["layer"] == "top") { - default_mode.layer = bar_layer::TOP; - } else if (config["layer"] == "overlay") { - default_mode.layer = bar_layer::OVERLAY; + /* Read custom modes if available */ + if (auto modes = config.get("modes", {}); modes.isObject()) { + from_json(modes, configured_modes); } - if (config["exclusive"].isBool()) { - default_mode.exclusive = config["exclusive"].asBool(); - } + /* Update "default" mode with the global bar options */ + from_json(config, configured_modes[MODE_DEFAULT]); - if (config["passthrough"].isBool()) { - default_mode.passthrough = config["passthrough"].asBool(); - } - - if (auto mode = config["mode"]; mode.isString()) { + if (auto mode = config.get("mode", {}); mode.isString()) { setMode(config["mode"].asString()); } else { setMode(MODE_DEFAULT); From 9dac851f6d0e63e4315c7a8447a4b956353a996b Mon Sep 17 00:00:00 2001 From: Patrick Nicolas Date: Tue, 30 Nov 2021 16:31:11 +0100 Subject: [PATCH 64/78] Allow sink in addition to source for pulse icon --- src/modules/pulseaudio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index cf427800..8f2b25de 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -211,7 +211,7 @@ static const std::array ports = { }; const std::vector waybar::modules::Pulseaudio::getPulseIcon() const { - std::vector res = {default_source_name_}; + std::vector res = {current_sink_name_, default_source_name_}; std::string nameLC = port_name_ + form_factor_; std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { From 2240c79b1a79546b5e6126d4218ddbfaa8613ac5 Mon Sep 17 00:00:00 2001 From: Brent George Date: Wed, 1 Dec 2021 18:24:35 -0700 Subject: [PATCH 65/78] Adjust max wifi strength that is possible --- src/modules/network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e7b20ab5..b86989f3 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -789,8 +789,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; - // WiFi-hardware usually operates in the range -90 to -20dBm. - const int hardwareMax = -20; + // WiFi-hardware usually operates in the range -90 to -30dBm. + const int hardwareMax = -30; const int hardwareMin = -90; const int strength = ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; From 5cbbd65ac4d64c60f80c994bad54e18d32da22e1 Mon Sep 17 00:00:00 2001 From: 187Qrly <56300140+187Qrly@users.noreply.github.com> Date: Fri, 3 Dec 2021 20:11:25 +0100 Subject: [PATCH 66/78] Add Gentoo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98b99a2d..587a5540 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Highly customizable Wayland bar for Sway and Wlroots based compositors.
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or -[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
+[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* #### Current features From f573e32d0b788867a783e9b013b94a4f3029d83a Mon Sep 17 00:00:00 2001 From: John Fredriksson <94405030+jfred9@users.noreply.github.com> Date: Fri, 3 Dec 2021 23:56:51 +0100 Subject: [PATCH 67/78] bar: Fix crash when unplugging HDMI There is a double delete situation which causes a SIGSEGV to happen during destruction of bar. This was introduced by the group feature patch. The same object pointer is stored in two different vectors of unique_ptr element. Replace with shared_ptr to handle reference counting correctly and avoid double delete. --- include/bar.hpp | 8 ++++---- src/bar.cpp | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 4aa17c17..01a9d034 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -112,14 +112,14 @@ class Bar { Gtk::Box center_; Gtk::Box right_; Gtk::Box box_; - std::vector> modules_left_; - std::vector> modules_center_; - std::vector> modules_right_; + std::vector> modules_left_; + std::vector> modules_center_; + std::vector> modules_right_; #ifdef HAVE_SWAY using BarIpcClient = modules::sway::BarIpcClient; std::unique_ptr _ipc_client; #endif - std::vector> modules_all_; + std::vector> modules_all_; }; } // namespace waybar diff --git a/src/bar.cpp b/src/bar.cpp index fbd4623f..133c29aa 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -735,18 +735,19 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk module = factory.makeModule(ref); } - modules_all_.emplace_back(module); + std::shared_ptr module_sp(module); + modules_all_.emplace_back(module_sp); if (group) { group->pack_start(*module, false, false); } else { if (pos == "modules-left") { - modules_left_.emplace_back(module); + modules_left_.emplace_back(module_sp); } if (pos == "modules-center") { - modules_center_.emplace_back(module); + modules_center_.emplace_back(module_sp); } if (pos == "modules-right") { - modules_right_.emplace_back(module); + modules_right_.emplace_back(module_sp); } } module->dp.connect([module, &name] { From eae65099d0717a3dcf9d3e37dba52cf646d293fe Mon Sep 17 00:00:00 2001 From: Alexis Cellier Date: Fri, 3 Dec 2021 23:32:53 +0100 Subject: [PATCH 68/78] Add logind feature, with its 'inhibitor' module The logind feature adds a new inhibitor module which allows to acquire the inhibitor locks that logind presents. Signed-off-by: Alexis Cellier --- include/factory.hpp | 3 + include/modules/inhibitor.hpp | 27 ++++++ meson.build | 7 +- meson_options.txt | 1 + src/factory.cpp | 5 + src/modules/inhibitor.cpp | 175 ++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 include/modules/inhibitor.hpp create mode 100644 src/modules/inhibitor.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 43dd2cfd..3855ce22 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -51,6 +51,9 @@ #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" #endif +#ifdef HAVE_GIO_UNIX +#include "modules/inhibitor.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp new file mode 100644 index 00000000..aa2f97d4 --- /dev/null +++ b/include/modules/inhibitor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" + +namespace waybar::modules { + +class Inhibitor : public ALabel { + public: + Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); + ~Inhibitor() override; + auto update() -> void; + auto activated() -> bool; + + private: + auto handleToggle(::GdkEventButton* const& e) -> bool; + + const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; + const std::string inhibitors_; + int handle_ = -1; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 7f2d9562..5e62eecf 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) +giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) @@ -242,6 +242,11 @@ if libsndio.found() src_files += 'src/modules/sndio.cpp' endif +if (giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') + src_files += 'src/modules/inhibitor.cpp' +endif + if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') diff --git a/meson_options.txt b/meson_options.txt index f4f60d09..230a53d6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,5 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') +option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') diff --git a/src/factory.cpp b/src/factory.cpp index 351aa332..900653b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -94,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sndio") { return new waybar::modules::Sndio(id, config_[name]); } +#endif +#ifdef HAVE_GIO_UNIX + if (ref == "inhibitor") { + return new waybar::modules::Inhibitor(id, bar_, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp new file mode 100644 index 00000000..1e3f2d35 --- /dev/null +++ b/src/modules/inhibitor.cpp @@ -0,0 +1,175 @@ +#include "modules/inhibitor.hpp" + +#include +#include +#include + +namespace { + +using DBus = std::unique_ptr; + +auto dbus() -> DBus { + GError *error = nullptr; + GDBusConnection* connection = + g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + + if (error) { + spdlog::error("g_bus_get_sync() failed: {}", error->message); + g_error_free(error); + connection = nullptr; + } + + auto destructor = [](GDBusConnection* connection) { + GError *error = nullptr; + g_dbus_connection_close_sync(connection, nullptr, &error); + if (error) { + spdlog::error( + "g_bus_connection_close_sync failed(): {}", + error->message); + g_error_free(error); + } + }; + + return DBus{connection, destructor}; +} + +auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { + GError *error = nullptr; + GUnixFDList* fd_list; + int handle; + + auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit", + g_variant_new( + "(ssss)", + inhibitors.c_str(), + "waybar", + "Asked by user", + "block"), + G_VARIANT_TYPE("(h)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &fd_list, + nullptr, + &error); + if (error) { + spdlog::error( + "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", + error->message); + g_error_free(error); + handle = -1; + } else { + gint index; + g_variant_get(reply, "(h)", &index); + g_variant_unref(reply); + handle = g_unix_fd_list_get(fd_list, index, nullptr); + g_object_unref(fd_list); + } + + return handle; +} + +auto checkInhibitor(const std::string& inhibitor) -> const std::string& { + static const auto inhibitors = std::array{ + "idle", + "shutdown", + "sleep", + "handle-power-key", + "handle-suspend-key", + "handle-hibernate-key", + "handle-lid-switch" + }; + + if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) + == inhibitors.end()) { + throw std::runtime_error("invalid logind inhibitor " + inhibitor); + } + + return inhibitor; +} + +auto getInhibitors(const Json::Value& config) -> std::string { + std::string inhibitors = "idle"; + + if (config["what"].empty()) { + return inhibitors; + } + + if (config["what"].isString()) { + return checkInhibitor(config["what"].asString()); + } + + if (config["what"].isArray()) { + inhibitors = checkInhibitor(config["what"][0].asString()); + for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { + inhibitors += ":" + checkInhibitor(config["what"][i].asString()); + } + return inhibitors; + } + + return inhibitors; +} + +} + +namespace waybar::modules { + +Inhibitor::Inhibitor(const std::string& id, const Bar& bar, + const Json::Value& config) + : ALabel(config, "inhibitor", id, "{status}", true), + dbus_(::dbus()), + inhibitors_(::getInhibitors(config)) { + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &Inhibitor::handleToggle)); + dp.emit(); +} + +Inhibitor::~Inhibitor() { + if (handle_ != -1) { + ::close(handle_); + } +} + +auto Inhibitor::activated() -> bool { + return handle_ != -1; +} + +auto Inhibitor::update() -> void { + std::string status_text = activated() ? "activated" : "deactivated"; + + label_.get_style_context()->remove_class( + activated() ? "deactivated" : "activated"); + label_.set_markup( + fmt::format(format_, fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); + label_.get_style_context()->add_class(status_text); + + if (tooltipEnabled()) { + label_.set_tooltip_text(status_text); + } + + return ALabel::update(); +} + +auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { + if (e->button == 1) { + if (activated()) { + ::close(handle_); + handle_ = -1; + } else { + handle_ = ::getLocks(dbus_, inhibitors_); + if (handle_ == -1) { + spdlog::error("cannot get inhibitor locks"); + } + } + } + + return ALabel::handleToggle(e); +} + +} // waybar::modules From 795246263f80b61b28925383eb36b00b8b8d79f0 Mon Sep 17 00:00:00 2001 From: Alexis Cellier Date: Tue, 14 Dec 2021 08:15:44 +0100 Subject: [PATCH 69/78] man: Add waybar-inhibitor man page --- man/waybar-inhibitor.5.scd | 92 ++++++++++++++++++++++++++++++++++++++ meson.build | 4 ++ 2 files changed, 96 insertions(+) create mode 100644 man/waybar-inhibitor.5.scd diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd new file mode 100644 index 00000000..0838f4d6 --- /dev/null +++ b/man/waybar-inhibitor.5.scd @@ -0,0 +1,92 @@ +waybar-inhibitor(5) + +# NAME + +waybar - inhibitor module + +# DESCRIPTION + +The *inhibitor* module allows to take an inhibitor lock that logind provides. +See *systemd-inhibit*(1) for more information. + +# CONFIGURATION + +*what*: ++ + typeof: string or array ++ + The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*. + +*format*: ++ + typeof: string ++ + The format, how the state should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the current state, the corresponding icon gets selected. + +*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. A click also toggles the state + +*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. + +*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. + +# FORMAT REPLACEMENTS + +*{status}*: status (*activated* or *deactivated*) + +*{icon}*: Icon, as defined in *format-icons* + +# EXAMPLES + +``` +"inhibitor": { + "what": "handle-lid-switch", + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + } +} +``` diff --git a/meson.build b/meson.build index 5e62eecf..cfe2d513 100644 --- a/meson.build +++ b/meson.build @@ -352,6 +352,10 @@ if scdoc.found() 'waybar-sndio.5.scd', ] + if (giounix.found() and not get_option('logind').disabled()) + man_files += 'waybar-inhibitor.5.scd' + endif + foreach file : man_files path = '@0@'.format(file) basename = path.split('/')[-1] From d1f8b42d229aa4a505ece5ef898c6f60749856e2 Mon Sep 17 00:00:00 2001 From: Brent George Date: Tue, 14 Dec 2021 11:28:13 -0700 Subject: [PATCH 70/78] add Debian build dependency to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 587a5540..c5806e3b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] libsndio [sndio module] libevdev [KeyboardState module] +xkbregistry ``` **Build dependencies** @@ -101,7 +102,8 @@ sudo apt install \ libsigc++-2.0-dev \ libspdlog-dev \ libwayland-dev \ - scdoc + scdoc \ + libxkbregistry-dev ``` From 9bc86347be53023987a9c331ec113cdec1f189b6 Mon Sep 17 00:00:00 2001 From: Brent George Date: Tue, 14 Dec 2021 11:34:15 -0700 Subject: [PATCH 71/78] change signal strength to penalize overly strong signals --- src/modules/network.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index b86989f3..56cea04b 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -788,13 +788,16 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; - // WiFi-hardware usually operates in the range -90 to -30dBm. - const int hardwareMax = -30; + + // If a signal is too strong, it can overwhelm receiving circuity that is designed + // to pick up and process a certain signal level. The following percentage is scaled to + // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm). + const int hardwareOptimum = -45; const int hardwareMin = -90; const int strength = - ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; - signal_strength_ = std::clamp(strength, 0, 100); + 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); + signal_strength_ = std::clamp(strength, 0, 100); } if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); From 3218612d3bc7ab12a81fbe97e85d7259e8027312 Mon Sep 17 00:00:00 2001 From: Brent George Date: Tue, 14 Dec 2021 11:36:46 -0700 Subject: [PATCH 72/78] change frequency to GHz --- include/modules/network.hpp | 2 +- src/modules/network.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index c91b598b..f7f43fb0 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -73,7 +73,7 @@ class Network : public ALabel { int cidr_; int32_t signal_strength_dbm_; uint8_t signal_strength_; - uint32_t frequency_; + float frequency_; uint32_t route_priority; util::SleeperThread thread_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 56cea04b..88e53382 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -88,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_WLAN}, #endif - frequency_(0) { + frequency_(0.0) { // Start with some "text" in the module's label_, update() will then // update it. Since the text should be different, update() will be able @@ -336,7 +336,7 @@ auto waybar::modules::Network::update() -> void { fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), @@ -365,7 +365,7 @@ auto waybar::modules::Network::update() -> void { fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -403,7 +403,7 @@ void waybar::modules::Network::clearIface() { cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; - frequency_ = 0; + frequency_ = 0.0; } int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { @@ -470,7 +470,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->essid_.clear(); net->signal_strength_dbm_ = 0; net->signal_strength_ = 0; - net->frequency_ = 0; + net->frequency_ = 0.0; } } net->carrier_ = carrier.value(); @@ -806,8 +806,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { void waybar::modules::Network::parseFreq(struct nlattr **bss) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) { - // in MHz - frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + // in GHz + frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; } } From 13d25d403e3823fed06ec76f629c1dc47e4d6b51 Mon Sep 17 00:00:00 2001 From: Brent George Date: Tue, 14 Dec 2021 11:37:39 -0700 Subject: [PATCH 73/78] add to network module - signalStrengthApp shows what applications can run at current signal strength --- include/modules/network.hpp | 1 + src/modules/network.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index f7f43fb0..7b8281b1 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -73,6 +73,7 @@ class Network : public ALabel { int cidr_; int32_t signal_strength_dbm_; uint8_t signal_strength_; + std::string signal_strength_app_; float frequency_; uint32_t route_priority; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 88e53382..4825a099 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,6 +331,7 @@ auto waybar::modules::Network::update() -> void { fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), + fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), @@ -360,6 +361,7 @@ auto waybar::modules::Network::update() -> void { fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), + fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), @@ -403,6 +405,7 @@ void waybar::modules::Network::clearIface() { cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; + signal_strength_app_.clear(); frequency_ = 0.0; } @@ -470,6 +473,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->essid_.clear(); net->signal_strength_dbm_ = 0; net->signal_strength_ = 0; + net->signal_strength_app_.clear(); net->frequency_ = 0.0; } } @@ -798,6 +802,20 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { const int strength = 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); signal_strength_ = std::clamp(strength, 0, 100); + + if (signal_strength_dbm_ >= -50) { + signal_strength_app_ = "Great Connectivity"; + } else if (signal_strength_dbm_ >= -60) { + signal_strength_app_ = "Good Connectivity"; + } else if (signal_strength_dbm_ >= -67) { + signal_strength_app_ = "Streaming"; + } else if (signal_strength_dbm_ >= -70) { + signal_strength_app_ = "Web Surfing"; + } else if (signal_strength_dbm_ >= -80) { + signal_strength_app_ = "Basic Connectivity"; + } else { + signal_strength_app_ = "Poor Connectivity"; + } } if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); From 548bd2ab1a834466e9d95560fd6ce1d6a2a398ba Mon Sep 17 00:00:00 2001 From: ilkecan Date: Tue, 28 Dec 2021 15:57:10 +0300 Subject: [PATCH 74/78] Add `fixed-center` option Resolves #957 --- man/waybar.5.scd.in | 6 ++++++ src/bar.cpp | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index c42f6eb8..249ca7e4 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -82,6 +82,12 @@ Also a minimal example configuration can be found on the at the bottom of this m default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. +*fixed-center* ++ + typeof: bool ++ + default: *true* + Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space. + When false, the center block is centered in the space between the left and right block. + *passthrough* ++ typeof: bool ++ default: *false* ++ diff --git a/src/bar.cpp b/src/bar.cpp index 133c29aa..9685c7a9 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -767,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk auto waybar::Bar::setupWidgets() -> void { window.add(box_); box_.pack_start(left_, false, false); - box_.set_center_widget(center_); + if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { + box_.set_center_widget(center_); + } else { + box_.pack_start(center_, true, false); + } box_.pack_end(right_, false, false); // Convert to button code for every module that is used. From 5a4f7a70ef55914cbd1ff8332ef624e8df14d412 Mon Sep 17 00:00:00 2001 From: Yura Shatunov <46374280+volucris1@users.noreply.github.com> Date: Tue, 4 Jan 2022 14:53:59 +0700 Subject: [PATCH 75/78] fix indent --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index cfe2d513..2c434266 100644 --- a/meson.build +++ b/meson.build @@ -295,7 +295,7 @@ executable( gtk_layer_shell, libsndio, tz_dep, - xkbregistry + xkbregistry ], include_directories: [include_directories('include')], install: true, From 1e560cf0c9ac11d6509fb9b7635788c3387e1e00 Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 5 Jan 2022 14:47:44 +0300 Subject: [PATCH 76/78] merge --- .github/workflows/freebsd.yml | 1 + README.md | 2 +- include/bar.hpp | 50 ++++- include/client.hpp | 1 + include/factory.hpp | 3 + include/group.hpp | 21 ++ include/modules/clock.hpp | 11 +- include/modules/inhibitor.hpp | 27 +++ include/modules/sway/bar.hpp | 49 +++++ include/modules/sway/language.hpp | 3 +- include/util/SafeSignal.hpp | 75 ++++++++ man/waybar-inhibitor.5.scd | 92 +++++++++ man/waybar-sway-language.5.scd | 2 + man/waybar.5.scd.in | 41 +++- meson.build | 18 +- meson_options.txt | 2 + resources/custom_modules/mediaplayer.py | 1 + src/bar.cpp | 242 ++++++++++++++++++------ src/client.cpp | 1 - src/factory.cpp | 7 + src/group.cpp | 19 ++ src/modules/clock.cpp | 110 +++++++---- src/modules/inhibitor.cpp | 175 +++++++++++++++++ src/modules/network.cpp | 4 +- src/modules/pulseaudio.cpp | 9 +- src/modules/sway/bar.cpp | 107 +++++++++++ src/modules/sway/language.cpp | 17 +- test/GlibTestsFixture.hpp | 24 +++ test/SafeSignal.cpp | 145 ++++++++++++++ test/meson.build | 14 ++ 30 files changed, 1149 insertions(+), 124 deletions(-) create mode 100644 include/group.hpp create mode 100644 include/modules/inhibitor.hpp create mode 100644 include/modules/sway/bar.hpp create mode 100644 include/util/SafeSignal.hpp create mode 100644 man/waybar-inhibitor.5.scd create mode 100644 src/group.cpp create mode 100644 src/modules/inhibitor.cpp create mode 100644 src/modules/sway/bar.cpp create mode 100644 test/GlibTestsFixture.hpp create mode 100644 test/SafeSignal.cpp diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 03e5d707..d5064fe4 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -13,6 +13,7 @@ jobs: - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 with: + mem: 2048 usesh: true prepare: | export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio diff --git a/README.md b/README.md index 98b99a2d..587a5540 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Highly customizable Wayland bar for Sway and Wlroots based compositors.
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or -[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
+[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* #### Current features diff --git a/include/bar.hpp b/include/bar.hpp index 6f3dfcf9..01a9d034 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include "AModule.hpp" #include "xdg-output-unstable-v1-client-protocol.h" @@ -36,6 +39,19 @@ struct bar_margins { int left = 0; }; +struct bar_mode { + bar_layer layer; + bool exclusive; + bool passthrough; + bool visible; +}; + +#ifdef HAVE_SWAY +namespace modules::sway { +class BarIpcClient; +} +#endif // HAVE_SWAY + class BarSurface { protected: BarSurface() = default; @@ -54,38 +70,56 @@ class BarSurface { class Bar { public: + using bar_mode_map = std::map; + static const bar_mode_map PRESET_MODES; + static const std::string_view MODE_DEFAULT; + static const std::string_view MODE_INVISIBLE; + Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; - ~Bar() = default; + ~Bar(); + void setMode(const std::string_view &); void setVisible(bool visible); void toggle(); void handleSignal(int); struct waybar_output *output; Json::Value config; - struct wl_surface * surface; - bool exclusive = true; + struct wl_surface *surface; bool visible = true; bool vertical = false; Gtk::Window window; +#ifdef HAVE_SWAY + std::string bar_id; +#endif + private: void onMap(GdkEventAny *); auto setupWidgets() -> void; - void getModules(const Factory &, const std::string &); + void getModules(const Factory &, const std::string &, Gtk::Box*); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); + void setMode(const bar_mode &); + + /* Copy initial set of modes to allow customization */ + bar_mode_map configured_modes = PRESET_MODES; + std::string last_mode_{MODE_DEFAULT}; std::unique_ptr surface_impl_; - bar_layer layer_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; Gtk::Box box_; - std::vector> modules_left_; - std::vector> modules_center_; - std::vector> modules_right_; + std::vector> modules_left_; + std::vector> modules_center_; + std::vector> modules_right_; +#ifdef HAVE_SWAY + using BarIpcClient = modules::sway::BarIpcClient; + std::unique_ptr _ipc_client; +#endif + std::vector> modules_all_; }; } // namespace waybar diff --git a/include/client.hpp b/include/client.hpp index bd80d0bd..7fc3dce7 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -29,6 +29,7 @@ class Client { struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; std::vector> bars; Config config; + std::string bar_id; private: Client() = default; diff --git a/include/factory.hpp b/include/factory.hpp index 43dd2cfd..3855ce22 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -51,6 +51,9 @@ #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" #endif +#ifdef HAVE_GIO_UNIX +#include "modules/inhibitor.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" diff --git a/include/group.hpp b/include/group.hpp new file mode 100644 index 00000000..f282f9c5 --- /dev/null +++ b/include/group.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include "AModule.hpp" +#include "bar.hpp" +#include "factory.hpp" + +namespace waybar { + +class Group : public AModule { + public: + Group(const std::string&, const Bar&, const Json::Value&); + ~Group() = default; + auto update() -> void; + operator Gtk::Widget &(); + Gtk::Box box; +}; + +} // namespace waybar diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 17752e4d..9f950192 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -17,6 +17,8 @@ struct waybar_time { date::zoned_seconds ztime; }; +const std::string kCalendarPlaceholder = "calendar"; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -26,18 +28,19 @@ class Clock : public ALabel { private: util::SleeperThread thread_; std::locale locale_; - const date::time_zone* time_zone_; - bool fixed_time_zone_; - int time_zone_idx_; + std::vector time_zones_; + int current_time_zone_idx_; date::year_month_day cached_calendar_ymd_ = date::January/1/0; std::string cached_calendar_text_; + bool is_calendar_in_tooltip_; bool handleScroll(GdkEventScroll* e); auto calendar_text(const waybar_time& wtime) -> std::string; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto first_day_of_week() -> date::weekday; - bool setTimeZone(Json::Value zone_name); + const date::time_zone* current_timezone(); + bool is_timezone_fixed(); }; } // namespace waybar::modules diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp new file mode 100644 index 00000000..aa2f97d4 --- /dev/null +++ b/include/modules/inhibitor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" + +namespace waybar::modules { + +class Inhibitor : public ALabel { + public: + Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); + ~Inhibitor() override; + auto update() -> void; + auto activated() -> bool; + + private: + auto handleToggle(::GdkEventButton* const& e) -> bool; + + const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; + const std::string inhibitors_; + int handle_ = -1; +}; + +} // namespace waybar::modules diff --git a/include/modules/sway/bar.hpp b/include/modules/sway/bar.hpp new file mode 100644 index 00000000..c4381a43 --- /dev/null +++ b/include/modules/sway/bar.hpp @@ -0,0 +1,49 @@ +#pragma once +#include + +#include "modules/sway/ipc/client.hpp" +#include "util/SafeSignal.hpp" +#include "util/json.hpp" + +namespace waybar { + +class Bar; + +namespace modules::sway { + +/* + * Supported subset of i3/sway IPC barconfig object + */ +struct swaybar_config { + std::string id; + std::string mode; + std::string hidden_state; +}; + +/** + * swaybar IPC client + */ +class BarIpcClient { + public: + BarIpcClient(waybar::Bar& bar); + + private: + void onInitialConfig(const struct Ipc::ipc_response& res); + void onIpcEvent(const struct Ipc::ipc_response&); + void onConfigUpdate(const swaybar_config& config); + void onVisibilityUpdate(bool visible_by_modifier); + void update(); + + Bar& bar_; + util::JsonParser parser_; + Ipc ipc_; + + swaybar_config bar_config_; + bool visible_by_modifier_ = false; + + SafeSignal signal_visible_; + SafeSignal signal_config_; +}; + +} // namespace modules::sway +} // namespace waybar diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 1faf52b3..92e2bbaa 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -32,6 +32,7 @@ class Language : public ALabel, public sigc::trackable { std::string short_name; std::string variant; std::string short_description; + std::string country_flag() const; }; class XKBContext { @@ -54,7 +55,7 @@ class Language : public ALabel, public sigc::trackable { const static std::string XKB_LAYOUT_NAMES_KEY; const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; - + Layout layout_; std::string tooltip_format_ = ""; std::map layouts_map_; diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp new file mode 100644 index 00000000..3b68653c --- /dev/null +++ b/include/util/SafeSignal.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace waybar { + +/** + * Thread-safe signal wrapper. + * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. + */ +template +struct SafeSignal : sigc::signal...)> { + public: + SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } + + template + void emit(EmitArgs&&... args) { + if (main_tid_ == std::this_thread::get_id()) { + /* + * Bypass the queue if the method is called the main thread. + * Ensures that events emitted from the main thread are processed synchronously and saves a + * few CPU cycles on locking/queuing. + * As a downside, this makes main thread events prioritized over the other threads and + * disrupts chronological order. + */ + signal_t::emit(std::forward(args)...); + } else { + { + std::unique_lock lock(mutex_); + queue_.emplace(std::forward(args)...); + } + dp_.emit(); + } + } + + template + inline void operator()(EmitArgs&&... args) { + emit(std::forward(args)...); + } + + protected: + using signal_t = sigc::signal...)>; + using slot_t = decltype(std::declval().make_slot()); + using arg_tuple_t = std::tuple...>; + // ensure that unwrapped methods are not accessible + using signal_t::emit_reverse; + using signal_t::make_slot; + + void handle_event() { + for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { + auto args = queue_.front(); + queue_.pop(); + lock.unlock(); + std::apply(cached_fn_, args); + } + } + + Glib::Dispatcher dp_; + std::mutex mutex_; + std::queue queue_; + const std::thread::id main_tid_ = std::this_thread::get_id(); + // cache functor for signal emission to avoid recreating it on each event + const slot_t cached_fn_ = make_slot(); +}; + +} // namespace waybar diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd new file mode 100644 index 00000000..0838f4d6 --- /dev/null +++ b/man/waybar-inhibitor.5.scd @@ -0,0 +1,92 @@ +waybar-inhibitor(5) + +# NAME + +waybar - inhibitor module + +# DESCRIPTION + +The *inhibitor* module allows to take an inhibitor lock that logind provides. +See *systemd-inhibit*(1) for more information. + +# CONFIGURATION + +*what*: ++ + typeof: string or array ++ + The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*. + +*format*: ++ + typeof: string ++ + The format, how the state should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the current state, the corresponding icon gets selected. + +*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. A click also toggles the state + +*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. + +*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. + +# FORMAT REPLACEMENTS + +*{status}*: status (*activated* or *deactivated*) + +*{icon}*: Icon, as defined in *format-icons* + +# EXAMPLES + +``` +"inhibitor": { + "what": "handle-lid-switch", + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + } +} +``` diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd index 92a647e6..1c88314c 100644 --- a/man/waybar-sway-language.5.scd +++ b/man/waybar-sway-language.5.scd @@ -37,6 +37,8 @@ Addressed by *sway/language* *{variant}*: Variant of layout (e.g. "dvorak"). +*{flag}*: Country flag of layout. + # EXAMPLES ``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 66d5b2eb..c42f6eb8 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -72,14 +72,19 @@ Also a minimal example configuration can be found on the at the bottom of this m typeof: string ++ Optional name added as a CSS class, for styling multiple waybars. +*mode* ++ + typeof: string ++ + Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++ + Note: *hide* and *invisible* modes may be not as useful without Sway IPC. + *exclusive* ++ typeof: bool ++ - default: *true* unless the layer is set to *overlay* ++ + default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. *passthrough* ++ typeof: bool ++ - default: *false* unless the layer is set to *overlay* ++ + default: *false* ++ Option to pass any pointer events to the window under the bar. Intended to be used with either *top* or *overlay* layers and without exclusive zone. @@ -89,6 +94,16 @@ Also a minimal example configuration can be found on the at the bottom of this m Option to disable the use of gtk-layer-shell for popups. Only functional if compiled with gtk-layer-shell support. +*ipc* ++ + typeof: bool ++ + default: false ++ + Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++ + Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option. + +*id* ++ + typeof: string ++ + *bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance. + *include* ++ typeof: string|array ++ Paths to additional configuration files. @@ -203,6 +218,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's Valid options for the "rotate" property are: 0, 90, 180 and 270. +## Grouping modules + +Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. + +A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example: + +``` +{ + "modules-right": ["group/hardware", "clock"], + + "group/hardware": { + "modules": [ + "cpu", + "memory", + "battery" + ] + }, + + ... +} +``` + # SUPPORTED MODULES - *waybar-backlight(5)* diff --git a/meson.build b/meson.build index 62ac8e36..cfe2d513 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) -giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) +giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) @@ -150,6 +150,7 @@ src_files = files( 'src/bar.cpp', 'src/client.cpp', 'src/config.cpp', + 'src/group.cpp', 'src/util/ustring_clen.cpp' ) @@ -177,6 +178,7 @@ endif add_project_arguments('-DHAVE_SWAY', language: 'cpp') src_files += [ 'src/modules/sway/ipc/client.cpp', + 'src/modules/sway/bar.cpp', 'src/modules/sway/mode.cpp', 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', @@ -240,6 +242,11 @@ if libsndio.found() src_files += 'src/modules/sndio.cpp' endif +if (giounix.found() and not get_option('logind').disabled()) + add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') + src_files += 'src/modules/inhibitor.cpp' +endif + if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') @@ -257,6 +264,10 @@ else src_files += 'src/modules/simpleclock.cpp' endif +if get_option('experimental') + add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') +endif + subdir('protocol') executable( @@ -341,6 +352,10 @@ if scdoc.found() 'waybar-sndio.5.scd', ] + if (giounix.found() and not get_option('logind').disabled()) + man_files += 'waybar-inhibitor.5.scd' + endif + foreach file : man_files path = '@0@'.format(file) basename = path.split('/')[-1] @@ -383,3 +398,4 @@ if clangtidy.found() '-p', meson.build_root() ] + src_files) endif + diff --git a/meson_options.txt b/meson_options.txt index 81e44689..230a53d6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,4 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') +option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') +option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index fa9aa58b..1630d97c 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -110,6 +110,7 @@ def main(): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) for player in manager.props.player_names: if arguments.player is not None and arguments.player != player.name: diff --git a/src/bar.cpp b/src/bar.cpp index a8b230e1..133c29aa 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -9,8 +9,13 @@ #include "bar.hpp" #include "client.hpp" #include "factory.hpp" +#include "group.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" +#ifdef HAVE_SWAY +#include "modules/sway/bar.hpp" +#endif + namespace waybar { static constexpr const char* MIN_HEIGHT_MSG = "Requested height: {} is less than the minimum height: {} required by the modules"; @@ -23,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: static constexpr const char* SIZE_DEFINED = "{} size is defined in the config file so it will stay like that"; +const Bar::bar_mode_map Bar::PRESET_MODES = { // + {"default", + {// Special mode to hold the global bar configuration + .layer = bar_layer::BOTTOM, + .exclusive = true, + .passthrough = false, + .visible = true}}, + {"dock", + {// Modes supported by the sway config; see man sway-bar(5) + .layer = bar_layer::BOTTOM, + .exclusive = true, + .passthrough = false, + .visible = true}}, + {"hide", + {// + .layer = bar_layer::TOP, + .exclusive = false, + .passthrough = false, + .visible = true}}, + {"invisible", + {// + .layer = bar_layer::BOTTOM, + .exclusive = false, + .passthrough = true, + .visible = false}}, + {"overlay", + {// + .layer = bar_layer::TOP, + .exclusive = false, + .passthrough = true, + .visible = true}}}; + +const std::string_view Bar::MODE_DEFAULT = "default"; +const std::string_view Bar::MODE_INVISIBLE = "invisible"; +const std::string_view DEFAULT_BAR_ID = "bar-0"; + +/* Deserializer for enum bar_layer */ +void from_json(const Json::Value& j, bar_layer& l) { + if (j == "bottom") { + l = bar_layer::BOTTOM; + } else if (j == "top") { + l = bar_layer::TOP; + } else if (j == "overlay") { + l = bar_layer::OVERLAY; + } +} + +/* Deserializer for struct bar_mode */ +void from_json(const Json::Value& j, bar_mode& m) { + if (j.isObject()) { + if (auto v = j["layer"]; v.isString()) { + from_json(v, m.layer); + } + if (auto v = j["exclusive"]; v.isBool()) { + m.exclusive = v.asBool(); + } + if (auto v = j["passthrough"]; v.isBool()) { + m.passthrough = v.asBool(); + } + if (auto v = j["visible"]; v.isBool()) { + m.visible = v.asBool(); + } + } +} + +/* Deserializer for JSON Object -> map + * Assumes that all the values in the object are deserializable to the same type. + */ +template ::value>> +void from_json(const Json::Value& j, std::map& m) { + if (j.isObject()) { + for (auto it = j.begin(); it != j.end(); ++it) { + from_json(*it, m[it.key().asString()]); + } + } +} + #ifdef HAVE_GTK_LAYER_SHELL struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { @@ -391,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, - layer_{bar_layer::BOTTOM}, left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -403,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["position"].asString()); - if (config["layer"] == "top") { - layer_ = bar_layer::TOP; - } else if (config["layer"] == "overlay") { - layer_ = bar_layer::OVERLAY; - } - - if (config["exclusive"].isBool()) { - exclusive = config["exclusive"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not reserve an exclusive zone - exclusive = false; - } - - bool passthrough = false; - if (config["passthrough"].isBool()) { - passthrough = config["passthrough"].asBool(); - } else if (layer_ == bar_layer::OVERLAY) { - // swaybar defaults: overlay mode does not accept pointer events. - passthrough = true; - } - auto position = config["position"].asString(); if (position == "right" || position == "left") { @@ -505,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_ = std::make_unique(window, *output); } - surface_impl_->setLayer(layer_); - surface_impl_->setExclusiveZone(exclusive); surface_impl_->setMargins(margins_); - surface_impl_->setPassThrough(passthrough); surface_impl_->setPosition(position); surface_impl_->setSize(width, height); + /* Read custom modes if available */ + if (auto modes = config.get("modes", {}); modes.isObject()) { + from_json(modes, configured_modes); + } + + /* Update "default" mode with the global bar options */ + from_json(config, configured_modes[MODE_DEFAULT]); + + if (auto mode = config.get("mode", {}); mode.isString()) { + setMode(config["mode"].asString()); + } else { + setMode(MODE_DEFAULT); + } + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); +#if HAVE_SWAY + if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { + bar_id = Client::inst()->bar_id; + if (auto id = config["id"]; id.isString()) { + bar_id = id.asString(); + } + if (bar_id.empty()) { + bar_id = DEFAULT_BAR_ID; + } + try { + _ipc_client = std::make_unique(*this); + } catch (const std::exception& exc) { + spdlog::warn("Failed to open bar ipc connection: {}", exc.what()); + } + } +#endif + setupWidgets(); window.show_all(); @@ -528,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } } +/* Need to define it here because of forward declared members */ +waybar::Bar::~Bar() = default; + +void waybar::Bar::setMode(const std::string_view& mode) { + using namespace std::literals::string_literals; + + auto style = window.get_style_context(); + /* remove styles added by previous setMode calls */ + style->remove_class("mode-"s + last_mode_); + + auto it = configured_modes.find(mode); + if (it != configured_modes.end()) { + last_mode_ = mode; + style->add_class("mode-"s + last_mode_); + setMode(it->second); + } else { + spdlog::warn("Unknown mode \"{}\" requested", mode); + last_mode_ = MODE_DEFAULT; + style->add_class("mode-"s + last_mode_); + setMode(configured_modes.at(MODE_DEFAULT)); + } +} + +void waybar::Bar::setMode(const struct bar_mode& mode) { + surface_impl_->setLayer(mode.layer); + surface_impl_->setExclusiveZone(mode.exclusive); + surface_impl_->setPassThrough(mode.passthrough); + + if (mode.visible) { + window.get_style_context()->remove_class("hidden"); + window.set_opacity(1); + } else { + window.get_style_context()->add_class("hidden"); + window.set_opacity(0); + } + surface_impl_->commit(); +} + void waybar::Bar::onMap(GdkEventAny*) { /* * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). @@ -538,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) { void waybar::Bar::setVisible(bool value) { visible = value; - if (!visible) { - window.get_style_context()->add_class("hidden"); - window.set_opacity(0); - surface_impl_->setLayer(bar_layer::BOTTOM); - } else { - window.get_style_context()->remove_class("hidden"); - window.set_opacity(1); - surface_impl_->setLayer(layer_); - } - surface_impl_->setExclusiveZone(exclusive && visible); - surface_impl_->commit(); + setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE); } void waybar::Bar::toggle() { setVisible(!visible); } @@ -594,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { } void waybar::Bar::handleSignal(int signal) { - for (auto& module : modules_left_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } - } - for (auto& module : modules_center_) { - auto* custom = dynamic_cast(module.get()); - if (custom != nullptr) { - custom->refresh(signal); - } - } - for (auto& module : modules_right_) { + for (auto& module : modules_all_) { auto* custom = dynamic_cast(module.get()); if (custom != nullptr) { custom->refresh(signal); @@ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) { } } -void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { - if (config[pos].isArray()) { - for (const auto& name : config[pos]) { +void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) { + auto module_list = group ? config[pos]["modules"] : config[pos]; + if (module_list.isArray()) { + for (const auto& name : module_list) { try { - auto module = factory.makeModule(name.asString()); - if (pos == "modules-left") { - modules_left_.emplace_back(module); + auto ref = name.asString(); + AModule* module; + + if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { + auto group_module = new waybar::Group(ref, *this, config[ref]); + getModules(factory, ref, &group_module->box); + module = group_module; + } else { + module = factory.makeModule(ref); } - if (pos == "modules-center") { - modules_center_.emplace_back(module); - } - if (pos == "modules-right") { - modules_right_.emplace_back(module); + + std::shared_ptr module_sp(module); + modules_all_.emplace_back(module_sp); + if (group) { + group->pack_start(*module, false, false); + } else { + if (pos == "modules-left") { + modules_left_.emplace_back(module_sp); + } + if (pos == "modules-center") { + modules_center_.emplace_back(module_sp); + } + if (pos == "modules-right") { + modules_right_.emplace_back(module_sp); + } } module->dp.connect([module, &name] { try { diff --git a/src/client.cpp b/src/client.cpp index 95f5a295..8adbeac1 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) { bool show_version = false; std::string config_opt; std::string style_opt; - std::string bar_id; std::string log_level; auto cli = clara::detail::Help(show_help) | clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | diff --git a/src/factory.cpp b/src/factory.cpp index a577751a..900653b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -30,10 +30,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "wlr/taskbar") { return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } +#ifdef USE_EXPERIMENTAL if (ref == "wlr/workspaces") { return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); } #endif +#endif #ifdef HAVE_RIVER if (ref == "river/tags") { return new waybar::modules::river::Tags(id, bar_, config_[name]); @@ -92,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sndio") { return new waybar::modules::Sndio(id, config_[name]); } +#endif +#ifdef HAVE_GIO_UNIX + if (ref == "inhibitor") { + return new waybar::modules::Inhibitor(id, bar_, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/group.cpp b/src/group.cpp new file mode 100644 index 00000000..9d2188cc --- /dev/null +++ b/src/group.cpp @@ -0,0 +1,19 @@ +#include "group.hpp" +#include +#include + +namespace waybar { + +Group::Group(const std::string& name, const Bar& bar, const Json::Value& config) + : AModule(config, name, "", false, false), + box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0} + { +} + +auto Group::update() -> void { + // noop +} + +Group::operator Gtk::Widget&() { return box; } + +} // namespace waybar diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 7c94c457..7e7d7420 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -14,17 +14,51 @@ using waybar::modules::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) - : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { + : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), + current_time_zone_idx_(0), + is_calendar_in_tooltip_(false) +{ if (config_["timezones"].isArray() && !config_["timezones"].empty()) { - time_zone_idx_ = 0; - setTimeZone(config_["timezones"][time_zone_idx_]); - } else { - setTimeZone(config_["timezone"]); + for (const auto& zone_name: config_["timezones"]) { + if (!zone_name.isString() || zone_name.asString().empty()) { + time_zones_.push_back(nullptr); + continue; + } + time_zones_.push_back( + date::locate_zone( + zone_name.asString() + ) + ); + } + } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) { + time_zones_.push_back( + date::locate_zone( + config_["timezone"].asString() + ) + ); } - if (fixed_time_zone_) { + + // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown. + if (!time_zones_.size()) { + time_zones_.push_back(nullptr); + } + + if (!is_timezone_fixed()) { spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); } + // Check if a particular placeholder is present in the tooltip format, to know what to calculate on update. + if (config_["tooltip-format"].isString()) { + std::string trimmed_format = config_["tooltip-format"].asString(); + trimmed_format.erase(std::remove_if(trimmed_format.begin(), + trimmed_format.end(), + [](unsigned char x){return std::isspace(x);}), + trimmed_format.end()); + if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { + is_calendar_in_tooltip_ = true; + } + } + if (config_["locale"].isString()) { locale_ = std::locale(config_["locale"].asString()); } else { @@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) }; } +const date::time_zone* waybar::modules::Clock::current_timezone() { + return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone(); +} + +bool waybar::modules::Clock::is_timezone_fixed() { + return time_zones_[current_time_zone_idx_] != nullptr; +} + auto waybar::modules::Clock::update() -> void { - if (!fixed_time_zone_) { - // Time zone can change. Be sure to pick that. - time_zone_ = date::current_zone(); - } - - auto now = std::chrono::system_clock::now(); + auto time_zone = current_timezone(); + auto now = std::chrono::system_clock::now(); waybar_time wtime = {locale_, - date::make_zoned(time_zone_, date::floor(now))}; - - std::string text; - if (!fixed_time_zone_) { + date::make_zoned(time_zone, date::floor(now))}; + std::string text = ""; + if (!is_timezone_fixed()) { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); text = fmt::format(format_, localtime); - label_.set_markup(text); } else { text = fmt::format(format_, wtime); - label_.set_markup(text); } + label_.set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { - const auto calendar = calendar_text(wtime); - auto tooltip_format = config_["tooltip-format"].asString(); - auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); - label_.set_tooltip_markup(tooltip_text); - } else { - label_.set_tooltip_markup(text); + std::string calendar_lines = ""; + if (is_calendar_in_tooltip_) { + calendar_lines = calendar_text(wtime); + } + auto tooltip_format = config_["tooltip-format"].asString(); + text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines)); } } + + label_.set_tooltip_markup(text); // Call parent update ALabel::update(); } -bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) { - if (!zone_name.isString() || zone_name.asString().empty()) { - fixed_time_zone_ = false; - return false; - } - - time_zone_ = date::locate_zone(zone_name.asString()); - fixed_time_zone_ = true; - return true; -} - bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { // defer to user commands if set if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { return true; } - if (!config_["timezones"].isArray() || config_["timezones"].empty()) { + if (time_zones_.size() == 1) { return true; } - auto nr_zones = config_["timezones"].size(); + + auto nr_zones = time_zones_.size(); if (dir == SCROLL_DIR::UP) { - size_t new_idx = time_zone_idx_ + 1; - time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; + size_t new_idx = current_time_zone_idx_ + 1; + current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; } else { - time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; + current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1; } - setTimeZone(config_["timezones"][time_zone_idx_]); + update(); return true; } diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp new file mode 100644 index 00000000..1e3f2d35 --- /dev/null +++ b/src/modules/inhibitor.cpp @@ -0,0 +1,175 @@ +#include "modules/inhibitor.hpp" + +#include +#include +#include + +namespace { + +using DBus = std::unique_ptr; + +auto dbus() -> DBus { + GError *error = nullptr; + GDBusConnection* connection = + g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + + if (error) { + spdlog::error("g_bus_get_sync() failed: {}", error->message); + g_error_free(error); + connection = nullptr; + } + + auto destructor = [](GDBusConnection* connection) { + GError *error = nullptr; + g_dbus_connection_close_sync(connection, nullptr, &error); + if (error) { + spdlog::error( + "g_bus_connection_close_sync failed(): {}", + error->message); + g_error_free(error); + } + }; + + return DBus{connection, destructor}; +} + +auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { + GError *error = nullptr; + GUnixFDList* fd_list; + int handle; + + auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "Inhibit", + g_variant_new( + "(ssss)", + inhibitors.c_str(), + "waybar", + "Asked by user", + "block"), + G_VARIANT_TYPE("(h)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &fd_list, + nullptr, + &error); + if (error) { + spdlog::error( + "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", + error->message); + g_error_free(error); + handle = -1; + } else { + gint index; + g_variant_get(reply, "(h)", &index); + g_variant_unref(reply); + handle = g_unix_fd_list_get(fd_list, index, nullptr); + g_object_unref(fd_list); + } + + return handle; +} + +auto checkInhibitor(const std::string& inhibitor) -> const std::string& { + static const auto inhibitors = std::array{ + "idle", + "shutdown", + "sleep", + "handle-power-key", + "handle-suspend-key", + "handle-hibernate-key", + "handle-lid-switch" + }; + + if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) + == inhibitors.end()) { + throw std::runtime_error("invalid logind inhibitor " + inhibitor); + } + + return inhibitor; +} + +auto getInhibitors(const Json::Value& config) -> std::string { + std::string inhibitors = "idle"; + + if (config["what"].empty()) { + return inhibitors; + } + + if (config["what"].isString()) { + return checkInhibitor(config["what"].asString()); + } + + if (config["what"].isArray()) { + inhibitors = checkInhibitor(config["what"][0].asString()); + for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { + inhibitors += ":" + checkInhibitor(config["what"][i].asString()); + } + return inhibitors; + } + + return inhibitors; +} + +} + +namespace waybar::modules { + +Inhibitor::Inhibitor(const std::string& id, const Bar& bar, + const Json::Value& config) + : ALabel(config, "inhibitor", id, "{status}", true), + dbus_(::dbus()), + inhibitors_(::getInhibitors(config)) { + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &Inhibitor::handleToggle)); + dp.emit(); +} + +Inhibitor::~Inhibitor() { + if (handle_ != -1) { + ::close(handle_); + } +} + +auto Inhibitor::activated() -> bool { + return handle_ != -1; +} + +auto Inhibitor::update() -> void { + std::string status_text = activated() ? "activated" : "deactivated"; + + label_.get_style_context()->remove_class( + activated() ? "deactivated" : "activated"); + label_.set_markup( + fmt::format(format_, fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text)))); + label_.get_style_context()->add_class(status_text); + + if (tooltipEnabled()) { + label_.set_tooltip_text(status_text); + } + + return ALabel::update(); +} + +auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { + if (e->button == 1) { + if (activated()) { + ::close(handle_); + handle_ = -1; + } else { + handle_ = ::getLocks(dbus_, inhibitors_); + if (handle_ == -1) { + spdlog::error("cannot get inhibitor locks"); + } + } + } + + return ALabel::handleToggle(e); +} + +} // waybar::modules diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e7b20ab5..b86989f3 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -789,8 +789,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; - // WiFi-hardware usually operates in the range -90 to -20dBm. - const int hardwareMax = -20; + // WiFi-hardware usually operates in the range -90 to -30dBm. + const int hardwareMax = -30; const int hardwareMin = -90; const int strength = ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index cf427800..3c320608 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { if (dir == SCROLL_DIR::NONE) { return true; } + if (config_["reverse-scrolling"].asInt() == 1){ + if (dir == SCROLL_DIR::UP) { + dir = SCROLL_DIR::DOWN; + } else if (dir == SCROLL_DIR::DOWN) { + dir = SCROLL_DIR::UP; + } + } double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; @@ -211,7 +218,7 @@ static const std::array ports = { }; const std::vector waybar::modules::Pulseaudio::getPulseIcon() const { - std::vector res = {default_source_name_}; + std::vector res = {current_sink_name_, default_source_name_}; std::string nameLC = port_name_ + form_factor_; std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp new file mode 100644 index 00000000..6ad74af1 --- /dev/null +++ b/src/modules/sway/bar.cpp @@ -0,0 +1,107 @@ +#include "modules/sway/bar.hpp" + +#include +#include + +#include + +#include "bar.hpp" +#include "modules/sway/ipc/ipc.hpp" + +namespace waybar::modules::sway { + +BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} { + { + sigc::connection handle = + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig)); + ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id); + + handle.disconnect(); + } + + signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate)); + signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate)); + + ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent)); + // Launch worker + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("BarIpcClient::handleEvent {}", e.what()); + } + }); +} + +struct swaybar_config parseConfig(const Json::Value& payload) { + swaybar_config conf; + if (auto id = payload["id"]; id.isString()) { + conf.id = id.asString(); + } + if (auto mode = payload["mode"]; mode.isString()) { + conf.mode = mode.asString(); + } + if (auto hs = payload["hidden_state"]; hs.isString()) { + conf.hidden_state = hs.asString(); + } + return conf; +} + +void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) { + auto payload = parser_.parse(res.payload); + if (auto success = payload.get("success", true); !success.asBool()) { + auto err = payload.get("error", "Unknown error"); + throw std::runtime_error(err.asString()); + } + auto config = parseConfig(payload); + onConfigUpdate(config); +} + +void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { + try { + auto payload = parser_.parse(res.payload); + if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) { + spdlog::trace("swaybar ipc: ignore event for {}", id.asString()); + return; + } + if (payload.isMember("visible_by_modifier")) { + // visibility change for hidden bar + signal_visible_(payload["visible_by_modifier"].asBool()); + } else { + // configuration update + auto config = parseConfig(payload); + signal_config_(std::move(config)); + } + } catch (const std::exception& e) { + spdlog::error("BarIpcClient::onEvent {}", e.what()); + } +} + +void BarIpcClient::onConfigUpdate(const swaybar_config& config) { + spdlog::info("config update for {}: id {}, mode {}, hidden_state {}", + bar_.bar_id, + config.id, + config.mode, + config.hidden_state); + bar_config_ = config; + update(); +} + +void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { + spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier); + visible_by_modifier_ = visible_by_modifier; + update(); +} + +void BarIpcClient::update() { + bool visible = visible_by_modifier_; + if (bar_config_.mode == "invisible") { + visible = false; + } else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") { + visible = true; + } + bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE); +} + +} // namespace waybar::modules::sway diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 186fa4bb..73a64c3b 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -99,7 +99,8 @@ auto Language::update() -> void { fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), - fmt::arg("variant", layout_.variant))); + fmt::arg("variant", layout_.variant), + fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { @@ -107,7 +108,8 @@ auto Language::update() -> void { fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), - fmt::arg("variant", layout_.variant))); + fmt::arg("variant", layout_.variant), + fmt::arg("flag", layout_.country_flag()))); label_.set_tooltip_markup(tooltip_display_layout); } else { label_.set_tooltip_markup(display_layout); @@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); delete layout_; } + +std::string Language::Layout::country_flag() const { + if (short_name.size() != 2) return ""; + unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00"; + result[3] = short_name[0] + 0x45; + result[7] = short_name[1] + 0x45; + // Check if both emojis are in A-Z symbol bounds + if (result[3] < 0xa6 || result[3] > 0xbf) return ""; + if (result[7] < 0xa6 || result[7] > 0xbf) return ""; + return std::string{reinterpret_cast(result)}; +} } // namespace waybar::modules::sway diff --git a/test/GlibTestsFixture.hpp b/test/GlibTestsFixture.hpp new file mode 100644 index 00000000..a21c8e07 --- /dev/null +++ b/test/GlibTestsFixture.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +/** + * Minimal Glib application to be used for tests that require Glib main loop + */ +class GlibTestsFixture : public sigc::trackable { + public: + GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} + + void setTimeout(int timeout) { + Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, + timeout); + } + + void run(std::function fn) { + Glib::signal_idle().connect_once(fn); + main_loop_->run(); + } + + void quit() { main_loop_->quit(); } + + protected: + Glib::RefPtr main_loop_; +}; diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp new file mode 100644 index 00000000..2c67317b --- /dev/null +++ b/test/SafeSignal.cpp @@ -0,0 +1,145 @@ +#define CATCH_CONFIG_RUNNER +#include "util/SafeSignal.hpp" + +#include + +#include +#include +#include + +#include "GlibTestsFixture.hpp" + +using namespace waybar; + +template +using remove_cvref_t = typename std::remove_cv::type>::type; + +/** + * Basic sanity test for SafeSignal: + * check that type deduction works, events are delivered and the order is right + * Running this with -fsanitize=thread should not fail + */ +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") { + const int NUM_EVENTS = 100; + int count = 0; + int last_value = 0; + + SafeSignal test_signal; + + const auto main_tid = std::this_thread::get_id(); + std::thread producer; + + // timeout the test in 500ms + setTimeout(500); + + test_signal.connect([&](auto val, auto str) { + static_assert(std::is_same::value); + static_assert(std::is_same::value); + // check that we're in the same thread as the main loop + REQUIRE(std::this_thread::get_id() == main_tid); + // check event order + REQUIRE(val == last_value + 1); + + last_value = val; + if (++count >= NUM_EVENTS) { + this->quit(); + }; + }); + + run([&]() { + // check that events from the same thread are delivered and processed synchronously + test_signal.emit(1, "test"); + REQUIRE(count == 1); + + // start another thread and generate events + producer = std::thread([&]() { + for (auto i = 2; i <= NUM_EVENTS; ++i) { + test_signal.emit(i, "test"); + } + }); + }); + producer.join(); + REQUIRE(count == NUM_EVENTS); +} + +template +struct TestObject { + T value; + unsigned copied = 0; + unsigned moved = 0; + + TestObject(const T& v) : value(v){}; + ~TestObject() = default; + + TestObject(const TestObject& other) + : value(other.value), copied(other.copied + 1), moved(other.moved) {} + + TestObject(TestObject&& other) noexcept + : value(std::move(other.value)), + copied(std::exchange(other.copied, 0)), + moved(std::exchange(other.moved, 0) + 1) {} + + TestObject& operator=(const TestObject& other) { + value = other.value; + copied = other.copied + 1; + moved = other.moved; + return *this; + } + + TestObject& operator=(TestObject&& other) noexcept { + value = std::move(other.value); + copied = std::exchange(other.copied, 0); + moved = std::exchange(other.moved, 0) + 1; + return *this; + } + + bool operator==(T other) const { return value == other; } + operator T() const { return value; } +}; + +/* + * Check the number of copies/moves performed on the object passed through SafeSignal + */ +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") { + const int NUM_EVENTS = 3; + int count = 0; + + SafeSignal> test_signal; + + std::thread producer; + + // timeout the test in 500ms + setTimeout(500); + + test_signal.connect([&](auto& val) { + static_assert(std::is_same, remove_cvref_t>::value); + + /* explicit move in the producer thread */ + REQUIRE(val.moved <= 1); + /* copy within the SafeSignal queuing code */ + REQUIRE(val.copied <= 1); + + if (++count >= NUM_EVENTS) { + this->quit(); + }; + }); + + run([&]() { + test_signal.emit(1); + REQUIRE(count == 1); + producer = std::thread([&]() { + for (auto i = 2; i <= NUM_EVENTS; ++i) { + TestObject t{i}; + // check that signal.emit accepts moved objects + test_signal.emit(std::move(t)); + } + }); + }); + producer.join(); + REQUIRE(count == NUM_EVENTS); +} + +int main(int argc, char* argv[]) { + Glib::init(); + return Catch::Session().run(argc, argv); +} diff --git a/test/meson.build b/test/meson.build index 85b9771f..bbef21e7 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ test_inc = include_directories('../include') test_dep = [ catch2, fmt, + gtkmm, jsoncpp, spdlog, ] @@ -14,8 +15,21 @@ config_test = executable( include_directories: test_inc, ) +safesignal_test = executable( + 'safesignal_test', + 'SafeSignal.cpp', + dependencies: test_dep, + include_directories: test_inc, +) + test( 'Configuration test', config_test, workdir: meson.source_root(), ) + +test( + 'SafeSignal test', + safesignal_test, + workdir: meson.source_root(), +) From 812832d38adeb62e675a85e004f05851ed60f26c Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 5 Jan 2022 15:38:52 +0300 Subject: [PATCH 77/78] fix: Missing tasks --- include/modules/wlr/taskbar.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 71c9fd24..973d15db 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -64,8 +64,8 @@ class Task Gtk::Label text_before_; Gtk::Label text_after_; Glib::RefPtr app_info_; - bool button_visible_; - bool ignored_; + bool button_visible_ = false; + bool ignored_ = false; bool with_icon_ = false; bool with_name_ = false; From 4a85ec0f59137b041d99801292a9b708b99a15a5 Mon Sep 17 00:00:00 2001 From: Johannes Wienke Date: Thu, 6 Jan 2022 17:49:06 +0100 Subject: [PATCH 78/78] fix: match sway workspace order with sway one Ensure that sway workspaces are always displayed in the same order as used internally by sway. The previous sorting code always sorted unnumbered workspaces lexicographically. This isn't the order used by sway internally. Therefore, commands such as "workspace next" might have jumped arbitrarily in waybar. This commit reworks the sorting code such that the internal order is always obeyed. Additionally, numbered persistent workspaces are inserted at their natural position at the front of the workspace list while unnumbered ones are appended. This should match the expectations of workspace ordering known from sway's behavior. The changes make the configuration property "numeric-first" unnecessary as this will always be the case now. There's also no reasonable way around this behavior now. Otherwise, persistent workspaces would jump around in the visual representation as soon as they become known to sway. Fixes #802 --- man/waybar-sway-workspaces.5.scd | 5 --- src/modules/sway/workspaces.cpp | 74 +++++++++++++++++--------------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index f2808b90..b575e098 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -69,10 +69,6 @@ Addressed by *sway/workspaces* typeof: string ++ Command to execute when the module is updated. -*numeric-first*: ++ - typeof: bool ++ - Whether to put workspaces starting with numbers before workspaces that do not start with a number. - *disable-auto-back-and-forth*: ++ typeof: bool ++ Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. @@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge "sway/workspaces": { "disable-scroll": true, "all-outputs": true, - "numeric-first": false, "format": "{name}: {icon}", "format-icons": { "1": "", diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 43dcf33c..074d145f 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { Json::Value v; v["name"] = p_w_name; v["target_output"] = bar_.output->name; + v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); break; } @@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { Json::Value v; v["name"] = p_w_name; v["target_output"] = ""; + v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); } } } - // config option to sort numeric workspace names before others - bool config_numeric_first = config_["numeric-first"].asBool(); - + // sway has a defined ordering of workspaces that should be preserved in + // the representation displayed by waybar to ensure that commands such + // as "workspace prev" or "workspace next" make sense when looking at + // the workspace representation in the bar. + // Due to waybar's own feature of persistent workspaces unknown to sway, + // custom sorting logic is necessary to make these workspaces appear + // naturally in the list of workspaces without messing up sway's + // sorting. For this purpose, a custom numbering property is created + // that preserves the order provided by sway while inserting numbered + // persistent workspaces at their natural positions. + // + // All of this code assumes that sway provides numbered workspaces first + // and other workspaces are sorted by their creation time. + // + // In a first pass, the maximum "num" value is computed to enqueue + // unnumbered workspaces behind numbered ones when computing the sort + // attribute. + int max_num = -1; + for (auto & workspace : workspaces_) { + max_num = std::max(workspace["num"].asInt(), max_num); + } + for (auto & workspace : workspaces_) { + auto workspace_num = workspace["num"].asInt(); + if (workspace_num > -1) { + workspace["sort"] = workspace_num; + } else { + workspace["sort"] = ++max_num; + } + } std::sort(workspaces_.begin(), workspaces_.end(), - [config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) { - // the "num" property (integer type): - // The workspace number or -1 for workspaces that do - // not start with a number. - // We could rely on sway providing this property: - // - // auto l = lhs["num"].asInt(); - // auto r = rhs["num"].asInt(); - // - // We cannot rely on the "num" property as provided by sway - // via IPC, because persistent workspace might not exist in - // sway's view. However, we need this property also for - // not-yet created persistent workspace. As such, we simply - // duplicate sway's logic of assigning the "num" property - // into waybar (see convertWorkspaceNameToNum). This way the - // sorting should work out even when we include workspaces - // that do not currently exist. + [](const Json::Value &lhs, const Json::Value &rhs) { auto lname = lhs["name"].asString(); auto rname = rhs["name"].asString(); - int l = convertWorkspaceNameToNum(lname); - int r = convertWorkspaceNameToNum(rname); + int l = lhs["sort"].asInt(); + int r = rhs["sort"].asInt(); if (l == r) { - // in case both integers are the same, lexicographical - // sort. This also covers the case when both don't have a - // number (i.e., l == r == -1). + // In case both integers are the same, lexicographical + // sort. The code above already ensure that this will only + // happend in case of explicitly numbered workspaces. return lname < rname; } - // one of the workspaces doesn't begin with a number, so - // num is -1. - if (l < 0 || r < 0) { - if (config_numeric_first) { - return r < 0; - } - return l < 0; - } - - // both workspaces have a "num" so let's just compare those return l < r; }); + } dp.emit(); } catch (const std::exception &e) {