diff --git a/native_bridge/src/FxGuiServer.cpp b/native_bridge/src/FxGuiServer.cpp index b9f525d..ee8b602 100644 --- a/native_bridge/src/FxGuiServer.cpp +++ b/native_bridge/src/FxGuiServer.cpp @@ -270,14 +270,51 @@ uint64_t frameHash(HWND hwnd) { } void danceStart(HWND hwnd, HWND target, int x, int y) { - // Embedded mode: the native window NEVER comes on-screen — showing it - // topmost over the panel was the "floating always-on-top" bug. Real - // hardware input would require the window on-screen, so it is disabled; - // relayInput always uses synthetic PostMessage. - // ponytail: re-enable real input only with a proper sibling placement - // (owned window, WS_EX_NOACTIVATE, no WS_EX_TOPMOST) if a plugin rejects - // synthetic clicks. - (void)hwnd; (void)target; (void)x; (void)y; + // Real-input fallback (only entered when synthetic PostMessage was probed + // as rejected — iZotope/JUCE editors): hardware input needs the plugin + // window visible under the cursor. Anchored mode: place it exactly over + // the embed panel rect (borderless, WS_EX_NOACTIVATE so the DAW keeps + // focus, HWND_TOP NOT TOPMOST so it never floats over other apps — the + // "floating always-on-top" bug that got real input disabled in 4bb0db2). + 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_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 + g_dance = true; + g_anchored = true; // stays over the panel until /close (re-anchored by /show) + g_parkUntil = 0; + return; + } + if (g_dance) return; + // No rect known: shift the window so the clicked point sits under the + // cursor (cursor already over the embed panel inside the DAW). + POINT pt = { x, y }; + ClientToScreen(target, &pt); // screen pos of the clicked point (child offscreen) + POINT cur; + GetCursorPos(&cur); + RECT wr = {0}; + GetWindowRect(hwnd, &wr); + const int nx = wr.left + (cur.x - pt.x); + const int ny = wr.top + (cur.y - pt.y); + GetCursorPos(&g_savedCursor); + SetWindowPos(hwnd, HWND_TOP, nx, ny, 0, 0, + SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE); + Sleep(60); // let the compositor repaint so the editor sees itself active + g_dance = true; + g_parkUntil = GetTickCount() + 3000; } void danceEnd(HWND hwnd) { @@ -353,7 +390,8 @@ void relayInput(HWND hwnd, const json_object_s* o) { if (resp) { g_miss = 0; // PostMessage responded — keep it } else if (++g_miss >= 2) { - g_miss = 0; // real-input mode disabled: window must stay off-screen + g_realInput = true; // synthetic input rejected — go real hardware input + g_miss = 0; } g_preClickHash = 0; } @@ -532,9 +570,13 @@ void runHttpServer(HWND hwnd, std::atomic& stop) { if (!IsWindowVisible(hwnd)) SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); - g_dance = false; - g_anchored = false; - g_parkUntil = 0; + if (!g_realInput) { + // Real-input active: the dance/anchor state is owned by the + // input relay — /show must not yank the window off-screen. + g_dance = false; + g_anchored = false; + g_parkUntil = 0; + } sendResponse(c, 200, "application/json", "{\"ok\":true}", 11); } else if (req.path == "/preset") { std::string b64; diff --git a/native_bridge/src/RenderFxJob.cpp b/native_bridge/src/RenderFxJob.cpp index 71d3c81..f1baf83 100644 --- a/native_bridge/src/RenderFxJob.cpp +++ b/native_bridge/src/RenderFxJob.cpp @@ -1725,7 +1725,7 @@ static int fxGuiCreateWindow(const std::string& name, bool offscreen, RegisterClassA(&wc); const int x = offscreen ? -20000 : CW_USEDEFAULT; const int y = offscreen ? -20000 : CW_USEDEFAULT; - const DWORD exStyle = offscreen ? WS_EX_TOOLWINDOW : 0; + const DWORD exStyle = offscreen ? (WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE) : 0; // Borderless (WS_POPUP): GetWindowRect == client rect, so anchored // placement over the embed panel is exact. A captioned/bordered window // (WS_OVERLAPPEDWINDOW) put the client area ~31px lower/right of the diff --git a/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe index 1e8f180..dd8de9e 100644 Binary files a/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe differ diff --git a/src-tauri/binaries/fx_vst_bridge.exe b/src-tauri/binaries/fx_vst_bridge.exe index dc76286..dd8de9e 100644 Binary files a/src-tauri/binaries/fx_vst_bridge.exe and b/src-tauri/binaries/fx_vst_bridge.exe differ