1+ # SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2+ #
3+ # SPDX-License-Identifier: MIT
4+
5+ import select
6+ import sys
7+ # pylint: disable=no-self-use
8+
9+ try :
10+ import termios
11+
12+ _orig_attr = None # pylint: disable=invalid-name
13+
14+ def _nonblocking ():
15+ global _orig_attr # pylint: disable=global-statement
16+ _orig_attr = termios .tcgetattr (sys .stdin )
17+ attr = termios .tcgetattr (sys .stdin )
18+ attr [3 ] &= ~ (termios .ECHO | termios .ICANON )
19+ attr [6 ][termios .VMIN ] = 1
20+ attr [6 ][termios .VTIME ] = 0
21+ termios .tcsetattr (sys .stdin , termios .TCSADRAIN , attr )
22+
23+ def _blocking ():
24+ if _orig_attr is not None :
25+ termios .tcsetattr (sys .stdin , termios .TCSADRAIN , _orig_attr )
26+
27+ except ImportError :
28+
29+ def _nonblocking ():
30+ pass
31+
32+ def _blocking ():
33+ pass
34+
35+
36+ # LINES = 24
37+ # COLS = 80
38+
39+ LINES = 33
40+ COLS = 120
41+
42+ special_keys = {
43+ "\x1b " : ..., # all prefixes of special keys must be entered as Ellipsis
44+ "\x1b [" : ...,
45+ "\x1b [3" : ...,
46+ "\x1b [5" : ...,
47+ "\x1b [6" : ...,
48+ "\x1b [A" : "KEY_UP" ,
49+ "\x1b [B" : "KEY_DOWN" ,
50+ "\x1b [C" : "KEY_RIGHT" ,
51+ "\x1b [D" : "KEY_LEFT" ,
52+ "\x1b [H" : "KEY_HOME" ,
53+ "\x1b [F" : "KEY_END" ,
54+ "\x1b [5~" : "KEY_PGUP" ,
55+ "\x1b [6~" : "KEY_PGDN" ,
56+ "\x1b [3~" : "KEY_DELETE" ,
57+ }
58+
59+
60+ class Screen :
61+ def __init__ (self , terminal = None ):
62+ self ._poll = select .poll ()
63+ self ._poll .register (sys .stdin , select .POLLIN )
64+ self ._pending = ""
65+ self ._terminal = terminal
66+
67+ def _sys_stdin_readable (self ):
68+ return hasattr (sys .stdin , "readable" ) and sys .stdin .readable ()
69+
70+ def _sys_stdout_flush (self ):
71+ if hasattr (sys .stdout , "flush" ):
72+ sys .stdout .flush ()
73+
74+ def _terminal_read_blocking (self ):
75+ return sys .stdin .read (1 )
76+
77+ def _terminal_read_timeout (self , timeout ):
78+ if self ._sys_stdin_readable () or self ._poll .poll (timeout ):
79+ r = sys .stdin .read (1 )
80+ return r
81+ return None
82+
83+ def move (self , y , x ):
84+ if self ._terminal is not None :
85+ self ._terminal .write (f"\033 [{ y + 1 } ;{ x + 1 } H" )
86+ else :
87+ print (end = f"\033 [{ y + 1 } ;{ x + 1 } H" )
88+
89+ def erase (self ):
90+ if self ._terminal is not None :
91+ self ._terminal .write ("\033 H\033 [2J" )
92+ else :
93+ print (end = "\033 H\033 [2J" )
94+
95+ def addstr (self , y , x , text ):
96+ self .move (y , x )
97+ if self ._terminal is not None :
98+ self ._terminal .write (text )
99+ else :
100+ print (end = text )
101+
102+ def getkey (self ):
103+ self ._sys_stdout_flush ()
104+ pending = self ._pending
105+ if pending and (code := special_keys .get (pending )) is None :
106+ self ._pending = pending [1 :]
107+ return pending [0 ]
108+
109+ while True :
110+ if pending :
111+ c = self ._terminal_read_timeout (50 )
112+ if c is None :
113+ self ._pending = pending [1 :]
114+ return pending [0 ]
115+ else :
116+ c = self ._terminal_read_blocking ()
117+ c = pending + c
118+
119+ code = special_keys .get (c )
120+
121+ if code is None :
122+ self ._pending = c [1 :]
123+ return c [0 ]
124+ if code is not Ellipsis :
125+ return code
126+
127+ pending = c
128+
129+
130+ def wrapper (func , * args , ** kwds ):
131+ stdscr = Screen ()
132+ try :
133+ _nonblocking ()
134+ return func (stdscr , * args , ** kwds )
135+ finally :
136+ _blocking ()
137+ stdscr .move (LINES - 1 , 0 )
138+ print ("\n " )
139+
140+ def custom_terminal_wrapper (terminal , func , * args , ** kwds ):
141+ stdscr = Screen (terminal )
142+ try :
143+ _nonblocking ()
144+ return func (stdscr , * args , ** kwds )
145+ finally :
146+ _blocking ()
147+ stdscr .move (LINES - 1 , 0 )
148+ print ("\n " )
0 commit comments