1
- """Light wrapper around the win32 Console API - this module should only be imported on Windows"""
1
+ """Light wrapper around the Win32 Console API - this module should only be imported on Windows
2
+
3
+ The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
4
+ """
2
5
import ctypes
3
6
import sys
4
7
from typing import IO , Any , NamedTuple , Type , cast
14
17
15
18
from rich .color import ColorSystem
16
19
from rich .style import Style
17
- from rich .text import Text
18
20
19
21
STDOUT = - 11
20
22
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
21
23
22
24
COORD = wintypes ._COORD
23
25
24
26
27
+ class LegacyWindowsError (Exception ):
28
+ pass
29
+
30
+
25
31
class WindowsCoordinates (NamedTuple ):
26
32
"""Coordinates in the Windows Console API are (y, x), not (x, y).
27
33
This class is intended to prevent that confusion.
@@ -34,6 +40,15 @@ class WindowsCoordinates(NamedTuple):
34
40
35
41
@classmethod
36
42
def from_param (cls , value : "WindowsCoordinates" ) -> COORD :
43
+ """Converts a WindowsCoordinates into a wintypes _COORD structure.
44
+ This classmethod is internally called by ctypes to perform the conversion.
45
+
46
+ Args:
47
+ value (WindowsCoordinates): The input coordinates to convert.
48
+
49
+ Returns:
50
+ wintypes._COORD: The converted coordinates struct.
51
+ """
37
52
return COORD (value .col , value .row )
38
53
39
54
@@ -59,6 +74,14 @@ class CONSOLE_CURSOR_INFO(ctypes.Structure):
59
74
60
75
61
76
def GetStdHandle (handle : int = STDOUT ) -> wintypes .HANDLE :
77
+ """Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
78
+
79
+ Args:
80
+ handle (int): Integer identifier for the handle. Defaults to -11 (stdout).
81
+
82
+ Returns:
83
+ wintypes.HANDLE: The handle
84
+ """
62
85
return cast (wintypes .HANDLE , _GetStdHandle (handle ))
63
86
64
87
@@ -67,8 +90,26 @@ def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:
67
90
_GetConsoleMode .restype = wintypes .BOOL
68
91
69
92
70
- def GetConsoleMode (std_handle : wintypes .HANDLE , console_mode : wintypes .DWORD ) -> bool :
71
- return bool (_GetConsoleMode (std_handle , console_mode ))
93
+ def GetConsoleMode (std_handle : wintypes .HANDLE ) -> int :
94
+ """Retrieves the current input mode of a console's input buffer
95
+ or the current output mode of a console screen buffer.
96
+
97
+ Args:
98
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
99
+
100
+ Raises:
101
+ LegacyWindowsError: If any error occurs while calling the Windows console API.
102
+
103
+ Returns:
104
+ int: Value representing the current console mode as documented at
105
+ https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters
106
+ """
107
+
108
+ console_mode = wintypes .DWORD ()
109
+ success = bool (_GetConsoleMode (std_handle , console_mode ))
110
+ if not success :
111
+ raise LegacyWindowsError ("Unable to get legacy Windows Console Mode" )
112
+ return console_mode .value
72
113
73
114
74
115
_FillConsoleOutputCharacterW = windll .kernel32 .FillConsoleOutputCharacterW
@@ -88,8 +129,17 @@ def FillConsoleOutputCharacter(
88
129
length : int ,
89
130
start : WindowsCoordinates ,
90
131
) -> int :
91
- """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates."""
92
- assert len (char ) == 1
132
+ """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.
133
+
134
+ Args:
135
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
136
+ char (str): The character to write. Must be a string of length 1.
137
+ length (int): The number of times to write the character.
138
+ start (WindowsCoordinates): The coordinates to start writing at.
139
+
140
+ Returns:
141
+ int: The number of characters written.
142
+ """
93
143
character = ctypes .c_char (char .encode ())
94
144
num_characters = wintypes .DWORD (length )
95
145
num_written = wintypes .DWORD (0 )
@@ -120,6 +170,18 @@ def FillConsoleOutputAttribute(
120
170
length : int ,
121
171
start : WindowsCoordinates ,
122
172
) -> int :
173
+ """Sets the character attributes for a specified number of character cells,
174
+ beginning at the specified coordinates in a screen buffer.
175
+
176
+ Args:
177
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
178
+ attributes (int): Integer value representing the foreground and background colours of the cells.
179
+ length (int): The number of cells to set the output attribute of.
180
+ start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.
181
+
182
+ Returns:
183
+ int: The number of cells whose attributes were actually set.
184
+ """
123
185
num_cells = wintypes .DWORD (length )
124
186
style_attrs = wintypes .WORD (attributes )
125
187
num_written = wintypes .DWORD (0 )
@@ -140,6 +202,16 @@ def FillConsoleOutputAttribute(
140
202
def SetConsoleTextAttribute (
141
203
std_handle : wintypes .HANDLE , attributes : wintypes .WORD
142
204
) -> bool :
205
+ """Set the colour attributes for all text written after this function is called.
206
+
207
+ Args:
208
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
209
+ attributes (int): Integer value representing the foreground and background colours.
210
+
211
+
212
+ Returns:
213
+ bool: True if the attribute was set successfully, otherwise False.
214
+ """
143
215
return bool (_SetConsoleTextAttribute (std_handle , attributes ))
144
216
145
217
@@ -154,6 +226,14 @@ def SetConsoleTextAttribute(
154
226
def GetConsoleScreenBufferInfo (
155
227
std_handle : wintypes .HANDLE ,
156
228
) -> CONSOLE_SCREEN_BUFFER_INFO :
229
+ """Retrieves information about the specified console screen buffer.
230
+
231
+ Args:
232
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
233
+
234
+ Returns:
235
+ CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about
236
+ screen size, cursor position, colour attributes, and more."""
157
237
console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO ()
158
238
_GetConsoleScreenBufferInfo (std_handle , byref (console_screen_buffer_info ))
159
239
return console_screen_buffer_info
@@ -170,6 +250,15 @@ def GetConsoleScreenBufferInfo(
170
250
def SetConsoleCursorPosition (
171
251
std_handle : wintypes .HANDLE , coords : WindowsCoordinates
172
252
) -> bool :
253
+ """Set the position of the cursor in the console screen
254
+
255
+ Args:
256
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
257
+ coords (WindowsCoordinates): The coordinates to move the cursor to.
258
+
259
+ Returns:
260
+ bool: True if the function succeeds, otherwise False.
261
+ """
173
262
return bool (_SetConsoleCursorPosition (std_handle , coords ))
174
263
175
264
@@ -184,6 +273,15 @@ def SetConsoleCursorPosition(
184
273
def SetConsoleCursorInfo (
185
274
std_handle : wintypes .HANDLE , cursor_info : CONSOLE_CURSOR_INFO
186
275
) -> bool :
276
+ """Set the cursor info - used for adjusting cursor visibility and width
277
+
278
+ Args:
279
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
280
+ cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.
281
+
282
+ Returns:
283
+ bool: True if the function succeeds, otherwise False.
284
+ """
187
285
return bool (_SetConsoleCursorInfo (std_handle , byref (cursor_info )))
188
286
189
287
@@ -193,9 +291,51 @@ def SetConsoleCursorInfo(
193
291
194
292
195
293
def SetConsoleTitle (title : str ) -> bool :
294
+ """Sets the title of the current console window
295
+
296
+ Args:
297
+ title (str): The new title of the console window.
298
+
299
+ Returns:
300
+ bool: True if the function succeeds, otherwise False.
301
+ """
196
302
return bool (_SetConsoleTitle (title ))
197
303
198
304
305
+ _WriteConsole = windll .kernel32 .WriteConsoleW
306
+ _WriteConsole .argtypes = [
307
+ wintypes .HANDLE ,
308
+ wintypes .LPWSTR ,
309
+ wintypes .DWORD ,
310
+ wintypes .LPDWORD ,
311
+ wintypes .LPVOID ,
312
+ ]
313
+ _WriteConsole .restype = wintypes .BOOL
314
+
315
+
316
+ def WriteConsole (std_handle : wintypes .HANDLE , text : str ) -> bool :
317
+ """Write a string of text to the console, starting at the current cursor position
318
+
319
+ Args:
320
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
321
+ text (str): The text to write.
322
+
323
+ Returns:
324
+ bool: True if the function succeeds, otherwise False.
325
+ """
326
+ buffer = wintypes .LPWSTR (text )
327
+ num_chars_written = wintypes .LPDWORD ()
328
+ return bool (
329
+ _WriteConsole (
330
+ std_handle ,
331
+ buffer ,
332
+ wintypes .DWORD (len (text )),
333
+ num_chars_written ,
334
+ wintypes .LPVOID (None ),
335
+ )
336
+ )
337
+
338
+
199
339
class LegacyWindowsTerm :
200
340
"""This class allows interaction with the legacy Windows Console API. It should only be used in the context
201
341
of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,
@@ -205,6 +345,8 @@ class LegacyWindowsTerm:
205
345
file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.
206
346
"""
207
347
348
+ BRIGHT_BIT = 8
349
+
208
350
# Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers
209
351
ANSI_TO_WINDOWS = [
210
352
0 , # black The Windows colours are defined in wincon.h as follows:
@@ -225,19 +367,15 @@ class LegacyWindowsTerm:
225
367
15 , # bright white
226
368
]
227
369
228
- def __init__ (self , file : IO [str ] = sys .stdout ):
229
- self .file = file
370
+ def __init__ (self ) -> None :
230
371
handle = GetStdHandle (STDOUT )
231
372
self ._handle = handle
232
373
default_text = GetConsoleScreenBufferInfo (handle ).wAttributes
233
374
self ._default_text = default_text
234
375
235
376
self ._default_fore = default_text & 7
236
377
self ._default_back = (default_text >> 4 ) & 7
237
- self ._default_attrs = self ._default_fore + self ._default_back * 16
238
-
239
- self .write = file .write
240
- self .flush = file .flush
378
+ self ._default_attrs = self ._default_fore | (self ._default_back << 4 )
241
379
242
380
@property
243
381
def cursor_position (self ) -> WindowsCoordinates :
@@ -267,25 +405,33 @@ def write_text(self, text: str) -> None:
267
405
Args:
268
406
text (str): The text to write to the console
269
407
"""
270
- self .write (text )
271
- self .flush ()
408
+ WriteConsole (self ._handle , text )
272
409
273
410
def write_styled (self , text : str , style : Style ) -> None :
274
- """Write styled text to the terminal
411
+ """Write styled text to the terminal.
275
412
276
413
Args:
277
414
text (str): The text to write
278
415
style (Style): The style of the text
279
416
"""
280
- if style .color :
281
- fore = style .color .downgrade (ColorSystem .WINDOWS ).number
417
+ color = style .color
418
+ bgcolor = style .bgcolor
419
+ if style .reverse :
420
+ color , bgcolor = bgcolor , color
421
+
422
+ if color :
423
+ fore = color .downgrade (ColorSystem .WINDOWS ).number
282
424
fore = fore if fore is not None else 7 # Default to ANSI 7: White
425
+ if style .bold :
426
+ fore = fore | self .BRIGHT_BIT
427
+ if style .dim :
428
+ fore = fore & ~ self .BRIGHT_BIT
283
429
fore = self .ANSI_TO_WINDOWS [fore ]
284
430
else :
285
431
fore = self ._default_fore
286
432
287
- if style . bgcolor :
288
- back = style . bgcolor .downgrade (ColorSystem .WINDOWS ).number
433
+ if bgcolor :
434
+ back = bgcolor .downgrade (ColorSystem .WINDOWS ).number
289
435
back = back if back is not None else 0 # Default to ANSI 0: Black
290
436
back = self .ANSI_TO_WINDOWS [back ]
291
437
else :
@@ -295,7 +441,7 @@ def write_styled(self, text: str, style: Style) -> None:
295
441
assert back is not None
296
442
297
443
SetConsoleTextAttribute (
298
- self ._handle , attributes = ctypes .c_ushort (fore + back * 16 )
444
+ self ._handle , attributes = ctypes .c_ushort (fore | ( back << 4 ) )
299
445
)
300
446
self .write_text (text )
301
447
SetConsoleTextAttribute (self ._handle , attributes = self ._default_text )
@@ -425,17 +571,12 @@ def set_title(self, title: str) -> None:
425
571
426
572
if __name__ == "__main__" :
427
573
handle = GetStdHandle ()
428
- console_mode = wintypes .DWORD ()
429
- rv = GetConsoleMode (handle , console_mode )
430
-
431
- print (rv )
432
- print (type (rv ))
433
574
434
575
from rich .console import Console
435
576
436
577
console = Console ()
437
578
438
- term = LegacyWindowsTerm (console . file )
579
+ term = LegacyWindowsTerm ()
439
580
term .set_title ("Win32 Console Examples" )
440
581
441
582
style = Style (color = "black" , bgcolor = "red" )
@@ -444,12 +585,15 @@ def set_title(self, title: str) -> None:
444
585
445
586
# Check colour output
446
587
console .rule ("Checking colour output" )
447
- # console.print("Checking colour output", style=Style.parse("black on green"))
448
- text = Text ("Hello world!" , style = style )
449
- console .print (text )
450
- console .print ("[bold green]bold green!" )
588
+ console .print ("[on red]on red!" )
589
+ console .print ("[blue]blue!" )
590
+ console .print ("[yellow]yellow!" )
591
+ console .print ("[bold yellow]bold yellow!" )
592
+ console .print ("[bright_yellow]bright_yellow!" )
593
+ console .print ("[dim bright_yellow]dim bright_yellow!" )
451
594
console .print ("[italic cyan]italic cyan!" )
452
595
console .print ("[bold white on blue]bold white on blue!" )
596
+ console .print ("[reverse bold white on blue]reverse bold white on blue!" )
453
597
console .print ("[bold black on cyan]bold black on cyan!" )
454
598
console .print ("[black on green]black on green!" )
455
599
console .print ("[blue on green]blue on green!" )
0 commit comments