LICENSE CHANGED TO MIT
[nikiroo-utils.git] / src / jexer / tterminal / ECMA48.java
index 41a5f4dc6a23ca2edcfd93689bd084b83ceea446..300625b9f244da6bb572f03607744abeea6ca81a 100644 (file)
@@ -1,29 +1,27 @@
-/**
+/*
  * Jexer - Java Text User Interface
  *
- * License: LGPLv3 or later
- *
- * This module is licensed under the GNU Lesser General Public License
- * Version 3.  Please see the file "COPYING" in this directory for more
- * information about the GNU Lesser General Public License Version 3.
+ * The MIT License (MIT)
  *
- *     Copyright (C) 2015  Kevin Lamonte
+ * Copyright (C) 2016 Kevin Lamonte
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see
- * http://www.gnu.org/licenses/, or write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
@@ -43,10 +41,11 @@ import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
+import jexer.TKeypress;
+import jexer.event.TMouseEvent;
 import jexer.bits.Color;
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
-import jexer.TKeypress;
 import static jexer.TKeypress.*;
 
 /**
@@ -58,6 +57,11 @@ import static jexer.TKeypress.*;
  * caveats:
  *
  * <p>
+ * - The vttest scenario for VT220 8-bit controls (11.1.2.3) reports a
+ *   failure with XTERM.  This is due to vttest failing to decode the UTF-8
+ *   stream.
+ *
+ * <p>
  * - Smooth scrolling, printing, keyboard locking, keyboard leds, and tests
  *   from VT100 are not supported.
  *
@@ -142,24 +146,26 @@ public class ECMA48 implements Runnable {
     /**
      * Return the proper TERM environment variable for this device type.
      *
-     * @return "TERM=vt100", "TERM=xterm", etc.
+     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
+     * @return "vt100", "xterm", etc.
      */
