6 from contextlib import contextmanager
8 __all__ = ['open', 'TextAttr', 'ClearType', 'CursorType', 'BTUIMode']
10 # Load the shared library into c types.
11 libbtui = ctypes.CDLL(os.path.dirname(os.path.abspath(__file__)) + os.path.sep + "libbtui.so")
13 class FILE(ctypes.Structure):
16 class BTUI_struct(ctypes.Structure):
18 ('in', ctypes.POINTER(FILE)),
19 ('out', ctypes.POINTER(FILE)),
20 ('width', ctypes.c_int),
21 ('height', ctypes.c_int),
22 ('size_changed', ctypes.c_int),
25 libbtui.btui_create.restype = ctypes.POINTER(BTUI_struct)
27 attr = lambda name: ctypes.c_longlong.in_dll(libbtui, name).value
28 attr_t = ctypes.c_longlong
30 class TextAttr(enum.IntEnum):
31 NORMAL = attr('BTUI_NORMAL')
32 BOLD = attr('BTUI_BOLD')
33 FAINT = attr('BTUI_FAINT')
34 DIM = attr('BTUI_FAINT')
35 ITALIC = attr('BTUI_ITALIC')
36 UNDERLINE = attr('BTUI_UNDERLINE')
37 BLINK_SLOW = attr('BTUI_BLINK_SLOW')
38 BLINK_FAST = attr('BTUI_BLINK_FAST')
39 REVERSE = attr('BTUI_REVERSE')
40 CONCEAL = attr('BTUI_CONCEAL')
41 STRIKETHROUGH = attr('BTUI_STRIKETHROUGH')
42 FRAKTUR = attr('BTUI_FRAKTUR')
43 DOUBLE_UNDERLINE = attr('BTUI_DOUBLE_UNDERLINE')
44 NO_BOLD_OR_FAINT = attr('BTUI_NO_BOLD_OR_FAINT')
45 NO_ITALIC_OR_FRAKTUR = attr('BTUI_NO_ITALIC_OR_FRAKTUR')
46 NO_UNDERLINE = attr('BTUI_NO_UNDERLINE')
47 NO_BLINK = attr('BTUI_NO_BLINK')
48 NO_REVERSE = attr('BTUI_NO_REVERSE')
49 NO_CONCEAL = attr('BTUI_NO_CONCEAL')
50 NO_STRIKETHROUGH = attr('BTUI_NO_STRIKETHROUGH')
51 FG_BLACK = attr('BTUI_FG_BLACK')
52 FG_RED = attr('BTUI_FG_RED')
53 FG_GREEN = attr('BTUI_FG_GREEN')
54 FG_YELLOW = attr('BTUI_FG_YELLOW')
55 FG_BLUE = attr('BTUI_FG_BLUE')
56 FG_MAGENTA = attr('BTUI_FG_MAGENTA')
57 FG_CYAN = attr('BTUI_FG_CYAN')
58 FG_WHITE = attr('BTUI_FG_WHITE')
59 FG_NORMAL = attr('BTUI_FG_NORMAL')
60 BG_BLACK = attr('BTUI_BG_BLACK')
61 BG_RED = attr('BTUI_BG_RED')
62 BG_GREEN = attr('BTUI_BG_GREEN')
63 BG_YELLOW = attr('BTUI_BG_YELLOW')
64 BG_BLUE = attr('BTUI_BG_BLUE')
65 BG_MAGENTA = attr('BTUI_BG_MAGENTA')
66 BG_CYAN = attr('BTUI_BG_CYAN')
67 BG_WHITE = attr('BTUI_BG_WHITE')
68 BG_NORMAL = attr('BTUI_BG_NORMAL')
69 FRAMED = attr('BTUI_FRAMED')
70 ENCIRCLED = attr('BTUI_ENCIRCLED')
71 OVERLINED = attr('BTUI_OVERLINED')
72 NO_FRAMED_OR_ENCIRCLED = attr('BTUI_NO_FRAMED_OR_ENCIRCLED')
73 NO_OVERLINED = attr('BTUI_NO_OVERLINED')
75 BTUI_INVERSE_ATTRS = {
76 TextAttr.NORMAL : TextAttr.NORMAL,
77 TextAttr.BOLD : TextAttr.NO_BOLD_OR_FAINT,
78 TextAttr.FAINT : TextAttr.NO_BOLD_OR_FAINT,
79 TextAttr.DIM : TextAttr.NO_BOLD_OR_FAINT,
80 TextAttr.ITALIC : TextAttr.NO_ITALIC_OR_FRAKTUR,
81 TextAttr.UNDERLINE : TextAttr.NO_UNDERLINE,
82 TextAttr.BLINK_SLOW : TextAttr.NO_BLINK,
83 TextAttr.BLINK_FAST : TextAttr.NO_BLINK,
84 TextAttr.REVERSE : TextAttr.NO_REVERSE,
85 TextAttr.CONCEAL : TextAttr.NO_CONCEAL,
86 TextAttr.STRIKETHROUGH : TextAttr.NO_STRIKETHROUGH,
87 TextAttr.FRAKTUR : TextAttr.NO_ITALIC_OR_FRAKTUR,
88 TextAttr.DOUBLE_UNDERLINE: TextAttr.NO_UNDERLINE,
89 TextAttr.FG_BLACK : TextAttr.FG_NORMAL,
90 TextAttr.FG_RED : TextAttr.FG_NORMAL,
91 TextAttr.FG_GREEN : TextAttr.FG_NORMAL,
92 TextAttr.FG_YELLOW : TextAttr.FG_NORMAL,
93 TextAttr.FG_BLUE : TextAttr.FG_NORMAL,
94 TextAttr.FG_MAGENTA : TextAttr.FG_NORMAL,
95 TextAttr.FG_CYAN : TextAttr.FG_NORMAL,
96 TextAttr.FG_WHITE : TextAttr.FG_NORMAL,
97 TextAttr.FG_NORMAL : TextAttr.FG_NORMAL,
98 TextAttr.BG_BLACK : TextAttr.BG_NORMAL,
99 TextAttr.BG_RED : TextAttr.BG_NORMAL,
100 TextAttr.BG_GREEN : TextAttr.BG_NORMAL,
101 TextAttr.BG_YELLOW : TextAttr.BG_NORMAL,
102 TextAttr.BG_BLUE : TextAttr.BG_NORMAL,
103 TextAttr.BG_MAGENTA : TextAttr.BG_NORMAL,
104 TextAttr.BG_CYAN : TextAttr.BG_NORMAL,
105 TextAttr.BG_WHITE : TextAttr.BG_NORMAL,
106 TextAttr.BG_NORMAL : TextAttr.BG_NORMAL,
107 TextAttr.FRAMED : TextAttr.NO_FRAMED_OR_ENCIRCLED,
108 TextAttr.ENCIRCLED : TextAttr.NO_FRAMED_OR_ENCIRCLED,
109 TextAttr.OVERLINED : TextAttr.NO_OVERLINED,
112 class BTUIMode(enum.IntEnum):
117 class CursorType(enum.IntEnum):
121 BLINKING_UNDERLINE = 3
126 class ClearType(enum.IntEnum):
138 def attributes(self, *attrs):
139 self.set_attributes(*attrs)
141 finally: self.unset_attributes(*attrs)
144 def bg(self, r, g, b):
147 finally: self.set_attributes("bg_normal")
149 def clear(self, clear_type=ClearType.SCREEN):
151 if isinstance(clear_type, str):
152 clear_type = ClearType[clear_type.upper()]
153 libbtui.btui_clear(self._btui, clear_type)
155 libbtui.btui_flush(self._btui)
158 libbtui.btui_disable(self._btui)
164 finally: self.enable()
166 def draw_shadow(self, x, y, w, h):
168 libbtui.btui_draw_shadow(self._btui, int(x), int(y), int(w), int(h))
169 libbtui.btui_flush(self._btui)
171 def enable(self, mode=BTUIMode.TUI):
172 if isinstance(mode, str):
173 mode = BTUIMode[mode.upper()]
174 self._btui = libbtui.btui_create(mode)
176 def set_mode(self, mode):
177 if isinstance(mode, str):
178 mode = BTUIMode[mode.upper()]
179 self._btui = libbtui.set_mode(self._btui, mode)
182 def fg(self, r, g, b):
185 finally: self.set_attributes("fg_normal")
187 def fill_box(self, x, y, w, h):
189 libbtui.btui_fill_box(self._btui, int(x), int(y), int(w), int(h))
191 libbtui.btui_flush(self._btui)
195 libbtui.btui_flush(self._btui)
200 prev_autoflush = self._autoflush
201 self._autoflush = False
203 self._autoflush = prev_autoflush
206 def getkey(self, timeout=None):
208 timeout = -1 if timeout is None else int(timeout)
209 mouse_x, mouse_y = ctypes.c_int(-1), ctypes.c_int(-1)
210 key = libbtui.btui_getkey(self._btui, timeout,
211 ctypes.byref(mouse_x), ctypes.byref(mouse_y))
212 buf = ctypes.create_string_buffer(64)
213 libbtui.btui_keyname(key, buf)
214 key = buf.value.decode('utf8')
215 if key == "<none>": key = None
216 if mouse_x.value == -1:
217 return key, None, None
219 return key, mouse_x.value, mouse_y.value
224 return self._btui.contents.height
226 def move(self, x, y):
228 libbtui.btui_move_cursor(self._btui, int(x), int(y))
230 libbtui.btui_flush(self._btui)
232 def set_cursor(self, cursor_type=CursorType.DEFAULT):
234 if isinstance(cursor_type, str):
235 cursor_type = CursorType[cursor_type.upper()]
236 libbtui.btui_set_cursor(self._btui, cursor_type)
238 libbtui.btui_flush(self._btui)
240 def hide_cursor(self):
242 libbtui.btui_hide_cursor(self._btui)
244 libbtui.btui_flush(self._btui)
246 def show_cursor(self):
248 libbtui.btui_show_cursor(self._btui)
250 libbtui.btui_flush(self._btui)
252 def outline_box(self, x, y, w, h):
254 libbtui.btui_draw_linebox(self._btui, int(x), int(y), int(w), int(h))
256 libbtui.btui_flush(self._btui)
258 def scroll(self, firstline, lastline=None, amount=None):
262 firstline, lastline = 0, self.height-1
263 libbtui.btui_scroll(self._btui, firstline, lastline, amount)
265 libbtui.btui_flush(self._btui)
267 def set_attributes(self, *attrs):
269 attr_long = ctypes.c_longlong(0)
271 if isinstance(a, str):
272 a = TextAttr[a.upper()]
274 libbtui.btui_set_attributes(self._btui, attr_long)
276 def set_bg(self, r, g, b):
278 libbtui.btui_set_bg(self._btui, int(r*255), int(g*255), int(b*255))
280 def set_fg(self, r, g, b):
282 libbtui.btui_set_fg(self._btui, int(r*255), int(g*255), int(b*255))
286 libbtui.btui_suspend(self._btui)
288 def unset_attributes(self, *attrs):
290 attr_long = ctypes.c_longlong(0)
292 if isinstance(a, str):
293 a = TextAttr[a.upper()]
294 attr_long.value |= BTUI_INVERSE_ATTRS[a]
295 libbtui.btui_set_attributes(self._btui, attr_long)
300 return self._btui.contents.width
302 def write(self, *args, sep=''):
305 self.write_bytes(bytes(s, 'utf8'))
307 def write_bytes(self, b):
309 libbtui.btui_puts(self._btui, b)
311 libbtui.btui_flush(self._btui)
315 def wrapped(self, *a, **k):
317 ret = fn(self, *a, **k)
318 time.sleep(self.delay)
319 libbtui.btui_show_cursor(self._btui)
323 class DebugBTUI(BTUI):
326 for fn_name in ('clear', 'draw_shadow', 'fill_box', 'move', 'set_cursor', 'hide_cursor', 'show_cursor', 'outline_box',
327 'scroll', 'set_attributes', 'set_bg', 'set_fg', 'unset_attributes', 'write_bytes'):
328 setattr(DebugBTUI, fn_name, delay(getattr(BTUI, fn_name)))
332 def open(*, debug=False, delay=0.05, mode=BTUIMode.TUI):
340 _btui.enable(mode=mode)
343 finally: _btui.disable()