[Twisted-Python] using stdio with Twisted

Hello, I'm building an old-school BBS client using Twisted. It needs to take user input, parse it, send messages over the wire and get messages back and display them.. the usual. I'm just now getting to writing the UI and have run into a couple problems. First, whether using protocol.Protocol or protocols.basic.LineReceiver (in raw mode) with internet.stdio.StandardIO, I have the same problem: the dataReceived (or rawDataReceived in the case of LineReceiver in raw mode) functions are only called after one types some text and hits return. I need to process input on a keystroke level, since there will be prompts at which a single keystroke will do things. Second, I was looking at twisted.conch.insults.insults.TerminalProtocol after some Googling -- does anyone know if I can just drop this in in place of protocols.basic.LineReceiver (or whatever) and have it work properly, or does it require some special invocation and/or grab stdio by itself? Thanks!

On Mon, 19 Mar 2007 19:16:23 -0700, Neurophyre <listbox@evernex.com> wrote:
Hello,
I'm building an old-school BBS client using Twisted. It needs to take user input, parse it, send messages over the wire and get messages back and display them.. the usual. I'm just now getting to writing the UI and have run into a couple problems.
First, whether using protocol.Protocol or protocols.basic.LineReceiver (in raw mode) with internet.stdio.StandardIO, I have the same problem: the dataReceived (or rawDataReceived in the case of LineReceiver in raw mode) functions are only called after one types some text and hits return. I need to process input on a keystroke level, since there will be prompts at which a single keystroke will do things.
Second, I was looking at twisted.conch.insults.insults.TerminalProtocol after some Googling -- does anyone know if I can just drop this in in place of protocols.basic.LineReceiver (or whatever) and have it work properly, or does it require some special invocation and/or grab stdio by itself?
Take a look at what twisted.conch.stdio.runWithProtocol does to the terminal. That's more or less what's necessary to kick it out of line buffered mode and give you each keystroke as it arrives. TerminalProtocol is meant to be used with ServerProtocol from the same module. ServerProtocol will interpret various terminal control sequences and translate them into calls onto whatever ITerminalProtocol you supply to it. It probably won't be much use to use LineReceiver, since keystrokeReceived will be called with things like backspace and home and other things LineReceiver has no idea what to do with. There are a couple other things you might be interested in though, like twisted.conch.recvline.RecvLine or a class in a project I've been working on a bit on and off, invective.widgets.LineInputWidget, which uses insults' widget system to implement a more re-usable line input class (you can find invective in my Twisted sandbox, http://twistedmatrix.com/trac/browser/sandbox/exarkun/invective/ ), which will hopefully move back into Twisted at some point. Jean-Paul

