#include "modules/mango/backend.hpp" #include #include #include #include #include #include #include #include #include #include #include "util/scoped_fd.hpp" namespace waybar::modules::mango { int IPC::connectToSocket() { const char* socket_path = getenv("MANGO_INSTANCE_SIGNATURE"); if (!socket_path) { throw std::runtime_error("Mango IPC: MANGO_INSTANCE_SIGNATURE not set"); } struct sockaddr_un addr; util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0)); if (fd == -1) throw std::runtime_error("socket() failed"); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { throw std::runtime_error("connect() failed"); } return fd.release(); } Json::Value IPC::sendCommand(const std::string& cmd) { util::ScopedFd fd(IPC::connectToSocket()); std::string full_cmd = cmd + "\n"; ssize_t total_written = 0; while (total_written < (ssize_t)full_cmd.size()) { ssize_t res = write(fd, full_cmd.c_str() + total_written, full_cmd.size() - total_written); if (res < 0) { if (errno == EINTR) continue; throw std::runtime_error("Failed to write command"); } total_written += res; } char buf[4096]; std::string response; while (true) { ssize_t n = read(fd, buf, sizeof(buf) - 1); if (n <= 0) { if (n == 0) break; if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; throw std::runtime_error("Read error"); } buf[n] = '\0'; response += buf; if (response.find('\n') != std::string::npos) break; } Json::Value root; std::istringstream iss(response); Json::CharReaderBuilder builder; std::string errors; if (!Json::parseFromStream(builder, iss, &root, &errors)) { throw std::runtime_error("JSON parse error: " + errors); } return root; } Json::Value IPC::send(const Json::Value& request) { if (!request.isMember("command")) { throw std::runtime_error("Mango IPC: request must have 'command' field"); } return sendCommand(request["command"].asString()); } void IPC::sendAsync(const Json::Value& request) { if (!request.isMember("command")) { spdlog::error("Mango IPC: request must have 'command' field"); return; } std::string cmd = request["command"].asString(); std::thread([cmd]() { try { IPC::sendCommand(cmd); } catch (const std::exception& e) { spdlog::error("IPC async send failed: {}", e.what()); } }).detach(); } IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); } IPC::~IPC() { if (sockfd_ != -1) close(sockfd_); if (ipc_thread_.joinable()) ipc_thread_.join(); } void IPC::startIPC() { sockfd_ = IPC::connectToSocket(); ipc_thread_ = std::thread([this]() { spdlog::info("Mango IPC thread started"); struct pollfd pfd; pfd.fd = sockfd_; pfd.events = POLLIN; const std::vector subs = {"watch all-monitors"}; for (const auto& cmd : subs) { if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() || write(sockfd_, "\n", 1) != 1) { spdlog::error("Failed to subscribe to {}", cmd); return; } } char buf[4096]; std::string buffer; while (true) { int ret = poll(&pfd, 1, 1000); if (ret == 0) continue; if (ret < 0) { if (errno == EINTR) continue; spdlog::error("IPC poll error: {}", strerror(errno)); break; } if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { spdlog::info("Mango IPC socket closed or invalid"); break; } if (pfd.revents & POLLIN) { ssize_t n = read(sockfd_, buf, sizeof(buf)); if (n == 0) { spdlog::info("Mango IPC connection closed"); break; } if (n < 0) { if (errno == EINTR) continue; spdlog::error("IPC read error: {}", strerror(errno)); break; } buffer.append(buf, n); size_t pos; while ((pos = buffer.find('\n')) != std::string::npos) { std::string line = buffer.substr(0, pos); buffer.erase(0, pos + 1); if (line.empty()) continue; try { parseIPC(line); } catch (const std::exception& e) { spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what()); } } } } }); } void IPC::parseIPC(const std::string& line) { Json::Value root; Json::CharReaderBuilder builder; std::string errors; std::istringstream iss(line); if (!Json::parseFromStream(builder, iss, &root, &errors)) { throw std::runtime_error("JSON parse error: " + errors); } if (root.isMember("monitors") && root["monitors"].isArray()) { for (const auto& mon : root["monitors"]) { handleMonitorUpdate(mon); } Json::Value active_monitor; for (const auto& mon : root["monitors"]) { if (mon["active"].asBool()) { active_monitor = mon; break; } } if (!active_monitor.isNull()) { const auto& active_client = active_monitor["active_client"]; updateFocusingClient(active_client); if (active_monitor.isMember("keyboardlayout")) { updateKeyboardLayout(active_monitor["keyboardlayout"].asString()); } if (active_monitor.isMember("keymode")) { std::lock_guard lock(data_mutex_); keymode_ = active_monitor["keymode"].asString(); } } std::vector handlers_to_notify; { std::lock_guard lock(callback_mutex_); for (auto& [ev, handler] : callbacks_) { if (ev == "monitor") { handlers_to_notify.push_back(handler); } } } for (auto* handler : handlers_to_notify) { handler->onEvent(root); } return; } spdlog::debug("Unhandled IPC message: {}", line); } std::unordered_map IPC::getMonitors() const { std::lock_guard lock(data_mutex_); return monitors_; } IPC& IPC::getInstance() { static IPC instance; return instance; } Json::Value IPC::getMonitor(const std::string& name) { std::lock_guard lock(data_mutex_); auto it = monitors_.find(name); if (it != monitors_.end()) { return it->second; } return Json::nullValue; } std::string IPC::getKeyboardLayout() const { std::lock_guard lock(data_mutex_); return keyboard_layout_; } std::string IPC::getKeymode() const { std::lock_guard lock(data_mutex_); return keymode_; } Json::Value IPC::getActiveClientForMonitor(const std::string& name) const { std::lock_guard lock(data_mutex_); auto it = monitors_.find(name); if (it != monitors_.end() && it->second.isMember("active_client")) { return it->second["active_client"]; } return Json::nullValue; } std::string IPC::getLayoutSymbolForMonitor(const std::string& name) const { std::lock_guard lock(data_mutex_); auto it = monitors_.find(name); if (it != monitors_.end() && it->second.isMember("layout_symbol")) { return it->second["layout_symbol"].asString(); } return {}; } void IPC::handleMonitorUpdate(const Json::Value& mon) { std::lock_guard lock(data_mutex_); monitors_[mon["name"].asString()] = mon; } void IPC::updateFocusingClient(const Json::Value& client) { { std::lock_guard lock(data_mutex_); active_client_ = client; if (client.isNull() || !client.isObject() || client["id"].isNull()) { focusing_client_id_ = 0; } else { focusing_client_id_ = client["id"].asUInt64(); clients_[focusing_client_id_] = client; } } } void IPC::updateKeyboardLayout(const std::string& layout) { { std::lock_guard lock(data_mutex_); keyboard_layout_ = layout; } } void IPC::registerForIPC(const std::string& ev, EventHandler* handler) { if (!handler) return; std::lock_guard lock(callback_mutex_); callbacks_.emplace_back(ev, handler); } void IPC::unregisterForIPC(EventHandler* handler) { if (!handler) return; std::lock_guard lock(callback_mutex_); for (auto it = callbacks_.begin(); it != callbacks_.end();) { if (it->second == handler) it = callbacks_.erase(it); else ++it; } } } // namespace waybar::modules::mango