master ohmu/fastfetch-openbsd / src / detection / sound / sound_apple.c
ohmu  ·  2026-10-15
  1#include "sound.h"
  2#include "common/apple/cf_helpers.h"
  3#include "common/debug.h"
  4
  5#include <CoreAudio/CoreAudio.h>
  6#include <AvailabilityMacros.h>
  7
  8#ifndef MAC_OS_VERSION_12_0
  9    #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
 10#endif
 11
 12const char* ffDetectSound(FFSoundOptions* options, FFlist* devices /* List of FFSoundDevice */) {
 13    AudioDeviceID mainDeviceId;
 14    UInt32 dataSize = sizeof(mainDeviceId);
 15    OSStatus defaultOutputStatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress) { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &mainDeviceId);
 16    if (defaultOutputStatus != kAudioHardwareNoError) {
 17        FF_DEBUG("AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed: OSStatus %d", (int) defaultOutputStatus);
 18        return "AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed";
 19    }
 20
 21    AudioObjectID deviceIds[32] = {};
 22    if (options->soundType & FF_SOUND_TYPE_MAIN) {
 23        deviceIds[0] = mainDeviceId;
 24        dataSize = sizeof(mainDeviceId);
 25    } else {
 26        dataSize = sizeof(deviceIds);
 27        OSStatus deviceListStatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress) { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &deviceIds);
 28        if (deviceListStatus != kAudioHardwareNoError) {
 29            FF_DEBUG("AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed: OSStatus %d", (int) deviceListStatus);
 30            return "AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed";
 31        }
 32    }
 33
 34    for (uint32_t index = 0, length = dataSize / sizeof(*deviceIds); index < length; ++index) {
 35        AudioDeviceID deviceId = deviceIds[index];
 36
 37        // Ignore input devices
 38        if (AudioObjectGetPropertyDataSize(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyStreams, kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize) == kAudioHardwareNoError && dataSize > 0) {
 39            continue;
 40        }
 41
 42        uint32_t dataSource;
 43        dataSize = sizeof(dataSource);
 44        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDataSource, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &dataSource) == kAudioHardwareNoError && dataSource == 'hdpn') {
 45            uint32_t connected;
 46            dataSize = sizeof(connected);
 47            if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyJackIsConnected, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &connected) == kAudioHardwareNoError) {
 48                if (!connected) {
 49                    continue;
 50                }
 51            }
 52        }
 53
 54        uint32_t active = true;
 55        dataSize = sizeof(active);
 56        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &active) == kAudioHardwareNoError) {
 57            if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !active) {
 58                continue;
 59            }
 60        }
 61
 62        FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
 63        device->type = (deviceId == mainDeviceId ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) |
 64            (active ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE);
 65        device->volume = FF_SOUND_VOLUME_UNKNOWN;
 66        ffStrbufInit(&device->identifier);
 67        ffStrbufInit(&device->name);
 68        ffStrbufInitStatic(&device->platformApi, "Core Audio");
 69
 70        FF_CFTYPE_AUTO_RELEASE CFStringRef uid = nullptr;
 71        dataSize = sizeof(uid);
 72        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &uid) == kAudioHardwareNoError) {
 73            ffCfStrGetString(uid, &device->identifier);
 74        } else {
 75            ffStrbufAppendF(&device->identifier, "ID-%u", (unsigned) deviceId);
 76        }
 77
 78        FF_CFTYPE_AUTO_RELEASE CFStringRef name = nullptr;
 79        dataSize = sizeof(name);
 80        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioObjectPropertyName, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &name) == kAudioHardwareNoError) {
 81            ffCfStrGetString(name, &device->name);
 82        } else {
 83            ffStrbufSet(&device->name, &device->identifier);
 84        }
 85
 86        uint32_t muted;
 87        dataSize = sizeof(muted);
 88        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &muted) != kAudioHardwareNoError) {
 89            muted = false; // Device may not support volume control
 90        }
 91
 92        if (muted) {
 93            device->volume = 0;
 94        } else {
 95            float volume;
 96            dataSize = sizeof(volume);
 97            if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &volume) == kAudioHardwareNoError) {
 98                device->volume = (uint8_t) (volume * 100 + 0.5);
 99            } else {
100                // Try detecting volume from channels
101                uint32_t channels[2];
102                dataSize = sizeof(channels);
103                if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyPreferredChannelsForStereo, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, channels) == kAudioHardwareNoError) {
104                    dataSize = sizeof(volume);
105                    if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[0] }, 0, nullptr, &dataSize, &volume) == kAudioHardwareNoError) {
106                        float temp;
107                        if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[1] }, 0, nullptr, &dataSize, &temp) == kAudioHardwareNoError) {
108                            device->volume = (uint8_t) ((volume + temp) / 2 * 100 + 0.5);
109                        }
110                    }
111                }
112            }
113        }
114    }
115
116    return nullptr;
117}