diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-13 05:43:39 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-13 05:43:39 +0000 |
commit | 19ae07d0d443ff8b777f46bcbe97119483356bfd (patch) | |
tree | dae169167c23ba7c61814101995de21d6abac2e8 /powermanager/powermanage.py | |
download | tde-guidance-19ae07d0d443ff8b777f46bcbe97119483356bfd.tar.gz tde-guidance-19ae07d0d443ff8b777f46bcbe97119483356bfd.zip |
Added KDE3 version of KDE Guidance utilities
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kde-guidance@1102646 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'powermanager/powermanage.py')
-rw-r--r-- | powermanager/powermanage.py | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/powermanager/powermanage.py b/powermanager/powermanage.py new file mode 100644 index 0000000..db62e8c --- /dev/null +++ b/powermanager/powermanage.py @@ -0,0 +1,606 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +########################################################################### +# Copyright (C) 2006 by Sebastian Kügler +# <sebas@kde.org> +# +# Copyright: See COPYING file that comes with this distribution +# +########################################################################### +# An API for changing the powerstate of a notebook + +import dbus +import dbus.glib +import os, time +from dcopext import DCOPClient, DCOPApp # Used for kscreensaver +import xf86misc + +DEBUG = False + +def debug(msg): + """ Print debug message to terminal. """ + if DEBUG: + print msg + +# Default values for actions when battery runs out. +BATTERY_CRITICAL_MINUTES=5 + +# Only do an emergency suspend if charge level percentage is below ... +CHARGE_LEVEL_THRESHOLD = 10 + +isroot = os.environ["USER"] == "root" + +# Send suspend / hibernate commands to HAL or use Sx_COMMANDS +SUSPEND_USE_HAL = True + +# Show the cpu frequency widgets in the tooltip? +SHOW_CPUFREQ = True + + +# Command to initiate suspend-to-disk when not using HAL +S4_COMMAND = "/usr/local/bin/hibernate" +# Command to initiate suspend-to-ram when not using HAL +S3_COMMAND = "/usr/local/bin/s2ram" + +# Override isLaptop method +#IS_LAPTOP = True + +def _readValue(filename, line=0): + """ Reads a single value from the first line of a file. """ + fhandle = open(filename) + value = fhandle.readlines()[line][:-1] + fhandle.close() + return value + +class PowerManage: + """ Class providing low-level power managerment functionality. """ + + def __init__(self): + # (En|Dis)able using hdparm to set disk timeout + self.USE_HDPARM = True + # (En|Dis)able using laptop_mode to make the disk spin up less often + self.USE_LAPTOP_MODE = True + # (En|Dis)able using cpufreq to control cpu frequency scaling + self.USE_CPUFREQ = True + # (En|Dis)able using wireless adapter powermanagement (causes lag in network connection) + self.USE_WI_PM = True + # (En|Dis)able using display powermanagement + self.USE_DPMS = True + # (En|Dis)able using display brightness switching + self.USE_DISPLAY = True + # (En|Dis)able screensaver blankonly + self.SCREENSAVER_BLANKONLY = True + + + try: + xg = xf86misc.XF86Server() + self.xscreen = xg.getDefaultScreen() + except xf86misc.XF86Error: + print "Problem connecting to X server for idletime detection." + # Currently only used in the test method + self.display_dark = 0.5 + self.display_light = 1 + + # Some status initialisations + self.lowBatteryState = False + self.warningBatteryState = False + self.criticalBatteryState = False + + self.criticalBatteryState = False + self.lidClosedState = False + + # What does HAL support on this machine + self.hasBrightness = False + self.hasAC = False + self.hasLid = False + self.hasBattery = False + self.hasCpuFreqGovernors = False + + # Used to track if the previous check reported a battery to determine + # if we want to fire a notice "battery removed|plugged in" + self.wasOnBattery = False + self._initHAL() + self._initBrightness() + self._initBattery() + self._initAc() + self._initLid() + self._checkSuspend() + self._checkCpuCapabilities() + self._findDisks() + + def checkHAL(self): + """ Handle HAL and DBus restarts """ + try: + self.hal_manager.FindDeviceByCapability("") + except dbus.DBusException, e: + if str(e) == 'org.freedesktop.DBus.Error.Disconnected: Connection is closed' \ + or str(e) == 'org.freedesktop.DBus.Error.Disconnected: Connection was disconnected before a reply was received': + # DBus doesn't support on-the-fly restart + print "connection with DBus lost, please restart the display manager" + return + + if os.system("ps aux|grep [h]ald-runner") == 0: + print "connection with HAL lost, trying to reconnect" + self._initHAL() + self._initBrightness() + self._initBattery() + self._initAc() + self._initLid() + self._checkSuspend() + self._checkCpuCapabilities() + else: + print "HAL is not running" + + def isLaptop(self): + """ Detect if system is laptop. """ + try: + return IS_LAPTOP + except NameError: + pass + self.computerObject = self.bus.get_object("org.freedesktop.Hal", + u'/org/freedesktop/Hal/devices/computer') + properties = self.computerObject.GetAllProperties(dbus_interface="org.freedesktop.Hal.Device") + # formfactor sometimes (ppc) also reports "unknown" for laptops + # workaround: consider laptop anything with primary battery (see LP #64053) + return properties["system.formfactor"] == "laptop" or self.hasBattery + + def _findDisks(self): + """ Scan /sys/block for non-removable and non-ramdisks, used for hdparm actions, + currently not implemented in the powermanager frontend. """ + self.disks = [] + blk_path = "/sys/block/" + for d in os.listdir(blk_path): + # No RAM disks, no DM-RAID + if d.startswith("ram") or d.startswith("dm"): + continue + fhandle = open(blk_path+d+"/removable") + if fhandle.readlines()[0][:-1] == "0": + self.disks.append(d) + debug("Detected disks: "+" ".join(self.disks)) + + def onBattery(self): + """ Find out if we're on AC or on battery using HAL. """ + if not self.hasAC: + print "No AC adapter found - assume that we are on batteries." + return False + properties = self.acObject.GetAllProperties(dbus_interface="org.freedesktop.Hal.Device") + if properties.has_key("ac_adapter.present"): + return not properties['ac_adapter.present'] + else: + print "Error: ac_adapter has no property \"present\"" + return False + + def _initBattery(self): + """ Looks for a battery in HAL. """ + batteryDevices = self.hal_manager.FindDeviceByCapability("battery") + self.batteries = {} + self.batteryIsPresent = {} + + numBatt = 0 + for batt in batteryDevices: + battObj = self.bus.get_object("org.freedesktop.Hal", batt) + properties = battObj.GetAllProperties(dbus_interface="org.freedesktop.Hal.Device") + if properties['battery.type'] != "primary": + continue + self.batteries[numBatt] = battObj + self.batteryIsPresent[numBatt] = properties['battery.present'] + numBatt += 1 + + if numBatt > 0: + self.hasBattery = True + else: + self.hasBattery = False + print "No battery found." + + def getBatteryState(self,batt): + """ Read battery status from HAL and return + (battery state, charge percentage, remaining seconds). + """ + try: + properties = self.batteries[batt].GetAllProperties(dbus_interface="org.freedesktop.Hal.Device") + except dbus.DBusException: + print "problem getting battery state from dbus." + return "not present", 0, 0, 0, 0, 0 + + if not properties['battery.present']: + return "not present", 0, 0, 0, 0, 0 + else: + current = full = level = remain = rate = 0 + if properties.has_key("battery.charge_level.current"): + current = properties["battery.charge_level.current"] + if properties.has_key("battery.charge_level.last_full"): + full = properties["battery.charge_level.last_full"] + + if properties["battery.rechargeable.is_charging"]: + state = "charging" + elif properties["battery.rechargeable.is_discharging"]: + if self.onBattery(): + state = "discharging" + else: + state = "charged" + elif not properties["battery.rechargeable.is_discharging"] \ + and not properties["battery.rechargeable.is_charging"]: + if current == 0: + state = "empty" + else: + state = "charged" + else: + print "Unknown battery state ..." + + # Sometimes, HAL doesn't report the percentage, but we can compute that ourselves anyway + if properties.has_key("battery.charge_level.percentage"): + level = properties["battery.charge_level.percentage"] + elif current > 0 and full > 0: + level = current / full + + if state in ("charging","discharging"): + if properties.has_key("battery.remaining_time"): + remain = properties["battery.remaining_time"] + if properties.has_key("battery.charge_level.rate"): + rate = properties["battery.charge_level.rate"] + + return state, level, remain, rate, current, full + + def showInfo(self): + """ Outputs some random information to show that it does not work yet. """ + print "OnBattery:", self.onBattery() + print "CPUs:", len(self.cpus) + + def _initHAL(self): + """ Initialise HAL client to be used later. """ + self.bus = dbus.SystemBus() + hal_manager_obj = self.bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager") + self.hal_manager = dbus.Interface(hal_manager_obj, "org.freedesktop.Hal.Manager") + + def _initLid(self): + """ Find out if there's a Lid device. """ + lidDevice = self.hal_manager.FindDeviceStringMatch("button.type", "lid") + if len(lidDevice) >= 1: + self.hasLid = True + self.lidObject = self.bus.get_object("org.freedesktop.Hal" ,lidDevice[0]) + + def _initAc(self): + """ Search HAL for detecting if power is plugged in. """ + acDevice = self.hal_manager.FindDeviceByCapability("ac_adapter") + if len(acDevice) >= 1: + self.hasAC = True + self.acObject = self.bus.get_object("org.freedesktop.Hal" ,acDevice[0]) + + def _checkSuspend(self): + """ Ask HAL whether we can suspend / hibernate. """ + if SUSPEND_USE_HAL: + self.computerObject = self.bus.get_object("org.freedesktop.Hal", + u'/org/freedesktop/Hal/devices/computer') + properties = self.computerObject.GetAllProperties( + dbus_interface="org.freedesktop.Hal.Device") + self.canSuspend = properties["power_management.can_suspend"] + self.canHibernate = properties["power_management.can_hibernate"] + else: + self.canSuspend = self.canHibernate = True + + def _initBrightness(self): + """ Search HAL for a screen with brightness controls.""" + + brightnessDevice = self.hal_manager.FindDeviceByCapability("laptop_panel") + + if len(brightnessDevice) >= 1: + self.hasBrightness = True + self.brightnessObject = self.bus.get_object("org.freedesktop.Hal", brightnessDevice[0]) + self.brightness_properties = self.brightnessObject.GetAllProperties( + dbus_interface="org.freedesktop.Hal.Device") + try: + self.brightness_levels = self.brightness_properties[u'laptop_panel.num_levels'] + except KeyError,e: + self.hasBrightness = False + return 0 # Really don't know what to do here, but don't crash in any case. + try: + self.old_b = self.brightness_levels[-1] # Setting cached brightness value to brightest + except TypeError,e: + return 0 # Really don't know what to do here, but don't crash in any case. + + def getBrightness(self): + """ Read brightness from HAL. """ + if not self.hasBrightness: + debug("Brightness setting not supported.") + return + try: + b = self.brightnessObject.GetBrightness( + dbus_interface="org.freedesktop.Hal.Device.LaptopPanel") + except dbus.DBusException, e: + # Sometimes, right after resume, the HAL call + # fails, in that case, we return the last value + # and hope that it goes well next time. + print "Warning: in getBrightness(): ", e + # try and return the old brightness setting, but don't die in any case: + try: + return self.old_b + except AttributeError, errmsg: + return + self.old_b = b + return b + + def adjustBrightness(self, level): + """ Adjust the brightness via HAL. """ + if not self.hasBrightness: + debug("Brightness setting not supported.") + return + try: + self.brightnessObject.SetBrightness(level, + dbus_interface="org.freedesktop.Hal.Device.LaptopPanel") + except dbus.DBusException, e: + print e + + def _checkCpuCapabilities(self): + """ Find out the number of CPUs / cores, check which governors are avaible.""" + cpufreq_dir = "/sys/devices/system/cpu" + self.cpus = [] + for cpu in os.listdir(cpufreq_dir): + if cpu.startswith('cpu') and cpu != 'cpuidle': + self.cpus.append(cpu) + self.cpus.sort() + + # Map our policies to cpufreq governors. + self.cpu_policy = {} + self.cpu_policy['dynamic/ac'] = [] + self.cpu_policy['dynamic/battery'] = [] + self.cpu_policy['powersave'] = [] + self.cpu_policy['performance'] = [] + + try: + comp_obj = self.bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/devices/computer') + self.cpufreq = dbus.Interface(comp_obj, 'org.freedesktop.Hal.Device.CPUFreq') + self.governor_available = self.cpufreq.GetCPUFreqAvailableGovernors() + except dbus.DBusException: + return + self.hasCpuFreqGovernors = True + + if 'ondemand' in self.governor_available: + self.cpu_policy['dynamic/ac'].append('ondemand') + self.cpu_policy['dynamic/battery'].append('ondemand') + if 'conservative' in self.governor_available: + self.cpu_policy['dynamic/ac'].append('conservative') + self.cpu_policy['dynamic/battery'].insert(0,'conservative') + if 'userspace' in self.governor_available: + self.cpu_policy['dynamic/ac'].append('userspace') + self.cpu_policy['dynamic/battery'].append('userspace') + if 'powersave' in self.governor_available: + self.cpu_policy['powersave'].append('powersave') + if 'performance' in self.governor_available: + self.cpu_policy['performance'].append('performance') + + def getSupportedCpuPolicies(self): + """ Report a list of supported CPU policies """ + policies = [] + if len(self.cpu_policy['dynamic/ac']) > 0: + policies.append('dynamic') + if len(self.cpu_policy['powersave']) > 0: + policies.append('powersave') + if len(self.cpu_policy['performance']) > 0: + policies.append('performance') + return policies + + def getCpuPolicy(self): + """ Translate current CPU frequency governor into policy """ + if not self.USE_CPUFREQ or not self.hasCpuFreqGovernors: + return "" + gov = self.cpufreq.GetCPUFreqGovernor() + for policy in self.cpu_policy.keys(): + if gov in self.cpu_policy[policy]: + return policy.split('/')[0] # strip ac or battery off + return gov ## return as-is - no conversion + + def setCpuPolicy(self,policy): + """ Using cpufreq governors. Mode is powersave, dynamic or performance. We're assuming that + the available governors are the same for all CPUs. This method changes the cpufreq + governor on all CPUs to a certain policy.""" + if not self.USE_CPUFREQ or not self.hasCpuFreqGovernors: + return False + + if policy == "dynamic": + if self.onBattery(): + policy = "dynamic/battery" + else: + policy = "dynamic/ac" + + for gov in self.cpu_policy[policy]: + try: + self.cpufreq.SetCPUFreqGovernor(gov) + return True + except dbus.DBusException: + pass + return False # no of governor worked + + def cpuIsOnline(self,cpu): + """ Check if cpu is online. CPU0 is always online, CPU1 might be unplugged. Since + /sys/devices/system/cpu/$cpu/cpufreq is not readable for normal users, we just + check for the cpufreq subdir (which is where it's really needed anyway). + """ + if cpu == "cpu0": return True + else: return os.path.isdir("/sys/devices/system/cpu/"+cpu+"/cpufreq") + + def getCpuState(self,cpu): + """ Reads the status of a CPU from /sys. """ + state = {} + state['online'] = self.cpuIsOnline(cpu) + if not state['online']: + debug("getCpuState: "+cpu+" is offline") + return state + try: + state['cpu'] = cpu + state['cur'] = int(_readValue("/sys/devices/system/cpu/"+cpu+"/cpufreq/scaling_cur_freq"))/1000 + state['governor'] = _readValue("/sys/devices/system/cpu/"+cpu+"/cpufreq/scaling_governor") + state['driver'] = _readValue("/sys/devices/system/cpu/"+cpu+"/cpufreq/scaling_driver") + state['steps'] = [] + freqs = _readValue("/sys/devices/system/cpu/"+cpu+"/cpufreq/scaling_available_frequencies") + except IOError: + # CPUFREQ has gone away, let's disable it. + state['online'] = False + return state + for v in freqs.split(): + state['steps'].append(int(v)/1000) + state['max'] = max(state['steps']) + state['min'] = min(state['steps']) + debug(state) + return state + + def getLidClosedState(self): + """ Returns True if the lid is currently closed, or False if it isn't. """ + try: + properties = self.lidObject.GetAllProperties(dbus_interface="org.freedesktop.Hal.Device") + return properties["button.state.value"] + except (KeyError, dbus.DBusException): + return False + + def setPowerSave(self, state): + # No SetPowerSave in Ubuntu's HAL + try: + self.computerObject.SetPowerSave(state, + dbus_interface="org.freedesktop.Hal.Device.SystemPowerManagement") + except dbus.DBusException, e: + print "Warning: While setting SystemPowerManagement to ", state, ": ", + print e + + def blankScreen(self): + """ Call dpms to switch off the screen immediately. """ + os.system('xset dpms force standby') + + def setScreensaverBlankOnly(self,blankonly): + """ Switches a screensaver to blankonly, so cpu hungry screensavers will not drain the poor + battery.""" + # create a new DCOP-Client: + client = DCOPClient() + # connect the client to the local DCOP-server: + client.attach() + # create a DCOP-Application-Object to talk to amarok: + kdesktop = DCOPApp('kdesktop', client) + # call a DCOP-function: + ok, foo = kdesktop.KScreensaverIface.setBlankOnly(blankonly) + if not ok: + debug("Failed to set kdesktop screensaver to blankonly.") + return False + return True + + def getIdleSeconds(self): + """ Get idle seconds from X server. """ + return self.xscreen.getIdleSeconds() + + def resetIdleSeconds(self): + """ Reset idle seconds. """ + return self.xscreen.resetIdleSeconds() + + def test(self): + """ Try all kinds of stuff and see what breaks.""" + print "Trying to adjust brightness ..." + bright = self.getBrightness() + self.adjustBrightness(2) + time.sleep(1) + self.adjustBrightness(bright) + print " ... OK." + + if self.USE_CPUFREQ: + print "Reading speeds from cpufreq..." + for cpu in self.cpus: + print self.getCpuState(cpu) + print "Report supported cpufreq policies..." + for policy in self.cpu_policy.keys(): + print "Policy:", policy, "=", self.cpu_policy[policy] + + print "Trying all cpufreq policies ..." + orig_pol = self.getCpuPolicy() + for pol in self.cpu_policy.keys(): + print ". ", pol + self.setCpuPolicy(pol) + self.setCpuPolicy(orig_pol) + print "... OK." + else: + print "Skipping CPUFREQ: USE_CPUFREQ = False" + + if self.SCREENSAVER_BLANKONLY: + if self.setScreensaverBlankOnly(True): + debug("Manipulating screensaver seems to work well.") + else: + debug("Manipulating screensaver seems broken.") + + if isroot: + print "Trying to use Disk powermanagement and laptop_mode" + self.setDiskPM(True) + time.sleep(1) + self.setDiskPM(False) + print "...OK" + else: + print "Skipping DiskPM, not root." + + if self.hasLid: + if self.getLidClosedState(): + print "Lid is closed." + else: + print "Lid is currently open." + + def setDiskPM(self,on=True): + """ Switches on laptop_mode and sets disks to advanced powermanagement.""" + if self.USE_LAPTOP_MODE: + # Check if laptop_mode exists: + laptop_mode = "/proc/sys/vm/laptop_mode" + if not os.path.isfile(laptop_mode): + self.USE_LAPTOP_MODE = False + debug("Laptop mode not supported, no "+laptop_mode) + else: + fhandle = open(laptop_mode,"w") + if on: val = 1 + else: val = 0 + fhandle.write(str(val)) + fhandle.close() + + if self.USE_HDPARM: + # Set disks to advanced PM + for disk in self.disks: + if on: + # Switch on advanced powermanagement + cmd = "hdparm -B1 /dev/"+disk+" > /dev/null" + else: + # Switch off advanced powermanagement + cmd = "hdparm -B255 /dev/"+disk+" > /dev/null" + if os.system(cmd) != 0: + self.USE_HDPARM = False + print "Switching advanced powermanagement failed, not using hdparm anymore" + + def suspend(self): + """ Run a suspend command, either via HAL or script. """ + if SUSPEND_USE_HAL: + try: + self.computerObject.Suspend(0, dbus_interface="org.freedesktop.Hal.Device.SystemPowerManagement") + except dbus.DBusException: + pass #we get a DBusException: No reply within specified time + else: + self._sleepMode(S3_COMMAND) + + def hibernate(self): + """ Implements suspend to disk (S4). """ + if SUSPEND_USE_HAL: + try: + self.computerObject.Hibernate(dbus_interface="org.freedesktop.Hal.Device.SystemPowerManagement") + except dbus.DBusException: + pass #we get a DBusException: No reply within specified time + else: + self._sleepMode(S4_COMMAND) + + def _sleepMode(self, command): + """ Send the system into S3 or S4 not using HAL. """ + debug("Initiating a sleep cycle") + if os.system(command) != 0: + print "sleepmode failed. ("+command+")" + return False + debug("Everything is dandy") + return True + + def shutdown(self): + """ Shutdown the system via HAL. """ + self.computerObject.Shutdown(dbus_interface="org.freedesktop.Hal.Device.SystemPowerManagement") + + +if __name__ == "__main__": + """ Run some tests, used for debugging.""" + pman = PowerManage() + pman.showInfo() + pman.test() + |