#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); // Wire the GUI created callback so we send notification to host process host.onGuiCreated = [this](const juce::String& portName, int width, int height) { sendGuiReady(portName, width, height); }; } HelperIPC::~HelperIPC() { host.onGuiCreated = nullptr; disconnect(); } bool HelperIPC::connect() { auto socketPath = IPC::getSocketPath(parentPid); socketFd = socket(AF_UNIX, SOCK_STREAM, 0); if (socketFd < 0) return false; 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::sendGuiReady(const juce::String& portName, int width, int height) { json msg = {{"notify", "gui_ready"}, {"layerPortName", portName.toStdString()}, {"width", width}, {"height", height}}; sendMessage(juce::String(msg.dump())); } void HelperIPC::sendGuiResized(int width, int height) { json msg = {{"notify", "resized"}, {"width", width}, {"height", height}}; 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 == "show_gui") { host.showGui(); // gui_ready notification sent via onGuiCreated callback (async) sendMessage("{\"ok\":true}"); } else if (cmd == "hide_gui") { host.hideGui(); 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},{"id",p.id.toStdString()},{"n",p.name.toStdString()}, {"v",std::round(p.value*1000.0f)/1000.0f},{"t",p.displayText.toStdString()}}); sendMessage(juce::String(json({{"ok",true},{"results",arr}}).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; sendMessage(juce::String(json({{"ok",true},{"values",values}}).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 == "mouse") { auto type = j.value("type", ""); float x = j.value("x", 0.0f), y = j.value("y", 0.0f); int button = j.value("button", 0); if (type == "down") host.injectMouseDown(x, y, button); else if (type == "up") host.injectMouseUp(x, y, button); else if (type == "move") host.injectMouseMove(x, y); else if (type == "drag") host.injectMouseDrag(x, y, button); else if (type == "scroll") host.injectMouseScroll(x, y, j.value("dx",0.0f), j.value("dy",0.0f)); } else if (cmd == "key") { auto type = j.value("type", ""); int keyCode = j.value("keyCode", 0); auto chars = j.value("chars", ""); int modifiers = j.value("modifiers", 0); if (type == "down") host.injectKeyDown(keyCode, juce::String(chars), modifiers); else if (type == "up") host.injectKeyUp(keyCode, juce::String(chars), modifiers); } else if (cmd == "get_state") { auto state = host.getStateBase64(); sendMessage(juce::String(json({{"ok",true},{"state",state.toStdString()}}).dump())); } else if (cmd == "set_state") { bool ok = host.setStateBase64(juce::String(j.value("state", ""))); sendMessage(ok ? "{\"ok\":true}" : "{\"ok\":false}"); } else if (cmd == "configure") { host.configure(j.value("sampleRate",44100.0), j.value("blockSize",512), j.value("channels",2)); sendMessage("{\"ok\":true}"); } else if (cmd == "quit") { juce::JUCEApplication::getInstance()->systemRequestedQuit(); } } catch (const std::exception& e) { DBG("HelperIPC: Error: " + juce::String(e.what())); sendMessage("{\"ok\":false,\"error\":\"parse error\"}"); } }