1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
# ruff: noqa: N801
"""
Windows specific code for the terminal.
Note that the naming convention here is non-pythonic because we are
matching the Windows API naming.
"""
from __future__ import annotations
import ctypes
import enum
from ctypes.wintypes import (
BOOL as _BOOL,
CHAR as _CHAR,
DWORD as _DWORD,
HANDLE as _HANDLE,
SHORT as _SHORT,
UINT as _UINT,
WCHAR as _WCHAR,
WORD as _WORD,
)
_kernel32 = ctypes.windll.Kernel32 # type: ignore
_STD_INPUT_HANDLE = _DWORD(-10)
_STD_OUTPUT_HANDLE = _DWORD(-11)
class WindowsConsoleModeFlags(enum.IntFlag):
ENABLE_ECHO_INPUT = 0x0004
ENABLE_EXTENDED_FLAGS = 0x0080
ENABLE_INSERT_MODE = 0x0020
ENABLE_LINE_INPUT = 0x0002
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_WINDOW_INPUT = 0x0008
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
def __str__(self) -> str:
return f'{self.name} (0x{self.value:04X})'
_GetConsoleMode = _kernel32.GetConsoleMode
_GetConsoleMode.restype = _BOOL
_SetConsoleMode = _kernel32.SetConsoleMode
_SetConsoleMode.restype = _BOOL
_GetStdHandle = _kernel32.GetStdHandle
_GetStdHandle.restype = _HANDLE
_ReadConsoleInput = _kernel32.ReadConsoleInputA
_ReadConsoleInput.restype = _BOOL
_h_console_input = _GetStdHandle(_STD_INPUT_HANDLE)
_input_mode = _DWORD()
_GetConsoleMode(_HANDLE(_h_console_input), ctypes.byref(_input_mode))
_h_console_output = _GetStdHandle(_STD_OUTPUT_HANDLE)
_output_mode = _DWORD()
_GetConsoleMode(_HANDLE(_h_console_output), ctypes.byref(_output_mode))
class _COORD(ctypes.Structure):
_fields_ = (('X', _SHORT), ('Y', _SHORT))
class _FOCUS_EVENT_RECORD(ctypes.Structure):
_fields_ = (('bSetFocus', _BOOL),)
class _KEY_EVENT_RECORD(ctypes.Structure):
class _uchar(ctypes.Union):
_fields_ = (('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR))
_fields_ = (
('bKeyDown', _BOOL),
('wRepeatCount', _WORD),
('wVirtualKeyCode', _WORD),
('wVirtualScanCode', _WORD),
('uChar', _uchar),
('dwControlKeyState', _DWORD),
)
class _MENU_EVENT_RECORD(ctypes.Structure):
_fields_ = (('dwCommandId', _UINT),)
class _MOUSE_EVENT_RECORD(ctypes.Structure):
_fields_ = (
('dwMousePosition', _COORD),
('dwButtonState', _DWORD),
('dwControlKeyState', _DWORD),
('dwEventFlags', _DWORD),
)
class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure):
_fields_ = (('dwSize', _COORD),)
class _INPUT_RECORD(ctypes.Structure):
class _Event(ctypes.Union):
_fields_ = (
('KeyEvent', _KEY_EVENT_RECORD),
('MouseEvent', _MOUSE_EVENT_RECORD),
('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD),
('MenuEvent', _MENU_EVENT_RECORD),
('FocusEvent', _FOCUS_EVENT_RECORD),
)
_fields_ = (('EventType', _WORD), ('Event', _Event))
def reset_console_mode() -> None:
_SetConsoleMode(_HANDLE(_h_console_input), _DWORD(_input_mode.value))
_SetConsoleMode(_HANDLE(_h_console_output), _DWORD(_output_mode.value))
def set_console_mode() -> bool:
mode = (
_input_mode.value
| WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_INPUT
)
_SetConsoleMode(_HANDLE(_h_console_input), _DWORD(mode))
mode = (
_output_mode.value
| WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT
| WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_PROCESSING
)
return bool(_SetConsoleMode(_HANDLE(_h_console_output), _DWORD(mode)))
def get_console_mode() -> int:
return _input_mode.value
def set_text_color(color) -> None:
_kernel32.SetConsoleTextAttribute(_h_console_output, color)
def print_color(text, color) -> None:
set_text_color(color)
print(text) # noqa: T201
set_text_color(7) # Reset to default color, grey
def getch():
lp_buffer = (_INPUT_RECORD * 2)()
n_length = _DWORD(2)
lp_number_of_events_read = _DWORD()
_ReadConsoleInput(
_HANDLE(_h_console_input),
lp_buffer,
n_length,
ctypes.byref(lp_number_of_events_read),
)
char = lp_buffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii')
if char == '\x00':
return None
return char
|