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