1 """This implements an ANSI (VT100) terminal emulator as a subclass of screen.
5 This license is approved by the OSI and FSF as GPL-compatible.
6 http://opensource.org/licenses/isc-license.txt
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.
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
35 # The 'Do.*' functions are helper functions for the ANSI class.
39 screen
= fsm
.memory
[0]
40 screen
.write_ch(fsm
.input_symbol
)
42 def DoStartNumber (fsm
):
44 fsm
.memory
.append (fsm
.input_symbol
)
46 def DoBuildNumber (fsm
):
49 ns
= ns
+ fsm
.input_symbol
50 fsm
.memory
.append (ns
)
54 screen
= fsm
.memory
[0]
59 count
= int(fsm
.memory
.pop())
60 screen
= fsm
.memory
[0]
61 screen
.cursor_back (count
)
65 screen
= fsm
.memory
[0]
70 count
= int(fsm
.memory
.pop())
71 screen
= fsm
.memory
[0]
72 screen
.cursor_down (count
)
74 def DoForwardOne (fsm
):
76 screen
= fsm
.memory
[0]
77 screen
.cursor_forward ()
81 count
= int(fsm
.memory
.pop())
82 screen
= fsm
.memory
[0]
83 screen
.cursor_forward (count
)
85 def DoUpReverse (fsm
):
87 screen
= fsm
.memory
[0]
88 screen
.cursor_up_reverse()
92 screen
= fsm
.memory
[0]
97 count
= int(fsm
.memory
.pop())
98 screen
= fsm
.memory
[0]
99 screen
.cursor_up (count
)
103 c
= int(fsm
.memory
.pop())
104 r
= int(fsm
.memory
.pop())
105 screen
= fsm
.memory
[0]
106 screen
.cursor_home (r
,c
)
108 def DoHomeOrigin (fsm
):
112 screen
= fsm
.memory
[0]
113 screen
.cursor_home (r
,c
)
115 def DoEraseDown (fsm
):
117 screen
= fsm
.memory
[0]
122 arg
= int(fsm
.memory
.pop())
123 screen
= fsm
.memory
[0]
129 screen
.erase_screen()
131 def DoEraseEndOfLine (fsm
):
133 screen
= fsm
.memory
[0]
134 screen
.erase_end_of_line()
136 def DoEraseLine (fsm
):
138 arg
= int(fsm
.memory
.pop())
139 screen
= fsm
.memory
[0]
141 screen
.erase_end_of_line()
143 screen
.erase_start_of_line()
147 def DoEnableScroll (fsm
):
149 screen
= fsm
.memory
[0]
150 screen
.scroll_screen()
152 def DoCursorSave (fsm
):
154 screen
= fsm
.memory
[0]
155 screen
.cursor_save_attrs()
157 def DoCursorRestore (fsm
):
159 screen
= fsm
.memory
[0]
160 screen
.cursor_restore_attrs()
162 def DoScrollRegion (fsm
):
164 screen
= fsm
.memory
[0]
165 r2
= int(fsm
.memory
.pop())
166 r1
= int(fsm
.memory
.pop())
167 screen
.scroll_screen_rows (r1
,r2
)
171 screen
= fsm
.memory
[0]
172 mode
= fsm
.memory
.pop() # Should be 4
173 # screen.setReplaceMode ()
177 screen
= fsm
.memory
[0]
178 fsm
.memory
= [screen
]
179 fout
= open ('log', 'a')
180 fout
.write (fsm
.input_symbol
+ ',' + fsm
.current_state
+ '\n')
183 class term (screen
.screen
):
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. """
190 def __init__ (self
, r
=24, c
=80):
192 screen
.screen
.__init
__(self
, r
,c
)
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. """
200 def __init__ (self
, r
=24,c
=80):
202 term
.__init
__(self
,r
,c
)
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')
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')
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')
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')
282 def process (self
, c
):
284 self
.state
.process(c
)
286 def process_list (self
, l
):
299 def write_ch (self
, ch
):
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. """
305 #\r and \n both produce a call to cr() and lf(), respectively.
314 if ch
== chr(screen
.BS
):
317 if ch
not in string
.printable
:
318 fout
= open ('log', 'a')
319 fout
.write ('Nonprint: ' + str(ord(ch
)) + '\n')
322 self
.put_abs(self
.cur_r
, self
.cur_c
, ch
)
325 self
.cursor_forward()
326 if old_c
== self
.cur_c
:
328 if old_r
!= self
.cur_r
:
329 self
.cursor_home (self
.cur_r
, 1)
332 self
.cursor_home (self
.cur_r
, 1)
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.)'
362 # for c in write_text:
366 #if __name__ == '__main__':