#include "HelperIPC.h" #include "HelperPluginHost.h" #include "IPCProtocol.h" #include #include #include #include #include #include "json.hpp" using json = nlohmann::json; HelperIPC::HelperIPC(int parentPid, HelperPluginHost& host) : parentPid(parentPid), host(host) { signal(SIGPIPE, SIG_IGN); } HelperIPC::~HelperIPC() { disconnect(); } bool HelperIPC::connect() { auto socketPath = IPC::getSocketPath(parentPid); socketFd = socket(AF_UNIX, SOCK_STREAM, 0); if (socketFd < 0) return false; // Set SO_NOSIGPIPE (macOS) int nosigpipe = 1; setsockopt(socketFd, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); int retries = 50; while (retries-- > 0) { if (::connect(socketFd, (struct sockaddr*)&addr, sizeof(addr)) == 0) { running.store(true); listenThread = std::thread([this]() { listenLoop(); }); return true; } usleep(100000); } close(socketFd); socketFd = -1; return false; } void HelperIPC::disconnect() { running.store(false); if (socketFd >= 0) { shutdown(socketFd, SHUT_RDWR); close(socketFd); socketFd = -1; } if (listenThread.joinable()) listenThread.join(); } void HelperIPC::sendReady() { json msg = {{"notify", "ready"}}; sendMessage(juce::String(msg.dump())); } void HelperIPC::sendParamChanged(int index, float value) { json msg = {{"notify", "param_changed"}, {"index", index}, {"value", value}}; sendMessage(juce::String(msg.dump())); } void HelperIPC::sendMessage(const juce::String& jsonStr) { if (socketFd < 0) return; auto data = jsonStr + "\n"; auto bytes = data.toRawUTF8(); auto len = (size_t)data.getNumBytesAsUTF8(); ::send(socketFd, bytes, len, 0); } void HelperIPC::listenLoop() { std::string buffer; char chunk[4096]; while (running.load()) { struct pollfd pfd; pfd.fd = socketFd; pfd.events = POLLIN; int ret = poll(&pfd, 1, 200); if (ret <= 0) continue; ssize_t n = recv(socketFd, chunk, sizeof(chunk) - 1, 0); if (n <= 0) { DBG("HelperIPC: Parent disconnected"); juce::JUCEApplication::getInstance()->systemRequestedQuit(); return; } chunk[n] = '\0'; buffer += chunk; size_t pos; while ((pos = buffer.find('\n')) != std::string::npos) { auto msg = buffer.substr(0, pos); buffer.erase(0, pos + 1); if (!msg.empty()) handleMessage(juce::String(msg)); } } } void HelperIPC::handleMessage(const juce::String& message) { try { auto j = json::parse(message.toStdString()); auto cmd = j.value("cmd", ""); if (cmd == "load") { auto path = j.value("path", ""); double sr = j.value("sampleRate", 44100.0); int bs = j.value("blockSize", 512); bool ok = host.loadPlugin(juce::String(path), sr, bs); json response; if (ok) response = {{"ok", true}, {"name", host.getPluginName().toStdString()}, {"params", host.getParameterCount()}}; else response = {{"ok", false}, {"error", "Failed to load plugin"}}; sendMessage(juce::String(response.dump())); } else if (cmd == "unload") { host.unloadPlugin(); sendMessage("{\"ok\":true}"); } else if (cmd == "search_param") { auto keyword = j.value("keyword", ""); auto results = host.searchParams(juce::String(keyword)); json arr = json::array(); for (auto& p : results) { arr.push_back({ {"i", p.index}, {"n", p.name.toStdString()}, {"v", std::round(p.value * 1000.0f) / 1000.0f}, {"t", p.displayText.toStdString()} }); } json response = {{"ok", true}, {"results", arr}}; sendMessage(juce::String(response.dump())); } else if (cmd == "get_params") { std::vector ids; for (auto& id : j["ids"]) ids.push_back(id.get()); auto results = host.getParams(ids); json values = json::object(); for (auto& [idx, val] : results) values[std::to_string(idx)] = std::round(val * 1000.0f) / 1000.0f; json response = {{"ok", true}, {"values", values}}; sendMessage(juce::String(response.dump())); } else if (cmd == "set_params") { std::map values; for (auto& [key, val] : j["values"].items()) values[std::stoi(key)] = val.get(); bool ok = host.setParams(values); sendMessage(ok ? "{\"ok\":true}" : "{\"ok\":false}"); } else if (cmd == "show_gui") { host.showGui(); sendMessage("{\"ok\":true}"); } else if (cmd == "hide_gui") { host.hideGui(); sendMessage("{\"ok\":true}"); } else if (cmd == "get_state") { auto state = host.getStateBase64(); json response = {{"ok", true}, {"state", state.toStdString()}}; sendMessage(juce::String(response.dump())); } else if (cmd == "set_state") { auto state = j.value("state", ""); bool ok = host.setStateBase64(juce::String(state)); sendMessage(ok ? "{\"ok\":true}" : "{\"ok\":false}"); } else if (cmd == "configure") { double sr = j.value("sampleRate", 44100.0); int bs = j.value("blockSize", 512); int ch = j.value("channels", 2); host.configure(sr, bs, ch); sendMessage("{\"ok\":true}"); } else if (cmd == "quit") { juce::JUCEApplication::getInstance()->systemRequestedQuit(); } } catch (const std::exception& e) { DBG("HelperIPC: Error handling message: " + juce::String(e.what())); sendMessage("{\"ok\":false,\"error\":\"parse error\"}"); } }