~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/insults/insults.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_insults -*-
 
2
# Copyright (c) 2001-2004 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']