pywmdockapps/examples/pywmseti.py

503 lines
21 KiB
Python
Raw Normal View History

2024-09-08 16:50:24 -06:00
#!/usr/bin/env python
"""pywmseti.py
WindowMaker dockapp to monitor the progress of your seti@home.
Copyright (C) 2003 Kristoffer Erlandsson
Licensed under the GNU General Public License.
Changes
2003-06-24 Kristoffer Erlandsson
Added event handling for graceful shutdown
2003-06-17 Kristoffer Erlandsson
First workingish version
"""
usage = """pywmseti.py [options]
Available options are:
-h, --help print this help
-t, --textcolor <color> set the text color
-p, --progressbarcolor <color> set the color of the progress bar
-g, --barbgcolor <color> set the background color of the progress bar
-i, --indicatorcolor <color> set the color of the running indicator
-b, --background <color> set the background color
-d, --setidir <directory> set the directory where seti@home resides
-n, --nice <value> set the nice value to run seti@home with
-r, --rgbfile <file> set the rgb file to get color codes from
-c, --configfile <file> set the config file to use
"""
import sys
import time
import getopt
import os
import wmdocklib
width = 64
height = 64
xOffset = 4
yOffset = 4
graphStartX = 7
graphStartY = 53
graphLength = 50
graphHeight = 4
graphBgStartX = 72
graphBgStartY = 53
graphLineStartX = 66
graphLineStartY = 58
runningIndX = 71
runningIndY = 1
runningIndWidth = 3
runningIndHeight = 15
numRunningInds = 4
defaultConfigFile = '~/.pywmsetirc'
defaultRGBFiles = ['/usr/lib/X11/rgb.txt', '/usr/X11R6/lib/X11/rgb.txt']
stateFileName = 'state.sah'
uinfoFileName = 'user_info.sah'
pidFileName = 'pid.sah'
execFileName = 'setiathome'
class PywmSeti:
def __init__(self, statePath, uinfoPath, pidPath, execCmd):
self._statePath = statePath
self._uinfoPath = uinfoPath
self._pidPath = pidPath
self._execCmd = execCmd
self._currentRunningInd = 0
self._lastTime = time.time()
self._lastNumResults = -1
self._progress = 0
def addString(self, s, x, y):
try:
wmdocklib.addString(s, x, y, digits,
xOffset, yOffset, width, height)
except ValueError, e:
sys.stderr.write('Error when painting string:\n' + str(e) + '\n')
sys.exit(3)
def getCenterStartPos(self, s):
return wmdocklib.getCenterStartPos(s, width, xOffset)
def getVertSpacing(self, numLines, margin):
return wmdocklib.getVertSpacing(numLines, margin, height, yOffset)
def getProgress(self, lines):
"""Return the progess of the current workunit.
Supply the lines of the statefile as argument.
"""
for line in lines:
if line.startswith('prog='):
try:
progress = float(line.split('=')[-1])
except ValueError:
progress = 0
return progress
return 0
def getNumResults(self, lines):
"""Return the number of results produced.
Supply the lines in the user info file as argument.
"""
for line in lines:
if line.startswith('nresults='):
try:
results = int(line.split('=')[-1])
except ValueError:
pass
else:
return results
sys.stderr.write(
"Error when reading uinfo file! Can't get number of results.\n")
return -1
def pidIsRunning(self, pid):
"""Determine if the process with PID pid is running.
Return 1 if it is running.
Return 0 if it is not running.
Return -1 if we do not have permission to signal the process
This could be slightly non-portal, but I can not find any better
way to do it.
"""
try:
os.kill(pid, 0)
except OSError, e:
if e.errno == 1:
return -1
return 0
return 1
def openFileRead(self, fileName):
try:
f = file(fileName, 'r')
except IOError, e:
sys.stderr.write('Error when opening %s: %s\n' % (fileName, str(e)))
return None
return f
def paintCurrentRunningIndicator(self):
"""Paint the running indicator.
"""
indX = runningIndX + self._currentRunningInd * \
(runningIndWidth + 2)
indY = runningIndY
w = runningIndWidth
h = runningIndHeight
targX = width - xOffset - w - 5
targY = yOffset + 5
wmdocklib.copyXPMArea(indX, indY, w, h, targX, targY)
def updateRunning(self):
"""Update the information regarding if we got seti@home running or not.
Return a tuple with (running, startStopenabled).
startStopEnabled is 1 if we own the process and got the permissions
to start and stop it, or if there is no process running.
"""
pidFile = self.openFileRead(self._pidPath)
if pidFile is None:
sys.stderr.write("Can't read pid file")
self._running = 0
self._startStopEnabled = 0
return
try:
self._pid = int(pidFile.read().strip())
except ValueError:
sys.stderr.write("Can't get pid from %s.\n" % self._pidPath)
self._running = 0
self._startStopEnabled = 0
return
pidFile.close()
self._running = self.pidIsRunning(self._pid)
if self._running == -1 and self._startStopEnabled:
sys.stderr.write(
"An other seti@home process which you don't own is running.\n")
sys.stderr.write(
"Starting and stopping of the process is disabled.\n")
self._startStopenabled = 0
if self._running == -1:
self._running = 1
else:
# If no process is running (we could have stopped the one
# running from an other process), enable starting and stopping.
self._startStopEnabled = 1
if self._running:
self._currentRunningInd = (self._currentRunningInd - 1) \
% numRunningInds
else:
self._currentRunningInd = 0
self.paintCurrentRunningIndicator()
def updateProgress(self):
"""Update the progress on the current workunit."""
stateFile = self.openFileRead(self._statePath)
if stateFile is None:
# Can't open file, probably in progress of gettin a new workunit.
progress = 0
else:
progress = self.getProgress(stateFile.readlines())
stateFile.close()
self._progress = progress
percent = int(progress * 100.0)
graphSize = int(round(progress * graphLength))
wmdocklib.copyXPMArea(
graphLineStartX, graphLineStartY, graphSize, graphHeight,
graphStartX, graphStartY)
wmdocklib.copyXPMArea(
graphBgStartX, graphBgStartY, graphLength - graphSize, graphHeight,
graphStartX + graphSize, graphStartY)
self.addString((str(percent) + '%').ljust(4), 4, 32)
def updateNumResults(self):
"""Update the number of workunits done."""
uinfoFile = self.openFileRead(self._uinfoPath)
numResults = self.getNumResults(uinfoFile.readlines())
if self._lastNumResults == -1:
self._lastNumResults = numResults
if numResults != self._lastNumResults and self._progress < 0.03:
# If we just got a new number of results and the progress of the
# current workunit is under 3%, assume we started working on a new
# workunit. The times this could be missleading is if we have an
# other seti@home process running on an other computer, but this is
# accurate enough I think.
self.nextWorkUnitStarted()
self._lastNumResults = numResults
uinfoFile.close()
self.addString(str(numResults)[:7], 4, 4)
def updateTime(self):
"""Update the time line.
We display the time that we have been on the current work unit, since
either the last one was done or since we started the program.
"""
timeSpent = time.time() - self._lastTime
hours = int(timeSpent / 3600)
mins = int((timeSpent - hours * 3600) / 60)
hours = str(hours)[:3]
mins = str(mins).zfill(2)
s = (hours + ':' + mins).ljust(6)
self.addString(s, 4, 18)
def nextWorkUnitStarted(self):
self._lastTime = time.time()
def handleMouseClick(self, region):
if region == 0:
if self._startStopEnabled:
if self._running:
try:
os.kill(self._pid, 15)
except OSError, e:
sys.stderr.write(
"Error when ending process: "+str(e)+'\n')
else:
os.system(self._execCmd) # Use fork instead?
def _checkForEvents(self):
"""Check for, and handle, X events."""
event = wmdocklib.getEvent()
while not event is None:
if event['type'] == 'buttonrelease':
region = wmdocklib.checkMouseRegion(event['x'],
event['y'])
self.handleMouseClick(region)
elif event['type'] == 'destroynotify':
sys.exit(0)
event = wmdocklib.getEvent()
def mainLoop(self):
counter = -1
self._startStopEnabled = 1
while 1:
counter += 1
self._checkForEvents()
if counter % 10 == 0:
self.updateRunning()
if counter % 100 == 0:
self.updateProgress()
self.updateNumResults()
self.updateTime()
if counter == 999999:
counter = -1
wmdocklib.redraw()
time.sleep(0.1)
def parseCommandLine(argv):
"""Parse the commandline. Return a dictionary with options and values."""
shorts = 'ht:b:n:d:r:c:p:g:i:'
longs = ['help', 'textcolor=', 'background=', 'setidir=', 'nice=',
'rgbfile=', 'configfile=', 'progressbarcolor=', 'barbgcolor=',
'indicatorcolor=']
try:
opts, nonOptArgs = getopt.getopt(argv[1:], shorts, longs)
except getopt.GetoptError, e:
sys.stderr.write('Error when parsing commandline: ' + str(e) + '\n')
sys.stderr.write(usage)
sys.exit(2)
d = {}
for o, a in opts:
if o in ('-h', '--help'):
sys.stdout.write(usage)
sys.exit(0)
if o in ('-t', '--textcolor'):
d['textcolor'] = a
if o in ('-b', '--background'):
d['background'] = a
if o in ('-d', '--setidir'):
d['setidir'] = a
if o in ('-n', '--nice'):
d['nice'] = a
if o in ('-r', '--rgbfile'):
d['rgbfile'] = a
if o in ('-c', '--configfile'):
d['configfile'] = a
if o in ('-p', '--progressbarcolor'):
d['progressbarcolor'] = a
if o in ('-g', '--barbgcolor'):
d['barbgcolor'] = a
if o in ('-i', '--indicatorcolor'):
d['indicatorcolor'] = a
return d
def parseColors(defaultRGBFileNames, config, xpm):
rgbFileName = ''
for fn in defaultRGBFileNames:
if os.access(fn, os.R_OK):
rgbFileName = fn
break
rgbFileName = config.get('rgbfile', rgbFileName)
useColors = 1
if not os.access(rgbFileName, os.R_OK):
sys.stderr.write(
"Can't read the RGB file, try setting it differently using -r,\n")
sys.stderr.write(
"Ignoring your color settings, using the defaults.\n")
useColors = 0
if useColors:
# Colors is a list with (<config_key>, <xpm-key>) pairs.
colors = (('indicatorcolor', 'indicator'),
('progressbarcolor', 'graph'),
('barbgcolor', 'graphbg'),
('textcolor', 'text'),
('background', 'background'))
for key, value in colors:
col = config.get(key)
if not col is None:
code = wmdocklib.getColorCode(col, rgbFileName)
if code is None:
sys.stderr.write('Bad colorcode for %s, ignoring.\n' % key)
else:
wmdocklib.setColor(xpm, value, code)
palette = {
' ': '#208120812081',
'.': '#00000000FFFF',
'o': '#C71BC30BC71B',
'O': '#861782078E38',
'+': '#EFBEF3CEEFBE',
'@': '#618561856185',
'#': '#9E79A2899E79',
'$': '#410341034103',
'o': '#2020b2b2aaaa',
'/': '#2020b2b2aaaa',
'-': '#707070707070',
'_': '#000000000000',
'%': '#2081B2CAAEBA',
}
background = \
[' ...............................................................................................',
' .///..___..ooo..___..___.......................................................................',
' .///..___..ooo..___..___.......................................................................',
' .///..___..ooo..___..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..ooo..___.......................................................................',
' .///..___..___..ooo..___.......................................................................',
' .///..___..___..ooo..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..___..___.......................................................................',
' .///..___..___..___..ooo.......................................................................',
' .///..___..___..___..ooo.......................................................................',
' .///..___..___..___..ooo.......................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...........................................................................................',
' .///...-------------------------------------------------------------------------------------...',
' .///...-------------------------------------------------------------------------------------...',
' .///...-------------------------------------------------------------------------------------...',
' .///...-------------------------------------------------------------------------------------...',
' .///...........................................................................................',
' .///////////////////////////////////////////////////////////////////////////////////////////...',
' .///////////////////////////////////////////////////////////////////////////////////////////...',
' .///////////////////////////////////////////////////////////////////////////////////////////...',
' .///////////////////////////////////////////////////////////////////////////////////////////...',
' ...............................................................................................',
' ...............................................................................................',
]
def main():
clConfig = parseCommandLine(sys.argv)
configFile = clConfig.get('configfile', defaultConfigFile)
configFile = os.path.expanduser(configFile)
fileConfig = wmdocklib.readConfigFile(configFile, sys.stderr)
# Merge the two configs, let the commandline options overwrite those in the
# configuration file.
config = fileConfig
for i in clConfig.iteritems():
config[i[0]] = i[1]
# Get the configurations
setiDir = config.get('setidir')
if setiDir is None:
sys.stderr.write(
'You have to supply a directory where seti@home resides. Either in\n')
sys.stderr.write(
'the configuration file or with -d/--setidir.\n')
sys.exit(3)
setiDir = os.path.expanduser(setiDir)
try:
os.chdir(setiDir)
except OSError, e:
sys.stderr.write('Error when accessing seti directory: %s\n' % str(e))
sys.exit(4)
statePath = os.path.join(setiDir, stateFileName)
uinfoPath = os.path.join(setiDir, uinfoFileName)
pidPath = os.path.join(setiDir, pidFileName)
execPath = os.path.join(setiDir, execFileName)
niceVal = config.get('nice')
if niceVal is None:
execCmd = execPath
else:
execCmd = execPath + ' -nice %s' % niceVal + '&'
try:
programName = sys.argv[0].split(os.sep)[-1]
except IndexError:
programName = ''
sys.argv[0] = programName
wmdocklib.initPixmap(background,
palette=palette)
wmdocklib.openXwindow(sys.argv, width, height)
wmdocklib.addMouseRegion(0, xOffset, yOffset, width - 2 * xOffset,
height - 2 * yOffset)
pwms = PywmSeti(statePath, uinfoPath, pidPath, execCmd)
pwms.mainLoop()
if __name__ == '__main__':
main()