Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / conch / insults / insults.py
1 # -*- test-case-name: twisted.conch.test.test_insults -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 VT102 and VT220 terminal manipulation.
7
8 @author: Jp Calderone
9 """
10
11 from zope.interface import implements, Interface
12
13 from twisted.internet import protocol, defer, interfaces as iinternet
14
15 class ITerminalProtocol(Interface):
16     def makeConnection(transport):
17         """Called with an L{ITerminalTransport} when a connection is established.
18         """
19
20     def keystrokeReceived(keyID, modifier):
21         """A keystroke was received.
22
23         Each keystroke corresponds to one invocation of this method.
24         keyID is a string identifier for that key.  Printable characters
25         are represented by themselves.  Control keys, such as arrows and
26         function keys, are represented with symbolic constants on
27         L{ServerProtocol}.
28         """
29
30     def terminalSize(width, height):
31         """Called to indicate the size of the terminal.
32
33         A terminal of 80x24 should be assumed if this method is not
34         called.  This method might not be called for real terminals.
35         """
36
37     def unhandledControlSequence(seq):
38         """Called when an unsupported control sequence is received.
39
40         @type seq: C{str}
41         @param seq: The whole control sequence which could not be interpreted.
42         """
43
44     def connectionLost(reason):
45         """Called when the connection has been lost.
46
47         reason is a Failure describing why.
48         """
49
50 class TerminalProtocol(object):
51     implements(ITerminalProtocol)
52
53     def makeConnection(self, terminal):
54         # assert ITerminalTransport.providedBy(transport), "TerminalProtocol.makeConnection must be passed an ITerminalTransport implementor"
55         self.terminal = terminal
56         self.connectionMade()
57
58     def connectionMade(self):
59         """Called after a connection has been established.
60         """
61
62     def keystrokeReceived(self, keyID, modifier):
63         pass
64
65     def terminalSize(self, width, height):
66         pass
67
68     def unhandledControlSequence(self, seq):
69         pass
70
71     def connectionLost(self, reason):
72         pass
73
74 class ITerminalTransport(iinternet.ITransport):
75     def cursorUp(n=1):
76         """Move the cursor up n lines.
77         """
78
79     def cursorDown(n=1):
80         """Move the cursor down n lines.
81         """
82
83     def cursorForward(n=1):
84         """Move the cursor right n columns.
85         """
86
87     def cursorBackward(n=1):
88         """Move the cursor left n columns.
89         """
90
91     def cursorPosition(column, line):
92         """Move the cursor to the given line and column.
93         """
94
95     def cursorHome():
96         """Move the cursor home.
97         """
98
99     def index():
100         """Move the cursor down one line, performing scrolling if necessary.
101         """
102
103     def reverseIndex():
104         """Move the cursor up one line, performing scrolling if necessary.
105         """
106
107     def nextLine():
108         """Move the cursor to the first position on the next line, performing scrolling if necessary.
109         """
110
111     def saveCursor():
112         """Save the cursor position, character attribute, character set, and origin mode selection.
113         """
114
115     def restoreCursor():
116         """Restore the previously saved cursor position, character attribute, character set, and origin mode selection.
117
118         If no cursor state was previously saved, move the cursor to the home position.
119         """
120
121     def setModes(modes):
122         """Set the given modes on the terminal.
123         """
124
125     def resetModes(mode):
126         """Reset the given modes on the terminal.
127         """
128
129
130     def setPrivateModes(modes):
131         """
132         Set the given DEC private modes on the terminal.
133         """
134
135
136     def resetPrivateModes(modes):
137         """
138         Reset the given DEC private modes on the terminal.
139         """
140
141
142     def applicationKeypadMode():
143         """Cause keypad to generate control functions.
144
145         Cursor key mode selects the type of characters generated by cursor keys.
146         """
147
148     def numericKeypadMode():
149         """Cause keypad to generate normal characters.
150         """
151
152     def selectCharacterSet(charSet, which):
153         """Select a character set.
154
155         charSet should be one of CS_US, CS_UK, CS_DRAWING, CS_ALTERNATE, or
156         CS_ALTERNATE_SPECIAL.
157
158         which should be one of G0 or G1.
159         """
160
161     def shiftIn():
162         """Activate the G0 character set.
163         """
164
165     def shiftOut():
166         """Activate the G1 character set.
167         """
168
169     def singleShift2():
170         """Shift to the G2 character set for a single character.
171         """
172
173     def singleShift3():
174         """Shift to the G3 character set for a single character.
175         """
176
177     def selectGraphicRendition(*attributes):
178         """Enabled one or more character attributes.
179
180         Arguments should be one or more of UNDERLINE, REVERSE_VIDEO, BLINK, or BOLD.
181         NORMAL may also be specified to disable all character attributes.
182         """
183
184     def horizontalTabulationSet():
185         """Set a tab stop at the current cursor position.
186         """
187
188     def tabulationClear():
189         """Clear the tab stop at the current cursor position.
190         """
191
192     def tabulationClearAll():
193         """Clear all tab stops.
194         """
195
196     def doubleHeightLine(top=True):
197         """Make the current line the top or bottom half of a double-height, double-width line.
198
199         If top is True, the current line is the top half.  Otherwise, it is the bottom half.
200         """
201
202     def singleWidthLine():
203         """Make the current line a single-width, single-height line.
204         """
205
206     def doubleWidthLine():
207         """Make the current line a double-width line.
208         """
209
210     def eraseToLineEnd():
211         """Erase from the cursor to the end of line, including cursor position.
212         """
213
214     def eraseToLineBeginning():
215         """Erase from the cursor to the beginning of the line, including the cursor position.
216         """
217
218     def eraseLine():
219         """Erase the entire cursor line.
220         """
221
222     def eraseToDisplayEnd():
223         """Erase from the cursor to the end of the display, including the cursor position.
224         """
225
226     def eraseToDisplayBeginning():
227         """Erase from the cursor to the beginning of the display, including the cursor position.
228         """
229
230     def eraseDisplay():
231         """Erase the entire display.
232         """
233
234     def deleteCharacter(n=1):
235         """Delete n characters starting at the cursor position.
236
237         Characters to the right of deleted characters are shifted to the left.
238         """
239
240     def insertLine(n=1):
241         """Insert n lines at the cursor position.
242
243         Lines below the cursor are shifted down.  Lines moved past the bottom margin are lost.
244         This command is ignored when the cursor is outside the scroll region.
245         """
246
247     def deleteLine(n=1):
248         """Delete n lines starting at the cursor position.
249
250         Lines below the cursor are shifted up.  This command is ignored when the cursor is outside
251         the scroll region.
252         """
253
254     def reportCursorPosition():
255         """Return a Deferred that fires with a two-tuple of (x, y) indicating the cursor position.
256         """
257
258     def reset():
259         """Reset the terminal to its initial state.
260         """
261
262     def unhandledControlSequence(seq):
263         """Called when an unsupported control sequence is received.
264
265         @type seq: C{str}
266         @param seq: The whole control sequence which could not be interpreted.
267         """
268
269
270 CSI = '\x1b'
271 CST = {'~': 'tilde'}
272
273 class modes:
274     """ECMA 48 standardized modes
275     """
276
277     # BREAKS YOPUR KEYBOARD MOFO
278     KEYBOARD_ACTION = KAM = 2
279
280     # When set, enables character insertion. New display characters
281     # move old display characters to the right. Characters moved past
282     # the right margin are lost.
283
284     # When reset, enables replacement mode (disables character
285     # insertion). New display characters replace old display
286     # characters at cursor position. The old character is erased.
287     INSERTION_REPLACEMENT = IRM = 4
288
289     # Set causes a received linefeed, form feed, or vertical tab to
290     # move cursor to first column of next line. RETURN transmits both
291     # a carriage return and linefeed. This selection is also called
292     # new line option.
293
294     # Reset causes a received linefeed, form feed, or vertical tab to
295     # move cursor to next line in current column. RETURN transmits a
296     # carriage return.
297     LINEFEED_NEWLINE = LNM = 20
298
299
300 class privateModes:
301     """ANSI-Compatible Private Modes
302     """
303     ERROR = 0
304     CURSOR_KEY = 1
305     ANSI_VT52 = 2
306     COLUMN = 3
307     SCROLL = 4
308     SCREEN = 5
309     ORIGIN = 6
310     AUTO_WRAP = 7
311     AUTO_REPEAT = 8
312     PRINTER_FORM_FEED = 18
313     PRINTER_EXTENT = 19
314
315     # Toggle cursor visibility (reset hides it)
316     CURSOR_MODE = 25
317
318
319 # Character sets
320 CS_US = 'CS_US'
321 CS_UK = 'CS_UK'
322 CS_DRAWING = 'CS_DRAWING'
323 CS_ALTERNATE = 'CS_ALTERNATE'
324 CS_ALTERNATE_SPECIAL = 'CS_ALTERNATE_SPECIAL'
325
326 # Groupings (or something?? These are like variables that can be bound to character sets)
327 G0 = 'G0'
328 G1 = 'G1'
329
330 # G2 and G3 cannot be changed, but they can be shifted to.
331 G2 = 'G2'
332 G3 = 'G3'
333
334 # Character attributes
335
336 NORMAL = 0
337 BOLD = 1
338 UNDERLINE = 4
339 BLINK = 5
340 REVERSE_VIDEO = 7
341
342 class Vector:
343     def __init__(self, x, y):
344         self.x = x
345         self.y = y
346
347 def log(s):
348     file('log', 'a').write(str(s) + '\n')
349
350 # XXX TODO - These attributes are really part of the
351 # ITerminalTransport interface, I think.
352 _KEY_NAMES = ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
353               'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN', 'NUMPAD_MIDDLE',
354               'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
355               'F10', 'F11', 'F12',
356
357               'ALT', 'SHIFT', 'CONTROL')
358
359 class _const(object):
360     """
361     @ivar name: A string naming this constant
362     """
363     def __init__(self, name):
364         self.name = name
365
366     def __repr__(self):
367         return '[' + self.name + ']'
368
369
370 FUNCTION_KEYS = [
371     _const(_name) for _name in _KEY_NAMES]
372
373 class ServerProtocol(protocol.Protocol):
374     implements(ITerminalTransport)
375
376     protocolFactory = None
377     terminalProtocol = None
378
379     TAB = '\t'
380     BACKSPACE = '\x7f'
381     ##
382
383     lastWrite = ''
384
385     state = 'data'
386
387     termSize = Vector(80, 24)
388     cursorPos = Vector(0, 0)
389     scrollRegion = None
390
391     # Factory who instantiated me
392     factory = None
393
394     def __init__(self, protocolFactory=None, *a, **kw):
395         """
396         @param protocolFactory: A callable which will be invoked with
397         *a, **kw and should return an ITerminalProtocol implementor.
398         This will be invoked when a connection to this ServerProtocol
399         is established.
400
401         @param a: Any positional arguments to pass to protocolFactory.
402         @param kw: Any keyword arguments to pass to protocolFactory.
403         """
404         # assert protocolFactory is None or ITerminalProtocol.implementedBy(protocolFactory), "ServerProtocol.__init__ must be passed an ITerminalProtocol implementor"
405         if protocolFactory is not None:
406             self.protocolFactory = protocolFactory
407         self.protocolArgs = a
408         self.protocolKwArgs = kw
409
410         self._cursorReports = []
411
412     def connectionMade(self):
413         if self.protocolFactory is not None:
414             self.terminalProtocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
415
416             try:
417                 factory = self.factory
418             except AttributeError:
419                 pass
420             else:
421                 self.terminalProtocol.factory = factory
422
423             self.terminalProtocol.makeConnection(self)
424
425     def dataReceived(self, data):
426         for ch in data:
427             if self.state == 'data':
428                 if ch == '\x1b':
429                     self.state = 'escaped'
430                 else:
431                     self.terminalProtocol.keystrokeReceived(ch, None)
432             elif self.state == 'escaped':
433                 if ch == '[':
434                     self.state = 'bracket-escaped'
435                     self.escBuf = []
436                 elif ch == 'O':
437                     self.state = 'low-function-escaped'
438                 else:
439                     self.state = 'data'
440                     self._handleShortControlSequence(ch)
441             elif self.state == 'bracket-escaped':
442                 if ch == 'O':
443                     self.state = 'low-function-escaped'
444                 elif ch.isalpha() or ch == '~':
445                     self._handleControlSequence(''.join(self.escBuf) + ch)
446                     del self.escBuf
447                     self.state = 'data'
448                 else:
449                     self.escBuf.append(ch)
450             elif self.state == 'low-function-escaped':
451                 self._handleLowFunctionControlSequence(ch)
452                 self.state = 'data'
453             else:
454                 raise ValueError("Illegal state")
455
456     def _handleShortControlSequence(self, ch):
457         self.terminalProtocol.keystrokeReceived(ch, self.ALT)
458
459     def _handleControlSequence(self, buf):
460         buf = '\x1b[' + buf
461         f = getattr(self.controlSequenceParser, CST.get(buf[-1], buf[-1]), None)
462         if f is None:
463             self.unhandledControlSequence(buf)
464         else:
465             f(self, self.terminalProtocol, buf[:-1])
466
467     def unhandledControlSequence(self, buf):
468         self.terminalProtocol.unhandledControlSequence(buf)
469
470     def _handleLowFunctionControlSequence(self, ch):
471         map = {'P': self.F1, 'Q': self.F2, 'R': self.F3, 'S': self.F4}
472         keyID = map.get(ch)
473         if keyID is not None:
474             self.terminalProtocol.keystrokeReceived(keyID, None)
475         else:
476             self.terminalProtocol.unhandledControlSequence('\x1b[O' + ch)
477
478     class ControlSequenceParser:
479         def A(self, proto, handler, buf):
480             if buf == '\x1b[':
481                 handler.keystrokeReceived(proto.UP_ARROW, None)
482             else:
483                 handler.unhandledControlSequence(buf + 'A')
484
485         def B(self, proto, handler, buf):
486             if buf == '\x1b[':
487                 handler.keystrokeReceived(proto.DOWN_ARROW, None)
488             else:
489                 handler.unhandledControlSequence(buf + 'B')
490
491         def C(self, proto, handler, buf):
492             if buf == '\x1b[':
493                 handler.keystrokeReceived(proto.RIGHT_ARROW, None)
494             else:
495                 handler.unhandledControlSequence(buf + 'C')
496
497         def D(self, proto, handler, buf):
498             if buf == '\x1b[':
499                 handler.keystrokeReceived(proto.LEFT_ARROW, None)
500             else:
501                 handler.unhandledControlSequence(buf + 'D')
502
503         def E(self, proto, handler, buf):
504             if buf == '\x1b[':
505                 handler.keystrokeReceived(proto.NUMPAD_MIDDLE, None)
506             else:
507                 handler.unhandledControlSequence(buf + 'E')
508
509         def F(self, proto, handler, buf):
510             if buf == '\x1b[':
511                 handler.keystrokeReceived(proto.END, None)
512             else:
513                 handler.unhandledControlSequence(buf + 'F')
514
515         def H(self, proto, handler, buf):
516             if buf == '\x1b[':
517                 handler.keystrokeReceived(proto.HOME, None)
518             else:
519                 handler.unhandledControlSequence(buf + 'H')
520
521         def R(self, proto, handler, buf):
522             if not proto._cursorReports:
523                 handler.unhandledControlSequence(buf + 'R')
524             elif buf.startswith('\x1b['):
525                 report = buf[2:]
526                 parts = report.split(';')
527                 if len(parts) != 2:
528                     handler.unhandledControlSequence(buf + 'R')
529                 else:
530                     Pl, Pc = parts
531                     try:
532                         Pl, Pc = int(Pl), int(Pc)
533                     except ValueError:
534                         handler.unhandledControlSequence(buf + 'R')
535                     else:
536                         d = proto._cursorReports.pop(0)
537                         d.callback((Pc - 1, Pl - 1))
538             else:
539                 handler.unhandledControlSequence(buf + 'R')
540
541         def Z(self, proto, handler, buf):
542             if buf == '\x1b[':
543                 handler.keystrokeReceived(proto.TAB, proto.SHIFT)
544             else:
545                 handler.unhandledControlSequence(buf + 'Z')
546
547         def tilde(self, proto, handler, buf):
548             map = {1: proto.HOME, 2: proto.INSERT, 3: proto.DELETE,
549                    4: proto.END,  5: proto.PGUP,   6: proto.PGDN,
550
551                    15: proto.F5,  17: proto.F6, 18: proto.F7,
552                    19: proto.F8,  20: proto.F9, 21: proto.F10,
553                    23: proto.F11, 24: proto.F12}
554
555             if buf.startswith('\x1b['):
556                 ch = buf[2:]
557                 try:
558                     v = int(ch)
559                 except ValueError:
560                     handler.unhandledControlSequence(buf + '~')
561                 else:
562                     symbolic = map.get(v)
563                     if symbolic is not None:
564                         handler.keystrokeReceived(map[v], None)
565                     else:
566                         handler.unhandledControlSequence(buf + '~')
567             else:
568                 handler.unhandledControlSequence(buf + '~')
569
570     controlSequenceParser = ControlSequenceParser()
571
572     # ITerminalTransport
573     def cursorUp(self, n=1):
574         assert n >= 1
575         self.cursorPos.y = max(self.cursorPos.y - n, 0)
576         self.write('\x1b[%dA' % (n,))
577
578     def cursorDown(self, n=1):
579         assert n >= 1
580         self.cursorPos.y = min(self.cursorPos.y + n, self.termSize.y - 1)
581         self.write('\x1b[%dB' % (n,))
582
583     def cursorForward(self, n=1):
584         assert n >= 1
585         self.cursorPos.x = min(self.cursorPos.x + n, self.termSize.x - 1)
586         self.write('\x1b[%dC' % (n,))
587
588     def cursorBackward(self, n=1):
589         assert n >= 1
590         self.cursorPos.x = max(self.cursorPos.x - n, 0)
591         self.write('\x1b[%dD' % (n,))
592
593     def cursorPosition(self, column, line):
594         self.write('\x1b[%d;%dH' % (line + 1, column + 1))
595
596     def cursorHome(self):
597         self.cursorPos.x = self.cursorPos.y = 0
598         self.write('\x1b[H')
599
600     def index(self):
601         self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
602         self.write('\x1bD')
603
604     def reverseIndex(self):
605         self.cursorPos.y = max(self.cursorPos.y - 1, 0)
606         self.write('\x1bM')
607
608     def nextLine(self):
609         self.cursorPos.x = 0
610         self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
611         self.write('\n')
612
613     def saveCursor(self):
614         self._savedCursorPos = Vector(self.cursorPos.x, self.cursorPos.y)
615         self.write('\x1b7')
616
617     def restoreCursor(self):
618         self.cursorPos = self._savedCursorPos
619         del self._savedCursorPos
620         self.write('\x1b8')
621
622     def setModes(self, modes):
623         # XXX Support ANSI-Compatible private modes
624         self.write('\x1b[%sh' % (';'.join(map(str, modes)),))
625
626     def setPrivateModes(self, modes):
627         self.write('\x1b[?%sh' % (';'.join(map(str, modes)),))
628
629     def resetModes(self, modes):
630         # XXX Support ANSI-Compatible private modes
631         self.write('\x1b[%sl' % (';'.join(map(str, modes)),))
632
633     def resetPrivateModes(self, modes):
634         self.write('\x1b[?%sl' % (';'.join(map(str, modes)),))
635
636     def applicationKeypadMode(self):
637         self.write('\x1b=')
638
639     def numericKeypadMode(self):
640         self.write('\x1b>')
641
642     def selectCharacterSet(self, charSet, which):
643         # XXX Rewrite these as dict lookups
644         if which == G0:
645             which = '('
646         elif which == G1:
647             which = ')'
648         else:
649             raise ValueError("`which' argument to selectCharacterSet must be G0 or G1")
650         if charSet == CS_UK:
651             charSet = 'A'
652         elif charSet == CS_US:
653             charSet = 'B'
654         elif charSet == CS_DRAWING:
655             charSet = '0'
656         elif charSet == CS_ALTERNATE:
657             charSet = '1'
658         elif charSet == CS_ALTERNATE_SPECIAL:
659             charSet = '2'
660         else:
661             raise ValueError("Invalid `charSet' argument to selectCharacterSet")
662         self.write('\x1b' + which + charSet)
663
664     def shiftIn(self):
665         self.write('\x15')
666
667     def shiftOut(self):
668         self.write('\x14')
669
670     def singleShift2(self):
671         self.write('\x1bN')
672
673     def singleShift3(self):
674         self.write('\x1bO')
675
676     def selectGraphicRendition(self, *attributes):
677         attrs = []
678         for a in attributes:
679             attrs.append(a)
680         self.write('\x1b[%sm' % (';'.join(attrs),))
681
682     def horizontalTabulationSet(self):
683         self.write('\x1bH')
684
685     def tabulationClear(self):
686         self.write('\x1b[q')
687
688     def tabulationClearAll(self):
689         self.write('\x1b[3q')
690
691     def doubleHeightLine(self, top=True):
692         if top:
693             self.write('\x1b#3')
694         else:
695             self.write('\x1b#4')
696
697     def singleWidthLine(self):
698         self.write('\x1b#5')
699
700     def doubleWidthLine(self):
701         self.write('\x1b#6')
702
703     def eraseToLineEnd(self):
704         self.write('\x1b[K')
705
706     def eraseToLineBeginning(self):
707         self.write('\x1b[1K')
708
709     def eraseLine(self):
710         self.write('\x1b[2K')
711
712     def eraseToDisplayEnd(self):
713         self.write('\x1b[J')
714
715     def eraseToDisplayBeginning(self):
716         self.write('\x1b[1J')
717
718     def eraseDisplay(self):
719         self.write('\x1b[2J')
720
721     def deleteCharacter(self, n=1):
722         self.write('\x1b[%dP' % (n,))
723
724     def insertLine(self, n=1):
725         self.write('\x1b[%dL' % (n,))
726
727     def deleteLine(self, n=1):
728         self.write('\x1b[%dM' % (n,))
729
730     def setScrollRegion(self, first=None, last=None):
731         if first is not None:
732             first = '%d' % (first,)
733         else:
734             first = ''
735         if last is not None:
736             last = '%d' % (last,)
737         else:
738             last = ''
739         self.write('\x1b[%s;%sr' % (first, last))
740
741     def resetScrollRegion(self):
742         self.setScrollRegion()
743
744     def reportCursorPosition(self):
745         d = defer.Deferred()
746         self._cursorReports.append(d)
747         self.write('\x1b[6n')
748         return d
749
750     def reset(self):
751         self.cursorPos.x = self.cursorPos.y = 0
752         try:
753             del self._savedCursorPos
754         except AttributeError:
755             pass
756         self.write('\x1bc')
757
758     # ITransport
759     def write(self, bytes):
760         if bytes:
761             self.lastWrite = bytes
762             self.transport.write('\r\n'.join(bytes.split('\n')))
763
764     def writeSequence(self, bytes):
765         self.write(''.join(bytes))
766
767     def loseConnection(self):
768         self.reset()
769         self.transport.loseConnection()
770
771     def connectionLost(self, reason):
772         if self.terminalProtocol is not None:
773             try:
774                 self.terminalProtocol.connectionLost(reason)
775             finally:
776                 self.terminalProtocol = None
777 # Add symbolic names for function keys
778 for name, const in zip(_KEY_NAMES, FUNCTION_KEYS):
779     setattr(ServerProtocol, name, const)
780
781
782
783 class ClientProtocol(protocol.Protocol):
784
785     terminalFactory = None
786     terminal = None
787
788     state = 'data'
789
790     _escBuf = None
791
792     _shorts = {
793         'D': 'index',
794         'M': 'reverseIndex',
795         'E': 'nextLine',
796         '7': 'saveCursor',
797         '8': 'restoreCursor',
798         '=': 'applicationKeypadMode',
799         '>': 'numericKeypadMode',
800         'N': 'singleShift2',
801         'O': 'singleShift3',
802         'H': 'horizontalTabulationSet',
803         'c': 'reset'}
804
805     _longs = {
806         '[': 'bracket-escape',
807         '(': 'select-g0',
808         ')': 'select-g1',
809         '#': 'select-height-width'}
810
811     _charsets = {
812         'A': CS_UK,
813         'B': CS_US,
814         '0': CS_DRAWING,
815         '1': CS_ALTERNATE,
816         '2': CS_ALTERNATE_SPECIAL}
817
818     # Factory who instantiated me
819     factory = None
820
821     def __init__(self, terminalFactory=None, *a, **kw):
822         """
823         @param terminalFactory: A callable which will be invoked with
824         *a, **kw and should return an ITerminalTransport provider.
825         This will be invoked when this ClientProtocol establishes a
826         connection.
827
828         @param a: Any positional arguments to pass to terminalFactory.
829         @param kw: Any keyword arguments to pass to terminalFactory.
830         """
831         # assert terminalFactory is None or ITerminalTransport.implementedBy(terminalFactory), "ClientProtocol.__init__ must be passed an ITerminalTransport implementor"
832         if terminalFactory is not None:
833             self.terminalFactory = terminalFactory
834         self.terminalArgs = a
835         self.terminalKwArgs = kw
836
837     def connectionMade(self):
838         if self.terminalFactory is not None:
839             self.terminal = self.terminalFactory(*self.terminalArgs, **self.terminalKwArgs)
840             self.terminal.factory = self.factory
841             self.terminal.makeConnection(self)
842
843     def connectionLost(self, reason):
844         if self.terminal is not None:
845             try:
846                 self.terminal.connectionLost(reason)
847             finally:
848                 del self.terminal
849
850     def dataReceived(self, bytes):
851         """
852         Parse the given data from a terminal server, dispatching to event
853         handlers defined by C{self.terminal}.
854         """
855         toWrite = []
856         for b in bytes:
857             if self.state == 'data':
858                 if b == '\x1b':
859                     if toWrite:
860                         self.terminal.write(''.join(toWrite))
861                         del toWrite[:]
862                     self.state = 'escaped'
863                 elif b == '\x14':
864                     if toWrite:
865                         self.terminal.write(''.join(toWrite))
866                         del toWrite[:]
867                     self.terminal.shiftOut()
868                 elif b == '\x15':
869                     if toWrite:
870                         self.terminal.write(''.join(toWrite))
871                         del toWrite[:]
872                     self.terminal.shiftIn()
873                 elif b == '\x08':
874                     if toWrite:
875                         self.terminal.write(''.join(toWrite))
876                         del toWrite[:]
877                     self.terminal.cursorBackward()
878                 else:
879                     toWrite.append(b)
880             elif self.state == 'escaped':
881                 fName = self._shorts.get(b)
882                 if fName is not None:
883                     self.state = 'data'
884                     getattr(self.terminal, fName)()
885                 else:
886                     state = self._longs.get(b)
887                     if state is not None:
888                         self.state = state
889                     else:
890                         self.terminal.unhandledControlSequence('\x1b' + b)
891                         self.state = 'data'
892             elif self.state == 'bracket-escape':
893                 if self._escBuf is None:
894                     self._escBuf = []
895                 if b.isalpha() or b == '~':
896                     self._handleControlSequence(''.join(self._escBuf), b)
897                     del self._escBuf
898                     self.state = 'data'
899                 else:
900                     self._escBuf.append(b)
901             elif self.state == 'select-g0':
902                 self.terminal.selectCharacterSet(self._charsets.get(b, b), G0)
903                 self.state = 'data'
904             elif self.state == 'select-g1':
905                 self.terminal.selectCharacterSet(self._charsets.get(b, b), G1)
906                 self.state = 'data'
907             elif self.state == 'select-height-width':
908                 self._handleHeightWidth(b)
909                 self.state = 'data'
910             else:
911                 raise ValueError("Illegal state")
912         if toWrite:
913             self.terminal.write(''.join(toWrite))
914
915
916     def _handleControlSequence(self, buf, terminal):
917         f = getattr(self.controlSequenceParser, CST.get(terminal, terminal), None)
918         if f is None:
919             self.terminal.unhandledControlSequence('\x1b[' + buf + terminal)
920         else:
921             f(self, self.terminal, buf)
922
923     class ControlSequenceParser:
924         def _makeSimple(ch, fName):
925             n = 'cursor' + fName
926             def simple(self, proto, handler, buf):
927                 if not buf:
928                     getattr(handler, n)(1)
929                 else:
930                     try:
931                         m = int(buf)
932                     except ValueError:
933                         handler.unhandledControlSequence('\x1b[' + buf + ch)
934                     else:
935                         getattr(handler, n)(m)
936             return simple
937         for (ch, fName) in (('A', 'Up'),
938                             ('B', 'Down'),
939                             ('C', 'Forward'),
940                             ('D', 'Backward')):
941             exec ch + " = _makeSimple(ch, fName)"
942         del _makeSimple
943
944         def h(self, proto, handler, buf):
945             # XXX - Handle '?' to introduce ANSI-Compatible private modes.
946             try:
947                 modes = map(int, buf.split(';'))
948             except ValueError:
949                 handler.unhandledControlSequence('\x1b[' + buf + 'h')
950             else:
951                 handler.setModes(modes)
952
953         def l(self, proto, handler, buf):
954             # XXX - Handle '?' to introduce ANSI-Compatible private modes.
955             try:
956                 modes = map(int, buf.split(';'))
957             except ValueError:
958                 handler.unhandledControlSequence('\x1b[' + buf + 'l')
959             else:
960                 handler.resetModes(modes)
961
962         def r(self, proto, handler, buf):
963             parts = buf.split(';')
964             if len(parts) == 1:
965                 handler.setScrollRegion(None, None)
966             elif len(parts) == 2:
967                 try:
968                     if parts[0]:
969                         pt = int(parts[0])
970                     else:
971                         pt = None
972                     if parts[1]:
973                         pb = int(parts[1])
974                     else:
975                         pb = None
976                 except ValueError:
977                     handler.unhandledControlSequence('\x1b[' + buf + 'r')
978                 else:
979                     handler.setScrollRegion(pt, pb)
980             else:
981                 handler.unhandledControlSequence('\x1b[' + buf + 'r')
982
983         def K(self, proto, handler, buf):
984             if not buf:
985                 handler.eraseToLineEnd()
986             elif buf == '1':
987                 handler.eraseToLineBeginning()
988             elif buf == '2':
989                 handler.eraseLine()
990             else:
991                 handler.unhandledControlSequence('\x1b[' + buf + 'K')
992
993         def H(self, proto, handler, buf):
994             handler.cursorHome()
995
996         def J(self, proto, handler, buf):
997             if not buf:
998                 handler.eraseToDisplayEnd()
999             elif buf == '1':
1000                 handler.eraseToDisplayBeginning()
1001             elif buf == '2':
1002                 handler.eraseDisplay()
1003             else:
1004                 handler.unhandledControlSequence('\x1b[' + buf + 'J')
1005
1006         def P(self, proto, handler, buf):
1007             if not buf:
1008                 handler.deleteCharacter(1)
1009             else:
1010                 try:
1011                     n = int(buf)
1012                 except ValueError:
1013                     handler.unhandledControlSequence('\x1b[' + buf + 'P')
1014                 else:
1015                     handler.deleteCharacter(n)
1016
1017         def L(self, proto, handler, buf):
1018             if not buf:
1019                 handler.insertLine(1)
1020             else:
1021                 try:
1022                     n = int(buf)
1023                 except ValueError:
1024                     handler.unhandledControlSequence('\x1b[' + buf + 'L')
1025                 else:
1026                     handler.insertLine(n)
1027
1028         def M(self, proto, handler, buf):
1029             if not buf:
1030                 handler.deleteLine(1)
1031             else:
1032                 try:
1033                     n = int(buf)
1034                 except ValueError:
1035                     handler.unhandledControlSequence('\x1b[' + buf + 'M')
1036                 else:
1037                     handler.deleteLine(n)
1038
1039         def n(self, proto, handler, buf):
1040             if buf == '6':
1041                 x, y = handler.reportCursorPosition()
1042                 proto.transport.write('\x1b[%d;%dR' % (x + 1, y + 1))
1043             else:
1044                 handler.unhandledControlSequence('\x1b[' + buf + 'n')
1045
1046         def m(self, proto, handler, buf):
1047             if not buf:
1048                 handler.selectGraphicRendition(NORMAL)
1049             else:
1050                 attrs = []
1051                 for a in buf.split(';'):
1052                     try:
1053                         a = int(a)
1054                     except ValueError:
1055                         pass
1056                     attrs.append(a)
1057                 handler.selectGraphicRendition(*attrs)
1058
1059     controlSequenceParser = ControlSequenceParser()
1060
1061     def _handleHeightWidth(self, b):
1062         if b == '3':
1063             self.terminal.doubleHeightLine(True)
1064         elif b == '4':
1065             self.terminal.doubleHeightLine(False)
1066         elif b == '5':
1067             self.terminal.singleWidthLine()
1068         elif b == '6':
1069             self.terminal.doubleWidthLine()
1070         else:
1071             self.terminal.unhandledControlSequence('\x1b#' + b)
1072
1073
1074 __all__ = [
1075     # Interfaces
1076     'ITerminalProtocol', 'ITerminalTransport',
1077
1078     # Symbolic constants
1079     'modes', 'privateModes', 'FUNCTION_KEYS',
1080
1081     'CS_US', 'CS_UK', 'CS_DRAWING', 'CS_ALTERNATE', 'CS_ALTERNATE_SPECIAL',
1082     'G0', 'G1', 'G2', 'G3',
1083
1084     'UNDERLINE', 'REVERSE_VIDEO', 'BLINK', 'BOLD', 'NORMAL',
1085
1086     # Protocol classes
1087     'ServerProtocol', 'ClientProtocol']