326 lines
9.4 KiB
Python
Executable File
326 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import curses
|
|
import datetime
|
|
import math
|
|
import os
|
|
import signal
|
|
import sys
|
|
import time
|
|
from optparse import OptionParser
|
|
|
|
ascii_digits = {
|
|
"0": "0000 00 00 0000",
|
|
"1": " 1" * 5,
|
|
"2": "222 22222 222",
|
|
"3": "333 3 33 3333",
|
|
"4": "4 4 4444 4 4",
|
|
"5": "5555 555 5555",
|
|
"6": "66 6 6666 6666",
|
|
"7": "777 7 7 7 7",
|
|
"8": "8888 88888 8888",
|
|
"9": "9999 9999 9 99",
|
|
":": " . . ",
|
|
}
|
|
|
|
# fmt: off
|
|
fancy_digits = {
|
|
"0": ("▗▄▖"
|
|
"█ █"
|
|
"█ █"
|
|
"█ █"
|
|
"▝▀▘"),
|
|
"1": ("▗▄ "
|
|
" █ "
|
|
" █ "
|
|
" █ "
|
|
"▝▀▘"),
|
|
"2": ("▗▄▖"
|
|
"▘ █"
|
|
" ▟▘"
|
|
"▟▘ "
|
|
"▀▀▀"),
|
|
|
|
"3": ("▗▄▖"
|
|
"▘ █"
|
|
" ▜▌"
|
|
"▖ █"
|
|
"▝▀▘"),
|
|
"4": (" ▗▖"
|
|
"▗▜▌"
|
|
"▌▐▌"
|
|
"▀▜▛"
|
|
" ▀▀"),
|
|
"5": ("▄▄▄"
|
|
"█ "
|
|
"▀▀▙"
|
|
"▖ █"
|
|
"▝▀▘"),
|
|
"6": ("▗▄▖"
|
|
"█ ▝"
|
|
"█▀▙"
|
|
"█ █"
|
|
"▝▀▘"),
|
|
"7": ("▄▄▄"
|
|
" █"
|
|
" ▐▌"
|
|
" █ "
|
|
"▝▘ "),
|
|
#▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟ █ ▀ ▄ ▌ ▐
|
|
"8": ("▗▄▖"
|
|
"█ █"
|
|
"▟▀▙"
|
|
"█ █"
|
|
"▝▀▘"),
|
|
"9": ("▗▄▖"
|
|
"█ █"
|
|
"▜▄█"
|
|
"▖ █"
|
|
"▝▀▘"),
|
|
":": " ▗ ▝ ",
|
|
}
|
|
# fmt: on
|
|
# ~ ░ ▒ ▓
|
|
|
|
|
|
def termsize():
|
|
rows, columns = os.popen("stty size", "r").read().split()
|
|
return (int(rows), int(columns))
|
|
|
|
|
|
def centerpoint(ry, rx):
|
|
r = int(round(min(ry / 1.5, rx / 1.5)) - 1)
|
|
return (int(round(ry / 2)), int(round(rx / 2)), r)
|
|
|
|
|
|
def saddstr(win, y, x, s, a=None):
|
|
if not a:
|
|
a = curses.color_pair(0)
|
|
try:
|
|
win.addstr(y, x, s, a)
|
|
except:
|
|
pass
|
|
|
|
|
|
def drawcircle(win, cy, cx, r, s="·", attr=None, precision=360):
|
|
# TODO: get seconds?, color by angle, darkening to past. current seconds bright
|
|
for a in range(precision):
|
|
alpha = 2.0 * math.pi * a / precision
|
|
dy = int(round(cy - float(r) * math.cos(alpha)))
|
|
dx = int(round(cx + 2.0 * float(r) * math.sin(alpha)))
|
|
saddstr(win, dy, dx, s, attr)
|
|
return
|
|
|
|
|
|
def drawline(win, cy, cx, a, s, r, char, attr=None):
|
|
prec = 2
|
|
points = {}
|
|
for l in range(int(r * prec)):
|
|
if l > s:
|
|
ly = cy - float(l) * math.cos(a) / prec
|
|
lx = cx + 2.0 * float(l) * math.sin(a) / prec
|
|
iy, ix = int(round(ly)), int(round(lx))
|
|
saddstr(win, iy, ix, char, attr)
|
|
|
|
return
|
|
|
|
|
|
def drawdigit(win, y, x, d, ascii=False):
|
|
s = " "
|
|
if ascii:
|
|
s = ascii_digits.get(d, s)
|
|
else:
|
|
s = fancy_digits.get(d, s)
|
|
drawsplitstr(win, y, x, s)
|
|
return
|
|
|
|
|
|
def drawsplitstr(win, y, x, st):
|
|
ls = list(st)
|
|
for r in range(5):
|
|
rs = 3 * r
|
|
saddstr(win, y + r, x, ls[rs] + ls[rs + 1] + ls[rs + 2], curses.A_BOLD)
|
|
return
|
|
|
|
|
|
def drawdigital(win, y, x, t, ascii=False, tick_tock=False):
|
|
c = ":" if tick_tock else " "
|
|
sec = "{:}{:02d}".format(c, t.tm_sec) if options.seconds else ""
|
|
hrs = list("{:02d}{:}{:02d}{:}".format(t.tm_hour, c, t.tm_min, sec))
|
|
for c in range(len(hrs)):
|
|
drawdigit(win, y, x + 4 * c, hrs[c], ascii=ascii)
|
|
|
|
|
|
def drawalarms(stdscr, y, x, t, alarms, tick_tock):
|
|
ya = y
|
|
now = 100 * t.tm_hour + t.tm_min
|
|
is_alarm = False
|
|
for alarm in alarms:
|
|
if alarm["hstart"] <= now and alarm["hend"] >= now:
|
|
color = (
|
|
curses.color_pair(4) + curses.A_BOLD + curses.A_REVERSE
|
|
if tick_tock
|
|
else curses.color_pair(2) + curses.A_BOLD
|
|
)
|
|
is_alarm = True
|
|
else:
|
|
color = curses.color_pair(7)
|
|
st = "{:02d}:{:02d}".format(*alarm["start"])
|
|
ed = "{:02d}:{:02d}".format(*alarm["end"])
|
|
s = f"{st}-{ed} {alarm['name']}"
|
|
stdscr.addstr(ya, x, s, color)
|
|
ya += 1
|
|
return is_alarm
|
|
|
|
|
|
def parse_alarms(alarms):
|
|
|
|
parsed = []
|
|
for alarm in alarms:
|
|
t, n = alarm.split("/", 1)
|
|
t = t.split("-")
|
|
tstart = [int(x) for x in t[0].split(":")]
|
|
if len(t) > 1:
|
|
tend = [int(x) for x in t[1].split(":")]
|
|
else:
|
|
tend = tstart
|
|
parsed.append(
|
|
{
|
|
"start": tstart,
|
|
"end": tend,
|
|
"name": n,
|
|
"hstart": 100 * tstart[0] + tstart[1],
|
|
"hend": 100 * tend[0] + tend[1],
|
|
}
|
|
)
|
|
parsed.sort(key=lambda x: x["start"][1])
|
|
parsed.sort(key=lambda x: x["start"][0])
|
|
return parsed
|
|
|
|
|
|
def readinput(win, timeout):
|
|
started = time.time()
|
|
try:
|
|
while time.time() - timeout < started:
|
|
input = win.getch()
|
|
if input in [ord(x) for x in ["x", "X", "q", "Q"]]:
|
|
return "x"
|
|
time.sleep(0.01)
|
|
except:
|
|
return ""
|
|
return ""
|
|
|
|
|
|
class timer_struct:
|
|
"""Class for storing timer."""
|
|
|
|
def __init__(self, h, m, s):
|
|
self.tm_hour = int(h)
|
|
self.tm_min = int(m)
|
|
self.tm_sec = int(s)
|
|
|
|
|
|
def main():
|
|
stdscr = curses.initscr()
|
|
alarms = parse_alarms(options.alarm)
|
|
curses.curs_set(0)
|
|
curses.start_color()
|
|
curses.use_default_colors()
|
|
stdscr.nodelay(2)
|
|
for i in range(0, curses.COLORS):
|
|
curses.init_pair(i + 1, i, -1)
|
|
start_t = time.time()
|
|
is_alarm = False
|
|
t_old = time.localtime(time.time())
|
|
tick_tock = False
|
|
exact_seconds = options.refresh % 1 == 0
|
|
try:
|
|
# rows,columns = termsize()
|
|
curses.cbreak()
|
|
while 1:
|
|
rows, columns = stdscr.getmaxyx()
|
|
cy, cx, r = centerpoint(rows, columns)
|
|
now = time.time()
|
|
t = time.localtime(now)
|
|
if options.timer:
|
|
t_new = now - start_t
|
|
t_m, t_s = divmod(t_new, 60)
|
|
t_h, t_m = divmod(t_m, 60)
|
|
f = 0
|
|
else:
|
|
f = 0 if exact_seconds else now % 1
|
|
t_s = float(t.tm_sec)
|
|
t_m = float(t.tm_min)
|
|
t_h = float(t.tm_hour)
|
|
alphas = math.pi * (f + t_s) / 30.0
|
|
alpham = math.pi * t_m / 30.0 + alphas / 60.0
|
|
alphah = math.pi * t_h / 6.0 + alpham / 12.0
|
|
if t_old.tm_min != t.tm_min:
|
|
# clear once a minute
|
|
stdscr.clear()
|
|
|
|
tick_tock = not tick_tock if options.refresh > 1 else t.tm_sec % 2
|
|
|
|
#for s in range(60):
|
|
# drawline(stdscr, cy, cx, math.pi * s / 30.0, r-2, r / 2, "∙",
|
|
# attr=curses.color_pair(2) + curses.A_BOLD + curses.A_BLINK if is_alarm else curses.color_pair(8))
|
|
drawcircle(
|
|
stdscr,
|
|
cy,
|
|
cx,
|
|
r / 2,
|
|
attr=curses.color_pair(2) + curses.A_BOLD + curses.A_BLINK if is_alarm else curses.color_pair(8),
|
|
precision=60
|
|
)
|
|
if options.seconds:
|
|
drawline(stdscr, cy, cx, alphas, 1, r / 2, "■", curses.color_pair(2))
|
|
drawline(stdscr, cy, cx, alpham, 1, int(round(r * 0.9) / 2), "█", curses.color_pair(3))
|
|
drawline(stdscr, cy, cx, alphah, 1, int(round(r * 0.6) / 2), "██", curses.color_pair(7))
|
|
stdscr.addstr(cy, cx, "⊙")
|
|
|
|
for h in range(12):
|
|
drawline(stdscr, cy, cx, math.pi * h / 6.0, r, 1 + r / 2, "◆", curses.color_pair(4))
|
|
if options.timer:
|
|
drawdigital(stdscr, 1, 1, timer_struct(t_h, t_m, t_s), options.ascii, tick_tock)
|
|
drawdigital(stdscr, 7, 1, t, options.ascii, tick_tock)
|
|
else:
|
|
drawdigital(stdscr, 1, 1, t, options.ascii, tick_tock)
|
|
is_alarm = drawalarms(stdscr, 1, 35, t, alarms, tick_tock)
|
|
stdscr.refresh()
|
|
userinput = readinput(stdscr, options.refresh)
|
|
if userinput == "x":
|
|
curses.nocbreak()
|
|
stdscr.keypad(0)
|
|
curses.endwin()
|
|
sys.exit(0)
|
|
# wipe
|
|
if options.seconds:
|
|
drawline(stdscr, cy, cx, alphas, 1, r / 2, " ", curses.color_pair(2))
|
|
drawline(stdscr, cy, cx, alpham, 1, int(round(r * 0.9) / 2), " ", curses.color_pair(3))
|
|
drawline(stdscr, cy, cx, alphah, 1, int(round(r * 0.6) / 2), " ", curses.color_pair(7))
|
|
t_old = t
|
|
|
|
except KeyboardInterrupt:
|
|
curses.nocbreak()
|
|
stdscr.keypad(0)
|
|
# curses.echo()
|
|
|
|
curses.endwin()
|
|
|
|
|
|
usage = """Usage: %prog [options]
|
|
|
|
Display a clockface
|
|
"""
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("-s", action="store_true", dest="seconds", default=False, help="Show seconds [%default]")
|
|
parser.add_option("-r", type="float", dest="refresh", default=3, help="Refresh rate in seconds [%default]")
|
|
parser.add_option(
|
|
"-t", action="store_true", dest="timer", default=False, help="Timer instead of current time [%default]"
|
|
)
|
|
parser.add_option("-a", action="store_true", dest="ascii", default=False, help="Plain ascii characters only [%default]")
|
|
parser.add_option("--alarm", action="append", dest="alarm", default=[], help="alarms")
|
|
global options
|
|
(options, args) = parser.parse_args()
|
|
|
|
main()
|