| #include "HelperIPC.h" |
| #include "HelperPluginHost.h" |
| #include "IPCProtocol.h" |
|
|
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include <poll.h> |
| #include <signal.h> |
|
|
| #include "json.hpp" |
| using json = nlohmann::json; |
|
|
| HelperIPC::HelperIPC(int parentPid, HelperPluginHost& host) |
| : parentPid(parentPid), host(host) |
| { |
| signal(SIGPIPE, SIG_IGN); |
|
|
| |
| 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(); |
| |
| 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<int> ids; |
| for (auto& id : j["ids"]) ids.push_back(id.get<int>()); |
| 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<int, float> values; |
| for (auto& [key, val] : j["values"].items()) values[std::stoi(key)] = val.get<float>(); |
| 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\"}"); |
| } |
| } |
|
|