fix(bridge): anchored fx-gui embed - dance dat window dung panel rect, khong park/restore cursor, resize view qua WM_FXGUI_RESIZE
This commit is contained in:
+31
-2
@@ -11908,6 +11908,7 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
|
||||
const imgRef = React.useRef(null);
|
||||
const boxRef = React.useRef(null);
|
||||
const deadRef = React.useRef(false);
|
||||
const screenRectRef = React.useRef(null);
|
||||
const [dead, setDead] = React.useState(false);
|
||||
const post = (payload) => { try { fetch(url + '/input', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(() => {}); } catch (e) {} };
|
||||
React.useEffect(() => {
|
||||
@@ -11922,6 +11923,34 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
|
||||
return () => { clearInterval(tick); clearInterval(ping); };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [url]);
|
||||
// Embed panel screen rect (physical px) for the bridge's anchored real-input
|
||||
// dance: Tauri innerPosition + scaleFactor; window.screenX/Y fallback when
|
||||
// not running under Tauri. Refreshed ~500ms so scroll/resize stays current.
|
||||
React.useEffect(() => {
|
||||
let alive = true;
|
||||
const refresh = () => {
|
||||
const box = boxRef.current;
|
||||
if (!box) return;
|
||||
const r = box.getBoundingClientRect();
|
||||
if (r.width < 4 || r.height < 4) return;
|
||||
try {
|
||||
const tw = window.__TAURI__ && window.__TAURI__.window;
|
||||
if (tw && tw.getCurrentWindow) {
|
||||
Promise.all([tw.getCurrentWindow().innerPosition(), tw.getCurrentWindow().scaleFactor()]).then(([pos, sf]) => {
|
||||
if (!alive || !boxRef.current) return;
|
||||
const rb = boxRef.current.getBoundingClientRect();
|
||||
screenRectRef.current = { x: Math.round(pos.x + rb.left * sf), y: Math.round(pos.y + rb.top * sf), w: Math.round(rb.width * sf), h: Math.round(rb.height * sf) };
|
||||
}).catch(() => {});
|
||||
} else {
|
||||
screenRectRef.current = { x: Math.round((window.screenX || 0) + r.left), y: Math.round((window.screenY || 0) + r.top), w: Math.round(r.width), h: Math.round(r.height) };
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
refresh();
|
||||
const iv = setInterval(refresh, 500);
|
||||
return () => { alive = false; clearInterval(iv); };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [url]);
|
||||
const pt = (e) => {
|
||||
const box = boxRef.current, img = imgRef.current;
|
||||
if (!box || !img || !img.naturalWidth || !img.naturalHeight) return null;
|
||||
@@ -11934,7 +11963,7 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
|
||||
};
|
||||
const handle = (e) => {
|
||||
const p = pt(e); if (!p) return;
|
||||
post({ type: e.type === 'mousedown' ? 'mousedown' : e.type === 'mouseup' ? 'mouseup' : 'mousemove', x: p.x, y: p.y, button: e.button, buttons: e.buttons });
|
||||
post({ type: e.type === 'mousedown' ? 'mousedown' : e.type === 'mouseup' ? 'mouseup' : 'mousemove', x: p.x, y: p.y, button: e.button, buttons: e.buttons, rect: screenRectRef.current || undefined });
|
||||
};
|
||||
const close = () => { try { fetch(url + '/close', { method: 'POST' }).catch(() => {}); } catch (e) {} onClose && onClose(); };
|
||||
return (
|
||||
@@ -11949,7 +11978,7 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
|
||||
<div ref={boxRef} tabIndex={0} className="flex-1 relative bg-slate-950 border border-slate-800 rounded-b-lg overflow-hidden outline-none"
|
||||
onMouseDown={handle} onMouseUp={handle} onMouseMove={handle}
|
||||
onMouseLeave={() => post({ type: 'mouseup', x: -1, y: -1, button: 0 })}
|
||||
onWheel={e => { const p = pt(e); if (p) post({ type: 'wheel', x: p.x, y: p.y, deltaY: e.deltaY }); }}
|
||||
onWheel={e => { const p = pt(e); if (p) post({ type: 'wheel', x: p.x, y: p.y, deltaY: e.deltaY, rect: screenRectRef.current || undefined }); }}
|
||||
onKeyDown={e => { e.preventDefault(); post({ type: 'keydown', keyCode: e.keyCode || e.which }); }}
|
||||
onKeyUp={e => { e.preventDefault(); post({ type: 'keyup', keyCode: e.keyCode || e.which }); }}>
|
||||
<img ref={imgRef} alt="VST GUI" className="w-full h-full object-contain select-none pointer-events-none" draggable={false} />
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -50,7 +50,7 @@
|
||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608182350" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608182359" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Binary file not shown.
@@ -41,6 +41,10 @@
|
||||
#include "vst3sdk/public.sdk/source/vst/moduleinfo/json.h"
|
||||
#endif
|
||||
|
||||
// Defined in RenderFxJob.cpp (global scope); declared here outside the
|
||||
// anonymous namespace so both TUs see the same symbol.
|
||||
extern void fxGuiRequestResize(HWND hwnd, int w, int h);
|
||||
|
||||
namespace {
|
||||
|
||||
// --- tiny JSON helpers (same API as RenderFxJob.cpp) ------------------------
|
||||
@@ -245,6 +249,12 @@ std::atomic<DWORD> g_parkUntil{0}; // safety restore deadline (GetTickCount)
|
||||
POINT g_savedCursor = {0, 0}; // cursor to restore after the dance
|
||||
int g_miss = 0; // consecutive no-response PostMessage clicks
|
||||
uint64_t g_preClickHash = 0;
|
||||
// Anchored mode: the frontend sends the embed panel's screen rect (physical
|
||||
// px) with every /input, so the real-input dance places the plugin window
|
||||
// exactly over the panel -- cursor never moves, no park/restore churn. Zero
|
||||
// rect = legacy cursor-relative mode.
|
||||
RECT g_embedRect = {0, 0, 0, 0};
|
||||
bool g_anchored = false; // window anchored over panel (stays until /close)
|
||||
|
||||
uint64_t frameHash(HWND hwnd) {
|
||||
std::vector<unsigned char> jpg;
|
||||
@@ -255,6 +265,33 @@ uint64_t frameHash(HWND hwnd) {
|
||||
}
|
||||
|
||||
void danceStart(HWND hwnd, HWND target, int x, int y) {
|
||||
// Anchored mode: place the host window's client area exactly over the embed
|
||||
// panel rect (borderless + topmost) so real mouse events hit the plugin
|
||||
// without moving the cursor, then ask the pump thread to size the plugin
|
||||
// view to the panel. No park deadline -- the window stays until /close.
|
||||
// Re-runs on every click so a scrolled/resized panel re-anchors.
|
||||
if (g_embedRect.right > g_embedRect.left && g_embedRect.bottom > g_embedRect.top) {
|
||||
const int cw = g_embedRect.right - g_embedRect.left;
|
||||
const int ch = g_embedRect.bottom - g_embedRect.top;
|
||||
LONG_PTR style = GetWindowLongPtrA(hwnd, GWL_STYLE);
|
||||
if (style & (WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
|
||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX)) {
|
||||
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
|
||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
||||
SetWindowLongPtrA(hwnd, GWL_STYLE, style);
|
||||
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
|
||||
SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
||||
}
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 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
|
||||
g_dance = true;
|
||||
g_anchored = true;
|
||||
g_parkUntil = 0; // anchored: stay until /close
|
||||
return;
|
||||
}
|
||||
if (g_dance) return;
|
||||
// Dat window ngay tai vi tri cursor (dang nam tren panel embed trong DAW)
|
||||
// chu khong nhay ra goc man hinh: shift window sao cho client point (x,y)
|
||||
@@ -278,6 +315,7 @@ void danceStart(HWND hwnd, HWND target, int x, int y) {
|
||||
|
||||
void danceEnd(HWND hwnd) {
|
||||
if (!g_dance) return;
|
||||
if (g_anchored) return; // anchored: stays until /close
|
||||
SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
SetCursorPos(g_savedCursor.x, g_savedCursor.y);
|
||||
@@ -305,6 +343,21 @@ void relayInput(HWND hwnd, const json_object_s* o) {
|
||||
const std::string type = memberString(o, "type", "");
|
||||
const int x = (int)memberInt(o, "x", 0);
|
||||
const int y = (int)memberInt(o, "y", 0);
|
||||
// Anchored rect from the embed panel (screen coords, physical px).
|
||||
const json_value_s* rv = memberValue(o, "rect");
|
||||
if (rv && rv->type == json_type_object) {
|
||||
const json_object_s* ro = static_cast<const json_object_s*>(rv->payload);
|
||||
const LONG rw = (LONG)memberInt(ro, "w", 0);
|
||||
const LONG rh = (LONG)memberInt(ro, "h", 0);
|
||||
if (rw > 0 && rh > 0) {
|
||||
g_embedRect.left = (LONG)memberInt(ro, "x", 0);
|
||||
g_embedRect.top = (LONG)memberInt(ro, "y", 0);
|
||||
g_embedRect.right = g_embedRect.left + rw;
|
||||
g_embedRect.bottom = g_embedRect.top + rh;
|
||||
} else {
|
||||
g_embedRect = {0, 0, 0, 0}; // empty rect -> back to cursor mode
|
||||
}
|
||||
}
|
||||
const int button = (int)memberInt(o, "button", 0);
|
||||
const int buttons = (int)memberInt(o, "buttons", 0); // DOM e.buttons bitmask
|
||||
const int deltaY = (int)memberInt(o, "deltaY", 0);
|
||||
@@ -324,7 +377,12 @@ void relayInput(HWND hwnd, const json_object_s* o) {
|
||||
// responds (reset miss); two consecutive no-response clicks mean the
|
||||
// editor rejects synthetic input — switch to real hardware input.
|
||||
if (!g_realInput) {
|
||||
if (isDown) {
|
||||
if (g_embedRect.right > g_embedRect.left) {
|
||||
// Anchored mode exists for plugins that need real input; the window
|
||||
// appears exactly over the panel, so probing PostMessage first only
|
||||
// wastes clicks.
|
||||
g_realInput = true;
|
||||
} else if (isDown) {
|
||||
g_preClickHash = frameHash(hwnd);
|
||||
} else if (isUp && g_preClickHash) {
|
||||
Sleep(250);
|
||||
|
||||
@@ -689,6 +689,37 @@ public:
|
||||
delete s->plugFrame;
|
||||
s->plugFrame = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Resize the editor view to w x h (VST3 host resize flow: checkSizeConstraint
|
||||
// lets the plugin clamp to a supported size, host resizes the window, then
|
||||
// onSize tells the plugin -- it moves/resizes its own child window). Runs on
|
||||
// the GUI pump thread (WM_FXGUI_RESIZE) -- plugin calls must not come from
|
||||
// the HTTP worker thread.
|
||||
bool resizeEditor(void* parentWindowHandle, int w, int h) {
|
||||
#ifndef HAVE_VST3SDK
|
||||
(void)parentWindowHandle; (void)w; (void)h;
|
||||
return false;
|
||||
#else
|
||||
auto* s = static_cast<Vst3FxState*>(state_);
|
||||
if (!s || !s->view || w <= 0 || h <= 0) return false;
|
||||
Steinberg::ViewRect vr = {0, 0, w, h};
|
||||
s->view->checkSizeConstraint(&vr); // plugin clamps to a supported size
|
||||
const int cw = vr.right - vr.left;
|
||||
const int ch = vr.bottom - vr.top;
|
||||
if (cw <= 0 || ch <= 0) return false;
|
||||
#ifdef _WIN32
|
||||
RECT r = {0, 0, cw, ch};
|
||||
AdjustWindowRectEx(&r,
|
||||
(DWORD)GetWindowLongPtrA((HWND)parentWindowHandle, GWL_STYLE),
|
||||
FALSE,
|
||||
(DWORD)GetWindowLongPtrA((HWND)parentWindowHandle, GWL_EXSTYLE));
|
||||
SetWindowPos((HWND)parentWindowHandle, nullptr, 0, 0,
|
||||
r.right - r.left, r.bottom - r.top,
|
||||
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
#endif
|
||||
s->view->onSize(&vr);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
bool loaded() const { return state_ != nullptr; }
|
||||
@@ -892,6 +923,10 @@ int run_render_fx_job(const std::string& jobPath, const std::string& inPath,
|
||||
|
||||
// --- --open-fx-gui <job.json>: native editor window for one VST3 FX ----------
|
||||
#ifdef _WIN32
|
||||
// Worker (HTTP) thread -> pump thread editor-resize request. Declared
|
||||
// outside the anonymous namespace: the definition sits after the namespace.
|
||||
static void fxGuiResizeSafe(void* fx, HWND hwnd, int w, int h);
|
||||
|
||||
namespace {
|
||||
|
||||
const char* kFxGuiWndClass = "SonicForge_FX_GUI_Class";
|
||||
@@ -900,6 +935,10 @@ struct FxGuiCtx {
|
||||
bool closing = false;
|
||||
};
|
||||
|
||||
// Worker (HTTP) thread -> pump thread editor-resize request. The plugin's
|
||||
// checkSizeConstraint/onSize must run on the thread that owns the editor.
|
||||
#define WM_FXGUI_RESIZE (WM_APP + 0x51)
|
||||
|
||||
LRESULT CALLBACK FxGuiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
FxGuiCtx* ctx = (FxGuiCtx*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||
switch (msg) {
|
||||
@@ -909,6 +948,9 @@ LRESULT CALLBACK FxGuiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
case WM_DESTROY:
|
||||
if (ctx) ctx->closing = true;
|
||||
return 0;
|
||||
case WM_FXGUI_RESIZE:
|
||||
fxGuiResizeSafe((void*)GetPropA(hwnd, "FXGUI_FX"), hwnd, (int)wp, (int)lp);
|
||||
return 0;
|
||||
default:
|
||||
return DefWindowProcA(hwnd, msg, wp, lp);
|
||||
}
|
||||
@@ -934,6 +976,18 @@ static void fxGuiDetachSafe(void* fx) {
|
||||
(void)fx;
|
||||
#endif
|
||||
}
|
||||
static void fxGuiResizeSafe(void* fx, HWND hwnd, int w, int h) {
|
||||
#ifdef HAVE_VST3SDK
|
||||
__try { static_cast<Vst3Fx*>(fx)->resizeEditor(hwnd, w, h); }
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {}
|
||||
#else
|
||||
(void)fx; (void)hwnd; (void)w; (void)h;
|
||||
#endif
|
||||
}
|
||||
// Worker (HTTP) thread asks the GUI pump thread to resize the plugin view.
|
||||
void fxGuiRequestResize(HWND hwnd, int w, int h) {
|
||||
PostMessageA(hwnd, WM_FXGUI_RESIZE, (WPARAM)w, (LPARAM)h);
|
||||
}
|
||||
|
||||
// Shared setup for both GUI modes: parse job, load plugin, create host window
|
||||
// (offscreen for embedded capture), attach editor (SEH-guarded). Returns 0 ok,
|
||||
@@ -1034,8 +1088,10 @@ int run_fx_gui_server(const std::string& jobPath) {
|
||||
HWND hwnd = nullptr;
|
||||
const int rc = fxGuiSetup(jobPath, &fx, &hwnd, /*offscreen=*/true, 0);
|
||||
if (rc) return rc;
|
||||
SetPropA(hwnd, "FXGUI_FX", &fx); // for WM_FXGUI_RESIZE on the pump thread
|
||||
|
||||
const int srv = fxGuiServerLoop(hwnd);
|
||||
RemovePropA(hwnd, "FXGUI_FX");
|
||||
fxGuiDetachSafe(&fx);
|
||||
std::cout << "[FxGuiServer] closed" << std::endl;
|
||||
return srv;
|
||||
|
||||
Reference in New Issue
Block a user