-    public String deviceTypeTerm() {
-        switch (type) {
+    public static String deviceTypeTerm(final DeviceType deviceType) {
+        switch (deviceType) {
         case VT100:
-            return "TERM=vt100";
+            return "vt100";
 
         case VT102:
-            return "TERM=vt102";
+            return "vt102";
 
         case VT220:
-            return "TERM=vt220";
+            return "vt220";
 
         case XTERM:
-            return "TERM=xterm";
+            return "xterm";
 
         default:
-            throw new IllegalArgumentException("Invalid device type: " + type);
+            throw new IllegalArgumentException("Invalid device type: "
+                + deviceType);
         }
     }
 
@@ -168,27 +174,27 @@ public class ECMA48 implements Runnable {
      * about UTF-8, the others are defined by their standard to be either
      * 7-bit or 8-bit characters only.
      *
+     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
      * @param baseLang a base language without UTF-8 flag such as "C" or
      * "en_US"
-     * @return "LANG=en_US", "LANG=en_US.UTF-8", etc.
+     * @return "en_US", "en_US.UTF-8", etc.
      */
-    public String deviceTypeLang(final String baseLang) {
-        switch (type) {
+    public static String deviceTypeLang(final DeviceType deviceType,
+        final String baseLang) {
 
-        case VT100:
-            return "LANG=" + baseLang;
+        switch (deviceType) {
 
+        case VT100:
         case VT102:
-            return "LANG=" + baseLang;
-
         case VT220:
-            return "LANG=" + baseLang;
+            return baseLang;
 
         case XTERM:
-            return "LANG=" + baseLang + ".UTF-8";
+            return baseLang + ".UTF-8";
 
         default:
-            throw new IllegalArgumentException("Invalid device type: " + type);
+            throw new IllegalArgumentException("Invalid device type: "
+                + deviceType);
         }
     }
 
@@ -249,8 +255,34 @@ public class ECMA48 implements Runnable {
         // Synchronize so we don't stomp on the reader thread.
         synchronized (this) {
 
-            // Tell the reader thread to stop looking at input.  It will
-            // close the input stream as it exits.
+            // Close the input stream
+            switch (type) {
+            case VT100:
+            case VT102:
+            case VT220:
+                if (inputStream != null) {
+                    try {
+                        inputStream.close();
+                    } catch (IOException e) {
+                        // SQUASH
+                    }
+                    inputStream = null;
+                }
+                break;
+            case XTERM:
+                if (input != null) {
+                    try {
+                        input.close();
+                    } catch (IOException e) {
+                        // SQUASH
+                    }
+                    input = null;
+                    inputStream = null;
+                }
+                break;
+            }
+
+            // Tell the reader thread to stop looking at input.
             if (stopReaderThread == false) {
                 stopReaderThread = true;
                 try {
@@ -294,7 +326,7 @@ public class ECMA48 implements Runnable {
     /**
      * When true, the reader thread is expected to exit.
      */
-    private boolean stopReaderThread = false;
+    private volatile boolean stopReaderThread = false;
 
     /**
      * The reader thread.
@@ -329,7 +361,7 @@ public class ECMA48 implements Runnable {
     /**
      * The scrollback buffer characters + attributes.
      */
-    private List<DisplayLine> scrollback;
+    private volatile List<DisplayLine> scrollback;
 
     /**
      * Get the scrollback buffer.
@@ -343,7 +375,7 @@ public class ECMA48 implements Runnable {
     /**
      * The raw display buffer characters + attributes.
      */
-    private List<DisplayLine> display;
+    private volatile List<DisplayLine> display;
 
     /**
      * Get the display buffer.
@@ -467,6 +499,36 @@ public class ECMA48 implements Runnable {
         G3_GL
     }
 
+    /**
+     * XTERM mouse reporting protocols.
+     */
+    private enum MouseProtocol {
+        OFF,
+        X10,
+        NORMAL,
+        BUTTONEVENT,
+        ANYEVENT
+    }
+
+    /**
+     * Which mouse protocol is active.
+     */
+    private MouseProtocol mouseProtocol = MouseProtocol.OFF;
+
+    /**
+     * XTERM mouse reporting encodings.
+     */
+    private enum MouseEncoding {
+        X10,
+        UTF8,
+        SGR
+    }
+
+    /**
+     * Which mouse encoding is active.
+     */
+    private MouseEncoding mouseEncoding = MouseEncoding.X10;
+
     /**
      * Physical display width.  We start at 80x24, but the user can resize us
      * bigger/smaller.
@@ -551,7 +613,7 @@ public class ECMA48 implements Runnable {
      *
      * @return if true, the cursor is visible
      */
-    public final boolean visibleCursor() {
+    public final boolean isCursorVisible() {
         return cursorVisible;
     }
 
@@ -572,12 +634,6 @@ public class ECMA48 implements Runnable {
         return screenTitle;
     }
 
-    /**
-     * Array of flags that have come in, e.g. '?' (DEC private mode), '=',
-     * '>' (&lt;), ...
-     */
-    private List<Character> csiFlags;
-
     /**
      * Parameter characters being collected.
      */
@@ -586,7 +642,7 @@ public class ECMA48 implements Runnable {
     /**
      * Non-csi collect buffer.
      */
-    private List<Character> collectBuffer;
+    private StringBuilder collectBuffer;
 
     /**
      * When true, use the G1 character set.
@@ -625,6 +681,7 @@ public class ECMA48 implements Runnable {
      * Whether number pad keys send VT100 or VT52, application or numeric
      * sequences.
      */
+    @SuppressWarnings("unused")
     private KeypadMode keypadMode;
 
     /**
@@ -633,6 +690,15 @@ public class ECMA48 implements Runnable {
     private boolean columns132 = false;
 
     /**
+     * Get 132 columns value.
+     *
+     * @return if true, the terminal is in 132 column mode
+     */
+    public final boolean isColumns132() {
+                return columns132;
+        }
+
+        /**
      * true = reverse video.  Set by DECSCNM.
      */
     private boolean reverseVideo = false;
@@ -770,8 +836,7 @@ public class ECMA48 implements Runnable {
      */
     private void toGround() {
         csiParams.clear();
-        csiFlags.clear();
-        collectBuffer.clear();
+        collectBuffer = new StringBuilder(8);
         scanState = ScanState.GROUND;
     }
 
@@ -818,6 +883,10 @@ public class ECMA48 implements Runnable {
         s8c1t                   = false;
         printerControllerMode   = false;
 
+        // XTERM
+        mouseProtocol           = MouseProtocol.OFF;
+        mouseEncoding           = MouseEncoding.X10;
+
         // Tab stops
         resetTabStops();
 
@@ -831,7 +900,7 @@ public class ECMA48 implements Runnable {
      * @param type one of the DeviceType constants to select VT100, VT102,
      * VT220, or XTERM
      * @param inputStream an InputStream connected to the remote side.  For
-     * type == XTERM, inputStrem is converted to a Reader with UTF-8
+     * type == XTERM, inputStream is converted to a Reader with UTF-8
      * encoding.
      * @param outputStream an OutputStream connected to the remote user.  For
      * type == XTERM, outputStream is converted to a Writer with UTF-8
@@ -845,9 +914,7 @@ public class ECMA48 implements Runnable {
         assert (inputStream != null);
         assert (outputStream != null);
 
-        csiFlags          = new ArrayList<Character>();
         csiParams         = new ArrayList<Integer>();
-        collectBuffer     = new ArrayList<Character>();
         tabStops          = new ArrayList<Integer>();
         scrollback        = new LinkedList<DisplayLine>();
         display           = new LinkedList<DisplayLine>();
@@ -920,7 +987,6 @@ public class ECMA48 implements Runnable {
      * Handle a linefeed.
      */
     private void linefeed() {
-        int i;
 
         if (currentState.cursorY < scrollRegionBottom) {
             // Increment screen y
@@ -1031,6 +1097,154 @@ public class ECMA48 implements Runnable {
         }
     }
 
+    /**
+     * Translate the mouse event to a VT100, VT220, or XTERM sequence and
+     * send to the remote side.
+     *
+     * @param mouse mouse event received from the local user
+     */
+    public void mouse(final TMouseEvent mouse) {
+
+        /*
+        System.err.printf("mouse(): protocol %s encoding %s mouse %s\n",
+            mouseProtocol, mouseEncoding, mouse);
+         */
+
+        if (mouseEncoding == MouseEncoding.X10) {
+            // We will support X10 but only for (160,94) and smaller.
+            if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) {
+                return;
+            }
+        }
+
+        switch (mouseProtocol) {
+
+        case OFF:
+            // Do nothing
+            return;
+
+        case X10:
+            // Only report button presses
+            if (mouse.getType() != TMouseEvent.Type.MOUSE_DOWN) {
+                return;
+            }
+            break;
+
+        case NORMAL:
+            // Only report button presses and releases
+            if ((mouse.getType() != TMouseEvent.Type.MOUSE_DOWN)
+                && (mouse.getType() != TMouseEvent.Type.MOUSE_UP)
+            ) {
+                return;
+            }
+            break;
+
+        case BUTTONEVENT:
+            /*
+             * Only report button presses, button releases, and motions that
+             * have a button down (i.e. drag-and-drop).
+             */
+            if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                if (!mouse.isMouse1()
+                    && !mouse.isMouse2()
+                    && !mouse.isMouse3()
+                    && !mouse.isMouseWheelUp()
+                    && !mouse.isMouseWheelDown()
+                ) {
+                    return;
+                }
+            }
+            break;
+
+        case ANYEVENT:
+            // Report everything
+            break;
+        }
+
+        // Now encode the event
+        StringBuilder sb = new StringBuilder(6);
+        if (mouseEncoding == MouseEncoding.SGR) {
+            sb.append((char) 0x1B);
+            sb.append("[<");
+
+            if (mouse.isMouse1()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("32;");
+                } else {
+                    sb.append("0;");
+                }
+            } else if (mouse.isMouse2()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("33;");
+                } else {
+                    sb.append("1;");
+                }
+            } else if (mouse.isMouse3()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append("34;");
+                } else {
+                    sb.append("2;");
+                }
+            } else if (mouse.isMouseWheelUp()) {
+                sb.append("64;");
+            } else if (mouse.isMouseWheelDown()) {
+                sb.append("65;");
+            } else {
+                // This is motion with no buttons down.
+                sb.append("35;");
+            }
+
+            sb.append(String.format("%d;%d", mouse.getX() + 1,
+                    mouse.getY() + 1));
+
+            if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                sb.append("m");
+            } else {
+                sb.append("M");
+            }
+
+        } else {
+            // X10 and UTF8 encodings
+            sb.append((char) 0x1B);
+            sb.append('[');
+            sb.append('M');
+            if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                sb.append((char) (0x03 + 32));
+            } else if (mouse.isMouse1()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x00 + 32 + 32));
+                } else {
+                    sb.append((char) (0x00 + 32));
+                }
+            } else if (mouse.isMouse2()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x01 + 32 + 32));
+                } else {
+                    sb.append((char) (0x01 + 32));
+                }
+            } else if (mouse.isMouse3()) {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
+                    sb.append((char) (0x02 + 32 + 32));
+                } else {
+                    sb.append((char) (0x02 + 32));
+                }
+            } else if (mouse.isMouseWheelUp()) {
+                sb.append((char) (0x04 + 64));
+            } else if (mouse.isMouseWheelDown()) {
+                sb.append((char) (0x05 + 64));
+            } else {
+                // This is motion with no buttons down.
+                sb.append((char) (0x03 + 32));
+            }
+
+            sb.append((char) (mouse.getX() + 33));
+            sb.append((char) (mouse.getY() + 33));
+        }
+
+        // System.err.printf("Would write: \'%s\'\n", sb.toString());
+        writeRemote(sb.toString());
+    }
+
     /**
      * Translate the keyboard press to a VT100, VT220, or XTERM sequence and
      * send to the remote side.
@@ -1049,16 +1263,16 @@ public class ECMA48 implements Runnable {
      */
     private String keypressToString(final TKeypress keypress) {
 
-        if ((fullDuplex == false) && (!keypress.getIsKey())) {
+        if ((fullDuplex == false) && (!keypress.isFnKey())) {
             /*
              * If this is a control character, process it like it came from
              * the remote side.
              */
-            if (keypress.getCh() < 0x20) {
-                handleControlChar(keypress.getCh());
+            if (keypress.getChar() < 0x20) {
+                handleControlChar(keypress.getChar());
             } else {
                 // Local echo for everything else
-                printCharacter(keypress.getCh());
+                printCharacter(keypress.getChar());
             }
         }
 
@@ -1068,18 +1282,18 @@ public class ECMA48 implements Runnable {
         }
 
         // Handle control characters
-        if ((keypress.getCtrl()) && (!keypress.getIsKey())) {
+        if ((keypress.isCtrl()) && (!keypress.isFnKey())) {
             StringBuilder sb = new StringBuilder();
-            char ch = keypress.getCh();
+            char ch = keypress.getChar();
             ch -= 0x40;
             sb.append(ch);
             return sb.toString();
         }
 
         // Handle alt characters
-        if ((keypress.getAlt()) && (!keypress.getIsKey())) {
+        if ((keypress.isAlt()) && (!keypress.isFnKey())) {
             StringBuilder sb = new StringBuilder("\033");
-            char ch = keypress.getCh();
+            char ch = keypress.getChar();
             sb.append(ch);
             return sb.toString();
         }
@@ -1477,9 +1691,9 @@ public class ECMA48 implements Runnable {
         }
 
         // Non-alt, non-ctrl characters
-        if (!keypress.getIsKey()) {
+        if (!keypress.isFnKey()) {
             StringBuilder sb = new StringBuilder();
-            sb.append(keypress.getCh());
+            sb.append(keypress.getChar());
             return sb.toString();
         }
         return "";
@@ -1907,7 +2121,7 @@ public class ECMA48 implements Runnable {
      * @param ch character to save
      */
     private void collect(final char ch) {
-        collectBuffer.add(new Character(ch));
+        collectBuffer.append(ch);
     }
 
     /**
@@ -1969,14 +2183,16 @@ public class ECMA48 implements Runnable {
     }
 
     /**
-     * Set or unset a toggle.  value is 'true' for set ('h'), false for reset
-     * ('l').
+     * Set or unset a toggle.
+     *
+     * @param value true for set ('h'), false for reset ('l')
      */
     private void setToggle(final boolean value) {
         boolean decPrivateModeFlag = false;
-        for (Character ch: collectBuffer) {
-            if (ch == '?') {
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
                 decPrivateModeFlag = true;
+                break;
             }
         }
 
@@ -2230,6 +2446,71 @@ public class ECMA48 implements Runnable {
 
                 break;
 
+            case 1000:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: normal tracking mode
+                    if (value == true) {
+                        mouseProtocol = MouseProtocol.NORMAL;
+                    } else {
+                        mouseProtocol = MouseProtocol.OFF;
+                    }
+                }
+                break;
+
+            case 1002:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: normal tracking mode
+                    if (value == true) {
+                        mouseProtocol = MouseProtocol.BUTTONEVENT;
+                    } else {
+                        mouseProtocol = MouseProtocol.OFF;
+                    }
+                }
+                break;
+
+            case 1003:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: Any-event tracking mode
+                    if (value == true) {
+                        mouseProtocol = MouseProtocol.ANYEVENT;
+                    } else {
+                        mouseProtocol = MouseProtocol.OFF;
+                    }
+                }
+                break;
+
+            case 1005:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: UTF-8 coordinates
+                    if (value == true) {
+                        mouseEncoding = MouseEncoding.UTF8;
+                    } else {
+                        mouseEncoding = MouseEncoding.X10;
+                    }
+                }
+                break;
+
+            case 1006:
+                if ((type == DeviceType.XTERM)
+                    && (decPrivateModeFlag == true)
+                ) {
+                    // Mouse: SGR coordinates
+                    if (value == true) {
+                        mouseEncoding = MouseEncoding.SGR;
+                    } else {
+                        mouseEncoding = MouseEncoding.X10;
+                    }
+                }
+                break;
+
             default:
                 break;
 
@@ -2651,9 +2932,10 @@ public class ECMA48 implements Runnable {
         boolean honorProtected = false;
         boolean decPrivateModeFlag = false;
 
-        for (Character ch: collectBuffer) {
-            if (ch == '?') {
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
                 decPrivateModeFlag = true;
+                break;
             }
         }
 
@@ -2690,9 +2972,10 @@ public class ECMA48 implements Runnable {
         boolean honorProtected = false;
         boolean decPrivateModeFlag = false;
 
-        for (Character ch: collectBuffer) {
-            if (ch == '?') {
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
                 decPrivateModeFlag = true;
+                break;
             }
         }
 
@@ -3032,21 +3315,16 @@ public class ECMA48 implements Runnable {
     private void da() {
         int extendedFlag = 0;
         int i = 0;
-        Character [] chars = collectBuffer.toArray(new Character[0]);
-        StringBuilder args = new StringBuilder();
-        for (int j = 1; j < chars.length; j++) {
-            args.append(chars[j]);
-        }
-
-        if (chars.length > 0) {
-            if (chars[0] == '>') {
+        if (collectBuffer.length() > 0) {
+            String args = collectBuffer.substring(1);
+            if (collectBuffer.charAt(0) == '>') {
                 extendedFlag = 1;
-                if (chars.length >= 2) {
+                if (collectBuffer.length() >= 2) {
                     i = Integer.parseInt(args.toString());
                 }
-            } else if (chars[0] == '=') {
+            } else if (collectBuffer.charAt(0) == '=') {
                 extendedFlag = 2;
-                if (chars.length >= 2) {
+                if (collectBuffer.length() >= 2) {
                     i = Integer.parseInt(args.toString());
                 }
             } else {
@@ -3107,17 +3385,31 @@ public class ECMA48 implements Runnable {
      * DECSTBM - Set top and bottom margins.
      */
     private void decstbm() {
-        int top = getCsiParam(0, 1, 1, height) - 1;
-        int bottom = getCsiParam(1, height, 1, height) - 1;
+        boolean decPrivateModeFlag = false;
 
-        if (top > bottom) {
-            top = bottom;
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
+                decPrivateModeFlag = true;
+                break;
+            }
         }
-        scrollRegionTop = top;
-        scrollRegionBottom = bottom;
+        if (decPrivateModeFlag) {
+            // This could be restore DEC private mode values.
+            // Ignore it.
+        } else {
+            // DECSTBM
+            int top = getCsiParam(0, 1, 1, height) - 1;
+            int bottom = getCsiParam(1, height, 1, height) - 1;
 
-        // Home cursor
-        cursorPosition(0, 0);
+            if (top > bottom) {
+                top = bottom;
+            }
+            scrollRegionTop = top;
+            scrollRegionBottom = bottom;
+
+            // Home cursor
+            cursorPosition(0, 0);
+        }
     }
 
     /**
@@ -3185,9 +3477,10 @@ public class ECMA48 implements Runnable {
     private void dsr() {
         boolean decPrivateModeFlag = false;
 
-        for (Character ch: collectBuffer) {
-            if (ch == '?') {
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
                 decPrivateModeFlag = true;
+                break;
             }
         }
 
@@ -3323,7 +3616,7 @@ public class ECMA48 implements Runnable {
         for (int i = start; i <= end; i++) {
             DisplayLine line = display.get(currentState.cursorY);
             if ((!honorProtected)
-                || ((honorProtected) && (!line.charAt(i).getProtect()))) {
+                || ((honorProtected) && (!line.charAt(i).isProtect()))) {
 
                 switch (type) {
                 case VT100:
@@ -3394,9 +3687,10 @@ public class ECMA48 implements Runnable {
      */
     private void printerFunctions() {
         boolean decPrivateModeFlag = false;
-        for (Character ch: collectBuffer) {
-            if (ch == '?') {
+        for (int i = 0; i < collectBuffer.length(); i++) {
+            if (collectBuffer.charAt(i) == '?') {
                 decPrivateModeFlag = true;
+                break;
             }
         }
 
@@ -3454,15 +3748,12 @@ public class ECMA48 implements Runnable {
      */
     private void oscPut(final char xtermChar) {
         // Collect first
-        collectBuffer.add(new Character(xtermChar));
+        collectBuffer.append(xtermChar);
 
         // Xterm cases...
         if (xtermChar == 0x07) {
-            Character [] chars = collectBuffer.toArray(new Character[0]);
-            StringBuilder args = new StringBuilder();
-            for (int j = 0; j < chars.length - 1; j++) {
-                args.append(chars[j]);
-            }
+            String args = collectBuffer.substring(0,
+                collectBuffer.length() - 1);
             String [] p = args.toString().split(";");
             if (p.length > 0) {
                 if ((p[0].equals("0")) || (p[0].equals("2"))) {
@@ -3484,7 +3775,7 @@ public class ECMA48 implements Runnable {
      *
      * @param ch character from the remote side
      */
-    public void consume(char ch) {
+    private void consume(char ch) {
 
         // DEBUG
         // System.err.printf("%c", ch);
@@ -3934,150 +4225,150 @@ public class ECMA48 implements Runnable {
             if ((ch >= 0x30) && (ch <= 0x7E)) {
                 switch (ch) {
                 case '0':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '(')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '(')) {
                         // G0 --> Special graphics
                         currentState.g0Charset = CharacterSet.DRAWING;
                     }
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == ')')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == ')')) {
                         // G1 --> Special graphics
                         currentState.g1Charset = CharacterSet.DRAWING;
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> Special graphics
                             currentState.g2Charset = CharacterSet.DRAWING;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> Special graphics
                             currentState.g3Charset = CharacterSet.DRAWING;
                         }
                     }
                     break;
                 case '1':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '(')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '(')) {
                         // G0 --> Alternate character ROM standard character set
                         currentState.g0Charset = CharacterSet.ROM;
                     }
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == ')')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == ')')) {
                         // G1 --> Alternate character ROM standard character set
                         currentState.g1Charset = CharacterSet.ROM;
                     }
                     break;
                 case '2':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '(')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '(')) {
                         // G0 --> Alternate character ROM special graphics
                         currentState.g0Charset = CharacterSet.ROM_SPECIAL;
                     }
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == ')')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == ')')) {
                         // G1 --> Alternate character ROM special graphics
                         currentState.g1Charset = CharacterSet.ROM_SPECIAL;
                     }
                     break;
                 case '3':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '#')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '#')) {
                         // DECDHL - Double-height line (top half)
                         dechdl(true);
                     }
                     break;
                 case '4':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '#')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '#')) {
                         // DECDHL - Double-height line (bottom half)
                         dechdl(false);
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> DUTCH
                             currentState.g0Charset = CharacterSet.NRC_DUTCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> DUTCH
                             currentState.g1Charset = CharacterSet.NRC_DUTCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> DUTCH
                             currentState.g2Charset = CharacterSet.NRC_DUTCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> DUTCH
                             currentState.g3Charset = CharacterSet.NRC_DUTCH;
                         }
                     }
                     break;
                 case '5':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '#')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '#')) {
                         // DECSWL - Single-width line
                         decswl();
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> FINNISH
                             currentState.g0Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> FINNISH
                             currentState.g1Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> FINNISH
                             currentState.g2Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> FINNISH
                             currentState.g3Charset = CharacterSet.NRC_FINNISH;
                         }
                     }
                     break;
                 case '6':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '#')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '#')) {
                         // DECDWL - Double-width line
                         decdwl();
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> NORWEGIAN
                             currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> NORWEGIAN
                             currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> NORWEGIAN
                             currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> NORWEGIAN
                             currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
                         }
