You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tde-guidance/powermanager/guidance-power-manager.py

1135 lines
49 KiB

#!/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 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("<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 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('<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))