Many changes:
[fanfix.git] / src / jexer / backend / ECMA48Terminal.java
index 13704151df3037585b4d4d0f442593444a880834..7edb64b20e87b519fa939ababfc781a7c6cb54e2 100644 (file)
@@ -40,7 +40,6 @@ import java.io.PrintWriter;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.LinkedList;
 
@@ -57,8 +56,29 @@ import static jexer.TKeypress.*;
  * This class reads keystrokes and mouse events and emits output to ANSI
  * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, etc.
  */
-public final class ECMA48Terminal extends LogicalScreen
-                                  implements TerminalReader, Runnable {
+public class ECMA48Terminal extends LogicalScreen
+                            implements TerminalReader, Runnable {
+
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * States in the input parser.
+     */
+    private enum ParseState {
+        GROUND,
+        ESCAPE,
+        ESCAPE_INTERMEDIATE,
+        CSI_ENTRY,
+        CSI_PARAM,
+        MOUSE,
+        MOUSE_SGR,
+    }
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Emit debugging to stderr.
@@ -66,8 +86,9 @@ public final class ECMA48Terminal extends LogicalScreen
     private boolean debugToStderr = false;
 
     /**
-     * If true, emit T.416-style RGB colors.  This is a) expensive in
-     * bandwidth, and b) potentially terrible looking for non-xterms.
+     * If true, emit T.416-style RGB colors for normal system colors.  This
+     * is a) expensive in bandwidth, and b) potentially terrible looking for
+     * non-xterms.
      */
     private static boolean doRgbColor = false;
 
@@ -76,15 +97,6 @@ public final class ECMA48Terminal extends LogicalScreen
      */
     private SessionInfo sessionInfo;
 
-    /**
-     * Getter for sessionInfo.
-     *
-     * @return the SessionInfo
-     */
-    public SessionInfo getSessionInfo() {
-        return sessionInfo;
-    }
-
     /**
      * The event queue, filled up by a thread reading on input.
      */
@@ -104,20 +116,7 @@ public final class ECMA48Terminal extends LogicalScreen
      * Parameters being collected.  E.g. if the string is \033[1;3m, then
      * params[0] will be 1 and params[1] will be 3.
      */
-    private ArrayList<String> params;
-
-    /**
-     * States in the input parser.
-     */
-    private enum ParseState {
-        GROUND,
-        ESCAPE,
-        ESCAPE_INTERMEDIATE,
-        CSI_ENTRY,
-        CSI_PARAM,
-        MOUSE,
-        MOUSE_SGR,
-    }
+    private List<String> params;
 
     /**
      * Current parsing state.
@@ -195,90 +194,39 @@ public final class ECMA48Terminal extends LogicalScreen
      */
     private Object listener;
 
-    /**
-     * Get the output writer.
-     *
-     * @return the Writer
-     */
-    public PrintWriter getOutput() {
-        return output;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
-     * Check if there are events in the queue.
-     *
-     * @return if true, getEvents() has something to return to the backend
-     */
-    public boolean hasEvents() {
-        synchronized (eventQueue) {
-            return (eventQueue.size() > 0);
-        }
-    }
-
-    /**
-     * Call 'stty' to set cooked mode.
+     * Constructor sets up state for getEvent().
      *
-     * <p>Actually executes '/bin/sh -c stty sane cooked &lt; /dev/tty'
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
+     * @param input an InputStream connected to the remote user, or null for
+     * System.in.  If System.in is used, then on non-Windows systems it will
+     * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
+     * mode.  input is always converted to a Reader with UTF-8 encoding.
+     * @param output an OutputStream connected to the remote user, or null
+     * for System.out.  output is always converted to a Writer with UTF-8
+     * encoding.
+     * @param windowWidth the number of text columns to start with
+     * @param windowHeight the number of text rows to start with
+     * @throws UnsupportedEncodingException if an exception is thrown when
+     * creating the InputStreamReader
      */
-    private void sttyCooked() {
-        doStty(false);
-    }
+    public ECMA48Terminal(final Object listener, final InputStream input,
+        final OutputStream output, final int windowWidth,
+        final int windowHeight) throws UnsupportedEncodingException {
 
-    /**
-     * Call 'stty' to set raw mode.
-     *
-     * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
-     * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
-     * -parenb cs8 min 1 &lt; /dev/tty'
-     */
-    private void sttyRaw() {
-        doStty(true);
-    }
+        this(listener, input, output);
 
-    /**
-     * Call 'stty' to set raw or cooked mode.
-     *
-     * @param mode if true, set raw mode, otherwise set cooked mode
-     */
-    private void doStty(final boolean mode) {
-        String [] cmdRaw = {
-            "/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 sane cooked < /dev/tty"
-        };
-        try {
-            Process process;
-            if (mode) {
-                process = Runtime.getRuntime().exec(cmdRaw);
-            } else {
-                process = Runtime.getRuntime().exec(cmdCooked);
-            }
-            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
-            String line = in.readLine();
-            if ((line != null) && (line.length() > 0)) {
-                System.err.println("WEIRD?! Normal output from stty: " + line);
-            }
-            while (true) {
-                BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
-                line = err.readLine();
-                if ((line != null) && (line.length() > 0)) {
-                    System.err.println("Error output from stty: " + line);
-                }
-                try {
-                    process.waitFor();
-                    break;
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-            }
-            int rc = process.exitValue();
-            if (rc != 0) {
-                System.err.println("stty returned error code: " + rc);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+        // Send dtterm/xterm sequences, which will probably not work because
+        // allowWindowOps is defaulted to false.
+        String resizeString = String.format("\033[8;%d;%dt", windowHeight,
+            windowWidth);
+        this.output.write(resizeString);
+        this.output.flush();
     }
 
     /**
@@ -342,6 +290,11 @@ public final class ECMA48Terminal extends LogicalScreen
         this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
         this.output.flush();
 
+        // Query the screen size
+        sessionInfo.queryWindowSize();
+        setDimensions(sessionInfo.getWindowWidth(),
+            sessionInfo.getWindowHeight());
+
         // Hang onto the window size
         windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
             sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
@@ -350,6 +303,8 @@ public final class ECMA48Terminal extends LogicalScreen
         if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
             if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
                 doRgbColor = true;
+            } else {
+                doRgbColor = false;
             }
         }
 
@@ -358,10 +313,6 @@ public final class ECMA48Terminal extends LogicalScreen
         readerThread = new Thread(this);
         readerThread.start();
 
-        // Query the screen size
-        setDimensions(sessionInfo.getWindowWidth(),
-            sessionInfo.getWindowHeight());
-
         // Clear the screen
         this.output.write(clearAll());
         this.output.flush();
@@ -429,6 +380,11 @@ public final class ECMA48Terminal extends LogicalScreen
         this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
         this.output.flush();
 
+        // Query the screen size
+        sessionInfo.queryWindowSize();
+        setDimensions(sessionInfo.getWindowWidth(),
+            sessionInfo.getWindowHeight());
+
         // Hang onto the window size
         windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
             sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
@@ -437,6 +393,8 @@ public final class ECMA48Terminal extends LogicalScreen
         if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
             if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
                 doRgbColor = true;
+            } else {
+                doRgbColor = false;
             }
         }
 
@@ -445,10 +403,6 @@ public final class ECMA48Terminal extends LogicalScreen
         readerThread = new Thread(this);
         readerThread.start();
 
-        // Query the screen size
-        setDimensions(sessionInfo.getWindowWidth(),
-            sessionInfo.getWindowHeight());
-
         // Clear the screen
         this.output.write(clearAll());
         this.output.flush();
@@ -471,6 +425,73 @@ public final class ECMA48Terminal extends LogicalScreen
         this(listener, input, reader, writer, false);
     }
 
+    // ------------------------------------------------------------------------
+    // LogicalScreen ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    @Override
+    public void setTitle(final String title) {
+        output.write(getSetTitleString(title));
+        flush();
+    }
+
+    /**
+     * Push the logical screen to the physical device.
+     */
+    @Override
+    public void flushPhysical() {
+        String result = flushString();
+        if ((cursorVisible)
+            && (cursorY >= 0)
+            && (cursorX >= 0)
+            && (cursorY <= height - 1)
+            && (cursorX <= width - 1)
+        ) {
+            result += cursor(true);
+            result += gotoXY(cursorX, cursorY);
+        } else {
+            result += cursor(false);
+        }
+        output.write(result);
+        flush();
+    }
+
+    // ------------------------------------------------------------------------
+    // TerminalReader ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the backend
+     */
+    public boolean hasEvents() {
+        synchronized (eventQueue) {
+            return (eventQueue.size() > 0);
+        }
+    }
+
+    /**
+     * Return any events in the IO queue.
+     *
+     * @param queue list to append new events to
+     */
+    public void getEvents(final List<TInputEvent> queue) {
+        synchronized (eventQueue) {
+            if (eventQueue.size() > 0) {
+                synchronized (queue) {
+                    queue.addAll(eventQueue);
+                }
+                eventQueue.clear();
+            }
+        }
+    }
+
     /**
      * Restore terminal to normal state.
      */
@@ -502,13 +523,202 @@ public final class ECMA48Terminal extends LogicalScreen
                     input.close();
                     input = null;
                 }
-                if (output != null) {
-                    output.close();
-                    output = null;
+                if (output != null) {
+                    output.close();
+                    output = null;
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Set listener to a different Object.
+     *
+     * @param listener the new listening object that run() wakes up on new
+     * input
+     */
+    public void setListener(final Object listener) {
+        this.listener = listener;
+    }
+
+    // ------------------------------------------------------------------------
+    // Runnable ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * 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];
+        List<TInputEvent> events = new LinkedList<TInputEvent>();
+
+        while (!done && !stopReaderThread) {
+            try {
+                // We assume that if inputStream has bytes available, then
+                // input won't block on read().
+                int n = inputStream.available();
+
+                /*
+                System.err.printf("inputStream.available(): %d\n", n);
+                System.err.flush();
+                */
+
+                if (n > 0) {
+                    if (readBuffer.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        readBuffer = new char[readBuffer.length * 2];
+                    }
+
+                    // System.err.printf("BEFORE read()\n"); System.err.flush();
+
+                    int rc = input.read(readBuffer, 0, readBuffer.length);
+
+                    /*
+                    System.err.printf("AFTER read() %d\n", rc);
+                    System.err.flush();
+                    */
+
+                    if (rc == -1) {
+                        // This is EOF
+                        done = true;
+                    } else {
+                        for (int i = 0; i < rc; i++) {
+                            int ch = readBuffer[i];
+                            processChar(events, (char)ch);
+                        }
+                        getIdleEvents(events);
+                        if (events.size() > 0) {
+                            // Add to the queue for the backend thread to
+                            // be able to obtain.
+                            synchronized (eventQueue) {
+                                eventQueue.addAll(events);
+                            }
+                            if (listener != null) {
+                                synchronized (listener) {
+                                    listener.notifyAll();
+                                }
+                            }
+                            events.clear();
+                        }
+                    }
+                } else {
+                    getIdleEvents(events);
+                    if (events.size() > 0) {
+                        synchronized (eventQueue) {
+                            eventQueue.addAll(events);
+                        }
+                        if (listener != null) {
+                            synchronized (listener) {
+                                listener.notifyAll();
+                            }
+                        }
+                        events.clear();
+                    }
+
+                    // Wait 20 millis for more data
+                    Thread.sleep(20);
+                }
+                // 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();
+    }
+
+    // ------------------------------------------------------------------------
+    // ECMA48Terminal ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Getter for sessionInfo.
+     *
+     * @return the SessionInfo
+     */
+    public SessionInfo getSessionInfo() {
+        return sessionInfo;
+    }
+
+    /**
+     * Get the output writer.
+     *
+     * @return the Writer
+     */
+    public PrintWriter getOutput() {
+        return output;
+    }
+
+    /**
+     * Call 'stty' to set cooked mode.
+     *
+     * <p>Actually executes '/bin/sh -c stty sane cooked &lt; /dev/tty'
+     */
+    private void sttyCooked() {
+        doStty(false);
+    }
+
+    /**
+     * Call 'stty' to set raw mode.
+     *
+     * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
+     * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
+     * -parenb cs8 min 1 &lt; /dev/tty'
+     */
+    private void sttyRaw() {
+        doStty(true);
+    }
+
+    /**
+     * Call 'stty' to set raw or cooked mode.
+     *
+     * @param mode if true, set raw mode, otherwise set cooked mode
+     */
+    private void doStty(final boolean mode) {
+        String [] cmdRaw = {
+            "/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 sane cooked < /dev/tty"
+        };
+        try {
+            Process process;
+            if (mode) {
+                process = Runtime.getRuntime().exec(cmdRaw);
+            } else {
+                process = Runtime.getRuntime().exec(cmdCooked);
+            }
+            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
+            String line = in.readLine();
+            if ((line != null) && (line.length() > 0)) {
+                System.err.println("WEIRD?! Normal output from stty: " + line);
+            }
+            while (true) {
+                BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
+                line = err.readLine();
+                if ((line != null) && (line.length() > 0)) {
+                    System.err.println("Error output from stty: " + line);
+                }
+                try {
+                    process.waitFor();
+                    break;
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
                 }
-            } catch (IOException e) {
-                e.printStackTrace();
             }
+            int rc = process.exitValue();
+            if (rc != 0) {
+                System.err.println("stty returned error code: " + rc);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
         }
     }
 
@@ -588,6 +798,7 @@ public final class ECMA48Terminal extends LogicalScreen
                 // Now emit only the modified attributes
                 if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -600,8 +811,25 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("1 Change only fore/back colors\n");
                     }
+
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Both colors changed, attributes the same
+                    sb.append(colorRGB(lCell.getForeColorRGB(),
+                            lCell.getBackColorRGB()));
+
+                    if (debugToStderr) {
+                        System.err.printf("1 Change only fore/back colors (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() != lastAttr.isBold())
                     && (lCell.isReverse() != lastAttr.isReverse())
                     && (lCell.isUnderline() != lastAttr.isUnderline())
@@ -619,6 +847,7 @@ public final class ECMA48Terminal extends LogicalScreen
                     }
                 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
                     && (lCell.getBackColor() == lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -632,8 +861,25 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("3 Change foreColor\n");
                     }
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
+                    && (lCell.getForeColorRGB() >= 0)
+                    && (lCell.getBackColorRGB() >= 0)
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Attributes same, foreColor different
+                    sb.append(colorRGB(lCell.getForeColorRGB(), true));
+
+                    if (debugToStderr) {
+                        System.err.printf("3 Change foreColor (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
                     && (lCell.getBackColor() != lastAttr.getBackColor())
+                    && (!lCell.isRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -646,8 +892,24 @@ public final class ECMA48Terminal extends LogicalScreen
                     if (debugToStderr) {
                         System.err.printf("4 Change backColor\n");
                     }
+                } else if (lCell.isRGB()
+                    && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+                    && (lCell.isBold() == lastAttr.isBold())
+                    && (lCell.isReverse() == lastAttr.isReverse())
+                    && (lCell.isUnderline() == lastAttr.isUnderline())
+                    && (lCell.isBlink() == lastAttr.isBlink())
+                ) {
+                    // Attributes same, foreColor different
+                    sb.append(colorRGB(lCell.getBackColorRGB(), false));
+
+                    if (debugToStderr) {
+                        System.err.printf("4 Change backColor (RGB)\n");
+                    }
                 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
                     && (lCell.getBackColor() == lastAttr.getBackColor())
+                    && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+                    && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
                     && (lCell.isBold() == lastAttr.isBold())
                     && (lCell.isReverse() == lastAttr.isReverse())
                     && (lCell.isUnderline() == lastAttr.isUnderline())
@@ -662,16 +924,29 @@ public final class ECMA48Terminal extends LogicalScreen
                     }
                 } else {
                     // Just reset everything again
-                    sb.append(color(lCell.getForeColor(),
-                            lCell.getBackColor(),
-                            lCell.isBold(),
-                            lCell.isReverse(),
-                            lCell.isBlink(),
-                            lCell.isUnderline()));
-
-                    if (debugToStderr) {
-                        System.err.printf("6 Change all attributes\n");
+                    if (!lCell.isRGB()) {
+                        sb.append(color(lCell.getForeColor(),
+                                lCell.getBackColor(),
+                                lCell.isBold(),
+                                lCell.isReverse(),
+                                lCell.isBlink(),
+                                lCell.isUnderline()));
+
+                        if (debugToStderr) {
+                            System.err.printf("6 Change all attributes\n");
+                        }
+                    } else {
+                        sb.append(colorRGB(lCell.getForeColorRGB(),
+                                lCell.getBackColorRGB(),
+                                lCell.isBold(),
+                                lCell.isReverse(),
+                                lCell.isBlink(),
+                                lCell.isUnderline()));
+                        if (debugToStderr) {
+                            System.err.printf("6 Change all attributes (RGB)\n");
+                        }
                     }
+
                 }
                 // Emit the character
                 sb.append(lCell.getChar());
@@ -696,11 +971,6 @@ public final class ECMA48Terminal extends LogicalScreen
      * physical screen
      */
     private String flushString() {
-        if (!dirty) {
-            assert (!reallyCleared);
-            return "";
-        }
-
         CellAttributes attr = null;
 
         StringBuilder sb = new StringBuilder();
@@ -713,7 +983,6 @@ public final class ECMA48Terminal extends LogicalScreen
             flushLine(y, sb, attr);
         }
 
-        dirty = false;
         reallyCleared = false;
 
         String result = sb.toString();
@@ -723,35 +992,6 @@ public final class ECMA48Terminal extends LogicalScreen
         return result;
     }
 
-    /**
-     * Push the logical screen to the physical device.
-     */
-    @Override
-    public void flushPhysical() {
-        String result = flushString();
-        if ((cursorVisible)
-            && (cursorY <= height - 1)
-            && (cursorX <= width - 1)
-        ) {
-            result += cursor(true);
-            result += gotoXY(cursorX, cursorY);
-        } else {
-            result += cursor(false);
-        }
-        output.write(result);
-        flush();
-    }
-
-    /**
-     * Set the window title.
-     *
-     * @param title the new title
-     */
-    public void setTitle(final String title) {
-        output.write(getSetTitleString(title));
-        flush();
-    }
-
     /**
      * Reset keyboard/mouse input parser.
      */
@@ -1066,51 +1306,44 @@ public final class ECMA48Terminal extends LogicalScreen
             eventMouseWheelUp, eventMouseWheelDown);
     }
 
-    /**
-     * Return any events in the IO queue.
-     *
-     * @param queue list to append new events to
-     */
-    public void getEvents(final List<TInputEvent> queue) {
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                synchronized (queue) {
-                    queue.addAll(eventQueue);
-                }
-                eventQueue.clear();
-            }
-        }
-    }
-
     /**
      * Return any events in the IO queue due to timeout.
      *
      * @param queue list to append new events to
      */
     private void getIdleEvents(final List<TInputEvent> queue) {
-        Date now = new Date();
+        long nowTime = System.currentTimeMillis();
 
         // Check for new window size
-        long windowSizeDelay = now.getTime() - windowSizeTime;
+        long windowSizeDelay = nowTime - windowSizeTime;
         if (windowSizeDelay > 1000) {
             sessionInfo.queryWindowSize();
             int newWidth = sessionInfo.getWindowWidth();
             int newHeight = sessionInfo.getWindowHeight();
+
             if ((newWidth != windowResize.getWidth())
                 || (newHeight != windowResize.getHeight())
             ) {
+
+                if (debugToStderr) {
+                    System.err.println("Screen size changed, old size " +
+                        windowResize);
+                    System.err.println("                     new size " +
+                        newWidth + " x " + newHeight);
+                }
+
                 TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
                     newWidth, newHeight);
                 windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
                     newWidth, newHeight);
                 queue.add(event);
             }
-            windowSizeTime = now.getTime();
+            windowSizeTime = nowTime;
         }
 
         // ESCDELAY type timeout
         if (state == ParseState.ESCAPE) {
-            long escDelay = now.getTime() - escapeTime;
+            long escDelay = nowTime - escapeTime;
             if (escDelay > 100) {
                 // After 0.1 seconds, assume a true escape character
                 queue.add(controlChar((char)0x1B, false));
@@ -1174,9 +1407,9 @@ public final class ECMA48Terminal extends LogicalScreen
     private void processChar(final List<TInputEvent> events, final char ch) {
 
         // ESCDELAY type timeout
-        Date now = new Date();
+        long nowTime = System.currentTimeMillis();
         if (state == ParseState.ESCAPE) {
-            long escDelay = now.getTime() - escapeTime;
+            long escDelay = nowTime - escapeTime;
             if (escDelay > 250) {
                 // After 0.25 seconds, assume a true escape character
                 events.add(controlChar((char)0x1B, false));
@@ -1196,7 +1429,7 @@ public final class ECMA48Terminal extends LogicalScreen
 
             if (ch == 0x1B) {
                 state = ParseState.ESCAPE;
-                escapeTime = now.getTime();
+                escapeTime = nowTime;
                 return;
             }
 
@@ -1527,6 +1760,55 @@ public final class ECMA48Terminal extends LogicalScreen
                 rgbColor(bold, color, foreground);
     }
 
+    /**
+     * Create a T.416 RGB parameter sequence for a single color change.
+     *
+     * @param colorRGB a 24-bit RGB value for foreground color
+     * @param foreground if true, this is a foreground color
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
+     */
+    private String colorRGB(final int colorRGB, final boolean foreground) {
+
+        int colorRed     = (colorRGB >> 16) & 0xFF;
+        int colorGreen   = (colorRGB >>  8) & 0xFF;
+        int colorBlue    =  colorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        if (foreground) {
+            sb.append("\033[38;2;");
+        } else {
+            sb.append("\033[48;2;");
+        }
+        sb.append(String.format("%d;%d;%dm", colorRed, colorGreen, colorBlue));
+        return sb.toString();
+    }
+
+    /**
+     * Create a T.416 RGB parameter sequence for both foreground and
+     * background color change.
+     *
+     * @param foreColorRGB a 24-bit RGB value for foreground color
+     * @param backColorRGB a 24-bit RGB value for foreground color
+     * @return the string to emit to an ANSI / ECMA-style terminal,
+     * e.g. "\033[42m"
+     */
+    private String colorRGB(final int foreColorRGB, final int backColorRGB) {
+        int foreColorRed     = (foreColorRGB >> 16) & 0xFF;
+        int foreColorGreen   = (foreColorRGB >>  8) & 0xFF;
+        int foreColorBlue    =  foreColorRGB        & 0xFF;
+        int backColorRed     = (backColorRGB >> 16) & 0xFF;
+        int backColorGreen   = (backColorRGB >>  8) & 0xFF;
+        int backColorBlue    =  backColorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(String.format("\033[38;2;%d;%d;%dm",
+                foreColorRed, foreColorGreen, foreColorBlue));
+        sb.append(String.format("\033[48;2;%d;%d;%dm",
+                backColorRed, backColorGreen, backColorBlue));
+        return sb.toString();
+    }
+
     /**
      * Create a T.416 RGB parameter sequence for a single color change.
      *
@@ -1748,6 +2030,77 @@ public final class ECMA48Terminal extends LogicalScreen
         return sb.toString();
     }
 
+    /**
+     * 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.
+     *
+     * @param foreColorRGB a 24-bit RGB value for foreground color
+     * @param backColorRGB a 24-bit RGB value for foreground color
+     * @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"
+     */
+    private String colorRGB(final int foreColorRGB, final int backColorRGB,
+        final boolean bold, final boolean reverse, final boolean blink,
+        final boolean underline) {
+
+        int foreColorRed     = (foreColorRGB >> 16) & 0xFF;
+        int foreColorGreen   = (foreColorRGB >>  8) & 0xFF;
+        int foreColorBlue    =  foreColorRGB        & 0xFF;
+        int backColorRed     = (backColorRGB >> 16) & 0xFF;
+        int backColorGreen   = (backColorRGB >>  8) & 0xFF;
+        int backColorBlue    =  backColorRGB        & 0xFF;
+
+        StringBuilder sb = new StringBuilder();
+        if        (  bold &&  reverse &&  blink && !underline ) {
+            sb.append("\033[0;1;7;5;");
+        } else if (  bold &&  reverse && !blink && !underline ) {
+            sb.append("\033[0;1;7;");
+        } else if ( !bold &&  reverse &&  blink && !underline ) {
+            sb.append("\033[0;7;5;");
+        } else if (  bold && !reverse &&  blink && !underline ) {
+            sb.append("\033[0;1;5;");
+        } else if (  bold && !reverse && !blink && !underline ) {
+            sb.append("\033[0;1;");
+        } else if ( !bold &&  reverse && !blink && !underline ) {
+            sb.append("\033[0;7;");
+        } else if ( !bold && !reverse &&  blink && !underline) {
+            sb.append("\033[0;5;");
+        } else if (  bold &&  reverse &&  blink &&  underline ) {
+            sb.append("\033[0;1;7;5;4;");
+        } else if (  bold &&  reverse && !blink &&  underline ) {
+            sb.append("\033[0;1;7;4;");
+        } else if ( !bold &&  reverse &&  blink &&  underline ) {
+            sb.append("\033[0;7;5;4;");
+        } else if (  bold && !reverse &&  blink &&  underline ) {
+            sb.append("\033[0;1;5;4;");
+        } else if (  bold && !reverse && !blink &&  underline ) {
+            sb.append("\033[0;1;4;");
+        } else if ( !bold &&  reverse && !blink &&  underline ) {
+            sb.append("\033[0;7;4;");
+        } else if ( !bold && !reverse &&  blink &&  underline) {
+            sb.append("\033[0;5;4;");
+        } else if ( !bold && !reverse && !blink &&  underline) {
+            sb.append("\033[0;4;");
+        } else {
+            assert (!bold && !reverse && !blink && !underline);
+            sb.append("\033[0;");
+        }
+
+        sb.append("m\033[38;2;");
+        sb.append(String.format("%d;%d;%d", foreColorRed, foreColorGreen,
+                foreColorBlue));
+        sb.append("m\033[48;2;");
+        sb.append(String.format("%d;%d;%d", backColorRed, backColorGreen,
+                backColorBlue));
+        sb.append("m");
+        return sb.toString();
+    }
+
     /**
      * Create a SGR parameter sequence to reset to defaults.
      *
@@ -1844,74 +2197,4 @@ public final class ECMA48Terminal extends LogicalScreen
         return "\033[?1002;1003;1006;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];
-        List<TInputEvent> events = new LinkedList<TInputEvent>();
-
-        while (!done && !stopReaderThread) {
-            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, readBuffer.length);
-                    // 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];
-                            processChar(events, (char)ch);
-                        }
-                        getIdleEvents(events);
-                        if (events.size() > 0) {
-                            // Add to the queue for the backend thread to
-                            // be able to obtain.
-                            synchronized (eventQueue) {
-                                eventQueue.addAll(events);
-                            }
-                            synchronized (listener) {
-                                listener.notifyAll();
-                            }
-                            events.clear();
-                        }
-                    }
-                } else {
-                    getIdleEvents(events);
-                    if (events.size() > 0) {
-                        synchronized (eventQueue) {
-                            eventQueue.addAll(events);
-                        }
-                        events.clear();
-                        synchronized (listener) {
-                            listener.notifyAll();
-                        }
-                    }
-
-                    // Wait 10 millis for more data
-                    Thread.sleep(10);
-                }
-                // 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();
-    }
-
 }