@@ -4087,31 +4378,31 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> SWEDISH
                             currentState.g0Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> SWEDISH
                             currentState.g1Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> SWEDISH
                             currentState.g2Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> SWEDISH
                             currentState.g3Charset = CharacterSet.NRC_SWEDISH;
                         }
                     }
                     break;
                 case '8':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '#')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '#')) {
                         // DECALN - Screen alignment display
                         decaln();
                     }
@@ -4124,23 +4415,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> DEC_SUPPLEMENTAL
                             currentState.g0Charset = CharacterSet.DEC_SUPPLEMENTAL;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> DEC_SUPPLEMENTAL
                             currentState.g1Charset = CharacterSet.DEC_SUPPLEMENTAL;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> DEC_SUPPLEMENTAL
                             currentState.g2Charset = CharacterSet.DEC_SUPPLEMENTAL;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> DEC_SUPPLEMENTAL
                             currentState.g3Charset = CharacterSet.DEC_SUPPLEMENTAL;
                         }
@@ -4150,23 +4441,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> SWISS
                             currentState.g0Charset = CharacterSet.NRC_SWISS;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> SWISS
                             currentState.g1Charset = CharacterSet.NRC_SWISS;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> SWISS
                             currentState.g2Charset = CharacterSet.NRC_SWISS;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> SWISS
                             currentState.g3Charset = CharacterSet.NRC_SWISS;
                         }
