#! /usr/bin/env python # "M Clock" # # An implementation in software of an original design by Rob Juda. # Clock implementation: Guido van Rossum. # Alarm and Gong features: Sape Mullender. # # XXX TO DO: # add arguments to specify initial window position and size # find out local time zone difference automatically # add a date indicator # allow multiple alarms # allow the menu to change more parameters import sys from gl import * from GL import * from DEVICE import * import time import getopt import string import os from math import pi import math FULLC = 3600 # Full circle in 1/10-ths of a degree MIDN = 900 # Angle of the 12 o'clock position R, G, B = 0, 1, 2 # Indices of colors in RGB list HOUR = 3600 # Number of seconds per hour MINUTE = 60 # Number of seconds per minute class struct: pass # Class to define featureless structures Gl = struct() # Object to hold writable global variables # Default constants (used in multiple places) SCREENBG = 127, 156, 191 NPARTS = 9 TITLE = 'M Clock' # Set timezone, check for daylight saving time TZDIFF = time.timezone if time.localtime(time.time())[-1]: TZDIFF = time.altzone # Default parameters Gl.foreground = 0 # If set, run in the foreground Gl.fullscreen = 0 # If set, run on full screen Gl.tzdiff = TZDIFF # Seconds west of Greenwich (winter time) Gl.nparts = NPARTS # Number of parts each circle is divided in (>= 2) Gl.debug = 0 # If set, print debug output Gl.doublebuffer = 1 # If set, use double buffering Gl.update = 0 # Update interval; seconds hand is suppressed if > 1 Gl.colorsubset = 0 # If set, display only a subset of the colors Gl.cyan = 0 # If set, display cyan overlay (big hand) Gl.magenta = 0 # If set, display magenta overlay (little hand) Gl.yellow = 0 # If set, display yellow overlay (fixed background) Gl.black = 0 # If set, display black overlay (hands) Gl.colormap = 0 # If set, use colormap mode instead of RGB mode Gl.warnings = 0 # If set, print warnings Gl.title = '' # Window title (default set later) Gl.name = 'mclock' # Window title for resources Gl.border = 1 # If set, use a window border (and title) Gl.bg = 0, 0, 0 # Background color R, G, B value Gl.iconic = 0 # Set in iconic state Gl.fg = 255, 0, 0 # Alarm background RGB (either normal or alarm) Gl.ox,Gl.oy = 0,0 # Window origin Gl.cx,Gl.cy = 0,0 # Window size Gl.alarm_set = 0 # Alarm on or off Gl.alarm_on = 0 # Alarm is ringing Gl.alarm_time = 0 # Alarm time in seconds after midnight Gl.alarm_hours = 0 # Alarm hour setting, 24 hour clock Gl.alarm_minutes = 0 # Alarm minutes setting Gl.alarm_rgb = 0,0,0 # Alarm display RGB colors Gl.alarm_cmd = '' # Command to execute when alarm goes off Gl.mouse2down = 0 # Mouse button state Gl.mouse3down = 0 # Mouse button state Gl.gong_cmd = '' # Command to execute when chimes go off Gl.gong_int = 3600 # Gong interval Gl.indices = R, G, B # Colors (permuted when alarm is on) def main(): # sys.stdout = sys.stderr # All output is errors/warnings etc. # try: args = getoptions() except string.atoi_error, value: usage(string.atoi_error, value) except getopt.error, msg: usage(getopt.error, msg) # if args: realtime = 0 hours = string.atoi(args[0]) minutes = seconds = 0 if args[1:]: minutes = string.atoi(args[1]) if args[2:]: seconds = string.atoi(args[2]) localtime = ((hours*60)+minutes)*60+seconds else: realtime = 1 # if Gl.title == '': if realtime: Gl.title = TITLE else: title = '' for arg in args: title = title + ' ' + arg Gl.title = title[1:] del title # wid = makewindow() Gl.ox,Gl.oy = getorigin() Gl.cx,Gl.cy = getsize() initmenu() clearall() # if not Gl.update: Gl.update = 60 # if Gl.update <= 1: Gl.timernoise = 6 else: Gl.timernoise = 60 noise(TIMER0, Gl.timernoise) # qdevice(WINSHUT) qdevice(WINQUIT) qdevice(ESCKEY) if realtime: qdevice(TIMER0) qdevice(REDRAW) qdevice(WINFREEZE) qdevice(WINTHAW) qdevice(MENUBUTTON) # MOUSE1 qdevice(MOUSE3) # Left button qdevice(MOUSE2) # Middle button unqdevice(INPUTCHANGE) # lasttime = 0 Gl.change = 1 while 1: if realtime: localtime = int(time.time() - Gl.tzdiff) if Gl.alarm_set: if localtime%(24*HOUR) == Gl.alarm_time: # Ring the alarm! if Gl.debug: print 'Rrrringg!' Gl.alarm_on = 1 if Gl.alarm_cmd <> '': d = os.system(Gl.alarm_cmd+' '+`Gl.alarm_time/3600`+' '+`(Gl.alarm_time/60)%60` + ' &') Gl.change = 1 clearall() if Gl.alarm_on: if (localtime - Gl.alarm_time) % (24*HOUR) > 300: # More than 5 minutes away from alarm Gl.alarm_on = 0 if Gl.debug: print 'Alarm turned off' Gl.change = 1 clearall() Gl.indices = R, G, B else: if localtime % 2 == 0: # Permute color indices Gl.indices = Gl.indices[2:] + Gl.indices[:2] Gl.change = 1 if Gl.gong_cmd <> '' and localtime%Gl.gong_int == 0: d = os.system(Gl.gong_cmd+' '+`(localtime/3600)%24`+' '+`(localtime/60)%60` + ' &') if localtime/Gl.update <> lasttime/Gl.update: if Gl.debug: print 'new time' Gl.change = 1 if Gl.change: if Gl.debug: print 'drawing' doit(localtime) lasttime = localtime Gl.change = 0 dev, data = qread() if Gl.debug and dev <> TIMER0: print dev, data if dev == TIMER0: if Gl.debug > 1: print dev, data elif dev == MOUSE3: mousex = getvaluator(MOUSEX) mousey = getvaluator(MOUSEY) if mouseclick(3, data, mousex, mousey): Gl.change = 1 elif dev == MOUSE2: mousex = getvaluator(MOUSEX) mousey = getvaluator(MOUSEY) if mouseclick(2, data, mousex, mousey): Gl.change = 1 elif dev == MOUSEX: mousex = data if Gl.mouse2down: mouse2track(mousex, mousey) if Gl.mouse3down: mouse3track(mousex, mousey) elif dev == MOUSEY: mousey = data if Gl.mouse2down: mouse2track(mousex, mousey) if Gl.mouse3down: mouse3track(mousex, mousey) elif dev == REDRAW or dev == REDRAWICONIC: if Gl.debug: if dev == REDRAW: print 'REDRAW' else: print 'REDRAWICONIC' reshapeviewport() Gl.ox,Gl.oy = getorigin() Gl.cx,Gl.cy = getsize() Gl.change = 1 clearall() elif dev == MENUBUTTON: if Gl.debug: print 'MENUBUTTON' handlemenu() elif dev == WINFREEZE: if Gl.debug: print 'WINFREEZE' Gl.iconic = 1 noise(TIMER0, 60*60) # Redraw every 60 seconds only elif dev == WINTHAW: if Gl.debug: print 'WINTHAW' Gl.iconic = 0 noise(TIMER0, Gl.timernoise) Gl.change = 1 elif dev == ESCKEY or dev == WINSHUT or dev == WINQUIT: if Gl.debug: print 'Exit' sys.exit(0) def getoptions(): optlist, args = getopt.getopt(sys.argv[1:], 'A:a:B:bc:dFfG:g:n:sT:t:u:wCMYK') for optname, optarg in optlist: if optname == '-A': Gl.fg = eval(optarg) # Should be (r,g,b) elif optname == '-a': Gl.alarm_cmd = optarg elif optname == '-B': Gl.bg = eval(optarg) # Should be (r,g,b) elif optname == '-b': Gl.border = 0 elif optname == '-c': Gl.colormap = string.atoi(optarg) elif optname == '-d': Gl.debug = Gl.debug + 1 Gl.warnings = 1 elif optname == '-F': Gl.foreground = 1 elif optname == '-f': Gl.fullscreen = 1 elif optname == '-G': Gl.gong_int = 60*string.atoi(optarg) elif optname == '-g': Gl.gong_cmd = optarg elif optname == '-n': Gl.nparts = string.atoi(optarg) elif optname == '-s': Gl.doublebuffer = 0 elif optname == '-T': Gl.title = Gl.name = optarg elif optname == '-t': Gl.tzdiff = string.atoi(optarg) elif optname == '-u': Gl.update = string.atoi(optarg) elif optname == '-w': Gl.warnings = 1 elif optname == '-C': Gl.cyan = Gl.colorsubset = 1 elif optname == '-M': Gl.magenta = Gl.colorsubset = 1 elif optname == '-Y': Gl.yellow = Gl.colorsubset = 1 elif optname == '-K': Gl.black = Gl.colorsubset = 1 else: print 'Unsupported option', optname return args def usage(exc, msg): if sys.argv: progname = os.path.basename(sys.argv[0]) else: progname = 'mclock' # print progname + ':', if exc == string.atoi_error: print 'non-numeric argument:', print msg # print 'usage:', progname, '[options] [hh [mm [ss]]]' # print '-A r,g,b : alarm background red,green,blue [255,0,0]' print '-a cmd : shell command executed when alarm goes off' print '-B r,g,b : background red,green,blue [0,0,0]' print ' (-B SCREENBG uses the default screen background)' print '-b : suppress window border and title' print '-c cmapid : select explicit colormap' print '-d : more debug output (implies -F, -w)' print '-F : run in foreground' print '-f : use full screen' print '-G intrvl : interval between chimes in minutes [60]' print '-g cmd : shell command executed when chimes go off' print '-s : single buffer mode' print '-w : print various warnings' print '-n nparts : number of parts [' + `NPARTS` + ']' print '-T title : alternate window title [\'' + TITLE + '\']' print '-t tzdiff : time zone difference [' + `TZDIFF` + ']' print '-u update : update interval [60]' print '-CMYK : Cyan, Magenta, Yellow or blacK overlay only' print 'if hh [mm [ss]] is specified, display that time statically' print 'on machines with < 12 bitplanes, -s is forced on' # sys.exit(2) def doit(localtime): hands = makehands(localtime) list = makelist(hands) render(list, hands) def makehands(localtime): localtime = localtime % (12*HOUR) seconds_hand = MIDN + FULLC - (localtime*60) % FULLC big_hand = (MIDN + FULLC - (localtime%HOUR)) % FULLC little_hand = (MIDN + FULLC - ((localtime/12) % HOUR)) % FULLC return little_hand, big_hand, seconds_hand def makelist(hands): little_hand, big_hand, seconds_hand = hands total = [] if Gl.cyan or not Gl.colorsubset: total = total + makesublist(big_hand, Gl.indices[0]) if Gl.magenta or not Gl.colorsubset: total = total + makesublist(little_hand, Gl.indices[1]) if Gl.yellow or not Gl.colorsubset: total = total + makesublist(MIDN, Gl.indices[2]) total.sort() return total def makesublist(first, icolor): list = [] alpha = FULLC/Gl.nparts a = first - alpha/2 for i in range(Gl.nparts): angle = (a + i*alpha + FULLC) % FULLC value = 255*(Gl.nparts-1-i)/(Gl.nparts-1) list.append((angle, icolor, value)) list.sort() a, icolor, value = list[0] if a <> 0: a, icolor, value = list[len(list)-1] t = 0, icolor, value list.insert(0, t) return list def rgb_fg(): return Gl.fg # Obsolete code: if Gl.alarm_on: return Gl.bg else: return Gl.fg def rgb_bg(): return Gl.bg # Obsolete code: if Gl.alarm_on: return Gl.fg else: return Gl.bg def clearall(): Gl.c3i(rgb_bg()) clear() if Gl.doublebuffer: swapbuffers() clear() def draw_alarm(color): frontbuffer(TRUE) Gl.c3i(color) pushmatrix() rotate(-((Gl.alarm_time/12)%3600), 'z') bgnpolygon() v2f( 0.00,1.00) v2f( 0.04,1.05) v2f(-0.04,1.05) endpolygon() popmatrix() # pushmatrix() rotate(-((Gl.alarm_time)%3600), 'z') bgnpolygon() v2f( 0.00,1.05) v2f( 0.07,1.10) v2f(-0.07,1.10) endpolygon() popmatrix() # cmov2(-1.06, -1.06) charstr(string.rjust(`Gl.alarm_time/3600`,2)) charstr(':') charstr(string.zfill((Gl.alarm_time/60)%60,2)) frontbuffer(FALSE) def render(list, (little_hand, big_hand, seconds_hand)): # if Gl.colormap: resetindex() # if not list: Gl.c3i((255, 255, 255)) # White circf(0.0, 0.0, 1.0) else: list.append((3600, 0, 255)) # Sentinel # rgb = [255, 255, 255] a_prev = 0 for a, icolor, value in list: if a <> a_prev: [r, g, b] = rgb if Gl.debug > 1: print rgb, a_prev, a Gl.c3i((r, g, b)) arcf(0.0, 0.0, 1.0, a_prev, a) rgb[icolor] = value a_prev = a # if Gl.black or not Gl.colorsubset: # # Draw the hands -- in black # Gl.c3i((0, 0, 0)) # if Gl.update == 1 and not Gl.iconic: # Seconds hand is only drawn if we update every second pushmatrix() rotate(seconds_hand, 'z') bgnline() v2f(0.0, 0.0) v2f(1.0, 0.0) endline() popmatrix() # pushmatrix() rotate(big_hand, 'z') rectf(0.0, -0.01, 0.97, 0.01) circf(0.0, 0.0, 0.01) circf(0.97, 0.0, 0.01) popmatrix() # pushmatrix() rotate(little_hand, 'z') rectf(0.04, -0.02, 0.63, 0.02) circf(0.04, 0.0, 0.02) circf(0.63, 0.0, 0.02) popmatrix() # # Draw the alarm time, if set or being set # if Gl.alarm_set: draw_alarm(rgb_fg()) # if Gl.doublebuffer: swapbuffers() def makewindow(): # if Gl.debug or Gl.foreground: foreground() # if Gl.fullscreen: scrwidth, scrheight = getgdesc(GD_XPMAX), getgdesc(GD_YPMAX) prefposition(0, scrwidth-1, 0, scrheight-1) else: keepaspect(1, 1) prefsize(80, 80) # if not Gl.border: noborder() wid = winopen(Gl.name) wintitle(Gl.title) # if not Gl.fullscreen: keepaspect(1, 1) minsize(10, 10) maxsize(2000, 2000) iconsize(66, 66) winconstraints() # nplanes = getplanes() nmaps = getgdesc(GD_NMMAPS) if Gl.warnings: print nplanes, 'color planes,', nmaps, 'color maps' # if Gl.doublebuffer and not Gl.colormap and nplanes < 12: if Gl.warnings: print 'forcing single buffer mode' Gl.doublebuffer = 0 # if Gl.colormap: if not Gl.colormap: Gl.colormap = nmaps - 1 if Gl.warnings: print 'not enough color planes available', print 'for RGB mode; forcing colormap mode' print 'using color map number', Gl.colormap if not Gl.colorsubset: needed = 3 else: needed = Gl.cyan + Gl.magenta + Gl.yellow needed = needed*Gl.nparts if Gl.bg <> (0, 0, 0): needed = needed+1 if Gl.fg <> (0, 0, 0): needed = needed+1 if Gl.doublebuffer: if needed > available(nplanes/2): Gl.doublebuffer = 0 if Gl.warnings: print 'not enough colors available', print 'for double buffer mode;', print 'forcing single buffer mode' else: nplanes = nplanes/2 if needed > available(nplanes): # Do this warning always print 'still not enough colors available;', print 'parts will be left white' print '(needed', needed, 'but have only', print available(nplanes), 'colors available)' # if Gl.doublebuffer: doublebuffer() gconfig() # if Gl.colormap: Gl.c3i = pseudo_c3i fixcolormap() else: Gl.c3i = c3i RGBmode() gconfig() # if Gl.fullscreen: # XXX Should find out true screen size using getgdesc() ortho2(-1.1*1.280, 1.1*1.280, -1.1*1.024, 1.1*1.024) else: ortho2(-1.1, 1.1, -1.1, 1.1) # return wid def available(nplanes): return pow(2, nplanes) - 1 # Reserve one pixel for black def fixcolormap(): multimap() gconfig() nplanes = getplanes() if Gl.warnings: print 'multimap mode has', nplanes, 'color planes' imap = Gl.colormap Gl.startindex = pow(2, nplanes) - 1 Gl.stopindex = 1 setmap(imap) mapcolor(0, 0, 0, 0) # Fixed entry for black if Gl.bg <> (0, 0, 0): r, g, b = Gl.bg mapcolor(1, r, g, b) # Fixed entry for Gl.bg Gl.stopindex = 2 if Gl.fg <> (0, 0, 0): r, g, b = Gl.fg mapcolor(2, r, g, b) # Fixed entry for Gl.fg Gl.stopindex = 3 Gl.overflow_seen = 0 resetindex() def resetindex(): Gl.index = Gl.startindex r0g0b0 = (0, 0, 0) def pseudo_c3i(rgb): if rgb == r0g0b0: index = 0 elif rgb == Gl.bg: index = 1 elif rgb == Gl.fg: index = 2 else: index = definecolor(rgb) color(index) def definecolor(rgb): index = Gl.index if index < Gl.stopindex: if Gl.debug: print 'definecolor hard case', rgb # First see if we already have this one... for index in range(Gl.stopindex, Gl.startindex+1): if rgb == getmcolor(index): if Gl.debug: print 'return', index return index # Don't clobber reserverd colormap entries if not Gl.overflow_seen: # Shouldn't happen any more, hence no Gl.warnings test print 'mclock: out of colormap entries' Gl.overflow_seen = 1 return Gl.stopindex r, g, b = rgb if Gl.debug > 1: print 'mapcolor', (index, r, g, b) mapcolor(index, r, g, b) Gl.index = index - 1 return index # Compute n**i def pow(n, i): x = 1 for j in range(i): x = x*n return x def mouseclick(mouse, updown, x, y): if updown == 1: # mouse button came down, start tracking if Gl.debug: print 'mouse', mouse, 'down at', x, y if mouse == 2: Gl.mouse2down = 1 mouse2track(x, y) elif mouse == 3: Gl.mouse3down = 1 mouse3track(x, y) else: print 'fatal error' qdevice(MOUSEX) qdevice(MOUSEY) return 0 else: # mouse button came up, stop tracking if Gl.debug: print 'mouse', mouse, 'up at', x, y unqdevice(MOUSEX) unqdevice(MOUSEY) if mouse == 2: mouse2track(x, y) Gl.mouse2down = 0 elif mouse == 3: mouse3track(x, y) Gl.mouse3down = 0 else: print 'fatal error' Gl.alarm_set = 1 return 1 def mouse3track(x, y): # first compute polar coordinates from x and y cx, cy = Gl.ox + Gl.cx/2, Gl.oy + Gl.cy/2 x, y = x - cx, y - cy if (x, y) == (0, 0): return # would cause an exception minutes = int(30.5 + 30.0*math.atan2(float(-x), float(-y))/pi) if minutes == 60: minutes = 0 a,b = Gl.alarm_minutes/15, minutes/15 if (a,b) == (0,3): # Moved backward through 12 o'clock: Gl.alarm_hours = Gl.alarm_hours - 1 if Gl.alarm_hours < 0: Gl.alarm_hours = Gl.alarm_hours + 24 if (a,b) == (3,0): # Moved forward through 12 o'clock: Gl.alarm_hours = Gl.alarm_hours + 1 if Gl.alarm_hours >= 24: Gl.alarm_hours = Gl.alarm_hours - 24 Gl.alarm_minutes = minutes seconds = Gl.alarm_hours * HOUR + Gl.alarm_minutes * MINUTE if seconds <> Gl.alarm_time: draw_alarm(rgb_bg()) Gl.alarm_time = seconds draw_alarm(rgb_fg()) def mouse2track(x, y): # first compute polar coordinates from x and y cx, cy = Gl.ox + Gl.cx/2, Gl.oy + Gl.cy/2 x, y = x - cx, y - cy if (x, y) == (0, 0): return # would cause an exception hours = int(6.5 - float(Gl.alarm_minutes)/60.0 + 6.0*math.atan2(float(-x), float(-y))/pi) if hours == 12: hours = 0 if (Gl.alarm_hours,hours) == (0,11): # Moved backward through midnight: Gl.alarm_hours = 23 elif (Gl.alarm_hours,hours) == (12,11): # Moved backward through noon: Gl.alarm_hours = 11 elif (Gl.alarm_hours,hours) == (11,0): # Moved forward through noon: Gl.alarm_hours = 12 elif (Gl.alarm_hours,hours) == (23,0): # Moved forward through midnight: Gl.alarm_hours = 0 elif Gl.alarm_hours < 12: Gl.alarm_hours = hours else: Gl.alarm_hours = hours + 12 seconds = Gl.alarm_hours * HOUR + Gl.alarm_minutes * MINUTE if seconds <> Gl.alarm_time: draw_alarm(rgb_bg()) Gl.alarm_time = seconds draw_alarm(rgb_fg()) def initmenu(): Gl.pup = pup = newpup() addtopup(pup, 'M Clock%t|Alarm On/Off|Seconds Hand On/Off|Quit', 0) def handlemenu(): item = dopup(Gl.pup) if item == 1: # Toggle alarm if Gl.alarm_set: Gl.alarm_set = 0 Gl.alarm_on = 0 else: Gl.alarm_set = 1 Gl.change = 1 clearall() elif item == 2: # Toggle Seconds Hand if Gl.update == 1: Gl.update = 60 Gl.timernoise = 60 else: Gl.update = 1 Gl.timernoise = 6 Gl.change = 1 elif item == 3: if Gl.debug: print 'Exit' sys.exit(0) main()