blob: 453c2d60b802f61d459dfae2225f98925c06bb29 [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"
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
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000047using base::win::ScopedHString;
junweifua8cea852017-10-17 06:21:16 +000048using base::win::GetActivationFactory;
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
shaochuane58f9c72016-08-30 22:27:08 -070066template <typename T>
67std::string GetIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070068 HSTRING result;
69 HRESULT hr = obj->get_Id(&result);
70 if (FAILED(hr)) {
71 VLOG(1) << "get_Id failed: " << PrintHr(hr);
72 return std::string();
73 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000074 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070075}
76
77template <typename T>
78std::string GetDeviceIdString(T* obj) {
shaochuan80f1fba2016-09-01 20:44:51 -070079 HSTRING result;
80 HRESULT hr = obj->get_DeviceId(&result);
81 if (FAILED(hr)) {
82 VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
83 return std::string();
84 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000085 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070086}
87
88std::string GetNameString(IDeviceInformation* info) {
shaochuan80f1fba2016-09-01 20:44:51 -070089 HSTRING result;
90 HRESULT hr = info->get_Name(&result);
91 if (FAILED(hr)) {
92 VLOG(1) << "get_Name failed: " << PrintHr(hr);
93 return std::string();
94 }
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +000095 return ScopedHString(result).GetAsUTF8();
shaochuane58f9c72016-08-30 22:27:08 -070096}
97
shaochuan110262b2016-08-31 02:15:16 -070098// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
99// instance.
100bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
Robert Liao4a680c32017-10-18 19:10:01 +0000101 WRL::ComPtr<IMidiSynthesizerStatics> midi_synthesizer_statics;
junweifua8cea852017-10-17 06:21:16 +0000102 HRESULT hr =
103 GetActivationFactory<IMidiSynthesizerStatics,
104 RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>(
105 &midi_synthesizer_statics);
106 if (FAILED(hr)) {
107 VLOG(1) << "IMidiSynthesizerStatics factory failed: " << PrintHr(hr);
108 return false;
109 }
shaochuan110262b2016-08-31 02:15:16 -0700110 boolean result = FALSE;
junweifua8cea852017-10-17 06:21:16 +0000111 hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
shaochuan110262b2016-08-31 02:15:16 -0700112 VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
113 return result != FALSE;
114}
115
shaochuan4eff30e2016-09-09 01:24:14 -0700116void GetDevPropString(DEVINST handle,
117 const DEVPROPKEY* devprop_key,
118 std::string* out) {
119 DEVPROPTYPE devprop_type;
120 unsigned long buffer_size = 0;
shaochuan17bc4a02016-09-06 01:42:12 -0700121
shaochuan4eff30e2016-09-09 01:24:14 -0700122 // Retrieve |buffer_size| and allocate buffer later for receiving data.
123 CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
124 nullptr, &buffer_size, 0);
125 if (cr != CR_BUFFER_SMALL) {
126 // Here we print error codes in hex instead of using PrintHr() with
127 // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
128 // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
129 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
130 return;
shaochuan17bc4a02016-09-06 01:42:12 -0700131 }
shaochuan4eff30e2016-09-09 01:24:14 -0700132 if (devprop_type != DEVPROP_TYPE_STRING) {
133 VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
134 << "expected DEVPROP_TYPE_STRING";
135 return;
136 }
shaochuan17bc4a02016-09-06 01:42:12 -0700137
shaochuan4eff30e2016-09-09 01:24:14 -0700138 std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
139
140 // Receive property data.
141 cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type, buffer.get(),
142 &buffer_size, 0);
143 if (cr != CR_SUCCESS)
144 VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
145 else
146 *out = base::WideToUTF8(reinterpret_cast<base::char16*>(buffer.get()));
147}
shaochuan17bc4a02016-09-06 01:42:12 -0700148
149// Retrieves manufacturer (provider) and version information of underlying
shaochuan4eff30e2016-09-09 01:24:14 -0700150// device driver through PnP Configuration Manager, given device (interface) ID
151// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
152// modified if retrieval fails.
shaochuan17bc4a02016-09-06 01:42:12 -0700153//
154// Device instance ID is extracted from device (interface) ID provided by WinRT
155// APIs, for example from the following interface ID:
156// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
157// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
shaochuan4eff30e2016-09-09 01:24:14 -0700158//
159// However the extracted device instance ID represent a "software device"
160// provided by Microsoft, which is an interface on top of the hardware for each
161// input/output port. Therefore we further locate its parent device, which is
162// the actual hardware device, for driver information.
shaochuan17bc4a02016-09-06 01:42:12 -0700163void GetDriverInfoFromDeviceId(const std::string& dev_id,
164 std::string* out_manufacturer,
165 std::string* out_driver_version) {
166 base::string16 dev_instance_id =
167 base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
168 base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);
169
shaochuan4eff30e2016-09-09 01:24:14 -0700170 DEVINST dev_instance_handle;
171 CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
172 CM_LOCATE_DEVNODE_NORMAL);
173 if (cr != CR_SUCCESS) {
174 VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700175 return;
176 }
177
shaochuan4eff30e2016-09-09 01:24:14 -0700178 DEVINST parent_handle;
179 cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
180 if (cr != CR_SUCCESS) {
181 VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
shaochuan17bc4a02016-09-06 01:42:12 -0700182 return;
183 }
184
shaochuan4eff30e2016-09-09 01:24:14 -0700185 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
186 out_manufacturer);
187 GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
188 out_driver_version);
shaochuan17bc4a02016-09-06 01:42:12 -0700189}
190
shaochuane58f9c72016-08-30 22:27:08 -0700191// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
192const int64_t kInvalidTokenValue = 0;
193
194template <typename InterfaceType>
195struct MidiPort {
196 MidiPort() = default;
197
198 uint32_t index;
Robert Liao4a680c32017-10-18 19:10:01 +0000199 WRL::ComPtr<InterfaceType> handle;
shaochuane58f9c72016-08-30 22:27:08 -0700200 EventRegistrationToken token_MessageReceived;
201
202 private:
203 DISALLOW_COPY_AND_ASSIGN(MidiPort);
204};
205
206} // namespace
207
208template <typename InterfaceType,
209 typename RuntimeType,
210 typename StaticsInterfaceType,
211 base::char16 const* runtime_class_id>
212class MidiManagerWinrt::MidiPortManager {
213 public:
214 // MidiPortManager instances should be constructed on the COM thread.
215 MidiPortManager(MidiManagerWinrt* midi_manager)
216 : midi_manager_(midi_manager),
217 task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
218
219 virtual ~MidiPortManager() { DCHECK(thread_checker_.CalledOnValidThread()); }
220
221 bool StartWatcher() {
222 DCHECK(thread_checker_.CalledOnValidThread());
223
junweifua8cea852017-10-17 06:21:16 +0000224 HRESULT hr = GetActivationFactory<StaticsInterfaceType, runtime_class_id>(
225 &midi_port_statics_);
226 if (FAILED(hr)) {
227 VLOG(1) << "StaticsInterfaceType factory failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700228 return false;
junweifua8cea852017-10-17 06:21:16 +0000229 }
shaochuane58f9c72016-08-30 22:27:08 -0700230
231 HSTRING device_selector = nullptr;
232 hr = midi_port_statics_->GetDeviceSelector(&device_selector);
233 if (FAILED(hr)) {
234 VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
235 return false;
236 }
237
Robert Liao4a680c32017-10-18 19:10:01 +0000238 WRL::ComPtr<IDeviceInformationStatics> dev_info_statics;
junweifua8cea852017-10-17 06:21:16 +0000239 hr = GetActivationFactory<
shaochuane58f9c72016-08-30 22:27:08 -0700240 IDeviceInformationStatics,
junweifua8cea852017-10-17 06:21:16 +0000241 RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
242 &dev_info_statics);
243 if (FAILED(hr)) {
244 VLOG(1) << "IDeviceInformationStatics failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700245 return false;
junweifua8cea852017-10-17 06:21:16 +0000246 }
shaochuane58f9c72016-08-30 22:27:08 -0700247
248 hr = dev_info_statics->CreateWatcherAqsFilter(device_selector,
robliao8d08e692017-05-11 10:14:00 -0700249 watcher_.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700250 if (FAILED(hr)) {
251 VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
252 return false;
253 }
254
255 // Register callbacks to WinRT that post state-modifying jobs back to COM
256 // thread. |weak_ptr| and |task_runner| are captured by lambda callbacks for
257 // posting jobs. Note that WinRT callback arguments should not be passed
258 // outside the callback since the pointers may be unavailable afterwards.
259 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
260 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
261
262 hr = watcher_->add_Added(
263 WRL::Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>>(
264 [weak_ptr, task_runner](IDeviceWatcher* watcher,
265 IDeviceInformation* info) {
shaochuanc2894522016-09-20 01:10:50 -0700266 if (!info) {
267 VLOG(1) << "DeviceWatcher.Added callback provides null "
268 "pointer, ignoring";
269 return S_OK;
270 }
271
shaochuan110262b2016-08-31 02:15:16 -0700272 // Disable Microsoft GS Wavetable Synth due to security reasons.
273 // http://crbug.com/499279
274 if (IsMicrosoftSynthesizer(info))
275 return S_OK;
276
shaochuane58f9c72016-08-30 22:27:08 -0700277 std::string dev_id = GetIdString(info),
278 dev_name = GetNameString(info);
279
280 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000281 FROM_HERE, base::BindOnce(&MidiPortManager::OnAdded, weak_ptr,
282 dev_id, dev_name));
shaochuane58f9c72016-08-30 22:27:08 -0700283
284 return S_OK;
285 })
286 .Get(),
287 &token_Added_);
288 if (FAILED(hr)) {
289 VLOG(1) << "add_Added failed: " << PrintHr(hr);
290 return false;
291 }
292
293 hr = watcher_->add_EnumerationCompleted(
294 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
295 [weak_ptr, task_runner](IDeviceWatcher* watcher,
296 IInspectable* insp) {
297 task_runner->PostTask(
298 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000299 base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
300 weak_ptr));
shaochuane58f9c72016-08-30 22:27:08 -0700301
302 return S_OK;
303 })
304 .Get(),
305 &token_EnumerationCompleted_);
306 if (FAILED(hr)) {
307 VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
308 return false;
309 }
310
311 hr = watcher_->add_Removed(
312 WRL::Callback<
313 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
314 [weak_ptr, task_runner](IDeviceWatcher* watcher,
315 IDeviceInformationUpdate* update) {
shaochuanc2894522016-09-20 01:10:50 -0700316 if (!update) {
317 VLOG(1) << "DeviceWatcher.Removed callback provides null "
318 "pointer, ignoring";
319 return S_OK;
320 }
321
shaochuane58f9c72016-08-30 22:27:08 -0700322 std::string dev_id = GetIdString(update);
323
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000324 task_runner->PostTask(FROM_HERE,
325 base::BindOnce(&MidiPortManager::OnRemoved,
326 weak_ptr, dev_id));
shaochuane58f9c72016-08-30 22:27:08 -0700327
328 return S_OK;
329 })
330 .Get(),
331 &token_Removed_);
332 if (FAILED(hr)) {
333 VLOG(1) << "add_Removed failed: " << PrintHr(hr);
334 return false;
335 }
336
337 hr = watcher_->add_Stopped(
338 WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
339 [](IDeviceWatcher* watcher, IInspectable* insp) {
340 // Placeholder, does nothing for now.
341 return S_OK;
342 })
343 .Get(),
344 &token_Stopped_);
345 if (FAILED(hr)) {
346 VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
347 return false;
348 }
349
350 hr = watcher_->add_Updated(
351 WRL::Callback<
352 ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
353 [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
354 // TODO(shaochuan): Check for fields to be updated here.
355 return S_OK;
356 })
357 .Get(),
358 &token_Updated_);
359 if (FAILED(hr)) {
360 VLOG(1) << "add_Updated failed: " << PrintHr(hr);
361 return false;
362 }
363
364 hr = watcher_->Start();
365 if (FAILED(hr)) {
366 VLOG(1) << "Start failed: " << PrintHr(hr);
367 return false;
368 }
369
370 is_initialized_ = true;
371 return true;
372 }
373
374 void StopWatcher() {
375 DCHECK(thread_checker_.CalledOnValidThread());
376
377 HRESULT hr;
378
379 for (const auto& entry : ports_)
380 RemovePortEventHandlers(entry.second.get());
381
382 if (token_Added_.value != kInvalidTokenValue) {
383 hr = watcher_->remove_Added(token_Added_);
384 VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
385 token_Added_.value = kInvalidTokenValue;
386 }
387 if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
388 hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
389 VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
390 << PrintHr(hr);
391 token_EnumerationCompleted_.value = kInvalidTokenValue;
392 }
393 if (token_Removed_.value != kInvalidTokenValue) {
394 hr = watcher_->remove_Removed(token_Removed_);
395 VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
396 token_Removed_.value = kInvalidTokenValue;
397 }
398 if (token_Stopped_.value != kInvalidTokenValue) {
399 hr = watcher_->remove_Stopped(token_Stopped_);
400 VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
401 token_Stopped_.value = kInvalidTokenValue;
402 }
403 if (token_Updated_.value != kInvalidTokenValue) {
404 hr = watcher_->remove_Updated(token_Updated_);
405 VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
406 token_Updated_.value = kInvalidTokenValue;
407 }
408
409 if (is_initialized_) {
410 hr = watcher_->Stop();
411 VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
412 is_initialized_ = false;
413 }
414 }
415
416 MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
417 DCHECK(thread_checker_.CalledOnValidThread());
418 CHECK(is_initialized_);
419
420 auto it = ports_.find(dev_id);
421 if (it == ports_.end())
422 return nullptr;
423 return it->second.get();
424 }
425
426 MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
427 DCHECK(thread_checker_.CalledOnValidThread());
428 CHECK(is_initialized_);
429
430 return GetPortByDeviceId(port_ids_[port_index]);
431 }
432
433 protected:
434 // Points to the MidiManagerWinrt instance, which is expected to outlive the
435 // MidiPortManager instance.
436 MidiManagerWinrt* midi_manager_;
437
438 // Task runner of the COM thread.
439 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
440
441 // Ensures all methods are called on the COM thread.
442 base::ThreadChecker thread_checker_;
443
444 private:
445 // DeviceWatcher callbacks:
446 void OnAdded(std::string dev_id, std::string dev_name) {
447 DCHECK(thread_checker_.CalledOnValidThread());
448 CHECK(is_initialized_);
449
shaochuane58f9c72016-08-30 22:27:08 -0700450 port_names_[dev_id] = dev_name;
451
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000452 ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
shaochuan9ff63b82016-09-01 01:58:44 -0700453 if (!dev_id_hstring.is_valid())
shaochuane58f9c72016-08-30 22:27:08 -0700454 return;
shaochuane58f9c72016-08-30 22:27:08 -0700455
456 IAsyncOperation<RuntimeType*>* async_op;
457
shaochuan9ff63b82016-09-01 01:58:44 -0700458 HRESULT hr =
459 midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
shaochuane58f9c72016-08-30 22:27:08 -0700460 if (FAILED(hr)) {
461 VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
462 return;
463 }
464
465 base::WeakPtr<MidiPortManager> weak_ptr = GetWeakPtrFromFactory();
466 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
467
468 hr = async_op->put_Completed(
469 WRL::Callback<IAsyncOperationCompletedHandler<RuntimeType*>>(
470 [weak_ptr, task_runner](IAsyncOperation<RuntimeType*>* async_op,
471 AsyncStatus status) {
shaochuane58f9c72016-08-30 22:27:08 -0700472 // A reference to |async_op| is kept in |async_ops_|, safe to pass
473 // outside.
474 task_runner->PostTask(
475 FROM_HERE,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000476 base::BindOnce(
477 &MidiPortManager::OnCompletedGetPortFromIdAsync, weak_ptr,
478 async_op));
shaochuane58f9c72016-08-30 22:27:08 -0700479
480 return S_OK;
481 })
482 .Get());
483 if (FAILED(hr)) {
484 VLOG(1) << "put_Completed failed: " << PrintHr(hr);
485 return;
486 }
487
488 // Keep a reference to incompleted |async_op| for releasing later.
489 async_ops_.insert(async_op);
490 }
491
492 void OnEnumerationCompleted() {
493 DCHECK(thread_checker_.CalledOnValidThread());
494 CHECK(is_initialized_);
495
496 if (async_ops_.empty())
497 midi_manager_->OnPortManagerReady();
498 else
499 enumeration_completed_not_ready_ = true;
500 }
501
502 void OnRemoved(std::string dev_id) {
503 DCHECK(thread_checker_.CalledOnValidThread());
504 CHECK(is_initialized_);
505
shaochuan110262b2016-08-31 02:15:16 -0700506 // Note: in case Microsoft GS Wavetable Synth triggers this event for some
507 // reason, it will be ignored here with log emitted.
shaochuane58f9c72016-08-30 22:27:08 -0700508 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
509 if (!port) {
510 VLOG(1) << "Removing non-existent port " << dev_id;
511 return;
512 }
513
toyoshimec2570a2016-10-21 02:15:27 -0700514 SetPortState(port->index, PortState::DISCONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700515
516 RemovePortEventHandlers(port);
517 port->handle = nullptr;
518 }
519
shaochuanc2894522016-09-20 01:10:50 -0700520 void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
shaochuane58f9c72016-08-30 22:27:08 -0700521 DCHECK(thread_checker_.CalledOnValidThread());
522 CHECK(is_initialized_);
523
shaochuanc2894522016-09-20 01:10:50 -0700524 InterfaceType* handle = nullptr;
525 HRESULT hr = async_op->GetResults(&handle);
526 if (FAILED(hr)) {
527 VLOG(1) << "GetResults failed: " << PrintHr(hr);
528 return;
529 }
530
531 // Manually release COM interface to completed |async_op|.
532 auto it = async_ops_.find(async_op);
533 CHECK(it != async_ops_.end());
534 (*it)->Release();
535 async_ops_.erase(it);
536
537 if (!handle) {
538 VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
539 "ignoring";
540 return;
541 }
542
shaochuane58f9c72016-08-30 22:27:08 -0700543 EventRegistrationToken token = {kInvalidTokenValue};
544 if (!RegisterOnMessageReceived(handle, &token))
545 return;
546
547 std::string dev_id = GetDeviceIdString(handle);
548
549 MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
550
551 if (port == nullptr) {
shaochuan17bc4a02016-09-06 01:42:12 -0700552 std::string manufacturer = "Unknown", driver_version = "Unknown";
553 GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);
554
555 AddPort(MidiPortInfo(dev_id, manufacturer, port_names_[dev_id],
toyoshimec2570a2016-10-21 02:15:27 -0700556 driver_version, PortState::OPENED));
shaochuane58f9c72016-08-30 22:27:08 -0700557
558 port = new MidiPort<InterfaceType>;
559 port->index = static_cast<uint32_t>(port_ids_.size());
560
561 ports_[dev_id].reset(port);
562 port_ids_.push_back(dev_id);
563 } else {
toyoshimec2570a2016-10-21 02:15:27 -0700564 SetPortState(port->index, PortState::CONNECTED);
shaochuane58f9c72016-08-30 22:27:08 -0700565 }
566
567 port->handle = handle;
568 port->token_MessageReceived = token;
569
shaochuane58f9c72016-08-30 22:27:08 -0700570 if (enumeration_completed_not_ready_ && async_ops_.empty()) {
571 midi_manager_->OnPortManagerReady();
572 enumeration_completed_not_ready_ = false;
573 }
574 }
575
576 // Overrided by MidiInPortManager to listen to input ports.
577 virtual bool RegisterOnMessageReceived(InterfaceType* handle,
578 EventRegistrationToken* p_token) {
579 return true;
580 }
581
582 // Overrided by MidiInPortManager to remove MessageReceived event handler.
583 virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}
584
585 // Calls midi_manager_->Add{Input,Output}Port.
586 virtual void AddPort(MidiPortInfo info) = 0;
587
588 // Calls midi_manager_->Set{Input,Output}PortState.
toyoshimec2570a2016-10-21 02:15:27 -0700589 virtual void SetPortState(uint32_t port_index, PortState state) = 0;
shaochuane58f9c72016-08-30 22:27:08 -0700590
591 // WeakPtrFactory has to be declared in derived class, use this method to
592 // retrieve upcasted WeakPtr for posting tasks.
593 virtual base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() = 0;
594
595 // Midi{In,Out}PortStatics instance.
Robert Liao4a680c32017-10-18 19:10:01 +0000596 WRL::ComPtr<StaticsInterfaceType> midi_port_statics_;
shaochuane58f9c72016-08-30 22:27:08 -0700597
598 // DeviceWatcher instance and event registration tokens for unsubscribing
599 // events in destructor.
Robert Liao4a680c32017-10-18 19:10:01 +0000600 WRL::ComPtr<IDeviceWatcher> watcher_;
shaochuane58f9c72016-08-30 22:27:08 -0700601 EventRegistrationToken token_Added_ = {kInvalidTokenValue},
602 token_EnumerationCompleted_ = {kInvalidTokenValue},
603 token_Removed_ = {kInvalidTokenValue},
604 token_Stopped_ = {kInvalidTokenValue},
605 token_Updated_ = {kInvalidTokenValue};
606
607 // All manipulations to these fields should be done on COM thread.
608 std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
609 ports_;
610 std::vector<std::string> port_ids_;
611 std::unordered_map<std::string, std::string> port_names_;
612
613 // Keeps AsyncOperation references before the operation completes. Note that
614 // raw pointers are used here and the COM interfaces should be released
615 // manually.
616 std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;
617
618 // Set when device enumeration is completed but OnPortManagerReady() is not
619 // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
620 // In such cases, OnPortManagerReady() will be called in
621 // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
622 bool enumeration_completed_not_ready_ = false;
623
624 // Set if the instance is initialized without error. Should be checked in all
625 // methods on COM thread except StartWatcher().
626 bool is_initialized_ = false;
627};
628
629class MidiManagerWinrt::MidiInPortManager final
630 : public MidiPortManager<IMidiInPort,
631 MidiInPort,
632 IMidiInPortStatics,
633 RuntimeClass_Windows_Devices_Midi_MidiInPort> {
634 public:
635 MidiInPortManager(MidiManagerWinrt* midi_manager)
636 : MidiPortManager(midi_manager), weak_factory_(this) {}
637
638 private:
639 // MidiPortManager overrides:
640 bool RegisterOnMessageReceived(IMidiInPort* handle,
641 EventRegistrationToken* p_token) override {
642 DCHECK(thread_checker_.CalledOnValidThread());
643
644 base::WeakPtr<MidiInPortManager> weak_ptr = weak_factory_.GetWeakPtr();
645 scoped_refptr<base::SingleThreadTaskRunner> task_runner = task_runner_;
646
647 HRESULT hr = handle->add_MessageReceived(
648 WRL::Callback<
649 ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>>(
650 [weak_ptr, task_runner](IMidiInPort* handle,
651 IMidiMessageReceivedEventArgs* args) {
652 const base::TimeTicks now = base::TimeTicks::Now();
653
654 std::string dev_id = GetDeviceIdString(handle);
655
Robert Liao4a680c32017-10-18 19:10:01 +0000656 WRL::ComPtr<IMidiMessage> message;
robliao8d08e692017-05-11 10:14:00 -0700657 HRESULT hr = args->get_Message(message.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700658 if (FAILED(hr)) {
659 VLOG(1) << "get_Message failed: " << PrintHr(hr);
660 return hr;
661 }
662
Robert Liao4a680c32017-10-18 19:10:01 +0000663 WRL::ComPtr<IBuffer> buffer;
robliao8d08e692017-05-11 10:14:00 -0700664 hr = message->get_RawData(buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700665 if (FAILED(hr)) {
666 VLOG(1) << "get_RawData failed: " << PrintHr(hr);
667 return hr;
668 }
669
670 uint8_t* p_buffer_data = nullptr;
junweifua8cea852017-10-17 06:21:16 +0000671 hr = base::win::GetPointerToBufferData(buffer.Get(),
672 &p_buffer_data);
shaochuane58f9c72016-08-30 22:27:08 -0700673 if (FAILED(hr))
674 return hr;
675
676 uint32_t data_length = 0;
677 hr = buffer->get_Length(&data_length);
678 if (FAILED(hr)) {
679 VLOG(1) << "get_Length failed: " << PrintHr(hr);
680 return hr;
681 }
682
683 std::vector<uint8_t> data(p_buffer_data,
684 p_buffer_data + data_length);
685
686 task_runner->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000687 FROM_HERE,
688 base::BindOnce(&MidiInPortManager::OnMessageReceived,
689 weak_ptr, dev_id, data, now));
shaochuane58f9c72016-08-30 22:27:08 -0700690
691 return S_OK;
692 })
693 .Get(),
694 p_token);
695 if (FAILED(hr)) {
696 VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
697 return false;
698 }
699
700 return true;
701 }
702
703 void RemovePortEventHandlers(MidiPort<IMidiInPort>* port) override {
704 if (!(port->handle &&
705 port->token_MessageReceived.value != kInvalidTokenValue))
706 return;
707
708 HRESULT hr =
709 port->handle->remove_MessageReceived(port->token_MessageReceived);
710 VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
711 port->token_MessageReceived.value = kInvalidTokenValue;
712 }
713
714 void AddPort(MidiPortInfo info) final { midi_manager_->AddInputPort(info); }
715
toyoshimec2570a2016-10-21 02:15:27 -0700716 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700717 midi_manager_->SetInputPortState(port_index, state);
718 }
719
720 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
721 DCHECK(thread_checker_.CalledOnValidThread());
722
723 return weak_factory_.GetWeakPtr();
724 }
725
726 // Callback on receiving MIDI input message.
727 void OnMessageReceived(std::string dev_id,
728 std::vector<uint8_t> data,
729 base::TimeTicks time) {
730 DCHECK(thread_checker_.CalledOnValidThread());
731
732 MidiPort<IMidiInPort>* port = GetPortByDeviceId(dev_id);
733 CHECK(port);
734
735 midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
736 }
737
738 // Last member to ensure destructed first.
739 base::WeakPtrFactory<MidiInPortManager> weak_factory_;
740
741 DISALLOW_COPY_AND_ASSIGN(MidiInPortManager);
742};
743
744class MidiManagerWinrt::MidiOutPortManager final
745 : public MidiPortManager<IMidiOutPort,
746 IMidiOutPort,
747 IMidiOutPortStatics,
748 RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
749 public:
750 MidiOutPortManager(MidiManagerWinrt* midi_manager)
751 : MidiPortManager(midi_manager), weak_factory_(this) {}
752
753 private:
754 // MidiPortManager overrides:
755 void AddPort(MidiPortInfo info) final { midi_manager_->AddOutputPort(info); }
756
toyoshimec2570a2016-10-21 02:15:27 -0700757 void SetPortState(uint32_t port_index, PortState state) final {
shaochuane58f9c72016-08-30 22:27:08 -0700758 midi_manager_->SetOutputPortState(port_index, state);
759 }
760
761 base::WeakPtr<MidiPortManager> GetWeakPtrFromFactory() final {
762 DCHECK(thread_checker_.CalledOnValidThread());
763
764 return weak_factory_.GetWeakPtr();
765 }
766
767 // Last member to ensure destructed first.
768 base::WeakPtrFactory<MidiOutPortManager> weak_factory_;
769
770 DISALLOW_COPY_AND_ASSIGN(MidiOutPortManager);
771};
772
toyoshimf4d61522017-02-10 02:03:32 -0800773MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
774 : MidiManager(service), com_thread_("Windows MIDI COM Thread") {}
shaochuane58f9c72016-08-30 22:27:08 -0700775
776MidiManagerWinrt::~MidiManagerWinrt() {
777 base::AutoLock auto_lock(lazy_init_member_lock_);
778
779 CHECK(!com_thread_checker_);
780 CHECK(!port_manager_in_);
781 CHECK(!port_manager_out_);
782 CHECK(!scheduler_);
783}
784
785void MidiManagerWinrt::StartInitialization() {
shaochuane58f9c72016-08-30 22:27:08 -0700786 com_thread_.init_com_with_mta(true);
787 com_thread_.Start();
788
789 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000790 FROM_HERE, base::BindOnce(&MidiManagerWinrt::InitializeOnComThread,
791 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700792}
793
794void MidiManagerWinrt::Finalize() {
795 com_thread_.task_runner()->PostTask(
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000796 FROM_HERE, base::BindOnce(&MidiManagerWinrt::FinalizeOnComThread,
797 base::Unretained(this)));
shaochuane58f9c72016-08-30 22:27:08 -0700798
799 // Blocks until FinalizeOnComThread() returns. Delayed MIDI send data tasks
800 // will be ignored.
801 com_thread_.Stop();
802}
803
804void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
805 uint32_t port_index,
806 const std::vector<uint8_t>& data,
807 double timestamp) {
808 CHECK(scheduler_);
809
810 scheduler_->PostSendDataTask(
811 client, data.size(), timestamp,
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000812 base::BindOnce(&MidiManagerWinrt::SendOnComThread, base::Unretained(this),
813 port_index, data));
shaochuane58f9c72016-08-30 22:27:08 -0700814}
815
816void MidiManagerWinrt::InitializeOnComThread() {
817 base::AutoLock auto_lock(lazy_init_member_lock_);
818
819 com_thread_checker_.reset(new base::ThreadChecker);
Finnur Thorarinssonee8428f2017-09-30 23:36:49 +0000820 bool preload_success = base::win::ResolveCoreWinRTDelayload() &&
821 ScopedHString::ResolveCoreWinRTStringDelayload();
822 if (!preload_success) {
shaochuan9ff63b82016-09-01 01:58:44 -0700823 CompleteInitialization(Result::INITIALIZATION_ERROR);
824 return;
825 }
826
shaochuane58f9c72016-08-30 22:27:08 -0700827 port_manager_in_.reset(new MidiInPortManager(this));
828 port_manager_out_.reset(new MidiOutPortManager(this));
829
830 scheduler_.reset(new MidiScheduler(this));
831
832 if (!(port_manager_in_->StartWatcher() &&
833 port_manager_out_->StartWatcher())) {
834 port_manager_in_->StopWatcher();
835 port_manager_out_->StopWatcher();
836 CompleteInitialization(Result::INITIALIZATION_ERROR);
837 }
838}
839
840void MidiManagerWinrt::FinalizeOnComThread() {
841 base::AutoLock auto_lock(lazy_init_member_lock_);
842
843 DCHECK(com_thread_checker_->CalledOnValidThread());
844
845 scheduler_.reset();
846
shaochuan9ff63b82016-09-01 01:58:44 -0700847 if (port_manager_in_) {
848 port_manager_in_->StopWatcher();
849 port_manager_in_.reset();
850 }
851
852 if (port_manager_out_) {
853 port_manager_out_->StopWatcher();
854 port_manager_out_.reset();
855 }
shaochuane58f9c72016-08-30 22:27:08 -0700856
857 com_thread_checker_.reset();
858}
859
860void MidiManagerWinrt::SendOnComThread(uint32_t port_index,
861 const std::vector<uint8_t>& data) {
862 DCHECK(com_thread_checker_->CalledOnValidThread());
863
864 MidiPort<IMidiOutPort>* port = port_manager_out_->GetPortByIndex(port_index);
865 if (!(port && port->handle)) {
866 VLOG(1) << "Port not available: " << port_index;
867 return;
868 }
869
Robert Liao4a680c32017-10-18 19:10:01 +0000870 WRL::ComPtr<IBuffer> buffer;
junweifua8cea852017-10-17 06:21:16 +0000871 HRESULT hr = base::win::CreateIBufferFromData(
872 data.data(), static_cast<UINT32>(data.size()), buffer.GetAddressOf());
shaochuane58f9c72016-08-30 22:27:08 -0700873 if (FAILED(hr)) {
junweifua8cea852017-10-17 06:21:16 +0000874 VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
shaochuane58f9c72016-08-30 22:27:08 -0700875 return;
876 }
877
robliao3566d1a2017-04-18 17:28:09 -0700878 hr = port->handle->SendBuffer(buffer.Get());
shaochuane58f9c72016-08-30 22:27:08 -0700879 if (FAILED(hr)) {
880 VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
881 return;
882 }
883}
884
885void MidiManagerWinrt::OnPortManagerReady() {
886 DCHECK(com_thread_checker_->CalledOnValidThread());
887 DCHECK(port_manager_ready_count_ < 2);
888
889 if (++port_manager_ready_count_ == 2)
890 CompleteInitialization(Result::OK);
891}
892
shaochuane58f9c72016-08-30 22:27:08 -0700893} // namespace midi