@@ -4177,52 +4468,52 @@ public class ECMA48 implements Runnable {
                 case '@':
                     break;
                 case 'A':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '(')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '(')) {
                         // G0 --> United Kingdom set
                         currentState.g0Charset = CharacterSet.UK;
                     }
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == ')')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == ')')) {
                         // G1 --> United Kingdom set
                         currentState.g1Charset = CharacterSet.UK;
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> United Kingdom set
                             currentState.g2Charset = CharacterSet.UK;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> United Kingdom set
                             currentState.g3Charset = CharacterSet.UK;
                         }
                     }
                     break;
                 case 'B':
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == '(')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == '(')) {
                         // G0 --> ASCII set
                         currentState.g0Charset = CharacterSet.US;
                     }
-                    if ((collectBuffer.size() == 1)
-                        && (collectBuffer.get(0) == ')')) {
+                    if ((collectBuffer.length() == 1)
+                        && (collectBuffer.charAt(0) == ')')) {
                         // G1 --> ASCII set
                         currentState.g1Charset = CharacterSet.US;
                     }
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> ASCII
                             currentState.g2Charset = CharacterSet.US;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> ASCII
                             currentState.g3Charset = CharacterSet.US;
                         }
@@ -4232,23 +4523,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> FINNISH
                             currentState.g0Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> FINNISH
                             currentState.g1Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> FINNISH
                             currentState.g2Charset = CharacterSet.NRC_FINNISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> FINNISH
                             currentState.g3Charset = CharacterSet.NRC_FINNISH;
                         }
