Skip to content

Commit 4abe325

Browse files
committed
Improve provisoning qr display
1 parent 342dc34 commit 4abe325

File tree

2 files changed

+112
-5
lines changed

2 files changed

+112
-5
lines changed

src/ttotp/__main__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from dataclasses import dataclass, field
88

9-
import io
109
import signal
1110
import time
1211
import hashlib
@@ -35,6 +34,7 @@
3534
import qrcode
3635

3736
from .TinyProgress import TinyProgress as ProgressBar
37+
from .sixel import matrix_to_sixel
3838

3939
from typing import TypeGuard # use `typing_extensions` for Python 3.9 and below
4040

@@ -118,16 +118,18 @@ def do_clear_copy(self) -> bool:
118118
class Qr(ModalScreen[None]):
119119
"""Displays a QR code in a modal dialog"""
120120

121+
BINDINGS = [
122+
Binding("escape", "dismiss", "Close", show=True),
123+
]
124+
121125
def __init__(self, url: str) -> None:
122126
self.url = url
123127
super().__init__(classes="Qr")
124128

125129
def compose(self) -> ComposeResult:
126130
q = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L)
127131
q.add_data(self.url)
128-
out = io.StringIO()
129-
q.print_ascii(out=out)
130-
content = out.getvalue()
132+
content = matrix_to_sixel(q.get_matrix())
131133
yield Vertical(Label(content), Button("OK"), id="dialog")
132134

