faa513cafb
Panel drag no longer misfires on SWELL's always-zero wParam; stable/beta VST editors register distinct window classes against their own module instead of colliding on REAPER's; own-module basename matching moves to a tested pure helper.
223 lines
9.7 KiB
C++
223 lines
9.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({}));
|
|
}
|
|
|
|
// --- isOwnModulePath -----------------------------------------------------------
|
|
|
|
static void testOwnModulePathCaseFolds() {
|
|
CHECK(isOwnModulePath("C:\\VST3\\reasampler_9000.vst3", "reasampler_9000.vst3"));
|
|
CHECK(isOwnModulePath("C:\\VST3\\ReaSampler_9000.VST3", "reasampler_9000.vst3"));
|
|
}
|
|
|
|
static void testOwnModulePathBundlePathForwardAndBackSlash() {
|
|
CHECK(isOwnModulePath(
|
|
"C:\\VST3\\reasampler_9000.vst3\\Contents\\x86_64-win\\reasampler_9000.vst3",
|
|
"reasampler_9000.vst3"));
|
|
CHECK(isOwnModulePath(
|
|
"C:/VST3/reasampler_9000.vst3/Contents/x86_64-win/reasampler_9000.vst3",
|
|
"reasampler_9000.vst3"));
|
|
}
|
|
|
|
static void testOwnModulePathStableAndBetaDoNotMatchEachOther() {
|
|
CHECK(!isOwnModulePath("C:\\VST3\\reasampler_9000_beta.vst3", "reasampler_9000.vst3"));
|
|
CHECK(!isOwnModulePath("C:\\VST3\\reasampler_9000.vst3", "reasampler_9000_beta.vst3"));
|
|
}
|
|
|
|
int main() {
|
|
testHostExecutableIsHost();
|
|
testOsModulesAreSystem();
|
|
testSystemMatchIgnoresSpelling();
|
|
testSiblingDirectoryIsNotSystem();
|
|
testPluginBinariesArePlugin();
|
|
testOtherProcessIsPluginRegardlessOfModule();
|
|
testUnresolvedProcIsUnknown();
|
|
testMissingSystemRootNeverYieldsPlugin();
|
|
testOwnModuleIsInstrument();
|
|
|
|
testChromeChainIsNotPlugin();
|
|
testPluginWindowChainIsPlugin();
|
|
testStandardControlInsidePluginIsPlugin();
|
|
testBridgedEmbeddedEditorIsPlugin();
|
|
testOwnInstrumentChainIsNotPlugin();
|
|
testOtherPluginBesideOwnInstrumentIsStillPlugin();
|
|
testUnknownAndEmptyChainsAreNotPlugin();
|
|
|
|
testOwnModulePathCaseFolds();
|
|
testOwnModulePathBundlePathForwardAndBackSlash();
|
|
testOwnModulePathStableAndBetaDoNotMatchEachOther();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|