@@ -4260,23 +4551,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> NORWEGIAN
                             currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> NORWEGIAN
                             currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> NORWEGIAN
                             currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> NORWEGIAN
                             currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
                         }
@@ -4286,8 +4577,8 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ' ')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ' ')) {
                             // S7C1T
                             s8c1t = false;
                         }
@@ -4297,8 +4588,8 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ' ')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ' ')) {
                             // S8C1T
                             s8c1t = true;
                         }
@@ -4308,23 +4599,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> SWEDISH
                             currentState.g0Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> SWEDISH
                             currentState.g1Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> SWEDISH
                             currentState.g2Charset = CharacterSet.NRC_SWEDISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> SWEDISH
                             currentState.g3Charset = CharacterSet.NRC_SWEDISH;
                         }
@@ -4337,23 +4628,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> GERMAN
                             currentState.g0Charset = CharacterSet.NRC_GERMAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> GERMAN
                             currentState.g1Charset = CharacterSet.NRC_GERMAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> GERMAN
                             currentState.g2Charset = CharacterSet.NRC_GERMAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> GERMAN
                             currentState.g3Charset = CharacterSet.NRC_GERMAN;
                         }
@@ -4369,23 +4660,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> FRENCH_CA
                             currentState.g0Charset = CharacterSet.NRC_FRENCH_CA;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> FRENCH_CA
                             currentState.g1Charset = CharacterSet.NRC_FRENCH_CA;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> FRENCH_CA
                             currentState.g2Charset = CharacterSet.NRC_FRENCH_CA;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> FRENCH_CA
                             currentState.g3Charset = CharacterSet.NRC_FRENCH_CA;
                         }
