diff --git a/native_bridge/src/FxGuiServer.cpp b/native_bridge/src/FxGuiServer.cpp index e7f2afe..b9f525d 100644 --- a/native_bridge/src/FxGuiServer.cpp +++ b/native_bridge/src/FxGuiServer.cpp @@ -514,11 +514,21 @@ void runHttpServer(HWND hwnd, std::atomic& stop) { if (sroot) std::free(sroot); // Embedded mode: the native window NEVER comes on-screen — it stays // parked off-screen at -20000,-20000 (GDI capture + synthetic input - // work regardless of position). The panel rect is still recorded - // for the (disabled) real-input fallback and to keep the API - // contract; /hide hid the window, so re-show it off-screen so + // work regardless of position). The panel rect sizes the host + // window + plugin view so the capture frame matches the embed panel + // (layout bug); /hide hid the window, so re-show it off-screen so // /frame keeps capturing. - (void)rectOk; + if (rectOk) { + const int cw = g_embedRect.right - g_embedRect.left; + const int ch = g_embedRect.bottom - g_embedRect.top; + RECT cur = {}; + GetWindowRect(hwnd, &cur); + // Skip when already the right size: frontend re-POSTs /show + // every ~500ms; re-laying-out the plugin view every tick is + // wasteful (WM_FXGUI_RESIZE -> checkSizeConstraint/onSize). + if (cur.right - cur.left != cw || cur.bottom - cur.top != ch) + fxGuiRequestResize(hwnd, cw, ch); + } if (!IsWindowVisible(hwnd)) SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); diff --git a/native_bridge/verify_gui_fix.py b/native_bridge/verify_gui_fix.py new file mode 100644 index 0000000..ff8c2d9 --- /dev/null +++ b/native_bridge/verify_gui_fix.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +"""Verify GUI fix: /show resizes host window to panel rect, stays offscreen, not topmost.""" +import ctypes, time, subprocess, os, threading, json +from ctypes import wintypes +import urllib.request + +EXE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\fx_vst_bridge.exe" +JOB = r"C:\Users\locpham\AppData\Local\Temp\test_gui_WaveObserver.vst3.json" +CLASS = b"SonicForge_FX_GUI_Class" + +user32 = ctypes.WinDLL("user32", use_last_error=True) +user32.GetWindowRect.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.RECT)] +user32.IsWindowVisible.restype = ctypes.c_int +user32.GetWindowLongW.restype = ctypes.c_long +user32.GetWindowLongW.argtypes = [wintypes.HWND, ctypes.c_int] +user32.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)] +user32.GetClassNameA.argtypes = [wintypes.HWND, ctypes.c_char_p, ctypes.c_int] +GWL_EXSTYLE = -20 + +def find_own_windows(pid): + out = [] + WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM) + @WNDENUMPROC + def cb(hwnd, lp): + t = wintypes.DWORD() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(t)) + if t.value == pid: out.append(hwnd) + return True + user32.EnumWindows(cb, 0) + return out + +def http(method, url, body=None): + req = urllib.request.Request(url, method=method, + data=body.encode() if body else None, + headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=10) as r: + return r.read() + +proc = subprocess.Popen([EXE, "--fx-gui", JOB], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, + env=dict(os.environ, SF_PARENT_PID=str(os.getpid())), + creationflags=subprocess.CREATE_NO_WINDOW) +lines = [] +def reader(): + for line in proc.stdout: lines.append(line.rstrip()) +threading.Thread(target=reader, daemon=True).start() + +def rect_str(h): + r = wintypes.RECT(); user32.GetWindowRect(h, ctypes.byref(r)) + ex = user32.GetWindowLongW(h, GWL_EXSTYLE) + return "L%d T%d R%d B%d W%d H%d vis=%d ex=0x%X topmost=%d" % ( + r.left, r.top, r.right, r.bottom, r.right-r.left, r.bottom-r.top, + user32.IsWindowVisible(h), ex, bool(ex & 0x8)) + +try: + port = None + t0 = time.time() + while time.time() - t0 < 20: + for l in lines: + if l.startswith("SF_FXGUI_PORT="): + port = int(l.split("=", 1)[1]); break + if port: break + time.sleep(0.1) + base = f"http://127.0.0.1:{port}" + t0 = time.time() + while time.time() - t0 < 25: + if any("editor open" in l for l in lines): break + time.sleep(0.2) + hwnd = None + for h in find_own_windows(proc.pid): + buf = ctypes.create_string_buffer(256) + user32.GetClassNameA(h, buf, 256) + if buf.value == CLASS: + hwnd = h; break + assert hwnd, "fx-gui window not found in own process" + print("fx-gui hwnd:", hwnd) + print("before /show:", rect_str(hwnd)) + # /show with panel rect -> resize host window to panel, stay offscreen, no topmost + http("POST", base + "/show", json.dumps({"rect": {"x": 300, "y": 200, "w": 640, "h": 480}})) + time.sleep(1.0) + print("after /show:", rect_str(hwnd)) + r = wintypes.RECT(); user32.GetWindowRect(hwnd, ctypes.byref(r)) + w, h = r.right-r.left, r.bottom-r.top + assert r.left == -20000 and r.top == -20000, "window not offscreen!" + assert not (user32.GetWindowLongW(hwnd, GWL_EXSTYLE) & 0x8), "window topmost!" + assert (w, h) != (800, 600), "host window still default 800x600 — resize not applied" + print("RESIZE applied: host window now %dx%d (was default 800x600)" % (w, h)) + fr = http("GET", base + "/frame") + print("frame size after /show+resize:", len(fr)) + assert len(fr) > 1000, "capture broken after /show" + print("ALL CHECKS PASSED") +finally: + proc.terminate() + try: proc.wait(timeout=5) + except Exception: proc.kill() + print("cleanup done") diff --git a/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe index 0497d94..34cf129 100644 Binary files a/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe differ diff --git a/src-tauri/binaries/daw_vst_bridge.exe b/src-tauri/binaries/daw_vst_bridge.exe index 0497d94..34cf129 100644 Binary files a/src-tauri/binaries/daw_vst_bridge.exe and b/src-tauri/binaries/daw_vst_bridge.exe differ 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 ab5eb05..1c8300c 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 ab5eb05..1c8300c 100644 Binary files a/src-tauri/binaries/fx_vst_bridge.exe and b/src-tauri/binaries/fx_vst_bridge.exe differ