#!/usr/bin/python -Wall
import gtk
import gtk.glade
import gobject
import time
import os
import sys
from optparse import OptionParser
class BugMe:
def __init__(self):
# Time limit in seconds (600)
self.timeLimit = 600
# First warn the user when this many seconds remain (120)
self.firstWarn = 120
# How often to warn initially (secs)
self.warnInterval = 60
# How often to warn after time has expired (secs)
self.annoyInterval = 30
# Hard time limit; user will be killed after this time
self.killTime = self.timeLimit * 1.5
if options.debugMode:
self.timeLimit = 120
self.firstWarn = 90
self.warnInterval = 30
self.annoyInterval = 10
self.killTime = 180
# (foreground, background)
self.colors = ('black', 'white')
defaultScreen = gtk.gdk.screen_get_default()
self.monitorGeometry = defaultScreen.get_monitor_geometry(defaultScreen.get_primary_monitor())
try:
self.xml = gtk.glade.XML(options.gladeFile)
except:
print "Failed to create GladeXML object."
# Kill the child
os.kill(pid, 9)
sys.exit(255)
self.startTime = int(time.time())
self.timerWindow = self.xml.get_widget('TimerWindow')
self.timerWindow.connect("visibility_notify_event",
self.visibility_event_handler)
self.timerWindow.set_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
self.nagDialog = self.xml.get_widget('NagDialog')
self.nagLabel = self.xml.get_widget('NagLabel')
self.elapsed_label = self.xml.get_widget('ElapsedLabel')
self.elapsed_label.set_markup("00:00")
self.timer = gobject.timeout_add(1000, self.updateTimer)
self.xml.signal_autoconnect(self)
if (options.corner == "NW"):
# 26 px should allow room for top panel
self.timerWindow.move(0,26)
elif (options.corner == "SE"):
self.timerWindow.set_gravity(gtk.gdk.GRAVITY_SOUTH_EAST)
width, height = self.timerWindow.get_size()
self.timerWindow.move(self.monitorGeometry.x + (self.monitorGeometry.width - width), self.monitorGeometry.y + (self.monitorGeometry.height - height))
self.timerWindow.show_all()
self.nextWarn = self.startTime + (self.timeLimit - self.firstWarn)
self.timeExpired = False
self.timerWindow.modify_bg(gtk.STATE_NORMAL,
gtk.gdk.color_parse(self.colors[1]))
def updateTimer(self):
if pid == os.waitpid(pid, os.WNOHANG)[0]:
sys.exit(0)
now = int(time.time())
elapsed = now - self.startTime
elapsedTime = (elapsed / 60, elapsed % 60)
self.elapsed_label.set_markup("%02d:%02d" % (self.colors + elapsedTime))
self.timerWindow.modify_bg(gtk.STATE_NORMAL,
gtk.gdk.color_parse(self.colors[1]))
if elapsed >= self.timeLimit:
self.colors = ('white', 'red')
self.warnInterval = self.annoyInterval
self.timeExpired = True
if now >= self.nextWarn:
if elapsed < self.timeLimit:
self.colors = ('black', 'orange')
self.nextWarn = now + self.warnInterval
self.nag(((self.timeLimit - elapsed) / 60,
(self.timeLimit - elapsed) % 60))
if options.killMode and elapsed >= self.killTime:
# The user has outstayed their welcome, nuke their session
print "Quitting."
sys.exit(0)
# It's a little odd to put this here, but it doesn't work in
# __init__; see comments in Trac #386
self.timerWindow.set_keep_above(True)
# And the above code doesn't work if the user has an "Always
# On Top" window above it. The following does, and bugme lives
# up to its name:
if elapsed % 10 == 0:
self.timerWindow.present()
return True
def nag(self, remainingTime):
if self.timeExpired:
msg="Please log out immediately."
if options.killMode:
msg += "\n\nYour session will be terminated in %d seconds." % (self.killTime - (int(time.time()) - self.startTime))
self.nagLabel.set_markup(""+msg+"")
else:
seconds = "%d second%s" % (remainingTime[1],
remainingTime[1] != 1 and 's' or '')
minutes = "%d minute%s" % (remainingTime[0],
remainingTime[0] != 1 and 's' or '')
if remainingTime[0] < 1:
remaining = seconds
elif remainingTime[1] == 0:
remaining = minutes
else:
remaining = "%s, %s" % (minutes, seconds)
self.nagLabel.set_markup("You have %s remaining\nin this session." % (remaining))
self.nagDialog.show()
def on_dialog_response(self, dialog, response_id):
if response_id == gtk.RESPONSE_CLOSE:
sys.exit(0)
elif response_id == gtk.RESPONSE_OK:
self.nagDialog.hide()
def visibility_event_handler(self, widget, event):
if event.state != gtk.gdk.VISIBILITY_UNOBSCURED:
self.timerWindow.present()
return True
if __name__ == '__main__':
parser = OptionParser(usage="%prog [--debug] progname [args]",
version="%prog 0.1")
parser.disable_interspersed_args()
parser.add_option("--debug",
action="store_true", dest="debugMode", default=False,
help="Enable debug mode (time limit of 2 minutes)")
parser.add_option("--fatal",
action="store_true", dest="killMode", default=False,
help="Kill the user session after 1.5 times the time limit.")
parser.add_option("--glade", action="store", type="string",
dest="gladeFile",
default="/usr/share/bugme/bugme.glade",
help="Specify an alternate Glade file for debugging")
#TODO: --geometry
parser.add_option("--corner", action="store", type="string",
dest="corner",
default="NW",
help="Specify a corner for the timer")
(options, args) = parser.parse_args()
if options.corner not in ("SE", "NW"):
print "Sorry, I don't know about the %s corner of the screen" % (options.corner)
sys.exit(255)
if not os.access(options.gladeFile, os.R_OK):
print 'error: Unable to read glade file "' + gladeFile + '"'
sys.exit(255)
if len(args) < 1:
parser.error("'progname' is required")
pid = os.fork()
if not pid:
os.putenv('ATHENA_QUICK', '1')
try:
os.execvp(args[0], args)
except:
print "error: Could not execvp(%s,%s)" % (args[0], args)
else:
BugMe()
gtk.main()