Download Source/Plugin/HelperConnection.cpp from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 9.05 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/fdc4e418d34e7f32ef31422f1e9aae1de08f4e86/Source/Plugin/HelperConnection.cpp
- Command line
-
hf download hf://RAM2118/pluginbridge@fdc4e418d34e7f32ef31422f1e9aae1de08f4e86/Source/Plugin/HelperConnection.cpp
-
curl -L -o HelperConnection.cpp https://huggingface.co/RAM2118/pluginbridge/resolve/fdc4e418d34e7f32ef31422f1e9aae1de08f4e86/Source/Plugin/HelperConnection.cpp
9.05 kB
| extern char** environ; | |
| using json = nlohmann::json; | |
| HelperConnection::HelperConnection() | |
| { | |
| myPid = getpid(); | |
| signal(SIGPIPE, SIG_IGN); | |
| } | |
| HelperConnection::~HelperConnection() | |
| { | |
| killHelper(); | |
| } | |
| bool HelperConnection::spawnHelper() | |
| { | |
| killHelper(); | |
| auto shmName = IPC::getShmName(myPid); | |
| if (!sharedAudio.create(shmName)) | |
| { | |
| DBG("HelperConnection: Failed to create shared memory"); | |
| return false; | |
| } | |
| if (!inputSem.create(IPC::getInputSemName(myPid)) || | |
| !outputSem.create(IPC::getOutputSemName(myPid))) | |
| { | |
| DBG("HelperConnection: Failed to create semaphores"); | |
| return false; | |
| } | |
| auto socketPath = IPC::getSocketPath(myPid); | |
| unlink(socketPath.c_str()); | |
| serverSocketFd = socket(AF_UNIX, SOCK_STREAM, 0); | |
| if (serverSocketFd < 0) | |
| { | |
| DBG("HelperConnection: Failed to create socket"); | |
| return false; | |
| } | |
| 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); | |
| if (bind(serverSocketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0 || | |
| listen(serverSocketFd, 1) < 0) | |
| { | |
| DBG("HelperConnection: Failed to bind/listen"); | |
| close(serverSocketFd); | |
| serverSocketFd = -1; | |
| return false; | |
| } | |
| // Find helper .app bundle inside .vst3/Contents/Resources/ | |
| auto helperApp = juce::File::getSpecialLocation(juce::File::currentExecutableFile) | |
| .getParentDirectory() // MacOS/ | |
| .getParentDirectory() // Contents/ | |
| .getChildFile("Resources") | |
| .getChildFile("PluginBridgeHelper.app") | |
| .getChildFile("Contents") | |
| .getChildFile("MacOS") | |
| .getChildFile(PluginBridgeConstants::kHelperBinaryName); | |
| if (!helperApp.exists()) | |
| { | |
| // Fallback: bare binary | |
| helperApp = juce::File::getSpecialLocation(juce::File::currentExecutableFile) | |
| .getParentDirectory() | |
| .getParentDirectory() | |
| .getChildFile("Resources") | |
| .getChildFile(PluginBridgeConstants::kHelperBinaryName); | |
| } | |
| if (!helperApp.exists()) | |
| { | |
| DBG("HelperConnection: Helper not found at: " + helperApp.getFullPathName()); | |
| return false; | |
| } | |
| state.store(State::Starting); | |
| auto pidStr = juce::String(myPid).toStdString(); | |
| auto helperPathStr = helperApp.getFullPathName().toStdString(); | |
| pid_t pid; | |
| char* argv[] = { | |
| (char*)helperPathStr.c_str(), | |
| (char*)"--pid", | |
| (char*)pidStr.c_str(), | |
| nullptr | |
| }; | |
| int spawnResult = posix_spawn(&pid, helperPathStr.c_str(), nullptr, nullptr, argv, environ); | |
| if (spawnResult != 0) | |
| { | |
| DBG("HelperConnection: Failed to spawn: " + juce::String(strerror(spawnResult))); | |
| state.store(State::Disconnected); | |
| return false; | |
| } | |
| helperPid = pid; | |
| helperRunning.store(true); | |
| struct pollfd pfd; | |
| pfd.fd = serverSocketFd; | |
| pfd.events = POLLIN; | |
| if (poll(&pfd, 1, IPC::kHelperTimeoutMs) <= 0) | |
| { | |
| DBG("HelperConnection: Helper didn't connect in time"); | |
| killHelper(); | |
| return false; | |
| } | |
| clientSocketFd = accept(serverSocketFd, nullptr, nullptr); | |
| if (clientSocketFd < 0) | |
| { | |
| DBG("HelperConnection: Failed to accept connection"); | |
| killHelper(); | |
| return false; | |
| } | |
| int nosigpipe = 1; | |
| setsockopt(clientSocketFd, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); | |
| ipcRunning.store(true); | |
| ipcThread = std::thread([this]() { ipcListenLoop(); }); | |
| startTimerHz(4); | |
| int waitMs = 0; | |
| while (state.load() == State::Starting && waitMs < IPC::kHelperTimeoutMs) | |
| { | |
| juce::Thread::sleep(50); | |
| waitMs += 50; | |
| } | |
| return state.load() == State::Connected; | |
| } | |
| void HelperConnection::killHelper() | |
| { | |
| stopTimer(); | |
| ipcRunning.store(false); | |
| if (clientSocketFd >= 0) { close(clientSocketFd); clientSocketFd = -1; } | |
| if (serverSocketFd >= 0) { close(serverSocketFd); serverSocketFd = -1; } | |
| if (ipcThread.joinable()) ipcThread.join(); | |
| if (helperPid > 0 && helperRunning.load()) | |
| { | |
| kill(helperPid, SIGTERM); | |
| int status; | |
| waitpid(helperPid, &status, WNOHANG); | |
| usleep(100000); | |
| kill(helperPid, SIGKILL); | |
| waitpid(helperPid, &status, 0); | |
| } | |
| helperPid = 0; | |
| helperRunning.store(false); | |
| sharedAudio.destroy(); | |
| inputSem.destroy(); | |
| outputSem.destroy(); | |
| unlink(IPC::getSocketPath(myPid).c_str()); | |
| state.store(State::Disconnected); | |
| } | |
| std::string HelperConnection::sendCommand(const std::string& jsonCmd) | |
| { | |
| std::lock_guard<std::mutex> lock(commandMutex); | |
| if (clientSocketFd < 0 || !helperRunning.load()) | |
| return "{\"ok\":false,\"error\":\"not connected\"}"; | |
| auto data = jsonCmd + "\n"; | |
| if (::send(clientSocketFd, data.c_str(), data.size(), 0) < 0) | |
| return "{\"ok\":false,\"error\":\"send failed\"}"; | |
| responseReady.store(false); | |
| int waitMs = 0; | |
| while (!responseReady.load() && waitMs < 5000) | |
| { | |
| juce::Thread::sleep(10); | |
| waitMs += 10; | |
| } | |
| if (!responseReady.load()) | |
| return "{\"ok\":false,\"error\":\"timeout\"}"; | |
| return lastResponse; | |
| } | |
| void HelperConnection::timerCallback() | |
| { | |
| if (helperPid <= 0) return; | |
| int status; | |
| pid_t result = waitpid(helperPid, &status, WNOHANG); | |
| if (result == helperPid) | |
| { | |
| helperRunning.store(false); | |
| state.store(State::Crashed); | |
| stopTimer(); | |
| DBG("HelperConnection: Helper died"); | |
| if (onHelperCrashed) | |
| onHelperCrashed(); | |
| } | |
| } | |
| void HelperConnection::ipcListenLoop() | |
| { | |
| std::string buffer; | |
| char chunk[4096]; | |
| while (ipcRunning.load()) | |
| { | |
| struct pollfd pfd; | |
| pfd.fd = clientSocketFd; | |
| pfd.events = POLLIN; | |
| int ret = poll(&pfd, 1, 200); | |
| if (ret <= 0) continue; | |
| ssize_t n = recv(clientSocketFd, chunk, sizeof(chunk) - 1, 0); | |
| if (n <= 0) | |
| { | |
| if (ipcRunning.load()) | |
| { | |
| helperRunning.store(false); | |
| state.store(State::Crashed); | |
| if (onHelperCrashed) | |
| juce::MessageManager::callAsync([this]() { if (onHelperCrashed) onHelperCrashed(); }); | |
| } | |
| 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()) continue; | |
| try | |
| { | |
| auto j = json::parse(msg); | |
| if (j.contains("notify")) | |
| handleNotification(msg); | |
| else | |
| { | |
| lastResponse = msg; | |
| responseReady.store(true); | |
| } | |
| } | |
| catch (...) {} | |
| } | |
| } | |
| } | |
| void HelperConnection::handleNotification(const std::string& jsonStr) | |
| { | |
| try | |
| { | |
| auto j = json::parse(jsonStr); | |
| auto notify = j.value("notify", ""); | |
| if (notify == "ready") | |
| { | |
| state.store(State::Connected); | |
| if (onHelperReady) | |
| juce::MessageManager::callAsync([this]() { if (onHelperReady) onHelperReady(); }); | |
| } | |
| else if (notify == "gui_ready") | |
| { | |
| auto portName = juce::String(j.value("layerPortName", "")); | |
| int width = j.value("width", 0); | |
| int height = j.value("height", 0); | |
| if (onGuiReady) | |
| juce::MessageManager::callAsync([this, portName, width, height]() | |
| { if (onGuiReady) onGuiReady(portName, width, height); }); | |
| } | |
| else if (notify == "resized") | |
| { | |
| int width = j.value("width", 0); | |
| int height = j.value("height", 0); | |
| if (onGuiResized) | |
| juce::MessageManager::callAsync([this, width, height]() | |
| { if (onGuiResized) onGuiResized(width, height); }); | |
| } | |
| else if (notify == "param_changed") | |
| { | |
| int index = j.value("index", -1); | |
| float value = j.value("value", 0.0f); | |
| if (onParamChanged) | |
| onParamChanged(index, value); | |
| } | |
| else if (notify == "crashed") | |
| { | |
| state.store(State::Crashed); | |
| if (onHelperCrashed) | |
| juce::MessageManager::callAsync([this]() { if (onHelperCrashed) onHelperCrashed(); }); | |
| } | |
| } | |
| catch (...) {} | |
| } | |