aboutsummaryrefslogtreecommitdiff
path: root/tui.py
blob: 1f41dc9e7339fbbe3beca02de3196a9268b91d35 (plain) (blame)
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())