M7-M16: VST FX GUI native embed hoàn thiện — fix click-2-lần chuyển slot, anchor lần mở đầu, taskbar icon

- app.jsx: masterGuiOpenRef sync truoc setState — guard chuyen slot doc ref, het race closure (click 1 lan chuyen slot)
- JuceFxGuiHost.cpp: M14 applyAnchor truoc setVisible (het flash giua man hinh); M15 clear WS_EX_APPWINDOW + set TOOLWINDOW (het taskbar icon — JUCE set APPWINDOW 0x40000 thang TOOLWINDOW); M9 owner DAW + M9d skip applyOwner khi an
- JuceFxLoop.cpp: M16 bo clearAnchor khi ed.open (retry ed.open ~700ms xoa rect fresh -> window moi khong anchor duoc lan dau mo panel); chi clear khi Close
- JuceFxEngine/app.jsx/plugins.py/fx_realtime.py: editor session realtime, anchor rect routing, retry ed.open
- Rebuild engine PyInstaller + juce_fx_bridge cmake; binaries dong bo 5 noi (bridge 7464a7ef)
This commit is contained in:
2026-09-06 19:45:14 +07:00
parent ec2c925d09
commit ef521d9981
20 changed files with 2040 additions and 461 deletions
+97 -4
View File
@@ -668,7 +668,10 @@ class FxGuiRequest(BaseModel):
# đã chỉnh (trước đây GUI mở lại load default → mất settings khi
# hide/show).
preset_b64: str = ""
# C: master=True — GUI master chỉ mở qua session realtime (native editor
# của instance DSP, shm). JS warm-ensure session trước; master mà không có
# session/slot -> 409 (fallback embed đã ngưng cho master).
master: bool = False
class FxGuiCloseRequest(BaseModel):
"""Đóng bridge fx-gui đã mở cho plugin (embed mode)."""
@@ -719,6 +722,16 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
# 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
# C: master + session sống nhưng plugin chưa phải slot chain (module vừa add —
# JS set_chain async / watchdog respawn) -> chờ tối đa 15s cho slot xuất hiện;
# session chết giữa chừng -> bỏ (fallback embed bị chặn bên dưới cho master).
if req.master and _sess is not None and _sess.alive() and _sess.chain_slot_for_path(plugin_path) is None:
for _ in range(150):
if not _sess.alive():
break
if _sess.chain_slot_for_path(plugin_path) is not None:
break
_time.sleep(0.1)
if _sess is not None and _sess.alive():
_slot = _sess.chain_slot_for_path(plugin_path)
if _slot is not None:
@@ -727,11 +740,45 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
# trước, nếu không 1 plugin hiện 2 GUI (1 floating native + 1 embed)
# và floating không tắt được qua UI. Terminate proc cũ → frontend
# embed panel của nó onDead → tự đóng.
_terminate_fx_gui_for_path(plugin_path)
# M8: set_chain (juce bridge) reload async (watchdog respawn) —
# cho chain moi ap tren bridge truoc khi ed.open, khong thi C++
# bao "slot ngoai chain (M) — bo qua". Khong co reload dang chay
# -> tra ngay (khong them latency mo GUI).
_ready = _sess.wait_chain_ready(timeout=15)
logger.info("[fxgui] open native path=%s shm=%s slot=%s ready=%s",
plugin_path, _sess.name, _slot, _ready)
if not _ready:
# M13: chua READY (proc respawn / chain reload cham) — cho them
# 5s; van chet thi ed.open chi vao ctrl ring (C++ KHONG reset
# ring khi respawn → proc moi doc duoc) nen van gui duoc.
_sess.wait_chain_ready(timeout=5)
if not _sess.alive():
logger.warning("[fxgui] open native bo — session chet path=%s", plugin_path)
raise HTTPException(status_code=409,
detail="Session FX realtime đã đóng — bật lại FX PWR rồi thử mở GUI.")
_killed = _terminate_fx_gui_for_path(plugin_path)
_already = fx_realtime.editor_find(plugin_path)
_same = _already is not None and _already[0] == _sess.name
_sess.send_editor_cmd("open", slot=_slot)
_sent = _sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] open sent=%s killed_fallback=%s already=%s same=%s proc_pid=%s state=%s",
_sent, _killed, bool(_already), _same,
_sess.proc.pid if getattr(_sess, "proc", None) else None,
getattr(getattr(_sess, "h", None), "state", "?"))
fx_realtime.editor_register(plugin_path, _sess.name, _slot)
# M13 workaround click-1: gui doi khi khong hien window ngay (owner
# gan sau show → Win32 an window). Gửi lai ed.open sau 700ms = ban
# sao click-2 — C++ log "ALREADY open — re-show" thi re-show chinh
# la dieu can. ponytail: bo block nay khi log A (prefix p/t) xac
# nhan root cause that (show truoc owner).
def _retry_open(delay):
_time.sleep(delay)
try:
if _sess.alive():
_sess.send_editor_cmd("open", slot=_slot)
logger.info("[fxgui] retry ed.open path=%s slot=%s", plugin_path, _slot)
except Exception:
pass
threading.Thread(target=_retry_open, args=(0.7,), daemon=True).start()
return {"success": True, "started": not _same,
"already_running": bool(_same), "native": True,
"embed_url": None, "session_id": _sess.name, "slot": _slot}
@@ -749,6 +796,11 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
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:
return {"success": True, "started": False, "already_running": True, "cmd": [], "embed_url": getattr(proc, "_sf_embed_url", None) or None}
# C: tới đây mà master vẫn không có session/slot (native không mở được) ->
# từ chối spawn fallback embed (instance VST thứ 2, preset lệch, GUI không
# anchor preset master). Track GUI (không master) giữ fallback embed cũ.
if req.master:
raise HTTPException(status_code=409, detail="Mastering GUI chỉ mở qua session realtime (bật FX PWR hoặc để session warm) — fallback embed đã ngưng cho master.")
job = {"path": plugin_path, "name": req.name or os.path.basename(plugin_path)}
if req.shm:
job["shm"] = req.shm
@@ -867,6 +919,20 @@ class FxGuiNativeRequest(BaseModel):
path: str
class FxGuiRectBody(BaseModel):
"""Rect màn hình (px vật lý, gốc trên-trái; x/y âm = màn hình trái)."""
x: int = 0
y: int = 0
w: int = 0
h: int = 0
class FxGuiRectRequest(BaseModel):
"""M7: anchor rect cho GUI native — rect panel nơi GUI embed xuất hiện."""
path: str
rect: FxGuiRectBody = FxGuiRectBody()
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)
@@ -889,8 +955,13 @@ async def hide_fx_gui(req: FxGuiNativeRequest,
sess, slot = _fxrt_native_editor((req.path or "").strip())
if sess is None:
return {"success": True, "applied": False}
# M13: capture preset TRUOC khi an — dong mastering panel / save project
# giua luc chinh GUI native khong mat preset moi (truoc day chi capture o
# /fx-gui/close; hide chi an -> chain luu preset cu/rong sau khi reload).
_preset = sess.capture_editor_preset(slot)
sess.send_editor_cmd("hide", slot=slot)
return {"success": True, "applied": True, "native": True}
return {"success": True, "applied": True, "native": True,
"preset_b64": _preset}
@router.post("/fx-gui/show")
@@ -905,6 +976,28 @@ async def show_fx_gui(req: FxGuiNativeRequest,
return {"success": True, "applied": True, "native": True}
@router.post("/fx-gui/rect")
async def fx_gui_rect(req: FxGuiRectRequest,
current_user: dict = Depends(get_current_user)):
"""M7: anchor PluginWindow native vào rect panel embed (SetWindowPos phía
C++ khi rect đổi — mirror FxGuiServer /show của float/embed). Frontend đo
rect panel mỗi 250ms và gửi; chỉ áp dụng editor in-process fxrt — GUI spawn
mode (HTTP bridge) tự có /show riêng, trả applied=False không lỗi."""
enforce_password_changed(current_user)
sess, slot = _fxrt_native_editor((req.path or "").strip())
r = req.rect
if sess is None:
logger.info("[fxgui] rect path=%s editor=NONE rect=(%s,%s,%s,%s) — "
"chua mo native / registry mat", (req.path or "").strip(),
r.x, r.y, r.w, r.h)
return {"success": True, "applied": False}
_sent = sess.send_editor_rect(slot, r.x, r.y, r.w, r.h)
logger.info("[fxgui] rect path=%s editor=%s slot=%s rect=(%s,%s,%s,%s) sent=%s",
(req.path or "").strip(), sess.name, slot,
r.x, r.y, r.w, r.h, _sent)
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):