Files
reasampler/tests/test_window_origin.cpp
T
daniel fee941a83d Exempt ReaSampler 9000's own editor from the plugin OS drag hand-off
A dragged bank card over our own instrument's editor was OLE-dropping like a third-party plugin. Classify our channel's own .vst3 module as Instrument, not Plugin, so it keeps the normal instrument-drop outcome.
2026-09-25 13:57:11 -04:00

198 lines
8.7 KiB
C++

// Standalone tests for reasampler::ui::window_origin — no REAPER, no Win32, no test framework.
// Asserts the per-window code fold and the chain rule the plugin-editor hand-off proof rests on.
#include "../src/core/ui/window_origin.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace reasampler::ui;
static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
static const std::string kWinDir = "C:\\Windows";
static WindowFacts inModule(const std::string& path, bool host = false) {
WindowFacts f;
f.procResolved = true;
f.hostModule = host;
f.modulePath = path;
return f;
}
static WindowFacts ownModule(const std::string& path) {
WindowFacts f;
f.procResolved = true;
f.modulePath = path;
f.ownModule = true;
return f;
}
// --- classifyWindowCode --------------------------------------------------------
// REAPER's own chrome and containers: the class procedure lives in the executable.
static void testHostExecutableIsHost() {
CHECK(classifyWindowCode(inModule("C:\\Program Files\\REAPER (x64)\\reaper.exe", true),
kWinDir) == WindowCode::Host);
}
// Standard controls and the dialog class are implemented by OS modules, wherever the OS keeps
// them — System32, SysWOW64 for a 32-bit host, and the side-by-side store comctl32 v6 loads from.
static void testOsModulesAreSystem() {
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("C:\\Windows\\SysWOW64\\user32.dll"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(
inModule("C:\\Windows\\WinSxS\\amd64_microsoft.windows.common-controls_6595b64144ccf1df"
"_6.0.26100.1_none_x\\comctl32.dll"),
kWinDir) == WindowCode::System);
}
// The same directory spelled differently by two APIs still compares equal: case, separator
// direction, a trailing separator on the root, and the long-path prefix.
static void testSystemMatchIgnoresSpelling() {
CHECK(classifyWindowCode(inModule("c:/WINDOWS/system32/USER32.DLL"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), "C:\\WINDOWS\\") ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("\\\\?\\C:\\Windows\\System32\\user32.dll"), kWinDir) ==
WindowCode::System);
}
// Containment, not string prefix: a sibling directory whose name merely starts with the OS
// directory's is plugin territory, and so is the OS directory itself named as a file path.
static void testSiblingDirectoryIsNotSystem() {
CHECK(classifyWindowCode(inModule("C:\\WindowsApps\\thing.dll"), kWinDir) ==
WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("C:\\Windows2\\thing.dll"), kWinDir) == WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("C:\\Windows"), kWinDir) == WindowCode::Plugin);
}
// A plugin binary anywhere outside the OS directory — the VST3 bundle, a VST2 folder, a helper
// DLL a plugin loads beside itself — is Plugin.
static void testPluginBinariesArePlugin() {
CHECK(classifyWindowCode(
inModule("C:\\Program Files\\Common Files\\VST3\\Serum2.vst3\\Contents\\x86_64-win\\"
"Serum2.vst3"),
kWinDir) == WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("D:\\VstPlugins\\Qt5Widgets.dll"), kWinDir) ==
WindowCode::Plugin);
}
// Another process's window is plugin code by the process boundary alone, whatever its other facts
// claim: a procedure address from another process resolved in ours is meaningless.
static void testOtherProcessIsPluginRegardlessOfModule() {
WindowFacts f = inModule("C:\\Program Files\\REAPER (x64)\\reaper.exe", true);
f.otherProcess = true;
CHECK(classifyWindowCode(f, kWinDir) == WindowCode::Plugin);
WindowFacts g;
g.otherProcess = true; // nothing resolved at all
CHECK(classifyWindowCode(g, kWinDir) == WindowCode::Plugin);
}
// No module (a charset-mismatch handle, generated code) is no evidence — never Plugin.
static void testUnresolvedProcIsUnknown() {
WindowFacts f;
f.modulePath = "C:\\VST\\x.dll"; // ignored without a resolved procedure
CHECK(classifyWindowCode(f, kWinDir) == WindowCode::Unknown);
}
// Without the OS directory a standard control cannot be told from plugin code, so every non-host
// module degrades to Unknown — the failure must not read REAPER's buttons as a plugin.
static void testMissingSystemRootNeverYieldsPlugin() {
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), "") ==
WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\VST\\x.dll"), "") == WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\VST\\x.dll"), "\\") == WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\REAPER\\reaper.exe", true), "") == WindowCode::Host);
}
// Our own ReaSampler 9000 module — stable or beta, whichever the shell set ownModule for — is
// Instrument, not Plugin: FX chrome we own, so a drag over it must not hand off to the OS.
static void testOwnModuleIsInstrument() {
CHECK(classifyWindowCode(
ownModule("C:\\Program Files\\Common Files\\VST3\\reasampler_9000.vst3"), kWinDir) ==
WindowCode::Instrument);
CHECK(classifyWindowCode(
ownModule("C:\\Program Files\\Common Files\\VST3\\reasampler_9000_beta.vst3"),
kWinDir) == WindowCode::Instrument);
}
// --- isPluginEditorChain -------------------------------------------------------
// Pointer on REAPER's FX-window chrome: a standard control or a REAPER container, under the
// dialog-class root. No plugin code anywhere in the chain.
static void testChromeChainIsNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::System, WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Host}));
}
// Pointer on the plugin's editor window itself.
static void testPluginWindowChainIsPlugin() {
CHECK(isPluginEditorChain({WindowCode::Plugin, WindowCode::Host, WindowCode::System}));
}
// A standard control INSIDE the plugin's editor (a native text box) is System on its own; the
// plugin's window above it is what makes the point the plugin's.
static void testStandardControlInsidePluginIsPlugin() {
CHECK(isPluginEditorChain(
{WindowCode::System, WindowCode::Plugin, WindowCode::Host, WindowCode::System}));
}
// A bridged plugin's editor embedded across the process boundary: the hit window is Plugin by
// process, REAPER's container and root above it are in-process.
static void testBridgedEmbeddedEditorIsPlugin() {
WindowFacts bridged;
bridged.otherProcess = true;
CHECK(isPluginEditorChain({classifyWindowCode(bridged, kWinDir), WindowCode::Host,
WindowCode::System}));
}
// A pointer over our own instrument's editor — Instrument code, not Plugin — is not hand-off
// proof: it stays REAPER's FX chrome, unlike a genuine plugin editor (testPluginWindowChainIsPlugin).
static void testOwnInstrumentChainIsNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::Instrument, WindowCode::Host, WindowCode::System}));
}
// Another plugin's editor sitting alongside our own instrument in the same chain is still proof —
// only OUR module is exempted, nobody else's.
static void testOtherPluginBesideOwnInstrumentIsStillPlugin() {
CHECK(isPluginEditorChain(
{WindowCode::Plugin, WindowCode::Instrument, WindowCode::Host, WindowCode::System}));
}
// Unknown anywhere, and nothing else, is not proof; an empty chain (no window at all) is not.
static void testUnknownAndEmptyChainsAreNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::Unknown, WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Unknown}));
CHECK(!isPluginEditorChain({}));
}
int main() {
testHostExecutableIsHost();
testOsModulesAreSystem();
testSystemMatchIgnoresSpelling();
testSiblingDirectoryIsNotSystem();
testPluginBinariesArePlugin();
testOtherProcessIsPluginRegardlessOfModule();
testUnresolvedProcIsUnknown();
testMissingSystemRootNeverYieldsPlugin();
testOwnModuleIsInstrument();
testChromeChainIsNotPlugin();
testPluginWindowChainIsPlugin();
testStandardControlInsidePluginIsPlugin();
testBridgedEmbeddedEditorIsPlugin();
testOwnInstrumentChainIsNotPlugin();
testOtherPluginBesideOwnInstrumentIsStillPlugin();
testUnknownAndEmptyChainsAreNotPlugin();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}