From 64f70caa46359f772a9cd059200685dbfa4c18bc Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 5 Jul 2026 00:29:27 -0500 Subject: [PATCH] fix(niri): keep the initial IPC connect synchronous #5158 (6672e924) moved connectToSocket() off the constructing thread and into the detached IPC worker's own try/catch, so a missing NIRI_SOCKET no longer throws out of IPC::IPC(). That was needed to fix #5117 (the worker should reconnect instead of dying when an established stream drops), but it also meant the very first connection attempt can never fail anymore. Factory::makeModule()/Bar::getModules() rely on that constructor throwing to disable a module it can't construct. With niri/workspaces and niri/window always constructing successfully now, they get added to every bar regardless of which compositor is actually running, showing up as a permanently-empty widget next to the real workspace modules under Hyprland/Sway. Restore the old semantics for the first connection: connectToSocket() runs synchronously in IPC::IPC() again, so a missing socket still throws and the module gets disabled as before. Only a drop *after* that succeeds falls into the retrying reconnect loop, preserving the #5117 fix. --- include/modules/niri/backend.hpp | 2 +- src/modules/niri/backend.cpp | 36 ++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp index acbbf52e..94b6d66f 100644 --- a/include/modules/niri/backend.hpp +++ b/include/modules/niri/backend.hpp @@ -34,7 +34,7 @@ class IPC { unsigned keyboardLayoutCurrent() const { return keyboardLayoutCurrent_; } private: - void startIPC(); + void startIPC(int initial_socketfd); static int connectToSocket(); void parseIPC(const std::string&); diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 245bf2de..460754cc 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -21,7 +21,13 @@ namespace waybar::modules::niri { -IPC::IPC() { startIPC(); } +IPC::IPC() { + // Connect synchronously so a missing socket (this WM isn't the active + // compositor) throws here, same as before the reconnect loop below existed. + // That lets the module constructor fail and Factory disable the module, + // instead of the module always attaching with a permanently empty widget. + startIPC(connectToSocket()); +} IPC::~IPC() { running_ = false; } @@ -54,22 +60,30 @@ int IPC::connectToSocket() { return socketfd.release(); } -void IPC::startIPC() { +void IPC::startIPC(int initial_socketfd) { // will start IPC and relay events to parseIPC - std::thread([this]() { + std::thread([this, initial_socketfd]() { spdlog::info("Niri IPC starting"); - // Reconnect loop: if the event stream drops we back off briefly and - // re-establish the socket instead of leaving the module frozen forever. + bool have_initial_fd = true; + + // Reconnect loop: if the event stream drops *after* the initial connect + // above succeeded, we back off briefly and re-establish the socket + // instead of leaving the module frozen forever. while (running_) { int socketfd; - try { - socketfd = connectToSocket(); - } catch (std::exception& e) { - spdlog::error("Niri IPC: failed to connect: {}", e.what()); - std::this_thread::sleep_for(std::chrono::seconds(2)); - continue; + if (have_initial_fd) { + socketfd = initial_socketfd; + have_initial_fd = false; + } else { + try { + socketfd = connectToSocket(); + } catch (std::exception& e) { + spdlog::error("Niri IPC: failed to connect: {}", e.what()); + std::this_thread::sleep_for(std::chrono::seconds(2)); + continue; + } } auto unix_istream = Gio::UnixInputStream::create(socketfd, true);