5 # An implementation in software of an original design by Rob Juda.
6 # Clock implementation: Guido van Rossum.
7 # Alarm and Gong features: Sape Mullender.
10 # add arguments to specify initial window position and size
11 # find out local time zone difference automatically
12 # add a date indicator
13 # allow multiple alarms
14 # allow the menu to change more parameters
28 FULLC
= 3600 # Full circle in 1/10-ths of a degree
29 MIDN
= 900 # Angle of the 12 o'clock position
30 R
, G
, B
= 0, 1, 2 # Indices of colors in RGB list
32 HOUR
= 3600 # Number of seconds per hour
33 MINUTE
= 60 # Number of seconds per minute
35 class struct
: pass # Class to define featureless structures
36 Gl
= struct() # Object to hold writable global variables
38 # Default constants (used in multiple places)
40 SCREENBG
= 127, 156, 191
44 # Set timezone, check for daylight saving time
45 TZDIFF
= time
.timezone
46 if time
.localtime(time
.time())[-1]:
51 Gl
.foreground
= 0 # If set, run in the foreground
52 Gl
.fullscreen
= 0 # If set, run on full screen
53 Gl
.tzdiff
= TZDIFF
# Seconds west of Greenwich (winter time)
54 Gl
.nparts
= NPARTS
# Number of parts each circle is divided in (>= 2)
55 Gl
.debug
= 0 # If set, print debug output
56 Gl
.doublebuffer
= 1 # If set, use double buffering
57 Gl
.update
= 0 # Update interval; seconds hand is suppressed if > 1
58 Gl
.colorsubset
= 0 # If set, display only a subset of the colors
59 Gl
.cyan
= 0 # If set, display cyan overlay (big hand)
60 Gl
.magenta
= 0 # If set, display magenta overlay (little hand)
61 Gl
.yellow
= 0 # If set, display yellow overlay (fixed background)
62 Gl
.black
= 0 # If set, display black overlay (hands)
63 Gl
.colormap
= 0 # If set, use colormap mode instead of RGB mode
64 Gl
.warnings
= 0 # If set, print warnings
65 Gl
.title
= '' # Window title (default set later)
66 Gl
.name
= 'mclock' # Window title for resources
67 Gl
.border
= 1 # If set, use a window border (and title)
68 Gl
.bg
= 0, 0, 0 # Background color R, G, B value
69 Gl
.iconic
= 0 # Set in iconic state
70 Gl
.fg
= 255, 0, 0 # Alarm background RGB (either normal or alarm)
71 Gl
.ox
,Gl
.oy
= 0,0 # Window origin
72 Gl
.cx
,Gl
.cy
= 0,0 # Window size
73 Gl
.alarm_set
= 0 # Alarm on or off
74 Gl
.alarm_on
= 0 # Alarm is ringing
75 Gl
.alarm_time
= 0 # Alarm time in seconds after midnight
76 Gl
.alarm_hours
= 0 # Alarm hour setting, 24 hour clock
77 Gl
.alarm_minutes
= 0 # Alarm minutes setting
78 Gl
.alarm_rgb
= 0,0,0 # Alarm display RGB colors
79 Gl
.alarm_cmd
= '' # Command to execute when alarm goes off
80 Gl
.mouse2down
= 0 # Mouse button state
81 Gl
.mouse3down
= 0 # Mouse button state
82 Gl
.gong_cmd
= '' # Command to execute when chimes go off
83 Gl
.gong_int
= 3600 # Gong interval
84 Gl
.indices
= R
, G
, B
# Colors (permuted when alarm is on)
88 sys
.stdout
= sys
.stderr
# All output is errors/warnings etc.
92 except string
.atoi_error
, value
:
93 usage(string
.atoi_error
, value
)
94 except getopt
.error
, msg
:
95 usage(getopt
.error
, msg
)
99 hours
= string
.atoi(args
[0])
100 minutes
= seconds
= 0
101 if args
[1:]: minutes
= string
.atoi(args
[1])
102 if args
[2:]: seconds
= string
.atoi(args
[2])
103 localtime
= ((hours
*60)+minutes
)*60+seconds
112 for arg
in args
: title
= title
+ ' ' + arg
117 Gl
.ox
,Gl
.oy
= getorigin()
118 Gl
.cx
,Gl
.cy
= getsize()
129 noise(TIMER0
, Gl
.timernoise
)
139 qdevice(MENUBUTTON
) # MOUSE1
140 qdevice(MOUSE3
) # Left button
141 qdevice(MOUSE2
) # Middle button
142 unqdevice(INPUTCHANGE
)
148 localtime
= int(time
.time() - Gl
.tzdiff
)
150 if localtime
%(24*HOUR
) == Gl
.alarm_time
:
155 if Gl
.alarm_cmd
<> '':
156 d
= os
.system(Gl
.alarm_cmd
+' '+`Gl
.alarm_time
/3600`
+' '+`
(Gl
.alarm_time
/60)%60`
+ ' &')
160 if (localtime
- Gl
.alarm_time
) % (24*HOUR
) > 300:
161 # More than 5 minutes away from alarm
164 print 'Alarm turned off'
169 if localtime
% 2 == 0:
170 # Permute color indices
171 Gl
.indices
= Gl
.indices
[2:] + Gl
.indices
[:2]
173 if Gl
.gong_cmd
<> '' and localtime
%Gl
.gong_int
== 0:
174 d
= os
.system(Gl
.gong_cmd
+' '+`
(localtime
/3600)%24`
+' '+`
(localtime
/60)%60`
+ ' &')
175 if localtime
/Gl
.update
<> lasttime
/Gl
.update
:
176 if Gl
.debug
: print 'new time'
179 if Gl
.debug
: print 'drawing'
184 if Gl
.debug
and dev
<> TIMER0
:
190 mousex
= getvaluator(MOUSEX
)
191 mousey
= getvaluator(MOUSEY
)
192 if mouseclick(3, data
, mousex
, mousey
):
195 mousex
= getvaluator(MOUSEX
)
196 mousey
= getvaluator(MOUSEY
)
197 if mouseclick(2, data
, mousex
, mousey
):
202 mouse2track(mousex
, mousey
)
204 mouse3track(mousex
, mousey
)
208 mouse2track(mousex
, mousey
)
210 mouse3track(mousex
, mousey
)
211 elif dev
== REDRAW
or dev
== REDRAWICONIC
:
213 if dev
== REDRAW
: print 'REDRAW'
214 else: print 'REDRAWICONIC'
216 Gl
.ox
,Gl
.oy
= getorigin()
217 Gl
.cx
,Gl
.cy
= getsize()
220 elif dev
== MENUBUTTON
:
221 if Gl
.debug
: print 'MENUBUTTON'
223 elif dev
== WINFREEZE
:
224 if Gl
.debug
: print 'WINFREEZE'
226 noise(TIMER0
, 60*60) # Redraw every 60 seconds only
228 if Gl
.debug
: print 'WINTHAW'
230 noise(TIMER0
, Gl
.timernoise
)
232 elif dev
== ESCKEY
or dev
== WINSHUT
or dev
== WINQUIT
:
233 if Gl
.debug
: print 'Exit'
237 optlist
, args
= getopt
.getopt(sys
.argv
[1:], 'A:a:B:bc:dFfG:g:n:sT:t:u:wCMYK')
238 for optname
, optarg
in optlist
:
240 Gl
.fg
= eval(optarg
) # Should be (r,g,b)
241 elif optname
== '-a':
242 Gl
.alarm_cmd
= optarg
243 elif optname
== '-B':
244 Gl
.bg
= eval(optarg
) # Should be (r,g,b)
245 elif optname
== '-b':
247 elif optname
== '-c':
248 Gl
.colormap
= string
.atoi(optarg
)
249 elif optname
== '-d':
250 Gl
.debug
= Gl
.debug
+ 1
252 elif optname
== '-F':
254 elif optname
== '-f':
256 elif optname
== '-G':
257 Gl
.gong_int
= 60*string
.atoi(optarg
)
258 elif optname
== '-g':
260 elif optname
== '-n':
261 Gl
.nparts
= string
.atoi(optarg
)
262 elif optname
== '-s':
264 elif optname
== '-T':
265 Gl
.title
= Gl
.name
= optarg
266 elif optname
== '-t':
267 Gl
.tzdiff
= string
.atoi(optarg
)
268 elif optname
== '-u':
269 Gl
.update
= string
.atoi(optarg
)
270 elif optname
== '-w':
272 elif optname
== '-C':
273 Gl
.cyan
= Gl
.colorsubset
= 1
274 elif optname
== '-M':
275 Gl
.magenta
= Gl
.colorsubset
= 1
276 elif optname
== '-Y':
277 Gl
.yellow
= Gl
.colorsubset
= 1
278 elif optname
== '-K':
279 Gl
.black
= Gl
.colorsubset
= 1
281 print 'Unsupported option', optname
286 progname
= os
.path
.basename(sys
.argv
[0])
290 print progname
+ ':',
291 if exc
== string
.atoi_error
:
292 print 'non-numeric argument:',
295 print 'usage:', progname
, '[options] [hh [mm [ss]]]'
297 print '-A r,g,b : alarm background red,green,blue [255,0,0]'
298 print '-a cmd : shell command executed when alarm goes off'
299 print '-B r,g,b : background red,green,blue [0,0,0]'
300 print ' (-B SCREENBG uses the default screen background)'
301 print '-b : suppress window border and title'
302 print '-c cmapid : select explicit colormap'
303 print '-d : more debug output (implies -F, -w)'
304 print '-F : run in foreground'
305 print '-f : use full screen'
306 print '-G intrvl : interval between chimes in minutes [60]'
307 print '-g cmd : shell command executed when chimes go off'
308 print '-s : single buffer mode'
309 print '-w : print various warnings'
310 print '-n nparts : number of parts [' + `NPARTS`
+ ']'
311 print '-T title : alternate window title [\'' + TITLE
+ '\']'
312 print '-t tzdiff : time zone difference [' + `TZDIFF`
+ ']'
313 print '-u update : update interval [60]'
314 print '-CMYK : Cyan, Magenta, Yellow or blacK overlay only'
315 print 'if hh [mm [ss]] is specified, display that time statically'
316 print 'on machines with < 12 bitplanes, -s is forced on'
321 hands
= makehands(localtime
)
322 list = makelist(hands
)
325 def makehands(localtime
):
326 localtime
= localtime
% (12*HOUR
)
327 seconds_hand
= MIDN
+ FULLC
- (localtime
*60) % FULLC
328 big_hand
= (MIDN
+ FULLC
- (localtime
%HOUR
)) % FULLC
329 little_hand
= (MIDN
+ FULLC
- ((localtime
/12) % HOUR
)) % FULLC
330 return little_hand
, big_hand
, seconds_hand
333 little_hand
, big_hand
, seconds_hand
= hands
335 if Gl
.cyan
or not Gl
.colorsubset
:
336 total
= total
+ makesublist(big_hand
, Gl
.indices
[0])
337 if Gl
.magenta
or not Gl
.colorsubset
:
338 total
= total
+ makesublist(little_hand
, Gl
.indices
[1])
339 if Gl
.yellow
or not Gl
.colorsubset
:
340 total
= total
+ makesublist(MIDN
, Gl
.indices
[2])
344 def makesublist(first
, icolor
):
346 alpha
= FULLC
/Gl
.nparts
348 for i
in range(Gl
.nparts
):
349 angle
= (a
+ i
*alpha
+ FULLC
) % FULLC
350 value
= 255*(Gl
.nparts
-1-i
)/(Gl
.nparts
-1)
351 list.append((angle
, icolor
, value
))
353 a
, icolor
, value
= list[0]
355 a
, icolor
, value
= list[len(list)-1]
383 def draw_alarm(color
):
387 rotate(-((Gl
.alarm_time
/12)%3600), 'z')
396 rotate(-((Gl
.alarm_time
)%3600), 'z')
405 charstr(string
.rjust(`Gl
.alarm_time
/3600`
,2))
407 charstr(string
.zfill((Gl
.alarm_time
/60)%60,2))
410 def render(list, (little_hand
, big_hand
, seconds_hand
)):
416 Gl
.c3i((255, 255, 255)) # White
419 list.append((3600, 0, 255)) # Sentinel
421 rgb
= [255, 255, 255]
423 for a
, icolor
, value
in list:
429 arcf(0.0, 0.0, 1.0, a_prev
, a
)
433 if Gl
.black
or not Gl
.colorsubset
:
435 # Draw the hands -- in black
439 if Gl
.update
== 1 and not Gl
.iconic
:
440 # Seconds hand is only drawn if we update every second
442 rotate(seconds_hand
, 'z')
450 rotate(big_hand
, 'z')
451 rectf(0.0, -0.01, 0.97, 0.01)
452 circf(0.0, 0.0, 0.01)
453 circf(0.97, 0.0, 0.01)
457 rotate(little_hand
, 'z')
458 rectf(0.04, -0.02, 0.63, 0.02)
459 circf(0.04, 0.0, 0.02)
460 circf(0.63, 0.0, 0.02)
463 # Draw the alarm time, if set or being set
468 if Gl
.doublebuffer
: swapbuffers()
472 if Gl
.debug
or Gl
.foreground
:
476 scrwidth
, scrheight
= getgdesc(GD_XPMAX
), getgdesc(GD_YPMAX
)
477 prefposition(0, scrwidth
-1, 0, scrheight
-1)
484 wid
= winopen(Gl
.name
)
487 if not Gl
.fullscreen
:
494 nplanes
= getplanes()
495 nmaps
= getgdesc(GD_NMMAPS
)
497 print nplanes
, 'color planes,', nmaps
, 'color maps'
499 if Gl
.doublebuffer
and not Gl
.colormap
and nplanes
< 12:
500 if Gl
.warnings
: print 'forcing single buffer mode'
505 Gl
.colormap
= nmaps
- 1
507 print 'not enough color planes available',
508 print 'for RGB mode; forcing colormap mode'
509 print 'using color map number', Gl
.colormap
510 if not Gl
.colorsubset
:
513 needed
= Gl
.cyan
+ Gl
.magenta
+ Gl
.yellow
514 needed
= needed
*Gl
.nparts
515 if Gl
.bg
<> (0, 0, 0):
517 if Gl
.fg
<> (0, 0, 0):
520 if needed
> available(nplanes
/2):
523 print 'not enough colors available',
524 print 'for double buffer mode;',
525 print 'forcing single buffer mode'
528 if needed
> available(nplanes
):
529 # Do this warning always
530 print 'still not enough colors available;',
531 print 'parts will be left white'
532 print '(needed', needed
, 'but have only',
533 print available(nplanes
), 'colors available)'
548 # XXX Should find out true screen size using getgdesc()
549 ortho2(-1.1*1.280, 1.1*1.280, -1.1*1.024, 1.1*1.024)
551 ortho2(-1.1, 1.1, -1.1, 1.1)
555 def available(nplanes
):
556 return pow(2, nplanes
) - 1 # Reserve one pixel for black
561 nplanes
= getplanes()
563 print 'multimap mode has', nplanes
, 'color planes'
565 Gl
.startindex
= pow(2, nplanes
) - 1
568 mapcolor(0, 0, 0, 0) # Fixed entry for black
569 if Gl
.bg
<> (0, 0, 0):
571 mapcolor(1, r
, g
, b
) # Fixed entry for Gl.bg
573 if Gl
.fg
<> (0, 0, 0):
575 mapcolor(2, r
, g
, b
) # Fixed entry for Gl.fg
581 Gl
.index
= Gl
.startindex
593 index
= definecolor(rgb
)
596 def definecolor(rgb
):
598 if index
< Gl
.stopindex
:
599 if Gl
.debug
: print 'definecolor hard case', rgb
600 # First see if we already have this one...
601 for index
in range(Gl
.stopindex
, Gl
.startindex
+1):
602 if rgb
== getmcolor(index
):
603 if Gl
.debug
: print 'return', index
605 # Don't clobber reserverd colormap entries
606 if not Gl
.overflow_seen
:
607 # Shouldn't happen any more, hence no Gl.warnings test
608 print 'mclock: out of colormap entries'
612 if Gl
.debug
> 1: print 'mapcolor', (index
, r
, g
, b
)
613 mapcolor(index
, r
, g
, b
)
620 for j
in range(i
): x
= x
*n
623 def mouseclick(mouse
, updown
, x
, y
):
625 # mouse button came down, start tracking
627 print 'mouse', mouse
, 'down at', x
, y
640 # mouse button came up, stop tracking
642 print 'mouse', mouse
, 'up at', x
, y
656 def mouse3track(x
, y
):
657 # first compute polar coordinates from x and y
658 cx
, cy
= Gl
.ox
+ Gl
.cx
/2, Gl
.oy
+ Gl
.cy
/2
659 x
, y
= x
- cx
, y
- cy
660 if (x
, y
) == (0, 0): return # would cause an exception
661 minutes
= int(30.5 + 30.0*math
.atan2(float(-x
), float(-y
))/pi
)
662 if minutes
== 60: minutes
= 0
663 a
,b
= Gl
.alarm_minutes
/15, minutes
/15
665 # Moved backward through 12 o'clock:
666 Gl
.alarm_hours
= Gl
.alarm_hours
- 1
667 if Gl
.alarm_hours
< 0: Gl
.alarm_hours
= Gl
.alarm_hours
+ 24
669 # Moved forward through 12 o'clock:
670 Gl
.alarm_hours
= Gl
.alarm_hours
+ 1
671 if Gl
.alarm_hours
>= 24: Gl
.alarm_hours
= Gl
.alarm_hours
- 24
672 Gl
.alarm_minutes
= minutes
673 seconds
= Gl
.alarm_hours
* HOUR
+ Gl
.alarm_minutes
* MINUTE
674 if seconds
<> Gl
.alarm_time
:
676 Gl
.alarm_time
= seconds
679 def mouse2track(x
, y
):
680 # first compute polar coordinates from x and y
681 cx
, cy
= Gl
.ox
+ Gl
.cx
/2, Gl
.oy
+ Gl
.cy
/2
682 x
, y
= x
- cx
, y
- cy
683 if (x
, y
) == (0, 0): return # would cause an exception
684 hours
= int(6.5 - float(Gl
.alarm_minutes
)/60.0 + 6.0*math
.atan2(float(-x
), float(-y
))/pi
)
685 if hours
== 12: hours
= 0
686 if (Gl
.alarm_hours
,hours
) == (0,11):
687 # Moved backward through midnight:
689 elif (Gl
.alarm_hours
,hours
) == (12,11):
690 # Moved backward through noon:
692 elif (Gl
.alarm_hours
,hours
) == (11,0):
693 # Moved forward through noon:
695 elif (Gl
.alarm_hours
,hours
) == (23,0):
696 # Moved forward through midnight:
698 elif Gl
.alarm_hours
< 12:
699 Gl
.alarm_hours
= hours
701 Gl
.alarm_hours
= hours
+ 12
702 seconds
= Gl
.alarm_hours
* HOUR
+ Gl
.alarm_minutes
* MINUTE
703 if seconds
<> Gl
.alarm_time
:
705 Gl
.alarm_time
= seconds
709 Gl
.pup
= pup
= newpup()
710 addtopup(pup
, 'M Clock%t|Alarm On/Off|Seconds Hand On/Off|Quit', 0)
724 # Toggle Seconds Hand
733 if Gl
.debug
: print 'Exit'