#pragma once #include #include #include #include #include #include #include #include #include namespace waybar { /** * Thread-safe signal wrapper. * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. */ template struct SafeSignal : sigc::signal...)> { public: SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } template void emit(EmitArgs&&... args) { if (main_tid_ == std::this_thread::get_id()) { /* * Bypass the queue if the method is called the main thread. * Ensures that events emitted from the main thread are processed synchronously and saves a * few CPU cycles on locking/queuing. * As a downside, this makes main thread events prioritized over the other threads and * disrupts chronological order. */ signal_t::emit(std::forward(args)...); } else { { std::unique_lock lock(mutex_); queue_.emplace(std::forward(args)...); } dp_.emit(); } } template inline void operator()(EmitArgs&&... args) { emit(std::forward(args)...); } protected: using signal_t = sigc::signal...)>; using slot_t = decltype(std::declval().make_slot()); using arg_tuple_t = std::tuple...>; // ensure that unwrapped methods are not accessible using signal_t::emit; using signal_t::make_slot; void handle_event() { for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { auto args = queue_.front(); queue_.pop(); lock.unlock(); std::apply(cached_fn_, args); } } Glib::Dispatcher dp_; std::mutex mutex_; std::queue queue_; const std::thread::id main_tid_ = std::this_thread::get_id(); // cache functor for signal emission to avoid recreating it on each event const slot_t cached_fn_ = make_slot(); }; } // namespace waybar