File: countdown.pyw
   1 #!/usr/bin/python3
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2024 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the “Software”), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # countdown.pyw [minutes...]
  27 #
  28 # Show a small window with a live second-by-second countdown you can
  29 # change/restart any time using the slider control. When not given a
  30 # number, the default first duration to count down from is 20 minutes.
  31 #
  32 # Keyboard shortcuts:
  33 #
  34 #   esc         ->  quit
  35 #   right arrow ->  restart countdown using 1 more minute
  36 #   left arrow  ->  restart countdown using 1 fewer minute
  37 
  38 
  39 from sys import argv
  40 from typing import Any
  41 from datetime import datetime
  42 from tkinter import Tk, Event, Label, Scale, TOP, BOTTOM, HORIZONTAL
  43 
  44 
  45 minutes = 20  # how many minutes to count down from
  46 timer = ''  # timer id to allow cancelling
  47 start = datetime.now()  # to determine the time left every second
  48 
  49 # handle optional cmd-line arg to change default countdown minutes
  50 if len(argv) > 1:
  51     try:
  52         n = int(argv[1])
  53         if n > 0:
  54             minutes = n
  55     except:
  56         pass
  57 
  58 
  59 def check_key(event: Any) -> None:
  60     char: str = event.char
  61     # quit when esc key is pressed
  62     if char != '' and ord(char) == 27:
  63         win.quit()
  64 
  65 
  66 def restart():
  67     global timer, minutes, start
  68     # don't force main window on top while a countdown is happening
  69     win.attributes('-topmost', False)
  70 
  71     m = 0
  72     try:
  73         m = float(input.get())
  74     except:
  75         m = 20
  76     minutes = m
  77 
  78     if timer != '':
  79         win.after_cancel(timer)
  80     start = datetime.now()
  81     tick()
  82 
  83 
  84 def tick() -> None:
  85     global timer
  86     diff = datetime.now() - start
  87     sl = 60 * minutes - diff.total_seconds()  # seconds left
  88 
  89     if sl < 0:
  90         # ensure main window is showing when countdown is over
  91         win.attributes('-topmost', True)
  92         output.configure(text='done')
  93         # returning prevents any further scheduled callback
  94         return
  95 
  96     output.configure(text=f'{int(sl/60):02d}:{int(sl%60):02d}')
  97     timer = win.after(1000, tick)
  98 
  99 
 100 win = Tk()
 101 win.title('Countdown')
 102 win.resizable(False, False)
 103 win.bind('<KeyPress>', check_key)
 104 
 105 input = Scale(win, from_=1, to=90, orient=HORIZONTAL)
 106 input.configure(command=lambda _: restart())
 107 input.pack(side=TOP, padx=5)
 108 input.set(minutes)
 109 input.focus()
 110 
 111 font = ('Lucida Console', 72)
 112 output = Label(text='', font=font)
 113 output.pack(side=BOTTOM, padx=10, pady=5)
 114 tick()
 115 
 116 win.mainloop()