The current implementation based on `IOPMCopyBatteryInfo` does not work (macOS 13.2, M2 Max, returns `kIOReturnUnsupported`) and is not recommended/supported by Apple:
WARNING! IOPMCoyBatteryInfo is unsupported on ALL Intel CPU based systems. For PPC CPU based systems, it remains not recommended. For almost all purposes, developers should use the richer IOPowerSources API (with change notifications) instead of using IOPMCopyBatteryInfo. Keys to decipher IOPMCopyBatteryInfo's return CFArray exist in IOPM.h.
-- v7: ntdll: Use IOPowerSources API to fill battery info on macOS
From: Marc-Aurel Zent marc_aurel@me.com
Apply 1 suggestion(s) to 1 file(s)
remove unnecessary CFTypeRef casts
handle errors is IOPSCopyPowerSourcesList
always release resources
use CFStringCompare --- dlls/ntdll/unix/system.c | 90 +++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 28 deletions(-)
diff --git a/dlls/ntdll/unix/system.c b/dlls/ntdll/unix/system.c index 33e93bbdf32..31377bd2b04 100644 --- a/dlls/ntdll/unix/system.c +++ b/dlls/ntdll/unix/system.c @@ -57,8 +57,7 @@ #ifdef __APPLE__ # include <CoreFoundation/CoreFoundation.h> # include <IOKit/IOKitLib.h> -# include <IOKit/pwr_mgt/IOPM.h> -# include <IOKit/pwr_mgt/IOPMLib.h> +# include <IOKit/ps/IOPSKeys.h> # include <IOKit/ps/IOPowerSources.h> # include <mach/mach.h> # include <mach/machine.h> @@ -3494,46 +3493,69 @@ static NTSTATUS fill_battery_state( SYSTEM_BATTERY_STATE *bs )
static NTSTATUS fill_battery_state( SYSTEM_BATTERY_STATE *bs ) { - CFArrayRef batteries; - CFDictionaryRef battery; - CFNumberRef prop; - uint32_t value, voltage; - CFTimeInterval remain; - - if (IOPMCopyBatteryInfo( 0, &batteries ) != kIOReturnSuccess) + CFTypeRef blob = IOPSCopyPowerSourcesInfo(); + CFArrayRef sources = IOPSCopyPowerSourcesList( blob ); + CFDictionaryRef source = NULL; + CFTypeRef prop; + Boolean is_charging; + int32_t value, voltage; + + if (!sources) + { + if (blob) CFRelease( blob ); return STATUS_ACCESS_DENIED; + }
- if (CFArrayGetCount( batteries ) == 0) + if (CFArrayGetCount( sources ) == 0) { /* Just assume we're on AC with no battery. */ bs->AcOnLine = TRUE; + CFRelease( blob ); + CFRelease( sources ); return STATUS_SUCCESS; } /* Just use the first battery. */ - battery = CFArrayGetValueAtIndex( batteries, 0 ); - - prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryFlagsKey) ); - CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); + source = IOPSGetPowerSourceDescription( blob, CFArrayGetValueAtIndex( sources, 0 ) ); + + if (!source) + { + CFRelease( blob ); + CFRelease( sources ); + return STATUS_ACCESS_DENIED; + }
- if (value & kIOBatteryInstalled) + prop = CFDictionaryGetValue( source, CFSTR(kIOPSTypeKey) ); + + if (!CFStringCompare( prop, CFSTR(kIOPSInternalBatteryType), 0 )) bs->BatteryPresent = TRUE; else /* Since we are executing code, we must have AC power. */ bs->AcOnLine = TRUE; - if (value & kIOBatteryChargerConnect) + + prop = CFDictionaryGetValue( source, CFSTR(kIOPSIsChargingKey) ); + is_charging = CFBooleanGetValue( prop ); + + prop = CFDictionaryGetValue( source, CFSTR(kIOPSPowerSourceStateKey) ); + + if (!CFStringCompare( prop, CFSTR(kIOPSACPowerValue), 0 )) { bs->AcOnLine = TRUE; - if (value & kIOBatteryCharge) + if (is_charging) bs->Charging = TRUE; } else bs->Discharging = TRUE;
/* We'll need the voltage to be able to interpret the other values. */ - prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryVoltageKey) ); - CFNumberGetValue( prop, kCFNumberSInt32Type, &voltage ); + prop = CFDictionaryGetValue( source, CFSTR(kIOPSVoltageKey) ); + if (prop) + CFNumberGetValue( prop, kCFNumberSInt32Type, &voltage ); + else + /* kIOPSVoltageKey is optional and might not be populated. + * Assume 11.4 V then, which is a common value for Apple laptops. */ + voltage = 11400;
- prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryCapacityKey) ); + prop = CFDictionaryGetValue( source, CFSTR(kIOPSMaxCapacityKey) ); CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); bs->MaxCapacity = value * voltage; /* Apple uses "estimated time < 10:00" and "22%" for these, but we'll follow @@ -3541,19 +3563,31 @@ static NTSTATUS fill_battery_state( SYSTEM_BATTERY_STATE *bs ) bs->DefaultAlert1 = bs->MaxCapacity / 20; bs->DefaultAlert2 = bs->MaxCapacity / 3;
- prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryCurrentChargeKey) ); + prop = CFDictionaryGetValue( source, CFSTR(kIOPSCurrentCapacityKey) ); CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); bs->RemainingCapacity = value * voltage;
- prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryAmperageKey) ); - CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); - bs->Rate = value * voltage; + prop = CFDictionaryGetValue( source, CFSTR(kIOPSCurrentKey) ); + if (prop) + CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); + else + /* kIOPSCurrentKey is optional and might not be populated. */ + value = 0; + + bs->Rate = value * voltage / 1000;
- remain = IOPSGetTimeRemainingEstimate(); - if (remain != kIOPSTimeRemainingUnknown && remain != kIOPSTimeRemainingUnlimited) - bs->EstimatedTime = (ULONG)remain; + prop = CFDictionaryGetValue( source, CFSTR(kIOPSTimeToEmptyKey) ); + if (prop) + { + CFNumberGetValue( prop, kCFNumberSInt32Type, &value ); + if (value > 0) + /* A value of -1 indicates "Still Calculating the Time", + * otherwise estimated minutes left on the battery. */ + bs->EstimatedTime = value * 60; + }
- CFRelease( batteries ); + CFRelease( blob ); + CFRelease( sources ); return STATUS_SUCCESS; }
Brendan Shanks (@bshanks) commented about dlls/ntdll/unix/system.c:
static NTSTATUS fill_battery_state( SYSTEM_BATTERY_STATE *bs ) {
- CFArrayRef batteries;
- CFDictionaryRef battery;
- CFNumberRef prop;
- uint32_t value, voltage;
- CFTimeInterval remain;
- if (IOPMCopyBatteryInfo( 0, &batteries ) != kIOReturnSuccess)
- CFTypeRef blob = IOPSCopyPowerSourcesInfo();
- CFArrayRef sources = IOPSCopyPowerSourcesList( blob );
- CFDictionaryRef source = NULL;
- CFTypeRef prop;
- Boolean is_charging;
- int32_t value, voltage;
All the blank lines in the patch have spaces, they should be empty
Brendan Shanks (@bshanks) commented about dlls/ntdll/unix/system.c:
- CFTimeInterval remain;
- if (IOPMCopyBatteryInfo( 0, &batteries ) != kIOReturnSuccess)
- CFTypeRef blob = IOPSCopyPowerSourcesInfo();
- CFArrayRef sources = IOPSCopyPowerSourcesList( blob );
- CFDictionaryRef source = NULL;
- CFTypeRef prop;
- Boolean is_charging;
- int32_t value, voltage;
- if (!sources)
- {
if (blob) CFRelease( blob ); return STATUS_ACCESS_DENIED;
- }
You can `CFRelease(blob)` at this point, and then remove all the calls from later in the function
Brendan Shanks (@bshanks) commented about dlls/ntdll/unix/system.c:
- if (!CFStringCompare( prop, CFSTR(kIOPSACPowerValue), 0 )) { bs->AcOnLine = TRUE;
if (value & kIOBatteryCharge)
if (is_charging) bs->Charging = TRUE;
} else bs->Discharging = TRUE;
/* We'll need the voltage to be able to interpret the other values. */
- prop = CFDictionaryGetValue( battery, CFSTR(kIOBatteryVoltageKey) );
- CFNumberGetValue( prop, kCFNumberSInt32Type, &voltage );
- prop = CFDictionaryGetValue( source, CFSTR(kIOPSVoltageKey) );
- if (prop)
CFNumberGetValue( prop, kCFNumberSInt32Type, &voltage );
The documentation for all these keys says that the type is `kCFNumberIntType` (equivalent to SInt32, but it would be better to use the documented type)
Thanks for this, I hadn't realized `IOPMCopyBatteryInfo` was so deprecated.
Looking at [Chromium's use of `IOPSCopyPowerSources`](https://source.chromium.org/chromium/chromium/src/+/main:services/device/bat...), they first check that the source is present and internal, which seems like a good idea.