From 55560980b8f085402f9e320c2d15512cf4909e0d Mon Sep 17 00:00:00 2001 From: Peter Wilhelmsson <2hdddg@gmail.com> Date: Sun, 21 Jan 2024 16:23:11 +0100 Subject: [PATCH] Fix crash in audio source Pulse Audio source is currently the only source that has its own thread to poll for changes. Embedded Lua is not thread safe so at some cases publishing audio source changes on that thread caused the program to crash. This refactors to make sure that publishing of source states to Lua always happens on main thread. --- src/DateTimeSources.cpp | 9 ++++++--- src/DateTimeSources.h | 2 ++ src/MainLoop.cpp | 13 +++++++------ src/MainLoop.h | 2 +- src/Manager.cpp | 27 ++++++-------------------- src/Manager.h | 23 +++++++--------------- src/NetworkSource.cpp | 21 +++++++++++--------- src/NetworkSource.h | 9 +++------ src/Output.cpp | 2 +- src/PowerSource.cpp | 27 ++++++++++++++------------ src/PowerSource.h | 9 +++------ src/PulseAudioSource.cpp | 28 +++++++++++++++++---------- src/PulseAudioSource.h | 19 +++++------------- src/Seat.cpp | 16 +++++++-------- src/Seat.h | 5 +---- src/Sources.cpp | 24 +++++++++++++++-------- src/Sources.h | 25 ++++++++++++++++-------- src/SwayCompositor.cpp | 42 ++++++++++++++++++++++------------------ src/SwayCompositor.h | 15 ++++++++------ src/main.cpp | 38 ++++++++++++++++++++++-------------- 20 files changed, 184 insertions(+), 172 deletions(-) diff --git a/src/DateTimeSources.cpp b/src/DateTimeSources.cpp index 72d9e95..0ded7e5 100644 --- a/src/DateTimeSources.cpp +++ b/src/DateTimeSources.cpp @@ -17,7 +17,9 @@ std::shared_ptr DateSource::Create() { } void DateSource::Evaluate() { - // TODO: Check dirty + // TODO: To save some redraws the redraw flag should check if date has changed since last draw + m_published = true; // No need to publish + m_drawn = false; } std::shared_ptr TimeSource::Create(MainLoop& mainLoop, @@ -53,7 +55,8 @@ bool TimeSource::OnRead() { // Either block or no events return false; } - m_sourceDirtyFlag = true; + m_published = true; // No need to publish + m_drawn = false; m_dateSource->Evaluate(); - return m_sourceDirtyFlag; + return !m_drawn; } diff --git a/src/DateTimeSources.h b/src/DateTimeSources.h index ce8bd55..e135e1d 100644 --- a/src/DateTimeSources.h +++ b/src/DateTimeSources.h @@ -8,6 +8,7 @@ class DateSource : public Source { static std::shared_ptr Create(); void Evaluate(); virtual ~DateSource() {} + void Publish(const std::string_view, ScriptContext&) override {} private: DateSource() {} @@ -19,6 +20,7 @@ class TimeSource : public Source, public IoHandler { std::shared_ptr dateSource); virtual ~TimeSource(); virtual bool OnRead() override; + void Publish(const std::string_view, ScriptContext&) override {} private: TimeSource(int fd, std::shared_ptr dateSource) diff --git a/src/MainLoop.cpp b/src/MainLoop.cpp index 628b244..6cd4d78 100644 --- a/src/MainLoop.cpp +++ b/src/MainLoop.cpp @@ -52,10 +52,8 @@ void MainLoop::Run() { } } } - if (anyDirty) { - for (auto& handler : m_batchHandlers) { - handler->OnBatchProcessed(); - } + if (anyDirty && m_batchHandler) { + m_batchHandler->OnBatchProcessed(); } } while (m_polls.size() > 0); } @@ -66,8 +64,11 @@ void MainLoop::Register(int fd, const std::string_view name, std::shared_ptr ioBatchHandler) { - m_batchHandlers.push_back(ioBatchHandler); +void MainLoop::RegisterBatchHandler(std::shared_ptr batchHandler) { + if (m_batchHandler) { + spdlog::error("Only one batch handler supported"); + } + m_batchHandler = batchHandler; } void MainLoop::Wakeup() { diff --git a/src/MainLoop.h b/src/MainLoop.h index cbdc351..f308502 100644 --- a/src/MainLoop.h +++ b/src/MainLoop.h @@ -38,5 +38,5 @@ class MainLoop { std::mutex m_wakupMutex; std::vector m_polls; std::map> m_handlers; - std::vector> m_batchHandlers; + std::shared_ptr m_batchHandler; }; diff --git a/src/Manager.cpp b/src/Manager.cpp index ca11251..94f5217 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -3,16 +3,8 @@ #include "spdlog/spdlog.h" #include "src/Registry.h" -std::shared_ptr Manager::Create(std::shared_ptr registry, - std::string_view sourceName, MainLoop& mainLoop, - std::unique_ptr sources, - std::shared_ptr scriptContext) { - auto manager = std::shared_ptr( - new Manager(registry, sourceName, std::move(sources), scriptContext)); - // Register as a source - manager->m_sources->Register(sourceName, manager); - // Register source batch handler - mainLoop.RegisterBatchHandler(manager); +std::shared_ptr Manager::Create(std::shared_ptr registry) { + auto manager = std::shared_ptr(new Manager(registry)); // Register click handler if (!registry->seat) { spdlog::error("No seat in registry"); @@ -29,39 +21,32 @@ void Manager::ClickSurface(wl_surface* surface, int x, int y) { } void Manager::OnBatchProcessed() { + // Always publish sources + m_sources->PublishAll(); // No need to redraw when not visible and not in transition if (!m_visibilityChanged && !m_isVisible) return; - spdlog::debug("Processing batch of dirty sources"); if (m_visibilityChanged) { m_visibilityChanged = false; if (m_isVisible) { - m_sources->DirtyAll(); + m_sources->ForceRedraw(); } else { m_registry->BorrowOutputs().Hide(*m_registry); return; } } m_registry->BorrowOutputs().Draw(*m_registry, *m_sources); - m_sources->CleanAll(); -} - -void Manager::Publish(const Displays& displays) { - m_scriptContext->Publish(m_sourceName, displays); - m_sourceDirtyFlag = true; - OnBatchProcessed(); + m_sources->SetAllDrawn(); } void Manager::Hide() { m_isVisible = false; m_visibilityChanged = true; - m_sourceDirtyFlag = true; OnBatchProcessed(); } void Manager::Show() { m_isVisible = true; m_visibilityChanged = true; - m_sourceDirtyFlag = true; OnBatchProcessed(); } diff --git a/src/Manager.h b/src/Manager.h index 58ead52..fdb2cbb 100644 --- a/src/Manager.h +++ b/src/Manager.h @@ -5,35 +5,26 @@ #include "src/ScriptContext.h" #include "src/Sources.h" -class Manager : public IoBatchHandler, public Source { +class Manager : public IoBatchHandler { public: - static std::shared_ptr Create(std::shared_ptr registry, - std::string_view sourceName, MainLoop& mainLoop, - std::unique_ptr sources, - std::shared_ptr scriptContext); + static std::shared_ptr Create(std::shared_ptr registry); + void SetSources(std::unique_ptr sources) { m_sources = std::move(sources); } virtual ~Manager() {} - // When a batch of IO events has been processed and sources are potentially dirty + // When a batch of IO events has been processed and sources needs to be published and/or needs + // to redrawn void OnBatchProcessed() override; - // Used by compositor implementation + // Compositors tells manager when the overlays should be visible void Show(); void Hide(); - void Publish(const Displays& displays); void ClickSurface(wl_surface* surface, int x, int y); private: - Manager(std::shared_ptr registry, std::string_view sourceName, - std::unique_ptr sources, std::shared_ptr scriptContext) - : m_registry(registry), - m_sourceName(sourceName), - m_sources(std::move(sources)), - m_scriptContext(scriptContext) {} + Manager(std::shared_ptr registry) : m_registry(registry) {} std::shared_ptr m_registry; - std::string m_sourceName; bool m_isVisible; bool m_visibilityChanged; std::unique_ptr m_sources; - std::shared_ptr m_scriptContext; }; diff --git a/src/NetworkSource.cpp b/src/NetworkSource.cpp index d0d45b5..4a0d3a8 100644 --- a/src/NetworkSource.cpp +++ b/src/NetworkSource.cpp @@ -30,8 +30,7 @@ static char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen) { return s; } -std::shared_ptr NetworkSource::Create(std::string_view name, MainLoop &mainLoop, - std::shared_ptr scriptContext) { +std::shared_ptr NetworkSource::Create(MainLoop &mainLoop) { auto sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock < 0) { return nullptr; @@ -48,8 +47,8 @@ std::shared_ptr NetworkSource::Create(std::string_view name, Main spdlog::error("Failed to set timer: {}", strerror(errno)); return nullptr; } - auto source = std::shared_ptr(new NetworkSource(name, sock, fd, scriptContext)); - mainLoop.Register(fd, name, source); + auto source = std::shared_ptr(new NetworkSource(sock, fd)); + mainLoop.Register(fd, "NetworkSource", source); return source; } @@ -90,10 +89,9 @@ void NetworkSource::ReadState() { network.address = get_ip_str(addr, ip, sizeof(ip)); networks.push_back(std::move(network)); } - m_sourceDirtyFlag = true; // m_networks != networks; - if (m_sourceDirtyFlag) { + if (m_networks != networks) { + m_drawn = m_published = false; m_networks = networks; - m_scriptContext->Publish(m_name, m_networks); } } @@ -105,6 +103,11 @@ bool NetworkSource::OnRead() { // Either block or no events return false; } - ReadState(); - return m_sourceDirtyFlag; + return !m_published; +} + +void NetworkSource::Publish(const std::string_view sourceName, ScriptContext &scriptContext) { + if (m_published) return; + scriptContext.Publish(sourceName, m_networks); + m_published = true; } diff --git a/src/NetworkSource.h b/src/NetworkSource.h index 2254b48..27031ac 100644 --- a/src/NetworkSource.h +++ b/src/NetworkSource.h @@ -8,19 +8,16 @@ class NetworkSource : public Source, public IoHandler { public: - static std::shared_ptr Create(std::string_view name, MainLoop& mainLoop, - std::shared_ptr scriptContext); + static std::shared_ptr Create(MainLoop& mainLoop); void Initialize(); void ReadState(); virtual bool OnRead() override; + void Publish(const std::string_view sourceName, ScriptContext& scriptContext) override; virtual ~NetworkSource() { close(m_timerfd); } private: - NetworkSource(std::string_view name, int socket, int timerfd, - std::shared_ptr scriptContext) - : m_name(name), m_socket(socket), m_timerfd(timerfd), m_scriptContext(scriptContext) {} + NetworkSource(int socket, int timerfd) : m_socket(socket), m_timerfd(timerfd) {} - const std::string m_name; int m_socket; int m_timerfd; std::shared_ptr m_scriptContext; diff --git a/src/Output.cpp b/src/Output.cpp index ee4da0a..16582b2 100644 --- a/src/Output.cpp +++ b/src/Output.cpp @@ -132,7 +132,7 @@ void Outputs::Draw(const Registry ®istry, const Sources &sources) { for (const auto &panelConfig : m_config->panels) { bool dirty = false; for (const auto &widgetConfig : panelConfig.widgets) { - if (sources.IsDirty(widgetConfig.sources)) { + if (sources.NeedsRedraw(widgetConfig.sources)) { dirty = true; break; } diff --git a/src/PowerSource.cpp b/src/PowerSource.cpp index 418d860..501430f 100644 --- a/src/PowerSource.cpp +++ b/src/PowerSource.cpp @@ -8,8 +8,7 @@ #include #include -std::shared_ptr PowerSource::Create(std::string_view name, MainLoop& mainLoop, - std::shared_ptr scriptContext) { +std::shared_ptr PowerSource::Create(MainLoop& mainLoop) { // Use non blocking to make sure we never hang on read auto fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (fd == -1) { @@ -23,15 +22,14 @@ std::shared_ptr PowerSource::Create(std::string_view name, MainLoop spdlog::error("Failed to set timer: {}", strerror(errno)); return nullptr; } - auto source = std::shared_ptr(new PowerSource(name, fd, scriptContext)); - mainLoop.Register(fd, name, source); + auto source = std::shared_ptr(new PowerSource(fd)); + mainLoop.Register(fd, "PowerSource", source); return source; } bool PowerSource::Initialize() { - // Publish initial value to make sure that something is published - m_scriptContext->Publish(m_name, - PowerState{.IsPluggedIn = false, .IsCharging = false, .Capacity = 0}); + m_drawn = m_published = false; + m_sourceState = PowerState{.IsPluggedIn = false, .IsCharging = false, .Capacity = 0}; // TODO: Probe that these exists m_ac = "/sys/class/power_supply/AC/online"; // TODO: Always BAT0? @@ -91,12 +89,11 @@ void PowerSource::ReadState() { if (maybeString) { state.IsPluggedIn = std::stoi(*maybeString) != 0; } - m_sourceDirtyFlag = state != m_sourceState; - if (m_sourceDirtyFlag) { - m_sourceState = state; - m_scriptContext->Publish(m_name, m_sourceState); + if (state != m_sourceState) { spdlog::info("Power status changed, capacity {}, charging {}, plugged in {}", state.Capacity, state.IsCharging, state.IsPluggedIn); + m_sourceState = state; + m_drawn = m_published = false; } } @@ -111,5 +108,11 @@ bool PowerSource::OnRead() { return false; } ReadState(); - return m_sourceDirtyFlag; + return !m_published; +} + +void PowerSource::Publish(const std::string_view sourceName, ScriptContext& scriptContext) { + if (m_published) return; + scriptContext.Publish(sourceName, m_sourceState); + m_published = true; } diff --git a/src/PowerSource.h b/src/PowerSource.h index 7f299a9..d691756 100644 --- a/src/PowerSource.h +++ b/src/PowerSource.h @@ -8,21 +8,18 @@ class PowerSource : public Source, public IoHandler { public: - static std::shared_ptr Create(std::string_view name, MainLoop& mainLoop, - std::shared_ptr scriptContext); + static std::shared_ptr Create(MainLoop& mainLoop); bool Initialize(); void ReadState(); virtual bool OnRead() override; + void Publish(const std::string_view sourceName, ScriptContext& scriptContext) override; virtual ~PowerSource(); private: - PowerSource(std::string_view name, int fd, std::shared_ptr scriptContext) - : m_name(name), m_timerfd(fd), m_scriptContext(scriptContext) {} - const std::string m_name; + PowerSource(int fd) : m_timerfd(fd) {} std::filesystem::path m_batteryCapacity; std::filesystem::path m_batteryStatus; std::filesystem::path m_ac; int m_timerfd; - std::shared_ptr m_scriptContext; PowerState m_sourceState; }; diff --git a/src/PulseAudioSource.cpp b/src/PulseAudioSource.cpp index 56555be..16b0e9d 100644 --- a/src/PulseAudioSource.cpp +++ b/src/PulseAudioSource.cpp @@ -41,9 +41,7 @@ static void on_subscribe(pa_context* ctx, pa_subscription_event_type_t event_and } } -std::unique_ptr PulseAudioSource::Create( - std::string_view name, std::shared_ptr zenMainloop, - std::shared_ptr scriptContext) { +std::unique_ptr PulseAudioSource::Create(std::shared_ptr zenMainloop) { auto mainloop = pa_threaded_mainloop_new(); if (!mainloop) return nullptr; pa_threaded_mainloop_lock(mainloop); @@ -59,7 +57,7 @@ std::unique_ptr PulseAudioSource::Create( return nullptr; } auto backend = std::unique_ptr( - new PulseAudioSource(name, zenMainloop, mainloop, scriptContext, api, context)); + new PulseAudioSource(zenMainloop, mainloop, api, context)); // From now on the backend will free on error if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { @@ -144,10 +142,20 @@ void PulseAudioSource::OnSinkChange(const pa_sink_info* info) { break; } } - if (newState == m_sourceState) return; - spdlog::debug("Audio source is dirty"); - m_sourceDirtyFlag = true; - m_sourceState = newState; - m_scriptContext->Publish(m_name, m_sourceState); - m_zenMainloop->Wakeup(); + { + std::lock_guard lock(m_mutex); + if (newState == m_sourceState) return; + spdlog::debug("Audio source is dirty"); + m_drawn = m_published = false; + m_sourceState = newState; + m_zenMainloop->Wakeup(); + } +} + +// This is invoked on main thread. Can not publish on another thread +void PulseAudioSource::Publish(const std::string_view sourceName, ScriptContext& scriptContext) { + std::lock_guard lock(m_mutex); + if (m_published) return; + scriptContext.Publish(sourceName, m_sourceState); + m_published = true; } diff --git a/src/PulseAudioSource.h b/src/PulseAudioSource.h index ab7270d..795eafe 100644 --- a/src/PulseAudioSource.h +++ b/src/PulseAudioSource.h @@ -11,34 +11,25 @@ struct pa_threaded_mainloop; struct pa_mainloop_api; struct pa_server_info; struct pa_sink_info; -struct pa_source_info; class PulseAudioSource : public Source { public: - static std::unique_ptr Create(std::string_view name, - std::shared_ptr zenMainloop, - std::shared_ptr scriptContext); + static std::unique_ptr Create(std::shared_ptr zenMainloop); virtual ~PulseAudioSource(); void OnStateChange(); void OnServerChange(const pa_server_info*); void OnSinkChange(const pa_sink_info*); + void Publish(const std::string_view sourceName, ScriptContext& scriptContext) override; private: - PulseAudioSource(std::string_view name, std::shared_ptr mainloop, - pa_threaded_mainloop* mainLoop, std::shared_ptr scriptContext, + PulseAudioSource(std::shared_ptr mainloop, pa_threaded_mainloop* mainLoop, pa_mainloop_api* api, pa_context* ctx) - : m_name(name), - m_zenMainloop(mainloop), - m_mainLoop(mainLoop), - m_scriptContext(scriptContext), - m_api(api), - m_ctx(ctx) {} - const std::string m_name; + : m_zenMainloop(mainloop), m_mainLoop(mainLoop), m_api(api), m_ctx(ctx) {} std::shared_ptr m_zenMainloop; pa_threaded_mainloop* m_mainLoop; - std::shared_ptr m_scriptContext; pa_mainloop_api* m_api; pa_context* m_ctx; AudioState m_sourceState; + std::mutex m_mutex; }; diff --git a/src/Seat.cpp b/src/Seat.cpp index 087a840..03d5425 100644 --- a/src/Seat.cpp +++ b/src/Seat.cpp @@ -45,6 +45,7 @@ static const wl_pointer_listener pointer_listener = { .axis_source = nullptr, .axis_stop = nullptr, .axis_discrete = nullptr, + // Version > supported //.axis_value120 = nullptr, //.axis_relative_direction = nullptr, }; @@ -102,18 +103,17 @@ std::unique_ptr Keyboard::Create(std::shared_ptr mainloop, w } void Keyboard::SetLayout(const char* layout) { - m_sourceDirtyFlag = m_sourceState.layout != layout; - if (m_sourceDirtyFlag) { + if (m_sourceState.layout != layout) { + m_drawn = m_published = false; m_sourceState.layout = layout; - if (m_scriptContext) m_scriptContext->Publish(m_sourceName, m_sourceState); m_mainloop->Wakeup(); } } -void Keyboard::SetScriptContext(const std::string_view sourceName, - std::shared_ptr scriptContext) { - m_sourceName = sourceName; - m_scriptContext = scriptContext; - m_scriptContext->Publish(m_sourceName, m_sourceState); + +void Keyboard::Publish(const std::string_view sourceName, ScriptContext& scriptContext) { + if (m_published) return; + scriptContext.Publish(sourceName, m_sourceState); + m_published = true; } std::unique_ptr Seat::Create(std::shared_ptr mainLoop, wl_seat* wlseat) { diff --git a/src/Seat.h b/src/Seat.h index 9140744..00c0e96 100644 --- a/src/Seat.h +++ b/src/Seat.h @@ -21,15 +21,12 @@ class Keyboard : public Source { } static std::unique_ptr Create(std::shared_ptr mainloop, wl_seat* seat); void SetLayout(const char* layout); - void SetScriptContext(const std::string_view name, - std::shared_ptr scriptContext); + void Publish(const std::string_view sourceName, ScriptContext& scriptContext) override; private: std::shared_ptr m_mainloop; wl_keyboard* m_wlkeyboard; KeyboardState m_sourceState; - std::string m_sourceName; - std::shared_ptr m_scriptContext; }; class Pointer { diff --git a/src/Sources.cpp b/src/Sources.cpp index ad90d5d..8ff3aab 100644 --- a/src/Sources.cpp +++ b/src/Sources.cpp @@ -2,30 +2,38 @@ #include "spdlog/spdlog.h" -std::unique_ptr Sources::Create() { return std::unique_ptr(new Sources()); } +std::unique_ptr Sources::Create(std::unique_ptr scriptContext) { + return std::unique_ptr(new Sources(std::move(scriptContext))); +} void Sources::Register(std::string_view name, std::shared_ptr source) { m_sources[std::string(name)] = source; } -bool Sources::IsDirty(const std::set sources) const { +bool Sources::NeedsRedraw(const std::set sources) const { for (const auto& name : sources) { - if (m_sources.contains(name) && m_sources.at(name)->IsSourceDirty()) { - spdlog::trace("Source {} is dirty", name); + if (m_sources.contains(name) && !m_sources.at(name)->IsDrawn()) { + spdlog::trace("Source {} needs render", name); return true; } } return false; } -void Sources::CleanAll() { +void Sources::SetAllDrawn() { + for (auto const& source : m_sources) { + source.second->SetDrawn(); + } +} + +void Sources::ForceRedraw() { for (auto const& source : m_sources) { - source.second->CleanDirtySource(); + source.second->ClearDrawn(); } } -void Sources::DirtyAll() { +void Sources::PublishAll() { for (auto const& source : m_sources) { - source.second->ForceDirtySource(); + source.second->Publish(source.first, *m_scriptContext); } } diff --git a/src/Sources.h b/src/Sources.h index 90c6501..7a739e5 100644 --- a/src/Sources.h +++ b/src/Sources.h @@ -5,27 +5,36 @@ #include #include +#include "src/ScriptContext.h" + class Source { public: - virtual bool IsSourceDirty() const { return m_sourceDirtyFlag; } - virtual void CleanDirtySource() { m_sourceDirtyFlag = false; } - virtual void ForceDirtySource() { m_sourceDirtyFlag = true; } + void SetDrawn() { m_drawn = true; } + void ClearDrawn() { m_drawn = false; } + bool IsDrawn() { return m_drawn; } + + virtual void Publish(const std::string_view sourceName, ScriptContext& scriptContext) = 0; virtual ~Source() {} protected: - bool m_sourceDirtyFlag; + bool m_drawn; // Set to false to indicate that source needs to be redrawn + bool m_published; // Set to false to indicate that there is a new state to be published }; // Maintains set of sources class Sources { public: - static std::unique_ptr Create(); + static std::unique_ptr Create(std::unique_ptr scriptContext); void Register(std::string_view name, std::shared_ptr source); bool IsRegistered(const std::string& name) { return m_sources.find(name) != m_sources.end(); } - void CleanAll(); - void DirtyAll(); - bool IsDirty(const std::set sources) const; + void SetAllDrawn(); + void ForceRedraw(); + bool NeedsRedraw(const std::set sources) const; + void PublishAll(); private: + Sources(std::unique_ptr scriptContext) + : m_scriptContext(std::move(scriptContext)) {} std::map> m_sources; + std::unique_ptr m_scriptContext; }; diff --git a/src/SwayCompositor.cpp b/src/SwayCompositor.cpp index d887d32..cdfd8cc 100644 --- a/src/SwayCompositor.cpp +++ b/src/SwayCompositor.cpp @@ -132,20 +132,20 @@ static void ParseApplication(Workspace &workspace, nlohmann::basic_json<> applic workspace.applications.push_back(std::move(application)); } -static void ParseTree(const std::string &payload, Manager &manager) { +static std::optional ParseTree(const std::string &payload) { auto rootNode = json::parse(payload, Filter, false /*ignoring exceptions*/); if (rootNode.is_discarded()) { spdlog::error("Failed to parse Sway tree"); - return; + return {}; } if (!IsNodeType(rootNode, "root")) { spdlog::error("Sway tree root node should be of value root"); - return; + return {}; } auto outputNodes = rootNode["nodes"]; if (!IsArray(outputNodes)) { spdlog::error("Sway tree has invalid nodes"); - return; + return {}; } // Iterate over displays/outputs Displays displays; @@ -191,11 +191,10 @@ static void ParseTree(const std::string &payload, Manager &manager) { } displays.push_back(std::move(display)); } - manager.Publish(displays); + return displays; } -std::shared_ptr SwayCompositor::Connect(MainLoop &mainLoop, - std::shared_ptr manager) { +std::shared_ptr SwayCompositor::Connect(MainLoop &mainLoop, Visibility visibility) { auto path = getenv("SWAYSOCK"); spdlog::debug("Connecting to sway at {}", path); auto fd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -211,7 +210,7 @@ std::shared_ptr SwayCompositor::Connect(MainLoop &mainLoop, close(fd); return nullptr; } - auto t = std::shared_ptr(new SwayCompositor(fd, manager)); + auto t = std::shared_ptr(new SwayCompositor(fd, visibility)); mainLoop.Register(fd, "Sway", t); t->Initialize(); return t; @@ -250,13 +249,16 @@ bool SwayCompositor::OnRead() { m_payload.resize(len + 1); auto msg = Message(*((uint32_t *)(hdr + MAGIC_LENGTH + 4))); read(m_fd, m_payload.data(), len); - bool isDirty = false; switch (msg) { - case Message::GET_TREE: + case Message::GET_TREE: { spdlog::trace("Received sway tree"); - ParseTree(m_payload, *m_manager); - isDirty = true; + auto maybeDisplays = ParseTree(m_payload); + if (maybeDisplays) { + m_displays = std::move(*maybeDisplays); + m_drawn = m_published = false; + } // else, error! break; + } case Message::SUBSCRIBE: spdlog::debug("Sway subscriptions confirmed"); break; @@ -274,12 +276,8 @@ bool SwayCompositor::OnRead() { bool visible; ParseBarStateUpdateEvent(m_payload, visible); spdlog::debug("Sway bar state event, visible: {}", visible); - if (visible) { - m_manager->Show(); - } else { - m_manager->Hide(); - } - isDirty = true; + m_visibility(visible); + m_drawn = m_published = false; break; case Message::EVENT_SHUTDOWN: spdlog::trace("Sway shutdown event"); @@ -288,5 +286,11 @@ bool SwayCompositor::OnRead() { spdlog::error("Received unhandled sway message: {}", (uint32_t)msg); break; } - return isDirty; + // True if state changed + return !m_published; +} + +void SwayCompositor::Publish(const std::string_view sourceName, ScriptContext &scriptContext) { + scriptContext.Publish(sourceName, m_displays); + m_published = true; } diff --git a/src/SwayCompositor.h b/src/SwayCompositor.h index 627d760..f4f911c 100644 --- a/src/SwayCompositor.h +++ b/src/SwayCompositor.h @@ -3,20 +3,23 @@ #include #include "MainLoop.h" -#include "src/Manager.h" +#include "src/Sources.h" -class SwayCompositor : public IoHandler { +using Visibility = std::function; + +class SwayCompositor : public IoHandler, public Source { public: - static std::shared_ptr Connect(MainLoop& mainLoop, - std::shared_ptr manager); + static std::shared_ptr Connect(MainLoop& mainLoop, Visibility visibility); virtual ~SwayCompositor(); virtual bool OnRead() override; + void Publish(const std::string_view sourceName, ScriptContext& scriptContext) override; private: void Initialize(); - SwayCompositor(int fd, std::shared_ptr manager) : m_fd(fd), m_manager(manager) {} + SwayCompositor(int fd, Visibility visibility) : m_fd(fd), m_visibility(visibility) {} int m_fd; - std::shared_ptr m_manager; std::string m_payload; + Displays m_displays; + Visibility m_visibility; }; diff --git a/src/main.cpp b/src/main.cpp index ed4600c..bb64299 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,8 +42,7 @@ static const std::optional ProbeForConfig(int argc, char* } static void InitializeSource(const std::string& source, Sources& sources, - std::shared_ptr mainLoop, - std::shared_ptr scriptContext, const Registry& registry, + std::shared_ptr mainLoop, const Registry& registry, const Configuration config) { if (source == "date" || source == "time") { // Date time sources @@ -58,7 +57,7 @@ static void InitializeSource(const std::string& source, Sources& sources, return; } if (source == "networks") { - auto networkSource = NetworkSource::Create(source, *mainLoop, scriptContext); + auto networkSource = NetworkSource::Create(*mainLoop); if (!networkSource) { spdlog::error("Failed to initialize network source"); return; @@ -70,7 +69,7 @@ static void InitializeSource(const std::string& source, Sources& sources, if (source == "audio") { switch (config.audio.soundServer) { case SoundServer::PulseAudio: { - auto audioSource = PulseAudioSource::Create(source, mainLoop, scriptContext); + auto audioSource = PulseAudioSource::Create(mainLoop); if (!audioSource) { spdlog::error("Failed to initialize PulseAudio source"); return; @@ -84,7 +83,7 @@ static void InitializeSource(const std::string& source, Sources& sources, } } if (source == "power") { - auto powerSource = PowerSource::Create(source, *mainLoop, scriptContext); + auto powerSource = PowerSource::Create(*mainLoop); if (!powerSource) { spdlog::error("Failed to initialize battery source"); return; @@ -97,7 +96,6 @@ static void InitializeSource(const std::string& source, Sources& sources, if (source == "keyboard") { if (registry.seat && registry.seat->keyboard) { sources.Register(source, registry.seat->keyboard); - registry.seat->keyboard->SetScriptContext(source, scriptContext); } else { spdlog::warn("No keyboard source"); } @@ -110,7 +108,7 @@ int main(int argc, char* argv[]) { // Environment variable configurable logging spdlog::cfg::load_env_levels(); // Initialize Lua context - auto scriptContext = std::shared_ptr(ScriptContext::Create()); + auto scriptContext = ScriptContext::Create(); if (!scriptContext) { spdlog::error("Failed to create script context"); return -1; @@ -143,36 +141,48 @@ int main(int argc, char* argv[]) { return -1; } // Initialize sources - auto sources = Sources::Create(); + auto sources = Sources::Create(std::move(scriptContext)); + scriptContext = nullptr; for (auto panelConfig : config->panels) { // Check what sources are needed for the widgets in the panel for (const auto& widgetConfig : panelConfig.widgets) { for (const auto& source : widgetConfig.sources) { // Initialize source if not already done if (!sources->IsRegistered(source)) { - InitializeSource(source, *sources, mainLoop, scriptContext, *registry, *config); + InitializeSource(source, *sources, mainLoop, *registry, *config); } } } } // Manager handles displays and redrawing - auto manager = - Manager::Create(registry, "displays", *mainLoop, std::move(sources), scriptContext); + std::shared_ptr manager = Manager::Create(registry); // Initialize compositor switch (config->displays.compositor) { case Compositor::Sway: { - auto sway = SwayCompositor::Connect(*mainLoop, manager); + auto sway = SwayCompositor::Connect(*mainLoop, [manager](bool visible) { + if (visible) { + manager->Show(); + } else { + manager->Hide(); + } + }); if (!sway) { spdlog::error("Failed to connect to Sway"); return -1; } - // Leave the rest to main loop - mainLoop->Run(); + sources->Register("displays", sway); break; } default: spdlog::error("Unsupported window manager"); break; } + // Make sure that an initial state of all sources are published + sources->PublishAll(); + // Let over control to mainloop and manager + manager->SetSources(std::move(sources)); + sources = nullptr; + mainLoop->RegisterBatchHandler(manager); + mainLoop->Run(); return 0; }