blob: c6a65d57b86e54773e7ba0326cde6d38b67cc3e1 [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>
Robert Liao4a680c32017-10-18 19:10:01 +000018#include <wrl/client.h>
shaochuane58f9c72016-08-30 22:27:08 -070019#include <wrl/event.h>
20
21#include <iomanip>
22#include <unordered_map>
23#include <unordered_set>
24
25#include "base/bind.h"
shaochuan9ff63b82016-09-01 01:58:44 -070026#include "base/scoped_generic.h"
Gabriel Charette78f94a02017-05-16 14:03:45 -040027#include "base/single_thread_task_runner.h"
shaochuan17bc4a02016-09-06 01:42:12 -070028#include "base/strings/string_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070029#include "base/strings/utf_string_conversions.h"
30#include "base/threading/thread_checker.h"
31#include "base/threading/thread_task_runner_handle.h"
32#include "base/timer/timer.h"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000033#include "base/win/core_winrt_util.h"
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000034#include "base/win/scoped_hstring.h"
junweifuf51c5a02017-11-03 06:37:09 +000035#include "base/win/winrt_storage_util.h"
shaochuane58f9c72016-08-30 22:27:08 -070036#include "media/midi/midi_scheduler.h"
37
shaochuane58f9c72016-08-30 22:27:08 -070038namespace midi {
39namespace {
40
41namespace WRL = Microsoft::WRL;
42
43using namespace ABI::Windows::Devices::Enumeration;
44using namespace ABI::Windows::Devices::Midi;
45using namespace ABI::Windows::Foundation;
46using namespace ABI::Windows::Storage::Streams;
47
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) {
Robert Liao4a680c32017-10-18 19:10:01 +0000102 WRL::ComPtr<IMidiSynthesizerStatics> midi_synthesizer_statics;
junweifua8cea852017-10-17 06:21:16 +0000103 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;
Robert Liao4a680c32017-10-18 19:10:01 +0000200 WRL::ComPtr<InterfaceType> handle;
shaochuane58f9c72016-08-30 22:27:08 -0700201 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
Robert Liao4a680c32017-10-18 19:10:01 +0000239 WRL::ComPtr<IDeviceInformationStatics> dev_info_statics;
junweifua8cea852017-10-17 06:21:16 +0000240 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.
Robert Liao4a680c32017-10-18 19:10:01 +0000597 WRL::ComPtr<StaticsInterfaceType> midi_port_statics_;
shaochuane58f9c72016-08-30 22:27:08 -0700598
599 // DeviceWatcher instance and event registration tokens for unsubscribing
600 // events in destructor.
Robert Liao4a680c32017-10-18 19:10:01 +0000601 WRL::ComPtr<IDeviceWatcher> watcher_;
shaochuane58f9c72016-08-30 22:27:08 -0700602 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
Robert Liao4a680c32017-10-18 19:10:01 +0000657 WRL::ComPtr<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
Robert Liao4a680c32017-10-18 19:10:01 +0000664 WRL::ComPtr<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;
junweifuf51c5a02017-11-03 06:37:09 +0000672 uint32_t data_length = 0;
673 hr = base::win::GetPointerToBufferData(
674 buffer.Get(), &p_buffer_data, &data_length);
shaochuane58f9c72016-08-30 22:27:08 -0700675 if (FAILED(hr))
676 return hr;
677
shaochuane58f9c72016-08-30 22:27:08 -0700678 std::vector<uint8_t> data(p_buffer_data,
679 p_buffer_data + data_length);
680
681 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000682 FROM_HERE,
683 base::BindOnce(&MidiInPortManager::OnMessageReceived,
684 weak_ptr, dev_id, data, now));
shaochuane58f9c72016-08-30 22:27:08 -0700685
686 return S_OK;
687 })
688 .Get(),
689 p_token);
690 if (FAILED(hr)) {
691 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
692 return false;
693 }
694
695 return true;
696 }
697
698 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
699 if (!(port->handle &&
700 port->token_MessageReceived.value != kInvalidTokenValue))
701 return;
702
703 HRESULT hr =
704 port->handle->remove_MessageReceived(port->token_MessageReceived);
705 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
706 port->token_MessageReceived.value = kInvalidTokenValue;
707 }
708
709 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
710
toyoshimec2570a2016-10-21 02:15:27 -0700711 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700712 midi_manager_->SetInputPortState(port_index, state);
713 }
714
715 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
716 DCHECK(thread_checker_.CalledOnValidThread());
717
718 return weak_factory_.GetWeakPtr();
719 }
720
721 // Callback on receiving MIDI input message.
722 void OnMessageReceived(std::string dev_id,
723 std::vector<uint8_t> data,
724 base::TimeTicks time) {
725 DCHECK(thread_checker_.CalledOnValidThread());
726
727 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
728 CHECK(port);
729
730 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
731 }
732
733 // Last member to ensure destructed first.
734 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
735
736 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
737};
738
739class MidiManagerWinrt::MidiOutPortManager final
740 : public MidiPortManager<IMidiOutPort,
741 IMidiOutPort,
742 IMidiOutPortStatics,
743 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
744 public:
745 MidiOutPortManager(MidiManagerWinrt* midi_manager)
746 : MidiPortManager(midi_manager), weak_factory_(this) {}
747
748 private:
749 // MidiPortManager overrides:
750 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
751
toyoshimec2570a2016-10-21 02:15:27 -0700752 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700753 midi_manager_->SetOutputPortState(port_index, state);
754 }
755
756 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
757 DCHECK(thread_checker_.CalledOnValidThread());
758
759 return weak_factory_.GetWeakPtr();
760 }
761
762 // Last member to ensure destructed first.
763 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
764
765 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
766};
767
toyoshimf4d61522017-02-10 02:03:32 -0800768MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
769 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700770
771MidiManagerWinrt::~MidiManagerWinrt() {
772 base::AutoLock auto_lock(lazy_init_member_lock_);
773
774 CHECK(!com_thread_checker_);
775 CHECK(!port_manager_in_);
776 CHECK(!port_manager_out_);
777 CHECK(!scheduler_);
778}
779
780void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700781 com_thread_.init_com_with_mta(true);
782 com_thread_.Start();
783
784 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000785 FROM_HERE, base::BindOnce(&MidiManagerWinrt::InitializeOnComThread,
786 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700787}
788
789void MidiManagerWinrt::Finalize() {
790 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000791 FROM_HERE, base::BindOnce(&MidiManagerWinrt::FinalizeOnComThread,
792 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700793
794 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
795 // will be ignored.
796 com_thread_.Stop();
797}
798
799void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
800 uint32_t port_index,
801 const std::vector<uint8_t>& data,
802 double timestamp) {
803 CHECK(scheduler_);
804
805 scheduler_->PostSendDataTask(
806 client, data.size(), timestamp,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000807 base::BindOnce(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
808 port_index, data));
shaochuane58f9c72016-08-30 22:27:08 -0700809}
810
811void MidiManagerWinrt::InitializeOnComThread() {
812 base::AutoLock auto_lock(lazy_init_member_lock_);
813
814 com_thread_checker_.reset(new base::ThreadChecker);
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000815 bool preload_success = base::win::ResolveCoreWinRTDelayload() &&
816 ScopedHString::ResolveCoreWinRTStringDelayload();
817 if (!preload_success) {
shaochuan9ff63b82016-09-01 01:58:44 -0700818 CompleteInitialization(Result::INITIALIZATION_ERROR);
819 return;
820 }
821
shaochuane58f9c72016-08-30 22:27:08 -0700822 port_manager_in_.reset(new MidiInPortManager(this));
823 port_manager_out_.reset(new MidiOutPortManager(this));
824
825 scheduler_.reset(new MidiScheduler(this));
826
827 if (!(port_manager_in_->StartWatcher() &&
828 port_manager_out_->StartWatcher())) {
829 port_manager_in_->StopWatcher();
830 port_manager_out_->StopWatcher();
831 CompleteInitialization(Result::INITIALIZATION_ERROR);
832 }
833}
834
835void MidiManagerWinrt::FinalizeOnComThread() {
836 base::AutoLock auto_lock(lazy_init_member_lock_);
837
838 DCHECK(com_thread_checker_->CalledOnValidThread());
839
840 scheduler_.reset();
841
shaochuan9ff63b82016-09-01 01:58:44 -0700842 if (port_manager_in_) {
843 port_manager_in_->StopWatcher();
844 port_manager_in_.reset();
845 }
846
847 if (port_manager_out_) {
848 port_manager_out_->StopWatcher();
849 port_manager_out_.reset();
850 }
shaochuane58f9c72016-08-30 22:27:08 -0700851
852 com_thread_checker_.reset();
853}
854
855void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
856 const std::vector<uint8_t>& data) {
857 DCHECK(com_thread_checker_->CalledOnValidThread());
858
859 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
860 if (!(port && port->handle)) {
861 VLOG(1) << "Port not available: " << port_index;
862 return;
863 }
864
Robert Liao4a680c32017-10-18 19:10:01 +0000865 WRL::ComPtr<IBuffer> buffer;
junweifua8cea852017-10-17 06:21:16 +0000866 HRESULT hr = base::win::CreateIBufferFromData(
junweifuf51c5a02017-11-03 06:37:09 +0000867 data.data(), static_cast<UINT32>(data.size()), &buffer);
shaochuane58f9c72016-08-30 22:27:08 -0700868 if (FAILED(hr)) {
junweifua8cea852017-10-17 06:21:16 +0000869 VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700870 return;
871 }
872
robliao3566d1a2017-04-18 17:28:09 -0700873 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -0700874 if (FAILED(hr)) {
875 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
876 return;
877 }
878}
879
880void MidiManagerWinrt::OnPortManagerReady() {
881 DCHECK(com_thread_checker_->CalledOnValidThread());
882 DCHECK(port_manager_ready_count_ < 2);
883
884 if (++port_manager_ready_count_ == 2)
885 CompleteInitialization(Result::OK);
886}
887
shaochuane58f9c72016-08-30 22:27:08 -0700888} // namespace midi