1'''This implements an ANSI (VT100) terminal emulator as a subclass of screen. 2 3PEXPECT LICENSE 4 5 This license is approved by the OSI and FSF as GPL-compatible. 6 http://opensource.org/licenses/isc-license.txt 7 8 Copyright (c) 2012, Noah Spurrier <noah@noah.org> 9 PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 10 PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 11 COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 12 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20''' 21 22# references: 23# http://en.wikipedia.org/wiki/ANSI_escape_code 24# http://www.retards.org/terminals/vt102.html 25# http://vt100.net/docs/vt102-ug/contents.html 26# http://vt100.net/docs/vt220-rm/ 27# http://www.termsys.demon.co.uk/vtansi.htm 28 29from . import screen 30from . import FSM 31import string 32 33# 34# The 'Do.*' functions are helper functions for the ANSI class. 35# 36def DoEmit (fsm): 37 38 screen = fsm.memory[0] 39 screen.write_ch(fsm.input_symbol) 40 41def DoStartNumber (fsm): 42 43 fsm.memory.append (fsm.input_symbol) 44 45def DoBuildNumber (fsm): 46 47 ns = fsm.memory.pop() 48 ns = ns + fsm.input_symbol 49 fsm.memory.append (ns) 50 51def DoBackOne (fsm): 52 53 screen = fsm.memory[0] 54 screen.cursor_back () 55 56def DoBack (fsm): 57 58 count = int(fsm.memory.pop()) 59 screen = fsm.memory[0] 60 screen.cursor_back (count) 61 62def DoDownOne (fsm): 63 64 screen = fsm.memory[0] 65 screen.cursor_down () 66 67def DoDown (fsm): 68 69 count = int(fsm.memory.pop()) 70 screen = fsm.memory[0] 71 screen.cursor_down (count) 72 73def DoForwardOne (fsm): 74 75 screen = fsm.memory[0] 76 screen.cursor_forward () 77 78def DoForward (fsm): 79 80 count = int(fsm.memory.pop()) 81 screen = fsm.memory[0] 82 screen.cursor_forward (count) 83 84def DoUpReverse (fsm): 85 86 screen = fsm.memory[0] 87 screen.cursor_up_reverse() 88 89def DoUpOne (fsm): 90 91 screen = fsm.memory[0] 92 screen.cursor_up () 93 94def DoUp (fsm): 95 96 count = int(fsm.memory.pop()) 97 screen = fsm.memory[0] 98 screen.cursor_up (count) 99 100def DoHome (fsm): 101 102 c = int(fsm.memory.pop()) 103 r = int(fsm.memory.pop()) 104 screen = fsm.memory[0] 105 screen.cursor_home (r,c) 106 107def DoHomeOrigin (fsm): 108 109 c = 1 110 r = 1 111 screen = fsm.memory[0] 112 screen.cursor_home (r,c) 113 114def DoEraseDown (fsm): 115 116 screen = fsm.memory[0] 117 screen.erase_down() 118 119def DoErase (fsm): 120 121 arg = int(fsm.memory.pop()) 122 screen = fsm.memory[0] 123 if arg == 0: 124 screen.erase_down() 125 elif arg == 1: 126 screen.erase_up() 127 elif arg == 2: 128 screen.erase_screen() 129 130def DoEraseEndOfLine (fsm): 131 132 screen = fsm.memory[0] 133 screen.erase_end_of_line() 134 135def DoEraseLine (fsm): 136 137 arg = int(fsm.memory.pop()) 138 screen = fsm.memory[0] 139 if arg == 0: 140 screen.erase_end_of_line() 141 elif arg == 1: 142 screen.erase_start_of_line() 143 elif arg == 2: 144 screen.erase_line() 145 146def DoEnableScroll (fsm): 147 148 screen = fsm.memory[0] 149 screen.scroll_screen() 150 151def DoCursorSave (fsm): 152 153 screen = fsm.memory[0] 154 screen.cursor_save_attrs() 155 156def DoCursorRestore (fsm): 157 158 screen = fsm.memory[0] 159 screen.cursor_restore_attrs() 160 161def DoScrollRegion (fsm): 162 163 screen = fsm.memory[0] 164 r2 = int(fsm.memory.pop()) 165 r1 = int(fsm.memory.pop()) 166 screen.scroll_screen_rows (r1,r2) 167 168def DoMode (fsm): 169 170 screen = fsm.memory[0] 171 mode = fsm.memory.pop() # Should be 4 172 # screen.setReplaceMode () 173 174def DoLog (fsm): 175 176 screen = fsm.memory[0] 177 fsm.memory = [screen] 178 fout = open ('log', 'a') 179 fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') 180 fout.close() 181 182class term (screen.screen): 183 184 '''This class is an abstract, generic terminal. 185 This does nothing. This is a placeholder that 186 provides a common base class for other terminals 187 such as an ANSI terminal. ''' 188 189 def __init__ (self, r=24, c=80, *args, **kwargs): 190 191 screen.screen.__init__(self, r,c,*args,**kwargs) 192 193class ANSI (term): 194 '''This class implements an ANSI (VT100) terminal. 195 It is a stream filter that recognizes ANSI terminal 196 escape sequences and maintains the state of a screen object. ''' 197 198 def __init__ (self, r=24,c=80,*args,**kwargs): 199 200 term.__init__(self,r,c,*args,**kwargs) 201 202 #self.screen = screen (24,80) 203 self.state = FSM.FSM ('INIT',[self]) 204 self.state.set_default_transition (DoLog, 'INIT') 205 self.state.add_transition_any ('INIT', DoEmit, 'INIT') 206 self.state.add_transition ('\x1b', 'INIT', None, 'ESC') 207 self.state.add_transition_any ('ESC', DoLog, 'INIT') 208 self.state.add_transition ('(', 'ESC', None, 'G0SCS') 209 self.state.add_transition (')', 'ESC', None, 'G1SCS') 210 self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') 211 self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') 212 self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') 213 self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') 214 self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') 215 self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') 216 self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') 217 self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. 218 self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') 219 self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') 220 self.state.add_transition ('[', 'ESC', None, 'ELB') 221 # ELB means Escape Left Bracket. That is ^[[ 222 self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') 223 self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') 224 self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') 225 self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') 226 self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') 227 self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') 228 self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') 229 self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') 230 self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT') 231 self.state.add_transition ('?', 'ELB', None, 'MODECRAP') 232 self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1') 233 self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1') 234 self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') 235 self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') 236 self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') 237 self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') 238 self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') 239 self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') 240 self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') 241 ### It gets worse... the 'm' code can have infinite number of 242 ### number;number;number before it. I've never seen more than two, 243 ### but the specs say it's allowed. crap! 244 self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT') 245 ### LED control. Same implementation problem as 'm' code. 246 self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT') 247 248 # \E[?47h switch to alternate screen 249 # \E[?47l restores to normal screen from alternate screen. 250 self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM') 251 self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM') 252 self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT') 253 self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT') 254 255#RM Reset Mode Esc [ Ps l none 256 self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') 257 self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT') 258 self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2') 259 self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2') 260 self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT') 261 self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') 262 self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') 263 self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') 264 ### It gets worse... the 'm' code can have infinite number of 265 ### number;number;number before it. I've never seen more than two, 266 ### but the specs say it's allowed. crap! 267 self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT') 268 ### LED control. Same problem as 'm' code. 269 self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT') 270 self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') 271 272 # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers 273 self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT') 274 self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X') 275 self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X') 276 self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT') 277 self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT') 278 self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT') 279 self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X') 280 281 def process (self, c): 282 """Process a single character. Called by :meth:`write`.""" 283 if isinstance(c, bytes): 284 c = self._decode(c) 285 self.state.process(c) 286 287 def process_list (self, l): 288 289 self.write(l) 290 291 def write (self, s): 292 """Process text, writing it to the virtual screen while handling 293 ANSI escape codes. 294 """ 295 if isinstance(s, bytes): 296 s = self._decode(s) 297 for c in s: 298 self.process(c) 299 300 def flush (self): 301 pass 302 303 def write_ch (self, ch): 304 '''This puts a character at the current cursor position. The cursor 305 position is moved forward with wrap-around, but no scrolling is done if 306 the cursor hits the lower-right corner of the screen. ''' 307 308 if isinstance(ch, bytes): 309 ch = self._decode(ch) 310 311 #\r and \n both produce a call to cr() and lf(), respectively. 312 ch = ch[0] 313 314 if ch == u'\r': 315 self.cr() 316 return 317 if ch == u'\n': 318 self.crlf() 319 return 320 if ch == chr(screen.BS): 321 self.cursor_back() 322 return 323 self.put_abs(self.cur_r, self.cur_c, ch) 324 old_r = self.cur_r 325 old_c = self.cur_c 326 self.cursor_forward() 327 if old_c == self.cur_c: 328 self.cursor_down() 329 if old_r != self.cur_r: 330 self.cursor_home (self.cur_r, 1) 331 else: 332 self.scroll_up () 333 self.cursor_home (self.cur_r, 1) 334 self.erase_line() 335 336 def do_sgr (self, fsm): 337 '''Select Graphic Rendition, e.g. color. ''' 338 screen = fsm.memory[0] 339 fsm.memory = [screen] 340 341 def do_decsca (self, fsm): 342 '''Select character protection attribute. ''' 343 screen = fsm.memory[0] 344 fsm.memory = [screen] 345 346 def do_modecrap (self, fsm): 347 '''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone 348 wanted to actually use these, they'd need to add more states to the 349 FSM rather than just improve or override this method. ''' 350 screen = fsm.memory[0] 351 fsm.memory = [screen] 352