blob: 68a9a182bdc9fdfc0e701443233f7235d868e7a2 [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>
shaochuane58f9c72016-08-30 22:27:08 -070014#include <robuffer.h>
15#include <windows.devices.enumeration.h>
16#include <windows.devices.midi.h>
17#include <wrl/event.h>
18
19#include <iomanip>
20#include <unordered_map>
21#include <unordered_set>
22
23#include "base/bind.h"
shaochuan9ff63b82016-09-01 01:58:44 -070024#include "base/lazy_instance.h"
25#include "base/scoped_generic.h"
shaochuan17bc4a02016-09-06 01:42:12 -070026#include "base/strings/string_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070027#include "base/strings/utf_string_conversions.h"
28#include "base/threading/thread_checker.h"
29#include "base/threading/thread_task_runner_handle.h"
30#include "base/timer/timer.h"
31#include "base/win/scoped_comptr.h"
shaochuane58f9c72016-08-30 22:27:08 -070032#include "media/midi/midi_scheduler.h"
33
shaochuane58f9c72016-08-30 22:27:08 -070034namespace midi {
35namespace {
36
37namespace WRL = Microsoft::WRL;
38
39using namespace ABI::Windows::Devices::Enumeration;
40using namespace ABI::Windows::Devices::Midi;
41using namespace ABI::Windows::Foundation;
42using namespace ABI::Windows::Storage::Streams;
43
44using base::win::ScopedComPtr;
toyoshimec2570a2016-10-21 02:15:27 -070045using mojom::PortState;
toyoshim2f3a48f2016-10-17 01:54:13 -070046using mojom::Result;
shaochuane58f9c72016-08-30 22:27:08 -070047
48// Helpers for printing HRESULTs.
49struct PrintHr {
50 PrintHr(HRESULT hr) : hr(hr) {}
51 HRESULT hr;
52};
53
54std::ostream& operator<<(std::ostream& os, const PrintHr& phr) {
55 std::ios_base::fmtflags ff = os.flags();
56 os << _com_error(phr.hr).ErrorMessage() << " (0x" << std::hex
57 << std::uppercase << std::setfill('0') << std::setw(8) << phr.hr << ")";
58 os.flags(ff);
59 return os;
60}
61
shaochuan9ff63b82016-09-01 01:58:44 -070062// Provides access to functions in combase.dll which may not be available on
63// Windows 7. Loads functions dynamically at runtime to prevent library
64// dependencies. Use this class through the global LazyInstance
65// |g_combase_functions|.
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
138base::LazyInstance<CombaseFunctions> g_combase_functions =
139 LAZY_INSTANCE_INITIALIZER;
140
141// Scoped HSTRING class to maintain lifetime of HSTRINGs allocated with
142// WindowsCreateString().
143class ScopedHStringTraits {
144 public:
145 static HSTRING InvalidValue() { return nullptr; }
146
147 static void Free(HSTRING hstr) {
148 g_combase_functions.Get().WindowsDeleteString(hstr);
149 }
150};
151
152class ScopedHString : public base::ScopedGeneric<HSTRING, ScopedHStringTraits> {
153 public:
154 explicit ScopedHString(const base::char16* str) : ScopedGeneric(nullptr) {
155 HSTRING hstr;
156 HRESULT hr = g_combase_functions.Get().WindowsCreateString(
157 str, static_cast<uint32_t>(wcslen(str)), &hstr);
158 if (FAILED(hr))
159 VLOG(1) << "WindowsCreateString failed: " << PrintHr(hr);
160 else
161 reset(hstr);
162 }
163};
164
shaochuane58f9c72016-08-30 22:27:08 -0700165// Factory functions that activate and create WinRT components. The caller takes
166// ownership of the returning ComPtr.
167template <typename InterfaceType, base::char16 const* runtime_class_id>
168ScopedComPtr<InterfaceType> WrlStaticsFactory() {
169 ScopedComPtr<InterfaceType> com_ptr;
170
shaochuan9ff63b82016-09-01 01:58:44 -0700171 ScopedHString class_id_hstring(runtime_class_id);
172 if (!class_id_hstring.is_valid()) {
173 com_ptr = nullptr;
174 return com_ptr;
175 }
176
177 HRESULT hr = g_combase_functions.Get().RoGetActivationFactory(
178 class_id_hstring.get(), __uuidof(InterfaceType), com_ptr.ReceiveVoid());
shaochuane58f9c72016-08-30 22:27:08 -0700179 if (FAILED(hr)) {
shaochuan9ff63b82016-09-01 01:58:44 -0700180 VLOG(1) << "RoGetActivationFactory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700181 com_ptr = nullptr;
182 }
183
184 return com_ptr;
185}
186
shaochuan80f1fba2016-09-01 20:44:51 -0700187std::string HStringToString(HSTRING hstr) {
shaochuane58f9c72016-08-30 22:27:08 -0700188 // Note: empty HSTRINGs are represent as nullptr, and instantiating
189 // std::string with nullptr (in base::WideToUTF8) is undefined behavior.
shaochuan9ff63b82016-09-01 01:58:44 -0700190 const base::char16* buffer =
shaochuan80f1fba2016-09-01 20:44:51 -0700191 g_combase_functions.Get().WindowsGetStringRawBuffer(hstr, nullptr);
shaochuane58f9c72016-08-30 22:27:08 -0700192 if (buffer)
193 return base::WideToUTF8(buffer);
194 return std::string();
195}
196
197template <typename T>
198std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -0700199 HSTRING result;
200 HRESULT hr = obj->get_Id(&result);
201 if (FAILED(hr)) {
202 VLOG(1) << "get_Id failed: " << PrintHr(hr);
203 return std::string();
204 }
205 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700206}
207
208template <typename T>
209std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -0700210 HSTRING result;
211 HRESULT hr = obj->get_DeviceId(&result);
212 if (FAILED(hr)) {
213 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
214 return std::string();
215 }
216 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700217}
218
219std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -0700220 HSTRING result;
221 HRESULT hr = info->get_Name(&result);
222 if (FAILED(hr)) {
223 VLOG(1) << "get_Name failed: " << PrintHr(hr);
224 return std::string();
225 }
226 return HStringToString(result);
shaochuane58f9c72016-08-30 22:27:08 -0700227}
228
229HRESULT GetPointerToBufferData(IBuffer* buffer, uint8_t** out) {
230 ScopedComPtr<Windows::Storage::Streams::IBufferByteAccess> buffer_byte_access;
231
232 HRESULT hr = buffer_byte_access.QueryFrom(buffer);
233 if (FAILED(hr)) {
234 VLOG(1) << "QueryInterface failed: " << PrintHr(hr);
235 return hr;
236 }
237
238 // Lifetime of the pointing buffer is controlled by the buffer object.
239 hr = buffer_byte_access->Buffer(out);
240 if (FAILED(hr)) {
241 VLOG(1) << "Buffer failed: " << PrintHr(hr);
242 return hr;
243 }
244
245 return S_OK;
246}
247
shaochuan110262b2016-08-31 02:15:16 -0700248// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
249// instance.
250bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
251 auto midi_synthesizer_statics =
252 WrlStaticsFactory<IMidiSynthesizerStatics,
253 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>();
254 boolean result = FALSE;
255 HRESULT hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
256 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
257 return result != FALSE;
258}
259
shaochuan4eff30e2016-09-09 01:24:14 -0700260void GetDevPropString(DEVINST handle,
261 const DEVPROPKEY* devprop_key,
262 std::string* out) {
263 DEVPROPTYPE devprop_type;
264 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700265
shaochuan4eff30e2016-09-09 01:24:14 -0700266 // Retrieve |buffer_size| and allocate buffer later for receiving data.
267 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
268 nullptr, &buffer_size, 0);
269 if (cr != CR_BUFFER_SMALL) {
270 // Here we print error codes in hex instead of using PrintHr() with
271 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
272 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
273 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
274 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700275 }
shaochuan4eff30e2016-09-09 01:24:14 -0700276 if (devprop_type != DEVPROP_TYPE_STRING) {
277 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
278 << "expected DEVPROP_TYPE_STRING";
279 return;
280 }
shaochuan17bc4a02016-09-06 01:42:12 -0700281
shaochuan4eff30e2016-09-09 01:24:14 -0700282 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
283
284 // Receive property data.
285 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
286 &buffer_size, 0);
287 if (cr != CR_SUCCESS)
288 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
289 else
290 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
291}
shaochuan17bc4a02016-09-06 01:42:12 -0700292
293// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700294// device driver through PnP Configuration Manager, given device (interface) ID
295// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
296// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700297//
298// Device instance ID is extracted from device (interface) ID provided by WinRT
299// APIs, for example from the following interface ID:
300// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
301// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700302//
303// However the extracted device instance ID represent a "software device"
304// provided by Microsoft, which is an interface on top of the hardware for each
305// input/output port. Therefore we further locate its parent device, which is
306// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700307void GetDriverInfoFromDeviceId(const std::string& dev_id,
308 std::string* out_manufacturer,
309 std::string* out_driver_version) {
310 base::string16 dev_instance_id =
311 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
312 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
313
shaochuan4eff30e2016-09-09 01:24:14 -0700314 DEVINST dev_instance_handle;
315 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
316 CM_LOCATE_DEVNODE_NORMAL);
317 if (cr != CR_SUCCESS) {
318 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700319 return;
320 }
321
shaochuan4eff30e2016-09-09 01:24:14 -0700322 DEVINST parent_handle;
323 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
324 if (cr != CR_SUCCESS) {
325 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700326 return;
327 }
328
shaochuan4eff30e2016-09-09 01:24:14 -0700329 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
330 out_manufacturer);
331 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
332 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700333}
334
shaochuane58f9c72016-08-30 22:27:08 -0700335// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
336const int64_t kInvalidTokenValue = 0;
337
338template <typename InterfaceType>
339struct MidiPort {
340 MidiPort() = default;
341
342 uint32_t index;
343 ScopedComPtr<InterfaceType> handle;
344 EventRegistrationToken token_MessageReceived;
345
346 private:
347 DISALLOW_COPY_AND_ASSIGN(MidiPort);
348};
349
350} // namespace
351
352template <typename InterfaceType,
353 typename RuntimeType,
354 typename StaticsInterfaceType,
355 base::char16 const* runtime_class_id>
356class MidiManagerWinrt::MidiPortManager {
357 public:
358 // MidiPortManager instances should be constructed on the COM thread.
359 MidiPortManager(MidiManagerWinrt* midi_manager)
360 : midi_manager_(midi_manager),
361 task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
362
363 virtual ~MidiPortManager() { DCHECK(thread_checker_.CalledOnValidThread()); }
364
365 bool StartWatcher() {
366 DCHECK(thread_checker_.CalledOnValidThread());
367
368 HRESULT hr;
369
370 midi_port_statics_ =
371 WrlStaticsFactory<StaticsInterfaceType, runtime_class_id>();
372 if (!midi_port_statics_)
373 return false;
374
375 HSTRING device_selector = nullptr;
376 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
377 if (FAILED(hr)) {
378 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
379 return false;
380 }
381
382 auto dev_info_statics = WrlStaticsFactory<
383 IDeviceInformationStatics,
384 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>();
385 if (!dev_info_statics)
386 return false;
387
388 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
389 watcher_.Receive());
390 if (FAILED(hr)) {
391 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
392 return false;
393 }
394
395 // Register callbacks to WinRT that post state-modifying jobs back to COM
396 // thread. |weak_ptr| and |task_runner| are captured by lambda callbacks for
397 // posting jobs. Note that WinRT callback arguments should not be passed
398 // outside the callback since the pointers may be unavailable afterwards.
399 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
400 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
401
402 hr = watcher_->add_Added(
403 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
404 [weak_ptr, task_runner](IDeviceWatcher* watcher,
405 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700406 if (!info) {
407 VLOG(1) << "DeviceWatcher.Added callback provides null "
408 "pointer, ignoring";
409 return S_OK;
410 }
411
shaochuan110262b2016-08-31 02:15:16 -0700412 // Disable Microsoft GS Wavetable Synth due to security reasons.
413 // http://crbug.com/499279
414 if (IsMicrosoftSynthesizer(info))
415 return S_OK;
416
shaochuane58f9c72016-08-30 22:27:08 -0700417 std::string dev_id = GetIdString(info),
418 dev_name = GetNameString(info);
419
420 task_runner->PostTask(
421 FROM_HERE, base::Bind(&MidiPortManager::OnAdded, weak_ptr,
422 dev_id, dev_name));
423
424 return S_OK;
425 })
426 .Get(),
427 &token_Added_);
428 if (FAILED(hr)) {
429 VLOG(1) << "add_Added failed: " << PrintHr(hr);
430 return false;
431 }
432
433 hr = watcher_->add_EnumerationCompleted(
434 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
435 [weak_ptr, task_runner](IDeviceWatcher* watcher,
436 IInspectable* insp) {
437 task_runner->PostTask(
438 FROM_HERE,
439 base::Bind(&MidiPortManager::OnEnumerationCompleted,
440 weak_ptr));
441
442 return S_OK;
443 })
444 .Get(),
445 &token_EnumerationCompleted_);
446 if (FAILED(hr)) {
447 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
448 return false;
449 }
450
451 hr = watcher_->add_Removed(
452 WRL::Callback<
453 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
454 [weak_ptr, task_runner](IDeviceWatcher* watcher,
455 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700456 if (!update) {
457 VLOG(1) << "DeviceWatcher.Removed callback provides null "
458 "pointer, ignoring";
459 return S_OK;
460 }
461
shaochuane58f9c72016-08-30 22:27:08 -0700462 std::string dev_id = GetIdString(update);
463
464 task_runner->PostTask(
465 FROM_HERE,
466 base::Bind(&MidiPortManager::OnRemoved, weak_ptr, dev_id));
467
468 return S_OK;
469 })
470 .Get(),
471 &token_Removed_);
472 if (FAILED(hr)) {
473 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
474 return false;
475 }
476
477 hr = watcher_->add_Stopped(
478 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
479 [](IDeviceWatcher* watcher, IInspectable* insp) {
480 // Placeholder, does nothing for now.
481 return S_OK;
482 })
483 .Get(),
484 &token_Stopped_);
485 if (FAILED(hr)) {
486 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
487 return false;
488 }
489
490 hr = watcher_->add_Updated(
491 WRL::Callback<
492 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
493 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
494 // TODO(shaochuan): Check for fields to be updated here.
495 return S_OK;
496 })
497 .Get(),
498 &token_Updated_);
499 if (FAILED(hr)) {
500 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
501 return false;
502 }
503
504 hr = watcher_->Start();
505 if (FAILED(hr)) {
506 VLOG(1) << "Start failed: " << PrintHr(hr);
507 return false;
508 }
509
510 is_initialized_ = true;
511 return true;
512 }
513
514 void StopWatcher() {
515 DCHECK(thread_checker_.CalledOnValidThread());
516
517 HRESULT hr;
518
519 for (const auto& entry : ports_)
520 RemovePortEventHandlers(entry.second.get());
521
522 if (token_Added_.value != kInvalidTokenValue) {
523 hr = watcher_->remove_Added(token_Added_);
524 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
525 token_Added_.value = kInvalidTokenValue;
526 }
527 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
528 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
529 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
530 << PrintHr(hr);
531 token_EnumerationCompleted_.value = kInvalidTokenValue;
532 }
533 if (token_Removed_.value != kInvalidTokenValue) {
534 hr = watcher_->remove_Removed(token_Removed_);
535 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
536 token_Removed_.value = kInvalidTokenValue;
537 }
538 if (token_Stopped_.value != kInvalidTokenValue) {
539 hr = watcher_->remove_Stopped(token_Stopped_);
540 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
541 token_Stopped_.value = kInvalidTokenValue;
542 }
543 if (token_Updated_.value != kInvalidTokenValue) {
544 hr = watcher_->remove_Updated(token_Updated_);
545 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
546 token_Updated_.value = kInvalidTokenValue;
547 }
548
549 if (is_initialized_) {
550 hr = watcher_->Stop();
551 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
552 is_initialized_ = false;
553 }
554 }
555
556 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
557 DCHECK(thread_checker_.CalledOnValidThread());
558 CHECK(is_initialized_);
559
560 auto it = ports_.find(dev_id);
561 if (it == ports_.end())
562 return nullptr;
563 return it->second.get();
564 }
565
566 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
567 DCHECK(thread_checker_.CalledOnValidThread());
568 CHECK(is_initialized_);
569
570 return GetPortByDeviceId(port_ids_[port_index]);
571 }
572
573 protected:
574 // Points to the MidiManagerWinrt instance, which is expected to outlive the
575 // MidiPortManager instance.
576 MidiManagerWinrt* midi_manager_;
577
578 // Task runner of the COM thread.
579 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
580
581 // Ensures all methods are called on the COM thread.
582 base::ThreadChecker thread_checker_;
583
584 private:
585 // DeviceWatcher callbacks:
586 void OnAdded(std::string dev_id, std::string dev_name) {
587 DCHECK(thread_checker_.CalledOnValidThread());
588 CHECK(is_initialized_);
589
shaochuane58f9c72016-08-30 22:27:08 -0700590 port_names_[dev_id] = dev_name;
591
shaochuan9ff63b82016-09-01 01:58:44 -0700592 ScopedHString dev_id_hstring(base::UTF8ToWide(dev_id).c_str());
593 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700594 return;
shaochuane58f9c72016-08-30 22:27:08 -0700595
596 IAsyncOperation<RuntimeType*>* async_op;
597
shaochuan9ff63b82016-09-01 01:58:44 -0700598 HRESULT hr =
599 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700600 if (FAILED(hr)) {
601 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
602 return;
603 }
604
605 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
606 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
607
608 hr = async_op->put_Completed(
609 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
610 [weak_ptr, task_runner](IAsyncOperation<RuntimeType*>* async_op,
611 AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700612 // A reference to |async_op| is kept in |async_ops_|, safe to pass
613 // outside.
614 task_runner->PostTask(
615 FROM_HERE,
616 base::Bind(&MidiPortManager::OnCompletedGetPortFromIdAsync,
shaochuanc2894522016-09-20 01:10:50 -0700617 weak_ptr, async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700618
619 return S_OK;
620 })
621 .Get());
622 if (FAILED(hr)) {
623 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
624 return;
625 }
626
627 // Keep a reference to incompleted |async_op| for releasing later.
628 async_ops_.insert(async_op);
629 }
630
631 void OnEnumerationCompleted() {
632 DCHECK(thread_checker_.CalledOnValidThread());
633 CHECK(is_initialized_);
634
635 if (async_ops_.empty())
636 midi_manager_->OnPortManagerReady();
637 else
638 enumeration_completed_not_ready_ = true;
639 }
640
641 void OnRemoved(std::string dev_id) {
642 DCHECK(thread_checker_.CalledOnValidThread());
643 CHECK(is_initialized_);
644
shaochuan110262b2016-08-31 02:15:16 -0700645 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
646 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700647 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
648 if (!port) {
649 VLOG(1) << "Removing non-existent port " << dev_id;
650 return;
651 }
652
toyoshimec2570a2016-10-21 02:15:27 -0700653 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700654
655 RemovePortEventHandlers(port);
656 port->handle = nullptr;
657 }
658
shaochuanc2894522016-09-20 01:10:50 -0700659 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
shaochuane58f9c72016-08-30 22:27:08 -0700660 DCHECK(thread_checker_.CalledOnValidThread());
661 CHECK(is_initialized_);
662
shaochuanc2894522016-09-20 01:10:50 -0700663 InterfaceType* handle = nullptr;
664 HRESULT hr = async_op->GetResults(&handle);
665 if (FAILED(hr)) {
666 VLOG(1) << "GetResults failed: " << PrintHr(hr);
667 return;
668 }
669
670 // Manually release COM interface to completed |async_op|.
671 auto it = async_ops_.find(async_op);
672 CHECK(it != async_ops_.end());
673 (*it)->Release();
674 async_ops_.erase(it);
675
676 if (!handle) {
677 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
678 "ignoring";
679 return;
680 }
681
shaochuane58f9c72016-08-30 22:27:08 -0700682 EventRegistrationToken token = {kInvalidTokenValue};
683 if (!RegisterOnMessageReceived(handle, &token))
684 return;
685
686 std::string dev_id = GetDeviceIdString(handle);
687
688 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
689
690 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700691 std::string manufacturer = "Unknown", driver_version = "Unknown";
692 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
693
694 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700695 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700696
697 port = new MidiPort<InterfaceType>;
698 port->index = static_cast<uint32_t>(port_ids_.size());
699
700 ports_[dev_id].reset(port);
701 port_ids_.push_back(dev_id);
702 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700703 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700704 }
705
706 port->handle = handle;
707 port->token_MessageReceived = token;
708
shaochuane58f9c72016-08-30 22:27:08 -0700709 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
710 midi_manager_->OnPortManagerReady();
711 enumeration_completed_not_ready_ = false;
712 }
713 }
714
715 // Overrided by MidiInPortManager to listen to input ports.
716 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
717 EventRegistrationToken* p_token) {
718 return true;
719 }
720
721 // Overrided by MidiInPortManager to remove MessageReceived event handler.
722 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
723
724 // Calls midi_manager_->Add{Input,Output}Port.
725 virtual void AddPort(MidiPortInfo info) = 0;
726
727 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700728 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700729
730 // WeakPtrFactory has to be declared in derived class, use this method to
731 // retrieve upcasted WeakPtr for posting tasks.
732 virtual base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() = 0;
733
734 // Midi{In,Out}PortStatics instance.
735 ScopedComPtr<StaticsInterfaceType> midi_port_statics_;
736
737 // DeviceWatcher instance and event registration tokens for unsubscribing
738 // events in destructor.
739 ScopedComPtr<IDeviceWatcher> watcher_;
740 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
741 token_EnumerationCompleted_ = {kInvalidTokenValue},
742 token_Removed_ = {kInvalidTokenValue},
743 token_Stopped_ = {kInvalidTokenValue},
744 token_Updated_ = {kInvalidTokenValue};
745
746 // All manipulations to these fields should be done on COM thread.
747 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
748 ports_;
749 std::vector<std::string> port_ids_;
750 std::unordered_map<std::string, std::string> port_names_;
751
752 // Keeps AsyncOperation references before the operation completes. Note that
753 // raw pointers are used here and the COM interfaces should be released
754 // manually.
755 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
756
757 // Set when device enumeration is completed but OnPortManagerReady() is not
758 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
759 // In such cases, OnPortManagerReady() will be called in
760 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
761 bool enumeration_completed_not_ready_ = false;
762
763 // Set if the instance is initialized without error. Should be checked in all
764 // methods on COM thread except StartWatcher().
765 bool is_initialized_ = false;
766};
767
768class MidiManagerWinrt::MidiInPortManager final
769 : public MidiPortManager<IMidiInPort,
770 MidiInPort,
771 IMidiInPortStatics,
772 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
773 public:
774 MidiInPortManager(MidiManagerWinrt* midi_manager)
775 : MidiPortManager(midi_manager), weak_factory_(this) {}
776
777 private:
778 // MidiPortManager overrides:
779 bool RegisterOnMessageReceived(IMidiInPort* handle,
780 EventRegistrationToken* p_token) override {
781 DCHECK(thread_checker_.CalledOnValidThread());
782
783 base::WeakPtr<MidiInPortManager> weak_ptr = weak_factory_.GetWeakPtr();
784 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
785
786 HRESULT hr = handle->add_MessageReceived(
787 WRL::Callback<
788 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
789 [weak_ptr, task_runner](IMidiInPort* handle,
790 IMidiMessageReceivedEventArgs* args) {
791 const base::TimeTicks now = base::TimeTicks::Now();
792
793 std::string dev_id = GetDeviceIdString(handle);
794
795 ScopedComPtr<IMidiMessage> message;
796 HRESULT hr = args->get_Message(message.Receive());
797 if (FAILED(hr)) {
798 VLOG(1) << "get_Message failed: " << PrintHr(hr);
799 return hr;
800 }
801
802 ScopedComPtr<IBuffer> buffer;
803 hr = message->get_RawData(buffer.Receive());
804 if (FAILED(hr)) {
805 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
806 return hr;
807 }
808
809 uint8_t* p_buffer_data = nullptr;
810 hr = GetPointerToBufferData(buffer.get(), &p_buffer_data);
811 if (FAILED(hr))
812 return hr;
813
814 uint32_t data_length = 0;
815 hr = buffer->get_Length(&data_length);
816 if (FAILED(hr)) {
817 VLOG(1) << "get_Length failed: " << PrintHr(hr);
818 return hr;
819 }
820
821 std::vector<uint8_t> data(p_buffer_data,
822 p_buffer_data + data_length);
823
824 task_runner->PostTask(
825 FROM_HERE, base::Bind(&MidiInPortManager::OnMessageReceived,
826 weak_ptr, dev_id, data, now));
827
828 return S_OK;
829 })
830 .Get(),
831 p_token);
832 if (FAILED(hr)) {
833 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
834 return false;
835 }
836
837 return true;
838 }
839
840 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
841 if (!(port->handle &&
842 port->token_MessageReceived.value != kInvalidTokenValue))
843 return;
844
845 HRESULT hr =
846 port->handle->remove_MessageReceived(port->token_MessageReceived);
847 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
848 port->token_MessageReceived.value = kInvalidTokenValue;
849 }
850
851 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
852
toyoshimec2570a2016-10-21 02:15:27 -0700853 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700854 midi_manager_->SetInputPortState(port_index, state);
855 }
856
857 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
858 DCHECK(thread_checker_.CalledOnValidThread());
859
860 return weak_factory_.GetWeakPtr();
861 }
862
863 // Callback on receiving MIDI input message.
864 void OnMessageReceived(std::string dev_id,
865 std::vector<uint8_t> data,
866 base::TimeTicks time) {
867 DCHECK(thread_checker_.CalledOnValidThread());
868
869 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
870 CHECK(port);
871
872 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
873 }
874
875 // Last member to ensure destructed first.
876 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
877
878 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
879};
880
881class MidiManagerWinrt::MidiOutPortManager final
882 : public MidiPortManager<IMidiOutPort,
883 IMidiOutPort,
884 IMidiOutPortStatics,
885 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
886 public:
887 MidiOutPortManager(MidiManagerWinrt* midi_manager)
888 : MidiPortManager(midi_manager), weak_factory_(this) {}
889
890 private:
891 // MidiPortManager overrides:
892 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
893
toyoshimec2570a2016-10-21 02:15:27 -0700894 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700895 midi_manager_->SetOutputPortState(port_index, state);
896 }
897
898 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
899 DCHECK(thread_checker_.CalledOnValidThread());
900
901 return weak_factory_.GetWeakPtr();
902 }
903
904 // Last member to ensure destructed first.
905 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
906
907 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
908};
909
toyoshimf4d61522017-02-10 02:03:32 -0800910MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
911 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700912
913MidiManagerWinrt::~MidiManagerWinrt() {
914 base::AutoLock auto_lock(lazy_init_member_lock_);
915
916 CHECK(!com_thread_checker_);
917 CHECK(!port_manager_in_);
918 CHECK(!port_manager_out_);
919 CHECK(!scheduler_);
920}
921
922void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700923 com_thread_.init_com_with_mta(true);
924 com_thread_.Start();
925
926 com_thread_.task_runner()->PostTask(
927 FROM_HERE, base::Bind(&MidiManagerWinrt::InitializeOnComThread,
928 base::Unretained(this)));
929}
930
931void MidiManagerWinrt::Finalize() {
932 com_thread_.task_runner()->PostTask(
933 FROM_HERE, base::Bind(&MidiManagerWinrt::FinalizeOnComThread,
934 base::Unretained(this)));
935
936 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
937 // will be ignored.
938 com_thread_.Stop();
939}
940
941void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
942 uint32_t port_index,
943 const std::vector<uint8_t>& data,
944 double timestamp) {
945 CHECK(scheduler_);
946
947 scheduler_->PostSendDataTask(
948 client, data.size(), timestamp,
949 base::Bind(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
950 port_index, data));
951}
952
953void MidiManagerWinrt::InitializeOnComThread() {
954 base::AutoLock auto_lock(lazy_init_member_lock_);
955
956 com_thread_checker_.reset(new base::ThreadChecker);
957
shaochuan9ff63b82016-09-01 01:58:44 -0700958 if (!g_combase_functions.Get().LoadFunctions()) {
959 VLOG(1) << "Failed loading functions from combase.dll: "
960 << PrintHr(HRESULT_FROM_WIN32(GetLastError()));
961 CompleteInitialization(Result::INITIALIZATION_ERROR);
962 return;
963 }
964
shaochuane58f9c72016-08-30 22:27:08 -0700965 port_manager_in_.reset(new MidiInPortManager(this));
966 port_manager_out_.reset(new MidiOutPortManager(this));
967
968 scheduler_.reset(new MidiScheduler(this));
969
970 if (!(port_manager_in_->StartWatcher() &&
971 port_manager_out_->StartWatcher())) {
972 port_manager_in_->StopWatcher();
973 port_manager_out_->StopWatcher();
974 CompleteInitialization(Result::INITIALIZATION_ERROR);
975 }
976}
977
978void MidiManagerWinrt::FinalizeOnComThread() {
979 base::AutoLock auto_lock(lazy_init_member_lock_);
980
981 DCHECK(com_thread_checker_->CalledOnValidThread());
982
983 scheduler_.reset();
984
shaochuan9ff63b82016-09-01 01:58:44 -0700985 if (port_manager_in_) {
986 port_manager_in_->StopWatcher();
987 port_manager_in_.reset();
988 }
989
990 if (port_manager_out_) {
991 port_manager_out_->StopWatcher();
992 port_manager_out_.reset();
993 }
shaochuane58f9c72016-08-30 22:27:08 -0700994
995 com_thread_checker_.reset();
996}
997
998void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
999 const std::vector<uint8_t>& data) {
1000 DCHECK(com_thread_checker_->CalledOnValidThread());
1001
1002 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
1003 if (!(port && port->handle)) {
1004 VLOG(1) << "Port not available: " << port_index;
1005 return;
1006 }
1007
1008 auto buffer_factory =
1009 WrlStaticsFactory<IBufferFactory,
1010 RuntimeClass_Windows_Storage_Streams_Buffer>();
1011 if (!buffer_factory)
1012 return;
1013
1014 ScopedComPtr<IBuffer> buffer;
1015 HRESULT hr = buffer_factory->Create(static_cast<UINT32>(data.size()),
1016 buffer.Receive());
1017 if (FAILED(hr)) {
1018 VLOG(1) << "Create failed: " << PrintHr(hr);
1019 return;
1020 }
1021
1022 hr = buffer->put_Length(static_cast<UINT32>(data.size()));
1023 if (FAILED(hr)) {
1024 VLOG(1) << "put_Length failed: " << PrintHr(hr);
1025 return;
1026 }
1027
1028 uint8_t* p_buffer_data = nullptr;
1029 hr = GetPointerToBufferData(buffer.get(), &p_buffer_data);
1030 if (FAILED(hr))
1031 return;
1032
1033 std::copy(data.begin(), data.end(), p_buffer_data);
1034
1035 hr = port->handle->SendBuffer(buffer.get());
1036 if (FAILED(hr)) {
1037 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
1038 return;
1039 }
1040}
1041
1042void MidiManagerWinrt::OnPortManagerReady() {
1043 DCHECK(com_thread_checker_->CalledOnValidThread());
1044 DCHECK(port_manager_ready_count_ < 2);
1045
1046 if (++port_manager_ready_count_ == 2)
1047 CompleteInitialization(Result::OK);
1048}
1049
shaochuane58f9c72016-08-30 22:27:08 -07001050} // namespace midi