On Mar 20, 2007, at 4:13 AM, Jean-Paul Calderone wrote:
Take a look at what twisted.conch.stdio.runWithProtocol does to the terminal. That's more or less what's necessary to kick it out of line buffered mode and give you each keystroke as it arrives.
Thanks for this pointer. I'm going to try rewriting my terminal I/O code, what little of it there is, to use that this morning.
TerminalProtocol is meant to be used with ServerProtocol from the same module. ServerProtocol will interpret various terminal control sequences and translate them into calls onto whatever ITerminalProtocol you supply to it. It probably
After looking at your invective code a second time (somebody in #twisted pointed me to it last night) I'm still not entirely sure how ServerProtocol integrates. It seems like you're mainly just using it as a sort of container of constants to interpret special keystrokes (like alt-whatever, or backspace or similar) and in the test cases to produce such sequences. Is that its main use?
what to do with. There are a couple other things you might be interested in though, like twisted.conch.recvline.RecvLine or a class in a project I've been working on a bit on and off, invective.widgets.LineInputWidget, which uses insults' widget system to implement a more re-usable line input class (you can find invective in my Twisted sandbox,
Yep, I think twisted.conch.recvline.RecvLine may be what I'm looking for. My program needs to respond to keystrokes in some cases, and allow line editing in others -- message entry, for example. Thanks again for your help and I'll probably be back here with more questions at some point. --Neuro

On Tue, 20 Mar 2007 12:02:36 -0700, Neurophyre <listbox@evernex.com> wrote:
[snip]
After looking at your invective code a second time (somebody in #twisted pointed me to it last night) I'm still not entirely sure how ServerProtocol integrates. It seems like you're mainly just using it as a sort of container of constants to interpret special keystrokes (like alt-whatever, or backspace or similar) and in the test cases to produce such sequences. Is that its main use?
Well, it is also a protocol implementation for something like VT102. A lot of the tests bypass the byte-level stuff because that's not what's interesting for what they're testing. In your actual code, ServerProtocol is what is responsible for calling keystrokeReceived with all of those interesting values. Jean-Paul

On Mar 20, 2007, at 12:43 PM, Jean-Paul Calderone wrote:
Well, it is also a protocol implementation for something like VT102. A lot of the tests bypass the byte-level stuff because that's not what's interesting for what they're testing. In your actual code, ServerProtocol is what is responsible for calling keystrokeReceived with all of those interesting values.
Sorry, but I don't understand how to make it do that. I have my own user interface 'protocol' which at this point is getting keystrokeReceived events and echoing them to the screen, but it's only getting a keyID and the modifier field is always None. Also, if I enter a control-key, the control character is echoed (for example ctrl-J produces a line feed) but the modifier is not set. I've grepped your code and found only one place, in widgets.py, where it's used that isn't a test case -- and that's just doing a comparison on a received keystroke, I think. Here's my relevant code, for reference: class DOCUIProtocol(recvline.RecvLine): """ A 'protocol' which handles the user interface. """ def connectionMade(self): """runWithProtocol() in main""" # process individual keystrokes, hopefully # http://svn.twistedmatrix.com/cvs/sandbox/exarkun/invective/ trunk/invective/tui.py?rev=19759&view=auto super(DOCUIProtocol, self).connectionMade() self.terminal.resetPrivateModes([privateModes.CURSOR_MODE]) # set up quasi-singleton state variables self.__buff = ClientUITidbits.KeystrokeBuffer() self.__clientUIState = ClientUITidbits.UIState() self.__clientUIState.setState(Constants.UISTATE_DISCONNECTED) # make a quasi-singleton instance of ourself so others can use write() and writeLine() dummy = ClientUITidbits.StdIO() dummy.stdioInstance = self; d = self.write(u'done.\n') self.connectBBS() def keystrokeReceived(self, keyID, modifier): # see http://twistedmatrix.com/documents/current/api/ twisted.conch.insults.insults.TerminalProtocol.html # TODO fix for control keys self.writeLine("Got key: " + keyID + " " + str(modifier)) # ? if keyID == ServerProtocol.RIGHT_ARROW: self.writeLine("killed") reactor.stop() #self.__buff.append(keyID) #ClientParseInput.KeystrokeDispatcher.parse() # TODO delete if we continue using insults def dataReceived(self, data): self.__buff.append(data) ClientParseInput.KeystrokeDispatcher.parse() def write(self, data): """write data to stdout, encoded in an output charset""" # TODO delete for conch # self.transport.write(data.encode(Constants.CHARSET_OUTPUT)) self.terminal.write(data.encode(Constants.CHARSET_OUTPUT)) def writeLine(self, data): """write data plus a newline, encoded in an output charset""" # TODO find out why this isn't working right # TODO delete for conch # self.transport.write(data.encode(Constants.CHARSET_OUTPUT + '\n')) self.terminal.write(data.encode(Constants.CHARSET_OUTPUT) + '\n') def connectBBS(self): """initiate the TCP connection to the BBS server and start the reactor.""" # the factory writes diagnostic messages to the stdio instance for us reactor.connectTCP('vapor.evernex.net', 9023, ClientProtocol.BBSClientProtocolFactory()) # reactor.run() class CommandLineUserInterface(DOCUIProtocol): """ See http://svn.twistedmatrix.com/cvs/sandbox/exarkun/invective/ trunk/invective/tui.py?rev=19759&view=auto """ def connectionMade(self): signal(SIGWINCH, self.windowChanged) winSize = self.getWindowSize() self.width = winSize[0] self.height = winSize[1] super(CommandLineUserInterface, self).connectionMade() def connectionLost(self, reason): reactor.stop() # XXX Should be part of runWithProtocol def getWindowSize(self): winsz = ioctl(0, TIOCGWINSZ, '12345678') winSize = unpack('4H', winsz) newSize = winSize[1], winSize[0], winSize[3], winSize[2] return newSize def windowChanged(self, signum, frame): winSize = self.getWindowSize() self.terminalSize(winSize[0], winSize[1]) if __name__ == "__main__": splashLine = Constants.VERSION_CLIENT_LINE + u' - ' + Constants.VERSION_COPYRIGHT print splashLine.encode(Constants.CHARSET_OUTPUT) print 'Connecting user interface: ', runWithProtocol(CommandLineUserInterface)
participants (2)
-
Jean-Paul Calderone
-
Neurophyre