M7-M16: VST FX GUI native embed hoàn thiện — fix click-2-lần chuyển slot, anchor lần mở đầu, taskbar icon

- app.jsx: masterGuiOpenRef sync truoc setState — guard chuyen slot doc ref, het race closure (click 1 lan chuyen slot)
- JuceFxGuiHost.cpp: M14 applyAnchor truoc setVisible (het flash giua man hinh); M15 clear WS_EX_APPWINDOW + set TOOLWINDOW (het taskbar icon — JUCE set APPWINDOW 0x40000 thang TOOLWINDOW); M9 owner DAW + M9d skip applyOwner khi an
- JuceFxLoop.cpp: M16 bo clearAnchor khi ed.open (retry ed.open ~700ms xoa rect fresh -> window moi khong anchor duoc lan dau mo panel); chi clear khi Close
- JuceFxEngine/app.jsx/plugins.py/fx_realtime.py: editor session realtime, anchor rect routing, retry ed.open
- Rebuild engine PyInstaller + juce_fx_bridge cmake; binaries dong bo 5 noi (bridge 7464a7ef)
This commit is contained in:
2026-09-06 19:45:14 +07:00
parent ec2c925d09
commit ef521d9981
20 changed files with 2040 additions and 461 deletions
+103
View File
@@ -25,6 +25,7 @@
#include <fstream>
#include <iostream>
#include <iterator>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
@@ -97,10 +98,97 @@ static std::string base64Encode(const std::string& in) {
}
return out;
}
// M13: prefix moi dong std::cerr bang [p=<pid> t=<ms-tu-start>] - log cua
// nhieu bridge process tron 1 file (FXRT_BRIDGE_LOG default) nen khong tach
// duoc event theo process/thoi gian. Wrapper stream buffer: phat prefix o dau
// dong, khong buffer ca dong (cerr unitbuf flush tung token - emit o sync()
// se tao prefix gia); mutex bao an toan thread (main/hb/ctrl/gui thread cung
// cerr). Cai dau run_juce_fx_loop truoc khi thread khac chay.
class PfxBuf : public std::streambuf {
public:
explicit PfxBuf(std::streambuf* sink)
: sink_(sink), lineStart_(true), t0_(std::chrono::steady_clock::now()) {
pid_ = (unsigned long)::GetCurrentProcessId();
}
~PfxBuf() override {
if (sink_) std::cerr.rdbuf(sink_);
}
protected:
int_type overflow(int_type c) override {
if (traits_type::eq_int_type(c, traits_type::eof()))
return traits_type::not_eof(c);
char ch = traits_type::to_char_type(c);
std::lock_guard<std::mutex> lk(mu_);
if (!writeOne(ch)) return traits_type::eof();
return c;
}
std::streamsize xsputn(const char* s, std::streamsize n) override {
std::lock_guard<std::mutex> lk(mu_);
std::streamsize done = 0;
while (done < n) {
const char c = s[done];
if (c == '\n') {
if (sink_->sputc('\n') == traits_type::eof()) break;
++done;
lineStart_ = true;
continue;
}
if (!emitPrefix()) break;
std::streamsize j = done;
while (j < n && s[j] != '\n') ++j;
const std::streamsize chunk = j - done;
if (chunk > 0) {
const std::streamsize w = sink_->sputn(s + done, chunk);
if (w != chunk) break;
done += chunk;
}
}
return done;
}
int sync() override {
std::lock_guard<std::mutex> lk(mu_);
return sink_->pubsync() == 0 ? 0 : -1;
}
private:
bool emitPrefix() {
if (!lineStart_) return true;
lineStart_ = false;
const long long ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0_).count();
char buf[64];
const int len = std::snprintf(buf, sizeof(buf), "[p=%lu t=%lld] ",
pid_, ms);
if (len <= 0) return true;
return sink_->sputn(buf, len) == (std::streamsize)len;
}
bool writeOne(char c) {
if (c == '\n') {
if (sink_->sputc('\n') == traits_type::eof()) return false;
lineStart_ = true;
return true;
}
if (!emitPrefix()) return false;
return sink_->sputc(c) != traits_type::eof();
}
std::streambuf* sink_;
std::mutex mu_;
bool lineStart_;
unsigned long pid_;
std::chrono::steady_clock::time_point t0_;
};
} // namespace
int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
uint32_t parentPid) {
PfxBuf pfx(std::cerr.rdbuf());
std::cerr.rdbuf(&pfx);
std::cerr << "[JuceFxLoop] start job=" << jobPath << " shm=" << shmName
<< " parent=" << parentPid << std::endl;
const std::string job = readFile(jobPath);
@@ -220,6 +308,16 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.ctrlRead++; // consume trước dispatch — không block ring
if (std::strncmp(key, "ed.", 3) != 0)
continue; // SET_PARAM cũ — no-op (xem comment trên)
// M7: ed.rx/ry/rw/rh = anchor rect (frontend đo panel embed,
// gửi 4 key mỗi 250ms) → guiHost.setAnchorField (thread-safe,
// không qua engine queue); reconcile GUI thread SetWindowPos
// khi rect đổi → window native nằm đúng rect panel.
if (key[3] == 'r' && (key[4] == 'x' || key[4] == 'y' ||
key[4] == 'w' || key[4] == 'h') &&
key[5] == '\0') {
guiHost.setAnchorField(slot, key[4], (int)value);
continue;
}
JuceFxEngine::EditorAction act;
bool known = true;
if (std::strcmp(key, "ed.open") == 0)
@@ -247,6 +345,11 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
w = v / 4096;
h = v % 4096;
}
// M16: KHÔNG clearAnchor khi Open — backend retry ed.open sau
// ~700ms (plugins.py) clear rect fresh JS vừa gửi → window mới
// tạo không anchor được (lần đầu mở panel). Rect cũ vẫn đúng khi
// panel yên (M7 dedupe); panel dời → JS gửi rect mới ≤250ms sửa.
// Chỉ clear khi Close (window xoá — rect cũ vô dụng).
engine.requestEditor(slot, act, w, h);
std::cerr << "[JuceFxLoop] ctrl " << key << " slot=" << slot
<< " w=" << w << " h=" << h << std::endl;