term.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. term.py: Terminal-handling routines for the MMGen suite
  20. """
  21. import os,struct
  22. from mmgen.common import *
  23. try:
  24. import tty,termios
  25. from select import select
  26. _platform = 'linux'
  27. except:
  28. try:
  29. import msvcrt,time
  30. _platform = 'win'
  31. except:
  32. die(2,'Unable to set terminal mode')
  33. if not sys.stdin.isatty():
  34. msvcrt.setmode(sys.stdin.fileno(),os.O_BINARY)
  35. def _kb_hold_protect_unix():
  36. if os.getenv('MMGEN_TEST_SUITE'): return
  37. fd = sys.stdin.fileno()
  38. old = termios.tcgetattr(fd)
  39. tty.setcbreak(fd)
  40. timeout = float(0.3)
  41. while True:
  42. key = select([sys.stdin], [], [], timeout)[0]
  43. if key: sys.stdin.read(1)
  44. else:
  45. termios.tcsetattr(fd, termios.TCSADRAIN, old)
  46. break
  47. # Use os.read(), not file.read(), to get a variable number of bytes without blocking.
  48. # Request 5 bytes to cover escape sequences generated by F1, F2, .. Fn keys (5 bytes)
  49. # as well as UTF8 chars (4 bytes max).
  50. def _get_keypress_unix(prompt='',immed_chars='',prehold_protect=True,num_chars=5):
  51. fd_err = sys.stderr.fileno()
  52. os.write(fd_err,prompt.encode())
  53. timeout = float(0.3)
  54. fd = sys.stdin.fileno()
  55. old = termios.tcgetattr(fd)
  56. tty.setcbreak(fd)
  57. immed_chars = immed_chars.encode()
  58. if os.getenv('MMGEN_TEST_SUITE'): prehold_protect = False
  59. while True:
  60. # Protect against held-down key before read()
  61. key = select([sys.stdin], [], [], timeout)[0]
  62. s = os.read(fd,num_chars)
  63. if prehold_protect:
  64. if key: continue
  65. if immed_chars == 'ALL' or s in immed_chars: break
  66. if immed_chars == 'ALL_EXCEPT_ENTER' and not s in '\n\r': break
  67. # Protect against long keypress
  68. key = select([sys.stdin], [], [], timeout)[0]
  69. if not key: break
  70. termios.tcsetattr(fd, termios.TCSADRAIN, old)
  71. return s
  72. def _get_keypress_unix_raw(prompt='',immed_chars='',prehold_protect=None,num_chars=5):
  73. msg_r(prompt)
  74. fd = sys.stdin.fileno()
  75. old = termios.tcgetattr(fd)
  76. tty.setcbreak(fd)
  77. ch = os.read(fd,num_chars)
  78. termios.tcsetattr(fd, termios.TCSADRAIN, old)
  79. return ch
  80. def _get_keypress_unix_stub(prompt='',immed_chars='',prehold_protect=None,num_chars=None):
  81. msg_r(prompt)
  82. return sys.stdin.read(1).encode()
  83. #_get_keypress_unix_stub = _get_keypress_unix
  84. def _kb_hold_protect_mswin():
  85. timeout = float(0.5)
  86. while True:
  87. hit_time = time.time()
  88. while True:
  89. if msvcrt.kbhit():
  90. msvcrt.getch()
  91. break
  92. if float(time.time() - hit_time) > timeout:
  93. return
  94. def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True,num_chars=None):
  95. msg_r(prompt)
  96. timeout = float(0.5)
  97. while True:
  98. if msvcrt.kbhit():
  99. ch = msvcrt.getch()
  100. if ord(ch) == 3: raise KeyboardInterrupt
  101. if immed_chars == 'ALL' or ch in immed_chars:
  102. return ch
  103. if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r':
  104. return ch
  105. hit_time = time.time()
  106. while True:
  107. if msvcrt.kbhit(): break
  108. if float(time.time() - hit_time) > timeout:
  109. return ch
  110. def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None,num_chars=None):
  111. msg_r(prompt)
  112. ch = msvcrt.getch()
  113. if ord(ch) == 3: raise KeyboardInterrupt
  114. return ch
  115. def _get_keypress_mswin_stub(prompt='',immed_chars='',prehold_protect=None,num_chars=None):
  116. msg_r(prompt)
  117. return sys.stdin.read(1)
  118. def _get_terminal_size_linux():
  119. def ioctl_GWINSZ(fd):
  120. try:
  121. import fcntl
  122. cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
  123. return cr
  124. except:
  125. pass
  126. cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
  127. if not cr:
  128. try:
  129. fd = os.open(os.ctermid(), os.O_RDONLY)
  130. cr = ioctl_GWINSZ(fd)
  131. os.close(fd)
  132. except:
  133. pass
  134. if not cr:
  135. try:
  136. cr = (os.environ['LINES'], os.environ['COLUMNS'])
  137. except:
  138. return 80,25
  139. return int(cr[1]), int(cr[0])
  140. def _get_terminal_size_mswin():
  141. import sys,os,struct
  142. x,y = 0,0
  143. try:
  144. from ctypes import windll,create_string_buffer
  145. # handles - stdin: -10, stdout: -11, stderr: -12
  146. csbi = create_string_buffer(22)
  147. h = windll.kernel32.GetStdHandle(-12)
  148. res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
  149. if res:
  150. (bufx, bufy, curx, cury, wattr, left, top, right, bottom,
  151. maxx, maxy) = struct.unpack('hhhhHhhhhhh', csbi.raw)
  152. x = right - left + 1
  153. y = bottom - top + 1
  154. except:
  155. pass
  156. if x and y:
  157. return x, y
  158. else:
  159. msg(yellow('Warning: could not get terminal size. Using fallback dimensions.'))
  160. return 80,25
  161. def set_terminal_vars():
  162. global get_char,get_char_raw,kb_hold_protect,get_terminal_size
  163. if _platform == 'linux':
  164. get_char = _get_keypress_unix
  165. get_char_raw = _get_keypress_unix_raw
  166. kb_hold_protect = _kb_hold_protect_unix
  167. if not sys.stdin.isatty():
  168. get_char = get_char_raw = _get_keypress_unix_stub
  169. kb_hold_protect = lambda: None
  170. get_terminal_size = _get_terminal_size_linux
  171. else:
  172. get_char = _get_keypress_mswin
  173. get_char_raw = _get_keypress_mswin_raw
  174. kb_hold_protect = _kb_hold_protect_mswin
  175. if not sys.stdin.isatty():
  176. get_char = get_char_raw = _get_keypress_mswin_stub
  177. kb_hold_protect = lambda: None
  178. get_terminal_size = _get_terminal_size_mswin