@@ -4395,23 +4686,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> FRENCH
                             currentState.g0Charset = CharacterSet.NRC_FRENCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> FRENCH
                             currentState.g1Charset = CharacterSet.NRC_FRENCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> FRENCH
                             currentState.g2Charset = CharacterSet.NRC_FRENCH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> FRENCH
                             currentState.g3Charset = CharacterSet.NRC_FRENCH;
                         }
@@ -4428,23 +4719,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> ITALIAN
                             currentState.g0Charset = CharacterSet.NRC_ITALIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> ITALIAN
                             currentState.g1Charset = CharacterSet.NRC_ITALIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> ITALIAN
                             currentState.g2Charset = CharacterSet.NRC_ITALIAN;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> ITALIAN
                             currentState.g3Charset = CharacterSet.NRC_ITALIAN;
                         }
@@ -4454,23 +4745,23 @@ public class ECMA48 implements Runnable {
                     if ((type == DeviceType.VT220)
                         || (type == DeviceType.XTERM)) {
 
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '(')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '(')) {
                             // G0 --> SPANISH
                             currentState.g0Charset = CharacterSet.NRC_SPANISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == ')')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == ')')) {
                             // G1 --> SPANISH
                             currentState.g1Charset = CharacterSet.NRC_SPANISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '*')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '*')) {
                             // G2 --> SPANISH
                             currentState.g2Charset = CharacterSet.NRC_SPANISH;
                         }
