#!/usr/bin/env python3 import argparse import curses import hashlib import os import sys import termios import tty __version__ = "20250309.a" class getch: def get(self): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch def get_opts(): parser = argparse.ArgumentParser( description="Colorful password dialog", epilog="Dialog printed to stderr, user input echoed to stdout, i.e. input=$( qaskpass )", ) parser.add_argument("--title", "-t", action="store", default=None, help="Title for dialog") parser.add_argument("-w", action="store", type=int, default=5, help="Width of display area. 0 to disable display.") parser.add_argument("--no-color", action="store_true", default=False, help="Disable colors") parser.add_argument( "--expect-sha256", action="store", default=None, help="Show green dots when string matches the sha256 sum. Note: strip newlines before calculating!. Exitcode = 10 if checksum does not match.", ) parser.add_argument("--version", action="version", version="%(prog)s {version}".format(version=__version__)) args = parser.parse_args() return args def termsize(): rows, columns = os.popen("stty size", "r").read().split() return (int(rows), int(columns)) class bc: m = "\033[95m" b = "\033[94m" g = "\033[92m" y = "\033[93m" r = "\033[91m" c = "\033[96m" B = "\033[1m" z = "\033[0m" def disable(self): for x in "rgbcmyBz": setattr(self, x, "") def animchar(i, pos, colorpos, width, c): if i < 0: return " " if pos == colorpos: clr = c.c else: if i < 10: clr = c.r elif i < 20: clr = c.y else: clr = c.g return clr + "▁▂▃▄▅▆▇█▇▆▅▄▃▂"[int(i / width) % 14] def pquit(s="", e=0): print("", file=sys.stderr) print(s, end="", file=sys.stdout if e == 0 else sys.stderr) sys.exit(e) if __name__ == "__main__": opts = get_opts() # 3= ctrl-c, 13=enter # 127 = backspace ch = getch() user_input = "" display = "**** ****" c = bc() if opts.no_color: c.disable() if opts.title: print(f"{c.y}{c.B}{opts.title}{c.z}", file=sys.stderr) enter_exitcode = 0 while True: try: if opts.w > 0: dot_color = c.m if opts.expect_sha256: enter_exitcode = 10 if hashlib.sha256(user_input.encode("utf-8")).hexdigest() == opts.expect_sha256: dot_color = c.g + c.B enter_exitcode = 0 colorpos = len(user_input) % opts.w display = dot_color + "•••• " for i in range(opts.w): display += animchar(len(user_input) - i, i, colorpos, opts.w, c) display += dot_color + " ••••" + c.z print("\r" + display, file=sys.stderr, end="") sys.stderr.flush() key = ch.get() if ord(key) == 3: # ctrl-c pquit(e=1) if ord(key) == 13: # enter pquit(user_input, e=enter_exitcode) if ord(key) == 27: # esc (also starts control characters key = ch.get() if ord(key) == 27: pquit(e=1) continue if ord(key) == 127: # backspace user_input = user_input[0:-1] else: user_input += key # ~ print(f'-{key}-',file=sys.stderr) # ~ print(f'-{ord(key)}-',file=sys.stderr) except Exception as e: pquit(s=str(e), e=1)