Adobe's free Flex compiler

If you've been using Flash for awhile, then you know that there aren't many real-world use cases in which Adobe's free Flex SDK compiler is very useful. It does not allow you to create an applet with GUI controls, for example. The one scenario where it might work for you is if you are building a Flash applet that will be "invisible," providing something on a web page that you can't get from JavaScript or HTML. I was recently tasked with building just such an applet.

An "invisible" Flash applet is all well and good when you go to production. But what about when you're writing it? Obviously it's not going to work perfectly the first time you compile it. It will probably fail silently, and you'll have no indication of what went wrong.

One of the first example ActionScript programs I ran across featured a crude command-line interface drawn on the face of a Flash applet. It used only text controls, which are available for use with the free Flex SDK compiler. This seemed to me like a good way to interact with a faceless Flash app while you're working on it, so I decided to write a better version. The example program I will introduce to you now is the end result. It's about as sophisticated as a Flash command-line terminal interface can get.

Building the program

I wrote this using Flash 9 on Mac OS X. I've tested it on Windows, and it works fine. I haven't tested it at all on Linux. My experience is that the Flash player for Linux is not very stable.

Building this program requires the command-line Flash compiler that is part of the Flex 3 SDK. It's a free download from Adobe. The example code presented here has no dependencies on any other code, libraries, or resources other than what comes with the Flex 3 SDK. I haven't tried this with Flex 4, but my guess is it will probably work.

The Flash compiler program, mxmlc, should be available on your path, or the build instructions given below won't work. If you'd prefer, you can probably build this app with Adobe's Flash CS3/CS4, or whatever you normally use for compiling ActionScript. If you decide to go that route, you're on your own. I can't give you specific instructions.

Download the zip file that accompanies this article and unzip it into a directory of your choice. The zipped contents contain subdirectories, which must be preserved. Download the zip file by clicking this link.

At the top level of the hierarchy is a makefile, cleverly named makefile. It requires a GNU-standard make program, which is almost guaranteed to be available on Unix-like systems, including Linux and Mac OS X. Open a terminal window, navigate to the directory where you unzipped the contents, and type make to build term.swf with debugging info. make install builds a version without debugging info.

The makefile may not work if you're on Windows, and it certainly won't work if you don't have a GNU-compatible make program available. For that case, I have supplied Windows batch files that do more or less the same thing: win_make.bat and win_make_install.bat, respectively.

Running the program

Once you've built term.swf, you can run it just by double-clicking on its icon in your operating system's GUI, if you have an application installed that can run Flash applets. The Flex SDK comes with just such a program, called "Flash", if you're using Mac OS X. There is an equivalent for Windows as well, but I'm less familiar with that operating system. The applet should start, display a few lines of text, and present you with a blinking text cursor at the bottom of its window. For your first command, type help and press Enter. The program behaves exactly the same when run inside a web browser window.

Terminal features for the end-user

There's not much to using the terminal interface. You type a command, press Enter, and the terminal executes it. You can press the up arrow on your keyboard to recall the last command entered.

When text scrolls off the top of the terminal, you may be able to scroll backward to see it by using the scroll wheel on your mouse. This works for me when I run term.swf inside "Flash", the Mac OS X player app that came with the Flex 3 SDK. It does not work when the applet is embedded in a web page.

term.swf includes code that makes it aware of window size changes. If term.swf's window size gets bigger, the applet will resize itself accordingly, giving you more lines and columns of text to work with.

Adding the Terminal class to your program

Terminal was written under the assumption that it will be used for debugging and testing only, and won't be shown to end users. When your Flash applet is running "for real," it's assumed that it will be faceless, and given zero pixels on-screen.

When Terminal starts up, it checks to see how big the "stage" is, Adobe's term for the Flash applet's visible pixel area. If the face of the applet isn't at least 100 pixels wide and 100 pixels tall, Terminal will disable itself. If the face of the applet does have enough space, then Terminal will take over the entire stage.