-                        if ((collectBuffer.size() == 1)
-                            && (collectBuffer.get(0) == '+')) {
+                        if ((collectBuffer.length() == 1)
+                            && (collectBuffer.charAt(0) == '+')) {
                             // G3 --> SPANISH
                             currentState.g3Charset = CharacterSet.NRC_SPANISH;
                         }
@@ -5114,13 +5405,13 @@ public class ECMA48 implements Runnable {
                 case 'p':
                     if (((type == DeviceType.VT220)
                             || (type == DeviceType.XTERM))
-                        && (collectBuffer.get(collectBuffer.size() - 1) == '\"')
+                        && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
                     ) {
                         // DECSCL - compatibility level
                         decscl();
                     }
                     if ((type == DeviceType.XTERM)
-                        && (collectBuffer.get(collectBuffer.size() - 1) == '!')
+                        && (collectBuffer.charAt(collectBuffer.length() - 1) == '!')
                     ) {
                         // DECSTR - Soft terminal reset
                         decstr();
@@ -5129,7 +5420,7 @@ public class ECMA48 implements Runnable {
                 case 'q':
                     if (((type == DeviceType.VT220)
                             || (type == DeviceType.XTERM))
-                        && (collectBuffer.get(collectBuffer.size() - 1) == '\"')
+                        && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
                     ) {
                         // DECSCA
                         decsca();
@@ -5188,8 +5479,9 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.size() > 0)
-                    && (collectBuffer.get(collectBuffer.size() - 1) == 0x1B)) {
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
+                ) {
                     toGround();
                 }
             }
@@ -5241,8 +5533,9 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.size() > 0) &&
-                    (collectBuffer.get(collectBuffer.size() - 1) == 0x1B)) {
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
+                ) {
                     toGround();
                 }
             }
