V45.45: fix GUI embed VST den tren box (Nectar/iZotope) - first-paint kick real click + BitBlt black-frame fallback + on-screen parking MAC DINH (SF_FXGUI_EMBED_ONSCREEN=0 de tat) + plugins.py log TAT CA stdout bridge (khong nuot dong truoc SF_FXGUI_PORT=) + auto-open GUI khi add VST FX vao Mastering slot. Rebuild daw_vst_bridge+fx_vst_bridge+installer NSIS.
This commit is contained in:
@@ -172,6 +172,14 @@ void sendOptions(SOCKET c) {
|
||||
// frame, while the GDI BitBlt view is a stale first paint for GPU-rendered
|
||||
// editors (JUCE/FAST Compressor) and never updates. BitBlt stays as fallback.
|
||||
// Returns JPEG.
|
||||
// Mean of the raw BGRA frame (0..255) — black-frame guard below.
|
||||
static double frameMean(const unsigned char* px, int w, int h) {
|
||||
const size_t n = (size_t)w * h;
|
||||
unsigned long long s = 0;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
s += px[i * 4 + 2] + px[i * 4 + 1] + px[i * 4 + 0];
|
||||
return (double)s / (3.0 * (double)n);
|
||||
}
|
||||
bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality,
|
||||
float* outAvg = nullptr, bool* outOkB = nullptr,
|
||||
bool* outOkP = nullptr) {
|
||||
@@ -207,7 +215,18 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality,
|
||||
// for GPU-rendered editors (JUCE/FAST Compressor) and never updates, which
|
||||
// made the panel look frozen. BitBlt stays only as a fallback.
|
||||
unsigned char* px = nullptr;
|
||||
if (okP) px = pxP.data();
|
||||
if (okP && okB) {
|
||||
// Black-frame guard: PrintWindow can return TRUE with an ALL-BLACK
|
||||
// frame while the editor has not painted yet (iZotope-class, e.g.
|
||||
// Nectar — those editors paint only after their window was activated;
|
||||
// the /show first-paint kick fixes that, but until it fires BitBlt is
|
||||
// the only source of real pixels). If PrintWindow is pure black and
|
||||
// the GDI view has content, send the GDI view instead of a black panel.
|
||||
const double mP = frameMean(pxP.data(), w, h);
|
||||
const double mB = frameMean(pxB.data(), w, h);
|
||||
if (mP < 6.0 && mB > 6.0) px = pxB.data();
|
||||
else px = pxP.data();
|
||||
} else if (okP) px = pxP.data();
|
||||
else if (okB) px = pxB.data();
|
||||
if (outOkB) *outOkB = okB;
|
||||
if (outOkP) *outOkP = okP;
|
||||
@@ -277,15 +296,26 @@ POINT g_savedCursor = {0, 0}; // cursor to restore after the dance
|
||||
// rect = legacy cursor-relative mode.
|
||||
RECT g_embedRect = {0, 0, 0, 0};
|
||||
bool g_anchored = false; // window anchored over panel (stays until /close)
|
||||
// iZotope-class editors (Nectar/Ozone) paint their surface ONLY after the
|
||||
// window was actually visible AND activated at least once; parked HWND_BOTTOM
|
||||
// under the DAW they never paint -> black forever. /show performs a one-time
|
||||
// kick (raise above the DAW + invalidate + real click), then demotes the
|
||||
// window back under the DAW — the painted surface survives the demote.
|
||||
// Reset on /hide so each reopen re-kicks.
|
||||
std::atomic<bool> g_paintKicked{false};
|
||||
|
||||
// SF_FXGUI_EMBED_ONSCREEN=1: park the host window ON a monitor at the embed
|
||||
// panel rect (HWND_BOTTOM, under the DAW -> user never sees it) instead of
|
||||
// off-screen at -20000. DWM does not redirect windows parked fully off the
|
||||
// desktop on some drivers (Intel UHD 630 + iZotope GL editor) -> PrintWindow
|
||||
// captures only black. On a monitor (even occluded by the DAW above) DWM
|
||||
// keeps a redirection surface -> /frame gets real pixels. Real-input dance
|
||||
// still lifts the window to HWND_TOP for actual clicks (g_anchored guard).
|
||||
static const bool g_embedOnscreen = std::getenv("SF_FXGUI_EMBED_ONSCREEN") != nullptr;
|
||||
// On-screen embed parking (DEFAULT since V45.45): park the host window ON a
|
||||
// monitor at the embed panel rect (HWND_BOTTOM, under the DAW -> user never
|
||||
// sees it) instead of off-screen at -20000. DWM does not redirect windows
|
||||
// parked fully off the desktop on some drivers (Intel UHD 630 + iZotope GL
|
||||
// editor) -> capture sees only black. On a monitor (even occluded by the DAW
|
||||
// above) DWM keeps a redirection surface -> /frame gets real pixels. Real-input
|
||||
// dance still lifts the window to HWND_TOP for actual clicks (g_anchored
|
||||
// guard). SF_FXGUI_EMBED_ONSCREEN=0 disables (env kept for debugging).
|
||||
static const bool g_embedOnscreen = [] {
|
||||
const char* v = std::getenv("SF_FXGUI_EMBED_ONSCREEN");
|
||||
return v == nullptr || v[0] != '0';
|
||||
}();
|
||||
// SF_FXGUI_DEBUG=1: rate-limited /frame capture ground truth ([FxGuiDbg] lines).
|
||||
static const bool g_fxGuiDbg = std::getenv("SF_FXGUI_DEBUG") != nullptr;
|
||||
|
||||
@@ -522,8 +552,8 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
|
||||
// to show that blank frame as a white flash on open.
|
||||
sendResponse(c, 503, "application/json", "{\"error\":\"loading\"}", 19);
|
||||
} else {
|
||||
// EMBED_ONSCREEN: keep the host window ON a monitor at the
|
||||
// embed panel rect (HWND_BOTTOM, under the DAW) so DWM
|
||||
// On-screen embed parking: keep the host window ON a monitor
|
||||
// at the embed panel rect (HWND_BOTTOM, under the DAW) so DWM
|
||||
// redirects the editor's GPU surface and capture sees real
|
||||
// pixels — the one-shot rect /show may have run pre-attach
|
||||
// (deduped), so reconcile here on every poll until it fits.
|
||||
@@ -609,6 +639,7 @@ void runHttpServer(HWND hwnd, std::atomic<bool>& stop) {
|
||||
g_anchored = false;
|
||||
g_dance = false;
|
||||
g_parkUntil = 0;
|
||||
g_paintKicked = false; // next /show re-kicks the first paint
|
||||
sendResponse(c, 200, "application/json", "{\"ok\":true}", 11);
|
||||
} else if (req.path == "/show") {
|
||||
// Optional body {rect:{x,y,w,h}} (screen px) — anchored placement
|
||||
@@ -635,43 +666,74 @@ 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 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.
|
||||
if (rectOk) {
|
||||
// Embedded mode: park the host window ON a monitor at the embed
|
||||
// panel rect (HWND_BOTTOM, under the DAW -> user never sees it)
|
||||
// instead of off-screen at -20000. iZotope-class editors never
|
||||
// paint while fully off-screen, and DWM does not redirect windows
|
||||
// parked off the desktop on some drivers (Intel UHD 630) — on a
|
||||
// monitor (even occluded by the DAW above) capture sees real
|
||||
// pixels. The panel rect also sizes the host window + plugin view
|
||||
// so the capture frame matches the embed panel (layout bug);
|
||||
// /hide hid the window, so re-show it here.
|
||||
// Skip while the editor is still attaching (pump thread busy;
|
||||
// SetWindowPos from this worker would block until load/attach
|
||||
// finishes) and while anchored/dancing (real-input owns the
|
||||
// window position during clicks).
|
||||
if (g_embedOnscreen && rectOk && g_fxGuiEditorReady.load() &&
|
||||
!g_anchored && !g_dance) {
|
||||
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
|
||||
const bool samePlace =
|
||||
cur.left == g_embedRect.left && cur.top == g_embedRect.top &&
|
||||
cur.right - cur.left == cw && cur.bottom - cur.top == ch;
|
||||
// Skip when already parked right: 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);
|
||||
}
|
||||
// EMBED_ONSCREEN: park the window ON the monitor at the embed
|
||||
// panel rect, HWND_BOTTOM (below the DAW -> user never sees it)
|
||||
// so DWM redirects the editor's GPU surface and /frame can
|
||||
// capture it. Skip while the editor is still attaching (the
|
||||
// pump thread is busy; SetWindowPos from here would block this
|
||||
// worker until load/attach finishes) and while anchored/dancing
|
||||
// (real-input keeps the window on top over the panel for clicks).
|
||||
if (g_embedOnscreen && rectOk && !g_anchored && !g_dance &&
|
||||
g_fxGuiEditorReady.load()) {
|
||||
const int cw = g_embedRect.right - g_embedRect.left;
|
||||
const int ch = g_embedRect.bottom - g_embedRect.top;
|
||||
RECT cur = {};
|
||||
GetWindowRect(hwnd, &cur);
|
||||
if (!IsWindowVisible(hwnd) || cur.left != g_embedRect.left ||
|
||||
cur.top != g_embedRect.top || cur.right - cur.left != cw ||
|
||||
cur.bottom - cur.top != ch)
|
||||
if (!samePlace || !IsWindowVisible(hwnd))
|
||||
SetWindowPos(hwnd, HWND_BOTTOM, g_embedRect.left,
|
||||
g_embedRect.top, cw, ch,
|
||||
SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
if (cur.right - cur.left != cw || cur.bottom - cur.top != ch)
|
||||
fxGuiRequestResize(hwnd, cw, ch);
|
||||
// First-paint kick (one-time per /show cycle, only after the
|
||||
// editor attached). iZotope-class editors (Nectar/Ozone) paint
|
||||
// ONLY when their window is actually visible AND activated:
|
||||
// parked HWND_BOTTOM under the DAW they never paint -> black
|
||||
// forever, and SetForegroundWindow from this background process
|
||||
// is blocked by the Windows foreground lock. So: raise the
|
||||
// window ABOVE the DAW at the panel rect, paint it there, then
|
||||
// demote back under the DAW — the painted surface survives the
|
||||
// demote (capture stays non-black at HWND_BOTTOM).
|
||||
if (!g_paintKicked) {
|
||||
g_paintKicked = true;
|
||||
SetWindowPos(hwnd, HWND_TOP, g_embedRect.left, g_embedRect.top,
|
||||
cw, ch, SWP_SHOWWINDOW | SWP_NOACTIVATE);
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
InvalidateRect(hwnd, nullptr, TRUE);
|
||||
RedrawWindow(hwnd, nullptr, nullptr,
|
||||
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
|
||||
// Foreground-lock bypass: a real synthesized click (system
|
||||
// input, not subject to the SetForegroundWindow lock)
|
||||
// activates the window the same way a user click would.
|
||||
Sleep(200);
|
||||
RECT wr = {};
|
||||
GetWindowRect(hwnd, &wr);
|
||||
const int cx = wr.left + (wr.right - wr.left) / 2;
|
||||
const int cy = wr.top + 8; // title-bar strip, avoid knobs
|
||||
SetCursorPos(cx, cy);
|
||||
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
|
||||
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||
Sleep(500); // give the editor a beat to paint while on top
|
||||
SetWindowPos(hwnd, HWND_BOTTOM, g_embedRect.left, g_embedRect.top,
|
||||
cw, ch, SWP_NOACTIVATE);
|
||||
std::cerr << "[FxGui] first-paint kick fg=click @" << cx
|
||||
<< "," << cy << std::endl;
|
||||
}
|
||||
} else if (!IsWindowVisible(hwnd)) {
|
||||
// No usable rect yet (pre-layout / still attaching): keep the
|
||||
// window somewhere harmless until the frontend supplies it.
|
||||
SetWindowPos(hwnd, HWND_NOTOPMOST, -20000, -20000, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
}
|
||||
|
||||
@@ -1910,7 +1910,10 @@ int run_fx_gui_server(const std::string& jobPath) {
|
||||
// crash fix (attach on a real monitor) and the black-capture fix (stay
|
||||
// on a monitor afterwards, parked under the DAW at the panel rect) go
|
||||
// together — setting only EMBED_ONSCREEN must not regress the crash.
|
||||
const bool embedOnscreen = std::getenv("SF_FXGUI_EMBED_ONSCREEN") != nullptr;
|
||||
// V45.45: on-screen embed parking la MAC DINH (DWM Intel UHD 630 khong
|
||||
// redirect window parked -20000). Env chi con de TAT: SF_FXGUI_EMBED_ONSCREEN=0.
|
||||
const char* eosEnv = std::getenv("SF_FXGUI_EMBED_ONSCREEN");
|
||||
const bool embedOnscreen = eosEnv == nullptr || eosEnv[0] != '0';
|
||||
const bool attachOnscreen = embedOnscreen || std::getenv("SF_FXGUI_ATTACH_ONSCREEN") != nullptr;
|
||||
std::cout << "[FxGui] env SF_FXGUI_ATTACH_ONSCREEN=" << (attachOnscreen ? 1 : 0)
|
||||
<< " SF_FXGUI_EMBED_ONSCREEN=" << (embedOnscreen ? 1 : 0)
|
||||
|
||||
Reference in New Issue
Block a user