blob: b612ff893818f7b1d84ee7298c1736c1460648ed [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"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000032#include "base/win/core_winrt_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070033#include "base/win/scoped_comptr.h"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000034#include "base/win/scoped_hstring.h"
shaochuane58f9c72016-08-30 22:27:08 -070035#include "media/midi/midi_scheduler.h"
36
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
47using base::win::ScopedComPtr;
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000048using base::win::ScopedHString;
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
52// Helpers for printing HRESULTs.
53struct PrintHr {
54 PrintHr(HRESULT hr) : hr(hr) {}
55 HRESULT hr;
56};
57
58std::ostream& operator<<(std::ostream& os, const PrintHr& phr) {
59 std::ios_base::fmtflags ff = os.flags();
60 os << _com_error(phr.hr).ErrorMessage() << " (0x" << std::hex
61 << std::uppercase << std::setfill('0') << std::setw(8) << phr.hr << ")";
62 os.flags(ff);
63 return os;
64}
65
66// Factory functions that activate and create WinRT components. The caller takes
67// ownership of the returning ComPtr.
68template <typename InterfaceType, base::char16 const* runtime_class_id>
69ScopedComPtr<InterfaceType> WrlStaticsFactory() {
70 ScopedComPtr<InterfaceType> com_ptr;
71
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000072 ScopedHString class_id_hstring = ScopedHString::Create(runtime_class_id);
shaochuan9ff63b82016-09-01 01:58:44 -070073 if (!class_id_hstring.is_valid()) {
74 com_ptr = nullptr;
75 return com_ptr;
76 }
77
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000078 HRESULT hr = base::win::RoGetActivationFactory(class_id_hstring.get(),
79 IID_PPV_ARGS(&com_ptr));
shaochuane58f9c72016-08-30 22:27:08 -070080 if (FAILED(hr)) {
shaochuan9ff63b82016-09-01 01:58:44 -070081 VLOG(1) << "RoGetActivationFactory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -070082 com_ptr = nullptr;
83 }
84
85 return com_ptr;
86}
87
shaochuane58f9c72016-08-30 22:27:08 -070088template <typename T>
89std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070090 HSTRING result;
91 HRESULT hr = obj->get_Id(&result);
92 if (FAILED(hr)) {
93 VLOG(1) << "get_Id failed: " << PrintHr(hr);
94 return std::string();
95 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000096 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070097}
98
99template <typename T>
100std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -0700101 HSTRING result;
102 HRESULT hr = obj->get_DeviceId(&result);
103 if (FAILED(hr)) {
104 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
105 return std::string();
106 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000107 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -0700108}
109
110std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -0700111 HSTRING result;
112 HRESULT hr = info->get_Name(&result);
113 if (FAILED(hr)) {
114 VLOG(1) << "get_Name failed: " << PrintHr(hr);
115 return std::string();
116 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000117 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -0700118}
119
120HRESULT GetPointerToBufferData(IBuffer* buffer, uint8_t** out) {
121 ScopedComPtr<Windows::Storage::Streams::IBufferByteAccess> buffer_byte_access;
122
robliao026fda22017-05-17 12:10:54 -0700123 HRESULT hr = buffer->QueryInterface(IID_PPV_ARGS(&buffer_byte_access));
shaochuane58f9c72016-08-30 22:27:08 -0700124 if (FAILED(hr)) {
125 VLOG(1) << "QueryInterface failed: " << PrintHr(hr);
126 return hr;
127 }
128
129 // Lifetime of the pointing buffer is controlled by the buffer object.
130 hr = buffer_byte_access->Buffer(out);
131 if (FAILED(hr)) {
132 VLOG(1) << "Buffer failed: " << PrintHr(hr);
133 return hr;
134 }
135
136 return S_OK;
137}
138
shaochuan110262b2016-08-31 02:15:16 -0700139// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
140// instance.
141bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
142 auto midi_synthesizer_statics =
143 WrlStaticsFactory<IMidiSynthesizerStatics,
144 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>();
145 boolean result = FALSE;
146 HRESULT hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
147 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
148 return result != FALSE;
149}
150
shaochuan4eff30e2016-09-09 01:24:14 -0700151void GetDevPropString(DEVINST handle,
152 const DEVPROPKEY* devprop_key,
153 std::string* out) {
154 DEVPROPTYPE devprop_type;
155 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700156
shaochuan4eff30e2016-09-09 01:24:14 -0700157 // Retrieve |buffer_size| and allocate buffer later for receiving data.
158 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
159 nullptr, &buffer_size, 0);
160 if (cr != CR_BUFFER_SMALL) {
161 // Here we print error codes in hex instead of using PrintHr() with
162 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
163 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
164 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
165 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700166 }
shaochuan4eff30e2016-09-09 01:24:14 -0700167 if (devprop_type != DEVPROP_TYPE_STRING) {
168 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
169 << "expected DEVPROP_TYPE_STRING";
170 return;
171 }
shaochuan17bc4a02016-09-06 01:42:12 -0700172
shaochuan4eff30e2016-09-09 01:24:14 -0700173 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
174
175 // Receive property data.
176 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
177 &buffer_size, 0);
178 if (cr != CR_SUCCESS)
179 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
180 else
181 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
182}
shaochuan17bc4a02016-09-06 01:42:12 -0700183
184// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700185// device driver through PnP Configuration Manager, given device (interface) ID
186// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
187// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700188//
189// Device instance ID is extracted from device (interface) ID provided by WinRT
190// APIs, for example from the following interface ID:
191// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
192// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700193//
194// However the extracted device instance ID represent a "software device"
195// provided by Microsoft, which is an interface on top of the hardware for each
196// input/output port. Therefore we further locate its parent device, which is
197// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700198void GetDriverInfoFromDeviceId(const std::string& dev_id,
199 std::string* out_manufacturer,
200 std::string* out_driver_version) {
201 base::string16 dev_instance_id =
202 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
203 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
204
shaochuan4eff30e2016-09-09 01:24:14 -0700205 DEVINST dev_instance_handle;
206 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
207 CM_LOCATE_DEVNODE_NORMAL);
208 if (cr != CR_SUCCESS) {
209 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700210 return;
211 }
212
shaochuan4eff30e2016-09-09 01:24:14 -0700213 DEVINST parent_handle;
214 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
215 if (cr != CR_SUCCESS) {
216 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700217 return;
218 }
219
shaochuan4eff30e2016-09-09 01:24:14 -0700220 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
221 out_manufacturer);
222 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
223 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700224}
225
shaochuane58f9c72016-08-30 22:27:08 -0700226// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
227const int64_t kInvalidTokenValue = 0;
228
229template <typename InterfaceType>
230struct MidiPort {
231 MidiPort() = default;
232
233 uint32_t index;
234 ScopedComPtr<InterfaceType> handle;
235 EventRegistrationToken token_MessageReceived;
236
237 private:
238 DISALLOW_COPY_AND_ASSIGN(MidiPort);
239};
240
241} // namespace
242
243template <typename InterfaceType,
244 typename RuntimeType,
245 typename StaticsInterfaceType,
246 base::char16 const* runtime_class_id>
247class MidiManagerWinrt::MidiPortManager {
248 public:
249 // MidiPortManager instances should be constructed on the COM thread.
250 MidiPortManager(MidiManagerWinrt* midi_manager)
251 : midi_manager_(midi_manager),
252 task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
253
254 virtual ~MidiPortManager() { DCHECK(thread_checker_.CalledOnValidThread()); }
255
256 bool StartWatcher() {
257 DCHECK(thread_checker_.CalledOnValidThread());
258
259 HRESULT hr;
260
261 midi_port_statics_ =
262 WrlStaticsFactory<StaticsInterfaceType, runtime_class_id>();
263 if (!midi_port_statics_)
264 return false;
265
266 HSTRING device_selector = nullptr;
267 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
268 if (FAILED(hr)) {
269 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
270 return false;
271 }
272
273 auto dev_info_statics = WrlStaticsFactory<
274 IDeviceInformationStatics,
275 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>();
276 if (!dev_info_statics)
277 return false;
278
279 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
robliao8d08e692017-05-11 10:14:00 -0700280 watcher_.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700281 if (FAILED(hr)) {
282 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
283 return false;
284 }
285
286 // Register callbacks to WinRT that post state-modifying jobs back to COM
287 // thread. |weak_ptr| and |task_runner| are captured by lambda callbacks for
288 // posting jobs. Note that WinRT callback arguments should not be passed
289 // outside the callback since the pointers may be unavailable afterwards.
290 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
291 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
292
293 hr = watcher_->add_Added(
294 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
295 [weak_ptr, task_runner](IDeviceWatcher* watcher,
296 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700297 if (!info) {
298 VLOG(1) << "DeviceWatcher.Added callback provides null "
299 "pointer, ignoring";
300 return S_OK;
301 }
302
shaochuan110262b2016-08-31 02:15:16 -0700303 // Disable Microsoft GS Wavetable Synth due to security reasons.
304 // http://crbug.com/499279
305 if (IsMicrosoftSynthesizer(info))
306 return S_OK;
307
shaochuane58f9c72016-08-30 22:27:08 -0700308 std::string dev_id = GetIdString(info),
309 dev_name = GetNameString(info);
310
311 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000312 FROM_HERE, base::BindOnce(&MidiPortManager::OnAdded, weak_ptr,
313 dev_id, dev_name));
shaochuane58f9c72016-08-30 22:27:08 -0700314
315 return S_OK;
316 })
317 .Get(),
318 &token_Added_);
319 if (FAILED(hr)) {
320 VLOG(1) << "add_Added failed: " << PrintHr(hr);
321 return false;
322 }
323
324 hr = watcher_->add_EnumerationCompleted(
325 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
326 [weak_ptr, task_runner](IDeviceWatcher* watcher,
327 IInspectable* insp) {
328 task_runner->PostTask(
329 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000330 base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
331 weak_ptr));
shaochuane58f9c72016-08-30 22:27:08 -0700332
333 return S_OK;
334 })
335 .Get(),
336 &token_EnumerationCompleted_);
337 if (FAILED(hr)) {
338 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
339 return false;
340 }
341
342 hr = watcher_->add_Removed(
343 WRL::Callback<
344 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
345 [weak_ptr, task_runner](IDeviceWatcher* watcher,
346 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700347 if (!update) {
348 VLOG(1) << "DeviceWatcher.Removed callback provides null "
349 "pointer, ignoring";
350 return S_OK;
351 }
352
shaochuane58f9c72016-08-30 22:27:08 -0700353 std::string dev_id = GetIdString(update);
354
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000355 task_runner->PostTask(FROM_HERE,
356 base::BindOnce(&MidiPortManager::OnRemoved,
357 weak_ptr, dev_id));
shaochuane58f9c72016-08-30 22:27:08 -0700358
359 return S_OK;
360 })
361 .Get(),
362 &token_Removed_);
363 if (FAILED(hr)) {
364 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
365 return false;
366 }
367
368 hr = watcher_->add_Stopped(
369 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
370 [](IDeviceWatcher* watcher, IInspectable* insp) {
371 // Placeholder, does nothing for now.
372 return S_OK;
373 })
374 .Get(),
375 &token_Stopped_);
376 if (FAILED(hr)) {
377 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
378 return false;
379 }
380
381 hr = watcher_->add_Updated(
382 WRL::Callback<
383 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
384 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
385 // TODO(shaochuan): Check for fields to be updated here.
386 return S_OK;
387 })
388 .Get(),
389 &token_Updated_);
390 if (FAILED(hr)) {
391 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
392 return false;
393 }
394
395 hr = watcher_->Start();
396 if (FAILED(hr)) {
397 VLOG(1) << "Start failed: " << PrintHr(hr);
398 return false;
399 }
400
401 is_initialized_ = true;
402 return true;
403 }
404
405 void StopWatcher() {
406 DCHECK(thread_checker_.CalledOnValidThread());
407
408 HRESULT hr;
409
410 for (const auto& entry : ports_)
411 RemovePortEventHandlers(entry.second.get());
412
413 if (token_Added_.value != kInvalidTokenValue) {
414 hr = watcher_->remove_Added(token_Added_);
415 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
416 token_Added_.value = kInvalidTokenValue;
417 }
418 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
419 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
420 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
421 << PrintHr(hr);
422 token_EnumerationCompleted_.value = kInvalidTokenValue;
423 }
424 if (token_Removed_.value != kInvalidTokenValue) {
425 hr = watcher_->remove_Removed(token_Removed_);
426 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
427 token_Removed_.value = kInvalidTokenValue;
428 }
429 if (token_Stopped_.value != kInvalidTokenValue) {
430 hr = watcher_->remove_Stopped(token_Stopped_);
431 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
432 token_Stopped_.value = kInvalidTokenValue;
433 }
434 if (token_Updated_.value != kInvalidTokenValue) {
435 hr = watcher_->remove_Updated(token_Updated_);
436 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
437 token_Updated_.value = kInvalidTokenValue;
438 }
439
440 if (is_initialized_) {
441 hr = watcher_->Stop();
442 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
443 is_initialized_ = false;
444 }
445 }
446
447 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
448 DCHECK(thread_checker_.CalledOnValidThread());
449 CHECK(is_initialized_);
450
451 auto it = ports_.find(dev_id);
452 if (it == ports_.end())
453 return nullptr;
454 return it->second.get();
455 }
456
457 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
458 DCHECK(thread_checker_.CalledOnValidThread());
459 CHECK(is_initialized_);
460
461 return GetPortByDeviceId(port_ids_[port_index]);
462 }
463
464 protected:
465 // Points to the MidiManagerWinrt instance, which is expected to outlive the
466 // MidiPortManager instance.
467 MidiManagerWinrt* midi_manager_;
468
469 // Task runner of the COM thread.
470 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
471
472 // Ensures all methods are called on the COM thread.
473 base::ThreadChecker thread_checker_;
474
475 private:
476 // DeviceWatcher callbacks:
477 void OnAdded(std::string dev_id, std::string dev_name) {
478 DCHECK(thread_checker_.CalledOnValidThread());
479 CHECK(is_initialized_);
480
shaochuane58f9c72016-08-30 22:27:08 -0700481 port_names_[dev_id] = dev_name;
482
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000483 ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
shaochuan9ff63b82016-09-01 01:58:44 -0700484 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700485 return;
shaochuane58f9c72016-08-30 22:27:08 -0700486
487 IAsyncOperation<RuntimeType*>* async_op;
488
shaochuan9ff63b82016-09-01 01:58:44 -0700489 HRESULT hr =
490 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700491 if (FAILED(hr)) {
492 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
493 return;
494 }
495
496 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
497 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
498
499 hr = async_op->put_Completed(
500 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
501 [weak_ptr, task_runner](IAsyncOperation<RuntimeType*>* async_op,
502 AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700503 // A reference to |async_op| is kept in |async_ops_|, safe to pass
504 // outside.
505 task_runner->PostTask(
506 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000507 base::BindOnce(
508 &MidiPortManager::OnCompletedGetPortFromIdAsync, weak_ptr,
509 async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700510
511 return S_OK;
512 })
513 .Get());
514 if (FAILED(hr)) {
515 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
516 return;
517 }
518
519 // Keep a reference to incompleted |async_op| for releasing later.
520 async_ops_.insert(async_op);
521 }
522
523 void OnEnumerationCompleted() {
524 DCHECK(thread_checker_.CalledOnValidThread());
525 CHECK(is_initialized_);
526
527 if (async_ops_.empty())
528 midi_manager_->OnPortManagerReady();
529 else
530 enumeration_completed_not_ready_ = true;
531 }
532
533 void OnRemoved(std::string dev_id) {
534 DCHECK(thread_checker_.CalledOnValidThread());
535 CHECK(is_initialized_);
536
shaochuan110262b2016-08-31 02:15:16 -0700537 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
538 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700539 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
540 if (!port) {
541 VLOG(1) << "Removing non-existent port " << dev_id;
542 return;
543 }
544
toyoshimec2570a2016-10-21 02:15:27 -0700545 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700546
547 RemovePortEventHandlers(port);
548 port->handle = nullptr;
549 }
550
shaochuanc2894522016-09-20 01:10:50 -0700551 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
shaochuane58f9c72016-08-30 22:27:08 -0700552 DCHECK(thread_checker_.CalledOnValidThread());
553 CHECK(is_initialized_);
554
shaochuanc2894522016-09-20 01:10:50 -0700555 InterfaceType* handle = nullptr;
556 HRESULT hr = async_op->GetResults(&handle);
557 if (FAILED(hr)) {
558 VLOG(1) << "GetResults failed: " << PrintHr(hr);
559 return;
560 }
561
562 // Manually release COM interface to completed |async_op|.
563 auto it = async_ops_.find(async_op);
564 CHECK(it != async_ops_.end());
565 (*it)->Release();
566 async_ops_.erase(it);
567
568 if (!handle) {
569 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
570 "ignoring";
571 return;
572 }
573
shaochuane58f9c72016-08-30 22:27:08 -0700574 EventRegistrationToken token = {kInvalidTokenValue};
575 if (!RegisterOnMessageReceived(handle, &token))
576 return;
577
578 std::string dev_id = GetDeviceIdString(handle);
579
580 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
581
582 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700583 std::string manufacturer = "Unknown", driver_version = "Unknown";
584 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
585
586 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700587 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700588
589 port = new MidiPort<InterfaceType>;
590 port->index = static_cast<uint32_t>(port_ids_.size());
591
592 ports_[dev_id].reset(port);
593 port_ids_.push_back(dev_id);
594 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700595 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700596 }
597
598 port->handle = handle;
599 port->token_MessageReceived = token;
600
shaochuane58f9c72016-08-30 22:27:08 -0700601 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
602 midi_manager_->OnPortManagerReady();
603 enumeration_completed_not_ready_ = false;
604 }
605 }
606
607 // Overrided by MidiInPortManager to listen to input ports.
608 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
609 EventRegistrationToken* p_token) {
610 return true;
611 }
612
613 // Overrided by MidiInPortManager to remove MessageReceived event handler.
614 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
615
616 // Calls midi_manager_->Add{Input,Output}Port.
617 virtual void AddPort(MidiPortInfo info) = 0;
618
619 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700620 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700621
622 // WeakPtrFactory has to be declared in derived class, use this method to
623 // retrieve upcasted WeakPtr for posting tasks.
624 virtual base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() = 0;
625
626 // Midi{In,Out}PortStatics instance.
627 ScopedComPtr<StaticsInterfaceType> midi_port_statics_;
628
629 // DeviceWatcher instance and event registration tokens for unsubscribing
630 // events in destructor.
631 ScopedComPtr<IDeviceWatcher> watcher_;
632 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
633 token_EnumerationCompleted_ = {kInvalidTokenValue},
634 token_Removed_ = {kInvalidTokenValue},
635 token_Stopped_ = {kInvalidTokenValue},
636 token_Updated_ = {kInvalidTokenValue};
637
638 // All manipulations to these fields should be done on COM thread.
639 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
640 ports_;
641 std::vector<std::string> port_ids_;
642 std::unordered_map<std::string, std::string> port_names_;
643
644 // Keeps AsyncOperation references before the operation completes. Note that
645 // raw pointers are used here and the COM interfaces should be released
646 // manually.
647 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
648
649 // Set when device enumeration is completed but OnPortManagerReady() is not
650 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
651 // In such cases, OnPortManagerReady() will be called in
652 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
653 bool enumeration_completed_not_ready_ = false;
654
655 // Set if the instance is initialized without error. Should be checked in all
656 // methods on COM thread except StartWatcher().
657 bool is_initialized_ = false;
658};
659
660class MidiManagerWinrt::MidiInPortManager final
661 : public MidiPortManager<IMidiInPort,
662 MidiInPort,
663 IMidiInPortStatics,
664 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
665 public:
666 MidiInPortManager(MidiManagerWinrt* midi_manager)
667 : MidiPortManager(midi_manager), weak_factory_(this) {}
668
669 private:
670 // MidiPortManager overrides:
671 bool RegisterOnMessageReceived(IMidiInPort* handle,
672 EventRegistrationToken* p_token) override {
673 DCHECK(thread_checker_.CalledOnValidThread());
674
675 base::WeakPtr<MidiInPortManager> weak_ptr = weak_factory_.GetWeakPtr();
676 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
677
678 HRESULT hr = handle->add_MessageReceived(
679 WRL::Callback<
680 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
681 [weak_ptr, task_runner](IMidiInPort* handle,
682 IMidiMessageReceivedEventArgs* args) {
683 const base::TimeTicks now = base::TimeTicks::Now();
684
685 std::string dev_id = GetDeviceIdString(handle);
686
687 ScopedComPtr<IMidiMessage> message;
robliao8d08e692017-05-11 10:14:00 -0700688 HRESULT hr = args->get_Message(message.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700689 if (FAILED(hr)) {
690 VLOG(1) << "get_Message failed: " << PrintHr(hr);
691 return hr;
692 }
693
694 ScopedComPtr<IBuffer> buffer;
robliao8d08e692017-05-11 10:14:00 -0700695 hr = message->get_RawData(buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700696 if (FAILED(hr)) {
697 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
698 return hr;
699 }
700
701 uint8_t* p_buffer_data = nullptr;
robliao3566d1a2017-04-18 17:28:09 -0700702 hr = GetPointerToBufferData(buffer.Get(), &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -0700703 if (FAILED(hr))
704 return hr;
705
706 uint32_t data_length = 0;
707 hr = buffer->get_Length(&data_length);
708 if (FAILED(hr)) {
709 VLOG(1) << "get_Length failed: " << PrintHr(hr);
710 return hr;
711 }
712
713 std::vector<uint8_t> data(p_buffer_data,
714 p_buffer_data + data_length);
715
716 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000717 FROM_HERE,
718 base::BindOnce(&MidiInPortManager::OnMessageReceived,
719 weak_ptr, dev_id, data, now));
shaochuane58f9c72016-08-30 22:27:08 -0700720
721 return S_OK;
722 })
723 .Get(),
724 p_token);
725 if (FAILED(hr)) {
726 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
727 return false;
728 }
729
730 return true;
731 }
732
733 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
734 if (!(port->handle &&
735 port->token_MessageReceived.value != kInvalidTokenValue))
736 return;
737
738 HRESULT hr =
739 port->handle->remove_MessageReceived(port->token_MessageReceived);
740 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
741 port->token_MessageReceived.value = kInvalidTokenValue;
742 }
743
744 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(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_->SetInputPortState(port_index, state);
748 }
749
750 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
751 DCHECK(thread_checker_.CalledOnValidThread());
752
753 return weak_factory_.GetWeakPtr();
754 }
755
756 // Callback on receiving MIDI input message.
757 void OnMessageReceived(std::string dev_id,
758 std::vector<uint8_t> data,
759 base::TimeTicks time) {
760 DCHECK(thread_checker_.CalledOnValidThread());
761
762 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
763 CHECK(port);
764
765 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
766 }
767
768 // Last member to ensure destructed first.
769 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
770
771 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
772};
773
774class MidiManagerWinrt::MidiOutPortManager final
775 : public MidiPortManager<IMidiOutPort,
776 IMidiOutPort,
777 IMidiOutPortStatics,
778 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
779 public:
780 MidiOutPortManager(MidiManagerWinrt* midi_manager)
781 : MidiPortManager(midi_manager), weak_factory_(this) {}
782
783 private:
784 // MidiPortManager overrides:
785 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
786
toyoshimec2570a2016-10-21 02:15:27 -0700787 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700788 midi_manager_->SetOutputPortState(port_index, state);
789 }
790
791 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
792 DCHECK(thread_checker_.CalledOnValidThread());
793
794 return weak_factory_.GetWeakPtr();
795 }
796
797 // Last member to ensure destructed first.
798 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
799
800 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
801};
802
toyoshimf4d61522017-02-10 02:03:32 -0800803MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
804 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700805
806MidiManagerWinrt::~MidiManagerWinrt() {
807 base::AutoLock auto_lock(lazy_init_member_lock_);
808
809 CHECK(!com_thread_checker_);
810 CHECK(!port_manager_in_);
811 CHECK(!port_manager_out_);
812 CHECK(!scheduler_);
813}
814
815void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700816 com_thread_.init_com_with_mta(true);
817 com_thread_.Start();
818
819 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000820 FROM_HERE, base::BindOnce(&MidiManagerWinrt::InitializeOnComThread,
821 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700822}
823
824void MidiManagerWinrt::Finalize() {
825 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000826 FROM_HERE, base::BindOnce(&MidiManagerWinrt::FinalizeOnComThread,
827 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700828
829 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
830 // will be ignored.
831 com_thread_.Stop();
832}
833
834void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
835 uint32_t port_index,
836 const std::vector<uint8_t>& data,
837 double timestamp) {
838 CHECK(scheduler_);
839
840 scheduler_->PostSendDataTask(
841 client, data.size(), timestamp,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000842 base::BindOnce(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
843 port_index, data));
shaochuane58f9c72016-08-30 22:27:08 -0700844}
845
846void MidiManagerWinrt::InitializeOnComThread() {
847 base::AutoLock auto_lock(lazy_init_member_lock_);
848
849 com_thread_checker_.reset(new base::ThreadChecker);
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000850 bool preload_success = base::win::ResolveCoreWinRTDelayload() &&
851 ScopedHString::ResolveCoreWinRTStringDelayload();
852 if (!preload_success) {
shaochuan9ff63b82016-09-01 01:58:44 -0700853 CompleteInitialization(Result::INITIALIZATION_ERROR);
854 return;
855 }
856
shaochuane58f9c72016-08-30 22:27:08 -0700857 port_manager_in_.reset(new MidiInPortManager(this));
858 port_manager_out_.reset(new MidiOutPortManager(this));
859
860 scheduler_.reset(new MidiScheduler(this));
861
862 if (!(port_manager_in_->StartWatcher() &&
863 port_manager_out_->StartWatcher())) {
864 port_manager_in_->StopWatcher();
865 port_manager_out_->StopWatcher();
866 CompleteInitialization(Result::INITIALIZATION_ERROR);
867 }
868}
869
870void MidiManagerWinrt::FinalizeOnComThread() {
871 base::AutoLock auto_lock(lazy_init_member_lock_);
872
873 DCHECK(com_thread_checker_->CalledOnValidThread());
874
875 scheduler_.reset();
876
shaochuan9ff63b82016-09-01 01:58:44 -0700877 if (port_manager_in_) {
878 port_manager_in_->StopWatcher();
879 port_manager_in_.reset();
880 }
881
882 if (port_manager_out_) {
883 port_manager_out_->StopWatcher();
884 port_manager_out_.reset();
885 }
shaochuane58f9c72016-08-30 22:27:08 -0700886
887 com_thread_checker_.reset();
888}
889
890void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
891 const std::vector<uint8_t>& data) {
892 DCHECK(com_thread_checker_->CalledOnValidThread());
893
894 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
895 if (!(port && port->handle)) {
896 VLOG(1) << "Port not available: " << port_index;
897 return;
898 }
899
900 auto buffer_factory =
901 WrlStaticsFactory<IBufferFactory,
902 RuntimeClass_Windows_Storage_Streams_Buffer>();
903 if (!buffer_factory)
904 return;
905
906 ScopedComPtr<IBuffer> buffer;
907 HRESULT hr = buffer_factory->Create(static_cast<UINT32>(data.size()),
robliao8d08e692017-05-11 10:14:00 -0700908 buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700909 if (FAILED(hr)) {
910 VLOG(1) << "Create failed: " << PrintHr(hr);
911 return;
912 }
913
914 hr = buffer->put_Length(static_cast<UINT32>(data.size()));
915 if (FAILED(hr)) {
916 VLOG(1) << "put_Length failed: " << PrintHr(hr);
917 return;
918 }
919
920 uint8_t* p_buffer_data = nullptr;
robliao3566d1a2017-04-18 17:28:09 -0700921 hr = GetPointerToBufferData(buffer.Get(), &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -0700922 if (FAILED(hr))
923 return;
924
925 std::copy(data.begin(), data.end(), p_buffer_data);
926
robliao3566d1a2017-04-18 17:28:09 -0700927 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -0700928 if (FAILED(hr)) {
929 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
930 return;
931 }
932}
933
934void MidiManagerWinrt::OnPortManagerReady() {
935 DCHECK(com_thread_checker_->CalledOnValidThread());
936 DCHECK(port_manager_ready_count_ < 2);
937
938 if (++port_manager_ready_count_ == 2)
939 CompleteInitialization(Result::OK);
940}
941
shaochuane58f9c72016-08-30 22:27:08 -0700942} // namespace midi