Varia's website
https://varia.zone
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
662 lines
22 KiB
662 lines
22 KiB
2 weeks ago
|
"""Light wrapper around the Win32 Console API - this module should only be imported on Windows
|
||
|
|
||
|
The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
|
||
|
"""
|
||
|
|
||
|
import ctypes
|
||
|
import sys
|
||
|
from typing import Any
|
||
|
|
||
|
windll: Any = None
|
||
|
if sys.platform == "win32":
|
||
|
windll = ctypes.LibraryLoader(ctypes.WinDLL)
|
||
|
else:
|
||
|
raise ImportError(f"{__name__} can only be imported on Windows")
|
||
|
|
||
|
import time
|
||
|
from ctypes import Structure, byref, wintypes
|
||
|
from typing import IO, NamedTuple, Type, cast
|
||
|
|
||
|
from rich.color import ColorSystem
|
||
|
from rich.style import Style
|
||
|
|
||
|
STDOUT = -11
|
||
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
||
|
|
||
|
COORD = wintypes._COORD
|
||
|
|
||
|
|
||
|
class LegacyWindowsError(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class WindowsCoordinates(NamedTuple):
|
||
|
"""Coordinates in the Windows Console API are (y, x), not (x, y).
|
||
|
This class is intended to prevent that confusion.
|
||
|
Rows and columns are indexed from 0.
|
||
|
This class can be used in place of wintypes._COORD in arguments and argtypes.
|
||
|
"""
|
||
|
|
||
|
row: int
|
||
|
col: int
|
||
|
|
||
|
@classmethod
|
||
|
def from_param(cls, value: "WindowsCoordinates") -> COORD:
|
||
|
"""Converts a WindowsCoordinates into a wintypes _COORD structure.
|
||
|
This classmethod is internally called by ctypes to perform the conversion.
|
||
|
|
||
|
Args:
|
||
|
value (WindowsCoordinates): The input coordinates to convert.
|
||
|
|
||
|
Returns:
|
||
|
wintypes._COORD: The converted coordinates struct.
|
||
|
"""
|
||
|
return COORD(value.col, value.row)
|
||
|
|
||
|
|
||
|
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
||
|
_fields_ = [
|
||
|
("dwSize", COORD),
|
||
|
("dwCursorPosition", COORD),
|
||
|
("wAttributes", wintypes.WORD),
|
||
|
("srWindow", wintypes.SMALL_RECT),
|
||
|
("dwMaximumWindowSize", COORD),
|
||
|
]
|
||
|
|
||
|
|
||
|
class CONSOLE_CURSOR_INFO(ctypes.Structure):
|
||
|
_fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)]
|
||
|
|
||
|
|
||
|
_GetStdHandle = windll.kernel32.GetStdHandle
|
||
|
_GetStdHandle.argtypes = [
|
||
|
wintypes.DWORD,
|
||
|
]
|
||
|
_GetStdHandle.restype = wintypes.HANDLE
|
||
|
|
||
|
|
||
|
def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:
|
||
|
"""Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
|
||
|
|
||
|
Args:
|
||
|
handle (int): Integer identifier for the handle. Defaults to -11 (stdout).
|
||
|
|
||
|
Returns:
|
||
|
wintypes.HANDLE: The handle
|
||
|
"""
|
||
|
return cast(wintypes.HANDLE, _GetStdHandle(handle))
|
||
|
|
||
|
|
||
|
_GetConsoleMode = windll.kernel32.GetConsoleMode
|
||
|
_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
|
||
|
_GetConsoleMode.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def GetConsoleMode(std_handle: wintypes.HANDLE) -> int:
|
||
|
"""Retrieves the current input mode of a console's input buffer
|
||
|
or the current output mode of a console screen buffer.
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
|
||
|
Raises:
|
||
|
LegacyWindowsError: If any error occurs while calling the Windows console API.
|
||
|
|
||
|
Returns:
|
||
|
int: Value representing the current console mode as documented at
|
||
|
https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters
|
||
|
"""
|
||
|
|
||
|
console_mode = wintypes.DWORD()
|
||
|
success = bool(_GetConsoleMode(std_handle, console_mode))
|
||
|
if not success:
|
||
|
raise LegacyWindowsError("Unable to get legacy Windows Console Mode")
|
||
|
return console_mode.value
|
||
|
|
||
|
|
||
|
_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW
|
||
|
_FillConsoleOutputCharacterW.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
ctypes.c_char,
|
||
|
wintypes.DWORD,
|
||
|
cast(Type[COORD], WindowsCoordinates),
|
||
|
ctypes.POINTER(wintypes.DWORD),
|
||
|
]
|
||
|
_FillConsoleOutputCharacterW.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def FillConsoleOutputCharacter(
|
||
|
std_handle: wintypes.HANDLE,
|
||
|
char: str,
|
||
|
length: int,
|
||
|
start: WindowsCoordinates,
|
||
|
) -> int:
|
||
|
"""Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
char (str): The character to write. Must be a string of length 1.
|
||
|
length (int): The number of times to write the character.
|
||
|
start (WindowsCoordinates): The coordinates to start writing at.
|
||
|
|
||
|
Returns:
|
||
|
int: The number of characters written.
|
||
|
"""
|
||
|
character = ctypes.c_char(char.encode())
|
||
|
num_characters = wintypes.DWORD(length)
|
||
|
num_written = wintypes.DWORD(0)
|
||
|
_FillConsoleOutputCharacterW(
|
||
|
std_handle,
|
||
|
character,
|
||
|
num_characters,
|
||
|
start,
|
||
|
byref(num_written),
|
||
|
)
|
||
|
return num_written.value
|
||
|
|
||
|
|
||
|
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
|
||
|
_FillConsoleOutputAttribute.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
wintypes.WORD,
|
||
|
wintypes.DWORD,
|
||
|
cast(Type[COORD], WindowsCoordinates),
|
||
|
ctypes.POINTER(wintypes.DWORD),
|
||
|
]
|
||
|
_FillConsoleOutputAttribute.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def FillConsoleOutputAttribute(
|
||
|
std_handle: wintypes.HANDLE,
|
||
|
attributes: int,
|
||
|
length: int,
|
||
|
start: WindowsCoordinates,
|
||
|
) -> int:
|
||
|
"""Sets the character attributes for a specified number of character cells,
|
||
|
beginning at the specified coordinates in a screen buffer.
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
attributes (int): Integer value representing the foreground and background colours of the cells.
|
||
|
length (int): The number of cells to set the output attribute of.
|
||
|
start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.
|
||
|
|
||
|
Returns:
|
||
|
int: The number of cells whose attributes were actually set.
|
||
|
"""
|
||
|
num_cells = wintypes.DWORD(length)
|
||
|
style_attrs = wintypes.WORD(attributes)
|
||
|
num_written = wintypes.DWORD(0)
|
||
|
_FillConsoleOutputAttribute(
|
||
|
std_handle, style_attrs, num_cells, start, byref(num_written)
|
||
|
)
|
||
|
return num_written.value
|
||
|
|
||
|
|
||
|
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
|
||
|
_SetConsoleTextAttribute.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
wintypes.WORD,
|
||
|
]
|
||
|
_SetConsoleTextAttribute.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def SetConsoleTextAttribute(
|
||
|
std_handle: wintypes.HANDLE, attributes: wintypes.WORD
|
||
|
) -> bool:
|
||
|
"""Set the colour attributes for all text written after this function is called.
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
attributes (int): Integer value representing the foreground and background colours.
|
||
|
|
||
|
|
||
|
Returns:
|
||
|
bool: True if the attribute was set successfully, otherwise False.
|
||
|
"""
|
||
|
return bool(_SetConsoleTextAttribute(std_handle, attributes))
|
||
|
|
||
|
|
||
|
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
|
||
|
_GetConsoleScreenBufferInfo.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),
|
||
|
]
|
||
|
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def GetConsoleScreenBufferInfo(
|
||
|
std_handle: wintypes.HANDLE,
|
||
|
) -> CONSOLE_SCREEN_BUFFER_INFO:
|
||
|
"""Retrieves information about the specified console screen buffer.
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
|
||
|
Returns:
|
||
|
CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about
|
||
|
screen size, cursor position, colour attributes, and more."""
|
||
|
console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO()
|
||
|
_GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info))
|
||
|
return console_screen_buffer_info
|
||
|
|
||
|
|
||
|
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
|
||
|
_SetConsoleCursorPosition.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
cast(Type[COORD], WindowsCoordinates),
|
||
|
]
|
||
|
_SetConsoleCursorPosition.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def SetConsoleCursorPosition(
|
||
|
std_handle: wintypes.HANDLE, coords: WindowsCoordinates
|
||
|
) -> bool:
|
||
|
"""Set the position of the cursor in the console screen
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
coords (WindowsCoordinates): The coordinates to move the cursor to.
|
||
|
|
||
|
Returns:
|
||
|
bool: True if the function succeeds, otherwise False.
|
||
|
"""
|
||
|
return bool(_SetConsoleCursorPosition(std_handle, coords))
|
||
|
|
||
|
|
||
|
_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo
|
||
|
_GetConsoleCursorInfo.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
ctypes.POINTER(CONSOLE_CURSOR_INFO),
|
||
|
]
|
||
|
_GetConsoleCursorInfo.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def GetConsoleCursorInfo(
|
||
|
std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
|
||
|
) -> bool:
|
||
|
"""Get the cursor info - used to get cursor visibility and width
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information
|
||
|
about the console's cursor.
|
||
|
|
||
|
Returns:
|
||
|
bool: True if the function succeeds, otherwise False.
|
||
|
"""
|
||
|
return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info)))
|
||
|
|
||
|
|
||
|
_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo
|
||
|
_SetConsoleCursorInfo.argtypes = [
|
||
|
wintypes.HANDLE,
|
||
|
ctypes.POINTER(CONSOLE_CURSOR_INFO),
|
||
|
]
|
||
|
_SetConsoleCursorInfo.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def SetConsoleCursorInfo(
|
||
|
std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
|
||
|
) -> bool:
|
||
|
"""Set the cursor info - used for adjusting cursor visibility and width
|
||
|
|
||
|
Args:
|
||
|
std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
|
||
|
cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.
|
||
|
|
||
|
Returns:
|
||
|
bool: True if the function succeeds, otherwise False.
|
||
|
"""
|
||
|
return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info)))
|
||
|
|
||
|
|
||
|
_SetConsoleTitle = windll.kernel32.SetConsoleTitleW
|
||
|
_SetConsoleTitle.argtypes = [wintypes.LPCWSTR]
|
||
|
_SetConsoleTitle.restype = wintypes.BOOL
|
||
|
|
||
|
|
||
|
def SetConsoleTitle(title: str) -> bool:
|
||
|
"""Sets the title of the current console window
|
||
|
|
||
|
Args:
|
||
|
title (str): The new title of the console window.
|
||
|
|
||
|
Returns:
|
||
|
bool: True if the function succeeds, otherwise False.
|
||
|
"""
|
||
|
return bool(_SetConsoleTitle(title))
|
||
|
|
||
|
|
||
|
class LegacyWindowsTerm:
|
||
|
"""This class allows interaction with the legacy Windows Console API. It should only be used in the context
|
||
|
of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,
|
||
|
the entire API should work.
|
||
|
|
||
|
Args:
|
||
|
file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.
|
||
|
"""
|
||
|
|
||
|
BRIGHT_BIT = 8
|
||
|
|
||
|
# Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers
|
||
|
ANSI_TO_WINDOWS = [
|
||
|
0, # black The Windows colours are defined in wincon.h as follows:
|
||
|
4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001
|
||
|
2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010
|
||
|
6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100
|
||
|
1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000
|
||
|
5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000
|
||
|
3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000
|
||
|
7, # white define BACKGROUND_RED 0x0040 -- 0100 0000
|
||
|
8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000
|
||
|
12, # bright red
|
||
|
10, # bright green
|
||
|
14, # bright yellow
|
||
|
9, # bright blue
|
||
|
13, # bright magenta
|
||
|
11, # bright cyan
|
||
|
15, # bright white
|
||
|
]
|
||
|
|
||
|
def __init__(self, file: "IO[str]") -> None:
|
||
|
handle = GetStdHandle(STDOUT)
|
||
|
self._handle = handle
|
||
|
default_text = GetConsoleScreenBufferInfo(handle).wAttributes
|
||
|
self._default_text = default_text
|
||
|
|
||
|
self._default_fore = default_text & 7
|
||
|
self._default_back = (default_text >> 4) & 7
|
||
|
self._default_attrs = self._default_fore | (self._default_back << 4)
|
||
|
|
||
|
self._file = file
|
||
|
self.write = file.write
|
||
|
self.flush = file.flush
|
||
|
|
||
|
@property
|
||
|
def cursor_position(self) -> WindowsCoordinates:
|
||
|
"""Returns the current position of the cursor (0-based)
|
||
|
|
||
|
Returns:
|
||
|
WindowsCoordinates: The current cursor position.
|
||
|
"""
|
||
|
coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
|
||
|
return WindowsCoordinates(row=coord.Y, col=coord.X)
|
||
|
|
||
|
@property
|
||
|
def screen_size(self) -> WindowsCoordinates:
|
||
|
"""Returns the current size of the console screen buffer, in character columns and rows
|
||
|
|
||
|
Returns:
|
||
|
WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
|
||
|
"""
|
||
|
screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
|
||
|
return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)
|
||
|
|
||
|
def write_text(self, text: str) -> None:
|
||
|
"""Write text directly to the terminal without any modification of styles
|
||
|
|
||
|
Args:
|
||
|
text (str): The text to write to the console
|
||
|
"""
|
||
|
self.write(text)
|
||
|
self.flush()
|
||
|
|
||
|
def write_styled(self, text: str, style: Style) -> None:
|
||
|
"""Write styled text to the terminal.
|
||
|
|
||
|
Args:
|
||
|
text (str): The text to write
|
||
|
style (Style): The style of the text
|
||
|
"""
|
||
|
color = style.color
|
||
|
bgcolor = style.bgcolor
|
||
|
if style.reverse:
|
||
|
color, bgcolor = bgcolor, color
|
||
|
|
||
|
if color:
|
||
|
fore = color.downgrade(ColorSystem.WINDOWS).number
|
||
|
fore = fore if fore is not None else 7 # Default to ANSI 7: White
|
||
|
if style.bold:
|
||
|
fore = fore | self.BRIGHT_BIT
|
||
|
if style.dim:
|
||
|
fore = fore & ~self.BRIGHT_BIT
|
||
|
fore = self.ANSI_TO_WINDOWS[fore]
|
||
|
else:
|
||
|
fore = self._default_fore
|
||
|
|
||
|
if bgcolor:
|
||
|
back = bgcolor.downgrade(ColorSystem.WINDOWS).number
|
||
|
back = back if back is not None else 0 # Default to ANSI 0: Black
|
||
|
back = self.ANSI_TO_WINDOWS[back]
|
||
|
else:
|
||
|
back = self._default_back
|
||
|
|
||
|
assert fore is not None
|
||
|
assert back is not None
|
||
|
|
||
|
SetConsoleTextAttribute(
|
||
|
self._handle, attributes=ctypes.c_ushort(fore | (back << 4))
|
||
|
)
|
||
|
self.write_text(text)
|
||
|
SetConsoleTextAttribute(self._handle, attributes=self._default_text)
|
||
|
|
||
|
def move_cursor_to(self, new_position: WindowsCoordinates) -> None:
|
||
|
"""Set the position of the cursor
|
||
|
|
||
|
Args:
|
||
|
new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor.
|
||
|
"""
|
||
|
if new_position.col < 0 or new_position.row < 0:
|
||
|
return
|
||
|
SetConsoleCursorPosition(self._handle, coords=new_position)
|
||
|
|
||
|
def erase_line(self) -> None:
|
||
|
"""Erase all content on the line the cursor is currently located at"""
|
||
|
screen_size = self.screen_size
|
||
|
cursor_position = self.cursor_position
|
||
|
cells_to_erase = screen_size.col
|
||
|
start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0)
|
||
|
FillConsoleOutputCharacter(
|
||
|
self._handle, " ", length=cells_to_erase, start=start_coordinates
|
||
|
)
|
||
|
FillConsoleOutputAttribute(
|
||
|
self._handle,
|
||
|
self._default_attrs,
|
||
|
length=cells_to_erase,
|
||
|
start=start_coordinates,
|
||
|
)
|
||
|
|
||
|
def erase_end_of_line(self) -> None:
|
||
|
"""Erase all content from the cursor position to the end of that line"""
|
||
|
cursor_position = self.cursor_position
|
||
|
cells_to_erase = self.screen_size.col - cursor_position.col
|
||
|
FillConsoleOutputCharacter(
|
||
|
self._handle, " ", length=cells_to_erase, start=cursor_position
|
||
|
)
|
||
|
FillConsoleOutputAttribute(
|
||
|
self._handle,
|
||
|
self._default_attrs,
|
||
|
length=cells_to_erase,
|
||
|
start=cursor_position,
|
||
|
)
|
||
|
|
||
|
def erase_start_of_line(self) -> None:
|
||
|
"""Erase all content from the cursor position to the start of that line"""
|
||
|
row, col = self.cursor_position
|
||
|
start = WindowsCoordinates(row, 0)
|
||
|
FillConsoleOutputCharacter(self._handle, " ", length=col, start=start)
|
||
|
FillConsoleOutputAttribute(
|
||
|
self._handle, self._default_attrs, length=col, start=start
|
||
|
)
|
||
|
|
||
|
def move_cursor_up(self) -> None:
|
||
|
"""Move the cursor up a single cell"""
|
||
|
cursor_position = self.cursor_position
|
||
|
SetConsoleCursorPosition(
|
||
|
self._handle,
|
||
|
coords=WindowsCoordinates(
|
||
|
row=cursor_position.row - 1, col=cursor_position.col
|
||
|
),
|
||
|
)
|
||
|
|
||
|
def move_cursor_down(self) -> None:
|
||
|
"""Move the cursor down a single cell"""
|
||
|
cursor_position = self.cursor_position
|
||
|
SetConsoleCursorPosition(
|
||
|
self._handle,
|
||
|
coords=WindowsCoordinates(
|
||
|
row=cursor_position.row + 1,
|
||
|
col=cursor_position.col,
|
||
|
),
|
||
|
)
|
||
|
|
||
|
def move_cursor_forward(self) -> None:
|
||
|
"""Move the cursor forward a single cell. Wrap to the next line if required."""
|
||
|
row, col = self.cursor_position
|
||
|
if col == self.screen_size.col - 1:
|
||
|
row += 1
|
||
|
col = 0
|
||
|
else:
|
||
|
col += 1
|
||
|
SetConsoleCursorPosition(
|
||
|
self._handle, coords=WindowsCoordinates(row=row, col=col)
|
||
|
)
|
||
|
|
||
|
def move_cursor_to_column(self, column: int) -> None:
|
||
|
"""Move cursor to the column specified by the zero-based column index, staying on the same row
|
||
|
|
||
|
Args:
|
||
|
column (int): The zero-based column index to move the cursor to.
|
||
|
"""
|
||
|
row, _ = self.cursor_position
|
||
|
SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column))
|
||
|
|
||
|
def move_cursor_backward(self) -> None:
|
||
|
"""Move the cursor backward a single cell. Wrap to the previous line if required."""
|
||
|
row, col = self.cursor_position
|
||
|
if col == 0:
|
||
|
row -= 1
|
||
|
col = self.screen_size.col - 1
|
||
|
else:
|
||
|
col -= 1
|
||
|
SetConsoleCursorPosition(
|
||
|
self._handle, coords=WindowsCoordinates(row=row, col=col)
|
||
|
)
|
||
|
|
||
|
def hide_cursor(self) -> None:
|
||
|
"""Hide the cursor"""
|
||
|
current_cursor_size = self._get_cursor_size()
|
||
|
invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0)
|
||
|
SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor)
|
||
|
|
||
|
def show_cursor(self) -> None:
|
||
|
"""Show the cursor"""
|
||
|
current_cursor_size = self._get_cursor_size()
|
||
|
visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1)
|
||
|
SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor)
|
||
|
|
||
|
def set_title(self, title: str) -> None:
|
||
|
"""Set the title of the terminal window
|
||
|
|
||
|
Args:
|
||
|
title (str): The new title of the console window
|
||
|
"""
|
||
|
assert len(title) < 255, "Console title must be less than 255 characters"
|
||
|
SetConsoleTitle(title)
|
||
|
|
||
|
def _get_cursor_size(self) -> int:
|
||
|
"""Get the percentage of the character cell that is filled by the cursor"""
|
||
|
cursor_info = CONSOLE_CURSOR_INFO()
|
||
|
GetConsoleCursorInfo(self._handle, cursor_info=cursor_info)
|
||
|
return int(cursor_info.dwSize)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
handle = GetStdHandle()
|
||
|
|
||
|
from rich.console import Console
|
||
|
|
||
|
console = Console()
|
||
|
|
||
|
term = LegacyWindowsTerm(sys.stdout)
|
||
|
term.set_title("Win32 Console Examples")
|
||
|
|
||
|
style = Style(color="black", bgcolor="red")
|
||
|
|
||
|
heading = Style.parse("black on green")
|
||
|
|
||
|
# Check colour output
|
||
|
console.rule("Checking colour output")
|
||
|
console.print("[on red]on red!")
|
||
|
console.print("[blue]blue!")
|
||
|
console.print("[yellow]yellow!")
|
||
|
console.print("[bold yellow]bold yellow!")
|
||
|
console.print("[bright_yellow]bright_yellow!")
|
||
|
console.print("[dim bright_yellow]dim bright_yellow!")
|
||
|
console.print("[italic cyan]italic cyan!")
|
||
|
console.print("[bold white on blue]bold white on blue!")
|
||
|
console.print("[reverse bold white on blue]reverse bold white on blue!")
|
||
|
console.print("[bold black on cyan]bold black on cyan!")
|
||
|
console.print("[black on green]black on green!")
|
||
|
console.print("[blue on green]blue on green!")
|
||
|
console.print("[white on black]white on black!")
|
||
|
console.print("[black on white]black on white!")
|
||
|
console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!")
|
||
|
|
||
|
# Check cursor movement
|
||
|
console.rule("Checking cursor movement")
|
||
|
console.print()
|
||
|
term.move_cursor_backward()
|
||
|
term.move_cursor_backward()
|
||
|
term.write_text("went back and wrapped to prev line")
|
||
|
time.sleep(1)
|
||
|
term.move_cursor_up()
|
||
|
term.write_text("we go up")
|
||
|
time.sleep(1)
|
||
|
term.move_cursor_down()
|
||
|
term.write_text("and down")
|
||
|
time.sleep(1)
|
||
|
term.move_cursor_up()
|
||
|
term.move_cursor_backward()
|
||
|
term.move_cursor_backward()
|
||
|
term.write_text("we went up and back 2")
|
||
|
time.sleep(1)
|
||
|
term.move_cursor_down()
|
||
|
term.move_cursor_backward()
|
||
|
term.move_cursor_backward()
|
||
|
term.write_text("we went down and back 2")
|
||
|
time.sleep(1)
|
||
|
|
||
|
# Check erasing of lines
|
||
|
term.hide_cursor()
|
||
|
console.print()
|
||
|
console.rule("Checking line erasing")
|
||
|
console.print("\n...Deleting to the start of the line...")
|
||
|
term.write_text("The red arrow shows the cursor location, and direction of erase")
|
||
|
time.sleep(1)
|
||
|
term.move_cursor_to_column(16)
|
||
|
term.write_styled("<", Style.parse("black on red"))
|
||
|
term.move_cursor_backward()
|
||
|
time.sleep(1)
|
||
|
term.erase_start_of_line()
|
||
|
time.sleep(1)
|
||
|
|
||
|
console.print("\n\n...And to the end of the line...")
|
||
|
term.write_text("The red arrow shows the cursor location, and direction of erase")
|
||
|
time.sleep(1)
|
||
|
|
||
|
term.move_cursor_to_column(16)
|
||
|
term.write_styled(">", Style.parse("black on red"))
|
||
|
time.sleep(1)
|
||
|
term.erase_end_of_line()
|
||
|
time.sleep(1)
|
||
|
|
||
|
console.print("\n\n...Now the whole line will be erased...")
|
||
|
term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan"))
|
||
|
time.sleep(1)
|
||
|
term.erase_line()
|
||
|
|
||
|
term.show_cursor()
|
||
|
print("\n")
|