diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 2665caf9..f506b0bf 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -2,10 +2,12 @@ #include +#include #include #include #include #include +#include #include "ipc.hpp" #include "util/SafeSignal.hpp" @@ -43,6 +45,14 @@ class Ipc { struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response recv(int fd); + // Re-establish the event socket and re-subscribe after sway drops us, backing + // off between attempts so we don't busy-loop while sway is unavailable. + void reconnectEvent(); + + std::string socketPath_; + std::vector subscribed_events_; + std::atomic running_{true}; + util::ScopedFd fd_; util::ScopedFd fd_event_; std::mutex mutex_; diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index dcbe7fa3..ce67279c 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -8,12 +8,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include "modules/sway/ipc/ipc.hpp" @@ -41,12 +43,15 @@ void sendAll(int fd, const char* data, size_t size, const char* what) { } // namespace Ipc::Ipc() { - const std::string socketPath = getSocketPath(); - fd_ = util::ScopedFd(open(socketPath)); - fd_event_ = util::ScopedFd(open(socketPath)); + socketPath_ = getSocketPath(); + fd_ = util::ScopedFd(open(socketPath_)); + fd_event_ = util::ScopedFd(open(socketPath_)); } Ipc::~Ipc() { + // Signal the worker before stopping it so an in-flight recv/reconnect bails + // out instead of trying to reconnect to a socket we're tearing down. + running_ = false; thread_.stop(); if (fd_ > 0) { @@ -191,11 +196,48 @@ void Ipc::subscribe(const std::string& payload) { if (res.payload != "{\"success\": true}") { throw std::runtime_error("Unable to subscribe ipc event"); } + // Remember the subscription so we can replay it if we have to reconnect. + subscribed_events_.push_back(payload); +} + +void Ipc::reconnectEvent() { + // Sway closed our event connection (typically because its send buffer filled + // up during an event flood). Re-establish the socket and re-subscribe to the + // same events, backing off between attempts so we don't busy-loop and peg a + // CPU while sway is unavailable or keeps dropping us. + while (running_) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + if (!running_) { + return; + } + try { + fd_event_.reset(open(socketPath_)); + for (const auto& payload : subscribed_events_) { + const auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload); + if (res.payload != "{\"success\": true}") { + throw std::runtime_error("Unable to re-subscribe ipc event"); + } + } + spdlog::info("Reconnected to sway IPC event socket"); + return; + } catch (const std::exception& e) { + spdlog::warn("Failed to reconnect to sway IPC ({}), retrying", e.what()); + } + } } void Ipc::handleEvent() { - const auto res = Ipc::recv(fd_event_); - signal_event.emit(res); + try { + const auto res = Ipc::recv(fd_event_); + signal_event.emit(res); + } catch (const std::exception& e) { + if (!running_) { + // The Ipc is being torn down; the socket was closed on purpose. + return; + } + spdlog::warn("Lost sway IPC event connection ({}), reconnecting", e.what()); + reconnectEvent(); + } } } // namespace waybar::modules::sway