#pragma once // drag_out_win — the host/OS boundary of native OS drag-out: whether the pointer has // 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.*. // // COPY-ONLY is STRUCTURAL on Windows: DoDragDrop's effect mask is DROPEFFECT_COPY // alone — MOVE is never offered, so no target can pull a file out of the bank folder // (only prune deletes bank bytes). macOS/Linux route through SWELL's file-list drag, // which exposes no per-source effect mask, so there the copy-only guarantee rests on // SWELL's copy semantics rather than an explicit mask. // // NON-DESTRUCTIVE: initiating a drag reads nothing but the path list; a cancelled or // failed drag mutates no sample/index/selection. #include #include struct HWND__; // avoid dragging windows.h into every includer; the shell casts as needed. 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, 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& 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 // gesture like an empty payload would. Windows re-runs the OLE-init + HDROP checks and frees the // probe HGLOBAL immediately; SWELL exposes no probe, so macOS/Linux reduces to the non-empty check. bool canInitiateDragOut(const std::vector& absolutePaths); // True iff a window belonging to the host (REAPER) process sits under the screen point — the // POSITIVE proof ui::DropContext::pointerOffHost is the negation of (that member owns the // evidence rule). Nearly every REAPER surface qualifies whether or not GetThingFromPoint can // name it, because the extension runs inside that process. // The exception, and it ships: a plugin set to "Run as separate/dedicated process" draws its // UI out of reaper_host32/64.exe, and WindowFromPoint descends to the deepest child — so this // reports false over a bridged plugin's editor although the pointer never left REAPER. The // law's hand-off gate absorbs that by also requiring REAPER's own hit-test to name nothing. // 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; // - an all-#32770 dialog-based editor (the generic Windows dialog class) resolves to no // recognizable plugin module — falls to REAPER's outcome; // - a .NET/WPF editor's class procedure is a CLR-thunked address with no resolvable module — // also falls to REAPER's outcome; `[verify]`; // - our own ReaSampler 9000 module (this channel's reasampler_9000[_beta].vst3, matched by // basename) also falls to REAPER's outcome, deliberately — see ui::WindowFacts::ownModule; // - ReaSampler 9000 itself run bridged/in a separate process reads as Plugin, same as any other // bridged plugin — banner (the OS hand-off fires over our own editor), harmless; // - a renamed ReaSampler 9000 binary (the basename no longer matches ownVstFileName()) misses // the exemption and reads as Plugin — banner, harmless. // The opposite direction is unguarded: a third-party hook that replaces REAPER's own chrome // class procedure via SetClassLongPtr(GCLP_WNDPROC) would make REAPER's own window read as // Plugin — toward OLE instead of away from it. // [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. // [verify — DAW] that OLE never walks up to an ancestor drop target (e.g. REAPER's own file // import) when the plugin editor's window registers no IDropTarget of its own. // [verify — DAW] that GetThingFromPoint cannot name a REAPER region (e.g. an fx_* token) when a // foreign window overlaps it at the same screen point — this fn trusts WindowFromPoint's hit // over whatever GetThingFromPoint separately reports. // SWELL: always false (no plugin-editor hand-off off Windows). Side-effect-free. bool pointerOverPluginEditor(int screenX, int screenY); } // namespace reasampler