From ceef724ecd1bc24b8af6a2fed119c1b3f90d8744 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 11:36:33 +0000 Subject: [PATCH 1/7] Use Windows Console API to write text --- rich/_win32_console.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 8a6af2433..7130bd0e0 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -1,4 +1,7 @@ -"""Light wrapper around the win32 Console API - this module should only be imported on Windows""" +"""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 IO, Any, NamedTuple, Type, cast @@ -196,6 +199,31 @@ def SetConsoleTitle(title: str) -> bool: return bool(_SetConsoleTitle(title)) +_WriteConsole = windll.kernel32.WriteConsoleW +_WriteConsole.argtypes = [ + wintypes.HANDLE, + wintypes.LPWSTR, + wintypes.DWORD, + wintypes.LPDWORD, + wintypes.LPVOID, +] +_WriteConsole.restype = wintypes.BOOL + + +def WriteConsole(std_handle: wintypes.HANDLE, text: str) -> bool: + buffer = wintypes.LPWSTR(text) + num_chars_written = wintypes.LPDWORD() + return bool( + _WriteConsole( + std_handle, + buffer, + wintypes.DWORD(len(text)), + num_chars_written, + wintypes.LPVOID(None), + ) + ) + + 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, @@ -267,8 +295,9 @@ def write_text(self, text: str) -> None: Args: text (str): The text to write to the console """ - self.write(text) - self.flush() + WriteConsole(self._handle, text) + # self.write(text) + # self.flush() def write_styled(self, text: str, style: Style) -> None: """Write styled text to the terminal From 1122501d6205742fe2883b140d69ff687b70d43c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 13:54:38 +0000 Subject: [PATCH 2/7] Use WriteConsoleW instead of file.write(...) in LegacyWindowsTerm --- rich/_win32_console.py | 2 -- tests/test_win32_console.py | 24 ++++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 7130bd0e0..a4a760090 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -296,8 +296,6 @@ def write_text(self, text: str) -> None: text (str): The text to write to the console """ WriteConsole(self._handle, text) - # self.write(text) - # self.flush() def write_styled(self, text: str, style: Style) -> None: """Write styled text to the terminal diff --git a/tests/test_win32_console.py b/tests/test_win32_console.py index d3cc28050..1576561e6 100644 --- a/tests/test_win32_console.py +++ b/tests/test_win32_console.py @@ -55,35 +55,39 @@ def test_screen_size(_): row=SCREEN_HEIGHT, col=SCREEN_WIDTH ) + @patch.object(_win32_console, "WriteConsole", return_value=True) @patch.object( _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo ) - def test_write_text(_): - f = StringIO() + def test_write_text(_, WriteConsole, win32_handle): text = "Hello, world!" - term = LegacyWindowsTerm(file=f) + term = LegacyWindowsTerm() term.write_text(text) - assert f.getvalue() == text + WriteConsole.assert_called_once_with(win32_handle, text) + @patch.object(_win32_console, "WriteConsole", return_value=True) @patch.object(_win32_console, "SetConsoleTextAttribute") @patch.object( _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo ) - def test_write_styled(_, SetConsoleTextAttribute, win32_handle): - f = StringIO() + def test_write_styled(_, SetConsoleTextAttribute, WriteConsole, win32_handle): style = Style.parse("black on red") text = "Hello, world!" - term = LegacyWindowsTerm(file=f) + term = LegacyWindowsTerm() term.write_styled(text, style) - call_args = SetConsoleTextAttribute.call_args_list + # Check that we've called the Console API to write the text + call_args = WriteConsole.call_args_list + assert len(call_args) == 1 + args, _ = call_args[0] + assert args == (win32_handle, text) - assert f.getvalue() == text # Ensure we set the text attributes and then reset them after writing styled text - + call_args = SetConsoleTextAttribute.call_args_list + assert len(call_args) == 2 first_args, first_kwargs = call_args[0] second_args, second_kwargs = call_args[1] From d87498bf3600dadc294c0b6058683dba54f5fb2c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 14:23:55 +0000 Subject: [PATCH 3/7] Use bitwise operators in LegacyWindowsTerm, fix formatting --- rich/_win32_console.py | 12 ++++-------- rich/console.py | 4 +--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index a4a760090..cf936c66e 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -253,8 +253,7 @@ class LegacyWindowsTerm: 15, # bright white ] - def __init__(self, file: IO[str] = sys.stdout): - self.file = file + def __init__(self) -> None: handle = GetStdHandle(STDOUT) self._handle = handle default_text = GetConsoleScreenBufferInfo(handle).wAttributes @@ -262,10 +261,7 @@ def __init__(self, file: IO[str] = sys.stdout): self._default_fore = default_text & 7 self._default_back = (default_text >> 4) & 7 - self._default_attrs = self._default_fore + self._default_back * 16 - - self.write = file.write - self.flush = file.flush + self._default_attrs = self._default_fore | (self._default_back << 4) @property def cursor_position(self) -> WindowsCoordinates: @@ -322,7 +318,7 @@ def write_styled(self, text: str, style: Style) -> None: assert back is not None SetConsoleTextAttribute( - self._handle, attributes=ctypes.c_ushort(fore + back * 16) + self._handle, attributes=ctypes.c_ushort(fore | (back << 4)) ) self.write_text(text) SetConsoleTextAttribute(self._handle, attributes=self._default_text) @@ -462,7 +458,7 @@ def set_title(self, title: str) -> None: console = Console() - term = LegacyWindowsTerm(console.file) + term = LegacyWindowsTerm() term.set_title("Win32 Console Examples") style = Style(color="black", bgcolor="red") diff --git a/rich/console.py b/rich/console.py index 15a787208..9275a1e7b 100644 --- a/rich/console.py +++ b/rich/console.py @@ -1921,9 +1921,7 @@ def _check_buffer(self) -> None: from rich._win32_console import LegacyWindowsTerm from rich._windows_renderer import legacy_windows_render - legacy_windows_render( - self._buffer[:], LegacyWindowsTerm(self.file) - ) + legacy_windows_render(self._buffer[:], LegacyWindowsTerm()) output_capture_enabled = bool(self._buffer_index) if not legacy_windows_stdout or output_capture_enabled: From 5a051f9f8d4e48da32b610b1fe540bc16e721484 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 14:31:35 +0000 Subject: [PATCH 4/7] Make GetConsoleMode Windows Console wrapper more Pythonic --- rich/_win32_console.py | 8 ++++++-- rich/_windows.py | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index cf936c66e..05f81b1b9 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -70,8 +70,12 @@ def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE: _GetConsoleMode.restype = wintypes.BOOL -def GetConsoleMode(std_handle: wintypes.HANDLE, console_mode: wintypes.DWORD) -> bool: - return bool(_GetConsoleMode(std_handle, console_mode)) +def GetConsoleMode(std_handle: wintypes.HANDLE) -> int: + console_mode = wintypes.DWORD() + success = bool(_GetConsoleMode(std_handle, console_mode)) + if not success: + raise WindowsError("Unable to get legacy Windows Console Mode") + return console_mode.value _FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW diff --git a/rich/_windows.py b/rich/_windows.py index a330cae77..de50009f7 100644 --- a/rich/_windows.py +++ b/rich/_windows.py @@ -44,9 +44,8 @@ def get_windows_console_features() -> WindowsConsoleFeatures: WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. """ handle = GetStdHandle() - console_mode = wintypes.DWORD() - result = GetConsoleMode(handle, console_mode) - vt = bool(result and console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + console_mode = GetConsoleMode(handle) + vt = bool(console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) truecolor = False if vt: win_version = sys.getwindowsversion() From 8f738c7f2af3f2757c47d4c3235f10133b4eb5c3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 15:13:25 +0000 Subject: [PATCH 5/7] Support reverse, bold (bright), and dim --- rich/_win32_console.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 05f81b1b9..efaf73271 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -237,6 +237,8 @@ class LegacyWindowsTerm: 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: @@ -298,21 +300,30 @@ def write_text(self, text: str) -> None: WriteConsole(self._handle, text) def write_styled(self, text: str, style: Style) -> None: - """Write styled text to the terminal + """Write styled text to the terminal. Args: text (str): The text to write style (Style): The style of the text """ - if style.color: - fore = style.color.downgrade(ColorSystem.WINDOWS).number + 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 style.bgcolor: - back = style.bgcolor.downgrade(ColorSystem.WINDOWS).number + 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: @@ -452,11 +463,6 @@ def set_title(self, title: str) -> None: if __name__ == "__main__": handle = GetStdHandle() - console_mode = wintypes.DWORD() - rv = GetConsoleMode(handle, console_mode) - - print(rv) - print(type(rv)) from rich.console import Console @@ -474,9 +480,13 @@ def set_title(self, title: str) -> None: # console.print("Checking colour output", style=Style.parse("black on green")) text = Text("Hello world!", style=style) console.print(text) - console.print("[bold green]bold green!") + 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!") From ca3e966de90b41df8ac215481044df1beea9d3bd Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 16:14:01 +0000 Subject: [PATCH 6/7] Handle legacy windows error --- rich/_win32_console.py | 12 +++-- rich/_windows.py | 10 +++- tests/test_win32_console.py | 105 ++++++++++++++++++++++++++++++++++-- 3 files changed, 117 insertions(+), 10 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index efaf73271..265b75266 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -17,7 +17,6 @@ from rich.color import ColorSystem from rich.style import Style -from rich.text import Text STDOUT = -11 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 @@ -25,6 +24,10 @@ 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. @@ -74,7 +77,7 @@ def GetConsoleMode(std_handle: wintypes.HANDLE) -> int: console_mode = wintypes.DWORD() success = bool(_GetConsoleMode(std_handle, console_mode)) if not success: - raise WindowsError("Unable to get legacy Windows Console Mode") + raise LegacyWindowsError("Unable to get legacy Windows Console Mode") return console_mode.value @@ -477,9 +480,8 @@ def set_title(self, title: str) -> None: # Check colour output console.rule("Checking colour output") - # console.print("Checking colour output", style=Style.parse("black on green")) - text = Text("Hello world!", style=style) - console.print(text) + 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!") diff --git a/rich/_windows.py b/rich/_windows.py index de50009f7..54d834e62 100644 --- a/rich/_windows.py +++ b/rich/_windows.py @@ -26,6 +26,7 @@ class WindowsConsoleFeatures: ENABLE_VIRTUAL_TERMINAL_PROCESSING, GetConsoleMode, GetStdHandle, + LegacyWindowsError, ) except (AttributeError, ImportError, ValueError): @@ -44,8 +45,13 @@ def get_windows_console_features() -> WindowsConsoleFeatures: WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. """ handle = GetStdHandle() - console_mode = GetConsoleMode(handle) - vt = bool(console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + try: + console_mode = GetConsoleMode(handle) + success = True + except LegacyWindowsError: + console_mode = 0 + success = False + vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) truecolor = False if vt: win_version = sys.getwindowsversion() diff --git a/tests/test_win32_console.py b/tests/test_win32_console.py index 1576561e6..4523600d6 100644 --- a/tests/test_win32_console.py +++ b/tests/test_win32_console.py @@ -1,15 +1,13 @@ import dataclasses import sys -from io import StringIO from unittest import mock -from unittest.mock import call, patch +from unittest.mock import patch import pytest from rich.style import Style if sys.platform == "win32": - from rich import _win32_console from rich._win32_console import COORD, LegacyWindowsTerm, WindowsCoordinates @@ -96,6 +94,107 @@ def test_write_styled(_, SetConsoleTextAttribute, WriteConsole, win32_handle): assert second_args == (win32_handle,) assert second_kwargs["attributes"] == DEFAULT_STYLE_ATTRIBUTE + @patch.object(_win32_console, "WriteConsole", return_value=True) + @patch.object(_win32_console, "SetConsoleTextAttribute") + @patch.object( + _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo + ) + def test_write_styled_bold(_, SetConsoleTextAttribute, __, win32_handle): + style = Style.parse("bold black on red") + text = "Hello, world!" + term = LegacyWindowsTerm() + + term.write_styled(text, style) + + call_args = SetConsoleTextAttribute.call_args_list + first_args, first_kwargs = call_args[0] + + expected_attr = 64 + 8 # 64 for red bg, +8 for bright black + assert first_args == (win32_handle,) + assert first_kwargs["attributes"].value == expected_attr + + @patch.object(_win32_console, "WriteConsole", return_value=True) + @patch.object(_win32_console, "SetConsoleTextAttribute") + @patch.object( + _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo + ) + def test_write_styled_reverse(_, SetConsoleTextAttribute, __, win32_handle): + style = Style.parse("reverse red on blue") + text = "Hello, world!" + term = LegacyWindowsTerm() + + term.write_styled(text, style) + + call_args = SetConsoleTextAttribute.call_args_list + first_args, first_kwargs = call_args[0] + + expected_attr = 64 + 1 # 64 for red bg (after reverse), +1 for blue fg + assert first_args == (win32_handle,) + assert first_kwargs["attributes"].value == expected_attr + + @patch.object(_win32_console, "WriteConsole", return_value=True) + @patch.object(_win32_console, "SetConsoleTextAttribute") + @patch.object( + _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo + ) + def test_write_styled_reverse(_, SetConsoleTextAttribute, __, win32_handle): + style = Style.parse("dim bright_red on blue") + text = "Hello, world!" + term = LegacyWindowsTerm() + + term.write_styled(text, style) + + call_args = SetConsoleTextAttribute.call_args_list + first_args, first_kwargs = call_args[0] + + expected_attr = 4 + 16 # 4 for red text (after dim), +16 for blue bg + assert first_args == (win32_handle,) + assert first_kwargs["attributes"].value == expected_attr + + @patch.object(_win32_console, "WriteConsole", return_value=True) + @patch.object(_win32_console, "SetConsoleTextAttribute") + @patch.object( + _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo + ) + def test_write_styled_no_foreground_color( + _, SetConsoleTextAttribute, __, win32_handle + ): + style = Style.parse("on blue") + text = "Hello, world!" + term = LegacyWindowsTerm() + + term.write_styled(text, style) + + call_args = SetConsoleTextAttribute.call_args_list + first_args, first_kwargs = call_args[0] + + expected_attr = 16 | term._default_fore # 16 for blue bg, plus default fg color + assert first_args == (win32_handle,) + assert first_kwargs["attributes"].value == expected_attr + + @patch.object(_win32_console, "WriteConsole", return_value=True) + @patch.object(_win32_console, "SetConsoleTextAttribute") + @patch.object( + _win32_console, "GetConsoleScreenBufferInfo", return_value=StubScreenBufferInfo + ) + def test_write_styled_no_background_color( + _, SetConsoleTextAttribute, __, win32_handle + ): + style = Style.parse("blue") + text = "Hello, world!" + term = LegacyWindowsTerm() + + term.write_styled(text, style) + + call_args = SetConsoleTextAttribute.call_args_list + first_args, first_kwargs = call_args[0] + + expected_attr = ( + 16 | term._default_back + ) # 16 for blue foreground, plus default bg color + assert first_args == (win32_handle,) + assert first_kwargs["attributes"].value == expected_attr + @patch.object(_win32_console, "FillConsoleOutputCharacter", return_value=None) @patch.object(_win32_console, "FillConsoleOutputAttribute", return_value=None) @patch.object( From 81c4dc411523f15d61c999262271d3feb3c2b25e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 3 Mar 2022 17:13:45 +0000 Subject: [PATCH 7/7] Add docstrings to Windows console wrapper functions --- rich/_win32_console.py | 109 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 265b75266..c88287564 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -40,6 +40,15 @@ class WindowsCoordinates(NamedTuple): @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) @@ -65,6 +74,14 @@ class CONSOLE_CURSOR_INFO(ctypes.Structure): 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)) @@ -74,6 +91,20 @@ def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE: 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: @@ -98,8 +129,17 @@ def FillConsoleOutputCharacter( length: int, start: WindowsCoordinates, ) -> int: - """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.""" - assert len(char) == 1 + """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) @@ -130,6 +170,18 @@ def FillConsoleOutputAttribute( 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) @@ -150,6 +202,16 @@ def FillConsoleOutputAttribute( 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)) @@ -164,6 +226,14 @@ def SetConsoleTextAttribute( 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 @@ -180,6 +250,15 @@ def GetConsoleScreenBufferInfo( 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)) @@ -194,6 +273,15 @@ def SetConsoleCursorPosition( 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))) @@ -203,6 +291,14 @@ def SetConsoleCursorInfo( 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)) @@ -218,6 +314,15 @@ def SetConsoleTitle(title: str) -> bool: def WriteConsole(std_handle: wintypes.HANDLE, text: str) -> bool: + """Write a string of text to the console, starting at the current cursor position + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + text (str): The text to write. + + Returns: + bool: True if the function succeeds, otherwise False. + """ buffer = wintypes.LPWSTR(text) num_chars_written = wintypes.LPDWORD() return bool(