Hand a card dragged onto a plugin's own editor to the OS as a file drop; withdraw the OLE drag when the pointer returns to REAPER

A code-ownership proof (window class procedure's module, via the pure window_origin) plus REAPER's FX-surface token routes a plugin editor to OLE; FX chrome keeps the instrument drop. The OLE loop re-asks the law each turn, so passing a plugin editor stays reversible.
This commit is contained in:
2026-09-25 13:39:39 -04:00
parent 9ff34b0a9e
commit fe1aec5925
14 changed files with 720 additions and 91 deletions
+30 -4
View File
@@ -1,6 +1,7 @@
#pragma once
// drag_out_win — the host/OS boundary of native OS drag-out: whether the pointer has
// crossed it, whether the OS is ready to take the drag, and the hand-off itself, which
// crossed it (left the host process, or reached a plugin's own editor), whether the OS is
// ready to take the drag, and the hand-off itself, which
// gives a resolved existing-file path list to the OS's drag-drop machinery so the user can
// drop bank samples into Explorer / another app / another DAW. The pure gesture-boundary
// decision + path-list assembly live in drag_out.*.
@@ -21,11 +22,22 @@ struct HWND__; // avoid dragging windows.h into every includer; the shell casts
namespace reasampler {
// How the OS drag ended, as far as the caller must act on it. Whether a Concluded drag was
// accepted is not reported — nothing acts on it.
enum class OsDragEnd {
Concluded, // dropped, cancelled, or failed to start: the gesture is over
Withdrawn, // ui::OsDragTurn::Withdraw — the button is still down
WithdrawnAtRelease, // ui::OsDragTurn::WithdrawAtRelease — the button came up over REAPER
};
// `absolutePaths` must already be resolved/existing/de-duped (drag_out::assemblePathList
// output). No-op when empty. BLOCKING on Windows: OLE DoDragDrop runs its own modal
// message loop until drop/cancel. Returns true iff the drop was accepted
// (DROPEFFECT_COPY); the return is advisory — a failed drag surfaces no error.
bool initiateDragOut(HWND__* panelHwnd, const std::vector<std::string>& absolutePaths);
// message loop, re-asking `lawHandsOff` (the gesture law at the current pointer) on every
// IDropSource::QueryContinueDrag and withdrawing per ui::decideOsDragTurn. macOS/Linux: SWELL's
// file drag is fire-and-forget with no such hook, so it always reports Concluded — the hand-off
// stays irreversible there.
OsDragEnd initiateDragOut(HWND__* panelHwnd, const std::vector<std::string>& absolutePaths,
bool (*lawHandsOff)());
// Side-effect-free readiness probe for initiateDragOut. Call BEFORE tearing down internal drag
// state — an OS that isn't ready (OLE unavailable, HDROP build failure) must not consume the
@@ -44,4 +56,18 @@ bool canInitiateDragOut(const std::vector<std::string>& absolutePaths);
// Side-effect-free; safe on every mouse-move.
bool pointerOverHostWindow(int screenX, int screenY);
// True iff plugin code implements the window under the screen point or one of its ancestors —
// the POSITIVE proof behind ui::DropContext::pointerOverPluginEditor. Windows reads, per window
// from the hit window up to its root, the owning process and the module holding the window
// CLASS's procedure, and lets ui::window_origin decide. The failure modes, all toward REAPER's
// own outcome (the instrument drop) rather than toward OLE:
// - a plugin that draws straight into the parent REAPER hands it, creating no window of its
// own, leaves nothing of its code in the chain;
// - a plugin binary installed beneath the Windows directory reads as an OS module;
// - a class procedure that resolves to no module (a charset-mismatch handle) is no evidence.
// [verify — DAW] that REAPER's GetThingFromPoint names an fx_* token over the plugin's own child
// window — the law requires it, so an unnamed editor would refuse rather than hand off.
// SWELL: always false (no plugin-editor hand-off off Windows). Side-effect-free.
bool pointerOverPluginEditor(int screenX, int screenY);
} // namespace reasampler