Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions SarAsio/configui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ INT_PTR CALLBACK SimpleDialog::dialogProcStub(
return 0;
}

EndpointsPropertySheetPage::EndpointsPropertySheetPage(DriverConfig& config)
: _config(config)
EndpointsPropertySheetPage::EndpointsPropertySheetPage(
DriverConfig& config, IASIO *runningDriver,
const std::string& runningDriverClsid)
: _config(config), _runningDriver(runningDriver),
_runningDriverClsid(runningDriverClsid)
{
pszTemplate = MAKEINTRESOURCE(IDD_CONFIG_ENDPOINTS);

Expand Down Expand Up @@ -198,6 +201,14 @@ void EndpointsPropertySheetPage::onConfigureHardwareInterface()
{
for (auto& driver : _drivers) {
if (driver.clsid == _config.driverClsid) {
// ASIO drivers expect a single instance per process, and some
// crash when a second one is created next to a running one. Use
// the instance the host already has when it is the selected one.
if (_runningDriver && driver.clsid == _runningDriverClsid) {
_runningDriver->controlPanel();
break;
}

CComPtr<IASIO> asio;

if (SUCCEEDED(driver.open(&asio))) {
Expand Down Expand Up @@ -856,10 +867,13 @@ void ApplicationConfigDialog::onBrowseClicked()
}
}

ConfigurationPropertyDialog::ConfigurationPropertyDialog(DriverConfig& config)
ConfigurationPropertyDialog::ConfigurationPropertyDialog(
DriverConfig& config, IASIO *runningDriver,
const std::string& runningDriverClsid)
: _originalConfig(config),
_newConfig(_originalConfig),
_endpoints(std::make_shared<EndpointsPropertySheetPage>(_newConfig)),
_endpoints(std::make_shared<EndpointsPropertySheetPage>(
_newConfig, runningDriver, runningDriverClsid)),
_applications(std::make_shared<ApplicationsPropertySheetPage>(_newConfig))
{
pszCaption = TEXT("Synchronous Audio Router");
Expand Down
12 changes: 10 additions & 2 deletions SarAsio/configui.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ struct SimpleDialog

struct EndpointsPropertySheetPage: public PropertySheetPage
{
EndpointsPropertySheetPage(DriverConfig& config);
// runningDriver is the hardware interface the caller already has open and
// initialized (CLSID runningDriverClsid), if any.
EndpointsPropertySheetPage(
DriverConfig& config, IASIO *runningDriver = nullptr,
const std::string& runningDriverClsid = "");

protected:
virtual INT_PTR dialogProc(
Expand All @@ -87,6 +91,8 @@ struct EndpointsPropertySheetPage: public PropertySheetPage

DriverConfig& _config;
std::vector<AsioDriver> _drivers;
IASIO *_runningDriver;
std::string _runningDriverClsid;
HWND _hardwareInterfaceDropdown;
HWND _hardwareInterfaceConfigButton;
HWND _listView;
Expand Down Expand Up @@ -170,7 +176,9 @@ struct ApplicationConfigDialog: public SimpleDialog

struct ConfigurationPropertyDialog: public PropertyDialog
{
ConfigurationPropertyDialog(DriverConfig& config);
ConfigurationPropertyDialog(
DriverConfig& config, IASIO *runningDriver = nullptr,
const std::string& runningDriverClsid = "");
DriverConfig newConfig() { return _newConfig; }

private:
Expand Down
5 changes: 4 additions & 1 deletion SarAsio/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,8 @@ AsioStatus SarAsioWrapper::disposeBuffers()
AsioStatus SarAsioWrapper::controlPanel()
{
LOG(INFO) << "SarAsioWrapper::controlPanel";
auto sheet = std::make_shared<ConfigurationPropertyDialog>(_config);
auto sheet = std::make_shared<ConfigurationPropertyDialog>(
_config, _innerDriver, _innerDriverClsid);

if (sheet->show(_hwnd) > 0) {
_config = sheet->newConfig();
Expand Down Expand Up @@ -578,6 +579,7 @@ AsioStatus SarAsioWrapper::outputReady()
bool SarAsioWrapper::initInnerDriver()
{
_innerDriver = nullptr;
_innerDriverClsid = "";

for (auto driver : InstalledAsioDrivers()) {
if (driver.clsid == _config.driverClsid) {
Expand All @@ -593,6 +595,7 @@ bool SarAsioWrapper::initInnerDriver()
return false;
}

_innerDriverClsid = driver.clsid;
return true;
}
}
Expand Down
1 change: 1 addition & 0 deletions SarAsio/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ struct ATL_NO_VTABLE SarAsioWrapper:
std::shared_ptr<SarClient> _sar;
std::shared_ptr<SarCastMaster> _castMaster;
CComPtr<IASIO> _innerDriver;
std::string _innerDriverClsid;
std::vector<VirtualChannel> _virtualInputs;
std::vector<VirtualChannel> _virtualOutputs;
AsioTickCallback *_userTick;
Expand Down
169 changes: 169 additions & 0 deletions SarTest/SarTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#include "wasapi.h"
#include "clockstats.h"

#include <prsht.h>
#include <thread>

using namespace SarTest;

namespace {
Expand All @@ -48,6 +51,10 @@ int usage()
" SarTest race [--cycles K] [--up S] [--down MS] [--openers T] [--hold MS]\n"
" Start and stop the host repeatedly while threads keep opening and\n"
" closing streams on its endpoints; report any call that hangs.\n"
" SarTest control-panel\n"
" Open SAR's control panel on a running host, press the hardware\n"
" interface's Configure button and close it again; fail if a second\n"
" clock instance was created (issue #133).\n"
"\n"
"Layout options: --endpoints N (playback/recording pairs, default 2)\n"
" --channels C (per endpoint, default 2) --prefix <name>\n"
Expand Down Expand Up @@ -484,6 +491,166 @@ int cmdRun(const Args& args)
return rc;
}

// Control IDs in SarAsio's endpoints page (SarAsio.rc).
const int kConfigureHardwareInterfaceButton = 1002;

struct ControlPanelWindow
{
DWORD pid = 0;
HWND sheet = nullptr;
HWND button = nullptr;
};

BOOL CALLBACK findConfigureButton(HWND hwnd, LPARAM lparam)
{
auto *find = (ControlPanelWindow *)lparam;
wchar_t cls[32] = {};

GetClassNameW(hwnd, cls, 32);

if (GetDlgCtrlID(hwnd) == kConfigureHardwareInterfaceButton &&
_wcsicmp(cls, L"Button") == 0) {
find->button = hwnd;
return FALSE;
}

return TRUE;
}

BOOL CALLBACK findControlPanel(HWND hwnd, LPARAM lparam)
{
auto *find = (ControlPanelWindow *)lparam;
DWORD pid = 0;

GetWindowThreadProcessId(hwnd, &pid);

if (pid != find->pid || !IsWindowVisible(hwnd)) {
return TRUE;
}

EnumChildWindows(hwnd, findConfigureButton, lparam);

if (find->button) {
find->sheet = hwnd;
return FALSE;
}

return TRUE;
}

// Issue #133: with the host running, the hardware interface's Configure
// button in SAR's control panel created a second instance of the inner ASIO
// driver instead of using the running one. ASIO drivers may assume one
// instance per process; FlexASIO crashed on it.
int cmdControlPanel(const Args& args)
{
EndpointLayout layout = EndpointLayout::fromArgs(args);

if (!prepareConfig(args, layout)) {
return 1;
}

AsioHost host(hostOptions(args, layout));

if (!host.load() || !host.open()) {
return 1;
}

if (!host.start()) {
host.close();
return 1;
}

bool found = false;
bool enabled = false;
bool clicked = false;
std::thread clicker([&] {
ControlPanelWindow window;

window.pid = GetCurrentProcessId();

for (int i = 0; i < 200 && !window.button; ++i) {
Sleep(50);
EnumWindows(findControlPanel, (LPARAM)&window);
}

if (!window.button) {
logf("SAR's control panel did not appear within 10 s");
return;
}

found = true;
enabled = IsWindowEnabled(window.button) != FALSE;
logf("Found the control panel; Configure is %s", enabled ? "enabled" : "disabled");

DWORD_PTR ignored = 0;

// What a click on the button sends its page. The handler runs on the
// panel's thread and returns once the inner driver's panel does.
clicked = SendMessageTimeoutW(GetParent(window.button), WM_COMMAND,
MAKEWPARAM(kConfigureHardwareInterfaceButton, BN_CLICKED),
(LPARAM)window.button, SMTO_NORMAL, 10000, &ignored) != 0;

if (!clicked) {
logf("The Configure handler did not return within 10 s");
}

PostMessageW(window.sheet, PSM_PRESSBUTTON, PSBTN_CANCEL, 0);
});

logf("Opening SAR's control panel");

bool opened = host.controlPanel();

clicker.join();
host.readClockStats();

SarTestClock::ClockStats clock = host.stats().clock;

host.close();
logf("Clock instances: %u alive, at most %u; inner controlPanel calls: %llu",
clock.liveInstances, clock.maxLiveInstances,
(unsigned long long)clock.controlPanelCalls);

int rc = 0;
std::string failure;

if (!opened || !found || !enabled || !clicked) {
rc = 2;
failure = "could not drive the control panel";
} else if (clock.controlPanelCalls == 0) {
rc = 2;
failure = "Configure did not open the clock's control panel";
} else if (clock.maxLiveInstances > 1) {
rc = 2;
failure = "Configure created a second clock instance";
}

if (rc != 0) {
logf("%s", failure.c_str());
}

JsonObject result;

result.setString("command", "control-panel")
.setBool("passed", rc == 0)
.setInt("exitCode", rc)
.setBool("panelFound", found)
.setBool("configureEnabled", enabled)
.setBool("configureClicked", clicked)
.setInt("maxClockInstances", clock.maxLiveInstances)
.setInt("clockControlPanelCalls", (long long)clock.controlPanelCalls)
.setRaw("host", host.toJson());

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)
Expand Down Expand Up @@ -517,6 +684,8 @@ int wmain(int argc, wchar_t **argv)
rc = cmdRun(args);
} else if (args.command == L"race") {
rc = cmdRace(args);
} else if (args.command == L"control-panel") {
rc = cmdControlPanel(args);
} else {
rc = usage();
}
Expand Down
19 changes: 19 additions & 0 deletions SarTest/asiohost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,25 @@ void AsioHost::close()
_asio = nullptr;
}

bool AsioHost::controlPanel()
{
if (!_asio) {
return false;
}

// The panel waits for whoever closes it, so it is not a watched phase.
setPhase("idle");

AsioStatus status = _asio->controlPanel();

if (status != AsioStatus::OK) {
logf("IASIO::controlPanel failed: %ld", (long)status);
return false;
}

return true;
}

void AsioHost::readClockStats()
{
SarTestClock::ClockStats stats = {};
Expand Down
6 changes: 5 additions & 1 deletion SarTest/asiohost.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class AsioHost
bool start();
bool stop();
void close();
// IASIO::controlPanel. SarAsio's panel is modal: this returns once
// something closes it.
bool controlPanel();
// Refreshes stats().clock from the software clock.
void readClockStats();

bool running() const { return _running; }
const AsioHostStats& stats() const { return _stats; }
Expand All @@ -91,7 +96,6 @@ class AsioHost
void onTick(long bufferIndex);
void setPhase(const char *phase);
void watchdog();
void readClockStats();

AsioHostOptions _options;
AsioHostStats _stats;
Expand Down
Loading
Loading