133135
def on_button_pressed(self, event: Button.Pressed) -> None:
@@ -375,7 +377,7 @@ class TTOTP(App[None]):
375377
border: thick $background 80%;
376378
background: $surface;
377379
}
378-
#dialog Button { align: center middle; }
380+
#dialog Button { width: 100%; }
379381
"""
380382

381383
BINDINGS = [

src/ttotp/sixel.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-FileCopyrightText: 2023 Jeff Epler
2+
#
3+
# SPDX-License-Identifier: MIT
4+
from __future__ import annotations
5+
6+
TYPE_CHECKING = False
7+
if TYPE_CHECKING:
8+
from typing import Generator, Sequence
9+
10+
MatrixType = Sequence[Sequence[bool]]
11+
SixelMapType = dict[tuple[int, int, int, int, int, int], str]
12+
13+
_sixels = {
14+
(0, 0, 0, 0, 0, 0): " ", # Exception
15+
(1, 0, 0, 0, 0, 0): "\U0001fb00",
16+
(0, 1, 0, 0, 0, 0): "\U0001fb01",
17+
(1, 1, 0, 0, 0, 0): "\U0001fb02",
18+
(0, 0, 1, 0, 0, 0): "\U0001fb03",
19+
(1, 0, 1, 0, 0, 0): "\U0001fb04",
20+
(0, 1, 1, 0, 0, 0): "\U0001fb05",
21+
(1, 1, 1, 0, 0, 0): "\U0001fb06",
22+
(0, 0, 0, 1, 0, 0): "\U0001fb07",
23+
(1, 0, 0, 1, 0, 0): "\U0001fb08",
24+
(0, 1, 0, 1, 0, 0): "\U0001fb09",
25+
(1, 1, 0, 1, 0, 0): "\U0001fb0a",
26+
(0, 0, 1, 1, 0, 0): "\U0001fb0b",
27+
(1, 0, 1, 1, 0, 0): "\U0001fb0c",
28+
(0, 1, 1, 1, 0, 0): "\U0001fb0d",
29+
(1, 1, 1, 1, 0, 0): "\U0001fb0e",
30+
(0, 0, 0, 0, 1, 0): "\U0001fb0f",
31+
(1, 0, 0, 0, 1, 0): "\U0001fb10",
32+
(0, 1, 0, 0, 1, 0): "\U0001fb11",
33+
(1, 1, 0, 0, 1, 0): "\U0001fb12",
34+
(0, 0, 1, 0, 1, 0): "\U0001fb13",
35+
(1, 0, 1, 0, 1, 0): "\u258c", # Exception
36+
(0, 1, 1, 0, 1, 0): "\U0001fb14",
37+
(1, 1, 1, 0, 1, 0): "\U0001fb15",
38+
(0, 0, 0, 1, 1, 0): "\U0001fb16",
39+
(1, 0, 0, 1, 1, 0): "\U0001fb17",
40+
(0, 1, 0, 1, 1, 0): "\U0001fb18",
41+
(1, 1, 0, 1, 1, 0): "\U0001fb19",
42+
(0, 0, 1, 1, 1, 0): "\U0001fb1a",
43+
(1, 0, 1, 1, 1, 0): "\U0001fb1b",
44+
(0, 1, 1, 1, 1, 0): "\U0001fb1c",
45+
(1, 1, 1, 1, 1, 0): "\U0001fb1d",
46+
(0, 0, 0, 0, 0, 1): "\U0001fb1e",
47+
(1, 0, 0, 0, 0, 1): "\U0001fb1f",
48+
(0, 1, 0, 0, 0, 1): "\U0001fb20",
49+
(1, 1, 0, 0, 0, 1): "\U0001fb21",
50+
(0, 0, 1, 0, 0, 1): "\U0001fb22",
51+
(1, 0, 1, 0, 0, 1): "\U0001fb23",
52+
(0, 1, 1, 0, 0, 1): "\U0001fb24",
53+
(1, 1, 1, 0, 0, 1): "\U0001fb25",
54+
(0, 0, 0, 1, 0, 1): "\U0001fb26",
55+
(1, 0, 0, 1, 0, 1): "\U0001fb27",
56+
(0, 1, 0, 1, 0, 1): "\u2590", # Exception
57+
(1, 1, 0, 1, 0, 1): "\U0001fb28",
58+
(0, 0, 1, 1, 0, 1): "\U0001fb29",
59+
(1, 0, 1, 1, 0, 1): "\U0001fb2a",
60+
(0, 1, 1, 1, 0, 1): "\U0001fb2b",
61+
(1, 1, 1, 1, 0, 1): "\U0001fb2c",
62+
(0, 0, 0, 0, 1, 1): "\U0001fb2d",
63+
(1, 0, 0, 0, 1, 1): "\U0001fb2e",
64+
(0, 1, 0, 0, 1, 1): "\U0001fb2f",
65+
(1, 1, 0, 0, 1, 1): "\U0001fb30",
66+
(0, 0, 1, 0, 1, 1): "\U0001fb31",
67+
(1, 0, 1, 0, 1, 1): "\U0001fb32",
68+
(0, 1, 1, 0, 1, 1): "\U0001fb33",
69+
(1, 1, 1, 0, 1, 1): "\U0001fb34",
70+
(0, 0, 0, 1, 1, 1): "\U0001fb35",
71+
(1, 0, 0, 1, 1, 1): "\U0001fb36",
72+
(0, 1, 0, 1, 1, 1): "\U0001fb37",
73+
(1, 1, 0, 1, 1, 1): "\U0001fb38",
74+
(0, 0, 1, 1, 1, 1): "\U0001fb39",
75+
(1, 0, 1, 1, 1, 1): "\U0001fb3a",
76+
(0, 1, 1, 1, 1, 1): "\U0001fb3b",
77+
(1, 1, 1, 1, 1, 1): "\u2588", # Exception
78+
}
79+
80+
81+
def _sixel_gen(m: MatrixType, sixels: SixelMapType = _sixels) -> Generator[str]:
82+
n_rows = len(m)
83+
n_cols = len(m[0])
84+
85+
def get(r: int, c: int) -> bool:
86+
if r >= n_rows or c >= n_cols:
87+
return False
88+
return m[r][c]
89+
90+
for r in range(0, n_rows, 3):
91+
for c in range(0, n_cols, 2):
92+
sixel = (
93+
get(r, c),
94+
get(r, c + 1),
95+
get(r + 1, c),
96+
get(r + 1, c + 1),
97+
get(r + 2, c),
98+
get(r + 2, c + 1),
99+
)
100+
yield sixels[sixel]
101+
yield "\n"
102+
103+
104+
def matrix_to_sixel(m: MatrixType) -> str:
105+
return "".join(_sixel_gen(m))

0 commit comments

Comments
 (0)