master ohmu/fastfetch-openbsd / src / detection / sound / sound_windows.cpp
ohmu  ·  2026-10-15
  1extern "C" {
  2#include "sound.h"
  3#include "common/debug.h"
  4#include "common/windows/com.h"
  5}
  6#include "common/windows/unicode.hpp"
  7#include "common/windows/variant.hpp"
  8
  9#include <initguid.h>
 10#include <mmdeviceapi.h>
 11#include <endpointvolume.h>
 12#include <functiondiscoverykeys_devpkey.h>
 13
 14static void ffCoTaskMemFreeWrapper(void* pptr) {
 15    assert(pptr != nullptr);
 16    void* ptr = *(void**) pptr;
 17    if (ptr) {
 18        CoTaskMemFree(ptr);
 19    }
 20}
 21#define FF_COTASK_AUTO_FREE [[gnu::cleanup(ffCoTaskMemFreeWrapper)]]
 22
 23static const char* detectSoundDevice(FFlist* devices /* List of FFSoundDevice */, IMMDevice* immDevice, LPWSTR mainDeviceId) {
 24    FF_COTASK_AUTO_FREE LPWSTR immDeviceId = nullptr;
 25    HRESULT hr = immDevice->GetId(&immDeviceId);
 26    if (FAILED(hr)) {
 27        FF_DEBUG("immDevice->GetId() failed: %s", ffDebugHResult(hr));
 28        return "immDevice->GetId() failed";
 29    }
 30
 31    FF_AUTO_RELEASE_COM_OBJECT IPropertyStore* immPropStore = nullptr;
 32    hr = immDevice->OpenPropertyStore(STGM_READ, &immPropStore);
 33    if (FAILED(hr)) {
 34        FF_DEBUG("immDevice->OpenPropertyStore() failed: %s", ffDebugHResult(hr));
 35        return "immDevice->OpenPropertyStore() failed";
 36    }
 37
 38    DWORD immState;
 39    hr = immDevice->GetState(&immState);
 40    if (FAILED(hr)) {
 41        FF_DEBUG("immDevice->GetState() failed: %s", ffDebugHResult(hr));
 42        return "immDevice->GetState() failed";
 43    }
 44
 45    FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
 46    device->type = (FFSoundType) ((!mainDeviceId || wcscmp(immDeviceId, mainDeviceId) == 0 ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) |
 47        ((immState & DEVICE_STATE_ACTIVE) ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE));
 48    device->volume = FF_SOUND_VOLUME_UNKNOWN;
 49    ffStrbufInitWS(&device->identifier, immDeviceId);
 50    ffStrbufInit(&device->name);
 51    ffStrbufInitStatic(&device->platformApi, "Core Audio APIs");
 52
 53    {
 54        FFPropVariant friendlyName;
 55        if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_FriendlyName, &friendlyName))) {
 56            ffStrbufSetWSV(&device->name, friendlyName.get<std::wstring_view>());
 57        } else if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_DeviceDesc, &friendlyName))) {
 58            ffStrbufSetWSV(&device->name, friendlyName.get<std::wstring_view>());
 59        } else {
 60            ffStrbufSetStatic(&device->name, "Unknown Device");
 61        }
 62    }
 63
 64    FF_AUTO_RELEASE_COM_OBJECT IAudioEndpointVolume* immEndpointVolume = nullptr;
 65    if (SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, nullptr, (void**) &immEndpointVolume))) {
 66        BOOL muted;
 67        if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted) {
 68            FLOAT volume;
 69            if (SUCCEEDED(immEndpointVolume->GetMasterVolumeLevelScalar(&volume))) {
 70                device->volume = (uint8_t) (volume * 100 + 0.5);
 71            }
 72        }
 73    }
 74
 75    return nullptr;
 76}
 77
 78const char* ffDetectSound(FFSoundOptions* options, FFlist* devices /* List of FFSoundDevice */) {
 79    const char* error = ffInitCom();
 80    if (error) {
 81        return error;
 82    }
 83
 84    FF_AUTO_RELEASE_COM_OBJECT IMMDeviceEnumerator*  pEnum = nullptr;
 85
 86    HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pEnum));
 87    if (FAILED(hr)) {
 88        FF_DEBUG("CoCreateInstance(CLSID_MMDeviceEnumerator) failed: %s", ffDebugHResult(hr));
 89        return "CoCreateInstance(CLSID_MMDeviceEnumerator) failed";
 90    }
 91
 92    FF_COTASK_AUTO_FREE LPWSTR mainDeviceId = nullptr;
 93
 94    {
 95        FF_AUTO_RELEASE_COM_OBJECT IMMDevice* pDefaultDevice = nullptr;
 96
 97        HRESULT hrDefault = pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDefaultDevice);
 98        if (FAILED(hrDefault)) {
 99            FF_DEBUG("GetDefaultAudioEndpoint() failed: %s", ffDebugHResult(hrDefault));
100            return "GetDefaultAudioEndpoint() failed";
101        }
102
103        if (options->soundType & FF_SOUND_TYPE_MAIN) {
104            return detectSoundDevice(devices, pDefaultDevice, nullptr);
105        }
106
107        hrDefault = pDefaultDevice->GetId(&mainDeviceId);
108        if (FAILED(hrDefault)) {
109            FF_DEBUG("pDefaultDevice->GetId() failed: %s", ffDebugHResult(hrDefault));
110            return "pDefaultDevice->GetId() failed";
111        }
112    }
113
114    FF_AUTO_RELEASE_COM_OBJECT IMMDeviceCollection* pDevices = nullptr;
115
116    hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | (options->soundType & FF_SOUND_TYPE_ACTIVE ? 0 : DEVICE_STATE_DISABLED), &pDevices);
117    if (FAILED(hr)) {
118        FF_DEBUG("EnumAudioEndpoints() failed: %s", ffDebugHResult(hr));
119        return "EnumAudioEndpoints() failed";
120    }
121
122    uint32_t deviceCount;
123    hr = pDevices->GetCount(&deviceCount);
124    if (FAILED(hr)) {
125        FF_DEBUG("pDevices->GetCount() failed: %s", ffDebugHResult(hr));
126        return "pDevices->GetCount() failed";
127    }
128
129    for (uint32_t deviceIdx = 0; deviceIdx < deviceCount; ++deviceIdx) {
130        FF_AUTO_RELEASE_COM_OBJECT IMMDevice* immDevice = nullptr;
131        if (FAILED(pDevices->Item(deviceIdx, &immDevice))) {
132            continue;
133        }
134
135        detectSoundDevice(devices, immDevice, mainDeviceId);
136    }
137
138    return nullptr;
139}