mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
Merge pull request #1244 from alebastr/swaybar-ipc
Yet another swaybar ipc implementation
This commit is contained in:
commit
d41a60d2d9
1
.github/workflows/freebsd.yml
vendored
1
.github/workflows/freebsd.yml
vendored
@ -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
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <gtkmm/window.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
@ -36,6 +39,19 @@ struct bar_margins {
|
||||
int left = 0;
|
||||
};
|
||||
|
||||
struct bar_mode {
|
||||
bar_layer layer;
|
||||
bool exclusive;
|
||||
bool passthrough;
|
||||
bool visible;
|
||||
};
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
namespace modules::sway {
|
||||
class BarIpcClient;
|
||||
}
|
||||
#endif // HAVE_SWAY
|
||||
|
||||
class BarSurface {
|
||||
protected:
|
||||
BarSurface() = default;
|
||||
@ -54,31 +70,44 @@ class BarSurface {
|
||||
|
||||
class Bar {
|
||||
public:
|
||||
using bar_mode_map = std::map<std::string_view, struct bar_mode>;
|
||||
static const bar_mode_map PRESET_MODES;
|
||||
static const std::string_view MODE_DEFAULT;
|
||||
static const std::string_view MODE_INVISIBLE;
|
||||
|
||||
Bar(struct waybar_output *w_output, const Json::Value &);
|
||||
Bar(const Bar &) = delete;
|
||||
~Bar() = default;
|
||||
~Bar();
|
||||
|
||||
void setMode(const std::string_view &);
|
||||
void setVisible(bool visible);
|
||||
void toggle();
|
||||
void handleSignal(int);
|
||||
|
||||
struct waybar_output *output;
|
||||
Json::Value config;
|
||||
struct wl_surface * surface;
|
||||
bool exclusive = true;
|
||||
struct wl_surface *surface;
|
||||
bool visible = true;
|
||||
bool vertical = false;
|
||||
Gtk::Window window;
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
std::string bar_id;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void onMap(GdkEventAny *);
|
||||
auto setupWidgets() -> void;
|
||||
void getModules(const Factory &, const std::string &, Gtk::Box*);
|
||||
void setupAltFormatKeyForModule(const std::string &module_name);
|
||||
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
||||
void setMode(const bar_mode &);
|
||||
|
||||
/* Copy initial set of modes to allow customization */
|
||||
bar_mode_map configured_modes = PRESET_MODES;
|
||||
std::string last_mode_{MODE_DEFAULT};
|
||||
|
||||
std::unique_ptr<BarSurface> surface_impl_;
|
||||
bar_layer layer_;
|
||||
Gtk::Box left_;
|
||||
Gtk::Box center_;
|
||||
Gtk::Box right_;
|
||||
@ -86,6 +115,10 @@ class Bar {
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
|
||||
#ifdef HAVE_SWAY
|
||||
using BarIpcClient = modules::sway::BarIpcClient;
|
||||
std::unique_ptr<BarIpcClient> _ipc_client;
|
||||
#endif
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_all_;
|
||||
};
|
||||
|
||||
|
@ -29,6 +29,7 @@ class Client {
|
||||
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
|
||||
std::vector<std::unique_ptr<Bar>> bars;
|
||||
Config config;
|
||||
std::string bar_id;
|
||||
|
||||
private:
|
||||
Client() = default;
|
||||
|
49
include/modules/sway/bar.hpp
Normal file
49
include/modules/sway/bar.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "modules/sway/ipc/client.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
#include "util/json.hpp"
|
||||
|
||||
namespace waybar {
|
||||
|
||||
class Bar;
|
||||
|
||||
namespace modules::sway {
|
||||
|
||||
/*
|
||||
* Supported subset of i3/sway IPC barconfig object
|
||||
*/
|
||||
struct swaybar_config {
|
||||
std::string id;
|
||||
std::string mode;
|
||||
std::string hidden_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* swaybar IPC client
|
||||
*/
|
||||
class BarIpcClient {
|
||||
public:
|
||||
BarIpcClient(waybar::Bar& bar);
|
||||
|
||||
private:
|
||||
void onInitialConfig(const struct Ipc::ipc_response& res);
|
||||
void onIpcEvent(const struct Ipc::ipc_response&);
|
||||
void onConfigUpdate(const swaybar_config& config);
|
||||
void onVisibilityUpdate(bool visible_by_modifier);
|
||||
void update();
|
||||
|
||||
Bar& bar_;
|
||||
util::JsonParser parser_;
|
||||
Ipc ipc_;
|
||||
|
||||
swaybar_config bar_config_;
|
||||
bool visible_by_modifier_ = false;
|
||||
|
||||
SafeSignal<bool> signal_visible_;
|
||||
SafeSignal<swaybar_config> signal_config_;
|
||||
};
|
||||
|
||||
} // namespace modules::sway
|
||||
} // namespace waybar
|
75
include/util/SafeSignal.hpp
Normal file
75
include/util/SafeSignal.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <glibmm/dispatcher.h>
|
||||
#include <sigc++/signal.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
/**
|
||||
* Thread-safe signal wrapper.
|
||||
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
|
||||
*/
|
||||
template <typename... Args>
|
||||
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||
public:
|
||||
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
|
||||
|
||||
template <typename... EmitArgs>
|
||||
void emit(EmitArgs&&... args) {
|
||||
if (main_tid_ == std::this_thread::get_id()) {
|
||||
/*
|
||||
* Bypass the queue if the method is called the main thread.
|
||||
* Ensures that events emitted from the main thread are processed synchronously and saves a
|
||||
* few CPU cycles on locking/queuing.
|
||||
* As a downside, this makes main thread events prioritized over the other threads and
|
||||
* disrupts chronological order.
|
||||
*/
|
||||
signal_t::emit(std::forward<EmitArgs>(args)...);
|
||||
} else {
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
queue_.emplace(std::forward<EmitArgs>(args)...);
|
||||
}
|
||||
dp_.emit();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... EmitArgs>
|
||||
inline void operator()(EmitArgs&&... args) {
|
||||
emit(std::forward<EmitArgs>(args)...);
|
||||
}
|
||||
|
||||
protected:
|
||||
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
|
||||
using slot_t = decltype(std::declval<signal_t>().make_slot());
|
||||
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
|
||||
// ensure that unwrapped methods are not accessible
|
||||
using signal_t::emit_reverse;
|
||||
using signal_t::make_slot;
|
||||
|
||||
void handle_event() {
|
||||
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
|
||||
auto args = queue_.front();
|
||||
queue_.pop();
|
||||
lock.unlock();
|
||||
std::apply(cached_fn_, args);
|
||||
}
|
||||
}
|
||||
|
||||
Glib::Dispatcher dp_;
|
||||
std::mutex mutex_;
|
||||
std::queue<arg_tuple_t> queue_;
|
||||
const std::thread::id main_tid_ = std::this_thread::get_id();
|
||||
// cache functor for signal emission to avoid recreating it on each event
|
||||
const slot_t cached_fn_ = make_slot();
|
||||
};
|
||||
|
||||
} // namespace waybar
|
@ -72,14 +72,19 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||
typeof: string ++
|
||||
Optional name added as a CSS class, for styling multiple waybars.
|
||||
|
||||
*mode* ++
|
||||
typeof: string ++
|
||||
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
|
||||
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
|
||||
|
||||
*exclusive* ++
|
||||
typeof: bool ++
|
||||
default: *true* unless the layer is set to *overlay* ++
|
||||
default: *true* ++
|
||||
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
||||
|
||||
*passthrough* ++
|
||||
typeof: bool ++
|
||||
default: *false* unless the layer is set to *overlay* ++
|
||||
default: *false* ++
|
||||
Option to pass any pointer events to the window under the bar.
|
||||
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
|
||||
|
||||
@ -89,6 +94,16 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||
Option to disable the use of gtk-layer-shell for popups.
|
||||
Only functional if compiled with gtk-layer-shell support.
|
||||
|
||||
*ipc* ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++
|
||||
Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option.
|
||||
|
||||
*id* ++
|
||||
typeof: string ++
|
||||
*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance.
|
||||
|
||||
*include* ++
|
||||
typeof: string|array ++
|
||||
Paths to additional configuration files.
|
||||
|
@ -178,6 +178,7 @@ endif
|
||||
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
||||
src_files += [
|
||||
'src/modules/sway/ipc/client.cpp',
|
||||
'src/modules/sway/bar.cpp',
|
||||
'src/modules/sway/mode.cpp',
|
||||
'src/modules/sway/language.cpp',
|
||||
'src/modules/sway/window.cpp',
|
||||
|
188
src/bar.cpp
188
src/bar.cpp
@ -12,6 +12,10 @@
|
||||
#include "group.hpp"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
#include "modules/sway/bar.hpp"
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
static constexpr const char* MIN_HEIGHT_MSG =
|
||||
"Requested height: {} is less than the minimum height: {} required by the modules";
|
||||
@ -24,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height:
|
||||
static constexpr const char* SIZE_DEFINED =
|
||||
"{} size is defined in the config file so it will stay like that";
|
||||
|
||||
const Bar::bar_mode_map Bar::PRESET_MODES = { //
|
||||
{"default",
|
||||
{// Special mode to hold the global bar configuration
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = true,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"dock",
|
||||
{// Modes supported by the sway config; see man sway-bar(5)
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = true,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"hide",
|
||||
{//
|
||||
.layer = bar_layer::TOP,
|
||||
.exclusive = false,
|
||||
.passthrough = false,
|
||||
.visible = true}},
|
||||
{"invisible",
|
||||
{//
|
||||
.layer = bar_layer::BOTTOM,
|
||||
.exclusive = false,
|
||||
.passthrough = true,
|
||||
.visible = false}},
|
||||
{"overlay",
|
||||
{//
|
||||
.layer = bar_layer::TOP,
|
||||
.exclusive = false,
|
||||
.passthrough = true,
|
||||
.visible = true}}};
|
||||
|
||||
const std::string_view Bar::MODE_DEFAULT = "default";
|
||||
const std::string_view Bar::MODE_INVISIBLE = "invisible";
|
||||
const std::string_view DEFAULT_BAR_ID = "bar-0";
|
||||
|
||||
/* Deserializer for enum bar_layer */
|
||||
void from_json(const Json::Value& j, bar_layer& l) {
|
||||
if (j == "bottom") {
|
||||
l = bar_layer::BOTTOM;
|
||||
} else if (j == "top") {
|
||||
l = bar_layer::TOP;
|
||||
} else if (j == "overlay") {
|
||||
l = bar_layer::OVERLAY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deserializer for struct bar_mode */
|
||||
void from_json(const Json::Value& j, bar_mode& m) {
|
||||
if (j.isObject()) {
|
||||
if (auto v = j["layer"]; v.isString()) {
|
||||
from_json(v, m.layer);
|
||||
}
|
||||
if (auto v = j["exclusive"]; v.isBool()) {
|
||||
m.exclusive = v.asBool();
|
||||
}
|
||||
if (auto v = j["passthrough"]; v.isBool()) {
|
||||
m.passthrough = v.asBool();
|
||||
}
|
||||
if (auto v = j["visible"]; v.isBool()) {
|
||||
m.visible = v.asBool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Deserializer for JSON Object -> map<string compatible type, Value>
|
||||
* Assumes that all the values in the object are deserializable to the same type.
|
||||
*/
|
||||
template <typename Key, typename Value,
|
||||
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
|
||||
void from_json(const Json::Value& j, std::map<Key, Value>& 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} {
|
||||
@ -392,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
: output(w_output),
|
||||
config(w_config),
|
||||
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||
layer_{bar_layer::BOTTOM},
|
||||
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
@ -404,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
window.get_style_context()->add_class(config["name"].asString());
|
||||
window.get_style_context()->add_class(config["position"].asString());
|
||||
|
||||
if (config["layer"] == "top") {
|
||||
layer_ = bar_layer::TOP;
|
||||
} else if (config["layer"] == "overlay") {
|
||||
layer_ = bar_layer::OVERLAY;
|
||||
}
|
||||
|
||||
if (config["exclusive"].isBool()) {
|
||||
exclusive = config["exclusive"].asBool();
|
||||
} else if (layer_ == bar_layer::OVERLAY) {
|
||||
// swaybar defaults: overlay mode does not reserve an exclusive zone
|
||||
exclusive = false;
|
||||
}
|
||||
|
||||
bool passthrough = false;
|
||||
if (config["passthrough"].isBool()) {
|
||||
passthrough = config["passthrough"].asBool();
|
||||
} else if (layer_ == bar_layer::OVERLAY) {
|
||||
// swaybar defaults: overlay mode does not accept pointer events.
|
||||
passthrough = true;
|
||||
}
|
||||
|
||||
auto position = config["position"].asString();
|
||||
|
||||
if (position == "right" || position == "left") {
|
||||
@ -506,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
||||
}
|
||||
|
||||
surface_impl_->setLayer(layer_);
|
||||
surface_impl_->setExclusiveZone(exclusive);
|
||||
surface_impl_->setMargins(margins_);
|
||||
surface_impl_->setPassThrough(passthrough);
|
||||
surface_impl_->setPosition(position);
|
||||
surface_impl_->setSize(width, height);
|
||||
|
||||
/* Read custom modes if available */
|
||||
if (auto modes = config.get("modes", {}); modes.isObject()) {
|
||||
from_json(modes, configured_modes);
|
||||
}
|
||||
|
||||
/* Update "default" mode with the global bar options */
|
||||
from_json(config, configured_modes[MODE_DEFAULT]);
|
||||
|
||||
if (auto mode = config.get("mode", {}); mode.isString()) {
|
||||
setMode(config["mode"].asString());
|
||||
} else {
|
||||
setMode(MODE_DEFAULT);
|
||||
}
|
||||
|
||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||
|
||||
#if HAVE_SWAY
|
||||
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||
bar_id = Client::inst()->bar_id;
|
||||
if (auto id = config["id"]; id.isString()) {
|
||||
bar_id = id.asString();
|
||||
}
|
||||
if (bar_id.empty()) {
|
||||
bar_id = DEFAULT_BAR_ID;
|
||||
}
|
||||
try {
|
||||
_ipc_client = std::make_unique<BarIpcClient>(*this);
|
||||
} catch (const std::exception& exc) {
|
||||
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
setupWidgets();
|
||||
window.show_all();
|
||||
|
||||
@ -529,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||
}
|
||||
}
|
||||
|
||||
/* Need to define it here because of forward declared members */
|
||||
waybar::Bar::~Bar() = default;
|
||||
|
||||
void waybar::Bar::setMode(const std::string_view& mode) {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
auto style = window.get_style_context();
|
||||
/* remove styles added by previous setMode calls */
|
||||
style->remove_class("mode-"s + last_mode_);
|
||||
|
||||
auto it = configured_modes.find(mode);
|
||||
if (it != configured_modes.end()) {
|
||||
last_mode_ = mode;
|
||||
style->add_class("mode-"s + last_mode_);
|
||||
setMode(it->second);
|
||||
} else {
|
||||
spdlog::warn("Unknown mode \"{}\" requested", mode);
|
||||
last_mode_ = MODE_DEFAULT;
|
||||
style->add_class("mode-"s + last_mode_);
|
||||
setMode(configured_modes.at(MODE_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::Bar::setMode(const struct bar_mode& mode) {
|
||||
surface_impl_->setLayer(mode.layer);
|
||||
surface_impl_->setExclusiveZone(mode.exclusive);
|
||||
surface_impl_->setPassThrough(mode.passthrough);
|
||||
|
||||
if (mode.visible) {
|
||||
window.get_style_context()->remove_class("hidden");
|
||||
window.set_opacity(1);
|
||||
} else {
|
||||
window.get_style_context()->add_class("hidden");
|
||||
window.set_opacity(0);
|
||||
}
|
||||
surface_impl_->commit();
|
||||
}
|
||||
|
||||
void waybar::Bar::onMap(GdkEventAny*) {
|
||||
/*
|
||||
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
||||
@ -539,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
|
||||
|
||||
void waybar::Bar::setVisible(bool value) {
|
||||
visible = value;
|
||||
if (!visible) {
|
||||
window.get_style_context()->add_class("hidden");
|
||||
window.set_opacity(0);
|
||||
surface_impl_->setLayer(bar_layer::BOTTOM);
|
||||
} else {
|
||||
window.get_style_context()->remove_class("hidden");
|
||||
window.set_opacity(1);
|
||||
surface_impl_->setLayer(layer_);
|
||||
}
|
||||
surface_impl_->setExclusiveZone(exclusive && visible);
|
||||
surface_impl_->commit();
|
||||
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
|
||||
}
|
||||
|
||||
void waybar::Bar::toggle() { setVisible(!visible); }
|
||||
|
@ -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") |
|
||||
|
107
src/modules/sway/bar.cpp
Normal file
107
src/modules/sway/bar.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "modules/sway/bar.hpp"
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "bar.hpp"
|
||||
#include "modules/sway/ipc/ipc.hpp"
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
|
||||
{
|
||||
sigc::connection handle =
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
|
||||
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
|
||||
|
||||
handle.disconnect();
|
||||
}
|
||||
|
||||
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
|
||||
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
|
||||
|
||||
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
|
||||
// Launch worker
|
||||
ipc_.setWorker([this] {
|
||||
try {
|
||||
ipc_.handleEvent();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("BarIpcClient::handleEvent {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct swaybar_config parseConfig(const Json::Value& payload) {
|
||||
swaybar_config conf;
|
||||
if (auto id = payload["id"]; id.isString()) {
|
||||
conf.id = id.asString();
|
||||
}
|
||||
if (auto mode = payload["mode"]; mode.isString()) {
|
||||
conf.mode = mode.asString();
|
||||
}
|
||||
if (auto hs = payload["hidden_state"]; hs.isString()) {
|
||||
conf.hidden_state = hs.asString();
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
if (auto success = payload.get("success", true); !success.asBool()) {
|
||||
auto err = payload.get("error", "Unknown error");
|
||||
throw std::runtime_error(err.asString());
|
||||
}
|
||||
auto config = parseConfig(payload);
|
||||
onConfigUpdate(config);
|
||||
}
|
||||
|
||||
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
|
||||
try {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
|
||||
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
|
||||
return;
|
||||
}
|
||||
if (payload.isMember("visible_by_modifier")) {
|
||||
// visibility change for hidden bar
|
||||
signal_visible_(payload["visible_by_modifier"].asBool());
|
||||
} else {
|
||||
// configuration update
|
||||
auto config = parseConfig(payload);
|
||||
signal_config_(std::move(config));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("BarIpcClient::onEvent {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
|
||||
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
|
||||
bar_.bar_id,
|
||||
config.id,
|
||||
config.mode,
|
||||
config.hidden_state);
|
||||
bar_config_ = config;
|
||||
update();
|
||||
}
|
||||
|
||||
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
|
||||
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
|
||||
visible_by_modifier_ = visible_by_modifier;
|
||||
update();
|
||||
}
|
||||
|
||||
void BarIpcClient::update() {
|
||||
bool visible = visible_by_modifier_;
|
||||
if (bar_config_.mode == "invisible") {
|
||||
visible = false;
|
||||
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
|
||||
visible = true;
|
||||
}
|
||||
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::sway
|
24
test/GlibTestsFixture.hpp
Normal file
24
test/GlibTestsFixture.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <glibmm/main.h>
|
||||
/**
|
||||
* Minimal Glib application to be used for tests that require Glib main loop
|
||||
*/
|
||||
class GlibTestsFixture : public sigc::trackable {
|
||||
public:
|
||||
GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {}
|
||||
|
||||
void setTimeout(int timeout) {
|
||||
Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); },
|
||||
timeout);
|
||||
}
|
||||
|
||||
void run(std::function<void()> fn) {
|
||||
Glib::signal_idle().connect_once(fn);
|
||||
main_loop_->run();
|
||||
}
|
||||
|
||||
void quit() { main_loop_->quit(); }
|
||||
|
||||
protected:
|
||||
Glib::RefPtr<Glib::MainLoop> main_loop_;
|
||||
};
|
145
test/SafeSignal.cpp
Normal file
145
test/SafeSignal.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#define CATCH_CONFIG_RUNNER
|
||||
#include "util/SafeSignal.hpp"
|
||||
|
||||
#include <glibmm.h>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
#include "GlibTestsFixture.hpp"
|
||||
|
||||
using namespace waybar;
|
||||
|
||||
template <typename T>
|
||||
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
|
||||
/**
|
||||
* Basic sanity test for SafeSignal:
|
||||
* check that type deduction works, events are delivered and the order is right
|
||||
* Running this with -fsanitize=thread should not fail
|
||||
*/
|
||||
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") {
|
||||
const int NUM_EVENTS = 100;
|
||||
int count = 0;
|
||||
int last_value = 0;
|
||||
|
||||
SafeSignal<int, std::string> test_signal;
|
||||
|
||||
const auto main_tid = std::this_thread::get_id();
|
||||
std::thread producer;
|
||||
|
||||
// timeout the test in 500ms
|
||||
setTimeout(500);
|
||||
|
||||
test_signal.connect([&](auto val, auto str) {
|
||||
static_assert(std::is_same<int, decltype(val)>::value);
|
||||
static_assert(std::is_same<std::string, decltype(str)>::value);
|
||||
// check that we're in the same thread as the main loop
|
||||
REQUIRE(std::this_thread::get_id() == main_tid);
|
||||
// check event order
|
||||
REQUIRE(val == last_value + 1);
|
||||
|
||||
last_value = val;
|
||||
if (++count >= NUM_EVENTS) {
|
||||
this->quit();
|
||||
};
|
||||
});
|
||||
|
||||
run([&]() {
|
||||
// check that events from the same thread are delivered and processed synchronously
|
||||
test_signal.emit(1, "test");
|
||||
REQUIRE(count == 1);
|
||||
|
||||
// start another thread and generate events
|
||||
producer = std::thread([&]() {
|
||||
for (auto i = 2; i <= NUM_EVENTS; ++i) {
|
||||
test_signal.emit(i, "test");
|
||||
}
|
||||
});
|
||||
});
|
||||
producer.join();
|
||||
REQUIRE(count == NUM_EVENTS);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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<TestObject<int>> test_signal;
|
||||
|
||||
std::thread producer;
|
||||
|
||||
// timeout the test in 500ms
|
||||
setTimeout(500);
|
||||
|
||||
test_signal.connect([&](auto& val) {
|
||||
static_assert(std::is_same<TestObject<int>, remove_cvref_t<decltype(val)>>::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<int> 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);
|
||||
}
|
@ -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(),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user