1 """This implements an ANSI (VT100) terminal emulator as a subclass of screen. 2 3 PEXPECT 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 (at] 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 29 import screen 30 import FSM 31 import copy 32 import string 33 34 # 35 # The 'Do.*' functions are helper functions for the ANSI class. 36 # 37 def DoEmit (fsm): 38 39 screen = fsm.memory[0] 40 screen.write_ch(fsm.input_symbol) 41 42 def DoStartNumber (fsm): 43 44 fsm.memory.append (fsm.input_symbol) 45 46 def DoBuildNumber (fsm): 47 48 ns = fsm.memory.pop() 49 ns = ns + fsm.input_symbol 50 fsm.memory.append (ns) 51 52 def DoBackOne (fsm): 53 54 screen = fsm.memory[0] 55 screen.cursor_back () 56 57 def DoBack (fsm): 58 59 count = int(fsm.memory.pop()) 60 screen = fsm.memory[0] 61 screen.cursor_back (count) 62 63 def DoDownOne (fsm): 64 65 screen = fsm.memory[0] 66 screen.cursor_down () 67 68 def DoDown (fsm): 69 70 count = int(fsm.memory.pop()) 71 screen = fsm.memory[0] 72 screen.cursor_down (count) 73 74 def DoForwardOne (fsm): 75 76 screen = fsm.memory[0] 77 screen.cursor_forward () 78 79 def DoForward (fsm): 80 81 count = int(fsm.memory.pop()) 82 screen = fsm.memory[0] 83 screen.cursor_forward (count) 84 85 def DoUpReverse (fsm): 86 87 screen = fsm.memory[0] 88 screen.cursor_up_reverse() 89 90 def DoUpOne (fsm): 91 92 screen = fsm.memory[0] 93 screen.cursor_up () 94 95 def DoUp (fsm): 96 97 count = int(fsm.memory.pop()) 98 screen = fsm.memory[0] 99 screen.cursor_up (count) 100 101 def DoHome (fsm): 102 103 c = int(fsm.memory.pop()) 104 r = int(fsm.memory.pop()) 105 screen = fsm.memory[0] 106 screen.cursor_home (r,c) 107 108 def DoHomeOrigin (fsm): 109 110 c = 1 111 r = 1 112 screen = fsm.memory[0] 113 screen.cursor_home (r,c) 114 115 def DoEraseDown (fsm): 116 117 screen = fsm.memory[0] 118 screen.erase_down() 119 120 def DoErase (fsm): 121 122 arg = int(fsm.memory.pop()) 123 screen = fsm.memory[0] 124 if arg == 0: 125 screen.erase_down() 126 elif arg == 1: 127 screen.erase_up() 128 elif arg == 2: 129 screen.erase_screen() 130 131 def DoEraseEndOfLine (fsm): 132 133 screen = fsm.memory[0] 134 screen.erase_end_of_line() 135 136 def DoEraseLine (fsm): 137 138 arg = int(fsm.memory.pop()) 139 screen = fsm.memory[0] 140 if arg == 0: 141 screen.erase_end_of_line() 142 elif arg == 1: 143 screen.erase_start_of_line() 144 elif arg == 2: 145 screen.erase_line() 146 147 def DoEnableScroll (fsm): 148 149 screen = fsm.memory[0] 150 screen.scroll_screen() 151 152 def DoCursorSave (fsm): 153 154 screen = fsm.memory[0] 155 screen.cursor_save_attrs() 156 157 def DoCursorRestore (fsm): 158 159 screen = fsm.memory[0] 160 screen.cursor_restore_attrs() 161 162 def DoScrollRegion (fsm): 163 164 screen = fsm.memory[0] 165 r2 = int(fsm.memory.pop()) 166 r1 = int(fsm.memory.pop()) 167 screen.scroll_screen_rows (r1,r2) 168 169 def DoMode (fsm): 170 171 screen = fsm.memory[0] 172 mode = fsm.memory.pop() # Should be 4 173 # screen.setReplaceMode () 174 175 def DoLog (fsm): 176 177 screen = fsm.memory[0] 178 fsm.memory = [screen] 179 fout = open ('log', 'a') 180 fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') 181 fout.close() 182 183 class term (screen.screen): 184 185 """This class is an abstract, generic terminal. 186 This does nothing. This is a placeholder that 187 provides a common base class for other terminals 188 such as an ANSI terminal. """ 189 190 def __init__ (self, r=24, c=80): 191 192 screen.screen.__init__(self, r,c) 193 194 class ANSI (term): 195 196 """This class implements an ANSI (VT100) terminal. 197 It is a stream filter that recognizes ANSI terminal 198 escape sequences and maintains the state of a screen object. """ 199 200 def __init__ (self, r=24,c=80): 201 202 term.__init__(self,r,c) 203 204 #self.screen = screen (24,80) 205 self.state = FSM.FSM ('INIT',[self]) 206 self.state.set_default_transition (DoLog, 'INIT') 207 self.state.add_transition_any ('INIT', DoEmit, 'INIT') 208 self.state.add_transition ('\x1b', 'INIT', None, 'ESC') 209 self.state.add_transition_any ('ESC', DoLog, 'INIT') 210 self.state.add_transition ('(', 'ESC', None, 'G0SCS') 211 self.state.add_transition (')', 'ESC', None, 'G1SCS') 212 self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') 213 self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') 214 self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') 215 self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') 216 self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') 217 self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') 218 self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') 219 self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. 220 self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') 221 self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') 222 self.state.add_transition ('[', 'ESC', None, 'ELB') 223 # ELB means Escape Left Bracket. That is ^[[ 224 self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') 225 self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') 226 self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') 227 self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') 228 self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') 229 self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') 230 self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') 231 self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') 232 self.state.add_transition ('m', 'ELB', None, 'INIT') 233 self.state.add_transition ('?', 'ELB', None, 'MODECRAP') 234 self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1') 235 self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1') 236 self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') 237 self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') 238 self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') 239 self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') 240 self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') 241 self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') 242 self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') 243 ### It gets worse... the 'm' code can have infinite number of 244 ### number;number;number before it. I've never seen more than two, 245 ### but the specs say it's allowed. crap! 246 self.state.add_transition ('m', 'NUMBER_1', None, 'INIT') 247 ### LED control. Same implementation problem as 'm' code. 248 self.state.add_transition ('q', 'NUMBER_1', None, 'INIT') 249 250 # \E[?47h switch to alternate screen 251 # \E[?47l restores to normal screen from alternate screen. 252 self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM') 253 self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM') 254 self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT') 255 self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT') 256 257 #RM Reset Mode Esc [ Ps l none 258 self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') 259 self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT') 260 self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2') 261 self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2') 262 self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT') 263 self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') 264 self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') 265 self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') 266 ### It gets worse... the 'm' code can have infinite number of 267 ### number;number;number before it. I've never seen more than two, 268 ### but the specs say it's allowed. crap! 269 self.state.add_transition ('m', 'NUMBER_2', None, 'INIT') 270 ### LED control. Same problem as 'm' code. 271 self.state.add_transition ('q', 'NUMBER_2', None, 'INIT') 272 self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') 273 274 # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers 275 self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT') 276 self.state.add_transition_list (string.digits, 'SEMICOLON_X', None, 'NUMBER_X') 277 self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT') 278 self.state.add_transition ('m', 'NUMBER_X', None, 'INIT') 279 self.state.add_transition ('q', 'NUMBER_X', None, 'INIT') 280 self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') 281 282 def process (self, c): 283 284 self.state.process(c) 285 286 def process_list (self, l): 287 288 self.write(l) 289 290 def write (self, s): 291 292 for c in s: 293 self.process(c) 294 295 def flush (self): 296 297 pass 298 299 def write_ch (self, ch): 300 301 """This puts a character at the current cursor position. The cursor 302 position is moved forward with wrap-around, but no scrolling is done if 303 the cursor hits the lower-right corner of the screen. """ 304 305 #\r and \n both produce a call to cr() and lf(), respectively. 306 ch = ch[0] 307 308 if ch == '\r': 309 self.cr() 310 return 311 if ch == '\n': 312 self.crlf() 313 return 314 if ch == chr(screen.BS): 315 self.cursor_back() 316 return 317 if ch not in string.printable: 318 fout = open ('log', 'a') 319 fout.write ('Nonprint: ' + str(ord(ch)) + '\n') 320 fout.close() 321 return 322 self.put_abs(self.cur_r, self.cur_c, ch) 323 old_r = self.cur_r 324 old_c = self.cur_c 325 self.cursor_forward() 326 if old_c == self.cur_c: 327 self.cursor_down() 328 if old_r != self.cur_r: 329 self.cursor_home (self.cur_r, 1) 330 else: 331 self.scroll_up () 332 self.cursor_home (self.cur_r, 1) 333 self.erase_line() 334 335 # def test (self): 336 # 337 # import sys 338 # write_text = 'I\'ve got a ferret sticking up my nose.\n' + \ 339 # '(He\'s got a ferret sticking up his nose.)\n' + \ 340 # 'How it got there I can\'t tell\n' + \ 341 # 'But now it\'s there it hurts like hell\n' + \ 342 # 'And what is more it radically affects my sense of smell.\n' + \ 343 # '(His sense of smell.)\n' + \ 344 # 'I can see a bare-bottomed mandril.\n' + \ 345 # '(Slyly eyeing his other nostril.)\n' + \ 346 # 'If it jumps inside there too I really don\'t know what to do\n' + \ 347 # 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \ 348 # '(A nasal zoo.)\n' + \ 349 # 'I\'ve got a ferret sticking up my nose.\n' + \ 350 # '(And what is worst of all it constantly explodes.)\n' + \ 351 # '"Ferrets don\'t explode," you say\n' + \ 352 # 'But it happened nine times yesterday\n' + \ 353 # 'And I should know for each time I was standing in the way.\n' + \ 354 # 'I\'ve got a ferret sticking up my nose.\n' + \ 355 # '(He\'s got a ferret sticking up his nose.)\n' + \ 356 # 'How it got there I can\'t tell\n' + \ 357 # 'But now it\'s there it hurts like hell\n' + \ 358 # 'And what is more it radically affects my sense of smell.\n' + \ 359 # '(His sense of smell.)' 360 # self.fill('.') 361 # self.cursor_home() 362 # for c in write_text: 363 # self.write_ch (c) 364 # print str(self) 365 # 366 #if __name__ == '__main__': 367 # t = ANSI(6,65) 368 # t.test() 369