Hi all. I'm trying to write a "terminal" component, wrapped in a service.Service, to drop into an application framework so that I can use it to build interactive command-line tools. I've almost, but not quite, got it working. I know about manhole, but I want to write a simple command-based syntax, rather than exposing a python namespace and interpreter. I could also just go with a telnet or ssh interface, but that would require an additional step (the actual telnet or ssh). There's also stdiodemo.py, but that doesn't offer readline-like functionality, which would be a Very Good Thing. (Besides, I'm really curious as to why this approach is not working!) The code below is a minimal stripped-down example of what I've got so far through frantic grepping and glimpseing through the Twisted codebase. (although there are some fragmentary holdovers from various experiments; ignore these). At the moment, the command handler (the lineReceived() method) just echoes the argument with some '+' signs prepended. The termios stuff comes from twisted/conch/stdio.py, via a mailing list post saying something to the effect of, "this is the minimal amount you have to do in order to hook up your terminal to your process's stdio"; I don't have the ML link handy right now, sorry. Note that whereas stdio.py passes a Protocol argument to ServerProtocol, I've created an intermediate class, CLIServerProtocol, which has a protocolFactory attribute. An examination of twisted/conch/insults/insults.py shows that this should be equivalent. For the curious bystanders, HistoricRecvLine is derived as follows: HistoricRecvLine -> RecvLine -> TerminalProtocol ... and TerminalProtocol implements things like connectionMade, etc. Here's the behaviour I see: With the code as is, I can type in lines and get a reply from lineReceived. However, none of the cursor keys or other things for which there is special code in twisted/conch/recvline.py work; I just get control codes all over the terminal. If I uncomment the "reactor.run()", however, the behaviour changes completely, and becomes much closer to what I expect: keypresses like Home, cursor keys, etc, are honoured, as well as the history. However, Ctrl-C merely loses the connection, and the terminal hangs and I have to kill the twistd process externally. The fact that adding reactor.run() (which shouldn't be needed in a Service, right?) implies to me that the reactor isn't starting up properly without it, and I can't see why. Any advice would be greatly appreciated. Ricky -- import os, tty, sys, termios from twisted.application import service from twisted.internet import reactor, stdio, protocol, defer from twisted.python import failure, reflect, log from twisted.protocols import basic from twisted.application import internet from twisted.conch.insults import insults from twisted.conch.manhole import ColoredManhole # from twisted.conch.stdio import ConsoleManhole from twisted.application import service from twisted.conch import recvline class CLIProtocol ( recvline.HistoricRecvLine ): service = None def connectionMade ( self ): recvline.HistoricRecvLine.connectionMade ( self ) self.keyHandlers [ '\x01' ] = self.handle_HOME self.keyHandlers [ '\x03' ] = self.handle_QUIT self.keyHandlers [ '\x1a' ] = self.handle_QUIT def connectionLost ( self, reason ): log.msg ( "Connection Lost" ) def handle_QUIT ( self ): self.terminal.loseConnection() def lineReceived ( self, line ): self.terminal.write ( '+++' + line ) class CLIServerProtocol ( insults.ServerProtocol ): protocolFactory = CLIProtocol class CLIService ( service.Service ): def startService ( self ): fd = sys.__stdin__.fileno() oldSettings = termios.tcgetattr ( fd ) tty.setraw ( fd ) try: p = CLIServerProtocol() stdio.StandardIO ( p ) # reactor.run() finally: termios.tcsetattr ( fd, termios.TCSANOW, oldSettings ) os.write ( fd, "\r\x1bc\r" ) return service.Service.startService ( self ) ###################################################################### # Create the application service hierarchy. ###################################################################### application = service.Application ( 'cliapp' ) cs = CLIService() cs.setServiceParent ( application )