| 1 | # -*- test-case-name: twisted.conch.test.test_manhole -*- |
|---|
| 2 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 3 | # See LICENSE for details. |
|---|
| 4 | |
|---|
| 5 | """ |
|---|
| 6 | Asynchronous local terminal input handling |
|---|
| 7 | |
|---|
| 8 | @author: Jp Calderone |
|---|
| 9 | """ |
|---|
| 10 | |
|---|
| 11 | import os, tty, sys, termios |
|---|
| 12 | |
|---|
| 13 | from twisted.internet import reactor, stdio, protocol, defer |
|---|
| 14 | from twisted.python import failure, reflect, log |
|---|
| 15 | |
|---|
| 16 | from twisted.conch.insults.insults import ServerProtocol |
|---|
| 17 | from twisted.conch.manhole import ColoredManhole |
|---|
| 18 | |
|---|
| 19 | class UnexpectedOutputError(Exception): |
|---|
| 20 | pass |
|---|
| 21 | |
|---|
| 22 | class TerminalProcessProtocol(protocol.ProcessProtocol): |
|---|
| 23 | def __init__(self, proto): |
|---|
| 24 | self.proto = proto |
|---|
| 25 | self.onConnection = defer.Deferred() |
|---|
| 26 | |
|---|
| 27 | def connectionMade(self): |
|---|
| 28 | self.proto.makeConnection(self) |
|---|
| 29 | self.onConnection.callback(None) |
|---|
| 30 | self.onConnection = None |
|---|
| 31 | |
|---|
| 32 | def write(self, bytes): |
|---|
| 33 | self.transport.write(bytes) |
|---|
| 34 | |
|---|
| 35 | def outReceived(self, bytes): |
|---|
| 36 | self.proto.dataReceived(bytes) |
|---|
| 37 | |
|---|
| 38 | def errReceived(self, bytes): |
|---|
| 39 | self.transport.loseConnection() |
|---|
| 40 | if self.proto is not None: |
|---|
| 41 | self.proto.connectionLost(failure.Failure(UnexpectedOutputError(bytes))) |
|---|
| 42 | self.proto = None |
|---|
| 43 | |
|---|
| 44 | def childConnectionLost(self, childFD): |
|---|
| 45 | if self.proto is not None: |
|---|
| 46 | self.proto.childConnectionLost(childFD) |
|---|
| 47 | |
|---|
| 48 | def processEnded(self, reason): |
|---|
| 49 | if self.proto is not None: |
|---|
| 50 | self.proto.connectionLost(reason) |
|---|
| 51 | self.proto = None |
|---|
| 52 | |
|---|
| 53 | |
|---|
| 54 | |
|---|
| 55 | class ConsoleManhole(ColoredManhole): |
|---|
| 56 | """ |
|---|
| 57 | A manhole protocol specifically for use with L{stdio.StandardIO}. |
|---|
| 58 | """ |
|---|
| 59 | def connectionLost(self, reason): |
|---|
| 60 | """ |
|---|
| 61 | When the connection is lost, there is nothing more to do. Stop the |
|---|
| 62 | reactor so that the process can exit. |
|---|
| 63 | """ |
|---|
| 64 | reactor.stop() |
|---|
| 65 | |
|---|
| 66 | |
|---|
| 67 | |
|---|
| 68 | def runWithProtocol(klass): |
|---|
| 69 | fd = sys.__stdin__.fileno() |
|---|
| 70 | oldSettings = termios.tcgetattr(fd) |
|---|
| 71 | tty.setraw(fd) |
|---|
| 72 | try: |
|---|
| 73 | p = ServerProtocol(klass) |
|---|
| 74 | stdio.StandardIO(p) |
|---|
| 75 | reactor.run() |
|---|
| 76 | finally: |
|---|
| 77 | termios.tcsetattr(fd, termios.TCSANOW, oldSettings) |
|---|
| 78 | os.write(fd, "\r\x1bc\r") |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | def main(argv=None): |
|---|
| 83 | log.startLogging(file('child.log', 'w')) |
|---|
| 84 | |
|---|
| 85 | if argv is None: |
|---|
| 86 | argv = sys.argv[1:] |
|---|
| 87 | if argv: |
|---|
| 88 | klass = reflect.namedClass(argv[0]) |
|---|
| 89 | else: |
|---|
| 90 | klass = ConsoleManhole |
|---|
| 91 | runWithProtocol(klass) |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | if __name__ == '__main__': |
|---|
| 95 | main() |
|---|