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