From 285a264aae64780f9c8fbf22f88ebdf7ee55ab86 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 28 Dec 2020 17:26:55 -0800 Subject: [PATCH 01/22] 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 0000000..b2beff4 --- /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/22] 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 b2beff4..15a8d2b 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/22] 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 15a8d2b..3b68653 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/22] 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 0000000..ffe6fd3 --- /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 0000000..b07e9ca --- /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 85b9771..bbef21e 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/22] 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 6f3dfcf..1f90f9a 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 a8b230e..3ce59e3 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/22] 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 1f90f9a..426c55c 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 3ce59e3..2f8bc27 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/22] 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 2f8bc27..b009110 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/22] 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 b009110..43a627a 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/22] 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 426c55c..1d7f4a9 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 43a627a..a7b24a6 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/22] 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 66d5b2e..8490ee5 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/22] 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 bd80d0b..7fc3dce 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 95f5a29..8adbeac 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/22] 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 1d7f4a9..1b9617b 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 0000000..e32dee4 --- /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 52c9d29..a15ef59 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 a7b24a6..8ad81e9 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 0000000..762353c --- /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/22] 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 762353c..3179221 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/22] 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 e32dee4..d3a697c 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 3179221..7cae4ba 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 5baffbf8f8b47b619fc59334caeb0adf6e9473f0 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 19 Nov 2021 20:28:57 -0800 Subject: [PATCH 15/22] 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 8490ee5..bfc9ab8 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 16/22] 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 d3a697c..c4381a4 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 7cae4ba..17382e3 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 17/22] 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 8ad81e9..26d64ec 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 17382e3..78e524a 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 8fe42ebd2eac1b6dbe79a18a5fe4ead9a78bac93 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Nov 2021 19:18:24 -0800 Subject: [PATCH 18/22] 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 bfc9ab8..92d0774 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 19/22] 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 03e5d70..d5064fe 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 4b5dc1bb3a5cda181bc0589992fb13076e3ad2b3 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sun, 28 Nov 2021 09:52:18 -0800 Subject: [PATCH 20/22] 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 ffe6fd3..a21c8e0 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 b07e9ca..2c67317 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 21/22] 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 78e524a..6ad74af 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 22/22] 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 26d64ec..789bea9 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);