2023-03-18 12:55:22 +01:00
|
|
|
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
2023-06-15 20:22:20 +02:00
|
|
|
try:
|
|
|
|
from msvcrt import get_osfhandle
|
|
|
|
except ImportError:
|
|
|
|
def get_osfhandle(_):
|
|
|
|
raise OSError("This isn't windows!")
|
|
|
|
|
2023-03-18 12:55:22 +01:00
|
|
|
|
2023-06-15 20:22:20 +02:00
|
|
|
from . import win32
|
2023-03-18 12:55:22 +01:00
|
|
|
|
|
|
|
# from wincon.h
|
|
|
|
class WinColor(object):
|
|
|
|
BLACK = 0
|
|
|
|
BLUE = 1
|
|
|
|
GREEN = 2
|
|
|
|
CYAN = 3
|
|
|
|
RED = 4
|
|
|
|
MAGENTA = 5
|
|
|
|
YELLOW = 6
|
|
|
|
GREY = 7
|
|
|
|
|
|
|
|
# from wincon.h
|
|
|
|
class WinStyle(object):
|
|
|
|
NORMAL = 0x00 # dim text, dim background
|
|
|
|
BRIGHT = 0x08 # bright text, dim background
|
|
|
|
BRIGHT_BACKGROUND = 0x80 # dim text, bright background
|
|
|
|
|
|
|
|
class WinTerm(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
|
|
|
|
self.set_attrs(self._default)
|
|
|
|
self._default_fore = self._fore
|
|
|
|
self._default_back = self._back
|
|
|
|
self._default_style = self._style
|
|
|
|
# In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
|
|
|
|
# So that LIGHT_EX colors and BRIGHT style do not clobber each other,
|
|
|
|
# we track them separately, since LIGHT_EX is overwritten by Fore/Back
|
|
|
|
# and BRIGHT is overwritten by Style codes.
|
|
|
|
self._light = 0
|
|
|
|
|
|
|
|
def get_attrs(self):
|
|
|
|
return self._fore + self._back * 16 + (self._style | self._light)
|
|
|
|
|
|
|
|
def set_attrs(self, value):
|
|
|
|
self._fore = value & 7
|
|
|
|
self._back = (value >> 4) & 7
|
|
|
|
self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
|
|
|
|
|
|
|
|
def reset_all(self, on_stderr=None):
|
|
|
|
self.set_attrs(self._default)
|
|
|
|
self.set_console(attrs=self._default)
|
|
|
|
self._light = 0
|
|
|
|
|
|
|
|
def fore(self, fore=None, light=False, on_stderr=False):
|
|
|
|
if fore is None:
|
|
|
|
fore = self._default_fore
|
|
|
|
self._fore = fore
|
|
|
|
# Emulate LIGHT_EX with BRIGHT Style
|
|
|
|
if light:
|
|
|
|
self._light |= WinStyle.BRIGHT
|
|
|
|
else:
|
|
|
|
self._light &= ~WinStyle.BRIGHT
|
|
|
|
self.set_console(on_stderr=on_stderr)
|
|
|
|
|
|
|
|
def back(self, back=None, light=False, on_stderr=False):
|
|
|
|
if back is None:
|
|
|
|
back = self._default_back
|
|
|
|
self._back = back
|
|
|
|
# Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
|
|
|
|
if light:
|
|
|
|
self._light |= WinStyle.BRIGHT_BACKGROUND
|
|
|
|
else:
|
|
|
|
self._light &= ~WinStyle.BRIGHT_BACKGROUND
|
|
|
|
self.set_console(on_stderr=on_stderr)
|
|
|
|
|
|
|
|
def style(self, style=None, on_stderr=False):
|
|
|
|
if style is None:
|
|
|
|
style = self._default_style
|
|
|
|
self._style = style
|
|
|
|
self.set_console(on_stderr=on_stderr)
|
|
|
|
|
|
|
|
def set_console(self, attrs=None, on_stderr=False):
|
|
|
|
if attrs is None:
|
|
|
|
attrs = self.get_attrs()
|
|
|
|
handle = win32.STDOUT
|
|
|
|
if on_stderr:
|
|
|
|
handle = win32.STDERR
|
|
|
|
win32.SetConsoleTextAttribute(handle, attrs)
|
|
|
|
|
|
|
|
def get_position(self, handle):
|
|
|
|
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
|
|
|
|
# Because Windows coordinates are 0-based,
|
|
|
|
# and win32.SetConsoleCursorPosition expects 1-based.
|
|
|
|
position.X += 1
|
|
|
|
position.Y += 1
|
|
|
|
return position
|
|
|
|
|
|
|
|
def set_cursor_position(self, position=None, on_stderr=False):
|
|
|
|
if position is None:
|
|
|
|
# I'm not currently tracking the position, so there is no default.
|
|
|
|
# position = self.get_position()
|
|
|
|
return
|
|
|
|
handle = win32.STDOUT
|
|
|
|
if on_stderr:
|
|
|
|
handle = win32.STDERR
|
|
|
|
win32.SetConsoleCursorPosition(handle, position)
|
|
|
|
|
|
|
|
def cursor_adjust(self, x, y, on_stderr=False):
|
|
|
|
handle = win32.STDOUT
|
|
|
|
if on_stderr:
|
|
|
|
handle = win32.STDERR
|
|
|
|
position = self.get_position(handle)
|
|
|
|
adjusted_position = (position.Y + y, position.X + x)
|
|
|
|
win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
|
|
|
|
|
|
|
|
def erase_screen(self, mode=0, on_stderr=False):
|
|
|
|
# 0 should clear from the cursor to the end of the screen.
|
|
|
|
# 1 should clear from the cursor to the beginning of the screen.
|
|
|
|
# 2 should clear the entire screen, and move cursor to (1,1)
|
|
|
|
handle = win32.STDOUT
|
|
|
|
if on_stderr:
|
|
|
|
handle = win32.STDERR
|
|
|
|
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
|
|
|
# get the number of character cells in the current buffer
|
|
|
|
cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
|
|
|
|
# get number of character cells before current cursor position
|
|
|
|
cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
|
|
|
|
if mode == 0:
|
|
|
|
from_coord = csbi.dwCursorPosition
|
|
|
|
cells_to_erase = cells_in_screen - cells_before_cursor
|
|
|
|
elif mode == 1:
|
|
|
|
from_coord = win32.COORD(0, 0)
|
|
|
|
cells_to_erase = cells_before_cursor
|
|
|
|
elif mode == 2:
|
|
|
|
from_coord = win32.COORD(0, 0)
|
|
|
|
cells_to_erase = cells_in_screen
|
|
|
|
else:
|
|
|
|
# invalid mode
|
|
|
|
return
|
|
|
|
# fill the entire screen with blanks
|
|
|
|
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
|
|
|
# now set the buffer's attributes accordingly
|
|
|
|
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
|
|
|
if mode == 2:
|
|
|
|
# put the cursor where needed
|
|
|
|
win32.SetConsoleCursorPosition(handle, (1, 1))
|
|
|
|
|
|
|
|
def erase_line(self, mode=0, on_stderr=False):
|
|
|
|
# 0 should clear from the cursor to the end of the line.
|
|
|
|
# 1 should clear from the cursor to the beginning of the line.
|
|
|
|
# 2 should clear the entire line.
|
|
|
|
handle = win32.STDOUT
|
|
|
|
if on_stderr:
|
|
|
|
handle = win32.STDERR
|
|
|
|
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
|
|
|
if mode == 0:
|
|
|
|
from_coord = csbi.dwCursorPosition
|
|
|
|
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
|
|
|
|
elif mode == 1:
|
|
|
|
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
|
|
|
cells_to_erase = csbi.dwCursorPosition.X
|
|
|
|
elif mode == 2:
|
|
|
|
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
|
|
|
cells_to_erase = csbi.dwSize.X
|
|
|
|
else:
|
|
|
|
# invalid mode
|
|
|
|
return
|
|
|
|
# fill the entire screen with blanks
|
|
|
|
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
|
|
|
# now set the buffer's attributes accordingly
|
|
|
|
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
|
|
|
|
|
|
|
def set_title(self, title):
|
|
|
|
win32.SetConsoleTitle(title)
|
2023-06-15 20:22:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
def enable_vt_processing(fd):
|
|
|
|
if win32.windll is None or not win32.winapi_test():
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
handle = get_osfhandle(fd)
|
|
|
|
mode = win32.GetConsoleMode(handle)
|
|
|
|
win32.SetConsoleMode(
|
|
|
|
handle,
|
|
|
|
mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
|
|
|
)
|
|
|
|
|
|
|
|
mode = win32.GetConsoleMode(handle)
|
|
|
|
if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
|
|
|
|
return True
|
|
|
|
# Can get TypeError in testsuite where 'fd' is a Mock()
|
|
|
|
except (OSError, TypeError):
|
|
|
|
return False
|