Merge plugin-editor drop: a card dragged onto a third-party plugin's editor is handed to the OS as a file drop

This commit is contained in:
2026-09-25 14:15:47 -04:00
19 changed files with 992 additions and 135 deletions
+32 -5
View File
@@ -13,6 +13,7 @@
#include "shell/instrument/editor_internal.h" // (transitively: lice + the kit, Windows only)
#include "shell/instrument/reasampler_processor.h"
#include "core/version/app_version.h" // vstOutputName — channel-forked window class name
using namespace Steinberg;
@@ -20,7 +21,32 @@ namespace reasampler::vst {
#ifdef _WIN32
namespace {
constexpr const wchar_t* kChildClassName = L"ReaSampler9000VstEditor";
// Channel-forked so stable and beta never fight over one class registration (a second
// RegisterClassW against an already-taken name silently fails, leaving that channel's editors
// running the FIRST-registered channel's wndProc). vstOutputName() is ASCII (CMake OUTPUT_NAME),
// so a plain widen is safe.
const std::wstring& childClassName() {
static const std::wstring name = [] {
const std::string& base = version::vstOutputName();
return std::wstring(base.begin(), base.end()) + L"VstEditor";
}();
return name;
}
// The module holding a given code address — used to resolve THIS wndProc's own module rather
// than the parent's hInstance REAPER hands us. Registering against the parent would put the class
// in REAPER's own module while the wndproc still points into our DLL — a dangling procedure
// pointer once our DLL unloads but REAPER's class registration outlives it. Takes the address
// rather than naming wndProc directly so a free function in this anonymous namespace doesn't need
// access to the private member.
HMODULE moduleHandleFor(void* codeAddress) {
HMODULE mod = nullptr;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(codeAddress), &mod);
return mod;
}
// The change-detection poll (WM_TIMER on the child window). A low-frequency UI-thread
// timer: responsive enough that a recapture/ingest/assign refreshes within a bounded
@@ -105,8 +131,9 @@ void ReaSamplerEditor::attachedToParent() {
HWND parent = static_cast<HWND>(systemWindow);
if (!parent) return;
HINSTANCE hInst =
reinterpret_cast<HINSTANCE>(GetWindowLongPtr(parent, GWLP_HINSTANCE));
// Own module, not the parent's hInstance — see moduleHandleFor() above.
HINSTANCE hInst = reinterpret_cast<HINSTANCE>(
moduleHandleFor(reinterpret_cast<void*>(&ReaSamplerEditor::wndProc)));
if (!hInst) hInst = GetModuleHandle(nullptr);
static bool classRegistered = false;
@@ -114,7 +141,7 @@ void ReaSamplerEditor::attachedToParent() {
WNDCLASSW wc{};
wc.lpfnWndProc = &ReaSamplerEditor::wndProc;
wc.hInstance = hInst;
wc.lpszClassName = kChildClassName;
wc.lpszClassName = childClassName().c_str();
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
// CS_DBLCLKS is what makes WM_LBUTTONDBLCLK arrive at all. It also REPLACES the second
// WM_LBUTTONDOWN of a double-click, so every surface that counted two downs (Browse's
@@ -134,7 +161,7 @@ void ReaSamplerEditor::attachedToParent() {
refreshFromBank();
const ViewRect& r = getRect();
childHwnd_ = CreateWindowExW(0, kChildClassName, L"", WS_CHILD | WS_VISIBLE, 0, 0,
childHwnd_ = CreateWindowExW(0, childClassName().c_str(), L"", WS_CHILD | WS_VISIBLE, 0, 0,
r.getWidth(), r.getHeight(), parent, nullptr, hInst, nullptr);
if (childHwnd_) {
SetWindowLongPtr(childHwnd_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));