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.
This commit is contained in:
+47
-8
@@ -41,7 +41,8 @@ static DropContext ctx(ReaperSurface surface, bool single = true, bool haveTrack
|
||||
}
|
||||
|
||||
// The same drag with the shell's positive off-REAPER proof set. `surface` is whatever the SDK
|
||||
// hit-test last reported; the point of the gate is that it no longer matters.
|
||||
// hit-test reported at that point — it still matters: the hand-off needs REAPER to have named
|
||||
// nothing too, so the defaults here (Other, no track) are the only combination that hands off.
|
||||
static DropContext offHost(ReaperSurface surface = ReaperSurface::Other, bool single = true,
|
||||
bool haveTrack = false) {
|
||||
DropContext c = ctx(surface, single, haveTrack);
|
||||
@@ -212,20 +213,57 @@ static void testNoInReaperCombinationCanHandOff() {
|
||||
}
|
||||
}
|
||||
|
||||
// The converse: once the shell has PROVEN the pointer left, the hand-off does not depend on what
|
||||
// the last hit-test happened to say or on whether a track was resolved — the gate sits ahead of
|
||||
// the surface switch, so a stale surface reading cannot suppress a genuine exit.
|
||||
static void testOffHostHandsOffWhateverTheSurfaceSaid() {
|
||||
// The converse, stated as the gate's exact shape: with the ownership proof set, the hand-off
|
||||
// happens EXACTLY on the cells where REAPER's hit-test also named nothing — no surface, no track.
|
||||
// Both directions in one loop, so neither half can be weakened without a failure: a gate that
|
||||
// dropped the token condition fails on the named cells, one that over-tightened fails on Other.
|
||||
static void testOffHostHandsOffOnlyWhereReaperNamedNothing() {
|
||||
for (ReaperSurface s : kAllSurfaces) {
|
||||
for (bool single : {true, false}) {
|
||||
for (bool haveTrack : {true, false}) {
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(s, single, haveTrack)) ==
|
||||
DropClass::OsHandoff);
|
||||
const bool namedNothing = (s == ReaperSurface::Other) && !haveTrack;
|
||||
const DropClass c =
|
||||
decideDropClass(kOutX, kOutY, kPanel, offHost(s, single, haveTrack));
|
||||
CHECK((c == DropClass::OsHandoff) == namedNothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The shipped exception the token condition exists for: REAPER runs a bridged plugin's UI in
|
||||
// reaper_host*.exe, so the window-ownership test reports off-host over a floating bridged FX
|
||||
// editor even though the pointer never left REAPER. There the recognised token vetoes the
|
||||
// hand-off and the surface's own REAPER-internal outcome stands — identical to the native case.
|
||||
static void testOffHostOverANamedSurfaceKeepsTheReaperOutcome() {
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(ReaperSurface::FxSurface, true, true)) ==
|
||||
DropClass::InstrumentDrop);
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(ReaperSurface::TrackPanel, true, true)) ==
|
||||
DropClass::InstrumentDrop);
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(ReaperSurface::Arrange, true, true)) ==
|
||||
DropClass::ArrangeInsert);
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(ReaperSurface::FxEmbed, true, true)) ==
|
||||
DropClass::Refuse);
|
||||
|
||||
// And not just for those four spot values: over every named cell the ownership proof changes
|
||||
// nothing at all, which is what "a veto can only move the answer toward staying in REAPER"
|
||||
// means operationally.
|
||||
for (ReaperSurface s : kAllSurfaces) {
|
||||
if (s == ReaperSurface::Other) continue; // the one surface the gate can hand off from
|
||||
for (bool single : {true, false}) {
|
||||
for (bool haveTrack : {true, false}) {
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(s, single, haveTrack)) ==
|
||||
decideDropClass(kOutX, kOutY, kPanel, ctx(s, single, haveTrack)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Same equality on the remaining named cell: Other WITH a track is a surface REAPER did
|
||||
// attribute, so it refuses off-host exactly as it does on-host.
|
||||
for (bool single : {true, false}) {
|
||||
CHECK(decideDropClass(kOutX, kOutY, kPanel,
|
||||
offHost(ReaperSurface::Other, single, true)) == DropClass::Refuse);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Not-a-drag ----------------------------------------------------------------
|
||||
|
||||
// No armed samples, or not dragging -> None regardless of position or surface.
|
||||
@@ -471,7 +509,8 @@ int main() {
|
||||
|
||||
testNullTrackWithSurfaceRefuses();
|
||||
testNoInReaperCombinationCanHandOff();
|
||||
testOffHostHandsOffWhateverTheSurfaceSaid();
|
||||
testOffHostHandsOffOnlyWhereReaperNamedNothing();
|
||||
testOffHostOverANamedSurfaceKeepsTheReaperOutcome();
|
||||
testNoDragOrNoSamplesIsNone();
|
||||
|
||||
testClassTransitionsAreReversible();
|
||||
|
||||
@@ -248,20 +248,15 @@ static void testUnnamedReaperSurfacesAreOther() {
|
||||
CHECK(surfaceFor("something_reaper_adds_in_2030") == ReaperSurface::Other);
|
||||
}
|
||||
|
||||
// An empty info string is Other, NOT an off-REAPER verdict. GetThingFromPoint documents no
|
||||
// off-REAPER return at all, so its silence over the transport, the toolbar or the docker chrome
|
||||
// says only "nothing I name" — reading it as "the user left REAPER" is what handed live drags to
|
||||
// OLE mid-gesture. Whether the pointer left REAPER is a window-ownership fact the shell proves
|
||||
// separately (drag_out_win::pointerOverHostWindow) and feeds to the law as
|
||||
// DropContext::pointerOffHost; no classifier output can produce a hand-off on its own.
|
||||
// An empty info string is Other, NOT an off-REAPER verdict — ui::DropContext::pointerOffHost
|
||||
// owns why.
|
||||
static void testEmptyInfoIsOtherNotOffReaper() {
|
||||
CHECK(surfaceFor("") == ReaperSurface::Other);
|
||||
}
|
||||
|
||||
// There is deliberately no second empty-info case: track presence stopped being an input to the
|
||||
// classifier when the off-REAPER verdict left the vocabulary. "No classifier output can trigger a
|
||||
// hand-off" is now structural (no such member exists; test_drag_out's static_assert pins the
|
||||
// member count) rather than something a per-token loop could falsify.
|
||||
// There is deliberately no second empty-info case: track presence is not an input to the
|
||||
// classifier, and "no classifier output alone can trigger a hand-off" is structural (no such
|
||||
// member exists) rather than something a per-token loop could falsify.
|
||||
|
||||
// Do not reintroduce a per-surface "capture carries" loop test: buildInstrumentDropPreset takes
|
||||
// only sampleId (proven by testPresetRoundTripsThroughInstrumentReader), and per-surface
|
||||
|
||||
Reference in New Issue
Block a user