blob: d5ce6175bffbf30f72b9cf0ac0f0169a6e8a3db8 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/midi/midi_manager_winrt.h"
#include "base/memory/raw_ptr.h"
#pragma warning(disable : 4467)
#define INITGUID
#include <windows.h>
#include <cfgmgr32.h>
#include <comdef.h>
#include <devpkey.h>
#include <initguid.h>
#include <objbase.h>
#include <robuffer.h>
#include <windows.devices.enumeration.h>
#include <windows.devices.midi.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <iomanip>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include "base/functional/bind.h"
#include "base/scoped_generic.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/timer.h"
#include "base/win/core_winrt_util.h"
#include "base/win/scoped_hstring.h"
#include "base/win/winrt_storage_util.h"
#include "media/midi/midi_service.h"
#include "media/midi/task_service.h"
namespace midi {
namespace {
namespace WRL = Microsoft::WRL;
namespace Win = ABI::Windows;
using base::win::GetActivationFactory;
using base::win::ScopedHString;
using mojom::PortState;
using mojom::Result;
using Win::Devices::Enumeration::DeviceInformationUpdate;
using Win::Devices::Enumeration::DeviceWatcher;
using Win::Devices::Enumeration::IDeviceInformation;
using Win::Devices::Enumeration::IDeviceInformationUpdate;
using Win::Devices::Enumeration::IDeviceWatcher;
using Win::Foundation::IAsyncOperation;
using Win::Foundation::ITypedEventHandler;
// Alias for printing HRESULT.
const auto PrintHr = logging::SystemErrorCodeToString;
enum {
kDefaultTaskRunner = TaskService::kDefaultRunnerId,
kComTaskRunner
};
template <typename T>
std::string GetIdString(T* obj) {
HSTRING result;
HRESULT hr = obj->get_Id(&result);
if (FAILED(hr)) {
VLOG(1) << "get_Id failed: " << PrintHr(hr);
return std::string();
}
return ScopedHString(result).GetAsUTF8();
}
template <typename T>
std::string GetDeviceIdString(T* obj) {
HSTRING result;
HRESULT hr = obj->get_DeviceId(&result);
if (FAILED(hr)) {
VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
return std::string();
}
return ScopedHString(result).GetAsUTF8();
}
std::string GetNameString(IDeviceInformation* info) {
HSTRING result;
HRESULT hr = info->get_Name(&result);
if (FAILED(hr)) {
VLOG(1) << "get_Name failed: " << PrintHr(hr);
return std::string();
}
return ScopedHString(result).GetAsUTF8();
}
// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
// instance.
bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
WRL::ComPtr<Win::Devices::Midi::IMidiSynthesizerStatics>
midi_synthesizer_statics;
HRESULT hr =
GetActivationFactory<Win::Devices::Midi::IMidiSynthesizerStatics,
RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>(
&midi_synthesizer_statics);
if (FAILED(hr)) {
VLOG(1) << "IMidiSynthesizerStatics factory failed: " << PrintHr(hr);
return false;
}
boolean result = FALSE;
hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
return result != FALSE;
}
void GetDevPropString(DEVINST handle,
const DEVPROPKEY* devprop_key,
std::string* out) {
DEVPROPTYPE devprop_type;
unsigned long buffer_size = 0;
// Retrieve |buffer_size| and allocate buffer later for receiving data.
CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
nullptr, &buffer_size, 0);
if (cr != CR_BUFFER_SMALL) {
// Here we print error codes in hex instead of using PrintHr() with
// HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
// CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
return;
}
if (devprop_type != DEVPROP_TYPE_STRING) {
VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
<< "expected DEVPROP_TYPE_STRING";
return;
}
std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
// Receive property data.
cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
&buffer_size, 0);
if (cr != CR_SUCCESS)
VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
else
*out = base::WideToUTF8(reinterpret_cast<wchar_t*>(buffer.get()));
}
// Retrieves manufacturer (provider) and version information of underlying
// device driver through PnP Configuration Manager, given device (interface) ID
// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
// modified if retrieval fails.
//
// Device instance ID is extracted from device (interface) ID provided by WinRT
// APIs, for example from the following interface ID:
// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
//
// However the extracted device instance ID represent a "software device"
// provided by Microsoft, which is an interface on top of the hardware for each
// input/output port. Therefore we further locate its parent device, which is
// the actual hardware device, for driver information.
void GetDriverInfoFromDeviceId(const std::string& dev_id,
std::string* out_manufacturer,
std::string* out_driver_version) {
std::wstring dev_instance_id =
base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
DEVINST dev_instance_handle;
CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
CM_LOCATE_DEVNODE_NORMAL);
if (cr != CR_SUCCESS) {
VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
return;
}
DEVINST parent_handle;
cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
if (cr != CR_SUCCESS) {
VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
return;
}
GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
out_manufacturer);
GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
out_driver_version);
}
// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
const int64_t kInvalidTokenValue = 0;
template <typename InterfaceType>
struct MidiPort {
MidiPort() = default;
MidiPort(const MidiPort&) = delete;
MidiPort& operator=(const MidiPort&) = delete;
uint32_t index;
WRL::ComPtr<InterfaceType> handle;
EventRegistrationToken token_MessageReceived;
};
} // namespace
template <typename InterfaceType,
typename RuntimeType,
typename StaticsInterfaceType,
wchar_t const* runtime_class_id>
class MidiManagerWinrt::MidiPortManager {
public:
// MidiPortManager instances should be constructed on the kComTaskRunner.
MidiPortManager(MidiManagerWinrt* midi_manager)
: midi_service_(midi_manager->service()), midi_manager_(midi_manager) {}
virtual ~MidiPortManager() {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
}
bool StartWatcher() {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
HRESULT hr = GetActivationFactory<StaticsInterfaceType, runtime_class_id>(
&midi_port_statics_);
if (FAILED(hr)) {
VLOG(1) << "StaticsInterfaceType factory failed: " << PrintHr(hr);
return false;
}
HSTRING device_selector = nullptr;
hr = midi_port_statics_->GetDeviceSelector(&device_selector);
if (FAILED(hr)) {
VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
return false;
}
WRL::ComPtr<Win::Devices::Enumeration::IDeviceInformationStatics>
dev_info_statics;
hr = GetActivationFactory<
Win::Devices::Enumeration::IDeviceInformationStatics,
RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
&dev_info_statics);
if (FAILED(hr)) {
VLOG(1) << "IDeviceInformationStatics failed: " << PrintHr(hr);
return false;
}
hr = dev_info_statics->CreateWatcherAqsFilter(device_selector, &watcher_);
if (FAILED(hr)) {
VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
return false;
}
// Register callbacks to WinRT that post state-modifying tasks back to
// kComTaskRunner. All posted tasks run only during the MidiPortManager
// instance is alive. This is ensured by MidiManagerWinrt by calling
// UnbindInstance() before destructing any MidiPortManager instance. Thus
// we can handle raw pointers safely in the following blocks.
MidiPortManager* port_manager = this;
TaskService* task_service = midi_service_->task_service();
hr = watcher_->add_Added(
WRL::Callback<ITypedEventHandler<
DeviceWatcher*, Win::Devices::Enumeration::DeviceInformation*>>(
[port_manager, task_service](IDeviceWatcher* watcher,
IDeviceInformation* info) {
if (!info) {
VLOG(1) << "DeviceWatcher.Added callback provides null "
"pointer, ignoring";
return S_OK;
}
// Disable Microsoft GS Wavetable Synth due to security reasons.
// http://crbug.com/499279
if (IsMicrosoftSynthesizer(info))
return S_OK;
std::string dev_id = GetIdString(info),
dev_name = GetNameString(info);
task_service->PostBoundTask(
kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded,
base::Unretained(port_manager),
dev_id, dev_name));
return S_OK;
})
.Get(),
&token_Added_);
if (FAILED(hr)) {
VLOG(1) << "add_Added failed: " << PrintHr(hr);
return false;
}
hr = watcher_->add_EnumerationCompleted(
WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
[port_manager, task_service](IDeviceWatcher* watcher,
IInspectable* insp) {
task_service->PostBoundTask(
kComTaskRunner,
base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
base::Unretained(port_manager)));
return S_OK;
})
.Get(),
&token_EnumerationCompleted_);
if (FAILED(hr)) {
VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
return false;
}
hr = watcher_->add_Removed(
WRL::Callback<
ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
[port_manager, task_service](IDeviceWatcher* watcher,
IDeviceInformationUpdate* update) {
if (!update) {
VLOG(1) << "DeviceWatcher.Removed callback provides null "
"pointer, ignoring";
return S_OK;
}
std::string dev_id = GetIdString(update);
task_service->PostBoundTask(
kComTaskRunner,
base::BindOnce(&MidiPortManager::OnRemoved,
base::Unretained(port_manager), dev_id));
return S_OK;
})
.Get(),
&token_Removed_);
if (FAILED(hr)) {
VLOG(1) << "add_Removed failed: " << PrintHr(hr);
return false;
}
hr = watcher_->add_Stopped(
WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
[](IDeviceWatcher* watcher, IInspectable* insp) {
// Placeholder, does nothing for now.
return S_OK;
})
.Get(),
&token_Stopped_);
if (FAILED(hr)) {
VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
return false;
}
hr = watcher_->add_Updated(
WRL::Callback<
ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
[](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
// TODO(shaochuan): Check for fields to be updated here.
return S_OK;
})
.Get(),
&token_Updated_);
if (FAILED(hr)) {
VLOG(1) << "add_Updated failed: " << PrintHr(hr);
return false;
}
hr = watcher_->Start();
if (FAILED(hr)) {
VLOG(1) << "Start failed: " << PrintHr(hr);
return false;
}
is_initialized_ = true;
return true;
}
void StopWatcher() {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
HRESULT hr;
for (const auto& entry : ports_)
RemovePortEventHandlers(entry.second.get());
if (token_Added_.value != kInvalidTokenValue) {
hr = watcher_->remove_Added(token_Added_);
VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
token_Added_.value = kInvalidTokenValue;
}
if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
<< PrintHr(hr);
token_EnumerationCompleted_.value = kInvalidTokenValue;
}
if (token_Removed_.value != kInvalidTokenValue) {
hr = watcher_->remove_Removed(token_Removed_);
VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
token_Removed_.value = kInvalidTokenValue;
}
if (token_Stopped_.value != kInvalidTokenValue) {
hr = watcher_->remove_Stopped(token_Stopped_);
VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
token_Stopped_.value = kInvalidTokenValue;
}
if (token_Updated_.value != kInvalidTokenValue) {
hr = watcher_->remove_Updated(token_Updated_);
VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
token_Updated_.value = kInvalidTokenValue;
}
if (is_initialized_) {
hr = watcher_->Stop();
VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
is_initialized_ = false;
}
}
MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
auto it = ports_.find(dev_id);
if (it == ports_.end())
return nullptr;
return it->second.get();
}
MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
return GetPortByDeviceId(port_ids_[port_index]);
}
protected:
// Points to the MidiService instance, which is expected to outlive the
// MidiPortManager instance.
raw_ptr<MidiService> midi_service_;
// Points to the MidiManagerWinrt instance, which is safe to be accessed
// from tasks that are invoked by TaskService.
raw_ptr<MidiManagerWinrt> midi_manager_;
private:
// DeviceWatcher callbacks:
void OnAdded(std::string dev_id, std::string dev_name) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
port_names_[dev_id] = dev_name;
ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
if (!dev_id_hstring.is_valid())
return;
IAsyncOperation<RuntimeType*>* async_op;
HRESULT hr =
midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
if (FAILED(hr)) {
VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
return;
}
MidiPortManager* port_manager = this;
TaskService* task_service = midi_service_->task_service();
hr = async_op->put_Completed(
WRL::Callback<
Win::Foundation::IAsyncOperationCompletedHandler<RuntimeType*>>(
[port_manager, task_service](
IAsyncOperation<RuntimeType*>* async_op, AsyncStatus status) {
// A reference to |async_op| is kept in |async_ops_|, safe to pass
// outside.
task_service->PostBoundTask(
kComTaskRunner,
base::BindOnce(
&MidiPortManager::OnCompletedGetPortFromIdAsync,
base::Unretained(port_manager),
base::Unretained(async_op)));
return S_OK;
})
.Get());
if (FAILED(hr)) {
VLOG(1) << "put_Completed failed: " << PrintHr(hr);
return;
}
// Keep a reference to incompleted |async_op| for releasing later.
async_ops_.insert(async_op);
}
void OnEnumerationCompleted() {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
if (async_ops_.empty())
midi_manager_->OnPortManagerReady();
else
enumeration_completed_not_ready_ = true;
}
void OnRemoved(std::string dev_id) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
// Note: in case Microsoft GS Wavetable Synth triggers this event for some
// reason, it will be ignored here with log emitted.
MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
if (!port) {
VLOG(1) << "Removing non-existent port " << dev_id;
return;
}
SetPortState(port->index, PortState::DISCONNECTED);
RemovePortEventHandlers(port);
port->handle = nullptr;
}
void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
CHECK(is_initialized_);
InterfaceType* handle = nullptr;
HRESULT hr = async_op->GetResults(&handle);
if (FAILED(hr)) {
VLOG(1) << "GetResults failed: " << PrintHr(hr);
return;
}
// Manually release COM interface to completed |async_op|.
auto it = async_ops_.find(async_op);
CHECK(it != async_ops_.end());
(*it)->Release();
async_ops_.erase(it);
if (!handle) {
VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
"ignoring";
return;
}
EventRegistrationToken token = {kInvalidTokenValue};
if (!RegisterOnMessageReceived(handle, &token))
return;
std::string dev_id = GetDeviceIdString(handle);
MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
if (port == nullptr) {
std::string manufacturer = "Unknown", driver_version = "Unknown";
GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
AddPort(mojom::PortInfo(dev_id, manufacturer, port_names_[dev_id],
driver_version, PortState::OPENED));
port = new MidiPort<InterfaceType>;
port->index = static_cast<uint32_t>(port_ids_.size());
ports_[dev_id].reset(port);
port_ids_.push_back(dev_id);
} else {
SetPortState(port->index, PortState::CONNECTED);
}
port->handle = handle;
port->token_MessageReceived = token;
if (enumeration_completed_not_ready_ && async_ops_.empty()) {
midi_manager_->OnPortManagerReady();
enumeration_completed_not_ready_ = false;
}
}
// Overrided by MidiInPortManager to listen to input ports.
virtual bool RegisterOnMessageReceived(InterfaceType* handle,
EventRegistrationToken* p_token) {
return true;
}
// Overrided by MidiInPortManager to remove MessageReceived event handler.
virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
// Calls midi_manager_->Add{Input,Output}Port.
virtual void AddPort(mojom::PortInfo info) = 0;
// Calls midi_manager_->Set{Input,Output}PortState.
virtual void SetPortState(uint32_t port_index, PortState state) = 0;
// Midi{In,Out}PortStatics instance.
WRL::ComPtr<StaticsInterfaceType> midi_port_statics_;
// DeviceWatcher instance and event registration tokens for unsubscribing
// events in destructor.
WRL::ComPtr<IDeviceWatcher> watcher_;
EventRegistrationToken token_Added_ = {kInvalidTokenValue},
token_EnumerationCompleted_ = {kInvalidTokenValue},
token_Removed_ = {kInvalidTokenValue},
token_Stopped_ = {kInvalidTokenValue},
token_Updated_ = {kInvalidTokenValue};
// All manipulations to these fields should be done on kComTaskRunner.
std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
ports_;
std::vector<std::string> port_ids_;
std::unordered_map<std::string, std::string> port_names_;
// Keeps AsyncOperation references before the operation completes. Note that
// raw pointers are used here and the COM interfaces should be released
// manually.
std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
// Set when device enumeration is completed but OnPortManagerReady() is not
// called since some ports are not yet ready (i.e. |async_ops_| is not empty).
// In such cases, OnPortManagerReady() will be called in
// OnCompletedGetPortFromIdAsync() when the last pending port is ready.
bool enumeration_completed_not_ready_ = false;
// Set if the instance is initialized without error. Should be checked in all
// methods on kComTaskRunner except StartWatcher().
bool is_initialized_ = false;
};
class MidiManagerWinrt::MidiInPortManager final
: public MidiPortManager<Win::Devices::Midi::IMidiInPort,
Win::Devices::Midi::MidiInPort,
Win::Devices::Midi::IMidiInPortStatics,
RuntimeClass_Windows_Devices_Midi_MidiInPort> {
public:
MidiInPortManager(MidiManagerWinrt* midi_manager)
: MidiPortManager(midi_manager) {}
MidiInPortManager(const MidiInPortManager&) = delete;
MidiInPortManager& operator=(const MidiInPortManager&) = delete;
private:
// MidiPortManager overrides:
bool RegisterOnMessageReceived(Win::Devices::Midi::IMidiInPort* handle,
EventRegistrationToken* p_token) override {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
MidiInPortManager* port_manager = this;
TaskService* task_service = midi_service_->task_service();
HRESULT hr = handle->add_MessageReceived(
WRL::Callback<ITypedEventHandler<
Win::Devices::Midi::MidiInPort*,
Win::Devices::Midi::MidiMessageReceivedEventArgs*>>(
[port_manager, task_service](
Win::Devices::Midi::IMidiInPort* handle,
Win::Devices::Midi::IMidiMessageReceivedEventArgs* args) {
const base::TimeTicks now = base::TimeTicks::Now();
std::string dev_id = GetDeviceIdString(handle);
WRL::ComPtr<Win::Devices::Midi::IMidiMessage> message;
HRESULT hr = args->get_Message(&message);
if (FAILED(hr)) {
VLOG(1) << "get_Message failed: " << PrintHr(hr);
return hr;
}
WRL::ComPtr<Win::Storage::Streams::IBuffer> buffer;
hr = message->get_RawData(&buffer);
if (FAILED(hr)) {
VLOG(1) << "get_RawData failed: " << PrintHr(hr);
return hr;
}
uint8_t* p_buffer_data = nullptr;
uint32_t data_length = 0;
hr = base::win::GetPointerToBufferData(
buffer.Get(), &p_buffer_data, &data_length);
if (FAILED(hr))
return hr;
std::vector<uint8_t> data(p_buffer_data,
p_buffer_data + data_length);
task_service->PostBoundTask(
kComTaskRunner,
base::BindOnce(&MidiInPortManager::OnMessageReceived,
base::Unretained(port_manager), dev_id, data,
now));
return S_OK;
})
.Get(),
p_token);
if (FAILED(hr)) {
VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
return false;
}
return true;
}
void RemovePortEventHandlers(
MidiPort<Win::Devices::Midi::IMidiInPort>* port) override {
if (!(port->handle &&
port->token_MessageReceived.value != kInvalidTokenValue))
return;
HRESULT hr =
port->handle->remove_MessageReceived(port->token_MessageReceived);
VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
port->token_MessageReceived.value = kInvalidTokenValue;
}
void AddPort(mojom::PortInfo info) final {
midi_manager_->AddInputPort(info);
}
void SetPortState(uint32_t port_index, PortState state) final {
midi_manager_->SetInputPortState(port_index, state);
}
// Callback on receiving MIDI input message.
void OnMessageReceived(std::string dev_id,
std::vector<uint8_t> data,
base::TimeTicks time) {
DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
MidiPort<Win::Devices::Midi::IMidiInPort>* port = GetPortByDeviceId(dev_id);
CHECK(port);
midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
}
};
class MidiManagerWinrt::MidiOutPortManager final
: public MidiPortManager<Win::Devices::Midi::IMidiOutPort,
Win::Devices::Midi::IMidiOutPort,
Win::Devices::Midi::IMidiOutPortStatics,
RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
public:
MidiOutPortManager(MidiManagerWinrt* midi_manager)
: MidiPortManager(midi_manager) {}
MidiOutPortManager(const MidiOutPortManager&) = delete;
MidiOutPortManager& operator=(const MidiOutPortManager&) = delete;
private:
// MidiPortManager overrides:
void AddPort(mojom::PortInfo info) final {
midi_manager_->AddOutputPort(info);
}
void SetPortState(uint32_t port_index, PortState state) final {
midi_manager_->SetOutputPortState(port_index, state);
}
};
namespace {
// FinalizeOnComRunner() run on kComTaskRunner even after the MidiManager
// instance destruction.
void FinalizeOnComRunner(
std::unique_ptr<MidiManagerWinrt::MidiInPortManager> port_manager_in,
std::unique_ptr<MidiManagerWinrt::MidiOutPortManager> port_manager_out) {
if (port_manager_in)
port_manager_in->StopWatcher();
if (port_manager_out)
port_manager_out->StopWatcher();
}
} // namespace
MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
: MidiManager(service) {}
MidiManagerWinrt::~MidiManagerWinrt() {
// Unbind and take a lock to ensure that InitializeOnComRunner should not run
// after here.
if (!service()->task_service()->UnbindInstance())
return;
base::AutoLock auto_lock(lazy_init_member_lock_);
service()->task_service()->PostStaticTask(
kComTaskRunner,
base::BindOnce(&FinalizeOnComRunner, std::move(port_manager_in_),
std::move(port_manager_out_)));
}
void MidiManagerWinrt::StartInitialization() {
if (!service()->task_service()->BindInstance())
return CompleteInitialization(Result::INITIALIZATION_ERROR);
service()->task_service()->PostBoundTask(
kComTaskRunner, base::BindOnce(&MidiManagerWinrt::InitializeOnComRunner,
base::Unretained(this)));
}
void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
base::TimeTicks timestamp) {
base::TimeDelta delay = MidiService::TimestampToTimeDeltaDelay(timestamp);
service()->task_service()->PostBoundDelayedTask(
kComTaskRunner,
base::BindOnce(&MidiManagerWinrt::SendOnComRunner, base::Unretained(this),
port_index, data),
delay);
service()->task_service()->PostBoundDelayedTask(
kComTaskRunner,
base::BindOnce(&MidiManagerWinrt::AccumulateMidiBytesSent,
base::Unretained(this), client, data.size()),
delay);
}
void MidiManagerWinrt::InitializeOnComRunner() {
base::AutoLock auto_lock(lazy_init_member_lock_);
DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
port_manager_in_ = std::make_unique<MidiInPortManager>(this);
port_manager_out_ = std::make_unique<MidiOutPortManager>(this);
if (!(port_manager_in_->StartWatcher() &&
port_manager_out_->StartWatcher())) {
port_manager_in_->StopWatcher();
port_manager_out_->StopWatcher();
service()->task_service()->PostBoundTask(
kDefaultTaskRunner,
base::BindOnce(&MidiManagerWinrt::CompleteInitialization,
base::Unretained(this), Result::INITIALIZATION_ERROR));
}
}
void MidiManagerWinrt::SendOnComRunner(uint32_t port_index,
const std::vector<uint8_t>& data) {
DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
base::AutoLock auto_lock(lazy_init_member_lock_);
MidiPort<Win::Devices::Midi::IMidiOutPort>* port =
port_manager_out_->GetPortByIndex(port_index);
if (!(port && port->handle)) {
VLOG(1) << "Port not available: " << port_index;
return;
}
WRL::ComPtr<Win::Storage::Streams::IBuffer> buffer;
HRESULT hr = base::win::CreateIBufferFromData(
data.data(), static_cast<UINT32>(data.size()), &buffer);
if (FAILED(hr)) {
VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
return;
}
hr = port->handle->SendBuffer(buffer.Get());
if (FAILED(hr)) {
VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
return;
}
}
void MidiManagerWinrt::OnPortManagerReady() {
DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
DCHECK(port_manager_ready_count_ < 2);
if (++port_manager_ready_count_ == 2) {
service()->task_service()->PostBoundTask(
kDefaultTaskRunner,
base::BindOnce(&MidiManagerWinrt::CompleteInitialization,
base::Unretained(this), Result::OK));
}
}
} // namespace midi