|
@@ -17,10 +17,11 @@
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
"""
|
|
|
-term.py: Terminal-handling routines for the MMGen suite
|
|
|
+term.py: Terminal classes for the MMGen suite
|
|
|
"""
|
|
|
|
|
|
-import os,struct
|
|
|
+import sys,os,time
|
|
|
+from collections import namedtuple
|
|
|
from mmgen.common import *
|
|
|
|
|
|
try:
|
|
@@ -29,176 +30,211 @@ try:
|
|
|
_platform = 'linux'
|
|
|
except:
|
|
|
try:
|
|
|
- import msvcrt,time
|
|
|
- _platform = 'win'
|
|
|
+ import msvcrt
|
|
|
+ _platform = 'mswin'
|
|
|
except:
|
|
|
die(2,'Unable to set terminal mode')
|
|
|
if not sys.stdin.isatty():
|
|
|
msvcrt.setmode(sys.stdin.fileno(),os.O_BINARY)
|
|
|
|
|
|
-def _kb_hold_protect_unix():
|
|
|
+class MMGenTerm(object):
|
|
|
|
|
|
- if g.test_suite: return
|
|
|
+ tdim = namedtuple('terminal_dimensions',['width','height'])
|
|
|
|
|
|
- fd = sys.stdin.fileno()
|
|
|
- old = termios.tcgetattr(fd)
|
|
|
- tty.setcbreak(fd)
|
|
|
+ @classmethod
|
|
|
+ def init(cls):
|
|
|
+ pass
|
|
|
|
|
|
- timeout = float(0.3)
|
|
|
+ @classmethod
|
|
|
+ def kb_hold_protect(cls):
|
|
|
+ return None
|
|
|
|
|
|
- while True:
|
|
|
- key = select([sys.stdin], [], [], timeout)[0]
|
|
|
- if key: sys.stdin.read(1)
|
|
|
- else:
|
|
|
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
|
- break
|
|
|
-
|
|
|
-# Use os.read(), not file.read(), to get a variable number of bytes without blocking.
|
|
|
-# Request 5 bytes to cover escape sequences generated by F1, F2, .. Fn keys (5 bytes)
|
|
|
-# as well as UTF8 chars (4 bytes max).
|
|
|
-def _get_keypress_unix(prompt='',immed_chars='',prehold_protect=True,num_chars=5,sleep=None):
|
|
|
- timeout = float(0.3)
|
|
|
- fd = sys.stdin.fileno()
|
|
|
- old = termios.tcgetattr(fd)
|
|
|
- tty.setcbreak(fd)
|
|
|
- if sleep:
|
|
|
- time.sleep(sleep)
|
|
|
- msg_r(prompt)
|
|
|
- immed_chars = immed_chars.encode()
|
|
|
- if g.test_suite: prehold_protect = False
|
|
|
- while True:
|
|
|
- # Protect against held-down key before read()
|
|
|
- key = select([sys.stdin], [], [], timeout)[0]
|
|
|
- s = os.read(fd,num_chars)
|
|
|
- if prehold_protect:
|
|
|
- if key: continue
|
|
|
- if immed_chars == 'ALL' or s in immed_chars: break
|
|
|
- if immed_chars == 'ALL_EXCEPT_ENTER' and not s in '\n\r': break
|
|
|
- # Protect against long keypress
|
|
|
- key = select([sys.stdin], [], [], timeout)[0]
|
|
|
- if not key: break
|
|
|
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
|
- return s
|
|
|
-
|
|
|
-def _get_keypress_unix_raw(prompt='',immed_chars='',prehold_protect=None,num_chars=5,sleep=None):
|
|
|
- fd = sys.stdin.fileno()
|
|
|
- old = termios.tcgetattr(fd)
|
|
|
- tty.setcbreak(fd)
|
|
|
- if sleep:
|
|
|
- time.sleep(sleep)
|
|
|
- msg_r(prompt)
|
|
|
- ch = os.read(fd,num_chars)
|
|
|
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
|
- return ch
|
|
|
-
|
|
|
-def _get_keypress_unix_stub(prompt='',immed_chars='',prehold_protect=None,num_chars=None,sleep=None):
|
|
|
- if sleep:
|
|
|
- time.sleep(0.1)
|
|
|
- msg_r(prompt)
|
|
|
- return sys.stdin.read(1).encode()
|
|
|
-
|
|
|
-#_get_keypress_unix_stub = _get_keypress_unix
|
|
|
-
|
|
|
-def _kb_hold_protect_mswin():
|
|
|
-
|
|
|
- timeout = float(0.5)
|
|
|
-
|
|
|
- while True:
|
|
|
- hit_time = time.time()
|
|
|
+class MMGenTermLinux(MMGenTerm):
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def init(cls):
|
|
|
+ cls.stdin_fd = sys.stdin.fileno()
|
|
|
+ cls.old_term = termios.tcgetattr(cls.stdin_fd)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_terminal_size(cls):
|
|
|
+ try:
|
|
|
+ ret = os.get_terminal_size()
|
|
|
+ except:
|
|
|
+ try:
|
|
|
+ ret = (os.environ['COLUMNS'],os.environ['LINES'])
|
|
|
+ except:
|
|
|
+ ret = (80,25)
|
|
|
+ return cls.tdim(*ret)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def kb_hold_protect(cls):
|
|
|
+ if g.test_suite:
|
|
|
+ return
|
|
|
+ tty.setcbreak(cls.stdin_fd)
|
|
|
+ timeout = 0.3
|
|
|
while True:
|
|
|
- if msvcrt.kbhit():
|
|
|
- msvcrt.getch()
|
|
|
+ key = select([sys.stdin], [], [], timeout)[0]
|
|
|
+ if key:
|
|
|
+ sys.stdin.read(1)
|
|
|
+ else:
|
|
|
+ termios.tcsetattr(cls.stdin_fd, termios.TCSADRAIN, cls.old_term)
|
|
|
break
|
|
|
- if float(time.time() - hit_time) > timeout:
|
|
|
- return
|
|
|
|
|
|
-def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True,num_chars=None,sleep=None):
|
|
|
+ @classmethod
|
|
|
+ def get_char(cls,prompt='',immed_chars='',prehold_protect=True,num_chars=5,sleep=None):
|
|
|
+ """
|
|
|
+ Use os.read(), not file.read(), to get a variable number of bytes without blocking.
|
|
|
+ Request 5 bytes to cover escape sequences generated by F1, F2, .. Fn keys (5 bytes)
|
|
|
+ as well as UTF8 chars (4 bytes max).
|
|
|
+ """
|
|
|
+ timeout = 0.3
|
|
|
+ tty.setcbreak(cls.stdin_fd)
|
|
|
+ if sleep:
|
|
|
+ time.sleep(sleep)
|
|
|
+ msg_r(prompt)
|
|
|
+ if g.test_suite:
|
|
|
+ prehold_protect = False
|
|
|
+ while True:
|
|
|
+ # Protect against held-down key before read()
|
|
|
+ key = select([sys.stdin], [], [], timeout)[0]
|
|
|
+ s = os.read(cls.stdin_fd,num_chars).decode()
|
|
|
+ if prehold_protect and key:
|
|
|
+ continue
|
|
|
+ if s in immed_chars:
|
|
|
+ break
|
|
|
+ # Protect against long keypress
|
|
|
+ key = select([sys.stdin], [], [], timeout)[0]
|
|
|
+ if not key:
|
|
|
+ break
|
|
|
+ termios.tcsetattr(cls.stdin_fd, termios.TCSADRAIN, cls.old_term)
|
|
|
+ return s
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_char_raw(cls,prompt='',num_chars=5,sleep=None):
|
|
|
+ tty.setcbreak(cls.stdin_fd)
|
|
|
+ if sleep:
|
|
|
+ time.sleep(sleep)
|
|
|
+ msg_r(prompt)
|
|
|
+ s = os.read(cls.stdin_fd,num_chars).decode()
|
|
|
+ termios.tcsetattr(cls.stdin_fd, termios.TCSADRAIN, cls.old_term)
|
|
|
+ return s
|
|
|
+
|
|
|
+class MMGenTermLinuxStub(MMGenTermLinux):
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def init(cls):
|
|
|
+ pass
|
|
|
|
|
|
- if sleep:
|
|
|
- time.sleep(sleep)
|
|
|
+ @classmethod
|
|
|
+ def get_char(cls,prompt='',immed_chars='',prehold_protect=None,num_chars=None,sleep=None):
|
|
|
+ if sleep:
|
|
|
+ time.sleep(0.1)
|
|
|
+ msg_r(prompt)
|
|
|
+ return sys.stdin.read(1)
|
|
|
|
|
|
- msg_r(prompt)
|
|
|
- timeout = float(0.5)
|
|
|
+ get_char_raw = get_char
|
|
|
|
|
|
- while True:
|
|
|
- if msvcrt.kbhit():
|
|
|
- ch = msvcrt.getch()
|
|
|
+class MMGenTermMSWin(MMGenTerm):
|
|
|
|
|
|
- if ord(ch) == 3: raise KeyboardInterrupt
|
|
|
+ @classmethod
|
|
|
+ def get_terminal_size(cls):
|
|
|
+ import struct
|
|
|
+ x,y = 0,0
|
|
|
+ try:
|
|
|
+ from ctypes import windll,create_string_buffer
|
|
|
+ # handles - stdin: -10, stdout: -11, stderr: -12
|
|
|
+ csbi = create_string_buffer(22)
|
|
|
+ h = windll.kernel32.GetStdHandle(-12)
|
|
|
+ res = windll.kernel32.GetConsoleScreenBufferInfo(h,csbi)
|
|
|
+ assert res, 'failed to get console screen buffer info'
|
|
|
+ left,top,right,bottom = struct.unpack('hhhhHhhhhhh', csbi.raw)[5:9]
|
|
|
+ x = right - left + 1
|
|
|
+ y = bottom - top + 1
|
|
|
+ except:
|
|
|
+ pass
|
|
|
|
|
|
- if immed_chars == 'ALL' or ch.decode() in immed_chars:
|
|
|
- return ch
|
|
|
- if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r':
|
|
|
- return ch
|
|
|
+ if x and y:
|
|
|
+ return cls.tdim(x,y)
|
|
|
+ else:
|
|
|
+ msg(yellow('Warning: could not get terminal size. Using fallback dimensions.'))
|
|
|
+ return cls.tdim(80,25)
|
|
|
|
|
|
+ @classmethod
|
|
|
+ def kb_hold_protect(cls):
|
|
|
+ timeout = 0.5
|
|
|
+ while True:
|
|
|
hit_time = time.time()
|
|
|
-
|
|
|
while True:
|
|
|
- if msvcrt.kbhit(): break
|
|
|
- if float(time.time() - hit_time) > timeout:
|
|
|
+ if msvcrt.kbhit():
|
|
|
+ msvcrt.getch()
|
|
|
+ break
|
|
|
+ if time.time() - hit_time > timeout:
|
|
|
+ return
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_char(cls,prompt='',immed_chars='',prehold_protect=True,num_chars=None,sleep=None):
|
|
|
+ """
|
|
|
+ always return a single character, ignore num_chars
|
|
|
+ first character of 2-character sequence returned by F1-F12 keys is discarded
|
|
|
+ prehold_protect is ignored
|
|
|
+ """
|
|
|
+ if sleep:
|
|
|
+ time.sleep(sleep)
|
|
|
+ msg_r(prompt)
|
|
|
+ timeout = 0.5
|
|
|
+ while True:
|
|
|
+ if msvcrt.kbhit():
|
|
|
+ ch = chr(msvcrt.getch()[0])
|
|
|
+ if ch == '\x03':
|
|
|
+ raise KeyboardInterrupt
|
|
|
+ if ch in immed_chars:
|
|
|
return ch
|
|
|
+ hit_time = time.time()
|
|
|
+ while True:
|
|
|
+ if msvcrt.kbhit():
|
|
|
+ break
|
|
|
+ if time.time() - hit_time > timeout:
|
|
|
+ return ch
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_char_raw(cls,prompt='',num_chars=None,sleep=None):
|
|
|
+ """
|
|
|
+ always return a single character, ignore num_chars
|
|
|
+ first character of 2-character sequence returned by F1-F12 keys is discarded
|
|
|
+ """
|
|
|
+ while True:
|
|
|
+ if sleep:
|
|
|
+ time.sleep(sleep)
|
|
|
+ msg_r(prompt)
|
|
|
+ ch = chr(msvcrt.getch()[0])
|
|
|
+ if ch in '\x00\xe0': # first char of 2-char sequence for F1-F12 keys
|
|
|
+ continue
|
|
|
+ if ch == '\x03':
|
|
|
+ raise KeyboardInterrupt
|
|
|
+ return ch
|
|
|
|
|
|
-def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None,num_chars=None,sleep=None):
|
|
|
- if sleep:
|
|
|
- time.sleep(sleep)
|
|
|
- msg_r(prompt)
|
|
|
- ch = msvcrt.getch()
|
|
|
- if ch == b'\x03': raise KeyboardInterrupt
|
|
|
- return ch
|
|
|
-
|
|
|
-def _get_keypress_mswin_stub(prompt='',immed_chars='',prehold_protect=None,num_chars=None,sleep=None):
|
|
|
- if sleep:
|
|
|
- time.sleep(0.1)
|
|
|
- msg_r(prompt)
|
|
|
- return os.read(0,1)
|
|
|
-
|
|
|
-def _get_terminal_size_linux():
|
|
|
- try:
|
|
|
- return tuple(os.get_terminal_size())
|
|
|
- except:
|
|
|
- try:
|
|
|
- return (os.environ['LINES'],os.environ['COLUMNS'])
|
|
|
- except:
|
|
|
- return (80,25)
|
|
|
+class MMGenTermMSWinStub(MMGenTermMSWin):
|
|
|
|
|
|
-def _get_terminal_size_mswin():
|
|
|
- import sys,os,struct
|
|
|
- x,y = 0,0
|
|
|
- try:
|
|
|
- from ctypes import windll,create_string_buffer
|
|
|
- # handles - stdin: -10, stdout: -11, stderr: -12
|
|
|
- csbi = create_string_buffer(22)
|
|
|
- h = windll.kernel32.GetStdHandle(-12)
|
|
|
- res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
|
|
|
- if res:
|
|
|
- (bufx, bufy, curx, cury, wattr, left, top, right, bottom,
|
|
|
- maxx, maxy) = struct.unpack('hhhhHhhhhhh', csbi.raw)
|
|
|
- x = right - left + 1
|
|
|
- y = bottom - top + 1
|
|
|
- except:
|
|
|
- pass
|
|
|
+ @classmethod
|
|
|
+ def get_char(cls,prompt='',immed_chars='',prehold_protect=None,num_chars=None,sleep=None):
|
|
|
+ if sleep:
|
|
|
+ time.sleep(0.1)
|
|
|
+ msg_r(prompt)
|
|
|
+ return os.read(0,1).decode()
|
|
|
|
|
|
- if x and y:
|
|
|
- return x, y
|
|
|
- else:
|
|
|
- msg(yellow('Warning: could not get terminal size. Using fallback dimensions.'))
|
|
|
- return 80,25
|
|
|
+ get_char_raw = get_char
|
|
|
+
|
|
|
+def init_term():
|
|
|
+
|
|
|
+ term = {
|
|
|
+ 'linux': (MMGenTermLinux if sys.stdin.isatty() else MMGenTermLinuxStub),
|
|
|
+ 'mswin': (MMGenTermMSWin if sys.stdin.isatty() else MMGenTermMSWinStub),
|
|
|
+ }[_platform]
|
|
|
+
|
|
|
+ term.init()
|
|
|
|
|
|
-def set_terminal_vars():
|
|
|
global get_char,get_char_raw,kb_hold_protect,get_terminal_size
|
|
|
- if _platform == 'linux':
|
|
|
- get_char = _get_keypress_unix
|
|
|
- get_char_raw = _get_keypress_unix_raw
|
|
|
- kb_hold_protect = _kb_hold_protect_unix
|
|
|
- if not sys.stdin.isatty():
|
|
|
- get_char = get_char_raw = _get_keypress_unix_stub
|
|
|
- kb_hold_protect = lambda: None
|
|
|
- get_terminal_size = _get_terminal_size_linux
|
|
|
- else:
|
|
|
- get_char = _get_keypress_mswin
|
|
|
- get_char_raw = _get_keypress_mswin_raw
|
|
|
- kb_hold_protect = _kb_hold_protect_mswin
|
|
|
- if not sys.stdin.isatty():
|
|
|
- get_char = get_char_raw = _get_keypress_mswin_stub
|
|
|
- kb_hold_protect = lambda: None
|
|
|
- get_terminal_size = _get_terminal_size_mswin
|
|
|
+
|
|
|
+ for var in ('get_char','get_char_raw','kb_hold_protect','get_terminal_size'):
|
|
|
+ globals()[var] = getattr(term,var)
|