pluginbridge / Source /Plugin /HelperConnection.cpp
RAM2118's picture
Upload Source/Plugin/HelperConnection.cpp
8335f9f verified
Raw
History Blame Contribute Delete
9.05 kB
#include "HelperConnection.h"
#include "Constants.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <poll.h>
#include <spawn.h>
extern char** environ;
#include "json.hpp"
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 (...) {}
}