v0.4: CARemoteLayerServer architecture — crash-safe + GUI inside Ableton window
Browse filesCOMPLETE REWRITE to proper solution:
- Plugin runs in Helper process (crash-safe, like v0.2)
- GUI renders in Helper via CARemoteLayerServer
- PluginBridge.vst3 displays it via CARemoteLayerClient (CALayer inside its own editor)
- Mach port transferred via NSMachBootstrapServer (named port per PID)
- Mouse/keyboard events forwarded from host → helper via Unix socket IPC
- Audio routes via shared memory (same as v0.2)
This gives BOTH crash safety AND GUI inside Ableton's window (including fullscreen).
Based on: Apple's CARemoteLayerServer/Client public API (macOS 10.7+)
Reference: krevis/RemoteLayerDemo on GitHub"
- Source/Shared/IPCProtocol.h +36 -53
Source/Shared/IPCProtocol.h
CHANGED
|
@@ -4,11 +4,15 @@
|
|
| 4 |
#include <cstdint>
|
| 5 |
|
| 6 |
// IPC Protocol between PluginBridge (parent) and PluginBridgeHelper (child)
|
| 7 |
-
//
|
| 8 |
-
//
|
| 9 |
-
//
|
| 10 |
-
//
|
| 11 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
namespace IPC {
|
| 14 |
|
|
@@ -18,7 +22,7 @@ inline std::string getSocketPath(int parentPid)
|
|
| 18 |
return "/tmp/pluginbridge_" + std::to_string(parentPid) + ".sock";
|
| 19 |
}
|
| 20 |
|
| 21 |
-
// Shared memory name
|
| 22 |
inline std::string getShmName(int parentPid)
|
| 23 |
{
|
| 24 |
return "pluginbridge_audio_" + std::to_string(parentPid);
|
|
@@ -35,64 +39,43 @@ inline std::string getOutputSemName(int parentPid)
|
|
| 35 |
return "pb_output_" + std::to_string(parentPid);
|
| 36 |
}
|
| 37 |
|
| 38 |
-
//
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
|
| 44 |
-
//
|
|
|
|
| 45 |
// {"cmd":"unload"}
|
| 46 |
-
// Response: {"ok":true}
|
| 47 |
-
|
| 48 |
-
// Search parameters by keyword
|
| 49 |
// {"cmd":"search_param","keyword":"gain"}
|
| 50 |
-
//
|
| 51 |
-
|
| 52 |
-
// Get parameter values by index
|
| 53 |
-
// {"cmd":"get_params","ids":[12,13,14]}
|
| 54 |
-
// Response: {"ok":true,"values":{"12":0.5,"13":0.7,"14":0.0}}
|
| 55 |
-
|
| 56 |
-
// Set parameter values
|
| 57 |
-
// {"cmd":"set_params","values":{"12":0.75,"13":0.3}}
|
| 58 |
-
// Response: {"ok":true}
|
| 59 |
-
|
| 60 |
-
// Show/hide plugin GUI
|
| 61 |
// {"cmd":"show_gui"}
|
| 62 |
// {"cmd":"hide_gui"}
|
| 63 |
-
// Response: {"ok":true}
|
| 64 |
-
|
| 65 |
-
// Get plugin state (for save)
|
| 66 |
// {"cmd":"get_state"}
|
| 67 |
-
//
|
| 68 |
-
|
| 69 |
-
// Set plugin state (for restore)
|
| 70 |
-
// {"cmd":"set_state","state":"<base64-encoded state>"}
|
| 71 |
-
// Response: {"ok":true}
|
| 72 |
-
|
| 73 |
-
// Configure audio
|
| 74 |
// {"cmd":"configure","sampleRate":44100,"blockSize":512,"channels":2}
|
| 75 |
-
// Response: {"ok":true}
|
| 76 |
-
|
| 77 |
-
// Quit helper
|
| 78 |
// {"cmd":"quit"}
|
| 79 |
-
//
|
| 80 |
-
|
| 81 |
-
//
|
| 82 |
-
|
| 83 |
-
//
|
| 84 |
-
// {"
|
| 85 |
-
|
| 86 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
// {"notify":"crashed","error":"..."}
|
| 88 |
|
| 89 |
-
// Helper ready (sent after startup)
|
| 90 |
-
// {"notify":"ready"}
|
| 91 |
-
|
| 92 |
// --- Constants ---
|
| 93 |
-
|
| 94 |
-
static constexpr int
|
| 95 |
-
static constexpr int kHelperTimeoutMs = 5000; // 5s to start up
|
| 96 |
static constexpr int kProcessBlockTimeoutMs = 50; // 50ms max wait in processBlock
|
| 97 |
|
| 98 |
} // namespace IPC
|
|
|
|
| 4 |
#include <cstdint>
|
| 5 |
|
| 6 |
// IPC Protocol between PluginBridge (parent) and PluginBridgeHelper (child)
|
| 7 |
+
//
|
| 8 |
+
// v0.4 Architecture:
|
| 9 |
+
// - Audio: POSIX shared memory (zero-copy, same as v0.2)
|
| 10 |
+
// - Sync: Named semaphores (same as v0.2)
|
| 11 |
+
// - Commands: Unix domain socket, newline-delimited JSON
|
| 12 |
+
// - GUI: CARemoteLayerServer in Helper → Mach port → CARemoteLayerClient in Host
|
| 13 |
+
//
|
| 14 |
+
// The Mach port for CARemoteLayerServer is transferred via NSMachBootstrapServer
|
| 15 |
+
// using the name "com.pluginbridge.layer.<parentPid>"
|
| 16 |
|
| 17 |
namespace IPC {
|
| 18 |
|
|
|
|
| 22 |
return "/tmp/pluginbridge_" + std::to_string(parentPid) + ".sock";
|
| 23 |
}
|
| 24 |
|
| 25 |
+
// Shared memory name
|
| 26 |
inline std::string getShmName(int parentPid)
|
| 27 |
{
|
| 28 |
return "pluginbridge_audio_" + std::to_string(parentPid);
|
|
|
|
| 39 |
return "pb_output_" + std::to_string(parentPid);
|
| 40 |
}
|
| 41 |
|
| 42 |
+
// Mach bootstrap port name for CARemoteLayerServer
|
| 43 |
+
inline std::string getLayerPortName(int parentPid)
|
| 44 |
+
{
|
| 45 |
+
return "com.pluginbridge.layer." + std::to_string(parentPid);
|
| 46 |
+
}
|
| 47 |
|
| 48 |
+
// --- Message Types (Parent → Helper) ---
|
| 49 |
+
// {"cmd":"load","path":"/path/to/plugin.vst3","sampleRate":44100,"blockSize":512}
|
| 50 |
// {"cmd":"unload"}
|
|
|
|
|
|
|
|
|
|
| 51 |
// {"cmd":"search_param","keyword":"gain"}
|
| 52 |
+
// {"cmd":"get_params","ids":[0,1,2]}
|
| 53 |
+
// {"cmd":"set_params","values":{"0":0.5,"1":0.75}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
// {"cmd":"show_gui"}
|
| 55 |
// {"cmd":"hide_gui"}
|
|
|
|
|
|
|
|
|
|
| 56 |
// {"cmd":"get_state"}
|
| 57 |
+
// {"cmd":"set_state","state":"<base64>"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
// {"cmd":"configure","sampleRate":44100,"blockSize":512,"channels":2}
|
|
|
|
|
|
|
|
|
|
| 59 |
// {"cmd":"quit"}
|
| 60 |
+
//
|
| 61 |
+
// --- Mouse/Keyboard forwarding (Parent → Helper) ---
|
| 62 |
+
// {"cmd":"mouse","type":"down","x":120.5,"y":45.0,"button":0}
|
| 63 |
+
// {"cmd":"mouse","type":"up","x":120.5,"y":45.0,"button":0}
|
| 64 |
+
// {"cmd":"mouse","type":"move","x":130.0,"y":50.0}
|
| 65 |
+
// {"cmd":"mouse","type":"drag","x":135.0,"y":52.0,"button":0}
|
| 66 |
+
// {"cmd":"mouse","type":"scroll","x":120.5,"y":45.0,"dx":0,"dy":-3.0}
|
| 67 |
+
// {"cmd":"key","type":"down","keyCode":36,"chars":"a","modifiers":0}
|
| 68 |
+
// {"cmd":"key","type":"up","keyCode":36,"chars":"a","modifiers":0}
|
| 69 |
+
//
|
| 70 |
+
// --- Notifications (Helper → Parent) ---
|
| 71 |
+
// {"notify":"ready","layerPortName":"com.pluginbridge.layer.12345","width":800,"height":600}
|
| 72 |
+
// {"notify":"param_changed","index":5,"value":0.75}
|
| 73 |
+
// {"notify":"resized","width":900,"height":650}
|
| 74 |
// {"notify":"crashed","error":"..."}
|
| 75 |
|
|
|
|
|
|
|
|
|
|
| 76 |
// --- Constants ---
|
| 77 |
+
static constexpr int kMaxMessageSize = 65536;
|
| 78 |
+
static constexpr int kHelperTimeoutMs = 10000; // 10s for startup (CARemoteLayer init takes time)
|
|
|
|
| 79 |
static constexpr int kProcessBlockTimeoutMs = 50; // 50ms max wait in processBlock
|
| 80 |
|
| 81 |
} // namespace IPC
|