From 285a264aae64780f9c8fbf22f88ebdf7ee55ab86 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 28 Dec 2020 17:26:55 -0800 Subject: [PATCH 01/50] 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 02/50] 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 03/50] 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 04/50] 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 05/50] 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 06/50] 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 07/50] 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 08/50] 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 09/50] 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 10/50] 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 11/50] 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 12/50] 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 13/50] 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 14/50] 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 15/50] 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 16/50] 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 17/50] 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 18/50] 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 110c66dd329427fb29169fe0389f3ee4cbbd8307 Mon Sep 17 00:00:00 2001 From: Sergey Mishin Date: Sun, 3 Oct 2021 16:48:21 +0000 Subject: [PATCH 19/50] 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 20/50] 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 21/50] 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 22/50] 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 5baffbf8f8b47b619fc59334caeb0adf6e9473f0 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:28:57 -0800 Subject: [PATCH 23/50] 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 6bfb674d1b034a9679c049668714532e2da880c1 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 21 Nov 2021 17:28:47 -0800 Subject: [PATCH 24/50] 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 2290fe10aa35a2c32953361df81a4618d55ee148 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Nov 2021 08:46:58 -0800 Subject: [PATCH 25/50] 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 26/50] 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 27/50] 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 28/50] 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 29/50] 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 30/50] 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 31/50] 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 32/50] 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 33/50] 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 34/50] 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 35/50] 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 36/50] 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 37/50] 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 38/50] 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 39/50] 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 40/50] 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 41/50] 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 42/50] 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 43/50] 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 44/50] 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 45/50] 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 46/50] 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 47/50] 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 48/50] 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 49/50] 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 50/50] 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,