diff --git a/psutil/__init__.py b/psutil/__init__.py index 12cd2c4cc6..c8da0a3dc2 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -2210,7 +2210,7 @@ def sensors_fans(): __all__.append("sensors_fans") -# Linux, Windows, FreeBSD +# Linux, Windows, FreeBSD, OSX if hasattr(_psplatform, "sensors_battery"): def sensors_battery(): diff --git a/psutil/_psosx.py b/psutil/_psosx.py index e9b6ed8243..567f39960b 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -207,6 +207,25 @@ def disk_partitions(all=False): return retlist +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_battery(): + """Return battery information. + """ + percent, minsleft, power_plugged = cext.sensors_battery() + power_plugged = power_plugged == 1 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + elif minsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = minsleft * 60 + return _common.sbattery(percent, secsleft, power_plugged) + + # ===================================================================== # --- network # ===================================================================== diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index 4675162630..2861b09ba4 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include "_psutil_common.h" #include "_psutil_posix.h" @@ -1788,6 +1790,93 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { } +/* + * Return battery information. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + PyObject *py_retlist = NULL; + CFTypeRef power_info = NULL; + CFArrayRef power_sources_list = NULL; + CFDictionaryRef power_sources_infomation = NULL; + CFNumberRef capacity_ref = NULL; + CFNumberRef time_to_empty_ref = NULL; + CFStringRef ps_state_ref = NULL; + uint32_t capacity; /* units are percent */ + int time_to_empty; /* units are minutes */ + int is_power_plugged; + + power_info = IOPSCopyPowerSourcesInfo(); + + if (!power_info) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to get power sources info"); + goto error; + } + + power_sources_list = IOPSCopyPowerSourcesList(power_info); + if (!power_sources_list) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to get power sources list"); + goto error; + } + + /* Should only get one source. But in practice, check for > 0 sources */ + if (!CFArrayGetCount(power_sources_list)) { + PyErr_SetString(PyExc_RuntimeError, + "Power sources list is empty"); + goto error; + } + + power_sources_infomation = IOPSGetPowerSourceDescription( + power_info, CFArrayGetValueAtIndex(power_sources_list, 0)); + + capacity_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_infomation, CFSTR(kIOPSCurrentCapacityKey)); + if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) { + PyErr_SetString(PyExc_RuntimeError, + "No battery capacity infomration in power sources info"); + goto error; + } + + ps_state_ref = (CFStringRef) CFDictionaryGetValue( + power_sources_infomation, CFSTR(kIOPSPowerSourceStateKey)); + is_power_plugged = CFStringCompare( + ps_state_ref, CFSTR(kIOPSACPowerValue), 0) + == kCFCompareEqualTo; + + time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_infomation, CFSTR(kIOPSTimeToEmptyKey)); + if (!CFNumberGetValue(time_to_empty_ref, + kCFNumberIntType, &time_to_empty)) { + /* This value is recommended for non-Apple power sources, so it's not + * an error if it doesn't exist. We'll return -1 for "unknown" */ + /* A value of -1 indicates "Still Calculating the Time" also for + * apple power source */ + time_to_empty = -1; + } + + py_retlist = Py_BuildValue("Iii", + capacity, time_to_empty, is_power_plugged); + if (!py_retlist) { + goto error; + } + + CFRelease(power_info); + CFRelease(power_sources_list); + /* Caller should NOT release power_sources_infomation */ + + return py_retlist; + +error: + if (power_info) + CFRelease(power_info); + if (power_sources_list) + CFRelease(power_sources_list); + Py_XDECREF(py_retlist); + return NULL; +} + /* * define the psutil C module methods and initialize the module. @@ -1854,6 +1943,8 @@ PsutilMethods[] = { "Return currently connected users as a list of tuples"}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return CPU statistics"}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, // --- others {"set_testing", psutil_set_testing, METH_NOARGS, diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 5e5c2e9a05..4c57b25a88 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -123,7 +123,7 @@ def test_sensors_fans(self): def test_battery(self): self.assertEqual(hasattr(psutil, "sensors_battery"), - LINUX or WINDOWS or FREEBSD) + LINUX or WINDOWS or FREEBSD or OSX) def test_proc_environ(self): self.assertEqual(hasattr(psutil.Process, "environ"),