Files
reasampler/src/core/ui/drag_out.cpp
T
daniel b71a05fbef Close the bridged-process hole in the drag-out hand-off gate
OsHandoff now needs the window-ownership proof AND a hit-test that named
nothing, so a bridged plugin's UI can't read as off-REAPER.
2026-08-03 16:42:59 -04:00

104 lines
3.9 KiB
C++

// drag_out — pure implementation. See drag_out.h.
#include "core/ui/drag_out.h"
#include <unordered_set>
namespace reasampler::ui {
namespace {
bool insideClient(int px, int py, const PanelClientRect& c) {
return px >= c.x && px < c.x + c.width &&
py >= c.y && py < c.y + c.height;
}
} // namespace
bool needsSurfaceProbe(int px, int py, const PanelClientRect& client) {
return !insideClient(px, py, client);
}
DropClass decideDropClass(int px, int py, const PanelClientRect& client,
const DropContext& ctx) {
if (!ctx.drag.dragging || !ctx.drag.hasArmedSamples) return DropClass::None;
if (insideClient(px, py, client)) return DropClass::Internal;
// The ONE route to OsHandoff, ahead of the surface switch, and it needs BOTH halves. The
// shell's ownership proof alone is not sufficient: REAPER runs a bridged plugin's UI in a
// separate process, so a window failing that test can still be REAPER's own UI. A recognised
// token therefore VETOES the hand-off instead of producing one — a veto can only move the
// answer toward staying inside REAPER, so pointerOffHost remains the only member whose true
// value can reach OLE. An off-host point REAPER DID name falls through to that surface's own
// outcome below: over a bridged FX window that is the instrument drop, as it is for a native
// one.
if (ctx.pointerOffHost && ctx.surface == ReaperSurface::Other && !ctx.haveTrack)
return DropClass::OsHandoff;
switch (ctx.surface) {
case ReaperSurface::TrackPanel:
case ReaperSurface::FxSurface:
// One instance holds one capture, so a multi payload names no instrument to build —
// it refuses with a cue rather than falling through to some other surface's outcome.
return (ctx.singlePayload && ctx.haveTrack) ? DropClass::InstrumentDrop
: DropClass::Refuse;
case ReaperSurface::Arrange:
// GetThingFromPoint may return a null track with a valid info string (the SDK says
// so): over the arrange that is the region below the last track, which names no lane
// to place on. Refuse rather than guess a track.
return ctx.haveTrack ? DropClass::ArrangeInsert : DropClass::Refuse;
case ReaperSurface::FxEmbed:
case ReaperSurface::Other:
return DropClass::Refuse;
case ReaperSurface::Count:
break; // the sentinel is not a surface; it falls to the refusal below
}
return DropClass::Refuse; // an unclassifiable surface still refuses visibly, never silently
}
DropCue cueForDropClass(DropClass cls) {
switch (cls) {
case DropClass::Internal: return DropCue::Internal;
case DropClass::InstrumentDrop: return DropCue::Instrument;
case DropClass::ArrangeInsert: return DropCue::ArrangeInsert;
case DropClass::Refuse: return DropCue::Refuse;
case DropClass::OsHandoff: return DropCue::OsOwned;
case DropClass::None: return DropCue::None;
}
return DropCue::None;
}
PathList assemblePathList(const std::vector<ResolvedSample>& resolved) {
PathList out;
std::unordered_set<std::string> seen;
seen.reserve(resolved.size());
for (const ResolvedSample& s : resolved) {
if (s.absolutePath.empty()) {
++out.skippedUnresolved;
continue;
}
if (!s.fileExists) {
++out.skippedMissing;
continue;
}
if (!seen.insert(s.absolutePath).second) {
++out.skippedDuplicate;
continue;
}
out.paths.push_back(s.absolutePath);
}
return out;
}
OsHandoff decideOsHandoff(const std::vector<std::string>& paths) {
OsHandoff out;
out.handOffToOs = !paths.empty();
return out;
}
} // namespace reasampler::ui