diff options
Diffstat (limited to 'powermanager/guidance-power-manager.py')
-rwxr-xr-x | powermanager/guidance-power-manager.py | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/powermanager/guidance-power-manager.py b/powermanager/guidance-power-manager.py new file mode 100755 index 0000000..41df5e8 --- /dev/null +++ b/powermanager/guidance-power-manager.py @@ -0,0 +1,1134 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +""" +Copyright 2006-2007 Sebastian Kügler, Canonical Ltd, Luka Renko + +Authors: + Sebastian Kügler <sebas@kde.org> + Jonathan Riddell <jriddell@ubuntu.com> + Luka Renko <lure@kubuntu.org> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +""" + +""" +A frontend to HAL's power features for KDE. +Supports screen brightness, battery level, plugged/unplugged notices, laptop lid closing actions +Specification at https://wiki.kubuntu.org/KubuntuPowerManagement + +Issues: + - We have to keep polling HAL rather than listening for signals because the Python DBUS bindings + don't have Qt mainloop integration + - Written in Python so will be slow to load up, will probably port to C++ Qt 4.2 in future + - Should also handle UPS and bluetooth batteries + - systray applet should be hidden if no battery, but then how do you suspend if no battery? + (ksmserver integration please) + - Needs lots more testing + - Use KUniqueApplication again as soon as dcop problem is sorted outc + - dcop calls need patch to dcopexport.py, already submitted upstream +""" + +import os +import sys +import subprocess +import dbus + +from qt import * +from kdecore import * +from kdeui import * + +from dcopext import DCOPClient, DCOPApp # used to lock the screen +from dcopexport import DCOPExObj + +from guidance_power_manager_ui import PowerManagerUI +from notify import NotifyWidget +from tooltip import ToolTip + +from powermanage import * + +POLL_INTERVAL = 5000 # in milliseconds + +class Notify(NotifyWidget): + """ Pop up a passive notication windows. """ + + def __init__(self,parent,msg,icon,caption=None): + NotifyWidget.__init__(self,parent,"notify") + self.setIcon(icon) + self.setText(msg) + if caption: + self.Caption(caption) + + def setIcon(self,pixmap): + """ Set an icon to be displayed in the notification. """ + if pixmap: + self.Icon.setPixmap(pixmap) + + def setCaption(self,caption): + """ Text to show in bold letters. """ + self.Caption.setText(QString("<b>")+caption+QString("</b>")) + + def setText(self,msg): + """" Set actual notification message. """ + self.Text.setText(msg) + + +class PowerManager(PowerManagerUI): + """ Our configuration dialog. """ + + def __init__ (self, parent, name): + PowerManagerUI.__init__(self, parent, name) + KGlobal.iconLoader().addAppDir("guidance") + + # The systray icon should show and hide the KDialogBase, not only this widget, + # therefore, it gets our parent as parent. + self.systray = KSystemTray(parent) + self.icon = "battery-charging-100" + self.systray.setPixmap(QPixmap(UserIcon(self.icon))) + self.connect(self.systray, SIGNAL("quitSelected()"), self.quit) + + # Configuration filename + self.config = KConfig("power-managerrc") + + self.powermanager = PowerManage() + + def prepare(self): + """ Prepare UI. """ + self._initBrightness() + self._initLid() + self._initBattery() + self.lastidlesec = 0 + + self._initConfigKeywords() + + self._initUI(self.parent()) + + self.configToUi() + + # Polling: evil. can't receive signals in python-dbus unless we have a glib mainloop, + # so we need to poll + self.pollTimer = QTimer(self) + self.connect(self.pollTimer, SIGNAL("timeout()"), self.poll) + self.pollTimer.start(POLL_INTERVAL) # 5 second poll, maybe make this configurable + self.poll(False) + + # check CPU freq policy and notify if it was changed + msg = self.checkCpuFreq() + if msg != "": + self.notify(msg) + + self.systray.show() + + def _initBrightness(self): + """ Check for brightness support and disable widgets if it's not there. """ + if not self.powermanager.hasBrightness: + self.PoweredBrightnessLabel.hide() + self.PoweredBrightnessSlider.hide() + self.BatteryBrightnessLabel.hide() + self.BatteryBrightnessSlider.hide() + + def _initLid(self): + """ Check for lid support and disable widgets if it's not there. """ + if not self.powermanager.hasLid: + self.LaptopLidRadios.setEnabled(False) + + def _initCB(self, combo, options, values): + """ Initialize QComboBox with proper values from provided options. """ + combo.clear() + for option in options: + combo.insertItem(values[option]) + + def _getCB(self, combo, options): + """ Get current item from QComboBox from config file (string) value. """ + try: + return options[combo.currentItem()] + except IndexError: + return "" + + def _setCB(self, combo, options, default, value): + """ Set current item in QComboBox from string value. """ + try: + num = options.index(value) + except ValueError: + num = default + pass + combo.setCurrentItem(num) + + def _getRB(self, radios, options): + """ Get current item from QRadioButton from config file (string) value. """ + try: + return options[radios.selectedId()] + except IndexError: + return "" + + def _setRB(self, radios, options, default, value): + """ Set current item in QRadioButton from string value. """ + try: + num = options.index(value) + except ValueError: + num = default + pass + radios.setButton(num) + + def _checkOldConfig(self, value, blank): + """ Convert old numerical values to keywords. """ + try: + num_val = int(value) + except ValueError: + return value + if blank: + if num_val == 0: return 'nothing' + if num_val == 1: return 'blank' + if num_val == 2: return 'suspend' + if num_val == 3: return 'hibernate' + if num_val == 4: return 'shutdown' + else: + if num_val == 0: return 'nothing' + if num_val == 1: return 'suspend' + if num_val == 2: return 'hibernate' + if num_val == 3: return 'shutdown' + return value + + + def _initConfigKeywords(self): + """ Define helper maps used with config file keywords. """ + # map action keyword to displayed name (l10n) + self.act_name = {} + self.act_name['nothing'] = i18n("Do nothing") + self.act_name['blank'] = i18n("Blank screen") + self.act_name['suspend'] = i18n("Suspend") + self.act_name['hibernate'] = i18n("Hibernate") + self.act_name['shutdown'] = i18n("Shutdown") + + # map action keyword to action methods + self.act_call = {} + self.act_call['nothing'] = None + self.act_call['blank'] = self.blankScreen + self.act_call['suspend'] = self.suspend + self.act_call['hibernate'] = self.hibernate + self.act_call['shutdown'] = self.shutdown + + # map action keyword to notification description (l10n) + self.act_notify = {} + self.act_notify['nothing'] = i18n("doing nothing") + self.act_notify['blank'] = i18n("blanking screen") + self.act_notify['suspend'] = i18n("suspending") + self.act_notify['hibernate'] = i18n("hibernating") + self.act_notify['shutdown'] = i18n("shutting down") + + # map action keyword to action icon used in notification window + self.act_icon = {} + self.act_icon['nothing'] = None + self.act_icon['blank'] = None + self.act_icon['suspend'] = SmallIcon("suspend") + self.act_icon['hibernate'] = SmallIcon("hibernate") + self.act_icon['shutdown'] = SmallIcon("exit") + + # map policy keyword to displayed name (l10n) + self.freq_name = {} + self.freq_name['dynamic'] = i18n("Dynamic") + self.freq_name['powersave'] = i18n("Powersave") + self.freq_name['performance'] = i18n("Performance") + + # map policy keyword to policy change methods + self.freq_call = {} + self.freq_call['dynamic'] = self.setCpuPolicyDynamic + self.freq_call['powersave'] = self.setCpuPolicyPowersave + self.freq_call['performance'] = self.setCpuPolicyPerformance + + + def _initUI(self, parent): + """ Build dynamic parts of the UI: context menu and tooltip. """ + self.canSuspend = self.powermanager.canSuspend and not self.config.readBoolEntry("disableSuspend", False) + self.canHibernate = self.powermanager.canHibernate and not self.config.readBoolEntry("disableHibernate", False) + + # Connect some signals. Updates in the dialogue apply instantly + self.connect(self.PoweredBrightnessSlider, SIGNAL("valueChanged(int)"), self.changePoweredBrightness) + self.connect(self.BatteryBrightnessSlider, SIGNAL("valueChanged(int)"), self.changeBatteryBrightness) + + #Add a blank tooltip, the tooltipgroup signals are then used for our KPassivePopup + toolTipGroup = QToolTipGroup(self.systray) + QToolTip.add(self.systray, "", toolTipGroup, "blah") + self.connect(toolTipGroup, SIGNAL("showTip(const QString&)"), self.showTip) + self.connect(toolTipGroup, SIGNAL("removeTip()"), self.hideTip) + + # Popup tooltip showing battery level + self.popup = KPassivePopup(self.systray) + + self.tooltip = ToolTip(self.popup) + + self._addBatteryWidgets() + + self._addCpuWidgets() + self.popup.setView(self.tooltip) + + # fill actions for LID + self.lid_act = ['nothing', 'blank', 'suspend', 'hibernate', 'shutdown'] + self.lid_act_def = 0 + # hide LID close actions that are not supported + if not self.canSuspend: + self.laptopClosedSuspend.hide() + if not self.canHibernate: + self.laptopClosedHibernate.hide() + + # fill in only CPU policies that are supported by HW + self.cb_freq = [] # list of supported cpu freq policies + self.cb_freq_def = 0 # always use first policy as default + if self.powermanager.hasCpuFreqGovernors: + self.cb_freq = self.powermanager.getSupportedCpuPolicies() + if len(self.cb_freq) > 0: + self._initCB(self.PoweredFreqCombo, self.cb_freq, self.freq_name) + self._initCB(self.BatteryFreqCombo, self.cb_freq, self.freq_name) + else: + self.PoweredFreqLabel.hide() + self.PoweredFreqCombo.hide() + self.BatteryFreqLabel.hide() + self.BatteryFreqCombo.hide() + + # fill actions in Idle/Critical battery combo boxes + self.cb_act = ['nothing'] # list of supported actions (keywords) + self.cb_act_def_critical = 0 # default action when critical battery + if self.canSuspend: + self.cb_act.append('suspend') + if self.canHibernate: + self.cb_act.append('hibernate') + self.cb_act_def_critical = len(self.cb_act) - 1 # hibernate + self.cb_act.append('shutdown') + if self.cb_act_def_critical == 0: + self.cb_act_def_critical = len(self.cb_act) - 1 # shutdown + self._initCB(self.PoweredIdleCombo, self.cb_act, self.act_name) + self._initCB(self.BatteryIdleCombo, self.cb_act, self.act_name) + self._initCB(self.BatteryCriticalCombo, self.cb_act, self.act_name) + + self.connect(self.PoweredIdleCombo,SIGNAL("activated(int)"),self.slotPoweredIdleActivated) + self.connect(self.BatteryIdleCombo,SIGNAL("activated(int)"),self.slotBatteryIdleActivated) + self.connect(self.BatteryCriticalCombo,SIGNAL("activated(int)"),self.slotBatteryCriticalActivated) + + # add suspend/hibernate to tray's context menu + menu = self.systray.contextMenu() + if self.canSuspend: + action = KAction( i18n("Suspend"), KShortcut(), self.suspend, + self.systray.actionCollection(), "suspend") + action.setIcon("suspend") + action.plug(menu) + if self.canHibernate: + action = KAction( i18n("Hibernate"), KShortcut(), self.hibernate, + self.systray.actionCollection(), "hibernate") + action.setIcon("hibernate") + action.plug(menu) + + # add list of governators + if self.powermanager.hasCpuFreqGovernors and len(self.cb_freq) > 0: + submenu = KPopupMenu(menu) + for policy in self.cb_freq: + action = KRadioAction(self.freq_name[policy], KShortcut(), + self.freq_call[policy], + self.systray.actionCollection(), policy) + action.setExclusiveGroup("freqs") + action.plug(submenu) + + policy = self.powermanager.getCpuPolicy() + if policy in self.cb_freq: + self.systray.actionCollection().action(policy).setChecked(True); + menu.insertItem(i18n("CPU policy"), submenu) + + + # KGlobalAccel crashes the application in pykde + # see http://mats.gmd.de/pipermail/pykde/2006-May/013224.html + #self.globalActions = KGlobalAccel(self) + #self.suspendShortcut = KShortcut("XF86Sleep") + #self.hibernateShortcut = KShortcut("XF86Standby") + #self.hshutdownShortcut = KShortcut("XF86PowerOff") + #self.globalActions.insert("suspend", i18n("Suspend"), i18n("what's this?"), self.suspendShortcut, #self.suspendShortcut, self.suspend) + #self.globalActions.updateConnections() + + def _initBattery(self): + """ Remove non-battery-related widgets if there's no battery bay. """ + if not self.powermanager.hasBattery: + # Disable the Batterybox in the config dialogue, + self.BatteryBox.setEnabled(False) + # And change the icon in the systray, remove the restore option + # This way, we're basically becoming a systray applet, you can + # hibernate and suspend from + self.systray.setPixmap(QPixmap(UserIcon(self.icon))) + if self.powermanager.hasAC: + self.wasOnBattery = self.powermanager.onBattery() + + def configToUi(self): + """ Setup the the values from the config file in the UI.""" + # brightness. + if self.powermanager.hasBrightness: + brightness_high = self.powermanager.brightness_levels + self.BatteryBrightnessSlider.setMaxValue(self.powermanager.brightness_levels-1) + self.PoweredBrightnessSlider.setMaxValue(self.powermanager.brightness_levels-1) + self.BatteryBrightnessSlider.setValue(self.config.readNumEntry("batteryBrightness", int(brightness_high/2))) #default middle + self.PoweredBrightnessSlider.setValue(self.config.readNumEntry("poweredBrightness", brightness_high)) #default highest + + tt_text = "Every step increases or decreases the brightness by %i%%" % int(100/brightness_high) + QToolTip.add(self.BatteryBrightnessSlider, tt_text) + QToolTip.add(self.PoweredBrightnessSlider, tt_text) + + self.lockScreenOnResume.setChecked(self.config.readBoolEntry("lockOnResume", True)) + + # Idletime-related configuration + self._setCB(self.PoweredIdleCombo, self.cb_act, 0, str(self.config.readEntry("poweredIdleAction"))) + self.PoweredIdleTime.setValue(self.config.readNumEntry("poweredIdleTime", 60)) + self._setCB(self.BatteryIdleCombo, self.cb_act, 0, str(self.config.readEntry("batteryIdleAction"))) + self.BatteryIdleTime.setValue(self.config.readNumEntry("batteryIdleTime", 10)) + + self._setCB(self.PoweredFreqCombo, self.cb_freq, self.cb_freq_def, str(self.config.readEntry("poweredFreqPolicy"))) + self._setCB(self.BatteryFreqCombo, self.cb_freq, self.cb_freq_def, str(self.config.readEntry("batteryFreqPolicy"))) + + self.BatteryIdleTime.setValue(self.config.readNumEntry("batteryIdleTime", 10)) # default Do nothing + # battery critical and lid actions. + self._setCB(self.BatteryCriticalCombo, self.cb_act, self.cb_act_def_critical, self._checkOldConfig(self.config.readEntry("batteryCriticalAction", ""), False)) + self._setRB(self.LaptopLidRadios, self.lid_act, self.lid_act_def, self._checkOldConfig(self.config.readEntry("laptopLidAction", ""), True)) + self.CriticalRemainTime.setValue(self.config.readNumEntry("criticalRemainTime", BATTERY_CRITICAL_MINUTES)) + self.criticalLevel = self.CriticalRemainTime.value() + + # Call some slots to disable various spinboxes if necessary + self.slotBatteryCriticalActivated() + self.slotPoweredIdleActivated() + self.slotBatteryIdleActivated() + + + def uiToConfig(self): + """ Read all values from the UI and write them to the config file. """ + self.config.writeEntry("poweredBrightness", self.PoweredBrightnessSlider.value()) + self.config.writeEntry("batteryBrightness", self.BatteryBrightnessSlider.value()) + + self.config.writeEntry("poweredIdleTime", self.PoweredIdleTime.value()) + self.config.writeEntry("poweredIdleAction", self._getCB(self.PoweredIdleCombo, self.cb_act)) + self.config.writeEntry("batteryIdleTime", self.BatteryIdleTime.value()) + self.config.writeEntry("batteryIdleAction", self._getCB(self.BatteryIdleCombo, self.cb_act)) + self.config.writeEntry("poweredFreqPolicy", self._getCB(self.PoweredFreqCombo, self.cb_freq)) + self.config.writeEntry("batteryFreqPolicy", self._getCB(self.BatteryFreqCombo, self.cb_freq)) + + self.config.writeEntry("batteryCriticalAction", self._getCB(self.BatteryCriticalCombo, self.cb_act)) + self.config.writeEntry("criticalRemainTime", self.CriticalRemainTime.value()) + + self.config.writeEntry("laptopLidAction", self._getRB(self.LaptopLidRadios, self.lid_act)) + self.config.writeEntry("lockOnResume", self.lockScreenOnResume.isChecked()) + + self.criticalLevel = self.CriticalRemainTime.value() + + self.config.sync() + + def quit(self): + """ Quit application. """ + kapp.quit() + + def showTip(self, text=""): + """ Pop up the tooltip showing battery data and CPU frequencies. """ + self.popup.show() + + + def showBrightnessPopup(self): + if self.powermanager.onBattery(): + value=self.BatteryBrightnessSlider.value()*100/self.BatteryBrightnessSlider.maxValue() + else: + value=self.PoweredBrightnessSlider.value()*100/self.PoweredBrightnessSlider.maxValue() + self.brightnessPopup = KPassivePopup.message('<b>Brightness:</b> '+str(value)+'%', self.systray) + """pop.setTimeout(3000)""" + self.brightnessPopup.show() + + def setBrightnessUp(self): + """Increments slider value by 10%""" + if self.powermanager.onBattery(): + self.BatteryBrightnessSlider.setValue(float(self.BatteryBrightnessSlider.value())+max(float(self.BatteryBrightnessSlider.maxValue())/float(10),1)) + else: + self.PoweredBrightnessSlider.setValue(float(self.PoweredBrightnessSlider.value())+max(float(self.PoweredBrightnessSlider.maxValue())/float(10),1)) + self.showBrightnessPopup() + + def setBrightnessDown(self): + """Decrements slider value by 10%""" + if self.powermanager.onBattery(): + self.BatteryBrightnessSlider.setValue(float(self.BatteryBrightnessSlider.value())-max(float(self.BatteryBrightnessSlider.maxValue())/float(10),1)) + else: + self.PoweredBrightnessSlider.setValue(float(self.PoweredBrightnessSlider.value())-max(float(self.PoweredBrightnessSlider.maxValue())/float(10),1)) + self.showBrightnessPopup() + + def getBrightness(self): + """Work with percentages - it's a bit nicer""" + if self.powermanager.onBattery(): + value=self.BatteryBrightnessSlider.value()*100/self.BatteryBrightnessSlider.maxValue() + else: + value=self.PoweredBrightnessSlider.value()*100/self.PoweredBrightnessSlider.maxValue() + return QString(str(value)) + + def hideTip(self): + """ Hide the tooltip.""" + self.popup.hide() + + def lockScreen(self): + """ locks the screen using kdesktop """ + # 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 kdesktop: + kdesktop = DCOPApp('kdesktop', client) + # call a DCOP-function: + try: + ok, foo = kdesktop.KScreensaverIface.lock() + except: + print "Unable to lock the screen. The KDE Screensaver does not seem to be running." + def suspend(self): + """ Lock the screen and initiate a suspend to RAM (S3). """ + if self.config.readBoolEntry("lockOnResume", True): + self.lockScreen() + try: + self.warningPopup.hide() + except AttributeError: + pass # No warningpopup, that's OK. + self.powermanager.suspend() + self.powermanager.resetIdleSeconds() + + def hibernate(self): + """ Lock the screen and initiate a suspend to disk (S4). """ + if self.config.readBoolEntry("lockOnResume", True): + self.lockScreen() + try: + self.warningPopup.hide() + except AttributeError: + pass # No warningpopup, that's OK. + self.powermanager.hibernate() + self.powermanager.resetIdleSeconds() + + def shutdown(self): + """ Perform system shutdown. """ + self.powermanager.shutdown() + + def setCpuPolicyDynamic(self): + """Change frequ for all cpu""" + self.powermanager.setCpuPolicy('dynamic') + self.notify(i18n("CPU frequency policy changed to %1.").arg(self.freq_name['dynamic'])) + + def setCpuPolicyPerformance(self): + """Change frequ for all cpu""" + self.powermanager.setCpuPolicy('performance') + self.notify(i18n("CPU frequency policy changed to %1.").arg(self.freq_name['performance'])) + + def setCpuPolicyPowersave(self): + """Change frequ for all cpu""" + self.powermanager.setCpuPolicy('powersave') + self.notify(i18n("CPU frequency policy changed to %1.").arg(self.freq_name['powersave'])) + + def trySuspend(self): + """ If supported, lock the screen and initiate a suspend to RAM (S3). """ + if self.canSuspend: + self.suspend() + else: + print "Warning: DCOP suspend() called, but not supported." + + def tryHibernate(self): + """ If supported, lock the screen and initiate a suspend to disk (S4). """ + if self.canHibernate: + self.hibernate() + else: + print "Warning: DCOP hibernate() called, but not supported." + + def blankScreen(self): + """ Lock and blank screen. """ + if self.config.readBoolEntry("lockOnResume", True): + self.lockScreen() + self.powermanager.blankScreen() + + def _getIcon(self): + """ Set systray icon depending on battery status/level. """ + if self.powermanager.hasBattery: + if self.batt_state == "not present": + self.icon = "ac-adapter" + if self.batt_state == "charged": + self.icon = "battery-charging-100" + elif self.batt_state == "discharging": + if self.batt_level >= 95: + self.icon = "battery-discharging-100" + elif self.batt_level < 95 and self.batt_level >= 85: + self.icon = "battery-discharging-090" + elif self.batt_level < 85 and self.batt_level >= 75: + self.icon = "battery-discharging-070" + elif self.batt_level < 75 and self.batt_level >= 60: + self.icon = "battery-discharging-060" + elif self.batt_level < 65 and self.batt_level >= 45: + self.icon = "battery-discharging-050" + elif self.batt_level < 45 and self.batt_level >= 30: + self.icon = "battery-discharging-040" + elif self.batt_level < 30 and self.batt_level >= 20: + self.icon = "battery-discharging-030" + elif self.batt_level < 20 and self.batt_level >= 10: + self.icon = "battery-discharging-020" + elif self.batt_level < 10 and self.batt_level >= 5: + self.icon = "battery-discharging-010" + else: + self.icon = "battery-discharging-000" + elif self.batt_state == "charging": + if self.batt_level >= 95: + self.icon = "battery-charging-100" + elif self.batt_level < 95 and self.batt_level >= 85: + self.icon = "battery-charging-090" + elif self.batt_level < 85 and self.batt_level >= 75: + self.icon = "battery-charging-070" + elif self.batt_level < 75 and self.batt_level >= 60: + self.icon = "battery-charging-060" + elif self.batt_level < 65 and self.batt_level >= 45: + self.icon = "battery-charging-050" + elif self.batt_level < 45 and self.batt_level >= 30: + self.icon = "battery-charging-040" + elif self.batt_level < 30 and self.batt_level >= 20: + self.icon = "battery-charging-030" + elif self.batt_level < 20 and self.batt_level >= 10: + self.icon = "battery-charging-020" + elif self.batt_level < 10 and self.batt_level >= 5: + self.icon = "battery-charging-010" + else: + self.icon = "battery-charging-000" + else: + self.icon = "ac-adapter" + return self.icon + + def getIcon(self): + """ Return current icon.""" + return UserIcon(self.icon) + + def setIcon(self): + """ Change the systray/tooltip icon.""" + oldIcon = self.icon + self.icon = self._getIcon() + if self.icon != oldIcon: + self.systray.setPixmap(QPixmap(UserIcon(self.icon))) + self.BattPixmap.setPixmap(QPixmap(UserIcon(self.icon))) + + def notify(self, msg, icon=None): + """ Send a notification popup. """ + if icon: + icon = QPixmap(icon) + else: + icon = QPixmap(SmallIcon("messagebox_info")) + try: + del self.warningPopup + except: + pass + self.warningPopup = KPassivePopup(self.systray) + label = Notify(self.warningPopup, msg, icon) + self.warningPopup.setView(label) + position = QPoint(5,5) + self.warningPopup.show(position) + + def poll(self,notify=True): + """ Check for changes in plugged in status, battery status and laptop lid closed status. """ + debug( "------------ POLL ---------------") + + self.powermanager.checkHAL() + # Battery stuff: + # check for last state, and run plugged / unplugged message if the state changed. + if self.powermanager.hasBattery: + plugged_num = 0 + self.batt_state = "not present" # unknown yet + self.batt_level = self.batt_remain = 0 + self.batt_rate = self.batt_charge = self.batt_full = 0 + for batt in self.powermanager.batteries: + state, level, remain, rate, current, full = self.powermanager.getBatteryState(batt) + self._updateBatteryWidget(batt, state, level, remain, rate) + + ## notify plugged/unplugged batteries + if state == "not present": + if self.powermanager.batteryIsPresent[batt]: + self.notify(i18n("The battery has been removed.")) + self.powermanager.batteryIsPresent[batt] = False + else: # battery present + if not self.powermanager.batteryIsPresent[batt]: + self.notify(i18n("The battery has been inserted.")) + self.powermanager.batteryIsPresent[batt] = True + + ## get cumulative charge levels/rate + self.batt_rate += rate + self.batt_charge += current + self.batt_full += full + + ## calculate overall level (average of present batteries) + self.batt_remain += remain + self.batt_level += level + plugged_num += 1 + + ## calculate overall state (charging/discharging/charged) + if state in ("charging","discharging"): + self.batt_state = state + elif not self.batt_state in ("charging, discharging"): + self.batt_state = state + + # if we know charge and full -> recalculate overall level + if self.batt_full > 0 and self.batt_charge > 0: + self.batt_level = 100 * self.batt_charge / self.batt_full + else: + # if more than one battery present, we need to calculate average level + if plugged_num > 1: + self.batt_level /= plugged_num + + # if rate is reported, calculate remaining time on our own + if self.batt_rate > 0: + if self.batt_state == "charging": + self.batt_remain = 3600 * (float(self.batt_full - self.batt_charge) / self.batt_rate) + if self.batt_state == "discharging": + self.batt_remain = 3600 * (float(self.batt_charge) / self.batt_rate) + + remain_h = self.batt_remain/3600 + remain_m = (self.batt_remain/60)%60 + + blabel = i18n("<b>Battery:</b>") + if self.batt_state == "charged": + blabel += i18n(" fully charged") + elif self.batt_state == "charging": + blabel += i18n(" %i:%02ih to charge" % (remain_h,remain_m)) + elif self.batt_state == "discharging": + blabel += i18n(" %i:%02ih remaining" % (remain_h,remain_m)) + self.BattMainLabel.setText(blabel) + + # update tray icon if needed + self.setIcon() + + # check battery state + self.checkBatteryCritical() + + # check Idletime + self.checkIdletime() + + # CPU stuff + self._updateCpuWidgets() + + if self.powermanager.hasBattery: + on_battery = self.powermanager.onBattery() + if self.powermanager.wasOnBattery != on_battery: + self.powermanager.wasOnBattery = on_battery + debug("poll: states differ") + if not on_battery: + debug("poll: Now on AC") + if notify: + self.powerHasBeenPlugged() + else: + debug("poll: Now on battery") + if notify: + self.powerHasBeenUnplugged() + else: + debug("poll: state is the same") + + # Lid stuff + if self.powermanager.hasLid: + if self.powermanager.getLidClosedState(): + if not self.powermanager.lidClosedState: + self.powermanager.lidClosedState = True + + action = self._getRB(self.LaptopLidRadios, self.lid_act) + if not self.act_name.has_key(action): + action = self.act_name[self.lid_act_def] + + if self.act_call[action] != None: + note = i18n("Laptop lid is closed, %1 now.").arg(self.act_notify[action]) + self.notify(note, self.act_icon[action]) + QTimer.singleShot(2000, self.act_call[action]) + else: + self.powermanager.lidClosedState = False + + def _addBatteryWidgets(self): + """ Adds progressbars to show battery status to the tooltip.""" + BattLayout = QHBoxLayout(None,0,6,"BattLayout") + + self.BattPixmap = QLabel(self.tooltip,"BattLabLayout") + self.BattPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed,0,0,self.BattPixmap.sizePolicy().hasHeightForWidth())) + self.BattPixmap.setPixmap(QPixmap(UserIcon(self.icon))) + self.BattPixmap.setScaledContents(1) + BattLayout.addWidget(self.BattPixmap) + self.BattMainLabel = QLabel(self.tooltip,"BattMainLabel") + self.BattMainLabel.setText(i18n("<b>Battery:</b>")) + BattLayout.addWidget(self.BattMainLabel) + + # Add to tooltip + self.tooltip.layout().addLayout(BattLayout) + + # Create a progressbar and a label for every battery found, and add it to tooltip + self.BattLabel = {} + self.BattLayout = {} + self.BattProgress = {} + i = 1 + for batt in self.powermanager.batteries: + self.BattLayout[batt] = QHBoxLayout(None,0,6,"BattBarLayout") + self.BattLabel[batt] = QLabel(self.tooltip,"BattLabel") + if len(self.powermanager.batteries) > 1: + self.BattLabel[batt].setText(i18n("Battery %i" % i)) + self.BattLayout[batt].addWidget(self.BattLabel[batt]) + self.BattProgress[batt] = KProgress(self.tooltip,"BattProgress") + self.BattProgress[batt].setMinimumSize(QSize(200,0)) + self.BattLayout[batt].addWidget(self.BattProgress[batt]) + self.tooltip.layout().addLayout(self.BattLayout[batt]) + i += 1 + + + def _updateBatteryWidget(self, batt, state, level, remain, rate): + """ Retrieve battery information and update the related widgets accordingly. """ + self.BattProgress[batt].setEnabled(True) + self.BattProgress[batt].setTotalSteps(100) + self.BattProgress[batt].setProgress(level) + if state == "not present": + self.BattProgress[batt].setFormat(i18n("not present")) + elif state == "charging": + self.BattProgress[batt].setFormat(i18n("Charging (%p%)")) + elif state == "discharging": + if rate > 0: + showrate = rate/1000 + self.BattProgress[batt].setFormat(i18n("Discharging (%p%)") + " - %.d W" % showrate) + else: + self.BattProgress[batt].setFormat(i18n("Discharging (%p%)")) + else: + self.BattProgress[batt].setFormat("%p%") + + def _addCpuWidgets(self): + """ Adds progressbars to show CPU frequencies to the tooltip.""" + if not SHOW_CPUFREQ: + return + if len(self.powermanager.cpus) == 0: + return + + LabelLayout = QHBoxLayout(None,0,6,"layout5") + + self.CpuPixmap = QLabel(self.tooltip,"CpuPixmap") + self.CpuPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed,0,0,self.CpuPixmap.sizePolicy().hasHeightForWidth())) + self.CpuPixmap.setPixmap(QPixmap(UserIcon("processor"))) + self.CpuPixmap.setScaledContents(1) + LabelLayout.addWidget(self.CpuPixmap) + self.CpuMainLabel = QLabel(self.tooltip,"CpuMainLabel") + self.CpuMainLabel.setText(i18n("<b>CPU Frequency:</b>")) + LabelLayout.addWidget(self.CpuMainLabel) + + # Add to tooltip + self.tooltip.layout().addLayout(LabelLayout) + + # Create a progressbar and a label for every CPU found, and add it to tooltip + self.CpuLabel = {} + self.CpuLayout = {} + self.CpuProgress = {} + i = 1 + for cpu in self.powermanager.cpus: + self.CpuLayout[cpu] = QHBoxLayout(None,0,6,"layout2") + self.CpuLabel[cpu] = QLabel(self.tooltip,"CpuLabel") + if len(self.powermanager.cpus) > 1: + self.CpuLabel[cpu].setText(i18n("Processor %i" % i)) + self.CpuLayout[cpu].addWidget(self.CpuLabel[cpu]) + self.CpuProgress[cpu] = KProgress(self.tooltip,"CpuProgress") + self.CpuProgress[cpu].setFormat("%v MHz") + self.CpuLayout[cpu].addWidget(self.CpuProgress[cpu]) + self.tooltip.layout().addLayout(self.CpuLayout[cpu]) + i += 1 + + def slotPoweredIdleActivated(self, index=False): + """ Signal slot for activated powered idle action. """ + if not index: + index = self.PoweredIdleCombo.currentItem() + self.PoweredIdleTime.setEnabled(index != 0) + + def slotBatteryIdleActivated(self, index=False): + """ Signal slot for activated battery idle action. """ + if not index: + index = self.BatteryIdleCombo.currentItem() + self.BatteryIdleTime.setEnabled(index != 0) + + def slotBatteryCriticalActivated(self, index=False): + """ Signal slot for activated battery critical action. """ + if not index: + index = self.BatteryCriticalCombo.currentItem() + self.CriticalRemainTime.setEnabled(index != 0) + + def _updateCpuWidgets(self): + """ Retrieve CPU freq information and update the related widgets accordingly. """ + if not SHOW_CPUFREQ: + return + if len(self.powermanager.cpus) == 0: + return + + clabel = i18n("<b>CPU Frequency:</b>") + " " + policy = self.powermanager.getCpuPolicy() + if self.freq_name.has_key(policy): + clabel += self.freq_name[policy] # get l10n name + else: + clabel += policy + self.CpuMainLabel.setText(clabel) + + for cpu in self.powermanager.cpus: + cpustate = self.powermanager.getCpuState(cpu) + if not cpustate['online']: + self.CpuProgress[cpu].setEnabled(False) + else: + self.CpuProgress[cpu].setEnabled(True) + self.CpuProgress[cpu].setTotalSteps(cpustate['max']) + self.CpuProgress[cpu].setProgress(cpustate['cur']) + if policy != "": + self.systray.actionCollection().action(policy).setChecked(True) + if policy in self.cb_freq: + self.systray.actionCollection().action(policy).setChecked(True) + + def changePoweredBrightness(self, level=None): + """ Mains-powered brigthness slider has been moved. """ + # Check if the state applies and adjust brightness immediately. + if not self.powermanager.onBattery() and self.powermanager.hasBrightness: + if not level: + level = self.PoweredBrightnessSlider.value() + self.powermanager.adjustBrightness(level) + + def changeBatteryBrightness(self, level=None): + """ Battery-powered brigthness slider has been moved. """ + # Check if the state applies and adjust brightness immediately. + if self.powermanager.onBattery() and self.powermanager.hasBrightness: + if not level: + level = self.BatteryBrightnessSlider.value() + self.powermanager.adjustBrightness(level) + + + + + def checkCpuFreq(self): + """ Adjust CPU frequency policy according to current state """ + if not self.powermanager.hasCpuFreqGovernors: + return "" + + if self.powermanager.onBattery(): + policy = str(self.config.readEntry("batteryFreqPolicy")) + else: + policy = str(self.config.readEntry("poweredFreqPolicy")) + if policy == "": + policy = 'dynamic' + + # check if specified policy is supported by HW + if not policy in self.cb_freq: + print "Warning: policy from config file not supported: ", policy + return "" + + current_policy = self.powermanager.getCpuPolicy() + if current_policy != policy: + debug("Switching CPU policy from %s to %s." % (current_policy, policy)) + self.powermanager.setCpuPolicy(policy) + return i18n("CPU frequency policy changed to %1.").arg(self.freq_name[policy]) + elif current_policy == 'dynamic': + debug("Dynamic policy -> update policy (conservative/ondemand)") + self.powermanager.setCpuPolicy(policy) + + debug("CPU policy will stay %s" % current_policy) + return "" + + def powerHasBeenUnplugged(self): + """ Actions to perform when the plug has been pulled.""" + if self.powermanager.hasBrightness: + self.powermanager.adjustBrightness(self.BatteryBrightnessSlider.value()) + self.powermanager.setPowerSave(True) + self.checkBatteryCritical() + self.changeBatteryBrightness() + self.powermanager.setScreensaverBlankOnly(True) + self.powermanager.resetIdleSeconds() + msg = self.checkCpuFreq() + if self.powermanager.hasAC: + self.notify(i18n("The AC adapter has been unplugged, switching to battery mode.")+"\n"+msg, self.getIcon()) + + def powerHasBeenPlugged(self): + """ Actions to perform when AC adapter has been plugged in. """ + if self.powermanager.hasBrightness: + self.powermanager.adjustBrightness(self.PoweredBrightnessSlider.value()) + self.powermanager.setPowerSave(False) + self.changePoweredBrightness() + self.powermanager.setScreensaverBlankOnly(False) + msg = self.checkCpuFreq() + self.powermanager.resetIdleSeconds() + self.notify(i18n("The AC adapter has been plugged in, switching to AC mode.")+"\n"+msg, self.getIcon()) + + def checkBatteryCritical(self): + """ Check for warning and critical battery label and notify-warn or + initiate the configured action. """ + + if not self.powermanager.hasBattery: + return + + if self.batt_state == "discharging": + currentLevel = int(self.batt_remain/60) + + warningLevel = self.criticalLevel + 5 # warn five minutes before critical + criticalLevel = self.criticalLevel + + debug("CurrentBat: %i, WarningBat: %i, CriticalBat: %i" % (currentLevel, warningLevel, criticalLevel)) + # We only want to suspend if the chargelevel is above a certain threshold, + # it sometimes takes some time for HAL to report remaining time correctly + if currentLevel <= criticalLevel and self.batt_level < CHARGE_LEVEL_THRESHOLD: + if not self.powermanager.criticalBatteryState and self.powermanager.onBattery(): + self.powermanager.criticalBatteryState = True + + action = str(self.config.readEntry("batteryCriticalAction")) + if not self.act_name.has_key(action): + action = self.act_name[self.cb_act_def_critical] + + note = i18n("You are about to run out of battery power, %1 now.").arg(self.act_notify[action]) + self.notify(note, self.act_icon[action]) + if self.act_call[action] != None: + QTimer.singleShot(2000, self.act_call[action]) + else: + self.powermanager.criticalBatteryState = False + if currentLevel <= warningLevel and self.batt_level < CHARGE_LEVEL_THRESHOLD: + if not self.powermanager.warningBatteryState: + self.powermanager.warningBatteryState = True + self.notify(i18n("You are low on battery power."), self.getIcon()) + else: + self.powermanager.warningBatteryState = False + + def checkIdletime(self): + """ Reads the idle time and does some action. """ + idlesec = round(self.powermanager.getIdleSeconds()/60, 2) + if self.powermanager.onBattery(): + idleTime = self.config.readNumEntry("batteryIdleTime", 10) + action = str(self.config.readEntry("batteryIdleAction")) + else: + idleTime = self.config.readNumEntry("poweredIdleTime", 60) + action = str(self.config.readEntry("poweredIdleAction")) + if not self.act_name.has_key(action): + action = 'nothing' + + if idlesec - self.lastidlesec > 100: + debug("last: %u" % (idlesec - self.lastidlesec)) + return # probably bogus idleseconds right after suspend + self.lastidlesec = idlesec + if self.act_call[action] == None: + return # doing nothing anyway + if idlesec > idleTime: + note = i18n("System idle for at least %1 minutes, %2 now.").arg(idleTime).arg(self.act_notify[action]) + self.notify(note, self.act_icon[action]) + QTimer.singleShot(2000, self.act_call[action]) + + + +def doDcop(kapp): + """ Register kvandale in dcop, so it can be controlled from outside. """ + my_dcop = kapp.dcopClient() + #my_dcop.attach() + #my_dcop.registerAs("power-manager") + + +class DcopIface (DCOPExObj): + """ Add some interface so we can use powermanager from the outside. """ + def __init__ (self, app, id='power-manager'): + DCOPExObj.__init__ (self, id) + # addMethod (<signature>, <Python method>) + #self.addMethod ('QString getQuery()', gvd.getZoekbegrip) + + # PM related. + self.addMethod ('void suspend ()', app.trySuspend) + self.addMethod ('void hibernate ()', app.tryHibernate) + self.addMethod ('void shutdown ()', app.shutdown) + + # UI related. + self.addMethod ('void showTip ()', app.showTip) + #self.addMethod ('void show ()', app.parent().show) + #self.addMethod ('void hide ()', app.parent().hide) + + #self.addMethod ('void plugged ()', app.powerHasBeenPlugged) + #self.addMethod ('void unplugged ()', app.powerHasBeenUnplugged) + self.addMethod ('bool onBattery ()', app.powermanager.onBattery) + + self.addMethod('void brightnessUp ()', app.setBrightnessUp) + self.addMethod('void brightnessDown ()', app.setBrightnessDown) + self.addMethod('QString getBrightness ()', app.getBrightness) + + #self.addMethod ('QString getCurrentResult()', gvd.getRawResult) + + +class PowermanagerApp(KDialogBase): + """ The KDialog providing the OK, Apply and Cancel buttons.""" + + def __init__(self,parent=None,name=None): + """ Initialise dialog and set mainwidget. """ + KGlobal.locale().insertCatalogue("guidance") + KGlobal.iconLoader().addAppDir("guidance") + + # We would like to use a KUniqueApplication, but that breaks dcop due to some + # strange bug. The following line is the revenge code for this bug, it is + # intentionally ugly. + if len(os.popen("dcop |grep guidance-").readlines()) > 1: + print "There is already an instance of power manager running. Exiting." + sys.exit(0) + + # Which buttons do we want? + KDialogBase.__init__(self,KJanusWidget.Swallow,i18n("Power Manager"), + KDialogBase.Ok|KDialogBase.Apply|KDialogBase.Cancel|KDialogBase.User1, KDialogBase.Close) + self.pmwidget = PowerManager(self,name) + self.setButtonText(KDialogBase.User1, i18n("About")) + + if not self.pmwidget.powermanager.isLaptop(): + print "This is not a laptop, quitting ... " + sys.exit(1) + + self.pmwidget.prepare() + + self.setMainWidget(self.pmwidget) + self.aboutus = KAboutApplication(self) + + def slotOk(self): + """ The OK button has been pressed, save configuration and pass on do whatever + needs to be done by KDialog. """ + self.pmwidget.uiToConfig() + self.pmwidget.checkCpuFreq() + KDialogBase.slotOk(self) + + def slotApply(self): + """ The Apply button has been pressed, save configuration and pass on do whatever + needs to be done by KDialog. """ + self.pmwidget.uiToConfig() + self.pmwidget.checkCpuFreq() + KDialogBase.slotApply(self) + + def slotCancel(self): + """ The Cancel button has been pressed, reset some values and hide dialogue. """ + # In case brightness has changed, we reset it to the configured value. + if self.pmwidget.powermanager.hasBrightness: + brightness_high = self.pmwidget.powermanager.brightness_levels + if not self.pmwidget.powermanager.onBattery(): + level = self.pmwidget.config.readNumEntry("poweredBrightness", brightness_high) + else: + level = self.pmwidget.config.readNumEntry("batteryBrightness", int(brightness_high/2)) + self.pmwidget.powermanager.adjustBrightness(level) + self.pmwidget.configToUi() + KDialogBase.slotCancel(self) + + def slotUser1(self): + self.aboutus.show() + +# There's a bug in KUniqueApplication that shows the pid in the dcop name, +# this fugly hack works around it. +class PMApp(KApplication): + + def name(self): + return "power-manager" + + +if __name__ == "__main__": + aboutdata = KAboutData("guidance", "Power Manager", "0.8.0", + "Handles battery, display and suspend modes for your computer.", KAboutData.License_GPL, + "(C) 2006-2007 Sebastian Kügler, Canonical Ltd, Luka Renko", + None, None, "jriddell@ubuntu.com") + aboutdata.addAuthor("Sebastian Kügler", "Developer", "sebas@kde.org","http://vizZzion.org") + aboutdata.addAuthor("Jonathan Riddell", "Developer", "jriddell@ubuntu.com") + aboutdata.addAuthor("Luka Renko", "Developer", "lure@kubuntu.org") + aboutdata.setProgramLogo(QImage("power-manager.png")) + KCmdLineArgs.init(sys.argv, aboutdata) + #kapp = KUniqueApplication(True, True, False) + #kapp = KApplication() + kapp = PMApp(True, True) + mainWindow = PowermanagerApp(None, "main window") + doDcop(kapp) + dcop_iface = DcopIface(mainWindow.pmwidget) + """Start helper module / button listener""" + try: + helperPid = os.spawnl(os.P_NOWAIT, os.path.dirname(__file__)+'/gpmhelper.py', 'gpmhelper.py') + except: + """Non-fatal if this fails""" + print "Unable to start button-listener" + + kapp.exec_loop() + + """Kill helper module / button listener""" + os.system('kill '+str(helperPid)) |