Adding Terminal to a Flash applet requires only a few simple lines in your main class' constructor. Have a look at src/ in the example zip file for details.

Printing text to the terminal

Terminal provides two static methods for printing text. The first is Terminal.echo(), which inputs a Flash String object and prints it on the face of the terminal. If the terminal is already full of text, the oldest line will be scrolled off to make room.

You can add newline characters to your output strings, in which case they will be printed on two or more lines. For example, this code:

  Terminal.echo("Line one\nLine two");

will result in this output:

  Line one
  Line two

I use a lot of echo commands in my code. I got tired of typing the full class and method name over and over again. So I created a small glue method, contained in the ActionScript source file, which allows you to type this instead:

  echo("This is much shorter.");

The second output method is Terminal.echoBlankLineIfNeeded(). It will print a blank line to the terminal if and only if the line most recently printed to the terminal isn't already a blank line. If that doesn't make any sense to you, then just don't use this function. Its use is entirely optional.

Terminal disables itself if its host applet starts without enough screen real estate to create the terminal interface. In that case, Terminal.echo() and Terminal.echoBlankLineIfNeeded() become no-ops.

Adding commands to the terminal

Terminal handles a few simple commands on its own: help, cls, and so on. It's up to you to add additional commands that do real work. Here's how to add a new command to the terminal.

Write a function that will be called to process your new command. It can be part of any class, or it can be a standalone method, but it must have the following method signature:

  function command(args:Array):Boolean

When called, args will be an array of strings representing the arguments the user typed after the name of your command. Your new function should return true if the arguments are acceptable, false if not. If the command method returns false, Terminal will display an error message for the user to see.

Next, create a new TerminalCommand ActionScript object and fill out its fields. At the very least, you need to fill out name, which is the string the user must type to run your command, and proc, which is an ActionScript Function object representing the method that will be called to process this command. Optionally, you can fill out help, which is a help string that will be displayed when the user enters the help command, and list, which is an array of strings that designate alternate "alias" names that can also be typed to invoke this command.

Finally, pass your newly-created TerminalCommand object to Terminal.addCommand(), which registers your new command with the terminal interface. That's it, you're done.

To test your new command, rebuild your Flash applet and run it. Entering help should show your new command in the master list, in alphabetical order. Entering the name of your new command should execute it.

Implementation details

Most of the Terminal class is pretty straightforward. It's centered around two TextField objects that take up the entire stage: one for terminal output, the other for the user to type into. Once you know that much, the details are not very mysterious.

Strangely enough, the hardest thing to implement was to get the two TextField objects to display their text in a fixed-width font. The TextField control defaults to a variable-width font, which doesn't work well for a terminal-like interface.

The Flash Font class can give you a list of fonts installed on the host computer, and the TextField control can be told to use one of those fonts. Alas, there are two big problems.

First problem. I don't see any way to determine whether a given font is fixed-width or variable-width. I worked around that one by hard-coding a list of font names into the Terminal class that are known to be fixed-width. A horrible kludge, to be sure, but I can't think of any other way around it.

Second problem. The TextField class is picky about the conditions under which it will allow you to set a new font. The class must have some text in it, it can't be empty. If it's an input field, you must add that first bit of text one way; if it's an output field, you have to add it another way. I determined all this by trial and error. I'm by no means a Flash expert, so perhaps there's a better way to make this work. If you know of such a method, I'd be happy to add it to the Terminal class.

Flash applets are cross-platform, by definition. As far as I'm aware, there is no way for a Flash applet to determine what type of operating system it's being run on. That's the way it should be.

BUT, unfortunately, the Terminal class runs a bit differently on Mac OS X than it does on Windows. It contains a list of fonts known to be fixed-width, and will choose a different font on each platform. As part of another hack that forces the Terminal object's two TextField controls to display fixed-width text, I had to put "invisible" characters into them. Alas, those "invisible" characters are quite visible on Windows, displayed as small dots. A cosmetic defect that does not affect the way the Terminal class functions.