5 Emulate a vt100 terminal in cmd.exe
7 By wrapping sys.stdout / sys.stderr with Ansiterm,
8 the vt100 escape characters will be interpreted and
9 the equivalent actions will be performed with Win32
15 from waflib
import Utils
17 wlock
= Utils
.threading
.Lock()
20 from ctypes
import Structure
, windll
, c_short
, c_ushort
, c_ulong
, c_int
, byref
, c_wchar
, POINTER
, c_long
23 class AnsiTerm(object):
24 def __init__(self
, stream
):
27 self
.errors
= self
.stream
.errors
28 except AttributeError:
30 self
.encoding
= self
.stream
.encoding
35 self
.stream
.write(txt
)
41 return self
.stream
.fileno()
47 return self
.stream
.isatty()
50 class COORD(Structure
):
51 _fields_
= [("X", c_short
), ("Y", c_short
)]
53 class SMALL_RECT(Structure
):
54 _fields_
= [("Left", c_short
), ("Top", c_short
), ("Right", c_short
), ("Bottom", c_short
)]
56 class CONSOLE_SCREEN_BUFFER_INFO(Structure
):
57 _fields_
= [("Size", COORD
), ("CursorPosition", COORD
), ("Attributes", c_ushort
), ("Window", SMALL_RECT
), ("MaximumWindowSize", COORD
)]
59 class CONSOLE_CURSOR_INFO(Structure
):
60 _fields_
= [('dwSize', c_ulong
), ('bVisible', c_int
)]
67 to_int
= lambda number
, default
: number
and int(number
) or default
69 STD_OUTPUT_HANDLE
= -11
70 STD_ERROR_HANDLE
= -12
72 windll
.kernel32
.GetStdHandle
.argtypes
= [c_ulong
]
73 windll
.kernel32
.GetStdHandle
.restype
= c_ulong
74 windll
.kernel32
.GetConsoleScreenBufferInfo
.argtypes
= [c_ulong
, POINTER(CONSOLE_SCREEN_BUFFER_INFO
)]
75 windll
.kernel32
.GetConsoleScreenBufferInfo
.restype
= c_long
76 windll
.kernel32
.SetConsoleTextAttribute
.argtypes
= [c_ulong
, c_ushort
]
77 windll
.kernel32
.SetConsoleTextAttribute
.restype
= c_long
78 windll
.kernel32
.FillConsoleOutputCharacterW
.argtypes
= [c_ulong
, c_wchar
, c_ulong
, POINTER(COORD
), POINTER(c_ulong
)]
79 windll
.kernel32
.FillConsoleOutputCharacterW
.restype
= c_long
80 windll
.kernel32
.FillConsoleOutputAttribute
.argtypes
= [c_ulong
, c_ushort
, c_ulong
, POINTER(COORD
), POINTER(c_ulong
) ]
81 windll
.kernel32
.FillConsoleOutputAttribute
.restype
= c_long
82 windll
.kernel32
.SetConsoleCursorPosition
.argtypes
= [c_ulong
, POINTER(COORD
) ]
83 windll
.kernel32
.SetConsoleCursorPosition
.restype
= c_long
84 windll
.kernel32
.SetConsoleCursorInfo
.argtypes
= [c_ulong
, POINTER(CONSOLE_CURSOR_INFO
)]
85 windll
.kernel32
.SetConsoleCursorInfo
.restype
= c_long
87 class AnsiTerm(object):
89 emulate a vt100 terminal in cmd.exe
91 def __init__(self
, s
):
94 self
.errors
= s
.errors
95 except AttributeError:
97 self
.encoding
= s
.encoding
98 self
.cursor_history
= []
100 handle
= (s
.fileno() == 2) and STD_ERROR_HANDLE
or STD_OUTPUT_HANDLE
101 self
.hconsole
= windll
.kernel32
.GetStdHandle(handle
)
103 self
._sbinfo
= CONSOLE_SCREEN_BUFFER_INFO()
105 self
._csinfo
= CONSOLE_CURSOR_INFO()
106 windll
.kernel32
.GetConsoleCursorInfo(self
.hconsole
, byref(self
._csinfo
))
108 # just to double check that the console is usable
109 self
._orig
_sbinfo
= CONSOLE_SCREEN_BUFFER_INFO()
110 r
= windll
.kernel32
.GetConsoleScreenBufferInfo(self
.hconsole
, byref(self
._orig
_sbinfo
))
111 self
._isatty
= r
== 1
113 def screen_buffer_info(self
):
115 Updates self._sbinfo and returns it
117 windll
.kernel32
.GetConsoleScreenBufferInfo(self
.hconsole
, byref(self
._sbinfo
))
120 def clear_line(self
, param
):
121 mode
= param
and int(param
) or 0
122 sbinfo
= self
.screen_buffer_info()
123 if mode
== 1: # Clear from beginning of line to cursor position
124 line_start
= COORD(0, sbinfo
.CursorPosition
.Y
)
125 line_length
= sbinfo
.Size
.X
126 elif mode
== 2: # Clear entire line
127 line_start
= COORD(sbinfo
.CursorPosition
.X
, sbinfo
.CursorPosition
.Y
)
128 line_length
= sbinfo
.Size
.X
- sbinfo
.CursorPosition
.X
129 else: # Clear from cursor position to end of line
130 line_start
= sbinfo
.CursorPosition
131 line_length
= sbinfo
.Size
.X
- sbinfo
.CursorPosition
.X
132 chars_written
= c_ulong()
133 windll
.kernel32
.FillConsoleOutputCharacterW(self
.hconsole
, c_wchar(' '), line_length
, line_start
, byref(chars_written
))
134 windll
.kernel32
.FillConsoleOutputAttribute(self
.hconsole
, sbinfo
.Attributes
, line_length
, line_start
, byref(chars_written
))
136 def clear_screen(self
, param
):
137 mode
= to_int(param
, 0)
138 sbinfo
= self
.screen_buffer_info()
139 if mode
== 1: # Clear from beginning of screen to cursor position
140 clear_start
= COORD(0, 0)
141 clear_length
= sbinfo
.CursorPosition
.X
* sbinfo
.CursorPosition
.Y
142 elif mode
== 2: # Clear entire screen and return cursor to home
143 clear_start
= COORD(0, 0)
144 clear_length
= sbinfo
.Size
.X
* sbinfo
.Size
.Y
145 windll
.kernel32
.SetConsoleCursorPosition(self
.hconsole
, clear_start
)
146 else: # Clear from cursor position to end of screen
147 clear_start
= sbinfo
.CursorPosition
148 clear_length
= ((sbinfo
.Size
.X
- sbinfo
.CursorPosition
.X
) + sbinfo
.Size
.X
* (sbinfo
.Size
.Y
- sbinfo
.CursorPosition
.Y
))
149 chars_written
= c_ulong()
150 windll
.kernel32
.FillConsoleOutputCharacterW(self
.hconsole
, c_wchar(' '), clear_length
, clear_start
, byref(chars_written
))
151 windll
.kernel32
.FillConsoleOutputAttribute(self
.hconsole
, sbinfo
.Attributes
, clear_length
, clear_start
, byref(chars_written
))
153 def push_cursor(self
, param
):
154 sbinfo
= self
.screen_buffer_info()
155 self
.cursor_history
.append(sbinfo
.CursorPosition
)
157 def pop_cursor(self
, param
):
158 if self
.cursor_history
:
159 old_pos
= self
.cursor_history
.pop()
160 windll
.kernel32
.SetConsoleCursorPosition(self
.hconsole
, old_pos
)
162 def set_cursor(self
, param
):
163 y
, sep
, x
= param
.partition(';')
166 sbinfo
= self
.screen_buffer_info()
168 min(max(0, x
), sbinfo
.Size
.X
),
169 min(max(0, y
), sbinfo
.Size
.Y
)
171 windll
.kernel32
.SetConsoleCursorPosition(self
.hconsole
, new_pos
)
173 def set_column(self
, param
):
174 x
= to_int(param
, 1) - 1
175 sbinfo
= self
.screen_buffer_info()
177 min(max(0, x
), sbinfo
.Size
.X
),
178 sbinfo
.CursorPosition
.Y
180 windll
.kernel32
.SetConsoleCursorPosition(self
.hconsole
, new_pos
)
182 def move_cursor(self
, x_offset
=0, y_offset
=0):
183 sbinfo
= self
.screen_buffer_info()
185 min(max(0, sbinfo
.CursorPosition
.X
+ x_offset
), sbinfo
.Size
.X
),
186 min(max(0, sbinfo
.CursorPosition
.Y
+ y_offset
), sbinfo
.Size
.Y
)
188 windll
.kernel32
.SetConsoleCursorPosition(self
.hconsole
, new_pos
)
190 def move_up(self
, param
):
191 self
.move_cursor(y_offset
= -to_int(param
, 1))
193 def move_down(self
, param
):
194 self
.move_cursor(y_offset
= to_int(param
, 1))
196 def move_left(self
, param
):
197 self
.move_cursor(x_offset
= -to_int(param
, 1))
199 def move_right(self
, param
):
200 self
.move_cursor(x_offset
= to_int(param
, 1))
202 def next_line(self
, param
):
203 sbinfo
= self
.screen_buffer_info()
205 x_offset
= -sbinfo
.CursorPosition
.X
,
206 y_offset
= to_int(param
, 1)
209 def prev_line(self
, param
):
210 sbinfo
= self
.screen_buffer_info()
212 x_offset
= -sbinfo
.CursorPosition
.X
,
213 y_offset
= -to_int(param
, 1)
216 def rgb2bgr(self
, c
):
217 return ((c
&1) << 2) |
(c
&2) |
((c
&4)>>2)
219 def set_color(self
, param
):
220 cols
= param
.split(';')
221 sbinfo
= self
.screen_buffer_info()
222 attr
= sbinfo
.Attributes
225 if 29 < c
< 38: # fgcolor
226 attr
= (attr
& 0xfff0) | self
.rgb2bgr(c
- 30)
227 elif 39 < c
< 48: # bgcolor
228 attr
= (attr
& 0xff0f) |
(self
.rgb2bgr(c
- 40) << 4)
230 attr
= self
._orig
_sbinfo
.Attributes
231 elif c
== 1: # strong
233 elif c
== 4: # blink not available -> bg intensity
235 elif c
== 7: # negative
236 attr
= (attr
& 0xff88) |
((attr
& 0x70) >> 4) |
((attr
& 0x07) << 4)
238 windll
.kernel32
.SetConsoleTextAttribute(self
.hconsole
, attr
)
240 def show_cursor(self
,param
):
241 self
._csinfo
.bVisible
= 1
242 windll
.kernel32
.SetConsoleCursorInfo(self
.hconsole
, byref(self
._csinfo
))
244 def hide_cursor(self
,param
):
245 self
._csinfo
.bVisible
= 0
246 windll
.kernel32
.SetConsoleCursorInfo(self
.hconsole
, byref(self
._csinfo
))
248 ansi_command_table
= {
266 # Match either the escape sequence or text not containing escape sequence
267 ansi_tokens
= re
.compile(r
'(?:\x1b\[([0-9?;]*)([a-zA-Z])|([^\x1b]+))')
268 def write(self
, text
):
272 for param
, cmd
, txt
in self
.ansi_tokens
.findall(text
):
274 cmd_func
= self
.ansi_command_table
.get(cmd
)
276 cmd_func(self
, param
)
278 self
.writeconsole(txt
)
280 # no support for colors in the console, just output the text:
281 # eclipse or msys may be able to interpret the escape sequences
282 self
.stream
.write(text
)
286 def writeconsole(self
, txt
):
287 chars_written
= c_ulong()
288 writeconsole
= windll
.kernel32
.WriteConsoleA
289 if isinstance(txt
, _type
):
290 writeconsole
= windll
.kernel32
.WriteConsoleW
292 # MSDN says that there is a shared buffer of 64 KB for the console
293 # writes. Attempt to not get ERROR_NOT_ENOUGH_MEMORY, see waf issue #746
298 doing
= min(chunk
, todo
)
299 buf
= txt
[done
:done
+doing
]
300 r
= writeconsole(self
.hconsole
, buf
, doing
, byref(chars_written
), None)
309 return self
.stream
.fileno()
317 if sys
.stdout
.isatty() or sys
.stderr
.isatty():
318 handle
= sys
.stdout
.isatty() and STD_OUTPUT_HANDLE
or STD_ERROR_HANDLE
319 console
= windll
.kernel32
.GetStdHandle(handle
)
320 sbinfo
= CONSOLE_SCREEN_BUFFER_INFO()
322 windll
.kernel32
.GetConsoleScreenBufferInfo(console
, byref(sbinfo
))
323 # Issue 1401 - the progress bar cannot reach the last character
324 return sbinfo
.Size
.X
- 1
328 import struct
, fcntl
, termios
332 if (sys
.stdout
.isatty() or sys
.stderr
.isatty()) and os
.environ
.get('TERM', '') not in ('dumb', 'emacs'):
333 FD
= sys
.stdout
.isatty() and sys
.stdout
.fileno() or sys
.stderr
.fileno()
335 return struct
.unpack("HHHH", fcntl
.ioctl(FD
, termios
.TIOCGWINSZ
, struct
.pack("HHHH", 0, 0, 0, 0)))[1]
338 except Exception as e
: