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