@@ -5272,8 +5565,9 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.size() > 0) &&
-                    (collectBuffer.get(collectBuffer.size() - 1) == 0x1B)) {
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
+                ) {
                     toGround();
                 }
             }
@@ -5319,8 +5613,8 @@ public class ECMA48 implements Runnable {
                 collect(ch);
             }
             if (ch == 0x5C) {
-                if ((collectBuffer.size() > 0)
-                    && (collectBuffer.get(collectBuffer.size() - 1) == 0x1B)
+                if ((collectBuffer.length() > 0)
+                    && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
                 ) {
                     toGround();
                 }
@@ -5387,12 +5681,12 @@ public class ECMA48 implements Runnable {
 
         case VT52_DIRECT_CURSOR_ADDRESS:
             // This is a special case for the VT52 sequence "ESC Y l c"
-            if (collectBuffer.size() == 0) {
+            if (collectBuffer.length() == 0) {
                 collect(ch);
-            } else if (collectBuffer.size() == 1) {
+            } else if (collectBuffer.length() == 1) {
                 // We've got the two characters, one in the buffer and the
                 // other in ch.
-                cursorPosition(collectBuffer.get(0) - '\040', ch - '\040');
+                cursorPosition(collectBuffer.charAt(0) - '\040', ch - '\040');
                 toGround();
             }
             return;
@@ -5406,6 +5700,9 @@ public class ECMA48 implements Runnable {
      * @return current cursor X
      */
     public final int getCursorX() {
+        if (display.get(currentState.cursorY).isDoubleWidth()) {
+            return currentState.cursorX * 2;
+        }
         return currentState.cursorX;
     }
 
@@ -5421,7 +5718,7 @@ public class ECMA48 implements Runnable {
     /**
      * Read function runs on a separate thread.
      */
-    public void run() {
+    public final void run() {
         boolean utf8 = false;
         boolean done = false;
 
@@ -5441,77 +5738,56 @@ public class ECMA48 implements Runnable {
 
         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) {
-                    // System.err.printf("available() %d\n", n); System.err.flush();
-                    if (utf8) {
-                        if (readBufferUTF8.length < n) {
-                            // The buffer wasn't big enough, make it huger
-                            int newSize = Math.max(readBufferUTF8.length * 2, n);
-
-                            readBufferUTF8 = new char[newSize];
-                        }
-                    } else {
-                        if (readBuffer.length < n) {
-                            // The buffer wasn't big enough, make it huger
-                            int newSize = Math.max(readBuffer.length * 2, n);
-                            readBuffer = new byte[newSize];
-                        }
-                    }
+                // System.err.printf("available() %d\n", n); System.err.flush();
+                if (utf8) {
+                    if (readBufferUTF8.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        int newSizeHalf = Math.max(readBufferUTF8.length, n);
 
-                    int rc = -1;
-                    if (utf8) {
-                        rc = input.read(readBufferUTF8, 0, n);
-                    } else {
-                        rc = inputStream.read(readBuffer, 0, n);
+                        readBufferUTF8 = new char[newSizeHalf * 2];
                     }
-                    // System.err.printf("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 = 0;
-                            if (utf8) {
-                                ch = readBufferUTF8[i];
-                            } else {
-                                ch = readBuffer[i];
-                            }
-                            // Don't step on UI events
-                            synchronized (this) {
-                                consume((char)ch);
-                            }
-                        }
+                } else {
+                    if (readBuffer.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        int newSizeHalf = Math.max(readBuffer.length, n);
+                        readBuffer = new byte[newSizeHalf * 2];
                     }
+                }
+
+                int rc = -1;
+                if (utf8) {
+                    rc = input.read(readBufferUTF8, 0,
+                        readBufferUTF8.length);
+                } else {
+                    rc = inputStream.read(readBuffer, 0,
+                        readBuffer.length);
+                }
+                // System.err.printf("read() %d\n", rc); System.err.flush();
+                if (rc == -1) {
+                    // This is EOF
+                    done = true;
                 } else {
-                    // Wait 10 millis for more data
-                    Thread.sleep(10);
+                    for (int i = 0; i < rc; i++) {
+                        int ch = 0;
+                        if (utf8) {
+                            ch = readBufferUTF8[i];
+                        } else {
+                            ch = readBuffer[i];
+                        }
+                        // Don't step on UI events
+                        synchronized (this) {
+                            consume((char)ch);
+                        }
+                    }
                 }
                 // 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))
 
-        // Close the input stream
-        try {
-            if (utf8) {
-                input.close();
-                input = null;
-                inputStream = null;
-            } else {
-                inputStream.close();
-                inputStream = null;
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-
         // Let the rest of the world know that I am done.
         stopReaderThread = true;