1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
#!/usr/bin/env python3
import curses
import sys
import time
import otp
BAR_WIDTH = 20
def put(stdscr, row, text):
maxy, maxx = stdscr.getmaxyx()
if row >= maxy:
return
try:
stdscr.addstr(row, 0, text[: maxx - 1])
except curses.error: # the terminal can shrink between getmaxyx and addstr
pass
def draw(stdscr, entries, message=None):
stdscr.erase()
now = int(time.time())
width = max((len(name) for name, *_ in entries), default=0)
cwidth = max((digits for _, _, _, digits, _ in entries), default=0)
for row, (name, key, period, digits, digest) in enumerate(entries):
code = otp.hotp(key, now // period, digits, digest)
remaining = period - now % period
filled = remaining * BAR_WIDTH // period
bar = "#" * filled + "-" * (BAR_WIDTH - filled)
put(stdscr, row, f"{name:<{width}} {code:>{cwidth}} {remaining:2d}s [{bar}]")
put(stdscr, len(entries) + 1, message or "q quit r reload")
stdscr.refresh()
def run(stdscr, entries):
curses.curs_set(0)
stdscr.timeout(250)
message = None
while True:
draw(stdscr, entries, message)
ch = stdscr.getch()
if ch in (ord("q"), 27):
break
if ch == ord("r"):
curses.endwin() # OTP_SECRETS_CMD may prompt on the terminal
try:
new_entries = otp.load_entries()
except otp.SecretsError as error:
message = f"reload failed: {error}"
else:
entries = new_entries
message = None
def main():
try:
entries = otp.load_entries()
curses.wrapper(run, entries)
except otp.SecretsError as error:
print(f"tui: {error}", file=sys.stderr)
return error.status
except KeyboardInterrupt:
return 0
if __name__ == "__main__":
sys.exit(main())
|