pyqonsole

view pyqonsole/emuVt102.py @ 188:f353f5aba78e

copyright update, remove __revision__
author "Sylvain <syt@logilab.fr>"
date Fri, 09 Mar 2007 14:05:04 +0100
parents 36c153fb95dc
children
line source
1 # -*- coding: ISO-8859-1 -*-
2 # Copyright (c) 2005-2007 LOGILAB S.A. (Paris, FRANCE).
3 # Copyright (c) 2005-2006 CEA Grenoble
4 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the CECILL license, available at
8 # http://www.inria.fr/valorisation/logiciels/Licence.CeCILL-V2.pdf
9 #
10 """Provide the EmuVt102 class, responsible for the VT102 Terminal Emulation.
12 Based on the konsole code from Lars Doelle.
14 OSC: Operating System Controls (introduced by 'ESC[')
15 CSI: Control Sequence Introducer (introduced by 'ESC]')
17 @author: Lars Doelle
18 @author: Benjamin Longuet
19 @author: Frederic Mantegazza
20 @author: Cyrille Boullier
21 @author: Sylvain Thenault
22 @copyright: 2003, 2005-2007
23 @organization: CEA-Grenoble
24 @organization: Logilab
25 @license: CECILL
26 """
28 import os
30 from pyqonsole.qtwrapper import qt, QEvent, ControlButton, ShiftButton, AltButton
32 import pyqonsole.keytrans as kt
33 from pyqonsole.emulation import Emulation, NOTIFYBELL, NOTIFYNORMAL
34 from pyqonsole import CTRL, screen, widget, ca
37 # VT102 modes
38 MODE_AppScreen = screen.MODES_SCREEN+0
39 MODE_AppCuKeys = screen.MODES_SCREEN+1
40 MODE_AppKeyPad = screen.MODES_SCREEN+2
41 MODE_Mouse1000 = screen.MODES_SCREEN+3
42 MODE_Ansi = screen.MODES_SCREEN+4
44 # Tokens
45 TY_CHR = 0
46 def TY_CTL(A):
47 return ((ord(A) & 0xff) << 8) | 1
48 def TY_ESC(A):
49 return ((ord(A) & 0xff) << 8) | 2
50 def TY_ESC_CS(A, B):
51 return ((ord(B) & 0xffff) << 16) | ((ord(A) & 0xff) << 8) | 3
52 def TY_ESC_DE(A):
53 return ((ord(A) & 0xff) << 8) | 4
54 def TY_CSI_PS(A, N):
55 return ((N & 0xffff) << 16) | ((ord(A) & 0xff) << 8) | 5
56 def TY_CSI_PN(A):
57 return ((ord(A) & 0xff) << 8) | 6
58 def TY_CSI_PR(A, N):
59 return ((N & 0xffff) << 16) | ((ord(A) & 0xff) << 8) | 7
60 def TY_VT52(A):
61 return ((ord(A) & 0xff) << 8) | 8
62 def TY_CSI_PG(A):
63 return ((ord(A) & 0xff) << 8) | 9
65 # Character Classes used while decoding
66 CTL = 1
67 CHR = 2
68 CPN = 4
69 DIG = 8
70 SCS = 16
71 GRP = 32
72 ESC = 27
75 # init tokenizer table
76 TOK_TBL = []
77 def init_tokenizer():
78 for i in xrange(32):
79 TOK_TBL.append(CTL)
80 for i in xrange(32, 256):
81 TOK_TBL.append(CHR)
82 for s in "@ABCDGHLMPXcdfry":
83 TOK_TBL[ord(s)] |= CPN
84 for s in "0123456789":
85 TOK_TBL[ord(s)] |= DIG
86 for s in "()+*%":
87 TOK_TBL[ord(s)] |= SCS
88 for s in "()+*#[]%":
89 TOK_TBL[ord(s)] |= GRP
90 init_tokenizer()
92 # decoder helpers
93 def lec(p, s, P, L, C):
94 """
95 P: the length of the token scanned so far.
96 L: (often P-1) the position on which contents we base a decision.
97 C: a character or a group of characters (taken from 'tbl').
99 s: input buffer
100 p: length of the input buffer
101 """
102 return p == P and s[L] == C
104 def lun(p, cc):
105 return p == 1 and cc >= 32
106 def eec(p, cc, C):
107 return p >= 3 and cc == C
108 def epp(p, s):
109 return p >= 3 and s[2] == ord('?')
110 def egt(p, s):
111 return p >= 3 and s[2] == ord('>')
112 def les(p, s, P, L, C):
113 return p == P and s[L] < 256 and (TOK_TBL[s[L]] & C) == C
114 def eps(p, s, cc, C):
115 return p >= 3 and s[2] != ord('?') and s[2] != ord('>') and cc < 256 and (TOK_TBL[cc] & C) == C
116 def ees(p, cc, C):
117 return p >= 3 and cc < 256 and (TOK_TBL[cc] & C) == C
120 class CharCodes:
121 """VT100 Charsets
123 Character Set Conversion
125 The processing contains a VT100 specific code translation layer.
126 It's still in use and mainly responsible for the line drawing graphics.
128 These and some other glyphs are assigned to codes (0x5f-0xfe)
129 normally occupied by the latin letters. Since this codes also
130 appear within control sequences, the extra code conversion
131 does not permute with the tokenizer and is placed behind it
132 in the pipeline. It only applies to tokens, which represent
133 plain characters.
135 This conversion it eventually continued in Widget, since
136 it might involve VT100 enhanced fonts, which have these
137 particular glyphs allocated in (0x00-0x1f) in their code page.
138 """
139 def __init__(self):
140 self.charset = [0, 0, 0, 0]
141 self.cu_cs = 0 # actual charset.
142 self.graphic = False # Some VT100 tricks
143 self.pound = False # Some VT100 tricks
144 self.trans = [0, 0, 0, 0, 0, 0, 0] # pre-latin conversion
145 self.sa_graphic = False # saved graphic
146 self.sa_pound = False # saved pound
147 self.sa_trans = [0, 0, 0, 0, 0, 0, 0] # saved pre-latin conversion
149 def reset(self):
150 self.charset = [ord(c) for c in "BBBB"]
151 self.cu_cs = 0
152 self.graphic = False
153 self.pound = False
154 self.trans_from_string("[\\]{|}~")
155 self.sa_graphic = False
156 self.sa_pound = False
158 def trans_from_string(self, string):
159 #assert len(string) == 6, string
160 self.trans = [ord(c) for c in string]
162 def applyCharset(self, c):
163 if self.graphic and 0x5f <= c and c <= 0x7e:
164 return widget.VT100_GRAPHICS[c-0x5f]
165 if self.pound and c == ord('#'):
166 return 0xa3 # Obsolete mode
167 if ord('[') <= c and c <= ord(']'):
168 return self.trans[c-ord('[')+0] & 0xff
169 if ord('{') <= c and c <= ord('~'):
170 return self.trans[c-ord('{')+3] & 0xff
171 return c
173 def setCharset(self, n, cs):
174 self.charset[n & 3] = cs
175 self.useCharset(self.cu_cs)
177 def save(self):
178 self.sa_graphic = self.graphic
179 self.sa_pound = self.pound
180 self.sa_trans = self.trans[:]
182 def restore(self):
183 self.graphic = self.sa_graphic
184 self.pound = self.sa_pound
185 self.trans = self.sa_trans[:]
187 def useCharset(self, n):
188 self.cu_cs = n & 3
189 self.graphic = (self.charset[n & 3] == '0')
190 self.pound = (self.charset[n & 3] == 'A') # This mode is obsolete
191 self.trans_from_string("[\\]{|}~") # ancient mode, identical
192 # FIXME: we might better use octal strings below to prevent filter problems
193 if self.charset[n & 3] == 'K':
194 self.trans_from_string("ÄÖÜäöüß") # ancient mode, german
195 elif self.charset[n & 3] == 'R':
196 self.trans_from_string("°ç§éùè¨") # ancient mode, french
199 class EmuVt102(Emulation):
200 """VT102 Terminal Emulation
202 This class puts together the screens, the pty and the widget to a
203 complete terminal emulation. Beside combining it's componentes, it
204 handles the emulations's protocol.
206 It consists of the following sections:
207 - Incoming Bytes Event pipeline
208 - Outgoing Bytes
209 - Mouse Events
210 - Keyboard Events
211 - Modes and Charset State
212 """
214 def __init__(self, gui):
215 super(EmuVt102, self).__init__(gui)
216 self._pbuf = []
217 self._argv = [0]
218 # file used while in print mode
219 self._print_fd = None
220 # mapping with mode as key and a boolean indicating wether it's
221 # activated as value
222 self._curr_mode = {}
223 self._save_mode = {}
224 self._charset = [CharCodes(), CharCodes()]
225 self._hold_screen = False
226 self.reset()
227 gui.myconnect("mouseSignal", self.onMouse)
229 def reset(self):
230 self._resetToken()
231 self._resetModes()
232 self._resetCharset(0)
233 self._resetCharset(1)
234 self._screen[0].reset()
235 self._screen[1].reset()
236 self._setCodec(0)
238 # Processing the incoming byte stream #####################################
239 """Incoming Bytes Event pipeline
241 This section deals with decoding the incoming character stream.
242 Decoding means here, that the stream is first seperated into `tokens'
243 which are then mapped to a `meaning' provided as operations by the
244 `TEScreen' class or by the emulation class itself.
246 The pipeline proceeds as follows:
248 - Tokenizing the ESC codes (onRcvChar)
249 - VT100 code page translation of plain characters (applyCharset)
250 - Interpretation of ESC codes (tau)
252 The escape codes and their meaning are described in the
253 technical reference of this program.
256 Tokens ------------------------------------------------------------------
259 Since the tokens are the central notion if this section, we've put them
260 in front. They provide the syntactical elements used to represent the
261 terminals operations as byte sequences.
263 They are encodes here into a single machine word, so that we can later
264 switch over them easily. Depending on the token itself, additional
265 argument variables are filled with parameter values.
267 The tokens are defined below:
269 - CHR - Printable characters (32..255 but DEL (=127))
270 - CTL - Control characters (0..31 but ESC (= 27), DEL)
271 - ESC - Escape codes of the form <ESC><CHR but `[]()+*#'>
272 - ESC_DE - Escape codes of the form <ESC><any of `()+*#%'> C
273 - CSI_PN - Escape codes of the form <ESC>'[' {Pn} ';' {Pn} C
274 - CSI_PS - Escape codes of the form <ESC>'[' {Pn} ';' ... C
275 - CSI_PR - Escape codes of the form <ESC>'[' '?' {Pn} ';' ... C
276 - VT52 - VT52 escape codes
277 - <ESC><Chr>
278 - <ESC>'Y'{Pc}{Pc}
279 - XTE_HA - Xterm hacks <ESC>`]' {Pn} `;' {Text} <BEL>
280 note that this is handled differently
282 The last two forms allow list of arguments. Since the elements of
283 the lists are treated individually the same way, they are passed
284 as individual tokens to the interpretation. Further, because the
285 meaning of the parameters are names (althought represented as numbers),
286 they are includes within the token ('N').
289 Tokenizer ---------------------------------------------------------------
291 The tokenizers state
293 The state is represented by the buffer (pbuf, ppos),
294 and accompanied by decoded arguments kept in (argv,argc).
295 Note that they are kept internal in the tokenizer.
298 Instead of keeping an explicit state, we deduce it from the
299 token scanned so far. It is then immediately combined with
300 the current character to form a scanning decision.
302 This is done by the following defines.
304 - P is the length of the token scanned so far.
305 - L (often P-1) is the position on which contents we base a decision.
306 - C is a character or a group of characters (taken from 'tbl').
308 Note that they need to applied in proper order.
309 """
311 def Xpe(self):
312 return len(self._pbuf) >= 2 and self._pbuf[1] == ord(']')
313 def Xte(self, cc):
314 return self.Xpe() and cc == 7
315 def ces(self, cc, C):
316 return cc < 256 and (TOK_TBL[cc] & C) == C and not self.Xte(cc)
318 def onRcvChar(self, cc):
319 """char received from the subprocess"""
320 if self._print_fd:
321 self.printScan(cc)
322 return
323 if cc == 127: # VT100: ignore.
324 return
325 if self.ces(cc, CTL):
326 # DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100
327 # This means, they do neither a resetToken nor a pushToToken. Some of them, do
328 # of course. Guess this originates from a weakly layered handling of the X-on
329 # X-off protocol, which comes really below this level.
330 if cc == CTRL('X') or cc == CTRL('Z') or cc == ESC: # VT100: CAN or SUB
331 self._resetToken()
332 if cc != ESC:
333 self.tau(TY_CTL(chr(cc+ord('@'))), 0, 0)
334 return
335 # Advance the state
336 self._pbuf.append(cc)
337 s = self._pbuf
338 p = len(self._pbuf)
340 if self.getMode(MODE_Ansi): # Decide on proper action
341 if lec(p, s, 1, 0, ESC):
342 pass
343 elif les(p, s, 2, 1, GRP):
344 pass
345 elif self.Xte(cc):
346 self._XtermHack()
347 self._resetToken()
348 elif self.Xpe():
349 pass
350 elif lec(p,s, 3, 2, ord('?')):
351 pass
352 elif lec(p, s, 3, 2, ord('>')):
353 pass
354 elif lun(p, cc):
355 self.tau(TY_CHR, self._applyCharset(cc), 0)
356 self._resetToken()
357 elif lec(p, s, 2, 0, ESC):
358 self.tau(TY_ESC(chr(s[1])), 0, 0)
359 self._resetToken()
360 elif les(p, s, 3, 1, SCS):
361 self.tau(TY_ESC_CS(chr(s[1]), chr(s[2])), 0, 0)
362 self._resetToken()
363 elif lec(p, s, 3, 1, ord('#')):
364 self.tau(TY_ESC_DE(chr(s[2])), 0, 0)
365 self._resetToken()
366 elif eps(p, s, cc, CPN):
367 if len(self._argv)> 1:
368 q = self._argv[-1]
369 else:
370 q = None
371 self.tau(TY_CSI_PN(chr(cc)), self._argv[0], q)
372 self._resetToken()
373 elif ees(p, cc, DIG):
374 self._addDigit(cc - ord('0'))
375 elif eec(p, cc, ord(';')):
376 self._argv.append(0)
377 else:
378 for arg in self._argv:
379 if epp(p, s):
380 self.tau(TY_CSI_PR(chr(cc), arg), 0, 0)
381 elif egt(p, s):
382 self.tau(TY_CSI_PG(chr(cc)), 0, 0) # spec. elif token == for ESC]>0c or ESC]>c
383 else:
384 self.tau(TY_CSI_PS(chr(cc), arg), 0, 0)
385 self._resetToken()
387 else: # mode VT52
388 if lec(p, s, 1, 0, ESC):
389 pass
390 elif les(p, s, 1, 0, CHR):
391 self.tau(TY_CHR, s[0], 0)
392 self._resetToken()
393 elif lec(p, s, 2, 1, ord('Y')):
394 pass
395 elif lec(p, s, 3, 1, ord('Y')):
396 pass
397 elif p < 4:
398 self.tau(TY_VT52(chr(s[1])), 0, 0)
399 self._resetToken()
400 else:
401 self.tau(TY_VT52(chr(s[1])), s[2], s[3])
402 self._resetToken()
405 def tau(self, token, p, q):
406 """
407 Interpretation of ESC codes
408 ---------------------------
410 Now that the incoming character stream is properly tokenized,
411 meaning is assigned to them. These are either operations of
412 the current screen, or of the emulation class itself.
414 The token to be interpreteted comes in as a machine word
415 possibly accompanied by two parameters.
417 Likewise, the operations assigned to, come with up to two
418 arguments. One could consider to make up a proper table
419 from the function below.
421 The technical reference manual provides more informations
422 about this mapping.
423 """
424 if token == TY_CHR: self._scr.showCharacter(p) # UTF16
426 # 127 DEL: ignored on input
428 elif token == TY_CTL('@') : pass # NUL: ignored
429 elif token == TY_CTL('A') : pass # SOH: ignored
430 elif token == TY_CTL('B') : pass # STX: ignored
431 elif token == TY_CTL('C') : pass # ETX: ignored
432 elif token == TY_CTL('D') : pass # EOT: ignored
433 elif token == TY_CTL('E') : self.reportAnswerBack() # VT100
434 elif token == TY_CTL('F') : pass # ACK: ignored
435 elif token == TY_CTL('G'):
436 if self._connected: # VT100
437 self._gui.bell()
438 self.myemit("notifySessionState", (NOTIFYBELL,))
439 elif token == TY_CTL('H') : self._scr.backSpace() # VT100
440 elif token == TY_CTL('I') : self._scr.tabulate() # VT100
441 elif token == TY_CTL('J') : self._scr.newLine() # VT100
442 elif token == TY_CTL('K') : self._scr.newLine() # VT100
443 elif token == TY_CTL('L') : self._scr.newLine() # VT100
444 elif token == TY_CTL('M') : self._scr.return_() # VT100
445 elif token == TY_CTL('N') : self._useCharset(1) # VT100
446 elif token == TY_CTL('O') : self._useCharset(0) # VT100
448 elif token == TY_CTL('P') : pass # DLE: ignored
449 elif token == TY_CTL('Q') : pass # DC1: XON continue # VT100
450 elif token == TY_CTL('R') : pass # DC2: ignored
451 elif token == TY_CTL('S') : pass # DC3: XOFF halt # VT100
452 elif token == TY_CTL('T') : pass # DC4: ignored
453 elif token == TY_CTL('U') : pass # NAK: ignored
454 elif token == TY_CTL('V') : pass # SYN: ignored
455 elif token == TY_CTL('W') : pass # ETB: ignored
456 elif token == TY_CTL('X') : self._scr.showCharacter(0x2592) # VT100 XXX Not in spec
457 elif token == TY_CTL('Y') : pass # EM : ignored
458 elif token == TY_CTL('Z') : self._scr.showCharacter(0x2592) # VT100 XXX Not in spec
459 elif token == TY_CTL('[') : pass # ESC: cannot be seen here.
460 elif token == TY_CTL('\\') : pass # FS : ignored
461 elif token == TY_CTL(']') : pass # GS : ignored
462 elif token == TY_CTL('^') : pass # RS : ignored
463 elif token == TY_CTL('_') : pass # US : ignored
465 elif token == TY_ESC('D') : self._scr.index() # VT100
466 elif token == TY_ESC('E') : self._scr.NextLine() # VT100
467 elif token == TY_ESC('H') : self._scr.changeTabStop(True) # VT100
468 elif token == TY_ESC('M') : self._scr.reverseIndex() # VT100
469 elif token == TY_ESC('Z') : self.reportTerminalType()
470 elif token == TY_ESC('c') : self.reset()
472 elif token == TY_ESC('n') : self._useCharset(2)
473 elif token == TY_ESC('o') : self._useCharset(3)
474 elif token == TY_ESC('7') : self._saveCursor()
475 elif token == TY_ESC('8') : self._restoreCursor()
477 elif token == TY_ESC('=') : self.setMode(MODE_AppKeyPad)
478 elif token == TY_ESC('>') : self.resetMode(MODE_AppKeyPad)
479 elif token == TY_ESC('<') : self.setMode(MODE_Ansi) # VT52
481 elif token == TY_ESC_CS('(', '0') : self._setCharset(0, '0') # VT100
482 elif token == TY_ESC_CS('(', 'A') : self._setCharset(0, 'A') # VT100
483 elif token == TY_ESC_CS('(', 'B') : self._setCharset(0, 'B') # VT100
484 elif token == TY_ESC_CS('(', 'K') : self._setCharset(0, 'K') # VT220
485 elif token == TY_ESC_CS('(', 'R') : self._setCharset(0, 'R') # VT220
487 elif token == TY_ESC_CS(')', '0') : self._setCharset(1, '0') # VT100
488 elif token == TY_ESC_CS(')', 'A') : self._setCharset(1, 'A') # VT100
489 elif token == TY_ESC_CS(')', 'B') : self._setCharset(1, 'B') # VT100
490 elif token == TY_ESC_CS(')', 'K') : self._setCharset(1, 'K') # VT220
491 elif token == TY_ESC_CS(')', 'R') : self._setCharset(1, 'R') # VT220
493 elif token == TY_ESC_CS('*', '0') : self._setCharset(2, '0') # VT100
494 elif token == TY_ESC_CS('*', 'A') : self._setCharset(2, 'A') # VT100
495 elif token == TY_ESC_CS('*', 'B') : self._setCharset(2, 'B') # VT100
496 elif token == TY_ESC_CS('*', 'K') : self._setCharset(2, 'K') # VT220
497 elif token == TY_ESC_CS('*', 'R') : self._setCharset(2, 'R') # VT220
499 elif token == TY_ESC_CS('+', '0') : self._setCharset(3, '0') # VT100
500 elif token == TY_ESC_CS('+', 'A') : self._setCharset(3, 'A') # VT100
501 elif token == TY_ESC_CS('+', 'B') : self._setCharset(3, 'B') # VT100
502 elif token == TY_ESC_CS('+', 'K') : self._setCharset(3, 'K') # VT220
503 elif token == TY_ESC_CS('+', 'R') : self._setCharset(3, 'R') # VT220
505 elif token == TY_ESC_CS('%', 'G') : self._setCodec(1) # LINUX
506 elif token == TY_ESC_CS('%', '@') : self._setCodec(0) # LINUX
508 elif token == TY_ESC_DE('3') : pass # IGNORED: double high, top half
509 elif token == TY_ESC_DE('4') : pass # IGNORED: double high, bottom half
510 elif token == TY_ESC_DE('5') : pass # IGNORED: single width, single high
511 elif token == TY_ESC_DE('6') : pass # IGNORED: double width, single high
512 elif token == TY_ESC_DE('8') : self._scr.helpAlign()
514 elif token == TY_CSI_PS('K', 0): self._scr.clearToEndOfLine()
515 elif token == TY_CSI_PS('K', 1): self._scr.clearToBeginOfLine()
516 elif token == TY_CSI_PS('K', 2): self._scr.clearEntireLine()
517 elif token == TY_CSI_PS('J', 0): self._scr.clearToEndOfScreen()
518 elif token == TY_CSI_PS('J', 1): self._scr.clearToBeginOfScreen()
519 elif token == TY_CSI_PS('J', 2): self._scr.clearEntireScreen()
520 elif token == TY_CSI_PS('g', 0): self._scr.changeTabStop(False) # VT100
521 elif token == TY_CSI_PS('g', 3): self._scr.clearTabStops() # VT100
522 elif token == TY_CSI_PS('h', 4): self._scr.setMode(screen.MODE_Insert)
523 elif token == TY_CSI_PS('h', 20): self.setMode(screen.MODE_NewLine)
524 elif token == TY_CSI_PS('i', 0): pass # IGNORE: attached printer # VT100
525 elif token == TY_CSI_PS('i', 4): pass # IGNORE: attached printer # VT100
526 elif token == TY_CSI_PS('i', 5): self.setPrinterMode(True) # VT100
527 elif token == TY_CSI_PS('l', 4): self._scr.resetMode(screen.MODE_Insert)
528 elif token == TY_CSI_PS('l', 20): self.resetMode(screen.MODE_NewLine)
529 elif token == TY_CSI_PS('s', 0): self._saveCursor() # XXX Not in spec
530 elif token == TY_CSI_PS('u', 0): self._restoreCursor() # XXX Not in spec
532 elif token == TY_CSI_PS('m', 0): self._scr.setDefaultRendition()
533 elif token == TY_CSI_PS('m', 1): self._scr.setRendition(ca.RE_BOLD) # VT100
534 elif token == TY_CSI_PS('m', 4): self._scr.setRendition(ca.RE_UNDERLINE) # VT100
535 elif token == TY_CSI_PS('m', 5): self._scr.setRendition(ca.RE_BLINK) # VT100
536 elif token == TY_CSI_PS('m', 7): self._scr.setRendition(ca.RE_REVERSE)
537 elif token == TY_CSI_PS('m', 10): pass # IGNORED: mapping related # LINUX
538 elif token == TY_CSI_PS('m', 11): pass # IGNORED: mapping related # LINUX
539 elif token == TY_CSI_PS('m', 12): pass # IGNORED: mapping related # LINUX
540 elif token == TY_CSI_PS('m', 22): self._scr.resetRendition(ca.RE_BOLD)
541 elif token == TY_CSI_PS('m', 24): self._scr.resetRendition(ca.RE_UNDERLINE)
542 elif token == TY_CSI_PS('m', 25): self._scr.resetRendition(ca.RE_BLINK)
543 elif token == TY_CSI_PS('m', 27): self._scr.resetRendition(ca.RE_REVERSE)
545 elif token == TY_CSI_PS('m', 30): self._scr.setForeColor(0)
546 elif token == TY_CSI_PS('m', 31): self._scr.setForeColor(1)
547 elif token == TY_CSI_PS('m', 32): self._scr.setForeColor(2)
548 elif token == TY_CSI_PS('m', 33): self._scr.setForeColor(3)
549 elif token == TY_CSI_PS('m', 34): self._scr.setForeColor(4)
550 elif token == TY_CSI_PS('m', 35): self._scr.setForeColor(5)
551 elif token == TY_CSI_PS('m', 36): self._scr.setForeColor(6)
552 elif token == TY_CSI_PS('m', 37): self._scr.setForeColor(7)
553 elif token == TY_CSI_PS('m', 39): self._scr.setForeColorToDefault()
555 elif token == TY_CSI_PS('m', 40): self._scr.setBackColor(0)
556 elif token == TY_CSI_PS('m', 41): self._scr.setBackColor(1)
557 elif token == TY_CSI_PS('m', 42): self._scr.setBackColor(2)
558 elif token == TY_CSI_PS('m', 43): self._scr.setBackColor(3)
559 elif token == TY_CSI_PS('m', 44): self._scr.setBackColor(4)
560 elif token == TY_CSI_PS('m', 45): self._scr.setBackColor(5)
561 elif token == TY_CSI_PS('m', 46): self._scr.setBackColor(6)
562 elif token == TY_CSI_PS('m', 47): self._scr.setBackColor(7)
563 elif token == TY_CSI_PS('m', 49): self._scr.setBackColorToDefault()
565 elif token == TY_CSI_PS('m', 90): self._scr.setForeColor( 8)
566 elif token == TY_CSI_PS('m', 91): self._scr.setForeColor( 9)
567 elif token == TY_CSI_PS('m', 92): self._scr.setForeColor(10)
568 elif token == TY_CSI_PS('m', 93): self._scr.setForeColor(11)
569 elif token == TY_CSI_PS('m', 94): self._scr.setForeColor(12)
570 elif token == TY_CSI_PS('m', 95): self._scr.setForeColor(13)
571 elif token == TY_CSI_PS('m', 96): self._scr.setForeColor(14)
572 elif token == TY_CSI_PS('m', 97): self._scr.setForeColor(15)
574 elif token == TY_CSI_PS('m', 100): self._scr.setBackColor( 8)
575 elif token == TY_CSI_PS('m', 101): self._scr.setBackColor( 9)
576 elif token == TY_CSI_PS('m', 102): self._scr.setBackColor(10)
577 elif token == TY_CSI_PS('m', 103): self._scr.setBackColor(11)
578 elif token == TY_CSI_PS('m', 104): self._scr.setBackColor(12)
579 elif token == TY_CSI_PS('m', 105): self._scr.setBackColor(13)
580 elif token == TY_CSI_PS('m', 106): self._scr.setBackColor(14)
581 elif token == TY_CSI_PS('m', 107): self._scr.setBackColor(15)
583 elif token == TY_CSI_PS('n', 5): self.reportStatus()
584 elif token == TY_CSI_PS('n', 6): self.reportCursorPosition()
585 elif token == TY_CSI_PS('q', 0): pass # IGNORED: LEDs off # VT100 XXX Not in spec
586 elif token == TY_CSI_PS('q', 1): pass # IGNORED: LED1 on # VT100 XXX Not in spec
587 elif token == TY_CSI_PS('q', 2): pass # IGNORED: LED2 on # VT100 XXX Not in spec
588 elif token == TY_CSI_PS('q', 3): pass # IGNORED: LED3 on # VT100 XXX Not in spec
589 elif token == TY_CSI_PS('q', 4): pass # IGNORED: LED4 on # VT100 XXX Not in spec
590 elif token == TY_CSI_PS('x', 0):self.reportTerminalParams(2) # VT100
591 elif token == TY_CSI_PS('x', 1):self.reportTerminalParams(3) # VT100
593 elif token == TY_CSI_PN('@'): self._scr.insertChars(p)
594 elif token == TY_CSI_PN('A'): self._scr.cursorUp(p) # VT100
595 elif token == TY_CSI_PN('B'): self._scr.cursorDown(p) # VT100
596 elif token == TY_CSI_PN('C'): self._scr.cursorRight(p) # VT100
597 elif token == TY_CSI_PN('D'): self._scr.cursorLeft(p) # VT100
598 elif token == TY_CSI_PN('G'): self._scr.setCursorX(p) # LINUX
599 elif token == TY_CSI_PN('H'): self._scr.setCursorYX(p, q) # VT100
600 elif token == TY_CSI_PN('L'): self._scr.insertLines(p)
601 elif token == TY_CSI_PN('M'): self._scr.deleteLines(p)
602 elif token == TY_CSI_PN('P'): self._scr.deleteChars(p)
603 elif token == TY_CSI_PN('X'): self._scr.eraseChars (p)
604 elif token == TY_CSI_PN('c'): self.reportTerminalType() # VT100
605 elif token == TY_CSI_PN('d'): self._scr.setCursorY(p) # LINUX
606 elif token == TY_CSI_PN('f'): self._scr.setCursorYX(p, q) # VT100
607 elif token == TY_CSI_PN('r'): self._setMargins(p, q) # VT100 XXX Not in spec
608 elif token == TY_CSI_PN('y'): pass # IGNORED: Confidence test # VT100 XXX Not in spec
610 elif token == TY_CSI_PR('h', 1): self.setMode(MODE_AppCuKeys) # VT100
611 elif token == TY_CSI_PR('l', 1): self.resetMode(MODE_AppCuKeys) # VT100
612 elif token == TY_CSI_PR('s', 1): self.saveMode(MODE_AppCuKeys) # FIXME
613 elif token == TY_CSI_PR('r', 1): self.restoreMode(MODE_AppCuKeys) # FIXME
615 elif token == TY_CSI_PR('l', 2): self.resetMode(MODE_Ansi) # VT100
617 elif token == TY_CSI_PR('h', 3): self._setColumns(132) # VT100
618 elif token == TY_CSI_PR('l', 3): self._setColumns(80) # VT100
620 elif token == TY_CSI_PR('h', 4): pass # IGNORED: soft scrolling # VT100
621 elif token == TY_CSI_PR('l', 4): pass # IGNORED: soft scrolling # VT100
623 elif token == TY_CSI_PR('h', 5): self._scr.setMode(screen.MODE_Screen) # VT100
624 elif token == TY_CSI_PR('l', 5): self._scr.resetMode(screen.MODE_Screen) # VT100
626 elif token == TY_CSI_PR('h', 6): self._scr.setMode(screen.MODE_Origin) # VT100
627 elif token == TY_CSI_PR('l', 6): self._scr.resetMode(screen.MODE_Origin) # VT100
628 elif token == TY_CSI_PR('s', 6): self._scr.saveMode(screen.MODE_Origin) # FIXME
629 elif token == TY_CSI_PR('r', 6): self._scr.restoreMode(screen.MODE_Origin) # FIXME
631 elif token == TY_CSI_PR('h', 7): self._scr.setMode(screen.MODE_Wrap) # VT100
632 elif token == TY_CSI_PR('l', 7): self._scr.resetMode(screen.MODE_Wrap) # VT100
633 elif token == TY_CSI_PR('s', 7): self._scr.saveMode(screen.MODE_Wrap) # FIXME
634 elif token == TY_CSI_PR('r', 7): self._scr.restoreMode(screen.MODE_Wrap) # FIXME
636 elif token == TY_CSI_PR('h', 8): pass # IGNORED: autorepeat on # VT100
637 elif token == TY_CSI_PR('l', 8): pass # IGNORED: autorepeat off # VT100
639 elif token == TY_CSI_PR('h', 9): pass # IGNORED: interlace # VT100
640 elif token == TY_CSI_PR('l', 9): pass # IGNORED: interlace # VT100
642 elif token == TY_CSI_PR('h', 25): self.setMode(screen.MODE_Cursor) # VT100
643 elif token == TY_CSI_PR('l', 25): self.resetMode(screen.MODE_Cursor) # VT100
645 elif token == TY_CSI_PR('h', 41): pass # IGNORED: obsolete more(1) fix # XTERM
646 elif token == TY_CSI_PR('l', 41): pass # IGNORED: obsolete more(1) fix # XTERM
647 elif token == TY_CSI_PR('s', 41): pass # IGNORED: obsolete more(1) fix # XTERM
648 elif token == TY_CSI_PR('r', 41): pass # IGNORED: obsolete more(1) fix # XTERM
650 elif token == TY_CSI_PR('h', 47): self.setMode(MODE_AppScreen) # VT100
651 elif token == TY_CSI_PR('l', 47): self.resetMode(MODE_AppScreen) # VT100
652 elif token == TY_CSI_PR('s', 47): self.saveMode(MODE_AppScreen) # XTERM
653 elif token == TY_CSI_PR('r', 47): self.restoreMode(MODE_AppScreen) # XTERM
655 # XTerm defines the following modes:
656 # SET_VT200_MOUSE 1000
657 # SET_VT200_HIGHLIGHT_MOUSE 1001
658 # SET_BTN_EVENT_MOUSE 1002
659 # SET_ANY_EVENT_MOUSE 1003
660 #
661 # FIXME: Modes 1000,1002 and 1003 have subtle differences which we don't
662 # support yet, we treat them all the same.
664 elif token == TY_CSI_PR('h', 1000): self.setMode(MODE_Mouse1000) # XTERM
665 elif token == TY_CSI_PR('l', 1000): self.resetMode(MODE_Mouse1000) # XTERM
666 elif token == TY_CSI_PR('s', 1000): self.saveMode(MODE_Mouse1000) # XTERM
667 elif token == TY_CSI_PR('r', 1000): self.restoreMode(MODE_Mouse1000) # XTERM
669 elif token == TY_CSI_PR('h', 1001): pass # IGNORED: hilite mouse tracking # XTERM
670 elif token == TY_CSI_PR('l', 1001): self.resetMode(MODE_Mouse1000) # XTERM
671 elif token == TY_CSI_PR('s', 1001): pass # IGNORED: hilite mouse tracking # XTERM
672 elif token == TY_CSI_PR('r', 1001): pass # IGNORED: hilite mouse tracking # XTERM
674 elif token == TY_CSI_PR('h', 1002): self.setMode(MODE_Mouse1000) # XTERM
675 elif token == TY_CSI_PR('l', 1002): self.resetMode(MODE_Mouse1000) # XTERM
676 elif token == TY_CSI_PR('s', 1002): self.saveMode(MODE_Mouse1000) # XTERM
677 elif token == TY_CSI_PR('r', 1002): self.restoreMode(MODE_Mouse1000) # XTERM
679 elif token == TY_CSI_PR('h', 1003): self.setMode(MODE_Mouse1000) # XTERM
680 elif token == TY_CSI_PR('l', 1003): self.resetMode(MODE_Mouse1000) # XTERM
681 elif token == TY_CSI_PR('s', 1003): self.saveMode(MODE_Mouse1000) # XTERM
682 elif token == TY_CSI_PR('r', 1003): self.restoreMode(MODE_Mouse1000) # XTERM
684 elif token == TY_CSI_PR('h', 1047): self.setMode(MODE_AppScreen) # XTERM
685 elif token == TY_CSI_PR('l', 1047): # XTERM
686 self._screen[1].clearEntireScreen()
687 self.resetMode(MODE_AppScreen)
688 elif token == TY_CSI_PR('s', 1047): self.saveMode(MODE_AppScreen) # XTERM
689 elif token == TY_CSI_PR('r', 1047): self.restoreMode(MODE_AppScreen) # XTERM
691 # FIXME: Unitoken: save translations
692 elif token == TY_CSI_PR('h', 1048): self._saveCursor() # XTERM
693 elif token == TY_CSI_PR('l', 1048): self._restoreCursor() # XTERM
694 elif token == TY_CSI_PR('s', 1048): self._saveCursor() # XTERM
695 elif token == TY_CSI_PR('r', 1048): self._restoreCursor() # XTERM
697 # FIXME: every once new sequences like this pop up in xterm.
698 # Here's a guess of what they could mean.
699 elif token == TY_CSI_PR('h', 1049): # XTERM
700 self._saveCursor()
701 self._screen[1].clearEntireScreen()
702 self.setMode(MODE_AppScreen)
703 elif token == TY_CSI_PR('l', 1049): # XTERM
704 self.resetMode(MODE_AppScreen)
705 self._restoreCursor()
707 # FIXME: when changing between vt52 and ansi mode evtl do some resetting.
708 elif token == TY_VT52('A'): self._scr.cursorUp(1) # VT52
709 elif token == TY_VT52('B'): self._scr.cursorDown(1) # VT52
710 elif token == TY_VT52('C'): self._scr.cursorRight(1) # VT52
711 elif token == TY_VT52('D'): self._scr.cursorLeft(1) # VT52
713 elif token == TY_VT52('F'): self._setAndUseCharset(0, '0') # VT52
714 elif token == TY_VT52('G'): self._setAndUseCharset(0, 'B') # VT52
716 elif token == TY_VT52('H'): self._scr.setCursorYX(1, 1) # VT52
717 elif token == TY_VT52('I'): self._scr.reverseIndex() # VT52
718 elif token == TY_VT52('J'): self._scr.clearToEndOfScreen() # VT52
719 elif token == TY_VT52('K'): self._scr.clearToEndOfLine() # VT52
720 elif token == TY_VT52('Y'): self._scr.setCursorYX(p-31, q-31 ) # VT52
721 elif token == TY_VT52('Z'): self.reportTerminalType() # VT52
722 elif token == TY_VT52('<'): self.setMode(MODE_Ansi) # VT52
723 elif token == TY_VT52('='): self.setMode(MODE_AppKeyPad) # VT52
724 elif token == TY_VT52('>'): self.resetMode(MODE_AppKeyPad) # VT52
726 elif token == TY_CSI_PG('c') : self.reportSecondaryAttributes() # VT100
728 else:
729 self.reportErrorToken(token, p, q);
731 def reportErrorToken(self, token, p, q):
732 print 'undecodable', token, p, q
734 def reportCursorPosition(self):
735 self.sendString("\033[%d;%dR" % (self._scr.getCursorX()+1,
736 self._scr.getCursorY()+1))
738 def setPrinterMode(self, on):
739 if on:
740 cmd = os.getenv("PRINT_COMMAND", "cat > /dev/null")
741 self._print_fd = os.popen(cmd, "w")
742 else:
743 self._print_fd = None
745 def printScan(self, cc):
746 assert self._print_fd
747 if cc == CTRL('Q') or cc == CTRL('S') or cc == 0:
748 return
749 self._pbuf.append(cc) # advance the state
750 s = self._pbuf
751 p = len(self._pbuf)
752 if lec(p, s, 1, 0, ESC): return
753 if lec(p, s, 2, 1, ord('[')): return
754 if lec(p, s, 3, 2, ord('4')): return
755 if lec(p, s, 3, 2, ord('5')): return
756 if lec(p, s, 4, 3, ord('i')) and s[2] == ord('4'):
757 self.setPrinterMode(False)
758 self._resetToken()
759 return
760 self._print_fd.write(''.join([chr(c) for c in s]))
761 self._resetToken()
763 def _XtermHack(self):
764 i = 2
765 arg = ''
766 while ord('0') <= self._pbuf[i] < ord('9'):
767 arg += chr(self._pbuf[i])
768 i += 1
769 arg = int(arg)
770 if self._pbuf[i] != ord(';'):
771 self.reportErrorToken('xterm hack', len(self._pbuf), self._pbuf[-1])
772 string = ''.join([chr(c) for c in self._pbuf[i+1:-1]])
773 # arg=0 changes title and icon, arg=1 only icon, arg=2 only title
774 self.myemit('changeTitle', (arg, string))
776 # Obsolete stuff
778 def reportTerminalType(self):
779 if self.getMode(MODE_Ansi):
780 self.sendString("\033[?1;2c") # I'm a VT100
781 else:
782 self.sendString("\033/Z") # I'm a VT52
784 def reportSecondaryAttributes(self):
785 if self.getMode(MODE_Ansi):
786 self.sendString("\033[>0;115;0c") # Why 115 ?
787 else:
788 self.sendString("\033/Z") # I don't think VT52 knows about it...
790 def reportTerminalParams(self, p):
791 self.sendString("\033[%d;1;1;112;112;1;0x" % p) # Not really true
793 def reportStatus(self):
794 """VT100. Device status report. 0 = Ready"""
795 self.sendString("\033[0n")
797 def reportAnswerBack(self):
798 """ANSWER_BACK "" // This is really obsolete VT100 stuff."""
799 self.sendString(os.getenv("ANSWER_BACK", ''))
801 # Mouse Handling ##########################################################
803 def onMouse(self, cb, cx, cy):
804 """Mouse clicks are possibly reported to the client application if
805 it has issued interest in them.
806 They are normally consumed by the widget for copy and paste, but may
807 be propagated from the widget when gui->setMouseMarks is set via
808 setMode(MODE_Mouse1000).
810 `x',`y' are 1-based.
811 `ev' (event) indicates the button pressed (0-2)
812 or a general mouse release (3).
813 """
814 if self._connected:
815 self.sendString("\033[M%c%c%c" % (cb+040, cx+040, cy+040))
817 # Keyboard Handling #######################################################
819 def scrollLock(self, lock):
820 self._hold_screen = lock
821 if lock:
822 self.sendString("\023") # XOFF (^S)
823 else:
824 self.sendString("\021") # XON (^Q)
826 def _onScrollLock(self):
827 self.scrollLock(not self._hold_screen)
829 def onKeyPress(self, ev):
830 """char received from the gui"""
831 if not self._connected: # Someone else gets the keys
832 return
833 self.myemit("notifySessionState", (NOTIFYNORMAL,))
834 ev_state = ev.state()
835 try:
836 entry = self._key_trans.findEntry(ev.key(),
837 self.getMode(screen.MODE_NewLine),
838 self.getMode(MODE_Ansi),
839 self.getMode(MODE_AppCuKeys),
840 ev_state & ControlButton == ControlButton,
841 ev_state & ShiftButton == ShiftButton,
842 ev_state & AltButton == AltButton)
843 except kt.EntryNotFound:
844 cmd = kt.CMD_none
845 else:
846 cmd = entry.cmd
847 if cmd == kt.CMD_emitClipboard: self._gui.emitSelection(False, False)
848 elif cmd == kt.CMD_emitSelection: self._gui.emitSelection(True, False)
849 elif cmd == kt.CMD_scrollPageUp: self._gui.doScroll(-self._gui.lines/2)
850 elif cmd == kt.CMD_scrollPageDown: self._gui.doScroll(+self._gui.lines/2)
851 elif cmd == kt.CMD_scrollLineUp: self._gui.doScroll(-1)
852 elif cmd == kt.CMD_scrollLineDown: self._gui.doScroll(+1)
853 elif cmd == kt.CMD_prevSession:
854 if qt.QApplication.reverseLayout():
855 self.myemit("nextSession")
856 else:
857 self.myemit("prevSession")
858 elif cmd == kt.CMD_nextSession:
859 if qt.QApplication.reverseLayout():
860 self.myemit("prevSession")
861 else:
862 self.myemit("nextSession")
863 elif cmd == kt.CMD_newSession: self.myemit("newSession")
864 elif cmd == kt.CMD_renameSession: self.myemit("renameSession")
865 elif cmd == kt.CMD_activateMenu: self.myemit("activateMenu")
866 elif cmd == kt.CMD_moveSessionLeft:
867 if qt.QApplication.reverseLayout():
868 self.myemit("moveSessionRight")
869 else:
870 self.myemit("moveSessionLeft")
871 elif cmd == kt.CMD_moveSessionRight:
872 if qt.QApplication.reverseLayout():
873 self.myemit("moveSessionLeft")
874 else:
875 self.myemit("moveSessionRight")
876 elif cmd == kt.CMD_scrollLock: self._onScrollLock()
878 # Revert to non-history when typing
879 if self._scr.hist_cursor != self._scr.getHistLines() and not ev.text().isEmpty() or \
880 ev.key() == QEvent.Key_Down or ev.key() == QEvent.Key_Up or \
881 ev.key() == QEvent.Key_Left or ev.key() == QEvent.Key_Right or \
882 ev.key() == QEvent.Key_PageUp or ev.key() == QEvent.Key_PageDown:
883 self._scr.hist_cursor = self._scr.getHistLines()
885 if cmd == kt.CMD_send:
886 if ev_state & AltButton and not entry.metaspecified():
887 self.sendString("\033") # ESC this is the ALT prefix
888 self.sendString(entry.txt)
889 return
890 # fall back handling
891 if not ev.text().isEmpty():
892 if ev_state & AltButton:
893 self.sendString("\033") # ESC this is the ALT prefix
894 s = self._codec.fromUnicode(ev.text()) # Encode for application
895 # FIXME: In Qt 2, QKeyEvent::text() would return "\003" for Ctrl-C etc.
896 # while in Qt 3 it returns the actual key ("c" or "C") which caused
897 # the ControlButton to be ignored. This hack seems to work for
898 # latin1 locales at least. Please anyone find a clean solution (malte)
899 if ev_state & ControlButton:
900 #print ev.ascii(), ev.key()
901 s.fill(chr(ev.ascii()), 1)
902 self.sendString(str(s))
904 # Charset related part of the emulation state #############################
906 def _applyCharset(self, c):
907 return self._charset[self._scr is self._screen[1]].applyCharset(c)
909 def _resetCharset(self, scrno):
910 self._charset[scrno].reset()
912 def _setCharset(self, n, cs):
913 self._charset[0].setCharset(n, cs)
914 self._charset[1].setCharset(n, cs)
916 def _setAndUseCharset(self, n, cs):
917 self._charset[self._scr is self._screen[1]].setCharset(n, cs)
919 def _useCharset(self, n):
920 self._charset[self._scr is self._screen[1]].useCharset(n)
922 def _saveCursor(self):
923 """save cursor position and rendition attribute settings"""
924 self._charset[self._scr is self._screen[1]].save()
925 self._scr.saveCursor()
927 def _restoreCursor(self):
928 """restor cursor position and rendition attribute settings"""
929 self._charset[self._scr is self._screen[1]].restore()
930 self._scr.restoreCursor()
932 # Mode Operations #########################################################
933 #
934 # Some of the emulations state is either added to the state of the screens.
935 #
936 # This causes some scoping problems, since different emulations choose to
937 # located the mode either to the current screen or to both.
938 #
939 # For strange reasons, the extend of the rendition attributes ranges over
940 # all screens and not over the actual screen.
941 #
942 # We decided on the precise precise extend, somehow.
944 def _resetModes(self):
945 """Mode related part of the state. These are all booleans."""
946 self.resetMode(MODE_Mouse1000)
947 self.saveMode(MODE_Mouse1000)
948 self.resetMode(MODE_AppScreen)
949 self.saveMode(MODE_AppScreen)
950 self.setMode(MODE_Ansi)
951 self._hold_screen = False
952 # Obsolete modes
953 self.resetMode(MODE_AppCuKeys)
954 self.saveMode(MODE_AppCuKeys)
955 self.resetMode(screen.MODE_NewLine)
956 # XXX those initialisations were missing from cpp code
957 self.resetMode(MODE_AppKeyPad)
958 self.resetMode(screen.MODE_Cursor)
960 def setMode(self, m):
961 self._curr_mode[m] = True
962 if m == MODE_Mouse1000:
963 self._gui.setMouseMarks(False)
964 elif m == MODE_AppScreen:
965 self._setScreen(1)
966 if m < screen.MODES_SCREEN:
967 self._screen[0].setMode(m)
968 self._screen[1].setMode(m)
970 def resetMode(self, m):
971 self._curr_mode[m] = False
972 if m == MODE_Mouse1000:
973 self._gui.setMouseMarks(True)
974 elif m == MODE_AppScreen:
975 self._setScreen(0)
976 if m < screen.MODES_SCREEN:
977 self._screen[0].resetMode(m)
978 self._screen[1].resetMode(m)
980 def saveMode(self, m):
981 self._save_mode[m] = self._curr_mode[m]
983 def restoreMode(self, m):
984 if self._save_mode[m]:
985 self.setMode(m)
986 else:
987 self.resetMode(m)
989 def getMode(self, m):
990 return self._curr_mode[m]
992 def setConnect(self, c):
993 super(EmuVt102, self).setConnect(c)
994 if c:
995 # Refresh mouse mode
996 if self.getMode(MODE_Mouse1000):
997 self.setMode(MODE_Mouse1000)
998 else:
999 self.resetMode(MODE_Mouse1000)
1001 def _setMargins(self, t, b):
1002 self._screen[0].setMargins(t, b)
1003 self._screen[1].setMargins(t, b)
1005 # private #################################################################
1007 def _resetToken(self):
1008 self._pbuf = []
1009 self._argv = [0]
1011 def _addDigit(self, dig):
1012 self._argv[-1] = 10*self._argv[-1] + dig