| // 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 |