1#!/usr/bin/env python 2# encoding: utf-8 3 4""" 5Emulate a vt100 terminal in cmd.exe 6 7By wrapping sys.stdout / sys.stderr with Ansiterm, 8the vt100 escape characters will be interpreted and 9the equivalent actions will be performed with Win32 10console commands. 11 12""" 13 14import os, re, sys 15from waflib import Utils 16 17wlock = Utils.threading.Lock() 18 19try: 20 from ctypes import Structure, windll, c_short, c_ushort, c_ulong, c_int, byref, c_wchar, POINTER, c_long 21except ImportError: 22 23 class AnsiTerm(object): 24 def __init__(self, stream): 25 self.stream = stream 26 try: 27 self.errors = self.stream.errors 28 except AttributeError: 29 pass # python 2.5 30 self.encoding = self.stream.encoding 31 32 def write(self, txt): 33 try: 34 wlock.acquire() 35 self.stream.write(txt) 36 self.stream.flush() 37 finally: 38 wlock.release() 39 40 def fileno(self): 41 return self.stream.fileno() 42 43 def flush(self): 44 self.stream.flush() 45 46 def isatty(self): 47 return self.stream.isatty() 48else: 49 50 class COORD(Structure): 51 _fields_ = [("X", c_short), ("Y", c_short)] 52 53 class SMALL_RECT(Structure): 54 _fields_ = [("Left", c_short), ("Top", c_short), ("Right", c_short), ("Bottom", c_short)] 55 56 class CONSOLE_SCREEN_BUFFER_INFO(Structure): 57 _fields_ = [("Size", COORD), ("CursorPosition", COORD), ("Attributes", c_ushort), ("Window", SMALL_RECT), ("MaximumWindowSize", COORD)] 58 59 class CONSOLE_CURSOR_INFO(Structure): 60 _fields_ = [('dwSize', c_ulong), ('bVisible', c_int)] 61 62 try: 63 _type = unicode 64 except NameError: 65 _type = str 66 67 to_int = lambda number, default: number and int(number) or default 68 69 STD_OUTPUT_HANDLE = -11 70 STD_ERROR_HANDLE = -12 71 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 86 87 class AnsiTerm(object): 88 """ 89 emulate a vt100 terminal in cmd.exe 90 """ 91 def __init__(self, s): 92 self.stream = s 93 try: 94 self.errors = s.errors 95 except AttributeError: 96 pass # python2.5 97 self.encoding = s.encoding 98 self.cursor_history = [] 99 100 handle = (s.fileno() == 2) and STD_ERROR_HANDLE or STD_OUTPUT_HANDLE 101 self.hconsole = windll.kernel32.GetStdHandle(handle) 102 103 self._sbinfo = CONSOLE_SCREEN_BUFFER_INFO() 104 105 self._csinfo = CONSOLE_CURSOR_INFO() 106 windll.kernel32.GetConsoleCursorInfo(self.hconsole, byref(self._csinfo)) 107 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 112 113 def screen_buffer_info(self): 114 """ 115 Updates self._sbinfo and returns it 116 """ 117 windll.kernel32.GetConsoleScreenBufferInfo(self.hconsole, byref(self._sbinfo)) 118 return self._sbinfo 119 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)) 135 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)) 152 153 def push_cursor(self, param): 154 sbinfo = self.screen_buffer_info() 155 self.cursor_history.append(sbinfo.CursorPosition) 156 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) 161 162 def set_cursor(self, param): 163 y, sep, x = param.partition(';') 164 x = to_int(x, 1) - 1 165 y = to_int(y, 1) - 1 166 sbinfo = self.screen_buffer_info() 167 new_pos = COORD( 168 min(max(0, x), sbinfo.Size.X), 169 min(max(0, y), sbinfo.Size.Y) 170 ) 171 windll.kernel32.SetConsoleCursorPosition(self.hconsole, new_pos) 172 173 def set_column(self, param): 174 x = to_int(param, 1) - 1 175 sbinfo = self.screen_buffer_info() 176 new_pos = COORD( 177 min(max(0, x), sbinfo.Size.X), 178 sbinfo.CursorPosition.Y 179 ) 180 windll.kernel32.SetConsoleCursorPosition(self.hconsole, new_pos) 181 182 def move_cursor(self, x_offset=0, y_offset=0): 183 sbinfo = self.screen_buffer_info() 184 new_pos = COORD( 185 min(max(0, sbinfo.CursorPosition.X + x_offset), sbinfo.Size.X), 186 min(max(0, sbinfo.CursorPosition.Y + y_offset), sbinfo.Size.Y) 187 ) 188 windll.kernel32.SetConsoleCursorPosition(self.hconsole, new_pos) 189 190 def move_up(self, param): 191 self.move_cursor(y_offset = -to_int(param, 1)) 192 193 def move_down(self, param): 194 self.move_cursor(y_offset = to_int(param, 1)) 195 196 def move_left(self, param): 197 self.move_cursor(x_offset = -to_int(param, 1)) 198 199 def move_right(self, param): 200 self.move_cursor(x_offset = to_int(param, 1)) 201 202 def next_line(self, param): 203 sbinfo = self.screen_buffer_info() 204 self.move_cursor( 205 x_offset = -sbinfo.CursorPosition.X, 206 y_offset = to_int(param, 1) 207 ) 208 209 def prev_line(self, param): 210 sbinfo = self.screen_buffer_info() 211 self.move_cursor( 212 x_offset = -sbinfo.CursorPosition.X, 213 y_offset = -to_int(param, 1) 214 ) 215 216 def rgb2bgr(self, c): 217 return ((c&1) << 2) | (c&2) | ((c&4)>>2) 218 219 def set_color(self, param): 220 cols = param.split(';') 221 sbinfo = self.screen_buffer_info() 222 attr = sbinfo.Attributes 223 for c in cols: 224 c = to_int(c, 0) 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) 229 elif c == 0: # reset 230 attr = self._orig_sbinfo.Attributes 231 elif c == 1: # strong 232 attr |= 0x08 233 elif c == 4: # blink not available -> bg intensity 234 attr |= 0x80 235 elif c == 7: # negative 236 attr = (attr & 0xff88) | ((attr & 0x70) >> 4) | ((attr & 0x07) << 4) 237 238 windll.kernel32.SetConsoleTextAttribute(self.hconsole, attr) 239 240 def show_cursor(self,param): 241 self._csinfo.bVisible = 1 242 windll.kernel32.SetConsoleCursorInfo(self.hconsole, byref(self._csinfo)) 243 244 def hide_cursor(self,param): 245 self._csinfo.bVisible = 0 246 windll.kernel32.SetConsoleCursorInfo(self.hconsole, byref(self._csinfo)) 247 248 ansi_command_table = { 249 'A': move_up, 250 'B': move_down, 251 'C': move_right, 252 'D': move_left, 253 'E': next_line, 254 'F': prev_line, 255 'G': set_column, 256 'H': set_cursor, 257 'f': set_cursor, 258 'J': clear_screen, 259 'K': clear_line, 260 'h': show_cursor, 261 'l': hide_cursor, 262 'm': set_color, 263 's': push_cursor, 264 'u': pop_cursor, 265 } 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): 269 try: 270 wlock.acquire() 271 if self._isatty: 272 for param, cmd, txt in self.ansi_tokens.findall(text): 273 if cmd: 274 cmd_func = self.ansi_command_table.get(cmd) 275 if cmd_func: 276 cmd_func(self, param) 277 else: 278 self.writeconsole(txt) 279 else: 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) 283 finally: 284 wlock.release() 285 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 291 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 294 done = 0 295 todo = len(txt) 296 chunk = 32<<10 297 while todo != 0: 298 doing = min(chunk, todo) 299 buf = txt[done:done+doing] 300 r = writeconsole(self.hconsole, buf, doing, byref(chars_written), None) 301 if r == 0: 302 chunk >>= 1 303 continue 304 done += doing 305 todo -= doing 306 307 308 def fileno(self): 309 return self.stream.fileno() 310 311 def flush(self): 312 pass 313 314 def isatty(self): 315 return self._isatty 316 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() 321 def get_term_cols(): 322 windll.kernel32.GetConsoleScreenBufferInfo(console, byref(sbinfo)) 323 # Issue 1401 - the progress bar cannot reach the last character 324 return sbinfo.Size.X - 1 325 326# just try and see 327try: 328 import struct, fcntl, termios 329except ImportError: 330 pass 331else: 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() 334 def fun(): 335 return struct.unpack("HHHH", fcntl.ioctl(FD, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)))[1] 336 try: 337 fun() 338 except Exception as e: 339 pass 340 else: 341 get_term_cols = fun 342 343