feat(native): realtime master FX chain + fx-gui fast start, hide/show, non-topmost

- FxGuiServer: HWND_TOP (no longer always-on-top), /hide + /show endpoints,
  onStarted callback prints SF_FXGUI_PORT before plugin load (fast connect)
- RenderFxJob: RealtimeFxChain (mutex+worker, SEH-guarded, bypass slots,
  deferred Vst3Fx dtor); --fx-gui split into parse/create/load-attach stages
- main: FreeConsole unless SF_KEEP_CONSOLE; control type 6 SET_FX_CHAIN feeds
  g_fxChain on the audio loop; shutdown before CoUninitialize
- plugins.py: CREATE_NO_WINDOW on both Popen paths
- lib.rs: set_fx_chain command -> push_control(6)
- app.jsx/nativeBridgeService: setFxChain sync from MasteringModal; panel close
  uses /hide + refs to fix closure bug; VstGuiEmbed rect refresh -> /show
This commit is contained in:
2026-08-19 09:59:13 +07:00
parent c6de0720f4
commit da0f8fa40e
11 changed files with 391 additions and 40 deletions
+51 -3
View File
@@ -283,7 +283,7 @@ void danceStart(HWND hwnd, HWND target, int x, int y) {
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOACTIVATE | SWP_FRAMECHANGED);
}
SetWindowPos(hwnd, HWND_TOPMOST, g_embedRect.left, g_embedRect.top,
SetWindowPos(hwnd, HWND_TOP, g_embedRect.left, g_embedRect.top,
cw, ch, SWP_NOACTIVATE | SWP_SHOWWINDOW);
fxGuiRequestResize(hwnd, cw, ch);
Sleep(60); // let the compositor repaint so the editor sees itself active
@@ -306,7 +306,7 @@ void danceStart(HWND hwnd, HWND target, int x, int y) {
const int nx = wr.left + (cur.x - pt.x);
const int ny = wr.top + (cur.y - pt.y);
GetCursorPos(&g_savedCursor);
SetWindowPos(hwnd, HWND_TOPMOST, nx, ny, 0, 0,
SetWindowPos(hwnd, HWND_TOP, nx, ny, 0, 0,
SWP_NOSIZE | SWP_SHOWWINDOW);
Sleep(60); // let the compositor repaint so the editor sees itself active
g_dance = true;
@@ -516,6 +516,50 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
if (root) std::free(root);
sendResponse(c, 400, "application/json", "{\"error\":\"bad json\"}", 20);
}
} else if (req.path == "/hide") {
ShowWindow(hwnd, SW_HIDE);
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
} else if (req.path == "/show") {
// Optional body {rect:{x,y,w,h}} (screen px) — anchored placement
// over the embed panel; without a rect just show + top (never
// topmost — the GUI must not stay above other apps).
json_parse_result_s spres = {};
json_value_s* sroot = json_parse_ex(req.body.data(), req.body.size(),
json_parse_flags_default, nullptr, nullptr, &spres);
bool rectOk = false;
if (sroot && sroot->type == json_type_object) {
const json_object_s* so = static_cast<const json_object_s*>(sroot->payload);
const json_value_s* srv = memberValue(so, "rect");
if (srv && srv->type == json_type_object) {
const json_object_s* sro = static_cast<const json_object_s*>(srv->payload);
const LONG srw = (LONG)memberInt(sro, "w", 0);
const LONG srh = (LONG)memberInt(sro, "h", 0);
if (srw > 0 && srh > 0) {
g_embedRect.left = (LONG)memberInt(sro, "x", 0);
g_embedRect.top = (LONG)memberInt(sro, "y", 0);
g_embedRect.right = g_embedRect.left + srw;
g_embedRect.bottom = g_embedRect.top + srh;
rectOk = true;
}
}
}
if (sroot) std::free(sroot);
if (rectOk) {
SetWindowPos(hwnd, HWND_TOP, g_embedRect.left, g_embedRect.top,
g_embedRect.right - g_embedRect.left,
g_embedRect.bottom - g_embedRect.top,
SWP_NOACTIVATE | SWP_SHOWWINDOW);
fxGuiRequestResize(hwnd, g_embedRect.right - g_embedRect.left,
g_embedRect.bottom - g_embedRect.top);
g_dance = true;
g_anchored = true;
g_parkUntil = 0;
} else {
ShowWindow(hwnd, SW_SHOW);
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
} else if (req.path == "/close") {
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
stop.store(true);
@@ -534,9 +578,13 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
// Runs the embedded GUI server: starts the HTTP worker thread, then pumps
// messages on this (STA) thread until the host window is destroyed (user
// closed it or POST /close). Returns 0.
int fxGuiServerLoop(HWND hwnd) {
int fxGuiServerLoop(HWND hwnd, const std::function<void()>& onStarted) {
std::atomic<bool> stop{false};
std::thread http(runHttpServer, hwnd, std::ref(stop));
// onStarted runs on the pump (STA) thread AFTER the HTTP server is up and
// the port printed — used to defer the slow plugin load/attach in server
// mode so the caller (plugins.py) doesn't block on VST load.
if (onStarted) onStarted();
MSG msg;
while (IsWindow(hwnd)) {
BOOL r = GetMessageA(&msg, nullptr, 0, 0);