#!/usr/bin/python # -*- coding: UTF-8 -*- """ Copyright 2006-2007 Sebastian Kügler, Canonical Ltd, Luka Renko Authors: Sebastian Kügler Jonathan Riddell Luka Renko 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 tdecore import * from tdeui 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("")+caption+QString("")) 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 pytde # see http://mats.gmd.de/pipermail/pytde/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('Brightness: '+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("Battery:") 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("Battery:")) 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("CPU Frequency:")) 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("CPU Frequency:") + " " 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 (, ) #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))