HELLO WORLD...
[fanfix.git] / src / jexer / io / ECMA48Terminal.java
index 8d3c0fe6d7901e3bab34d221fd06266802f164bd..ba745c5e9d562b614207e1d5ce314b06a06c0c90 100644 (file)
@@ -33,6 +33,8 @@
 package jexer.io;
 
 import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.IOException;
@@ -62,13 +64,28 @@ import static jexer.TKeypress.*;
  * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys,
  * etc.
  */
-public class ECMA48Terminal {
+public class ECMA48Terminal implements Runnable {
 
     /**
      * The session information
      */
     public SessionInfo session;
 
+    /**
+     * The event queue, filled up by a thread reading on input
+     */
+    private List<TInputEvent> eventQueue;
+
+    /**
+     * If true, we want the reader thread to exit gracefully.
+     */
+    private boolean stopReaderThread;
+
+    /**
+     * The reader thread
+     */
+    private Thread readerThread;
+
     /**
      * Parameters being collected.  E.g. if the string is \033[1;3m, then
      * params[0] will be 1 and params[1] will be 3.
@@ -139,11 +156,19 @@ public class ECMA48Terminal {
 
     /**
      * The terminal's input.  If an InputStream is not specified in the
-     * constructor, then this InputReader will be bound to System.in with
-     * UTF-8 encoding.
+     * constructor, then this InputStreamReader will be bound to System.in
+     * with UTF-8 encoding.
      */
     private Reader input;
 
+    /**
+     * The terminal's raw InputStream.  If an InputStream is not specified in
+     * the constructor, then this InputReader will be bound to System.in.
+     * This is used by run() to see if bytes are available() before calling
+     * (Reader)input.read().
+     */
+    private InputStream inputStream;
+
     /**
      * The terminal's output.  If an OutputStream is not specified in the
      * constructor, then this PrintWriter will be bound to System.out with
@@ -189,14 +214,12 @@ public class ECMA48Terminal {
      */
     private void doStty(boolean mode) {
        String [] cmdRaw = {
-           "/bin/sh", "-c", "stty raw < /dev/tty"
+           "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
        };
        String [] cmdCooked = {
-           "/bin/sh", "-c", "stty cooked < /dev/tty"
+           "/bin/sh", "-c", "stty sane cooked < /dev/tty"
        };
        try {
-           System.out.println("spawn stty");
-
            Process process;
            if (mode == true) {
                process = Runtime.getRuntime().exec(cmdRaw);
@@ -244,17 +267,21 @@ public class ECMA48Terminal {
     public ECMA48Terminal(InputStream input, OutputStream output) throws UnsupportedEncodingException {
 
        reset();
-       mouse1 = false;
-       mouse2 = false;
-       mouse3 = false;
+       mouse1           = false;
+       mouse2           = false;
+       mouse3           = false;
+       stopReaderThread = false;
 
        if (input == null) {
-           this.input = new InputStreamReader(System.in, "UTF-8");
+           // inputStream = System.in;
+           inputStream = new FileInputStream(FileDescriptor.in);
            sttyRaw();
            setRawMode = true;
        } else {
-           this.input = new InputStreamReader(input);
+           inputStream = input;
        }
+       this.input = new InputStreamReader(inputStream, "UTF-8");
+
        // TODO: include TelnetSocket from NIB and have it implement
        // SessionInfo
        if (input instanceof SessionInfo) {
@@ -283,18 +310,52 @@ public class ECMA48Terminal {
        // Hang onto the window size
        windowResize = new TResizeEvent(TResizeEvent.Type.Screen,
            session.getWindowWidth(), session.getWindowHeight());
+
+       // Spin up the input reader
+       eventQueue = new LinkedList<TInputEvent>();
+       readerThread = new Thread(this);
+       readerThread.start();
     }
 
     /**
      * Restore terminal to normal state
      */
     public void shutdown() {
+
+       // System.err.println("=== shutdown() ==="); System.err.flush();
+
+       // Tell the reader thread to stop looking at input
+       stopReaderThread = true;
+       try {
+           readerThread.join();
+       } catch (InterruptedException e) {
+           e.printStackTrace();
+       }
+
+       // Disable mouse reporting and show cursor
+       output.printf("%s%s%s", mouse(false), cursor(true), normal());
+       output.flush();
+
        if (setRawMode) {
            sttyCooked();
            setRawMode = false;
+           // We don't close System.in/out
+       } else {
+           // Shut down the streams, this should wake up the reader thread
+           // and make it exit.
+           try {
+               if (input != null) {
+                   input.close();
+                   input = null;
+               }
+               if (output != null) {
+                   output.close();
+                   output = null;
+               }
+           } catch (IOException e) {
+               e.printStackTrace();
+           }
        }
-       // Disable mouse reporting and show cursor
-       output.printf("%s%s%s", mouse(false), cursor(true), normal());
     }
 
     /**
@@ -309,6 +370,7 @@ public class ECMA48Terminal {
      */
     private void reset() {
        state = ParseState.GROUND;
+       params = new ArrayList<String>();
        paramI = 0;
        params.clear();
        params.add("");
@@ -329,8 +391,12 @@ public class ECMA48Terminal {
        // System.err.printf("controlChar: %02x\n", ch);
 
        switch (ch) {
-       case '\r':
-           // ENTER
+       case 0x0D:
+           // Carriage return --> ENTER
+           event.key = kbEnter;
+           break;
+       case 0x0A:
+           // Linefeed --> ENTER
            event.key = kbEnter;
            break;
        case 0x1B:
@@ -696,16 +762,33 @@ public class ECMA48Terminal {
     }
 
     /**
-     * Parses the next character of input to see if an InputEvent is
-     * fully here.
+     * Return any events in the IO queue.
      *
-     * Params:
-     *    ch = Unicode code point
-     *    noChar = if true, ignore ch.  This is currently used to
-     *    return a bare ESC and RESIZE events.
+     * @return list of new events (which may be empty)
+     */
+    public List<TInputEvent> getEvents() {
+       List<TInputEvent> events = new LinkedList<TInputEvent>();
+
+       synchronized(this) {
+           if (eventQueue.size() > 0) {
+               events.addAll(eventQueue);
+               eventQueue.clear();
+           }
+       }
+
+       // TEST: drop a cmAbort
+       // events.add(new jexer.event.TCommandEvent(jexer.TCommand.cmAbort));
+       // events.add(new jexer.event.TKeypressEvent(kbAltX));
+
+       return events;
+    }
+
+    /**
+     * Parses the next character of input to see if an InputEvent is fully
+     * here.
      *
-     * Returns:
-     *    list of new events (which may be empty)
+     * @param ch Unicode code point
+     * @return list of new events (which may be empty)
      */
     public List<TInputEvent> getEvents(char ch) {
        return getEvents(ch, false);
@@ -715,13 +798,10 @@ public class ECMA48Terminal {
      * Parses the next character of input to see if an InputEvent is
      * fully here.
      *
-     * Params:
-     *    ch = Unicode code point
-     *    noChar = if true, ignore ch.  This is currently used to
-     *    return a bare ESC and RESIZE events.
-     *
-     * Returns:
-     *    list of new events (which may be empty)
+     * @param ch Unicode code point
+     * @param noChar if true, ignore ch.  This is currently used to return a
+     * bare ESC and RESIZE events.
+     * @return list of new events (which may be empty)
      */
     public List<TInputEvent> getEvents(char ch, boolean noChar) {
        List<TInputEvent> events = new LinkedList<TInputEvent>();
@@ -729,6 +809,7 @@ public class ECMA48Terminal {
        TKeypressEvent keypress;
        Date now = new Date();
 
+       /*
        // ESCDELAY type timeout
        if (state == ParseState.ESCAPE) {
            long escDelay = now.getTime() - escapeTime;
@@ -738,6 +819,7 @@ public class ECMA48Terminal {
                reset();
            }
        }
+        */
 
        if (noChar == true) {
            int newWidth = session.getWindowWidth();
@@ -1103,15 +1185,12 @@ public class ECMA48Terminal {
     }
 
     /**
-     * Tell (u)xterm that we want alt- keystrokes to send escape +
-     * character rather than set the 8th bit.  Anyone who wants UTF8
-     * should want this enabled.
-     *
-     * Params:
-     *    on = if true, enable metaSendsEscape
+     * Tell (u)xterm that we want alt- keystrokes to send escape + character
+     * rather than set the 8th bit.  Anyone who wants UTF8 should want this
+     * enabled.
      *
-     * Returns:
-     *    the string to emit to xterm
+     * @param on if true, enable metaSendsEscape
+     * @return the string to emit to xterm
      */
     static public String xtermMetaSendsEscape(boolean on) {
        if (on) {
@@ -1121,15 +1200,13 @@ public class ECMA48Terminal {
     }
 
     /**
-     * Convert a list of SGR parameters into a full escape sequence.
-     * This also eliminates a trailing ';' which would otherwise reset
-     * everything to white-on-black not-bold.
-     *
-     * Params:
-     *    str = string of parameters, e.g. "31;1;"
+     * Convert a list of SGR parameters into a full escape sequence.  This
+     * also eliminates a trailing ';' which would otherwise reset everything
+     * to white-on-black not-bold.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;1m"
+     * @param str string of parameters, e.g. "31;1;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[31;1m"
      */
     static public String addHeaderSGR(String str) {
        if (str.length() > 0) {
@@ -1144,12 +1221,10 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for a single color change.
      *
-     * Params:
-     *    color = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    foreground = if true, this is a foreground color
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
+     * @param color one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param foreground if true, this is a foreground color
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
      */
     static public String color(Color color, boolean foreground) {
        return color(color, foreground, true);
@@ -1158,14 +1233,12 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for a single color change.
      *
-     * Params:
-     *    color = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    foreground = if true, this is a foreground color
-     *    header = if true, make the full header, otherwise just emit
-     *    the color parameter e.g. "42;"
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
+     * @param color one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param foreground if true, this is a foreground color
+     * @param header if true, make the full header, otherwise just emit the
+     * color parameter e.g. "42;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
      */
     static public String color(Color color, boolean foreground,
        boolean header) {
@@ -1190,12 +1263,10 @@ public class ECMA48Terminal {
      * Create a SGR parameter sequence for both foreground and
      * background color change.
      *
-     * Params:
-     *    foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    backColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
+     * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[31;42m"
      */
     static public String color(Color foreColor, Color backColor) {
        return color(foreColor, backColor, true);
@@ -1205,14 +1276,12 @@ public class ECMA48Terminal {
      * Create a SGR parameter sequence for both foreground and
      * background color change.
      *
-     * Params:
-     *    foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    backColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    header = if true, make the full header, otherwise just emit
-     *    the color parameter e.g. "31;42;"
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
+     * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param header if true, make the full header, otherwise just emit the
+     * color parameter e.g. "31;42;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[31;42m"
      */
     static public String color(Color foreColor, Color backColor,
        boolean header) {
@@ -1233,19 +1302,17 @@ public class ECMA48Terminal {
 
     /**
      * Create a SGR parameter sequence for foreground, background, and
-     * several attributes.  This sequence first resets all attributes
-     * to default, then sets attributes as per the parameters.
+     * several attributes.  This sequence first resets all attributes to
+     * default, then sets attributes as per the parameters.
      *
-     * Params:
-     *    foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    backColor = one of the Color.WHITE, Color.BLUE, etc. constants
-     *    bold = if true, set bold
-     *    reverse = if true, set reverse
-     *    blink = if true, set blink
-     *    underline = if true, set underline
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0;1;31;42m"
+     * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
+     * @param bold if true, set bold
+     * @param reverse if true, set reverse
+     * @param blink if true, set blink
+     * @param underline if true, set underline
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[0;1;31;42m"
      */
     static public String color(Color foreColor, Color backColor, boolean bold,
        boolean reverse, boolean blink, boolean underline) {
@@ -1299,11 +1366,9 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling reverse color.
      *
-     * Params:
-     *    on = if true, turn on reverse
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[7m"
+     * @param on if true, turn on reverse
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[7m"
      */
     static public String reverse(boolean on) {
        if (on) {
@@ -1315,8 +1380,8 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence to reset to defaults.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[0m"
      */
     static public String normal() {
        return normal(true);
@@ -1325,12 +1390,10 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence to reset to defaults.
      *
-     * Params:
-     *    header = if true, make the full header, otherwise just emit
-     *    the bare parameter e.g. "0;"
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
+     * @param header if true, make the full header, otherwise just emit the
+     * bare parameter e.g. "0;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[0m"
      */
     static public String normal(boolean header) {
        if (header) {
@@ -1342,11 +1405,9 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling boldface.
      *
-     * Params:
-     *    on = if true, turn on bold
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
+     * @param on if true, turn on bold
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[1m"
      */
     static public String bold(boolean on) {
        return bold(on, true);
@@ -1355,13 +1416,11 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling boldface.
      *
-     * Params:
-     *    on = if true, turn on bold
-     *    header = if true, make the full header, otherwise just emit
-     *    the bare parameter e.g. "1;"
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
+     * @param on if true, turn on bold
+     * @param header if true, make the full header, otherwise just emit the
+     * bare parameter e.g. "1;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[1m"
      */
     static public String bold(boolean on, boolean header) {
        if (header) {
@@ -1379,11 +1438,9 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling blinking text.
      *
-     * Params:
-     *    on = if true, turn on blink
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
+     * @param on if true, turn on blink
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[5m"
      */
     static public String blink(boolean on) {
        return blink(on, true);
@@ -1392,13 +1449,11 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling blinking text.
      *
-     * Params:
-     *    on = if true, turn on blink
-     *    header = if true, make the full header, otherwise just emit
-     *    the bare parameter e.g. "5;"
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
+     * @param on if true, turn on blink
+     * @param header if true, make the full header, otherwise just emit the
+     * bare parameter e.g. "5;"
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[5m"
      */
     static public String blink(boolean on, boolean header) {
        if (header) {
@@ -1414,14 +1469,12 @@ public class ECMA48Terminal {
     }
 
     /**
-     * Create a SGR parameter sequence for enabling underline /
-     * underscored text.
+     * Create a SGR parameter sequence for enabling underline / underscored
+     * text.
      *
-     * Params:
-     *    on = if true, turn on underline
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[4m"
+     * @param on if true, turn on underline
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[4m"
      */
     static public String underline(boolean on) {
        if (on) {
@@ -1433,11 +1486,8 @@ public class ECMA48Terminal {
     /**
      * Create a SGR parameter sequence for enabling the visible cursor.
      *
-     * Params:
-     *    on = if true, turn on cursor
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @param on if true, turn on cursor
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     public String cursor(boolean on) {
        if (on && (cursorOn == false)) {
@@ -1455,8 +1505,7 @@ public class ECMA48Terminal {
      * Clear the entire screen.  Because some terminals use back-color-erase,
      * set the color to white-on-black beforehand.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String clearAll() {
        return "\033[0;37;40m\033[2J";
@@ -1467,8 +1516,7 @@ public class ECMA48Terminal {
      * Because some terminals use back-color-erase, set the color to
      * white-on-black beforehand.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String clearRemainingLine() {
        return "\033[0;37;40m\033[K";
@@ -1478,8 +1526,7 @@ public class ECMA48Terminal {
      * Clear the line up the cursor (inclusive).  Because some terminals use
      * back-color-erase, set the color to white-on-black beforehand.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String clearPreceedingLine() {
        return "\033[0;37;40m\033[1K";
@@ -1489,8 +1536,7 @@ public class ECMA48Terminal {
      * Clear the line.  Because some terminals use back-color-erase, set the
      * color to white-on-black beforehand.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String clearLine() {
        return "\033[0;37;40m\033[2K";
@@ -1499,8 +1545,7 @@ public class ECMA48Terminal {
     /**
      * Move the cursor to the top-left corner.
      *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String home() {
        return "\033[H";
@@ -1509,29 +1554,25 @@ public class ECMA48Terminal {
     /**
      * Move the cursor to (x, y).
      *
-     * Params:
-     *    x = column coordinate.  0 is the left-most column.
-     *    y = row coordinate.  0 is the top-most row.
-     *
-     * Returns:
-     *    the string to emit to an ANSI / ECMA-style terminal
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @return the string to emit to an ANSI / ECMA-style terminal
      */
     static public String gotoXY(int x, int y) {
        return String.format("\033[%d;%dH", y + 1, x + 1);
     }
 
     /**
-     * Tell (u)xterm that we want to receive mouse events based on
-     * "Any event tracking" and UTF-8 coordinates.  See
+     * Tell (u)xterm that we want to receive mouse events based on "Any event
+     * tracking" and UTF-8 coordinates.  See
      * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
      *
-     * Finally, this sets the alternate screen buffer.
+     * Note that this also sets the alternate/primary screen buffer.
      *
-     * Params:
-     *    on = if true, enable mouse report
-     *
-     * Returns:
-     *    the string to emit to xterm
+     * @param on If true, enable mouse report and use the alternate screen
+     * buffer.  If false disable mouse reporting and use the primary screen
+     * buffer.
+     * @return the string to emit to xterm
      */
     static public String mouse(boolean on) {
        if (on) {
@@ -1540,4 +1581,59 @@ public class ECMA48Terminal {
        return "\033[?1003;1005l\033[?1049l";
     }
 
+    /**
+     * Read function runs on a separate thread.
+     */
+    public void run() {
+       boolean done = false;
+       // available() will often return > 1, so we need to read in chunks to
+       // stay caught up.
+       char [] readBuffer = new char[128];
+
+       while ((done == false) && (stopReaderThread == false)) {
+           try {
+               // We assume that if inputStream has bytes available, then
+               // input won't block on read().
+               int n = inputStream.available();
+               if (n > 0) {
+                   if (readBuffer.length < n) {
+                       // The buffer wasn't big enough, make it huger
+                       readBuffer = new char[readBuffer.length * 2];
+                   }
+
+                   int rc = input.read(readBuffer, 0, n);
+                   // System.err.printf("read() %d", rc); System.err.flush();
+                   if (rc == -1) {
+                       // This is EOF
+                       done = true;
+                   } else {
+                       for (int i = 0; i < rc; i++) {
+                           int ch = readBuffer[i];
+
+                           // System.err.printf("** READ 0x%x '%c'", ch, ch);
+                           List<TInputEvent> events = getEvents((char)ch);
+                           synchronized (this) {
+                               /*
+                               System.err.printf("adding %d events\n",
+                                   events.size());
+                                */
+                               eventQueue.addAll(events);
+                           }
+                       }
+                   }
+               } else {
+                   // Wait 5 millis for more data
+                   Thread.sleep(5);
+               }
+               // System.err.println("end while loop"); System.err.flush();
+           } catch (InterruptedException e) {
+               // SQUASH
+           } catch (IOException e) {
+               e.printStackTrace();
+               done = true;
+           }
+       } // while ((done == false) && (stopReaderThread == false))
+       // System.err.println("*** run() exiting..."); System.err.flush();
+    }
+
 }