M4: /fx-gui redirect vao session fxrt (native-window mode, embed_url=None) + watchdog auto-restart

This commit is contained in:
2026-09-04 11:37:08 +07:00
parent e6d01d90f1
commit 14fd794e6d
2 changed files with 216 additions and 0 deletions
+72
View File
@@ -711,6 +711,24 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
plugin_path = (req.path or "").strip()
if not plugin_path:
raise HTTPException(status_code=400, detail="Thiếu đường dẫn VST plugin.")
# ── Phase 2 (M4): plugin là slot của session FX realtime (mastering) → mở
# editor của CHÍNH instance DSP ngay trong fxrt_bridge (juce_fx_bridge) qua
# SHM ctrl ring ed.open — KHÔNG spawn fx_vst_bridge instance 2 + feeder
# (nguồn CPU 100%). GUI = cửa sổ native "FX slot N" trên desktop (không
# HTTP embed frame → embed_url=None, native=true — frontend M6 dùng mode
# này). Vẫn spawn fx_vst_bridge khi: không shm / session không tồn tại /
# plugin không phải slot chain (instrument, offline, fallback an toàn).
_sess = fx_realtime.get_session((req.shm or "").strip()) if (req.shm or "").strip() else None
if _sess is not None and _sess.alive():
_slot = _sess.chain_slot_for_path(plugin_path)
if _slot is not None:
_already = fx_realtime.editor_find(plugin_path)
_same = _already is not None and _already[0] == _sess.name
_sess.send_editor_cmd("open", slot=_slot)
fx_realtime.editor_register(plugin_path, _sess.name, _slot)
return {"success": True, "started": not _same,
"already_running": bool(_same), "native": True,
"embed_url": None, "session_id": _sess.name, "slot": _slot}
# Tránh mở 2 GUI cùng plugin: nếu tiến trình bridge fx-gui cho plugin này
# đang chạy → trả started=False (frontend có thể focus window cũ).
for proc in _FX_GUI_PROCESSES:
@@ -808,6 +826,17 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_
plugin_path = (req.path or "").strip()
if not plugin_path:
raise HTTPException(status_code=400, detail="Thiếu đường dẫn VST plugin.")
# M4: plugin đang mở native (editor instance DSP trong session fxrt) →
# gửi ed.close (đóng editor, GIỮ instance + state — mở lại nhanh).
_entry = fx_realtime.editor_find(plugin_path)
if _entry:
_sid, _slot = _entry
fx_realtime.editor_unregister(plugin_path)
_sess = fx_realtime.get_session(_sid)
if _sess is not None and _sess.alive():
_sess.send_editor_cmd("close", slot=_slot)
return {"success": True, "closed": True, "native": True}
# session đã chết — rơi xuống spawn fallback (đóng bridge fx-gui cũ nếu có)
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()
@@ -820,6 +849,49 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_
return {"success": True, "closed": False}
class FxGuiNativeRequest(BaseModel):
"""Hide/Show GUI native (editor instance DSP trong session fxrt)."""
path: str
def _fxrt_native_editor(plugin_path):
"""(session, slot) nếu plugin đang mở native trong session fxrt sống."""
entry = fx_realtime.editor_find(plugin_path)
if not entry:
return None, None
sess = fx_realtime.get_session(entry[0])
if sess is None or not sess.alive():
fx_realtime.editor_unregister(plugin_path)
return None, None
return sess, entry[1]
@router.post("/fx-gui/hide")
async def hide_fx_gui(req: FxGuiNativeRequest,
current_user: dict = Depends(get_current_user)):
"""Ẩn GUI native (ed.hide — giữ editor + instance, mở lại nhanh). Chỉ
áp dụng editor in-process fxrt; GUI spawn mode (HTTP bridge) tự có /hide
riêng — trả applied=False không lỗi."""
enforce_password_changed(current_user)
sess, slot = _fxrt_native_editor((req.path or "").strip())
if sess is None:
return {"success": True, "applied": False}
sess.send_editor_cmd("hide", slot=slot)
return {"success": True, "applied": True, "native": True}
@router.post("/fx-gui/show")
async def show_fx_gui(req: FxGuiNativeRequest,
current_user: dict = Depends(get_current_user)):
"""Hiện lại GUI native (ed.show)."""
enforce_password_changed(current_user)
sess, slot = _fxrt_native_editor((req.path or "").strip())
if sess is None:
return {"success": True, "applied": False}
sess.send_editor_cmd("show", slot=slot)
return {"success": True, "applied": True, "native": True}
_FX_GUI_PROCESSES = [] # list[subprocess.Popen] — các bridge fx-gui do app spawn
def _register_fx_gui_process(proc):