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 [5" : ...,
46+ "\x1b [6" : ...,
47+ "\x1b [A" : "KEY_UP" ,
48+ "\x1b [B" : "KEY_DOWN" ,
49+ "\x1b [C" : "KEY_RIGHT" ,
50+ "\x1b [D" : "KEY_LEFT" ,
51+ "\x1b [H" : "KEY_HOME" ,
52+ "\x1b [F" : "KEY_END" ,
53+ "\x1b [5~" : "KEY_PGUP" ,
54+ "\x1b [6~" : "KEY_PGDN" ,
55+ "\x1b [3~" : "KEY_DELETE" ,
56+ }
57+
58+
59+ class Screen :
60+ def __init__ (self , terminal = None ):
61+ self ._poll = select .poll ()
62+ self ._poll .register (sys .stdin , select .POLLIN )
63+ self ._pending = ""
64+ self ._terminal = terminal
65+
66+ def _sys_stdin_readable (self ):
67+ return hasattr (sys .stdin , "readable" ) and sys .stdin .readable ()
68+
69+ def _sys_stdout_flush (self ):
70+ if hasattr (sys .stdout , "flush" ):
71+ sys .stdout .flush ()
72+
73+ def _terminal_read_blocking (self ):
74+ return sys .stdin .read (1 )
75+
76+ def _terminal_read_timeout (self , timeout ):
77+ if self ._sys_stdin_readable () or self ._poll .poll (timeout ):
78+ r = sys .stdin .read (1 )
79+ return r
80+ return None
81+
82+ def move (self , y , x ):
83+ if self ._terminal is not None :
84+ self ._terminal .write (f"\033 [{ y + 1 } ;{ x + 1 } H" )
85+ else :
86+ print (end = f"\033 [{ y + 1 } ;{ x + 1 } H" )
87+
88+ def erase (self ):
89+ if self ._terminal is not None :
90+ self ._terminal .write ("\033 H\033 [2J" )
91+ else :
92+ print (end = "\033 H\033 [2J" )
93+
94+ def addstr (self , y , x , text ):
95+ self .move (y , x )
96+ if self ._terminal is not None :
97+ self ._terminal .write (text )
98+ else :
99+ print (end = text )
100+
101+ def getkey (self ):
102+ self ._sys_stdout_flush ()
103+ pending = self ._pending
104+ if pending and (code := special_keys .get (pending )) is None :
105+ self ._pending = pending [1 :]
106+ return pending [0 ]
107+
108+ while True :
109+ if pending :
110+ c = self ._terminal_read_timeout (50 )
111+ if c is None :
112+ self ._pending = pending [1 :]
113+ return pending [0 ]
114+ else :
115+ c = self ._terminal_read_blocking ()
116+ c = pending + c
117+
118+ code = special_keys .get (c )
119+
120+ if code is None :
121+ self ._pending = c [1 :]
122+ return c [0 ]
123+ if code is not Ellipsis :
124+ return code
125+
126+ pending = c
127+
128+
129+ def wrapper (func , * args , ** kwds ):
130+ stdscr = Screen ()
131+ try :
132+ _nonblocking ()
133+ return func (stdscr , * args , ** kwds )
134+ finally :
135+ _blocking ()
136+ stdscr .move (LINES - 1 , 0 )
137+ print ("\n " )
138+
139+ def custom_terminal_wrapper (terminal , func , * args , ** kwds ):
140+ stdscr = Screen (terminal )
141+ try :
142+ _nonblocking ()
143+ return func (stdscr , * args , ** kwds )
144+ finally :
145+ _blocking ()
146+ stdscr .move (LINES - 1 , 0 )
147+ print ("\n " )
0 commit comments