1 /*
2  * Strawberry Music Player
3  * Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
4  *
5  * Strawberry is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Strawberry is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "config.h"
21 
22 #include <windows.h>
23 #include <initguid.h>
24 #include <devpkey.h>
25 #ifdef _MSC_VER
26 #  include <functiondiscoverykeys.h>
27 #else
28 #  include <functiondiscoverykeys_devpkey.h>
29 #endif
30 #include <mmdeviceapi.h>
31 
32 #include <QList>
33 #include <QVariant>
34 #include <QString>
35 
36 #include "mmdevicefinder.h"
37 #include "core/logging.h"
38 
39 #ifdef _MSC_VER
40   DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6);
41   DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e);
42 #endif
43 
MMDeviceFinder()44 MMDeviceFinder::MMDeviceFinder() : DeviceFinder("mmdevice", { "wasapisink" }) {}
45 
ListDevices()46 QList<DeviceFinder::Device> MMDeviceFinder::ListDevices() {
47 
48   HRESULT hr_coinit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
49 
50   QList<Device> devices;
51   Device default_device;
52   default_device.description = "Default device";
53   default_device.iconname = GuessIconName(default_device.description);
54   devices.append(default_device);
55 
56   IMMDeviceEnumerator *enumerator = nullptr;
57   HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(&enumerator));
58   if (hr == S_OK) {
59     IMMDeviceCollection *collection = nullptr;
60     hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
61     if (hr == S_OK) {
62       UINT count;
63       hr = collection->GetCount(&count);
64       if (hr == S_OK) {
65         for (ULONG i = 0; i < count; i++) {
66           IMMDevice *endpoint = nullptr;
67           hr = collection->Item(i, &endpoint);
68           if (hr == S_OK) {
69             LPWSTR pwszid = nullptr;
70             hr = endpoint->GetId(&pwszid);
71             if (hr == S_OK) {
72               IPropertyStore *props = nullptr;
73               hr = endpoint->OpenPropertyStore(STGM_READ, &props);
74               if (hr == S_OK) {
75                 PROPVARIANT var_name;
76                 PropVariantInit(&var_name);
77                 hr = props->GetValue(PKEY_Device_FriendlyName, &var_name);
78                 if (hr == S_OK) {
79                   Device device;
80                   device.description = QString::fromWCharArray(var_name.pwszVal);
81                   device.iconname = GuessIconName(device.description);
82                   device.value = QString::fromStdWString(pwszid);
83                   devices.append(device);
84                   PropVariantClear(&var_name);
85                 }
86                 else {
87                   qLog(Error) << "IPropertyStore::GetValue failed." << Qt::hex << DWORD(hr);
88                 }
89                 props->Release();
90               }
91               else {
92                 qLog(Error) << "IPropertyStore::OpenPropertyStore failed." << Qt::hex << DWORD(hr);
93               }
94               CoTaskMemFree(pwszid);
95             }
96             else {
97               qLog(Error) << "IMMDevice::GetId failed." << Qt::hex << DWORD(hr);
98             }
99             endpoint->Release();
100           }
101           else {
102             qLog(Error) << "IMMDeviceCollection::Item failed." << Qt::hex << DWORD(hr);
103           }
104         }
105       }
106       else {
107         qLog(Error) << "IMMDeviceCollection::GetCount failed." << Qt::hex << DWORD(hr);
108       }
109       collection->Release();
110     }
111     else {
112       qLog(Error) << "EnumAudioEndpoints failed." << Qt::hex << DWORD(hr);
113     }
114     enumerator->Release();
115   }
116   else {
117     qLog(Error) << "CoCreateInstance failed." << Qt::hex << DWORD(hr);
118   }
119 
120   if (hr_coinit == S_OK || hr_coinit == S_FALSE) CoUninitialize();
121 
122   return devices;
123 
124 }
125