feat: embed native VST GUI in mastering + fx rack panels (bridge --fx-gui frame capture + input relay)
Linux parts (0L1-4L1 per TASKS_LINUX_WINDOWS.md): - native_bridge: FxGuiServer.cpp HTTP server (Winsock) — /frame BitBlt + PrintWindow fallback -> JPEG (libjpeg-turbo q70), /input PostMessage relay (mouse/wheel/key, coords scaled), /close, /ping; RenderFxJob fxGuiSetup + run_fx_gui_server (offscreen); main.cpp --fx-gui dispatch; vcpkg + CMakeLists WIN32-only + JPEG. - api: open_fx_gui embed flag spawn --fx-gui, parse SF_FXGUI_PORT -> embed_url, fx-gui/close endpoint; 4 unit tests. - ui: VstGuiEmbed component (frame refresh 100ms, ping 2s, input relay, scale coords) shared by MasteringModal + FXRackModal; swap graph<->GUI, cleanup on unmount, dead-bridge toast fallback. - docs: TASKS_LINUX_WINDOWS.md, WALKTHROUGH_WINDOWS.md, PLAN status. Windows tasks 0W1-4W1 remain (build + E2E real VST).
This commit is contained in:
+58
-1
@@ -503,6 +503,14 @@ class FxGuiRequest(BaseModel):
|
||||
"""Mở native GUI của VST FX qua bridge (Windows desktop)."""
|
||||
path: str
|
||||
name: Optional[str] = None
|
||||
# embed=True → bridge --fx-gui (off-screen + HTTP frame/input server,
|
||||
# PLAN_MASTERBUS_VST_GUI_EMBED.md); embed=False → cửa sổ rời cũ.
|
||||
embed: bool = False
|
||||
|
||||
|
||||
class FxGuiCloseRequest(BaseModel):
|
||||
"""Đóng bridge fx-gui đã mở cho plugin (embed mode)."""
|
||||
path: str
|
||||
|
||||
|
||||
class PreviewRequest(BaseModel):
|
||||
@@ -717,12 +725,41 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
||||
except Exception:
|
||||
os.close(fd)
|
||||
raise HTTPException(status_code=500, detail="Không ghi được job file cho bridge.")
|
||||
cmd = [exe, "--open-fx-gui", job_path]
|
||||
cmd = [exe, "--fx-gui" if req.embed else "--open-fx-gui", job_path]
|
||||
try:
|
||||
if req.embed:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, text=True,
|
||||
close_fds=os.name != "nt")
|
||||
# Bridge prints SF_FXGUI_PORT=<port> after binding its HTTP server.
|
||||
embed_url = None
|
||||
try:
|
||||
for line in proc.stdout:
|
||||
line = (line or "").strip()
|
||||
if line.startswith("SF_FXGUI_PORT="):
|
||||
port = int(line.split("=", 1)[1])
|
||||
embed_url = f"http://127.0.0.1:{port}"
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not embed_url:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
raise HTTPException(status_code=500, detail="Bridge không báo port fx-gui (mở GUI thất bại).")
|
||||
proc._sf_plugin_path = plugin_path
|
||||
proc._sf_embed_url = embed_url
|
||||
_register_fx_gui_process(proc)
|
||||
return {"success": True, "started": True, "already_running": False,
|
||||
"embed_url": embed_url, "cmd": cmd}
|
||||
proc = subprocess.Popen(cmd, close_fds=os.name != "nt")
|
||||
proc._sf_plugin_path = plugin_path
|
||||
_register_fx_gui_process(proc)
|
||||
return {"success": True, "started": True, "already_running": False, "cmd": cmd}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
try:
|
||||
os.remove(job_path)
|
||||
@@ -731,6 +768,26 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
||||
raise HTTPException(status_code=500, detail=f"Không mở được GUI của VST: {e}")
|
||||
|
||||
|
||||
@router.post("/fx-gui/close")
|
||||
async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_current_user)):
|
||||
"""Đóng bridge fx-gui đang chạy cho plugin (embed mode) — frontend gọi khi
|
||||
user đóng panel embed. Không lỗi nếu plugin chưa mở (closed=False)."""
|
||||
enforce_password_changed(current_user)
|
||||
plugin_path = (req.path or "").strip()
|
||||
if not plugin_path:
|
||||
raise HTTPException(status_code=400, detail="Thiếu đường dẫn VST plugin.")
|
||||
for proc in _FX_GUI_PROCESSES:
|
||||
if proc is not None and proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
_prune_fx_gui_processes()
|
||||
return {"success": True, "closed": True}
|
||||
return {"success": True, "closed": False}
|
||||
|
||||
|
||||
_FX_GUI_PROCESSES = [] # list[subprocess.Popen] — các bridge fx-gui do app spawn
|
||||
|
||||
def _register_fx_gui_process(proc):
|
||||
|
||||
Reference in New Issue
Block a user