master ohmu/fastfetch-openbsd / src / detection / battery / battery_apple.c
ohmu  ·  2026-10-15
  1#include "fastfetch.h"
  2#include "battery.h"
  3#include "common/debug.h"
  4#include "common/apple/cf_helpers.h"
  5#include "common/apple/smc_temps.h"
  6
  7#include <IOKit/IOKitLib.h>
  8#include <IOKit/pwr_mgt/IOPM.h>
  9
 10const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
 11    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
 12    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("AppleSmartBattery"), &iterator) != kIOReturnSuccess) {
 13        FF_DEBUG("IOServiceGetMatchingServices() failed");
 14        return "IOServiceGetMatchingServices() failed";
 15    }
 16
 17    io_registry_entry_t registryEntry;
 18    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
 19        FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryBattery = registryEntry;
 20        FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef properties = nullptr;
 21        if (IORegistryEntryCreateCFProperties(entryBattery, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
 22            continue;
 23        }
 24
 25        int currentCapacity, maxCapacity;
 26
 27        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSMaxCapacityKey), &maxCapacity) != nullptr || maxCapacity <= 0) {
 28            continue;
 29        }
 30
 31        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSCurrentCapacityKey), &currentCapacity) != nullptr || currentCapacity <= 0) {
 32            continue;
 33        }
 34
 35        bool boolValue;
 36
 37        FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
 38        battery->temperature = FF_BATTERY_TEMP_UNSET;
 39        ffStrbufInit(&battery->manufacturer);
 40        ffStrbufInit(&battery->modelName);
 41        ffStrbufInit(&battery->serial);
 42        ffStrbufInit(&battery->technology);
 43        ffStrbufInit(&battery->manufactureDate);
 44        battery->status = FF_BATTERY_STATUS_NONE;
 45        battery->capacity = currentCapacity * 100.0 / maxCapacity;
 46        battery->cycleCount = 0;
 47        battery->timeRemaining = -1;
 48
 49        ffCfDictGetString(properties, CFSTR(kIOPMDeviceNameKey), &battery->modelName);
 50        ffCfDictGetString(properties, CFSTR(kIOPMPSSerialKey), &battery->serial);
 51        ffCfDictGetString(properties, CFSTR(kIOPMPSManufacturerKey), &battery->manufacturer);
 52
 53        if (!ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue) {
 54            if (!battery->manufacturer.length) {
 55                ffStrbufAppendS(&battery->manufacturer, "Apple Inc.");
 56            }
 57            ffStrbufAppendS(&battery->technology, "Lithium");
 58            if (!battery->modelName.length) {
 59                ffStrbufAppendS(&battery->modelName, "Built-in");
 60            }
 61        }
 62
 63        int32_t cycleCount = 0;
 64        ffCfDictGetInt(properties, CFSTR(kIOPMPSCycleCountKey), &cycleCount);
 65        battery->cycleCount = cycleCount < 0 ? 0 : (uint32_t) cycleCount;
 66
 67        battery->timeRemaining = -1;
 68        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSExternalConnectedKey), &boolValue) == nullptr) {
 69            if (boolValue) {
 70                battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
 71            } else {
 72                battery->status |= FF_BATTERY_STATUS_DISCHARGING;
 73                ffCfDictGetInt(properties, CFSTR("AvgTimeToEmpty"), &battery->timeRemaining); // in minutes
 74                if (battery->timeRemaining < 0 || battery->timeRemaining >= 0xFFFF) {
 75                    battery->timeRemaining = -1;
 76                } else {
 77                    battery->timeRemaining *= 60;
 78                }
 79            }
 80        }
 81        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSIsChargingKey), &boolValue) == nullptr && boolValue) {
 82            battery->status |= FF_BATTERY_STATUS_CHARGING;
 83        }
 84        if (ffCfDictGetBool(properties, CFSTR(kIOPMPSAtCriticalLevelKey), &boolValue) == nullptr && boolValue) {
 85            battery->status |= FF_BATTERY_STATUS_CRITICAL;
 86        }
 87
 88        int sbdsManufactureDate = 0;
 89        if (ffCfDictGetInt(properties, CFSTR(kIOPMPSManufactureDateKey), &sbdsManufactureDate) == nullptr) {
 90            int day = sbdsManufactureDate & 0b11111;
 91            int month = (sbdsManufactureDate >> 5) & 0b1111;
 92            int year = (sbdsManufactureDate >> 9) + 1800;
 93            ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day);
 94        } else {
 95            CFDictionaryRef batteryData;
 96            if (ffCfDictGetDict(properties, CFSTR("BatteryData"), &batteryData) == nullptr) {
 97                char manufactureDate[sizeof(uint64_t)];
 98                if (ffCfDictGetInt64(batteryData, CFSTR(kIOPMPSManufactureDateKey), (int64_t*) manufactureDate) == nullptr) {
 99                    // https://github.com/AsahiLinux/linux/blob/b5c05cbffb0488c7618106926d522cc3b43d93d5/drivers/power/supply/macsmc_power.c#L410-L419
100                    int year = (manufactureDate[0] - '0') * 10 + (manufactureDate[1] - '0') + 2000 - 8;
101                    int month = (manufactureDate[2] - '0') * 10 + (manufactureDate[3] - '0');
102                    int day = (manufactureDate[4] - '0') * 10 + (manufactureDate[3] - '0');
103                    ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day);
104                }
105            }
106        }
107
108        if (options->temp) {
109            int64_t temp;
110            if (!ffCfDictGetInt64(properties, CFSTR(kIOPMPSBatteryTemperatureKey), &temp)) {
111                battery->temperature = (double) temp / 10 - 273.15;
112            } else {
113                ffDetectSmcTemps(FF_TEMP_BATTERY, &battery->temperature);
114            }
115        }
116    }
117
118    return nullptr;
119}