blob: 86b1cc32b0b2d52db6604f33c067a30cdbe67e29 [file] [log] [blame]
shaochuane58f9c72016-08-30 22:27:08 -07001// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/midi/midi_manager_winrt.h"
6
qiankun.miao53f2d662016-09-02 17:44:08 -07007#pragma warning(disable : 4467)
shaochuan80f1fba2016-09-01 20:44:51 -07008
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +00009#include <windows.h>
shaochuan4eff30e2016-09-09 01:24:14 -070010
11#include <cfgmgr32.h>
shaochuane58f9c72016-08-30 22:27:08 -070012#include <comdef.h>
shaochuan4eff30e2016-09-09 01:24:14 -070013#include <devpkey.h>
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +000014#include <initguid.h>
robliao048b1572017-04-21 12:46:39 -070015#include <objbase.h>
shaochuane58f9c72016-08-30 22:27:08 -070016#include <robuffer.h>
17#include <windows.devices.enumeration.h>
18#include <windows.devices.midi.h>
Robert Liao4a680c32017-10-18 19:10:01 +000019#include <wrl/client.h>
shaochuane58f9c72016-08-30 22:27:08 -070020#include <wrl/event.h>
21
22#include <iomanip>
23#include <unordered_map>
24#include <unordered_set>
25
26#include "base/bind.h"
shaochuan9ff63b82016-09-01 01:58:44 -070027#include "base/scoped_generic.h"
shaochuan17bc4a02016-09-06 01:42:12 -070028#include "base/strings/string_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070029#include "base/strings/utf_string_conversions.h"
shaochuane58f9c72016-08-30 22:27:08 -070030#include "base/timer/timer.h"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000031#include "base/win/core_winrt_util.h"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000032#include "base/win/scoped_hstring.h"
junweifuf51c5a02017-11-03 06:37:09 +000033#include "base/win/winrt_storage_util.h"
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +000034#include "media/midi/midi_service.h"
35#include "media/midi/task_service.h"
shaochuane58f9c72016-08-30 22:27:08 -070036
shaochuane58f9c72016-08-30 22:27:08 -070037namespace midi {
38namespace {
39
40namespace WRL = Microsoft::WRL;
41
42using namespace ABI::Windows::Devices::Enumeration;
43using namespace ABI::Windows::Devices::Midi;
44using namespace ABI::Windows::Foundation;
45using namespace ABI::Windows::Storage::Streams;
46
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000047using base::win::ScopedHString;
junweifua8cea852017-10-17 06:21:16 +000048using base::win::GetActivationFactory;
toyoshimec2570a2016-10-21 02:15:27 -070049using mojom::PortState;
toyoshim2f3a48f2016-10-17 01:54:13 -070050using mojom::Result;
shaochuane58f9c72016-08-30 22:27:08 -070051
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +000052enum {
53 kDefaultRunnerNotUsedOnWinrt = TaskService::kDefaultRunnerId,
54 kComTaskRunner
55};
56
shaochuane58f9c72016-08-30 22:27:08 -070057// Helpers for printing HRESULTs.
58struct PrintHr {
59 PrintHr(HRESULT hr) : hr(hr) {}
60 HRESULT hr;
61};
62
63std::ostream& operator<<(std::ostream& os, const PrintHr& phr) {
64 std::ios_base::fmtflags ff = os.flags();
65 os << _com_error(phr.hr).ErrorMessage() << " (0x" << std::hex
66 << std::uppercase << std::setfill('0') << std::setw(8) << phr.hr << ")";
67 os.flags(ff);
68 return os;
69}
70
shaochuane58f9c72016-08-30 22:27:08 -070071template <typename T>
72std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070073 HSTRING result;
74 HRESULT hr = obj->get_Id(&result);
75 if (FAILED(hr)) {
76 VLOG(1) << "get_Id failed: " << PrintHr(hr);
77 return std::string();
78 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000079 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070080}
81
82template <typename T>
83std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070084 HSTRING result;
85 HRESULT hr = obj->get_DeviceId(&result);
86 if (FAILED(hr)) {
87 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
88 return std::string();
89 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000090 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070091}
92
93std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -070094 HSTRING result;
95 HRESULT hr = info->get_Name(&result);
96 if (FAILED(hr)) {
97 VLOG(1) << "get_Name failed: " << PrintHr(hr);
98 return std::string();
99 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000100 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -0700101}
102
shaochuan110262b2016-08-31 02:15:16 -0700103// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
104// instance.
105bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
Robert Liao4a680c32017-10-18 19:10:01 +0000106 WRL::ComPtr<IMidiSynthesizerStatics> midi_synthesizer_statics;
junweifua8cea852017-10-17 06:21:16 +0000107 HRESULT hr =
108 GetActivationFactory<IMidiSynthesizerStatics,
109 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>(
110 &midi_synthesizer_statics);
111 if (FAILED(hr)) {
112 VLOG(1) << "IMidiSynthesizerStatics factory failed: " << PrintHr(hr);
113 return false;
114 }
shaochuan110262b2016-08-31 02:15:16 -0700115 boolean result = FALSE;
junweifua8cea852017-10-17 06:21:16 +0000116 hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
shaochuan110262b2016-08-31 02:15:16 -0700117 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
118 return result != FALSE;
119}
120
shaochuan4eff30e2016-09-09 01:24:14 -0700121void GetDevPropString(DEVINST handle,
122 const DEVPROPKEY* devprop_key,
123 std::string* out) {
124 DEVPROPTYPE devprop_type;
125 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700126
shaochuan4eff30e2016-09-09 01:24:14 -0700127 // Retrieve |buffer_size| and allocate buffer later for receiving data.
128 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
129 nullptr, &buffer_size, 0);
130 if (cr != CR_BUFFER_SMALL) {
131 // Here we print error codes in hex instead of using PrintHr() with
132 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
133 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
134 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
135 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700136 }
shaochuan4eff30e2016-09-09 01:24:14 -0700137 if (devprop_type != DEVPROP_TYPE_STRING) {
138 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
139 << "expected DEVPROP_TYPE_STRING";
140 return;
141 }
shaochuan17bc4a02016-09-06 01:42:12 -0700142
shaochuan4eff30e2016-09-09 01:24:14 -0700143 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
144
145 // Receive property data.
146 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
147 &buffer_size, 0);
148 if (cr != CR_SUCCESS)
149 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
150 else
151 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
152}
shaochuan17bc4a02016-09-06 01:42:12 -0700153
154// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700155// device driver through PnP Configuration Manager, given device (interface) ID
156// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
157// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700158//
159// Device instance ID is extracted from device (interface) ID provided by WinRT
160// APIs, for example from the following interface ID:
161// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
162// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700163//
164// However the extracted device instance ID represent a "software device"
165// provided by Microsoft, which is an interface on top of the hardware for each
166// input/output port. Therefore we further locate its parent device, which is
167// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700168void GetDriverInfoFromDeviceId(const std::string& dev_id,
169 std::string* out_manufacturer,
170 std::string* out_driver_version) {
171 base::string16 dev_instance_id =
172 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
173 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
174
shaochuan4eff30e2016-09-09 01:24:14 -0700175 DEVINST dev_instance_handle;
176 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
177 CM_LOCATE_DEVNODE_NORMAL);
178 if (cr != CR_SUCCESS) {
179 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700180 return;
181 }
182
shaochuan4eff30e2016-09-09 01:24:14 -0700183 DEVINST parent_handle;
184 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
185 if (cr != CR_SUCCESS) {
186 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700187 return;
188 }
189
shaochuan4eff30e2016-09-09 01:24:14 -0700190 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
191 out_manufacturer);
192 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
193 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700194}
195
shaochuane58f9c72016-08-30 22:27:08 -0700196// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
197const int64_t kInvalidTokenValue = 0;
198
199template <typename InterfaceType>
200struct MidiPort {
201 MidiPort() = default;
202
203 uint32_t index;
Robert Liao4a680c32017-10-18 19:10:01 +0000204 WRL::ComPtr<InterfaceType> handle;
shaochuane58f9c72016-08-30 22:27:08 -0700205 EventRegistrationToken token_MessageReceived;
206
207 private:
208 DISALLOW_COPY_AND_ASSIGN(MidiPort);
209};
210
211} // namespace
212
213template <typename InterfaceType,
214 typename RuntimeType,
215 typename StaticsInterfaceType,
216 base::char16 const* runtime_class_id>
217class MidiManagerWinrt::MidiPortManager {
218 public:
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000219 // MidiPortManager instances should be constructed on the kComTaskRunner.
shaochuane58f9c72016-08-30 22:27:08 -0700220 MidiPortManager(MidiManagerWinrt* midi_manager)
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000221 : midi_service_(midi_manager->service()), midi_manager_(midi_manager) {}
shaochuane58f9c72016-08-30 22:27:08 -0700222
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000223 virtual ~MidiPortManager() {
224 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
225 }
shaochuane58f9c72016-08-30 22:27:08 -0700226
227 bool StartWatcher() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000228 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700229
junweifua8cea852017-10-17 06:21:16 +0000230 HRESULT hr = GetActivationFactory<StaticsInterfaceType, runtime_class_id>(
231 &midi_port_statics_);
232 if (FAILED(hr)) {
233 VLOG(1) << "StaticsInterfaceType factory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700234 return false;
junweifua8cea852017-10-17 06:21:16 +0000235 }
shaochuane58f9c72016-08-30 22:27:08 -0700236
237 HSTRING device_selector = nullptr;
238 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
239 if (FAILED(hr)) {
240 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
241 return false;
242 }
243
Robert Liao4a680c32017-10-18 19:10:01 +0000244 WRL::ComPtr<IDeviceInformationStatics> dev_info_statics;
junweifua8cea852017-10-17 06:21:16 +0000245 hr = GetActivationFactory<
shaochuane58f9c72016-08-30 22:27:08 -0700246 IDeviceInformationStatics,
junweifua8cea852017-10-17 06:21:16 +0000247 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
248 &dev_info_statics);
249 if (FAILED(hr)) {
250 VLOG(1) << "IDeviceInformationStatics failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700251 return false;
junweifua8cea852017-10-17 06:21:16 +0000252 }
shaochuane58f9c72016-08-30 22:27:08 -0700253
254 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
robliao8d08e692017-05-11 10:14:00 -0700255 watcher_.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700256 if (FAILED(hr)) {
257 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
258 return false;
259 }
260
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000261 // Register callbacks to WinRT that post state-modifying tasks back to
262 // kComTaskRunner. All posted tasks run only during the MidiPortManager
263 // instance is alive. This is ensured by MidiManagerWinrt by calling
264 // UnbindInstance() before destructing any MidiPortManager instance. Thus
265 // we can handle raw pointers safely in the following blocks.
266 MidiPortManager* port_manager = this;
267 TaskService* task_service = midi_service_->task_service();
shaochuane58f9c72016-08-30 22:27:08 -0700268
269 hr = watcher_->add_Added(
270 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000271 [port_manager, task_service](IDeviceWatcher* watcher,
272 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700273 if (!info) {
274 VLOG(1) << "DeviceWatcher.Added callback provides null "
275 "pointer, ignoring";
276 return S_OK;
277 }
278
shaochuan110262b2016-08-31 02:15:16 -0700279 // Disable Microsoft GS Wavetable Synth due to security reasons.
280 // http://crbug.com/499279
281 if (IsMicrosoftSynthesizer(info))
282 return S_OK;
283
shaochuane58f9c72016-08-30 22:27:08 -0700284 std::string dev_id = GetIdString(info),
285 dev_name = GetNameString(info);
286
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000287 task_service->PostBoundTask(
288 kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded,
289 base::Unretained(port_manager),
290 dev_id, dev_name));
shaochuane58f9c72016-08-30 22:27:08 -0700291
292 return S_OK;
293 })
294 .Get(),
295 &token_Added_);
296 if (FAILED(hr)) {
297 VLOG(1) << "add_Added failed: " << PrintHr(hr);
298 return false;
299 }
300
301 hr = watcher_->add_EnumerationCompleted(
302 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000303 [port_manager, task_service](IDeviceWatcher* watcher,
304 IInspectable* insp) {
305 task_service->PostBoundTask(
306 kComTaskRunner,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000307 base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000308 base::Unretained(port_manager)));
shaochuane58f9c72016-08-30 22:27:08 -0700309
310 return S_OK;
311 })
312 .Get(),
313 &token_EnumerationCompleted_);
314 if (FAILED(hr)) {
315 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
316 return false;
317 }
318
319 hr = watcher_->add_Removed(
320 WRL::Callback<
321 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000322 [port_manager, task_service](IDeviceWatcher* watcher,
323 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700324 if (!update) {
325 VLOG(1) << "DeviceWatcher.Removed callback provides null "
326 "pointer, ignoring";
327 return S_OK;
328 }
329
shaochuane58f9c72016-08-30 22:27:08 -0700330 std::string dev_id = GetIdString(update);
331
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000332 task_service->PostBoundTask(
333 kComTaskRunner,
334 base::BindOnce(&MidiPortManager::OnRemoved,
335 base::Unretained(port_manager), dev_id));
shaochuane58f9c72016-08-30 22:27:08 -0700336
337 return S_OK;
338 })
339 .Get(),
340 &token_Removed_);
341 if (FAILED(hr)) {
342 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
343 return false;
344 }
345
346 hr = watcher_->add_Stopped(
347 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
348 [](IDeviceWatcher* watcher, IInspectable* insp) {
349 // Placeholder, does nothing for now.
350 return S_OK;
351 })
352 .Get(),
353 &token_Stopped_);
354 if (FAILED(hr)) {
355 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
356 return false;
357 }
358
359 hr = watcher_->add_Updated(
360 WRL::Callback<
361 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
362 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
363 // TODO(shaochuan): Check for fields to be updated here.
364 return S_OK;
365 })
366 .Get(),
367 &token_Updated_);
368 if (FAILED(hr)) {
369 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
370 return false;
371 }
372
373 hr = watcher_->Start();
374 if (FAILED(hr)) {
375 VLOG(1) << "Start failed: " << PrintHr(hr);
376 return false;
377 }
378
379 is_initialized_ = true;
380 return true;
381 }
382
383 void StopWatcher() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000384 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700385
386 HRESULT hr;
387
388 for (const auto& entry : ports_)
389 RemovePortEventHandlers(entry.second.get());
390
391 if (token_Added_.value != kInvalidTokenValue) {
392 hr = watcher_->remove_Added(token_Added_);
393 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
394 token_Added_.value = kInvalidTokenValue;
395 }
396 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
397 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
398 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
399 << PrintHr(hr);
400 token_EnumerationCompleted_.value = kInvalidTokenValue;
401 }
402 if (token_Removed_.value != kInvalidTokenValue) {
403 hr = watcher_->remove_Removed(token_Removed_);
404 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
405 token_Removed_.value = kInvalidTokenValue;
406 }
407 if (token_Stopped_.value != kInvalidTokenValue) {
408 hr = watcher_->remove_Stopped(token_Stopped_);
409 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
410 token_Stopped_.value = kInvalidTokenValue;
411 }
412 if (token_Updated_.value != kInvalidTokenValue) {
413 hr = watcher_->remove_Updated(token_Updated_);
414 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
415 token_Updated_.value = kInvalidTokenValue;
416 }
417
418 if (is_initialized_) {
419 hr = watcher_->Stop();
420 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
421 is_initialized_ = false;
422 }
423 }
424
425 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000426 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700427 CHECK(is_initialized_);
428
429 auto it = ports_.find(dev_id);
430 if (it == ports_.end())
431 return nullptr;
432 return it->second.get();
433 }
434
435 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000436 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700437 CHECK(is_initialized_);
438
439 return GetPortByDeviceId(port_ids_[port_index]);
440 }
441
442 protected:
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000443 // Points to the MidiService instance, which is expected to outlive the
shaochuane58f9c72016-08-30 22:27:08 -0700444 // MidiPortManager instance.
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000445 MidiService* midi_service_;
446
447 // Points to the MidiManagerWinrt instance, which is safe to be accessed
448 // from tasks that are invoked by TaskService.
shaochuane58f9c72016-08-30 22:27:08 -0700449 MidiManagerWinrt* midi_manager_;
450
shaochuane58f9c72016-08-30 22:27:08 -0700451 private:
452 // DeviceWatcher callbacks:
453 void OnAdded(std::string dev_id, std::string dev_name) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000454 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700455 CHECK(is_initialized_);
456
shaochuane58f9c72016-08-30 22:27:08 -0700457 port_names_[dev_id] = dev_name;
458
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000459 ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
shaochuan9ff63b82016-09-01 01:58:44 -0700460 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700461 return;
shaochuane58f9c72016-08-30 22:27:08 -0700462
463 IAsyncOperation<RuntimeType*>* async_op;
464
shaochuan9ff63b82016-09-01 01:58:44 -0700465 HRESULT hr =
466 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700467 if (FAILED(hr)) {
468 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
469 return;
470 }
471
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000472 MidiPortManager* port_manager = this;
473 TaskService* task_service = midi_service_->task_service();
shaochuane58f9c72016-08-30 22:27:08 -0700474
475 hr = async_op->put_Completed(
476 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000477 [port_manager, task_service](
478 IAsyncOperation<RuntimeType*>* async_op, AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700479 // A reference to |async_op| is kept in |async_ops_|, safe to pass
480 // outside.
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000481 task_service->PostBoundTask(
482 kComTaskRunner,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000483 base::BindOnce(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000484 &MidiPortManager::OnCompletedGetPortFromIdAsync,
485 base::Unretained(port_manager), async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700486
487 return S_OK;
488 })
489 .Get());
490 if (FAILED(hr)) {
491 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
492 return;
493 }
494
495 // Keep a reference to incompleted |async_op| for releasing later.
496 async_ops_.insert(async_op);
497 }
498
499 void OnEnumerationCompleted() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000500 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700501 CHECK(is_initialized_);
502
503 if (async_ops_.empty())
504 midi_manager_->OnPortManagerReady();
505 else
506 enumeration_completed_not_ready_ = true;
507 }
508
509 void OnRemoved(std::string dev_id) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000510 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700511 CHECK(is_initialized_);
512
shaochuan110262b2016-08-31 02:15:16 -0700513 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
514 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700515 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
516 if (!port) {
517 VLOG(1) << "Removing non-existent port " << dev_id;
518 return;
519 }
520
toyoshimec2570a2016-10-21 02:15:27 -0700521 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700522
523 RemovePortEventHandlers(port);
524 port->handle = nullptr;
525 }
526
shaochuanc2894522016-09-20 01:10:50 -0700527 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000528 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700529 CHECK(is_initialized_);
530
shaochuanc2894522016-09-20 01:10:50 -0700531 InterfaceType* handle = nullptr;
532 HRESULT hr = async_op->GetResults(&handle);
533 if (FAILED(hr)) {
534 VLOG(1) << "GetResults failed: " << PrintHr(hr);
535 return;
536 }
537
538 // Manually release COM interface to completed |async_op|.
539 auto it = async_ops_.find(async_op);
540 CHECK(it != async_ops_.end());
541 (*it)->Release();
542 async_ops_.erase(it);
543
544 if (!handle) {
545 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
546 "ignoring";
547 return;
548 }
549
shaochuane58f9c72016-08-30 22:27:08 -0700550 EventRegistrationToken token = {kInvalidTokenValue};
551 if (!RegisterOnMessageReceived(handle, &token))
552 return;
553
554 std::string dev_id = GetDeviceIdString(handle);
555
556 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
557
558 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700559 std::string manufacturer = "Unknown", driver_version = "Unknown";
560 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
561
562 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700563 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700564
565 port = new MidiPort<InterfaceType>;
566 port->index = static_cast<uint32_t>(port_ids_.size());
567
568 ports_[dev_id].reset(port);
569 port_ids_.push_back(dev_id);
570 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700571 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700572 }
573
574 port->handle = handle;
575 port->token_MessageReceived = token;
576
shaochuane58f9c72016-08-30 22:27:08 -0700577 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
578 midi_manager_->OnPortManagerReady();
579 enumeration_completed_not_ready_ = false;
580 }
581 }
582
583 // Overrided by MidiInPortManager to listen to input ports.
584 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
585 EventRegistrationToken* p_token) {
586 return true;
587 }
588
589 // Overrided by MidiInPortManager to remove MessageReceived event handler.
590 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
591
592 // Calls midi_manager_->Add{Input,Output}Port.
593 virtual void AddPort(MidiPortInfo info) = 0;
594
595 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700596 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700597
shaochuane58f9c72016-08-30 22:27:08 -0700598 // Midi{In,Out}PortStatics instance.
Robert Liao4a680c32017-10-18 19:10:01 +0000599 WRL::ComPtr<StaticsInterfaceType> midi_port_statics_;
shaochuane58f9c72016-08-30 22:27:08 -0700600
601 // DeviceWatcher instance and event registration tokens for unsubscribing
602 // events in destructor.
Robert Liao4a680c32017-10-18 19:10:01 +0000603 WRL::ComPtr<IDeviceWatcher> watcher_;
shaochuane58f9c72016-08-30 22:27:08 -0700604 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
605 token_EnumerationCompleted_ = {kInvalidTokenValue},
606 token_Removed_ = {kInvalidTokenValue},
607 token_Stopped_ = {kInvalidTokenValue},
608 token_Updated_ = {kInvalidTokenValue};
609
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000610 // All manipulations to these fields should be done on kComTaskRunner.
shaochuane58f9c72016-08-30 22:27:08 -0700611 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
612 ports_;
613 std::vector<std::string> port_ids_;
614 std::unordered_map<std::string, std::string> port_names_;
615
616 // Keeps AsyncOperation references before the operation completes. Note that
617 // raw pointers are used here and the COM interfaces should be released
618 // manually.
619 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
620
621 // Set when device enumeration is completed but OnPortManagerReady() is not
622 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
623 // In such cases, OnPortManagerReady() will be called in
624 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
625 bool enumeration_completed_not_ready_ = false;
626
627 // Set if the instance is initialized without error. Should be checked in all
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000628 // methods on kComTaskRunner except StartWatcher().
shaochuane58f9c72016-08-30 22:27:08 -0700629 bool is_initialized_ = false;
630};
631
632class MidiManagerWinrt::MidiInPortManager final
633 : public MidiPortManager<IMidiInPort,
634 MidiInPort,
635 IMidiInPortStatics,
636 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
637 public:
638 MidiInPortManager(MidiManagerWinrt* midi_manager)
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000639 : MidiPortManager(midi_manager) {}
shaochuane58f9c72016-08-30 22:27:08 -0700640
641 private:
642 // MidiPortManager overrides:
643 bool RegisterOnMessageReceived(IMidiInPort* handle,
644 EventRegistrationToken* p_token) override {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000645 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700646
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000647 MidiInPortManager* port_manager = this;
648 TaskService* task_service = midi_service_->task_service();
shaochuane58f9c72016-08-30 22:27:08 -0700649
650 HRESULT hr = handle->add_MessageReceived(
651 WRL::Callback<
652 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000653 [port_manager, task_service](IMidiInPort* handle,
654 IMidiMessageReceivedEventArgs* args) {
shaochuane58f9c72016-08-30 22:27:08 -0700655 const base::TimeTicks now = base::TimeTicks::Now();
656
657 std::string dev_id = GetDeviceIdString(handle);
658
Robert Liao4a680c32017-10-18 19:10:01 +0000659 WRL::ComPtr<IMidiMessage> message;
robliao8d08e692017-05-11 10:14:00 -0700660 HRESULT hr = args->get_Message(message.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700661 if (FAILED(hr)) {
662 VLOG(1) << "get_Message failed: " << PrintHr(hr);
663 return hr;
664 }
665
Robert Liao4a680c32017-10-18 19:10:01 +0000666 WRL::ComPtr<IBuffer> buffer;
robliao8d08e692017-05-11 10:14:00 -0700667 hr = message->get_RawData(buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700668 if (FAILED(hr)) {
669 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
670 return hr;
671 }
672
673 uint8_t* p_buffer_data = nullptr;
junweifuf51c5a02017-11-03 06:37:09 +0000674 uint32_t data_length = 0;
675 hr = base::win::GetPointerToBufferData(
676 buffer.Get(), &p_buffer_data, &data_length);
shaochuane58f9c72016-08-30 22:27:08 -0700677 if (FAILED(hr))
678 return hr;
679
shaochuane58f9c72016-08-30 22:27:08 -0700680 std::vector<uint8_t> data(p_buffer_data,
681 p_buffer_data + data_length);
682
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000683 task_service->PostBoundTask(
684 kComTaskRunner,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000685 base::BindOnce(&MidiInPortManager::OnMessageReceived,
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000686 base::Unretained(port_manager), dev_id, data,
687 now));
shaochuane58f9c72016-08-30 22:27:08 -0700688
689 return S_OK;
690 })
691 .Get(),
692 p_token);
693 if (FAILED(hr)) {
694 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
695 return false;
696 }
697
698 return true;
699 }
700
701 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
702 if (!(port->handle &&
703 port->token_MessageReceived.value != kInvalidTokenValue))
704 return;
705
706 HRESULT hr =
707 port->handle->remove_MessageReceived(port->token_MessageReceived);
708 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
709 port->token_MessageReceived.value = kInvalidTokenValue;
710 }
711
712 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
713
toyoshimec2570a2016-10-21 02:15:27 -0700714 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700715 midi_manager_->SetInputPortState(port_index, state);
716 }
717
shaochuane58f9c72016-08-30 22:27:08 -0700718 // Callback on receiving MIDI input message.
719 void OnMessageReceived(std::string dev_id,
720 std::vector<uint8_t> data,
721 base::TimeTicks time) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000722 DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700723
724 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
725 CHECK(port);
726
727 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
728 }
729
shaochuane58f9c72016-08-30 22:27:08 -0700730 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
731};
732
733class MidiManagerWinrt::MidiOutPortManager final
734 : public MidiPortManager<IMidiOutPort,
735 IMidiOutPort,
736 IMidiOutPortStatics,
737 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
738 public:
739 MidiOutPortManager(MidiManagerWinrt* midi_manager)
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000740 : MidiPortManager(midi_manager) {}
shaochuane58f9c72016-08-30 22:27:08 -0700741
742 private:
743 // MidiPortManager overrides:
744 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
745
toyoshimec2570a2016-10-21 02:15:27 -0700746 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700747 midi_manager_->SetOutputPortState(port_index, state);
748 }
749
shaochuane58f9c72016-08-30 22:27:08 -0700750 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
751};
752
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000753namespace {
754
755// FinalizeOnComRunner() run on kComTaskRunner even after the MidiManager
756// instance destruction.
757void FinalizeOnComRunner(
758 std::unique_ptr<MidiManagerWinrt::MidiInPortManager> port_manager_in,
759 std::unique_ptr<MidiManagerWinrt::MidiOutPortManager> port_manager_out) {
760 if (port_manager_in)
761 port_manager_in->StopWatcher();
762
763 if (port_manager_out)
764 port_manager_out->StopWatcher();
765}
766
767} // namespace
768
toyoshimf4d61522017-02-10 02:03:32 -0800769MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000770 : MidiManager(service) {}
shaochuane58f9c72016-08-30 22:27:08 -0700771
772MidiManagerWinrt::~MidiManagerWinrt() {
773 base::AutoLock auto_lock(lazy_init_member_lock_);
774
shaochuane58f9c72016-08-30 22:27:08 -0700775 CHECK(!port_manager_in_);
776 CHECK(!port_manager_out_);
shaochuane58f9c72016-08-30 22:27:08 -0700777}
778
779void MidiManagerWinrt::StartInitialization() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000780 bool result = service()->task_service()->BindInstance();
781 DCHECK(result);
shaochuane58f9c72016-08-30 22:27:08 -0700782
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000783 service()->task_service()->PostBoundTask(
784 kComTaskRunner, base::BindOnce(&MidiManagerWinrt::InitializeOnComRunner,
785 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700786}
787
788void MidiManagerWinrt::Finalize() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000789 // Unbind and take a lock to ensure that InitializeOnComRunner should not run
790 // after here.
791 bool result = service()->task_service()->UnbindInstance();
792 DCHECK(result);
shaochuane58f9c72016-08-30 22:27:08 -0700793
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000794 base::AutoLock auto_lock(lazy_init_member_lock_);
795
796 service()->task_service()->PostStaticTask(
797 kComTaskRunner,
798 base::BindOnce(&FinalizeOnComRunner, std::move(port_manager_in_),
799 std::move(port_manager_out_)));
shaochuane58f9c72016-08-30 22:27:08 -0700800}
801
802void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
803 uint32_t port_index,
804 const std::vector<uint8_t>& data,
tzik925e2c62018-02-02 07:39:45 +0000805 base::TimeTicks timestamp) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000806 base::TimeDelta delay = MidiService::TimestampToTimeDeltaDelay(timestamp);
807 service()->task_service()->PostBoundDelayedTask(
808 kComTaskRunner,
809 base::BindOnce(&MidiManagerWinrt::SendOnComRunner, base::Unretained(this),
810 port_index, data),
811 delay);
812 service()->task_service()->PostBoundDelayedTask(
813 kComTaskRunner,
814 base::BindOnce(&MidiManagerWinrt::AccumulateMidiBytesSent,
815 base::Unretained(this), client, data.size()),
816 delay);
shaochuane58f9c72016-08-30 22:27:08 -0700817}
818
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000819void MidiManagerWinrt::InitializeOnComRunner() {
shaochuane58f9c72016-08-30 22:27:08 -0700820 base::AutoLock auto_lock(lazy_init_member_lock_);
821
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000822 DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
823
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000824 bool preload_success = base::win::ResolveCoreWinRTDelayload() &&
825 ScopedHString::ResolveCoreWinRTStringDelayload();
826 if (!preload_success) {
shaochuan9ff63b82016-09-01 01:58:44 -0700827 CompleteInitialization(Result::INITIALIZATION_ERROR);
828 return;
829 }
830
shaochuane58f9c72016-08-30 22:27:08 -0700831 port_manager_in_.reset(new MidiInPortManager(this));
832 port_manager_out_.reset(new MidiOutPortManager(this));
833
shaochuane58f9c72016-08-30 22:27:08 -0700834 if (!(port_manager_in_->StartWatcher() &&
835 port_manager_out_->StartWatcher())) {
836 port_manager_in_->StopWatcher();
837 port_manager_out_->StopWatcher();
838 CompleteInitialization(Result::INITIALIZATION_ERROR);
839 }
840}
841
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000842void MidiManagerWinrt::SendOnComRunner(uint32_t port_index,
shaochuane58f9c72016-08-30 22:27:08 -0700843 const std::vector<uint8_t>& data) {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000844 DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700845
846 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
847 if (!(port && port->handle)) {
848 VLOG(1) << "Port not available: " << port_index;
849 return;
850 }
851
Robert Liao4a680c32017-10-18 19:10:01 +0000852 WRL::ComPtr<IBuffer> buffer;
junweifua8cea852017-10-17 06:21:16 +0000853 HRESULT hr = base::win::CreateIBufferFromData(
junweifuf51c5a02017-11-03 06:37:09 +0000854 data.data(), static_cast<UINT32>(data.size()), &buffer);
shaochuane58f9c72016-08-30 22:27:08 -0700855 if (FAILED(hr)) {
junweifua8cea852017-10-17 06:21:16 +0000856 VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700857 return;
858 }
859
robliao3566d1a2017-04-18 17:28:09 -0700860 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -0700861 if (FAILED(hr)) {
862 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
863 return;
864 }
865}
866
867void MidiManagerWinrt::OnPortManagerReady() {
Takashi Toyoshima3f0ea8f2018-01-17 09:19:59 +0000868 DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
shaochuane58f9c72016-08-30 22:27:08 -0700869 DCHECK(port_manager_ready_count_ < 2);
870
871 if (++port_manager_ready_count_ == 2)
872 CompleteInitialization(Result::OK);
873}
874
shaochuane58f9c72016-08-30 22:27:08 -0700875} // namespace midi