winterm.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
  2. try:
  3. from msvcrt import get_osfhandle
  4. except ImportError:
  5. def get_osfhandle(_):
  6. raise OSError("This isn't windows!")
  7. from . import win32
  8. # from wincon.h
  9. class WinColor(object):
  10. BLACK = 0
  11. BLUE = 1
  12. GREEN = 2
  13. CYAN = 3
  14. RED = 4
  15. MAGENTA = 5
  16. YELLOW = 6
  17. GREY = 7
  18. # from wincon.h
  19. class WinStyle(object):
  20. NORMAL = 0x00 # dim text, dim background
  21. BRIGHT = 0x08 # bright text, dim background
  22. BRIGHT_BACKGROUND = 0x80 # dim text, bright background
  23. class WinTerm(object):
  24. def __init__(self):
  25. self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
  26. self.set_attrs(self._default)
  27. self._default_fore = self._fore
  28. self._default_back = self._back
  29. self._default_style = self._style
  30. # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
  31. # So that LIGHT_EX colors and BRIGHT style do not clobber each other,
  32. # we track them separately, since LIGHT_EX is overwritten by Fore/Back
  33. # and BRIGHT is overwritten by Style codes.
  34. self._light = 0
  35. def get_attrs(self):
  36. return self._fore + self._back * 16 + (self._style | self._light)
  37. def set_attrs(self, value):
  38. self._fore = value & 7
  39. self._back = (value >> 4) & 7
  40. self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
  41. def reset_all(self, on_stderr=None):
  42. self.set_attrs(self._default)
  43. self.set_console(attrs=self._default)
  44. self._light = 0
  45. def fore(self, fore=None, light=False, on_stderr=False):
  46. if fore is None:
  47. fore = self._default_fore
  48. self._fore = fore
  49. # Emulate LIGHT_EX with BRIGHT Style
  50. if light:
  51. self._light |= WinStyle.BRIGHT
  52. else:
  53. self._light &= ~WinStyle.BRIGHT
  54. self.set_console(on_stderr=on_stderr)
  55. def back(self, back=None, light=False, on_stderr=False):
  56. if back is None:
  57. back = self._default_back
  58. self._back = back
  59. # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
  60. if light:
  61. self._light |= WinStyle.BRIGHT_BACKGROUND
  62. else:
  63. self._light &= ~WinStyle.BRIGHT_BACKGROUND
  64. self.set_console(on_stderr=on_stderr)
  65. def style(self, style=None, on_stderr=False):
  66. if style is None:
  67. style = self._default_style
  68. self._style = style
  69. self.set_console(on_stderr=on_stderr)
  70. def set_console(self, attrs=None, on_stderr=False):
  71. if attrs is None:
  72. attrs = self.get_attrs()
  73. handle = win32.STDOUT
  74. if on_stderr:
  75. handle = win32.STDERR
  76. win32.SetConsoleTextAttribute(handle, attrs)
  77. def get_position(self, handle):
  78. position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
  79. # Because Windows coordinates are 0-based,
  80. # and win32.SetConsoleCursorPosition expects 1-based.
  81. position.X += 1
  82. position.Y += 1
  83. return position
  84. def set_cursor_position(self, position=None, on_stderr=False):
  85. if position is None:
  86. # I'm not currently tracking the position, so there is no default.
  87. # position = self.get_position()
  88. return
  89. handle = win32.STDOUT
  90. if on_stderr:
  91. handle = win32.STDERR
  92. win32.SetConsoleCursorPosition(handle, position)
  93. def cursor_adjust(self, x, y, on_stderr=False):
  94. handle = win32.STDOUT
  95. if on_stderr:
  96. handle = win32.STDERR
  97. position = self.get_position(handle)
  98. adjusted_position = (position.Y + y, position.X + x)
  99. win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
  100. def erase_screen(self, mode=0, on_stderr=False):
  101. # 0 should clear from the cursor to the end of the screen.
  102. # 1 should clear from the cursor to the beginning of the screen.
  103. # 2 should clear the entire screen, and move cursor to (1,1)
  104. handle = win32.STDOUT
  105. if on_stderr:
  106. handle = win32.STDERR
  107. csbi = win32.GetConsoleScreenBufferInfo(handle)
  108. # get the number of character cells in the current buffer
  109. cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
  110. # get number of character cells before current cursor position
  111. cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
  112. if mode == 0:
  113. from_coord = csbi.dwCursorPosition
  114. cells_to_erase = cells_in_screen - cells_before_cursor
  115. elif mode == 1:
  116. from_coord = win32.COORD(0, 0)
  117. cells_to_erase = cells_before_cursor
  118. elif mode == 2:
  119. from_coord = win32.COORD(0, 0)
  120. cells_to_erase = cells_in_screen
  121. else:
  122. # invalid mode
  123. return
  124. # fill the entire screen with blanks
  125. win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
  126. # now set the buffer's attributes accordingly
  127. win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
  128. if mode == 2:
  129. # put the cursor where needed
  130. win32.SetConsoleCursorPosition(handle, (1, 1))
  131. def erase_line(self, mode=0, on_stderr=False):
  132. # 0 should clear from the cursor to the end of the line.
  133. # 1 should clear from the cursor to the beginning of the line.
  134. # 2 should clear the entire line.
  135. handle = win32.STDOUT
  136. if on_stderr:
  137. handle = win32.STDERR
  138. csbi = win32.GetConsoleScreenBufferInfo(handle)
  139. if mode == 0:
  140. from_coord = csbi.dwCursorPosition
  141. cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
  142. elif mode == 1:
  143. from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
  144. cells_to_erase = csbi.dwCursorPosition.X
  145. elif mode == 2:
  146. from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
  147. cells_to_erase = csbi.dwSize.X
  148. else:
  149. # invalid mode
  150. return
  151. # fill the entire screen with blanks
  152. win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
  153. # now set the buffer's attributes accordingly
  154. win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
  155. def set_title(self, title):
  156. win32.SetConsoleTitle(title)
  157. def enable_vt_processing(fd):
  158. if win32.windll is None or not win32.winapi_test():
  159. return False
  160. try:
  161. handle = get_osfhandle(fd)
  162. mode = win32.GetConsoleMode(handle)
  163. win32.SetConsoleMode(
  164. handle,
  165. mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
  166. )
  167. mode = win32.GetConsoleMode(handle)
  168. if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
  169. return True
  170. # Can get TypeError in testsuite where 'fd' is a Mock()
  171. except (OSError, TypeError):
  172. return False