Fix drag-out losing audio and FX-container drops losing the capture; re-home ingest under shell/actions

This commit is contained in:
2026-07-29 23:49:53 -04:00
parent a689fb75eb
commit 0800760833
16 changed files with 269 additions and 45 deletions
+32 -11
View File
@@ -62,6 +62,29 @@ HGLOBAL buildHDrop(const std::vector<std::string>& paths) {
return h;
}
// COM reference counts MUST be interlocked. A CF_HDROP target is free to marshal the data
// object into another apartment and finish the copy on a background thread AFTER DoDragDrop
// has returned (Explorer's async file copy does exactly this). A plain ++/-- there races the
// source thread's post-DoDragDrop Release: one lost increment destroys the object — and with
// it the source HGLOBAL — before the target reads it, and the drop lands with no file. That
// race is intermittent and a retry usually wins it; do not "simplify" these back.
inline ULONG comAddRef(volatile LONG& refs) {
return static_cast<ULONG>(InterlockedIncrement(&refs));
}
// DoDragDrop requires the calling thread to be OLE-initialized — CoInitialize alone is not
// enough, and an uninitialized thread fails the call outright, so the drag never starts.
// Relying on REAPER having done it is a first-use hazard: whether it has depends on what else
// ran first in the session. OleInitialize is per-thread refcounted, so this is additive to
// whatever the host did; we deliberately never OleUninitialize — the extension lives for the
// process, and unbalancing a REAPER-owned apartment is the hazard worth avoiding, not this.
// RPC_E_CHANGED_MODE means the thread joined an MTA, where OLE drag-drop is unavailable.
bool ensureOleForThisThread() {
static thread_local int state = 0; // 0 untried, 1 ready, -1 unavailable
if (state == 0) state = SUCCEEDED(OleInitialize(nullptr)) ? 1 : -1;
return state > 0;
}
// Minimal IDropSource: continue until the (left) button releases or Escape cancels; always
// request the copy cursor. This is the standard textbook drop source — no custom feedback.
class DropSource final : public IDropSource {
@@ -76,11 +99,11 @@ public:
*ppv = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
ULONG STDMETHODCALLTYPE AddRef() override { return comAddRef(refs_); }
ULONG STDMETHODCALLTYPE Release() override {
const ULONG r = --refs_;
const LONG r = InterlockedDecrement(&refs_);
if (r == 0) delete this;
return r;
return static_cast<ULONG>(r);
}
// IDropSource
HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL escapePressed, DWORD keyState) override {
@@ -92,7 +115,7 @@ public:
return DRAGDROP_S_USEDEFAULTCURSORS; // let OLE draw the standard copy cursor
}
private:
ULONG refs_ = 1;
volatile LONG refs_ = 1;
};
// Minimal IDataObject exposing exactly one format (CF_HDROP / TYMED_HGLOBAL). The HDROP is
@@ -112,11 +135,11 @@ public:
*ppv = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
ULONG STDMETHODCALLTYPE AddRef() override { return comAddRef(refs_); }
ULONG STDMETHODCALLTYPE Release() override {
const ULONG r = --refs_;
const LONG r = InterlockedDecrement(&refs_);
if (r == 0) delete this;
return r;
return static_cast<ULONG>(r);
}
// IDataObject — the two that matter for a drag source.
@@ -184,7 +207,7 @@ private:
(fe.tymed & TYMED_HGLOBAL) &&
fe.dwAspect == DVASPECT_CONTENT;
}
ULONG refs_ = 1;
volatile LONG refs_ = 1;
HGLOBAL hdrop_ = nullptr;
};
@@ -192,10 +215,8 @@ private:
bool initiateDragOut(HWND__* /*panelHwnd*/, const std::vector<std::string>& absolutePaths) {
if (absolutePaths.empty()) return false;
if (!ensureOleForThisThread()) return false;
// REAPER's main thread is already OLE-initialized (it hosts OLE drag targets); we
// deliberately do NOT call OleInitialize — pairing OleUninitialize across a
// REAPER-owned apartment is the kind of thing that bites.
HGLOBAL hdrop = buildHDrop(absolutePaths);
if (!hdrop) return false;