#!/usr/pkg/bin/python """logout current xwindows session after time elapsed displays digital stopwatch as time is being counted down... then sends kill signals to -1, which log out the user and restart X. in console mode, only counts down (for testing)""" Copyright = """ hourglass -- countdown and logout Xwindows session Copyright (C) 2004 John Comeau 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ errormessage = "Not all needed libraries found, upgrade or check path" try: True # not defined in older Python releases except: True, False = 1, 0 try: import sys, os, types, re, pwd sys.path.append(os.sep.join([pwd.getpwuid(os.geteuid())[5], 'lib', 'python'])) from com.jcomeau import gpl, jclicense except: try: sys.stderr.write("%s\n" % errormessage) except: print errormessage raise # get name this program was called as self = sys.argv[0].split(os.sep)[-1] command = self.split('.')[0] # strip suffix (extension) if any # now get name we gave it when we wrote it originalself = Copyright.split()[0] # globals and routines that should be in every program # (yes, you could import them, but there are problems in that approach too) def DebugPrint(*whatever): return False # defined instead by pytest module, use that for debugging # other globals, specific to this program import select, time from signal import * from fcntl import * from Tkinter import * from Tkconstants import * from com.jcomeau import quiescent logintimeout = 60 * 15 # logged-in default, in seconds timeout = 60 # non-logged-in default, in seconds seconds = timeout # timeout actually being used will vary tk = None frame = None console = False terminated = False logoutfile = '/tmp/hourglass.txt' updateinterval = 60 # update file every "this many" seconds def readseconds(*args): global logoutfile, timeout DebugPrint("reading initial timeout from file") try: filehandle = open(logoutfile, 'r') fileseconds = int(filehandle.readline()) filehandle.close() except: fileseconds = timeout # just use hard-coded default return fileseconds def setseconds(*args): global seconds, logoutfile, updateinterval DebugPrint("setting initial timeout") seconds = readseconds() if (seconds < updateinterval): seconds = updateinterval # use sane minimum def updateseconds(*args): """update seconds count in file""" global seconds, logoutfile DebugPrint("updating seconds count in file") try: filehandle = open(logoutfile, 'r+') # read in what it says, in case it was updated meanwhile seconds = max(int(filehandle.readline()) - updateinterval, seconds) except: try: filehandle = open(logoutfile, 'w') except: sys.stderr.write("cannot open %s for writing\n", logoutfile) sys.exit(1) try: flock(filehandle, LOCK_EX) except: sys.stderr.write("cannot lock %s\n", logoutfile) filehandle.seek(0, 0) # rewind to start of file filehandle.write("%d\n" % seconds) filehandle.close() # automatically releases lock def writeseconds(*args): global seconds, logoutfile DebugPrint("setting seconds count in file to %d" % seconds) try: filehandle = open(logoutfile, 'w') except: sys.stderr.write("cannot open %s for writing\n", logoutfile) sys.exit(1) try: flock(filehandle, LOCK_EX) except: sys.stderr.write("cannot lock %s\n", logoutfile) filehandle.seek(0, 0) # rewind to start of file filehandle.write("%d\n" % seconds) filehandle.close() # automatically releases lock def addpesos(*args): global logintimeout try: if len(args) == 1: pesos = float(args[0]) DebugPrint("found %f pesos from args" % pesos) else: pesos = float(sys.argv[1]) sys.argv.pop(0) DebugPrint("found %f pesos from sys.argv[1]" % pesos) amount = (pesos / 2) * logintimeout # FIXME -- hardcoded policy DebugPrint("calling addsand(%d)" % amount) addsand(amount) except: raise # FIXME -- something more intelligent than 'raise' def addminutes(*args): try: if len(args) == 1: minutes = int(args[0]) DebugPrint("found %d minutes from args" % minutes) else: minutes = int(sys.argv[1]) sys.argv.pop(0) DebugPrint("found %d minutes from sys.argv[1]" % minutes) addsand(minutes * 60) except: raise # FIXME -- something more intelligent than 'raise' def addsand(*args): global seconds, logoutfile, updateinterval DebugPrint("addsand: args: ", args) try: if len(args) == 1: added = max(int(args[0]), updateinterval) else: added = max(int(sys.argv[1]), updateinterval) except: DebugPrint("addsand: no amount specified, using default") added = logintimeout DebugPrint("adding %d to seconds count in file" % added) try: filehandle = open(logoutfile, 'r+') # read in current timeout and add to it seconds = max(int(filehandle.readline()), updateinterval) + added except: sys.stderr.write("%s must first be created by user process\n" % logoutfile) sys.exit(1) try: flock(filehandle, LOCK_EX) except: sys.stderr.write("cannot lock %s\n", logoutfile) filehandle.seek(0, 0) # rewind to start of file filehandle.write("%d\n" % seconds) filehandle.close() # automatically releases lock def logout(*args): global terminated, console, seconds DebugPrint("exiting, terminated=%s, console=%s" % (terminated, console)) DebugPrint("args passed were: ", args) seconds = 0 # fix so next startup is from default writeseconds() if console: pid = os.getpid() # just quit if testing program else: pid = -1 # logout the user DebugPrint("killing PID %d" % pid) if terminated: os.kill(pid, SIGKILL) else: os.kill(pid, SIGTERM) terminated = True def countdown(*args): global seconds, tk, frame, updateinterval, originalself seconds = max(seconds - 1, 0) # don't let it go negative timestring = timedisplay() if (seconds % updateinterval) == 0: updateseconds() if frame != None: frame.label['text'] = timestring tk.title("%s %s" % (originalself, timestring)) tk.iconname("%s %s" % (originalself, timestring)) else: sys.stderr.write("time remaining: %s\n" % timestring) if seconds == 0 and readseconds() == 0 and quiescent.minutes() >= 3: """do nothing... just stay in suspended animation at 0 seconds this lets the monitor go into "green" energy-saving mode""" elif seconds == 0: DebugPrint("attempting to logout") logout() alarm(1) # restart the countdown (yes, it's necessary -- I checked) def timedisplay(): global seconds return("%02d:%02d:%02d" % (int(seconds / 3600), int((seconds / 60) % 60), seconds % 60)) def hourglass(): global console, updateinterval, timeout, tk, frame, originalself setseconds() # get timeout and set global 'seconds' signal(SIGALRM, countdown) signal(SIGINT, logout) # FIXME -- need to trap ALL process killers alarm(1) # start the countdown try: tk = Tk() frame = Frame(tk, relief=RIDGE, borderwidth=2) frame.pack(fill=BOTH, expand=1) frame.label = Label(frame, text=timedisplay()) frame.label.pack(fill=X, expand=1) frame.button = Button(frame,text="Hide",command=tk.wm_iconify) frame.button.pack(side=BOTTOM) # smart-alecks will try closing the program... # they won't make that mistake more than once tk.protocol('WM_DELETE_WINDOW', logout) tk.title("%s %s" % (originalself, "--:--:--")) tk.iconify() # start minimized so idiots don't feel the need to close it tk.mainloop() except TclError: # testing in console mode console = True timeout = 60 # lower default values to something more useful for testing updateinterval = 10 sys.stderr.write("Timing out %d seconds\n" % seconds) sys.stderr.write("Hit ^C (control-c) to quit\n") DebugPrint("console: ", console) # this was the most elegant way I could think of to wait, # and let the signals drive the program while True: try: select.select([], [], [0]) # wait for exception on stdin except: # system call will keep getting interrupted by SIGALRM pass except: # FIXME: do something more intelligent than 'raise' here raise if __name__ == '__main__': # if this program was imported by another, the above test will fail, # and this following code won't be used... if command == 'hourglass': hourglass() elif command == 'addsand': addsand() elif command == 'addpesos': addpesos() elif command == 'addminutes': addminutes() else: sys.stderr.write("%s: unknown command: %s\n" % (originalself, command)) sys.exit(1) else: # if you want something to be done on import, do it here; otherwise pass pass