diff --git a/SarAsio/configui.cpp b/SarAsio/configui.cpp index 3511610..53e96de 100644 --- a/SarAsio/configui.cpp +++ b/SarAsio/configui.cpp @@ -236,9 +236,25 @@ void EndpointsPropertySheetPage::onAddEndpoint() { _epDialogConfig = EndpointConfig(); + // Windows keeps what it knows about an endpoint (the name it shows, the + // user's settings) under its ID, so an ID must never be handed to a + // different endpoint: a deleted endpoint's ID given to a new one made + // Windows keep showing the deleted endpoint's name. + GUID guid; + + if (SUCCEEDED(CoCreateGuid(&guid))) { + char id[40]; + + sprintf_s(id, "ep_%08lx%04hx%04hx%02x%02x%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], + guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + _epDialogConfig.id = id; + } + int counter = 1; - do { + while (_epDialogConfig.id.empty()) { std::ostringstream os; os << "ep_" << counter; @@ -248,7 +264,7 @@ void EndpointsPropertySheetPage::onAddEndpoint() } else { counter++; } - } while (_epDialogConfig.id.empty()); + } auto result = DialogBoxParam( gDllModule, diff --git a/SarTest/SarTest.cpp b/SarTest/SarTest.cpp index c8f784a..bb7fc2a 100644 --- a/SarTest/SarTest.cpp +++ b/SarTest/SarTest.cpp @@ -59,6 +59,9 @@ int usage() " Open SAR with no hardware interface configured, the way a DAW sees\n" " it before first-time setup; fail unless it offers a stereo input\n" " and output (issues #31, #54).\n" + " SarTest endpoint-names\n" + " Rename an endpoint keeping its ID, then give it a new ID, and report\n" + " the name Windows shows each time (issues #15, #66).\n" "\n" "Layout options: --endpoints N (playback/recording pairs, default 2)\n" " --channels C (per endpoint, default 2) --prefix \n" @@ -711,6 +714,112 @@ int cmdNoInterface(const Args& args) return rc; } +// Starts a host on the layout, records whether Windows shows the layout's +// endpoints (and, when stale is given, endpoints under those names instead) +// and stops it again. +bool showsEndpoints(const Args& args, const EndpointLayout& layout, + const EndpointLayout *stale, bool *staleShown) +{ + if (!prepareConfig(args, layout)) { + return false; + } + + AsioHost host(hostOptions(args, layout)); + + if (!host.load() || !host.open() || !host.start()) { + host.close(); + return false; + } + + FoundEndpoints found; + bool shown = findEndpoints(layout, args.getInt(L"wait", 30), &found); + + found.release(); + + if (stale) { + *staleShown = findEndpoints(*stale, 5, &found); + found.release(); + } + + host.close(); + + if (!waitForEndpointsGone(layout, args.getInt(L"wait-gone", 30))) { + logf("Endpoints lingered after stop"); + } + + return shown; +} + +// Issues #15 and #66: SarAsio's control panel gave a new endpoint the lowest +// unused "ep_N", so a deleted endpoint's ID went to the next one, and Windows +// kept showing the old endpoint's name for it. Windows keys what it stores +// about an endpoint (its name, the user's settings) on the ID. Checks that a +// fresh ID gets the new name, and reports what a reused ID shows. +int cmdEndpointNames(const Args& args) +{ + EndpointLayout before = EndpointLayout::fromArgs(args); + + before.pairs = 1; + before.prefix = L"SarTest Alpha"; + before.idPrefix = L"SarTest-names"; + + EndpointLayout renamed = before; + + renamed.prefix = L"SarTest Beta"; + + EndpointLayout fresh = renamed; + + fresh.idPrefix = L"SarTest-names-fresh"; + + logf("--- %s, IDs %s ---", narrow(before.prefix).c_str(), narrow(before.idPrefix).c_str()); + + bool firstShown = showsEndpoints(args, before, nullptr, nullptr); + + logf("--- renamed to %s, same IDs ---", narrow(renamed.prefix).c_str()); + + bool oldNameShown = false; + bool renamedShown = showsEndpoints(args, renamed, &before, &oldNameShown); + + logf("--- %s, new IDs %s ---", narrow(fresh.prefix).c_str(), narrow(fresh.idPrefix).c_str()); + + bool freshShown = showsEndpoints(args, fresh, nullptr, nullptr); + + logf("Reused IDs: Windows shows the %s name", renamedShown ? "new" : + oldNameShown ? "old" : "neither"); + + int rc = 0; + std::string failure; + + if (!firstShown) { + rc = 2; + failure = "endpoints never appeared"; + } else if (!freshShown) { + rc = 2; + failure = "endpoints with new IDs did not show their names"; + } + + if (rc != 0) { + logf("%s", failure.c_str()); + } + + JsonObject result; + + result.setString("command", "endpoint-names") + .setBool("passed", rc == 0) + .setInt("exitCode", rc) + .setBool("reusedIdShowsNewName", renamedShown) + .setBool("reusedIdShowsOldName", oldNameShown) + .setBool("newIdShowsNewName", freshShown); + + if (!failure.empty()) { + result.setString("failure", failure); + } + + writeResults(args, result.str()); + logf("%s", rc == 0 ? "PASSED" : "FAILED"); + return rc; +} + } // namespace int wmain(int argc, wchar_t **argv) @@ -748,6 +857,8 @@ int wmain(int argc, wchar_t **argv) rc = cmdControlPanel(args); } else if (args.command == L"no-interface") { rc = cmdNoInterface(args); + } else if (args.command == L"endpoint-names") { + rc = cmdEndpointNames(args); } else { rc = usage(); } diff --git a/SarTest/common.cpp b/SarTest/common.cpp index e4ca56c..8db42b7 100644 --- a/SarTest/common.cpp +++ b/SarTest/common.cpp @@ -201,12 +201,12 @@ std::wstring EndpointLayout::recordingName(int pair) const std::string EndpointLayout::playbackId(int pair) const { - return narrow(prefix) + "-out-" + std::to_string(pair + 1); + return narrow(idPrefix.empty() ? prefix : idPrefix) + "-out-" + std::to_string(pair + 1); } std::string EndpointLayout::recordingId(int pair) const { - return narrow(prefix) + "-in-" + std::to_string(pair + 1); + return narrow(idPrefix.empty() ? prefix : idPrefix) + "-in-" + std::to_string(pair + 1); } EndpointLayout EndpointLayout::fromArgs(const Args& args) diff --git a/SarTest/common.h b/SarTest/common.h index 2c161df..9c7fce7 100644 --- a/SarTest/common.h +++ b/SarTest/common.h @@ -66,6 +66,8 @@ struct EndpointLayout int pairs = 2; int channels = 2; std::wstring prefix = L"SarTest"; + // Prefix of the endpoint IDs; the name prefix when empty. + std::wstring idPrefix; std::wstring playbackName(int pair) const; std::wstring recordingName(int pair) const; diff --git a/tools/test/Invoke-SarTest.ps1 b/tools/test/Invoke-SarTest.ps1 index 7ba008d..20da33a 100644 --- a/tools/test/Invoke-SarTest.ps1 +++ b/tools/test/Invoke-SarTest.ps1 @@ -151,6 +151,8 @@ if ($Scenarios -contains 'issues') { @('control-panel', '--endpoints', 1) + $common) -Timeout 120 $summary.scenarios += Invoke-Scenario 'issue-54-no-interface' ( @('no-interface') + $common) -Timeout 120 + $summary.scenarios += Invoke-Scenario 'issue-15-endpoint-names' ( + @('endpoint-names') + $common) -Timeout 300 } if ($Scenarios -contains 'matrix') { diff --git a/tools/test/README.md b/tools/test/README.md index 06374d1..8bf996c 100644 --- a/tools/test/README.md +++ b/tools/test/README.md @@ -88,6 +88,9 @@ host, presses the hardware interface's Configure button and checks that the clock never had a second instance alive. It needs an interactive desktop. `SarTest.exe no-interface` (#31, #54) opens SAR with no hardware interface configured and checks that it still offers a stereo input and output. +`SarTest.exe endpoint-names` (#15, #66) renames an endpoint keeping its ID, +then gives it a new ID, and checks that the new ID shows the new name; the +results record what Windows shows for the reused ID. `Invoke-SarTest.ps1 -Scenarios` selects any of `issues`, `matrix`, `race` and `kill`. @@ -103,6 +106,7 @@ SarTest run [--iterations K] [--duration S] SarTest race [--cycles K] [--up S] [--down MS] [--openers T] [--hold MS] SarTest control-panel SarTest no-interface +SarTest endpoint-names ``` `install` creates the SAR software device node if needed, installs the