fix(fx-gui): /show resize window+plugin view theo rect panel (layout khớp ô embed) — giữ offscreen, không topmost; sync sidecar mới vào bundle _internal
This commit is contained in:
@@ -514,11 +514,21 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& 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);
|
||||
|
||||
@@ -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")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user