Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
342 lines (306 sloc) 15.8 KB
# coding=utf-8
"""
Implements PowerManagement functions using IOPowerSources.
Requires Mac OS X 10.6+
See doc/darwin for platform-specific details.
"""
__author__ = 'kulakov.ilya@gmail.com'
import weakref
import warnings
import objc
from objc import super
from Foundation import *
from power import common
# Generated in Mac OS X 10.8.2 using the following command:
# gen_bridge_metadata -c '-l/System/Library/Frameworks/IOKit.framework/IOKit -I/System/Library/Frameworks/IOKit.framework/Headers/ps/' /System/Library/Frameworks/IOKit.framework/Headers/ps/IOPowerSources.h /System/Library/Frameworks/IOKit.framework/Headers/ps/IOPSKeys.h
#
# Following keas are added manually, because public headers misses their definitions:
# http://opensource.apple.com/source/IOKitUser/IOKitUser-514.16.50/pwr_mgt.subproj/IOPMLibPrivate.h
# - kIOPMUPSPowerKey
# - kIOPMBatteryPowerKey
# - kIOPMACPowerKey
# - kIOPSProvidesTimeRemaining
IO_POWER_SOURCES_BRIDGESUPPORT = """<?xml version='1.0'?>
<!DOCTYPE signatures SYSTEM "file://localhost/System/Library/DTDs/BridgeSupport.dtd">
<signatures version='1.0'>
<depends_on path="/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation" />
<string_constant name='kIOPSACPowerValue' value='AC Power'/>
<string_constant name='kIOPSBatteryFailureModesKey' value='BatteryFailureModes'/>
<string_constant name='kIOPSBatteryHealthConditionKey' value='BatteryHealthCondition'/>
<string_constant name='kIOPSBatteryHealthKey' value='BatteryHealth'/>
<string_constant name='kIOPSBatteryPowerValue' value='Battery Power'/>
<string_constant name='kIOPSCheckBatteryValue' value='Check Battery'/>
<string_constant name='kIOPSCommandDelayedRemovePowerKey' value='Delayed Remove Power'/>
<string_constant name='kIOPSCommandEnableAudibleAlarmKey' value='Enable Audible Alarm'/>
<string_constant name='kIOPSCommandStartupDelayKey' value='Startup Delay'/>
<string_constant name='kIOPSCurrentCapacityKey' value='Current Capacity'/>
<string_constant name='kIOPSCurrentKey' value='Current'/>
<string_constant name='kIOPSDeadWarnLevelKey' value='Shutdown Level'/>
<string_constant name='kIOPSDesignCapacityKey' value='DesignCapacity'/>
<string_constant name='kIOPSDesignCycleCountKey' value='DesignCycleCount' />
<string_constant name='kIOPSDynamicStorePath' value='/IOKit/PowerSources'/>
<string_constant name='kIOPSFailureCellImbalance' value='Cell Imbalance'/>
<string_constant name='kIOPSFailureChargeFET' value='Charge FET'/>
<string_constant name='kIOPSFailureChargeOverCurrent' value='Charge Over-Current'/>
<string_constant name='kIOPSFailureChargeOverTemp' value='Charge Over-Temperature'/>
<string_constant name='kIOPSFailureDataFlushFault' value='Data Flush Fault'/>
<string_constant name='kIOPSFailureDischargeFET' value='Discharge FET'/>
<string_constant name='kIOPSFailureDischargeOverCurrent' value='Discharge Over-Current'/>
<string_constant name='kIOPSFailureDischargeOverTemp' value='Discharge Over-Temperature'/>
<string_constant name='kIOPSFailureExternalInput' value='Externally Indicated Failure'/>
<string_constant name='kIOPSFailureFuseBlown' value='Fuse Blown'/>
<string_constant name='kIOPSFailureOpenThermistor' value='Open Thermistor'/>
<string_constant name='kIOPSFailurePeriodicAFEComms' value='Periodic AFE Comms'/>
<string_constant name='kIOPSFailurePermanentAFEComms' value='Permanent AFE Comms'/>
<string_constant name='kIOPSFailureSafetyOverVoltage' value='Safety Over-Voltage'/>
<string_constant name='kIOPSFairValue' value='Fair'/>
<string_constant name='kIOPSGoodValue' value='Good'/>
<string_constant name='kIOPSHardwareSerialNumberKey' value='Hardware Serial Number'/>
<string_constant name='kIOPSHealthConfidenceKey' value='HealthConfidence'/>
<string_constant name='kIOPSInternalBatteryType' value='InternalBattery'/>
<string_constant name='kIOPSInternalType' value='Internal'/>
<string_constant name='kIOPSIsChargedKey' value='Is Charged'/>
<string_constant name='kIOPSIsChargingKey' value='Is Charging'/>
<string_constant name='kIOPSIsFinishingChargeKey' value='Is Finishing Charge'/>
<string_constant name='kIOPSIsPresentKey' value='Is Present'/>
<string_constant name='kIOPSLowWarnLevelKey' value='Low Warn Level'/>
<string_constant name='kIOPSMaxCapacityKey' value='Max Capacity'/>
<string_constant name='kIOPSMaxErrKey' value='MaxErr'/>
<string_constant name='kIOPSNameKey' value='Name'/>
<string_constant name='kIOPSNetworkTransportType' value='Ethernet'/>
<string_constant name='kIOPSNotifyLowBattery' value='com.apple.system.powersources.lowbattery'/>
<string_constant name='kIOPSOffLineValue' value='Off Line'/>
<string_constant name='kIOPSPermanentFailureValue' value='Permanent Battery Failure'/>
<string_constant name='kIOPSPoorValue' value='Poor'/>
<string_constant name='kIOPSPowerAdapterCurrentKey' value='Current'/>
<string_constant name='kIOPSPowerAdapterFamilyKey' value='FamilyCode'/>
<string_constant name='kIOPSPowerAdapterIDKey' value='AdapterID'/>
<string_constant name='kIOPSPowerAdapterRevisionKey' value='AdapterRevision'/>
<string_constant name='kIOPSPowerAdapterSerialNumberKey' value='SerialNumber'/>
<string_constant name='kIOPSPowerAdapterSourceKey' value='Source'/>
<string_constant name='kIOPSPowerAdapterWattsKey' value='Watts'/>
<string_constant name='kIOPSPowerSourceIDKey' value='Power Source ID'/>
<string_constant name='kIOPSPowerSourceStateKey' value='Power Source State'/>
<string_constant name='kIOPSProvidesTimeRemaining' value='Battery Provides Time Remaining' />
<string_constant name='kIOPSSerialTransportType' value='Serial'/>
<string_constant name='kIOPSTimeRemainingNotificationKey' value='com.apple.system.powersources.timeremaining'/>
<string_constant name='kIOPSTimeToEmptyKey' value='Time to Empty'/>
<string_constant name='kIOPSTimeToFullChargeKey' value='Time to Full Charge'/>
<string_constant name='kIOPSTransportTypeKey' value='Transport Type'/>
<string_constant name='kIOPSTypeKey' value='Type'/>
<string_constant name='kIOPSUPSManagementClaimed' value='/IOKit/UPSPowerManagementClaimed'/>
<string_constant name='kIOPSUPSType' value='UPS'/>
<string_constant name='kIOPSUSBTransportType' value='USB'/>
<string_constant name='kIOPSVendorDataKey' value='Vendor Specific Data'/>
<string_constant name='kIOPSVoltageKey' value='Voltage'/>
<string_constant name="kIOPMUPSPowerKey" value="UPS Power" />
<string_constant name="kIOPMBatteryPowerKey" value="Battery Power" />
<string_constant name="kIOPMACPowerKey" value="AC Power" />
<enum name='kIOPSLowBatteryWarningEarly' value='2'/>
<enum name='kIOPSLowBatteryWarningFinal' value='3'/>
<enum name='kIOPSLowBatteryWarningNone' value='1'/>
<enum name='kIOPSTimeRemainingUnknown' value='-1'/>
<enum name='kIOPSTimeRemainingUnlimited' value='-2'/>
<function name='IOPSCopyExternalPowerAdapterDetails'>
<retval type='^{__CFDictionary=}' already_retained='true'/>
</function>
<function name='IOPSCopyPowerSourcesInfo'>
<retval type='@' already_retained='true'/>
</function>
<function name='IOPSCopyPowerSourcesList'>
<arg type='@'/>
<retval type='^{__CFArray=}' already_retained='true'/>
</function>
<function name='IOPSGetBatteryWarningLevel'>
<retval type='i'/>
</function>
<function name='IOPSGetPowerSourceDescription'>
<arg type='@'/>
<arg type='@'/>
<retval type='^{__CFDictionary=}'/>
</function>
<function name='IOPSGetProvidingPowerSourceType'>
<arg type='@'/>
<retval type='^{__CFString=}'/>
</function>
<function name='IOPSGetTimeRemainingEstimate'>
<retval type='d'/>
</function>
<function name='IOPSNotificationCreateRunLoopSource'>
<arg type='^?' function_pointer='true'>
<arg type='^v'/>
<retval type='v'/>
</arg>
<arg type='^v'/>
<retval type='^{__CFRunLoopSource=}' already_retained='true'/>
</function>
</signatures>"""
objc.parseBridgeSupport(
IO_POWER_SOURCES_BRIDGESUPPORT,
globals(),
objc.pathForFramework("/System/Library/Frameworks/IOKit.framework")
)
POWER_TYPE_MAP = {
kIOPMACPowerKey: common.POWER_TYPE_AC,
kIOPMBatteryPowerKey: common.POWER_TYPE_BATTERY,
kIOPMUPSPowerKey: common.POWER_TYPE_UPS
}
WARNING_LEVEL_MAP = {
kIOPSLowBatteryWarningNone: common.LOW_BATTERY_WARNING_NONE,
kIOPSLowBatteryWarningEarly: common.LOW_BATTERY_WARNING_EARLY,
kIOPSLowBatteryWarningFinal: common.LOW_BATTERY_WARNING_FINAL
}
class PowerSourcesNotificationsObserver(NSObject):
"""
Manages NSThread instance which is used to run NSRunLoop with only source - IOPSNotificationCreateRunLoopSource.
Thread is automatically spawned when first observer is added and stopped when last observer is removed.
Does not keep strong references to observers.
@note: Method names break PEP8 convention to conform PyObjC naming conventions
"""
def init(self):
self = super(PowerSourcesNotificationsObserver, self).init()
if self is not None:
self._weak_observers = []
self._thread = None
self._lock = objc.object_lock(self)
return self
def startThread(self):
"""Spawns new NSThread to handle notifications."""
if self._thread is not None:
return
self._thread = NSThread.alloc().initWithTarget_selector_object_(self, 'runPowerNotificationsThread', None)
self._thread.start()
def stopThread(self):
"""Stops spawned NSThread."""
if self._thread is not None:
self.performSelector_onThread_withObject_waitUntilDone_('stopPowerNotificationsThread', self._thread, None, objc.YES)
self._thread = None
def runPowerNotificationsThread(self):
"""Main method of the spawned NSThread. Registers run loop source and runs current NSRunLoop."""
pool = NSAutoreleasePool.alloc().init()
@objc.callbackFor(IOPSNotificationCreateRunLoopSource)
def on_power_source_notification(context):
with self._lock:
for weak_observer in self._weak_observers:
observer = weak_observer()
if observer:
observer.on_power_source_notification()
self._source = IOPSNotificationCreateRunLoopSource(on_power_source_notification, None)
CFRunLoopAddSource(NSRunLoop.currentRunLoop().getCFRunLoop(), self._source, kCFRunLoopDefaultMode)
while not NSThread.currentThread().isCancelled():
NSRunLoop.currentRunLoop().runMode_beforeDate_(NSDefaultRunLoopMode, NSDate.distantFuture())
del pool
def stopPowerNotificationsThread(self):
"""Removes the only source from NSRunLoop and cancels thread."""
assert NSThread.currentThread() == self._thread
CFRunLoopSourceInvalidate(self._source)
self._source = None
NSThread.currentThread().cancel()
def addObserver(self, observer):
"""
Adds weak ref to an observer.
@param observer: Instance of class that implements on_power_source_notification()
"""
with self._lock:
self._weak_observers.append(weakref.ref(observer))
if len(self._weak_observers) == 1:
self.startThread()
def removeObserver(self, observer):
"""
Removes an observer.
@param observer: Previously added observer
"""
with self._lock:
self._weak_observers.remove(weakref.ref(observer))
if len(self._weak_observers) == 0:
self.stopThread()
class PowerManagement(common.PowerManagementBase):
notifications_observer = PowerSourcesNotificationsObserver.alloc().init()
def __init__(self, cf_run_loop=None):
"""
@param cf_run_loop: If provided, all notifications are posted within this loop
"""
super(PowerManagement, self).__init__()
self._cf_run_loop = cf_run_loop
def on_power_source_notification(self):
"""
Called in response to IOPSNotificationCreateRunLoopSource() event.
"""
for weak_observer in self._weak_observers:
observer = weak_observer()
if observer:
observer.on_power_sources_change(self)
observer.on_time_remaining_change(self)
def get_providing_power_source_type(self):
"""
Uses IOPSCopyPowerSourcesInfo and IOPSGetProvidingPowerSourceType to get providing power source type.
"""
blob = IOPSCopyPowerSourcesInfo()
type = IOPSGetProvidingPowerSourceType(blob)
return POWER_TYPE_MAP[type]
def get_low_battery_warning_level(self):
"""
Uses IOPSGetBatteryWarningLevel to get battery warning level.
"""
warning_level = IOPSGetBatteryWarningLevel()
return WARNING_LEVEL_MAP[warning_level]
def get_time_remaining_estimate(self):
"""
In Mac OS X 10.7+
Uses IOPSGetTimeRemainingEstimate to get time remaining estimate.
In Mac OS X 10.6
IOPSGetTimeRemainingEstimate is not available.
If providing power source type is AC, returns TIME_REMAINING_UNLIMITED.
Otherwise looks through all power sources returned by IOPSGetProvidingPowerSourceType
and returns total estimate.
"""
if IOPSGetTimeRemainingEstimate is not None: # Mac OS X 10.7+
estimate = float(IOPSGetTimeRemainingEstimate())
if estimate == -1.0:
return common.TIME_REMAINING_UNKNOWN
elif estimate == -2.0:
return common.TIME_REMAINING_UNLIMITED
else:
return estimate / 60.0
else: # Mac OS X 10.6
warnings.warn("IOPSGetTimeRemainingEstimate is not preset", RuntimeWarning)
blob = IOPSCopyPowerSourcesInfo()
type = IOPSGetProvidingPowerSourceType(blob)
if type == common.POWER_TYPE_AC:
return common.TIME_REMAINING_UNLIMITED
else:
estimate = 0.0
for source in IOPSCopyPowerSourcesList(blob):
description = IOPSGetPowerSourceDescription(blob, source)
if kIOPSIsPresentKey in description and description[kIOPSIsPresentKey] and kIOPSTimeToEmptyKey in description and description[kIOPSTimeToEmptyKey] > 0.0:
estimate += float(description[kIOPSTimeToEmptyKey])
if estimate > 0.0:
return float(estimate)
else:
return common.TIME_REMAINING_UNKNOWN
def add_observer(self, observer):
"""
Spawns thread or adds IOPSNotificationCreateRunLoopSource directly to provided cf_run_loop
@see: __init__
"""
super(PowerManagement, self).add_observer(observer)
if len(self._weak_observers) == 1:
if not self._cf_run_loop:
PowerManagement.notifications_observer.addObserver(self)
else:
@objc.callbackFor(IOPSNotificationCreateRunLoopSource)
def on_power_sources_change(context):
self.on_power_source_notification()
self._source = IOPSNotificationCreateRunLoopSource(on_power_sources_change, None)
CFRunLoopAddSource(self._cf_run_loop, self._source, kCFRunLoopDefaultMode)
def remove_observer(self, observer):
"""
Stops thread and invalidates source.
"""
super(PowerManagement, self).remove_observer(observer)
if len(self._weak_observers) == 0:
if not self._cf_run_loop:
PowerManagement.notifications_observer.removeObserver(self)
else:
CFRunLoopSourceInvalidate(self._source)
self._source = None