blob: 8fb17ecfd98af345669d80cb292291f4c7a431aa [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
shaochuan4eff30e2016-09-09 01:24:14 -07009#include <initguid.h> // Required by <devpkey.h>
10
11#include <cfgmgr32.h>
shaochuane58f9c72016-08-30 22:27:08 -070012#include <comdef.h>
shaochuan4eff30e2016-09-09 01:24:14 -070013#include <devpkey.h>
robliao048b1572017-04-21 12:46:39 -070014#include <objbase.h>
shaochuane58f9c72016-08-30 22:27:08 -070015#include <robuffer.h>
16#include <windows.devices.enumeration.h>
17#include <windows.devices.midi.h>
18#include <wrl/event.h>
19
20#include <iomanip>
21#include <unordered_map>
22#include <unordered_set>
23
24#include "base/bind.h"
shaochuan9ff63b82016-09-01 01:58:44 -070025#include "base/scoped_generic.h"
Gabriel Charette78f94a02017-05-16 14:03:45 -040026#include "base/single_thread_task_runner.h"
shaochuan17bc4a02016-09-06 01:42:12 -070027#include "base/strings/string_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070028#include "base/strings/utf_string_conversions.h"
29#include "base/threading/thread_checker.h"
30#include "base/threading/thread_task_runner_handle.h"
31#include "base/timer/timer.h"
32#include "base/win/scoped_comptr.h"
shaochuane58f9c72016-08-30 22:27:08 -070033#include "media/midi/midi_scheduler.h"
34
shaochuane58f9c72016-08-30 22:27:08 -070035namespace midi {
36namespace {
37
38namespace WRL = Microsoft::WRL;
39
40using namespace ABI::Windows::Devices::Enumeration;
41using namespace ABI::Windows::Devices::Midi;
42using namespace ABI::Windows::Foundation;
43using namespace ABI::Windows::Storage::Streams;
44
45using base::win::ScopedComPtr;
toyoshimec2570a2016-10-21 02:15:27 -070046using mojom::PortState;
toyoshim2f3a48f2016-10-17 01:54:13 -070047using mojom::Result;
shaochuane58f9c72016-08-30 22:27:08 -070048
49// Helpers for printing HRESULTs.
50struct PrintHr {
51 PrintHr(HRESULT hr) : hr(hr) {}
52 HRESULT hr;
53};
54
55std::ostream& operator<<(std::ostream& os, const PrintHr& phr) {
56 std::ios_base::fmtflags ff = os.flags();
57 os << _com_error(phr.hr).ErrorMessage() << " (0x" << std::hex
58 << std::uppercase << std::setfill('0') << std::setw(8) << phr.hr << ")";
59 os.flags(ff);
60 return os;
61}
62
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +000063// Provides access to functions in combase.dll which may not be available on
64// Windows 7. Loads functions dynamically at runtime to prevent library
65// dependencies.
66class CombaseFunctions {
67 public:
68 CombaseFunctions() = default;
69
70 ~CombaseFunctions() {
71 if (combase_dll_)
72 ::FreeLibrary(combase_dll_);
73 }
74
75 bool LoadFunctions() {
76 combase_dll_ = ::LoadLibrary(L"combase.dll");
77 if (!combase_dll_)
78 return false;
79
80 get_factory_func_ = reinterpret_cast<decltype(&::RoGetActivationFactory)>(
81 ::GetProcAddress(combase_dll_, "RoGetActivationFactory"));
82 if (!get_factory_func_)
83 return false;
84
85 create_string_func_ = reinterpret_cast<decltype(&::WindowsCreateString)>(
86 ::GetProcAddress(combase_dll_, "WindowsCreateString"));
87 if (!create_string_func_)
88 return false;
89
90 delete_string_func_ = reinterpret_cast<decltype(&::WindowsDeleteString)>(
91 ::GetProcAddress(combase_dll_, "WindowsDeleteString"));
92 if (!delete_string_func_)
93 return false;
94
95 get_string_raw_buffer_func_ =
96 reinterpret_cast<decltype(&::WindowsGetStringRawBuffer)>(
97 ::GetProcAddress(combase_dll_, "WindowsGetStringRawBuffer"));
98 if (!get_string_raw_buffer_func_)
99 return false;
100
101 return true;
102 }
103
104 HRESULT RoGetActivationFactory(HSTRING class_id,
105 const IID& iid,
106 void** out_factory) {
107 DCHECK(get_factory_func_);
108 return get_factory_func_(class_id, iid, out_factory);
109 }
110
111 HRESULT WindowsCreateString(const base::char16* src,
112 uint32_t len,
113 HSTRING* out_hstr) {
114 DCHECK(create_string_func_);
115 return create_string_func_(src, len, out_hstr);
116 }
117
118 HRESULT WindowsDeleteString(HSTRING hstr) {
119 DCHECK(delete_string_func_);
120 return delete_string_func_(hstr);
121 }
122
123 const base::char16* WindowsGetStringRawBuffer(HSTRING hstr,
124 uint32_t* out_len) {
125 DCHECK(get_string_raw_buffer_func_);
126 return get_string_raw_buffer_func_(hstr, out_len);
127 }
128
129 private:
130 HMODULE combase_dll_ = nullptr;
131
132 decltype(&::RoGetActivationFactory) get_factory_func_ = nullptr;
133 decltype(&::WindowsCreateString) create_string_func_ = nullptr;
134 decltype(&::WindowsDeleteString) delete_string_func_ = nullptr;
135 decltype(&::WindowsGetStringRawBuffer) get_string_raw_buffer_func_ = nullptr;
136};
137
138CombaseFunctions* GetCombaseFunctions() {
139 static CombaseFunctions* functions = new CombaseFunctions();
140 return functions;
141}
142
143// Scoped HSTRING class to maintain lifetime of HSTRINGs allocated with
144// WindowsCreateString().
145class ScopedHStringTraits {
146 public:
147 static HSTRING InvalidValue() { return nullptr; }
148
149 static void Free(HSTRING hstr) {
150 GetCombaseFunctions()->WindowsDeleteString(hstr);
151 }
152};
153
154class ScopedHString : public base::ScopedGeneric<HSTRING, ScopedHStringTraits> {
155 public:
156 explicit ScopedHString(const base::char16* str) : ScopedGeneric(nullptr) {
157 HSTRING hstr;
158 HRESULT hr = GetCombaseFunctions()->WindowsCreateString(
159 str, static_cast<uint32_t>(wcslen(str)), &hstr);
160 if (FAILED(hr))
161 VLOG(1) << "WindowsCreateString failed: " << PrintHr(hr);
162 else
163 reset(hstr);
164 }
165};
166
shaochuane58f9c72016-08-30 22:27:08 -0700167// Factory functions that activate and create WinRT components. The caller takes
168// ownership of the returning ComPtr.
169template <typename InterfaceType, base::char16 const* runtime_class_id>
170ScopedComPtr<InterfaceType> WrlStaticsFactory() {
171 ScopedComPtr<InterfaceType> com_ptr;
172
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000173 ScopedHString class_id_hstring(runtime_class_id);
shaochuan9ff63b82016-09-01 01:58:44 -0700174 if (!class_id_hstring.is_valid()) {
175 com_ptr = nullptr;
176 return com_ptr;
177 }
178
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000179 HRESULT hr = GetCombaseFunctions()->RoGetActivationFactory(
180 class_id_hstring.get(), IID_PPV_ARGS(&com_ptr));
shaochuane58f9c72016-08-30 22:27:08 -0700181 if (FAILED(hr)) {
shaochuan9ff63b82016-09-01 01:58:44 -0700182 VLOG(1) << "RoGetActivationFactory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700183 com_ptr = nullptr;
184 }
185
186 return com_ptr;
187}
188
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000189std::string HStringToString(HSTRING hstr) {
190 // Note: empty HSTRINGs are represent as nullptr, and instantiating
191 // std::string with nullptr (in base::WideToUTF8) is undefined behavior.
192 const base::char16* buffer =
193 GetCombaseFunctions()->WindowsGetStringRawBuffer(hstr, nullptr);
194 if (buffer)
195 return base::WideToUTF8(buffer);
196 return std::string();
197}
198
shaochuane58f9c72016-08-30 22:27:08 -0700199template <typename T>
200std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -0700201 HSTRING result;
202 HRESULT hr = obj->get_Id(&result);
203 if (FAILED(hr)) {
204 VLOG(1) << "get_Id failed: " << PrintHr(hr);
205 return std::string();
206 }
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000207 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700208}
209
210template <typename T>
211std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -0700212 HSTRING result;
213 HRESULT hr = obj->get_DeviceId(&result);
214 if (FAILED(hr)) {
215 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
216 return std::string();
217 }
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000218 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700219}
220
221std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -0700222 HSTRING result;
223 HRESULT hr = info->get_Name(&result);
224 if (FAILED(hr)) {
225 VLOG(1) << "get_Name failed: " << PrintHr(hr);
226 return std::string();
227 }
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000228 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700229}
230
231HRESULT GetPointerToBufferData(IBuffer* buffer, uint8_t** out) {
232 ScopedComPtr<Windows::Storage::Streams::IBufferByteAccess> buffer_byte_access;
233
robliao026fda22017-05-17 12:10:54 -0700234 HRESULT hr = buffer->QueryInterface(IID_PPV_ARGS(&buffer_byte_access));
shaochuane58f9c72016-08-30 22:27:08 -0700235 if (FAILED(hr)) {
236 VLOG(1) << "QueryInterface failed: " << PrintHr(hr);
237 return hr;
238 }
239
240 // Lifetime of the pointing buffer is controlled by the buffer object.
241 hr = buffer_byte_access->Buffer(out);
242 if (FAILED(hr)) {
243 VLOG(1) << "Buffer failed: " << PrintHr(hr);
244 return hr;
245 }
246
247 return S_OK;
248}
249
shaochuan110262b2016-08-31 02:15:16 -0700250// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
251// instance.
252bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
253 auto midi_synthesizer_statics =
254 WrlStaticsFactory<IMidiSynthesizerStatics,
255 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>();
256 boolean result = FALSE;
257 HRESULT hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
258 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
259 return result != FALSE;
260}
261
shaochuan4eff30e2016-09-09 01:24:14 -0700262void GetDevPropString(DEVINST handle,
263 const DEVPROPKEY* devprop_key,
264 std::string* out) {
265 DEVPROPTYPE devprop_type;
266 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700267
shaochuan4eff30e2016-09-09 01:24:14 -0700268 // Retrieve |buffer_size| and allocate buffer later for receiving data.
269 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
270 nullptr, &buffer_size, 0);
271 if (cr != CR_BUFFER_SMALL) {
272 // Here we print error codes in hex instead of using PrintHr() with
273 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
274 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
275 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
276 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700277 }
shaochuan4eff30e2016-09-09 01:24:14 -0700278 if (devprop_type != DEVPROP_TYPE_STRING) {
279 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
280 << "expected DEVPROP_TYPE_STRING";
281 return;
282 }
shaochuan17bc4a02016-09-06 01:42:12 -0700283
shaochuan4eff30e2016-09-09 01:24:14 -0700284 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
285
286 // Receive property data.
287 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
288 &buffer_size, 0);
289 if (cr != CR_SUCCESS)
290 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
291 else
292 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
293}
shaochuan17bc4a02016-09-06 01:42:12 -0700294
295// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700296// device driver through PnP Configuration Manager, given device (interface) ID
297// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
298// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700299//
300// Device instance ID is extracted from device (interface) ID provided by WinRT
301// APIs, for example from the following interface ID:
302// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
303// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700304//
305// However the extracted device instance ID represent a "software device"
306// provided by Microsoft, which is an interface on top of the hardware for each
307// input/output port. Therefore we further locate its parent device, which is
308// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700309void GetDriverInfoFromDeviceId(const std::string& dev_id,
310 std::string* out_manufacturer,
311 std::string* out_driver_version) {
312 base::string16 dev_instance_id =
313 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
314 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
315
shaochuan4eff30e2016-09-09 01:24:14 -0700316 DEVINST dev_instance_handle;
317 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
318 CM_LOCATE_DEVNODE_NORMAL);
319 if (cr != CR_SUCCESS) {
320 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700321 return;
322 }
323
shaochuan4eff30e2016-09-09 01:24:14 -0700324 DEVINST parent_handle;
325 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
326 if (cr != CR_SUCCESS) {
327 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700328 return;
329 }
330
shaochuan4eff30e2016-09-09 01:24:14 -0700331 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
332 out_manufacturer);
333 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
334 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700335}
336
shaochuane58f9c72016-08-30 22:27:08 -0700337// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
338const int64_t kInvalidTokenValue = 0;
339
340template <typename InterfaceType>
341struct MidiPort {
342 MidiPort() = default;
343
344 uint32_t index;
345 ScopedComPtr<InterfaceType> handle;
346 EventRegistrationToken token_MessageReceived;
347
348 private:
349 DISALLOW_COPY_AND_ASSIGN(MidiPort);
350};
351
352} // namespace
353
354template <typename InterfaceType,
355 typename RuntimeType,
356 typename StaticsInterfaceType,
357 base::char16 const* runtime_class_id>
358class MidiManagerWinrt::MidiPortManager {
359 public:
360 // MidiPortManager instances should be constructed on the COM thread.
361 MidiPortManager(MidiManagerWinrt* midi_manager)
362 : midi_manager_(midi_manager),
363 task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
364
365 virtual ~MidiPortManager() { DCHECK(thread_checker_.CalledOnValidThread()); }
366
367 bool StartWatcher() {
368 DCHECK(thread_checker_.CalledOnValidThread());
369
370 HRESULT hr;
371
372 midi_port_statics_ =
373 WrlStaticsFactory<StaticsInterfaceType, runtime_class_id>();
374 if (!midi_port_statics_)
375 return false;
376
377 HSTRING device_selector = nullptr;
378 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
379 if (FAILED(hr)) {
380 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
381 return false;
382 }
383
384 auto dev_info_statics = WrlStaticsFactory<
385 IDeviceInformationStatics,
386 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>();
387 if (!dev_info_statics)
388 return false;
389
390 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
robliao8d08e692017-05-11 10:14:00 -0700391 watcher_.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700392 if (FAILED(hr)) {
393 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
394 return false;
395 }
396
397 // Register callbacks to WinRT that post state-modifying jobs back to COM
398 // thread. |weak_ptr| and |task_runner| are captured by lambda callbacks for
399 // posting jobs. Note that WinRT callback arguments should not be passed
400 // outside the callback since the pointers may be unavailable afterwards.
401 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
402 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
403
404 hr = watcher_->add_Added(
405 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
406 [weak_ptr, task_runner](IDeviceWatcher* watcher,
407 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700408 if (!info) {
409 VLOG(1) << "DeviceWatcher.Added callback provides null "
410 "pointer, ignoring";
411 return S_OK;
412 }
413
shaochuan110262b2016-08-31 02:15:16 -0700414 // Disable Microsoft GS Wavetable Synth due to security reasons.
415 // http://crbug.com/499279
416 if (IsMicrosoftSynthesizer(info))
417 return S_OK;
418
shaochuane58f9c72016-08-30 22:27:08 -0700419 std::string dev_id = GetIdString(info),
420 dev_name = GetNameString(info);
421
422 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000423 FROM_HERE, base::BindOnce(&MidiPortManager::OnAdded, weak_ptr,
424 dev_id, dev_name));
shaochuane58f9c72016-08-30 22:27:08 -0700425
426 return S_OK;
427 })
428 .Get(),
429 &token_Added_);
430 if (FAILED(hr)) {
431 VLOG(1) << "add_Added failed: " << PrintHr(hr);
432 return false;
433 }
434
435 hr = watcher_->add_EnumerationCompleted(
436 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
437 [weak_ptr, task_runner](IDeviceWatcher* watcher,
438 IInspectable* insp) {
439 task_runner->PostTask(
440 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000441 base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
442 weak_ptr));
shaochuane58f9c72016-08-30 22:27:08 -0700443
444 return S_OK;
445 })
446 .Get(),
447 &token_EnumerationCompleted_);
448 if (FAILED(hr)) {
449 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
450 return false;
451 }
452
453 hr = watcher_->add_Removed(
454 WRL::Callback<
455 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
456 [weak_ptr, task_runner](IDeviceWatcher* watcher,
457 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700458 if (!update) {
459 VLOG(1) << "DeviceWatcher.Removed callback provides null "
460 "pointer, ignoring";
461 return S_OK;
462 }
463
shaochuane58f9c72016-08-30 22:27:08 -0700464 std::string dev_id = GetIdString(update);
465
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000466 task_runner->PostTask(FROM_HERE,
467 base::BindOnce(&MidiPortManager::OnRemoved,
468 weak_ptr, dev_id));
shaochuane58f9c72016-08-30 22:27:08 -0700469
470 return S_OK;
471 })
472 .Get(),
473 &token_Removed_);
474 if (FAILED(hr)) {
475 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
476 return false;
477 }
478
479 hr = watcher_->add_Stopped(
480 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
481 [](IDeviceWatcher* watcher, IInspectable* insp) {
482 // Placeholder, does nothing for now.
483 return S_OK;
484 })
485 .Get(),
486 &token_Stopped_);
487 if (FAILED(hr)) {
488 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
489 return false;
490 }
491
492 hr = watcher_->add_Updated(
493 WRL::Callback<
494 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
495 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
496 // TODO(shaochuan): Check for fields to be updated here.
497 return S_OK;
498 })
499 .Get(),
500 &token_Updated_);
501 if (FAILED(hr)) {
502 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
503 return false;
504 }
505
506 hr = watcher_->Start();
507 if (FAILED(hr)) {
508 VLOG(1) << "Start failed: " << PrintHr(hr);
509 return false;
510 }
511
512 is_initialized_ = true;
513 return true;
514 }
515
516 void StopWatcher() {
517 DCHECK(thread_checker_.CalledOnValidThread());
518
519 HRESULT hr;
520
521 for (const auto& entry : ports_)
522 RemovePortEventHandlers(entry.second.get());
523
524 if (token_Added_.value != kInvalidTokenValue) {
525 hr = watcher_->remove_Added(token_Added_);
526 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
527 token_Added_.value = kInvalidTokenValue;
528 }
529 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
530 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
531 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
532 << PrintHr(hr);
533 token_EnumerationCompleted_.value = kInvalidTokenValue;
534 }
535 if (token_Removed_.value != kInvalidTokenValue) {
536 hr = watcher_->remove_Removed(token_Removed_);
537 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
538 token_Removed_.value = kInvalidTokenValue;
539 }
540 if (token_Stopped_.value != kInvalidTokenValue) {
541 hr = watcher_->remove_Stopped(token_Stopped_);
542 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
543 token_Stopped_.value = kInvalidTokenValue;
544 }
545 if (token_Updated_.value != kInvalidTokenValue) {
546 hr = watcher_->remove_Updated(token_Updated_);
547 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
548 token_Updated_.value = kInvalidTokenValue;
549 }
550
551 if (is_initialized_) {
552 hr = watcher_->Stop();
553 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
554 is_initialized_ = false;
555 }
556 }
557
558 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
559 DCHECK(thread_checker_.CalledOnValidThread());
560 CHECK(is_initialized_);
561
562 auto it = ports_.find(dev_id);
563 if (it == ports_.end())
564 return nullptr;
565 return it->second.get();
566 }
567
568 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
569 DCHECK(thread_checker_.CalledOnValidThread());
570 CHECK(is_initialized_);
571
572 return GetPortByDeviceId(port_ids_[port_index]);
573 }
574
575 protected:
576 // Points to the MidiManagerWinrt instance, which is expected to outlive the
577 // MidiPortManager instance.
578 MidiManagerWinrt* midi_manager_;
579
580 // Task runner of the COM thread.
581 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
582
583 // Ensures all methods are called on the COM thread.
584 base::ThreadChecker thread_checker_;
585
586 private:
587 // DeviceWatcher callbacks:
588 void OnAdded(std::string dev_id, std::string dev_name) {
589 DCHECK(thread_checker_.CalledOnValidThread());
590 CHECK(is_initialized_);
591
shaochuane58f9c72016-08-30 22:27:08 -0700592 port_names_[dev_id] = dev_name;
593
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000594 ScopedHString dev_id_hstring(base::UTF8ToWide(dev_id).c_str());
shaochuan9ff63b82016-09-01 01:58:44 -0700595 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700596 return;
shaochuane58f9c72016-08-30 22:27:08 -0700597
598 IAsyncOperation<RuntimeType*>* async_op;
599
shaochuan9ff63b82016-09-01 01:58:44 -0700600 HRESULT hr =
601 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700602 if (FAILED(hr)) {
603 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
604 return;
605 }
606
607 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
608 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
609
610 hr = async_op->put_Completed(
611 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
612 [weak_ptr, task_runner](IAsyncOperation<RuntimeType*>* async_op,
613 AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700614 // A reference to |async_op| is kept in |async_ops_|, safe to pass
615 // outside.
616 task_runner->PostTask(
617 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000618 base::BindOnce(
619 &MidiPortManager::OnCompletedGetPortFromIdAsync, weak_ptr,
620 async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700621
622 return S_OK;
623 })
624 .Get());
625 if (FAILED(hr)) {
626 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
627 return;
628 }
629
630 // Keep a reference to incompleted |async_op| for releasing later.
631 async_ops_.insert(async_op);
632 }
633
634 void OnEnumerationCompleted() {
635 DCHECK(thread_checker_.CalledOnValidThread());
636 CHECK(is_initialized_);
637
638 if (async_ops_.empty())
639 midi_manager_->OnPortManagerReady();
640 else
641 enumeration_completed_not_ready_ = true;
642 }
643
644 void OnRemoved(std::string dev_id) {
645 DCHECK(thread_checker_.CalledOnValidThread());
646 CHECK(is_initialized_);
647
shaochuan110262b2016-08-31 02:15:16 -0700648 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
649 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700650 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
651 if (!port) {
652 VLOG(1) << "Removing non-existent port " << dev_id;
653 return;
654 }
655
toyoshimec2570a2016-10-21 02:15:27 -0700656 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700657
658 RemovePortEventHandlers(port);
659 port->handle = nullptr;
660 }
661
shaochuanc2894522016-09-20 01:10:50 -0700662 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
shaochuane58f9c72016-08-30 22:27:08 -0700663 DCHECK(thread_checker_.CalledOnValidThread());
664 CHECK(is_initialized_);
665
shaochuanc2894522016-09-20 01:10:50 -0700666 InterfaceType* handle = nullptr;
667 HRESULT hr = async_op->GetResults(&handle);
668 if (FAILED(hr)) {
669 VLOG(1) << "GetResults failed: " << PrintHr(hr);
670 return;
671 }
672
673 // Manually release COM interface to completed |async_op|.
674 auto it = async_ops_.find(async_op);
675 CHECK(it != async_ops_.end());
676 (*it)->Release();
677 async_ops_.erase(it);
678
679 if (!handle) {
680 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
681 "ignoring";
682 return;
683 }
684
shaochuane58f9c72016-08-30 22:27:08 -0700685 EventRegistrationToken token = {kInvalidTokenValue};
686 if (!RegisterOnMessageReceived(handle, &token))
687 return;
688
689 std::string dev_id = GetDeviceIdString(handle);
690
691 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
692
693 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700694 std::string manufacturer = "Unknown", driver_version = "Unknown";
695 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
696
697 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700698 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700699
700 port = new MidiPort<InterfaceType>;
701 port->index = static_cast<uint32_t>(port_ids_.size());
702
703 ports_[dev_id].reset(port);
704 port_ids_.push_back(dev_id);
705 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700706 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700707 }
708
709 port->handle = handle;
710 port->token_MessageReceived = token;
711
shaochuane58f9c72016-08-30 22:27:08 -0700712 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
713 midi_manager_->OnPortManagerReady();
714 enumeration_completed_not_ready_ = false;
715 }
716 }
717
718 // Overrided by MidiInPortManager to listen to input ports.
719 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
720 EventRegistrationToken* p_token) {
721 return true;
722 }
723
724 // Overrided by MidiInPortManager to remove MessageReceived event handler.
725 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
726
727 // Calls midi_manager_->Add{Input,Output}Port.
728 virtual void AddPort(MidiPortInfo info) = 0;
729
730 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700731 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700732
733 // WeakPtrFactory has to be declared in derived class, use this method to
734 // retrieve upcasted WeakPtr for posting tasks.
735 virtual base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() = 0;
736
737 // Midi{In,Out}PortStatics instance.
738 ScopedComPtr<StaticsInterfaceType> midi_port_statics_;
739
740 // DeviceWatcher instance and event registration tokens for unsubscribing
741 // events in destructor.
742 ScopedComPtr<IDeviceWatcher> watcher_;
743 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
744 token_EnumerationCompleted_ = {kInvalidTokenValue},
745 token_Removed_ = {kInvalidTokenValue},
746 token_Stopped_ = {kInvalidTokenValue},
747 token_Updated_ = {kInvalidTokenValue};
748
749 // All manipulations to these fields should be done on COM thread.
750 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
751 ports_;
752 std::vector<std::string> port_ids_;
753 std::unordered_map<std::string, std::string> port_names_;
754
755 // Keeps AsyncOperation references before the operation completes. Note that
756 // raw pointers are used here and the COM interfaces should be released
757 // manually.
758 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
759
760 // Set when device enumeration is completed but OnPortManagerReady() is not
761 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
762 // In such cases, OnPortManagerReady() will be called in
763 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
764 bool enumeration_completed_not_ready_ = false;
765
766 // Set if the instance is initialized without error. Should be checked in all
767 // methods on COM thread except StartWatcher().
768 bool is_initialized_ = false;
769};
770
771class MidiManagerWinrt::MidiInPortManager final
772 : public MidiPortManager<IMidiInPort,
773 MidiInPort,
774 IMidiInPortStatics,
775 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
776 public:
777 MidiInPortManager(MidiManagerWinrt* midi_manager)
778 : MidiPortManager(midi_manager), weak_factory_(this) {}
779
780 private:
781 // MidiPortManager overrides:
782 bool RegisterOnMessageReceived(IMidiInPort* handle,
783 EventRegistrationToken* p_token) override {
784 DCHECK(thread_checker_.CalledOnValidThread());
785
786 base::WeakPtr<MidiInPortManager> weak_ptr = weak_factory_.GetWeakPtr();
787 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
788
789 HRESULT hr = handle->add_MessageReceived(
790 WRL::Callback<
791 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
792 [weak_ptr, task_runner](IMidiInPort* handle,
793 IMidiMessageReceivedEventArgs* args) {
794 const base::TimeTicks now = base::TimeTicks::Now();
795
796 std::string dev_id = GetDeviceIdString(handle);
797
798 ScopedComPtr<IMidiMessage> message;
robliao8d08e692017-05-11 10:14:00 -0700799 HRESULT hr = args->get_Message(message.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700800 if (FAILED(hr)) {
801 VLOG(1) << "get_Message failed: " << PrintHr(hr);
802 return hr;
803 }
804
805 ScopedComPtr<IBuffer> buffer;
robliao8d08e692017-05-11 10:14:00 -0700806 hr = message->get_RawData(buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700807 if (FAILED(hr)) {
808 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
809 return hr;
810 }
811
812 uint8_t* p_buffer_data = nullptr;
robliao3566d1a2017-04-18 17:28:09 -0700813 hr = GetPointerToBufferData(buffer.Get(), &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -0700814 if (FAILED(hr))
815 return hr;
816
817 uint32_t data_length = 0;
818 hr = buffer->get_Length(&data_length);
819 if (FAILED(hr)) {
820 VLOG(1) << "get_Length failed: " << PrintHr(hr);
821 return hr;
822 }
823
824 std::vector<uint8_t> data(p_buffer_data,
825 p_buffer_data + data_length);
826
827 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000828 FROM_HERE,
829 base::BindOnce(&MidiInPortManager::OnMessageReceived,
830 weak_ptr, dev_id, data, now));
shaochuane58f9c72016-08-30 22:27:08 -0700831
832 return S_OK;
833 })
834 .Get(),
835 p_token);
836 if (FAILED(hr)) {
837 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
838 return false;
839 }
840
841 return true;
842 }
843
844 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
845 if (!(port->handle &&
846 port->token_MessageReceived.value != kInvalidTokenValue))
847 return;
848
849 HRESULT hr =
850 port->handle->remove_MessageReceived(port->token_MessageReceived);
851 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
852 port->token_MessageReceived.value = kInvalidTokenValue;
853 }
854
855 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
856
toyoshimec2570a2016-10-21 02:15:27 -0700857 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700858 midi_manager_->SetInputPortState(port_index, state);
859 }
860
861 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
862 DCHECK(thread_checker_.CalledOnValidThread());
863
864 return weak_factory_.GetWeakPtr();
865 }
866
867 // Callback on receiving MIDI input message.
868 void OnMessageReceived(std::string dev_id,
869 std::vector<uint8_t> data,
870 base::TimeTicks time) {
871 DCHECK(thread_checker_.CalledOnValidThread());
872
873 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
874 CHECK(port);
875
876 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
877 }
878
879 // Last member to ensure destructed first.
880 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
881
882 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
883};
884
885class MidiManagerWinrt::MidiOutPortManager final
886 : public MidiPortManager<IMidiOutPort,
887 IMidiOutPort,
888 IMidiOutPortStatics,
889 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
890 public:
891 MidiOutPortManager(MidiManagerWinrt* midi_manager)
892 : MidiPortManager(midi_manager), weak_factory_(this) {}
893
894 private:
895 // MidiPortManager overrides:
896 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
897
toyoshimec2570a2016-10-21 02:15:27 -0700898 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700899 midi_manager_->SetOutputPortState(port_index, state);
900 }
901
902 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
903 DCHECK(thread_checker_.CalledOnValidThread());
904
905 return weak_factory_.GetWeakPtr();
906 }
907
908 // Last member to ensure destructed first.
909 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
910
911 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
912};
913
toyoshimf4d61522017-02-10 02:03:32 -0800914MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
915 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700916
917MidiManagerWinrt::~MidiManagerWinrt() {
918 base::AutoLock auto_lock(lazy_init_member_lock_);
919
920 CHECK(!com_thread_checker_);
921 CHECK(!port_manager_in_);
922 CHECK(!port_manager_out_);
923 CHECK(!scheduler_);
924}
925
926void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700927 com_thread_.init_com_with_mta(true);
928 com_thread_.Start();
929
930 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000931 FROM_HERE, base::BindOnce(&MidiManagerWinrt::InitializeOnComThread,
932 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700933}
934
935void MidiManagerWinrt::Finalize() {
936 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000937 FROM_HERE, base::BindOnce(&MidiManagerWinrt::FinalizeOnComThread,
938 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700939
940 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
941 // will be ignored.
942 com_thread_.Stop();
943}
944
945void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
946 uint32_t port_index,
947 const std::vector<uint8_t>& data,
948 double timestamp) {
949 CHECK(scheduler_);
950
951 scheduler_->PostSendDataTask(
952 client, data.size(), timestamp,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000953 base::BindOnce(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
954 port_index, data));
shaochuane58f9c72016-08-30 22:27:08 -0700955}
956
957void MidiManagerWinrt::InitializeOnComThread() {
958 base::AutoLock auto_lock(lazy_init_member_lock_);
959
960 com_thread_checker_.reset(new base::ThreadChecker);
Sigurður Ásgeirsson0691d702017-09-29 14:43:09 +0000961
962 if (!GetCombaseFunctions()->LoadFunctions()) {
963 VLOG(1) << "Failed loading functions from combase.dll: "
964 << PrintHr(HRESULT_FROM_WIN32(GetLastError()));
shaochuan9ff63b82016-09-01 01:58:44 -0700965 CompleteInitialization(Result::INITIALIZATION_ERROR);
966 return;
967 }
968
shaochuane58f9c72016-08-30 22:27:08 -0700969 port_manager_in_.reset(new MidiInPortManager(this));
970 port_manager_out_.reset(new MidiOutPortManager(this));
971
972 scheduler_.reset(new MidiScheduler(this));
973
974 if (!(port_manager_in_->StartWatcher() &&
975 port_manager_out_->StartWatcher())) {
976 port_manager_in_->StopWatcher();
977 port_manager_out_->StopWatcher();
978 CompleteInitialization(Result::INITIALIZATION_ERROR);
979 }
980}
981
982void MidiManagerWinrt::FinalizeOnComThread() {
983 base::AutoLock auto_lock(lazy_init_member_lock_);
984
985 DCHECK(com_thread_checker_->CalledOnValidThread());
986
987 scheduler_.reset();
988
shaochuan9ff63b82016-09-01 01:58:44 -0700989 if (port_manager_in_) {
990 port_manager_in_->StopWatcher();
991 port_manager_in_.reset();
992 }
993
994 if (port_manager_out_) {
995 port_manager_out_->StopWatcher();
996 port_manager_out_.reset();
997 }
shaochuane58f9c72016-08-30 22:27:08 -0700998
999 com_thread_checker_.reset();
1000}
1001
1002void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
1003 const std::vector<uint8_t>& data) {
1004 DCHECK(com_thread_checker_->CalledOnValidThread());
1005
1006 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
1007 if (!(port && port->handle)) {
1008 VLOG(1) << "Port not available: " << port_index;
1009 return;
1010 }
1011
1012 auto buffer_factory =
1013 WrlStaticsFactory<IBufferFactory,
1014 RuntimeClass_Windows_Storage_Streams_Buffer>();
1015 if (!buffer_factory)
1016 return;
1017
1018 ScopedComPtr<IBuffer> buffer;
1019 HRESULT hr = buffer_factory->Create(static_cast<UINT32>(data.size()),
robliao8d08e692017-05-11 10:14:00 -07001020 buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -07001021 if (FAILED(hr)) {
1022 VLOG(1) << "Create failed: " << PrintHr(hr);
1023 return;
1024 }
1025
1026 hr = buffer->put_Length(static_cast<UINT32>(data.size()));
1027 if (FAILED(hr)) {
1028 VLOG(1) << "put_Length failed: " << PrintHr(hr);
1029 return;
1030 }
1031
1032 uint8_t* p_buffer_data = nullptr;
robliao3566d1a2017-04-18 17:28:09 -07001033 hr = GetPointerToBufferData(buffer.Get(), &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -07001034 if (FAILED(hr))
1035 return;
1036
1037 std::copy(data.begin(), data.end(), p_buffer_data);
1038
robliao3566d1a2017-04-18 17:28:09 -07001039 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -07001040 if (FAILED(hr)) {
1041 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
1042 return;
1043 }
1044}
1045
1046void MidiManagerWinrt::OnPortManagerReady() {
1047 DCHECK(com_thread_checker_->CalledOnValidThread());
1048 DCHECK(port_manager_ready_count_ < 2);
1049
1050 if (++port_manager_ready_count_ == 2)
1051 CompleteInitialization(Result::OK);
1052}
1053
shaochuane58f9c72016-08-30 22:27:08 -07001054} // namespace midi