Building a Console for E15

This week I worked on implementing a console on E15 that can display contents of stdout while also accepting stdin. Until now, we all used the Xcode console, but of course if we want to distribute a binary, we are going to need a console for the users. The task wasn’t as trivial as I initially thought, and the result is a bit of a hack, but I thought I’d just write it up.
The console is just a simple NSTextView that is contained in a NSDrawer attached to the bottom of the OpenGLView. The first thing to do is to print the contents of error output into that view. It’s easy enough to do, we just need to add the contents to the NSTextStorage of the console. This seemed to work fine, but it would hang the application if we write to it repeatedly in a short time. Something like:
for i in range(1000): print i
would hang at some arbitrary point. For a while I was convinced it was a problem in appending strings to the text view, but even after trying to append strings every way possible, it still didn’t work. In the end, the problem was due to threading. The python interpreter runs in a secondary thread, and I had thought that it was fine to modify the GUI from a secondary thread, since drawing to a view from a secondary thread was safe. Anyway, as soon as I used
[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]
to write the contents, everything was good to go. It would be nice to know the exact things that must run on the main thread in Cocoa are, but the Apple documentation is pretty bad about that. So the lesson learned: when something barfs unexpectedly, run in the main thread!
The other part to the console is to accept user input in the console and pipe it to stdin, to the Python interpreter. This is important since sometimes you would want to receive user information from input() or raw_input(). Initially I thought using NSTask and NSPipe would be the best, but that required me to set up the Python interpreter as a subprocess, but Kyle worked out all the stuff dealing with Python and threading and saving states and I didn’t want to mess with that. I knew that in Python, you can set any file handler as a stdin, stderror or stdout. So this is a total hack, and I don’t really know if there is a nicer way to go about it, but the idea is to grab the contents of the last line on the console when the user presses return or enter, then create a tmp file with the contents written inside it.
On the Python end, we would have to redefine raw_input(). I instead have a new method console_input() which is defined like this:
def console_input(message, magic_string='Yes'):
print message
open("/tmp/STDIN.txt", "w").write("# STDIN #")
saved_state = sys.stdin
sys.stdin = open("/tmp/STDIN.txt")
line = sys.stdin.readline().strip()
prev_result = line
first_time = True
while upper(line) != upper(magic_string):
if prev_result != line and first_time == False:
print 'Please answer ' + magic_string
prev_result = line
first_time = False
sys.stdin = open("/tmp/STDIN.txt")
line = sys.stdin.readline().strip()
sys.stdin = saved_state
So, the method prints out a message to the console, then also waits for the user to input the magic_string. If the user enters the magic string, the method will exit. Ghetto, I know…but it works and I can’t come up with a better solution.

February 18th, 2008 at 2:42 am
[...] new E15 interface with the new console. Now with [...]