blob: 0348f48415b92e94bc9c5bb6fb0c1c812da539bc [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;
junweifua8cea852017-10-17 06:21:16 +000049using base::win::GetActivationFactory;
toyoshimec2570a2016-10-21 02:15:27 -070050using mojom::PortState;
toyoshim2f3a48f2016-10-17 01:54:13 -070051using mojom::Result;
shaochuane58f9c72016-08-30 22:27:08 -070052
53// Helpers for printing HRESULTs.
54struct PrintHr {
55 PrintHr(HRESULT hr) : hr(hr) {}
56 HRESULT hr;
57};
58
59std::ostream& operator<<(std::ostream& os, const PrintHr& phr) {
60 std::ios_base::fmtflags ff = os.flags();
61 os << _com_error(phr.hr).ErrorMessage() << " (0x" << std::hex
62 << std::uppercase << std::setfill('0') << std::setw(8) << phr.hr << ")";
63 os.flags(ff);
64 return os;
65}
66
shaochuane58f9c72016-08-30 22:27:08 -070067template <typename T>
68std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070069 HSTRING result;
70 HRESULT hr = obj->get_Id(&result);
71 if (FAILED(hr)) {
72 VLOG(1) << "get_Id failed: " << PrintHr(hr);
73 return std::string();
74 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000075 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070076}
77
78template <typename T>
79std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070080 HSTRING result;
81 HRESULT hr = obj->get_DeviceId(&result);
82 if (FAILED(hr)) {
83 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
84 return std::string();
85 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000086 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070087}
88
89std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -070090 HSTRING result;
91 HRESULT hr = info->get_Name(&result);
92 if (FAILED(hr)) {
93 VLOG(1) << "get_Name 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
shaochuan110262b2016-08-31 02:15:16 -070099// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
100// instance.
101bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
junweifua8cea852017-10-17 06:21:16 +0000102 ScopedComPtr<IMidiSynthesizerStatics> midi_synthesizer_statics;
103 HRESULT hr =
104 GetActivationFactory<IMidiSynthesizerStatics,
105 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>(
106 &midi_synthesizer_statics);
107 if (FAILED(hr)) {
108 VLOG(1) << "IMidiSynthesizerStatics factory failed: " << PrintHr(hr);
109 return false;
110 }
shaochuan110262b2016-08-31 02:15:16 -0700111 boolean result = FALSE;
junweifua8cea852017-10-17 06:21:16 +0000112 hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
shaochuan110262b2016-08-31 02:15:16 -0700113 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
114 return result != FALSE;
115}
116
shaochuan4eff30e2016-09-09 01:24:14 -0700117void GetDevPropString(DEVINST handle,
118 const DEVPROPKEY* devprop_key,
119 std::string* out) {
120 DEVPROPTYPE devprop_type;
121 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700122
shaochuan4eff30e2016-09-09 01:24:14 -0700123 // Retrieve |buffer_size| and allocate buffer later for receiving data.
124 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
125 nullptr, &buffer_size, 0);
126 if (cr != CR_BUFFER_SMALL) {
127 // Here we print error codes in hex instead of using PrintHr() with
128 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
129 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
130 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
131 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700132 }
shaochuan4eff30e2016-09-09 01:24:14 -0700133 if (devprop_type != DEVPROP_TYPE_STRING) {
134 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
135 << "expected DEVPROP_TYPE_STRING";
136 return;
137 }
shaochuan17bc4a02016-09-06 01:42:12 -0700138
shaochuan4eff30e2016-09-09 01:24:14 -0700139 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
140
141 // Receive property data.
142 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
143 &buffer_size, 0);
144 if (cr != CR_SUCCESS)
145 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
146 else
147 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
148}
shaochuan17bc4a02016-09-06 01:42:12 -0700149
150// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700151// device driver through PnP Configuration Manager, given device (interface) ID
152// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
153// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700154//
155// Device instance ID is extracted from device (interface) ID provided by WinRT
156// APIs, for example from the following interface ID:
157// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
158// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700159//
160// However the extracted device instance ID represent a "software device"
161// provided by Microsoft, which is an interface on top of the hardware for each
162// input/output port. Therefore we further locate its parent device, which is
163// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700164void GetDriverInfoFromDeviceId(const std::string& dev_id,
165 std::string* out_manufacturer,
166 std::string* out_driver_version) {
167 base::string16 dev_instance_id =
168 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
169 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
170
shaochuan4eff30e2016-09-09 01:24:14 -0700171 DEVINST dev_instance_handle;
172 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
173 CM_LOCATE_DEVNODE_NORMAL);
174 if (cr != CR_SUCCESS) {
175 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700176 return;
177 }
178
shaochuan4eff30e2016-09-09 01:24:14 -0700179 DEVINST parent_handle;
180 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
181 if (cr != CR_SUCCESS) {
182 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700183 return;
184 }
185
shaochuan4eff30e2016-09-09 01:24:14 -0700186 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
187 out_manufacturer);
188 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
189 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700190}
191
shaochuane58f9c72016-08-30 22:27:08 -0700192// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
193const int64_t kInvalidTokenValue = 0;
194
195template <typename InterfaceType>
196struct MidiPort {
197 MidiPort() = default;
198
199 uint32_t index;
200 ScopedComPtr<InterfaceType> handle;
201 EventRegistrationToken token_MessageReceived;
202
203 private:
204 DISALLOW_COPY_AND_ASSIGN(MidiPort);
205};
206
207} // namespace
208
209template <typename InterfaceType,
210 typename RuntimeType,
211 typename StaticsInterfaceType,
212 base::char16 const* runtime_class_id>
213class MidiManagerWinrt::MidiPortManager {
214 public:
215 // MidiPortManager instances should be constructed on the COM thread.
216 MidiPortManager(MidiManagerWinrt* midi_manager)
217 : midi_manager_(midi_manager),
218 task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
219
220 virtual ~MidiPortManager() { DCHECK(thread_checker_.CalledOnValidThread()); }
221
222 bool StartWatcher() {
223 DCHECK(thread_checker_.CalledOnValidThread());
224
junweifua8cea852017-10-17 06:21:16 +0000225 HRESULT hr = GetActivationFactory<StaticsInterfaceType, runtime_class_id>(
226 &midi_port_statics_);
227 if (FAILED(hr)) {
228 VLOG(1) << "StaticsInterfaceType factory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700229 return false;
junweifua8cea852017-10-17 06:21:16 +0000230 }
shaochuane58f9c72016-08-30 22:27:08 -0700231
232 HSTRING device_selector = nullptr;
233 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
234 if (FAILED(hr)) {
235 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
236 return false;
237 }
238
junweifua8cea852017-10-17 06:21:16 +0000239 ScopedComPtr<IDeviceInformationStatics> dev_info_statics;
240 hr = GetActivationFactory<
shaochuane58f9c72016-08-30 22:27:08 -0700241 IDeviceInformationStatics,
junweifua8cea852017-10-17 06:21:16 +0000242 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
243 &dev_info_statics);
244 if (FAILED(hr)) {
245 VLOG(1) << "IDeviceInformationStatics failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700246 return false;
junweifua8cea852017-10-17 06:21:16 +0000247 }
shaochuane58f9c72016-08-30 22:27:08 -0700248
249 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
robliao8d08e692017-05-11 10:14:00 -0700250 watcher_.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700251 if (FAILED(hr)) {
252 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
253 return false;
254 }
255
256 // Register callbacks to WinRT that post state-modifying jobs back to COM
257 // thread. |weak_ptr| and |task_runner| are captured by lambda callbacks for
258 // posting jobs. Note that WinRT callback arguments should not be passed
259 // outside the callback since the pointers may be unavailable afterwards.
260 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
261 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
262
263 hr = watcher_->add_Added(
264 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
265 [weak_ptr, task_runner](IDeviceWatcher* watcher,
266 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700267 if (!info) {
268 VLOG(1) << "DeviceWatcher.Added callback provides null "
269 "pointer, ignoring";
270 return S_OK;
271 }
272
shaochuan110262b2016-08-31 02:15:16 -0700273 // Disable Microsoft GS Wavetable Synth due to security reasons.
274 // http://crbug.com/499279
275 if (IsMicrosoftSynthesizer(info))
276 return S_OK;
277
shaochuane58f9c72016-08-30 22:27:08 -0700278 std::string dev_id = GetIdString(info),
279 dev_name = GetNameString(info);
280
281 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000282 FROM_HERE, base::BindOnce(&MidiPortManager::OnAdded, weak_ptr,
283 dev_id, dev_name));
shaochuane58f9c72016-08-30 22:27:08 -0700284
285 return S_OK;
286 })
287 .Get(),
288 &token_Added_);
289 if (FAILED(hr)) {
290 VLOG(1) << "add_Added failed: " << PrintHr(hr);
291 return false;
292 }
293
294 hr = watcher_->add_EnumerationCompleted(
295 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
296 [weak_ptr, task_runner](IDeviceWatcher* watcher,
297 IInspectable* insp) {
298 task_runner->PostTask(
299 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000300 base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
301 weak_ptr));
shaochuane58f9c72016-08-30 22:27:08 -0700302
303 return S_OK;
304 })
305 .Get(),
306 &token_EnumerationCompleted_);
307 if (FAILED(hr)) {
308 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
309 return false;
310 }
311
312 hr = watcher_->add_Removed(
313 WRL::Callback<
314 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
315 [weak_ptr, task_runner](IDeviceWatcher* watcher,
316 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700317 if (!update) {
318 VLOG(1) << "DeviceWatcher.Removed callback provides null "
319 "pointer, ignoring";
320 return S_OK;
321 }
322
shaochuane58f9c72016-08-30 22:27:08 -0700323 std::string dev_id = GetIdString(update);
324
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000325 task_runner->PostTask(FROM_HERE,
326 base::BindOnce(&MidiPortManager::OnRemoved,
327 weak_ptr, dev_id));
shaochuane58f9c72016-08-30 22:27:08 -0700328
329 return S_OK;
330 })
331 .Get(),
332 &token_Removed_);
333 if (FAILED(hr)) {
334 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
335 return false;
336 }
337
338 hr = watcher_->add_Stopped(
339 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
340 [](IDeviceWatcher* watcher, IInspectable* insp) {
341 // Placeholder, does nothing for now.
342 return S_OK;
343 })
344 .Get(),
345 &token_Stopped_);
346 if (FAILED(hr)) {
347 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
348 return false;
349 }
350
351 hr = watcher_->add_Updated(
352 WRL::Callback<
353 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
354 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
355 // TODO(shaochuan): Check for fields to be updated here.
356 return S_OK;
357 })
358 .Get(),
359 &token_Updated_);
360 if (FAILED(hr)) {
361 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
362 return false;
363 }
364
365 hr = watcher_->Start();
366 if (FAILED(hr)) {
367 VLOG(1) << "Start failed: " << PrintHr(hr);
368 return false;
369 }
370
371 is_initialized_ = true;
372 return true;
373 }
374
375 void StopWatcher() {
376 DCHECK(thread_checker_.CalledOnValidThread());
377
378 HRESULT hr;
379
380 for (const auto& entry : ports_)
381 RemovePortEventHandlers(entry.second.get());
382
383 if (token_Added_.value != kInvalidTokenValue) {
384 hr = watcher_->remove_Added(token_Added_);
385 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
386 token_Added_.value = kInvalidTokenValue;
387 }
388 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
389 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
390 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
391 << PrintHr(hr);
392 token_EnumerationCompleted_.value = kInvalidTokenValue;
393 }
394 if (token_Removed_.value != kInvalidTokenValue) {
395 hr = watcher_->remove_Removed(token_Removed_);
396 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
397 token_Removed_.value = kInvalidTokenValue;
398 }
399 if (token_Stopped_.value != kInvalidTokenValue) {
400 hr = watcher_->remove_Stopped(token_Stopped_);
401 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
402 token_Stopped_.value = kInvalidTokenValue;
403 }
404 if (token_Updated_.value != kInvalidTokenValue) {
405 hr = watcher_->remove_Updated(token_Updated_);
406 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
407 token_Updated_.value = kInvalidTokenValue;
408 }
409
410 if (is_initialized_) {
411 hr = watcher_->Stop();
412 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
413 is_initialized_ = false;
414 }
415 }
416
417 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
418 DCHECK(thread_checker_.CalledOnValidThread());
419 CHECK(is_initialized_);
420
421 auto it = ports_.find(dev_id);
422 if (it == ports_.end())
423 return nullptr;
424 return it->second.get();
425 }
426
427 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
428 DCHECK(thread_checker_.CalledOnValidThread());
429 CHECK(is_initialized_);
430
431 return GetPortByDeviceId(port_ids_[port_index]);
432 }
433
434 protected:
435 // Points to the MidiManagerWinrt instance, which is expected to outlive the
436 // MidiPortManager instance.
437 MidiManagerWinrt* midi_manager_;
438
439 // Task runner of the COM thread.
440 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
441
442 // Ensures all methods are called on the COM thread.
443 base::ThreadChecker thread_checker_;
444
445 private:
446 // DeviceWatcher callbacks:
447 void OnAdded(std::string dev_id, std::string dev_name) {
448 DCHECK(thread_checker_.CalledOnValidThread());
449 CHECK(is_initialized_);
450
shaochuane58f9c72016-08-30 22:27:08 -0700451 port_names_[dev_id] = dev_name;
452
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000453 ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
shaochuan9ff63b82016-09-01 01:58:44 -0700454 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700455 return;
shaochuane58f9c72016-08-30 22:27:08 -0700456
457 IAsyncOperation<RuntimeType*>* async_op;
458
shaochuan9ff63b82016-09-01 01:58:44 -0700459 HRESULT hr =
460 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700461 if (FAILED(hr)) {
462 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
463 return;
464 }
465
466 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
467 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
468
469 hr = async_op->put_Completed(
470 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
471 [weak_ptr, task_runner](IAsyncOperation<RuntimeType*>* async_op,
472 AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700473 // A reference to |async_op| is kept in |async_ops_|, safe to pass
474 // outside.
475 task_runner->PostTask(
476 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000477 base::BindOnce(
478 &MidiPortManager::OnCompletedGetPortFromIdAsync, weak_ptr,
479 async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700480
481 return S_OK;
482 })
483 .Get());
484 if (FAILED(hr)) {
485 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
486 return;
487 }
488
489 // Keep a reference to incompleted |async_op| for releasing later.
490 async_ops_.insert(async_op);
491 }
492
493 void OnEnumerationCompleted() {
494 DCHECK(thread_checker_.CalledOnValidThread());
495 CHECK(is_initialized_);
496
497 if (async_ops_.empty())
498 midi_manager_->OnPortManagerReady();
499 else
500 enumeration_completed_not_ready_ = true;
501 }
502
503 void OnRemoved(std::string dev_id) {
504 DCHECK(thread_checker_.CalledOnValidThread());
505 CHECK(is_initialized_);
506
shaochuan110262b2016-08-31 02:15:16 -0700507 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
508 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700509 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
510 if (!port) {
511 VLOG(1) << "Removing non-existent port " << dev_id;
512 return;
513 }
514
toyoshimec2570a2016-10-21 02:15:27 -0700515 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700516
517 RemovePortEventHandlers(port);
518 port->handle = nullptr;
519 }
520
shaochuanc2894522016-09-20 01:10:50 -0700521 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
shaochuane58f9c72016-08-30 22:27:08 -0700522 DCHECK(thread_checker_.CalledOnValidThread());
523 CHECK(is_initialized_);
524
shaochuanc2894522016-09-20 01:10:50 -0700525 InterfaceType* handle = nullptr;
526 HRESULT hr = async_op->GetResults(&handle);
527 if (FAILED(hr)) {
528 VLOG(1) << "GetResults failed: " << PrintHr(hr);
529 return;
530 }
531
532 // Manually release COM interface to completed |async_op|.
533 auto it = async_ops_.find(async_op);
534 CHECK(it != async_ops_.end());
535 (*it)->Release();
536 async_ops_.erase(it);
537
538 if (!handle) {
539 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
540 "ignoring";
541 return;
542 }
543
shaochuane58f9c72016-08-30 22:27:08 -0700544 EventRegistrationToken token = {kInvalidTokenValue};
545 if (!RegisterOnMessageReceived(handle, &token))
546 return;
547
548 std::string dev_id = GetDeviceIdString(handle);
549
550 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
551
552 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700553 std::string manufacturer = "Unknown", driver_version = "Unknown";
554 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
555
556 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700557 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700558
559 port = new MidiPort<InterfaceType>;
560 port->index = static_cast<uint32_t>(port_ids_.size());
561
562 ports_[dev_id].reset(port);
563 port_ids_.push_back(dev_id);
564 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700565 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700566 }
567
568 port->handle = handle;
569 port->token_MessageReceived = token;
570
shaochuane58f9c72016-08-30 22:27:08 -0700571 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
572 midi_manager_->OnPortManagerReady();
573 enumeration_completed_not_ready_ = false;
574 }
575 }
576
577 // Overrided by MidiInPortManager to listen to input ports.
578 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
579 EventRegistrationToken* p_token) {
580 return true;
581 }
582
583 // Overrided by MidiInPortManager to remove MessageReceived event handler.
584 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
585
586 // Calls midi_manager_->Add{Input,Output}Port.
587 virtual void AddPort(MidiPortInfo info) = 0;
588
589 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700590 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700591
592 // WeakPtrFactory has to be declared in derived class, use this method to
593 // retrieve upcasted WeakPtr for posting tasks.
594 virtual base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() = 0;
595
596 // Midi{In,Out}PortStatics instance.
597 ScopedComPtr<StaticsInterfaceType> midi_port_statics_;
598
599 // DeviceWatcher instance and event registration tokens for unsubscribing
600 // events in destructor.
601 ScopedComPtr<IDeviceWatcher> watcher_;
602 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
603 token_EnumerationCompleted_ = {kInvalidTokenValue},
604 token_Removed_ = {kInvalidTokenValue},
605 token_Stopped_ = {kInvalidTokenValue},
606 token_Updated_ = {kInvalidTokenValue};
607
608 // All manipulations to these fields should be done on COM thread.
609 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
610 ports_;
611 std::vector<std::string> port_ids_;
612 std::unordered_map<std::string, std::string> port_names_;
613
614 // Keeps AsyncOperation references before the operation completes. Note that
615 // raw pointers are used here and the COM interfaces should be released
616 // manually.
617 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
618
619 // Set when device enumeration is completed but OnPortManagerReady() is not
620 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
621 // In such cases, OnPortManagerReady() will be called in
622 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
623 bool enumeration_completed_not_ready_ = false;
624
625 // Set if the instance is initialized without error. Should be checked in all
626 // methods on COM thread except StartWatcher().
627 bool is_initialized_ = false;
628};
629
630class MidiManagerWinrt::MidiInPortManager final
631 : public MidiPortManager<IMidiInPort,
632 MidiInPort,
633 IMidiInPortStatics,
634 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
635 public:
636 MidiInPortManager(MidiManagerWinrt* midi_manager)
637 : MidiPortManager(midi_manager), weak_factory_(this) {}
638
639 private:
640 // MidiPortManager overrides:
641 bool RegisterOnMessageReceived(IMidiInPort* handle,
642 EventRegistrationToken* p_token) override {
643 DCHECK(thread_checker_.CalledOnValidThread());
644
645 base::WeakPtr<MidiInPortManager> weak_ptr = weak_factory_.GetWeakPtr();
646 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
647
648 HRESULT hr = handle->add_MessageReceived(
649 WRL::Callback<
650 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
651 [weak_ptr, task_runner](IMidiInPort* handle,
652 IMidiMessageReceivedEventArgs* args) {
653 const base::TimeTicks now = base::TimeTicks::Now();
654
655 std::string dev_id = GetDeviceIdString(handle);
656
657 ScopedComPtr<IMidiMessage> message;
robliao8d08e692017-05-11 10:14:00 -0700658 HRESULT hr = args->get_Message(message.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700659 if (FAILED(hr)) {
660 VLOG(1) << "get_Message failed: " << PrintHr(hr);
661 return hr;
662 }
663
664 ScopedComPtr<IBuffer> buffer;
robliao8d08e692017-05-11 10:14:00 -0700665 hr = message->get_RawData(buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700666 if (FAILED(hr)) {
667 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
668 return hr;
669 }
670
671 uint8_t* p_buffer_data = nullptr;
junweifua8cea852017-10-17 06:21:16 +0000672 hr = base::win::GetPointerToBufferData(buffer.Get(),
673 &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -0700674 if (FAILED(hr))
675 return hr;
676
677 uint32_t data_length = 0;
678 hr = buffer->get_Length(&data_length);
679 if (FAILED(hr)) {
680 VLOG(1) << "get_Length failed: " << PrintHr(hr);
681 return hr;
682 }
683
684 std::vector<uint8_t> data(p_buffer_data,
685 p_buffer_data + data_length);
686
687 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000688 FROM_HERE,
689 base::BindOnce(&MidiInPortManager::OnMessageReceived,
690 weak_ptr, dev_id, data, now));
shaochuane58f9c72016-08-30 22:27:08 -0700691
692 return S_OK;
693 })
694 .Get(),
695 p_token);
696 if (FAILED(hr)) {
697 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
698 return false;
699 }
700
701 return true;
702 }
703
704 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
705 if (!(port->handle &&
706 port->token_MessageReceived.value != kInvalidTokenValue))
707 return;
708
709 HRESULT hr =
710 port->handle->remove_MessageReceived(port->token_MessageReceived);
711 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
712 port->token_MessageReceived.value = kInvalidTokenValue;
713 }
714
715 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
716
toyoshimec2570a2016-10-21 02:15:27 -0700717 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700718 midi_manager_->SetInputPortState(port_index, state);
719 }
720
721 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
722 DCHECK(thread_checker_.CalledOnValidThread());
723
724 return weak_factory_.GetWeakPtr();
725 }
726
727 // Callback on receiving MIDI input message.
728 void OnMessageReceived(std::string dev_id,
729 std::vector<uint8_t> data,
730 base::TimeTicks time) {
731 DCHECK(thread_checker_.CalledOnValidThread());
732
733 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
734 CHECK(port);
735
736 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
737 }
738
739 // Last member to ensure destructed first.
740 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
741
742 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
743};
744
745class MidiManagerWinrt::MidiOutPortManager final
746 : public MidiPortManager<IMidiOutPort,
747 IMidiOutPort,
748 IMidiOutPortStatics,
749 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
750 public:
751 MidiOutPortManager(MidiManagerWinrt* midi_manager)
752 : MidiPortManager(midi_manager), weak_factory_(this) {}
753
754 private:
755 // MidiPortManager overrides:
756 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
757
toyoshimec2570a2016-10-21 02:15:27 -0700758 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700759 midi_manager_->SetOutputPortState(port_index, state);
760 }
761
762 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
763 DCHECK(thread_checker_.CalledOnValidThread());
764
765 return weak_factory_.GetWeakPtr();
766 }
767
768 // Last member to ensure destructed first.
769 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
770
771 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
772};
773
toyoshimf4d61522017-02-10 02:03:32 -0800774MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
775 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700776
777MidiManagerWinrt::~MidiManagerWinrt() {
778 base::AutoLock auto_lock(lazy_init_member_lock_);
779
780 CHECK(!com_thread_checker_);
781 CHECK(!port_manager_in_);
782 CHECK(!port_manager_out_);
783 CHECK(!scheduler_);
784}
785
786void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700787 com_thread_.init_com_with_mta(true);
788 com_thread_.Start();
789
790 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000791 FROM_HERE, base::BindOnce(&MidiManagerWinrt::InitializeOnComThread,
792 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700793}
794
795void MidiManagerWinrt::Finalize() {
796 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000797 FROM_HERE, base::BindOnce(&MidiManagerWinrt::FinalizeOnComThread,
798 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700799
800 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
801 // will be ignored.
802 com_thread_.Stop();
803}
804
805void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
806 uint32_t port_index,
807 const std::vector<uint8_t>& data,
808 double timestamp) {
809 CHECK(scheduler_);
810
811 scheduler_->PostSendDataTask(
812 client, data.size(), timestamp,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000813 base::BindOnce(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
814 port_index, data));
shaochuane58f9c72016-08-30 22:27:08 -0700815}
816
817void MidiManagerWinrt::InitializeOnComThread() {
818 base::AutoLock auto_lock(lazy_init_member_lock_);
819
820 com_thread_checker_.reset(new base::ThreadChecker);
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000821 bool preload_success = base::win::ResolveCoreWinRTDelayload() &&
822 ScopedHString::ResolveCoreWinRTStringDelayload();
823 if (!preload_success) {
shaochuan9ff63b82016-09-01 01:58:44 -0700824 CompleteInitialization(Result::INITIALIZATION_ERROR);
825 return;
826 }
827
shaochuane58f9c72016-08-30 22:27:08 -0700828 port_manager_in_.reset(new MidiInPortManager(this));
829 port_manager_out_.reset(new MidiOutPortManager(this));
830
831 scheduler_.reset(new MidiScheduler(this));
832
833 if (!(port_manager_in_->StartWatcher() &&
834 port_manager_out_->StartWatcher())) {
835 port_manager_in_->StopWatcher();
836 port_manager_out_->StopWatcher();
837 CompleteInitialization(Result::INITIALIZATION_ERROR);
838 }
839}
840
841void MidiManagerWinrt::FinalizeOnComThread() {
842 base::AutoLock auto_lock(lazy_init_member_lock_);
843
844 DCHECK(com_thread_checker_->CalledOnValidThread());
845
846 scheduler_.reset();
847
shaochuan9ff63b82016-09-01 01:58:44 -0700848 if (port_manager_in_) {
849 port_manager_in_->StopWatcher();
850 port_manager_in_.reset();
851 }
852
853 if (port_manager_out_) {
854 port_manager_out_->StopWatcher();
855 port_manager_out_.reset();
856 }
shaochuane58f9c72016-08-30 22:27:08 -0700857
858 com_thread_checker_.reset();
859}
860
861void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
862 const std::vector<uint8_t>& data) {
863 DCHECK(com_thread_checker_->CalledOnValidThread());
864
865 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
866 if (!(port && port->handle)) {
867 VLOG(1) << "Port not available: " << port_index;
868 return;
869 }
870
shaochuane58f9c72016-08-30 22:27:08 -0700871 ScopedComPtr<IBuffer> buffer;
junweifua8cea852017-10-17 06:21:16 +0000872 HRESULT hr = base::win::CreateIBufferFromData(
873 data.data(), static_cast<UINT32>(data.size()), buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700874 if (FAILED(hr)) {
junweifua8cea852017-10-17 06:21:16 +0000875 VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700876 return;
877 }
878
robliao3566d1a2017-04-18 17:28:09 -0700879 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -0700880 if (FAILED(hr)) {
881 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
882 return;
883 }
884}
885
886void MidiManagerWinrt::OnPortManagerReady() {
887 DCHECK(com_thread_checker_->CalledOnValidThread());
888 DCHECK(port_manager_ready_count_ < 2);
889
890 if (++port_manager_ready_count_ == 2)
891 CompleteInitialization(Result::OK);
892}
893
shaochuane58f9c72016-08-30 22:27:08 -0700894} // namespace midi