first screenshot attempt
[fanfix.git] / src / jexer / io / ECMA48Terminal.java
index 6defbe4937c856840a6f97e2ca6f9918f783ebbb..7721330f8d852480d186c424c8965075ca89829d 100644 (file)
@@ -61,7 +61,7 @@ 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 class ECMA48Terminal implements Runnable {
+public final class ECMA48Terminal implements Runnable {
 
     /**
      * The session information.
@@ -73,7 +73,7 @@ public class ECMA48Terminal implements Runnable {
      *
      * @return the SessionInfo
      */
-    public final SessionInfo getSessionInfo() {
+    public SessionInfo getSessionInfo() {
         return sessionInfo;
     }
 
@@ -98,11 +98,6 @@ public class ECMA48Terminal implements Runnable {
      */
     private ArrayList<String> params;
 
-    /**
-     * params[paramI] is being appended to.
-     */
-    private int paramI;
-
     /**
      * States in the input parser.
      */
@@ -127,6 +122,12 @@ public class ECMA48Terminal implements Runnable {
      */
     private long escapeTime;
 
+    /**
+     * The time we last checked the window size.  We try not to spawn stty
+     * more than once per second.
+     */
+    private long windowSizeTime;
+
     /**
      * true if mouse1 was down.  Used to report mouse1 on the release event.
      */
@@ -181,6 +182,11 @@ public class ECMA48Terminal implements Runnable {
      */
     private PrintWriter output;
 
+    /**
+     * The listening object that run() wakes up on new input.
+     */
+    private Object listener;
+
     /**
      * When true, the terminal is sending non-UTF8 bytes when reporting mouse
      * events.
@@ -243,7 +249,7 @@ public class ECMA48Terminal implements Runnable {
         };
         try {
             Process process;
-            if (mode == true) {
+            if (mode) {
                 process = Runtime.getRuntime().exec(cmdRaw);
             } else {
                 process = Runtime.getRuntime().exec(cmdCooked);
@@ -278,6 +284,8 @@ public class ECMA48Terminal implements Runnable {
     /**
      * Constructor sets up state for getEvent().
      *
+     * @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
@@ -288,7 +296,7 @@ public class ECMA48Terminal implements Runnable {
      * @throws UnsupportedEncodingException if an exception is thrown when
      * creating the InputStreamReader
      */
-    public ECMA48Terminal(final InputStream input,
+    public ECMA48Terminal(final Object listener, final InputStream input,
         final OutputStream output) throws UnsupportedEncodingException {
 
         reset();
@@ -296,6 +304,7 @@ public class ECMA48Terminal implements Runnable {
         mouse2           = false;
         mouse3           = false;
         stopReaderThread = false;
+        this.listener    = listener;
 
         if (input == null) {
             // inputStream = System.in;
@@ -396,7 +405,6 @@ public class ECMA48Terminal implements Runnable {
     private void reset() {
         state = ParseState.GROUND;
         params = new ArrayList<String>();
-        paramI = 0;
         params.clear();
         params.add("");
     }
@@ -406,41 +414,34 @@ public class ECMA48Terminal implements Runnable {
      * etc.).
      *
      * @param ch Unicode code point
+     * @param alt if true, set alt on the TKeypress
      * @return one TKeypress event, either a control character (e.g. isKey ==
      * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
      * fnKey == ESC)
      */
-    private TKeypressEvent controlChar(final char ch) {
-        TKeypressEvent event = new TKeypressEvent();
-
+    private TKeypressEvent controlChar(final char ch, final boolean alt) {
         // System.err.printf("controlChar: %02x\n", ch);
 
         switch (ch) {
         case 0x0D:
             // Carriage return --> ENTER
-            event.key = kbEnter;
-            break;
+            return new TKeypressEvent(kbEnter, alt, false, false);
         case 0x0A:
             // Linefeed --> ENTER
-            event.key = kbEnter;
-            break;
+            return new TKeypressEvent(kbEnter, alt, false, false);
         case 0x1B:
             // ESC
-            event.key = kbEsc;
-            break;
+            return new TKeypressEvent(kbEsc, alt, false, false);
         case '\t':
             // TAB
-            event.key = kbTab;
-            break;
+            return new TKeypressEvent(kbTab, alt, false, false);
         default:
             // Make all other control characters come back as the alphabetic
             // character with the ctrl field set.  So SOH would be 'A' +
             // ctrl.
-            event.key = new TKeypress(false, 0, (char)(ch + 0x40),
-                false, true, false);
-            break;
+            return new TKeypressEvent(false, 0, (char)(ch + 0x40),
+                alt, true, false);
         }
-        return event;
     }
 
     /**
@@ -457,220 +458,64 @@ public class ECMA48Terminal implements Runnable {
         if (params.size() > 1) {
             modifier = Integer.parseInt(params.get(1));
         }
-        TKeypressEvent event = new TKeypressEvent();
+        boolean alt = false;
+        boolean ctrl = false;
+        boolean shift = false;
 
         switch (modifier) {
         case 0:
             // No modifier
-            switch (key) {
-            case 1:
-                event.key = kbHome;
-                break;
-            case 2:
-                event.key = kbIns;
-                break;
-            case 3:
-                event.key = kbDel;
-                break;
-            case 4:
-                event.key = kbEnd;
-                break;
-            case 5:
-                event.key = kbPgUp;
-                break;
-            case 6:
-                event.key = kbPgDn;
-                break;
-            case 15:
-                event.key = kbF5;
-                break;
-            case 17:
-                event.key = kbF6;
-                break;
-            case 18:
-                event.key = kbF7;
-                break;
-            case 19:
-                event.key = kbF8;
-                break;
-            case 20:
-                event.key = kbF9;
-                break;
-            case 21:
-                event.key = kbF10;
-                break;
-            case 23:
-                event.key = kbF11;
-                break;
-            case 24:
-                event.key = kbF12;
-                break;
-            default:
-                // Unknown
-                return null;
-            }
-
             break;
         case 2:
             // Shift
-            switch (key) {
-            case 1:
-                event.key = kbShiftHome;
-                break;
-            case 2:
-                event.key = kbShiftIns;
-                break;
-            case 3:
-                event.key = kbShiftDel;
-                break;
-            case 4:
-                event.key = kbShiftEnd;
-                break;
-            case 5:
-                event.key = kbShiftPgUp;
-                break;
-            case 6:
-                event.key = kbShiftPgDn;
-                break;
-            case 15:
-                event.key = kbShiftF5;
-                break;
-            case 17:
-                event.key = kbShiftF6;
-                break;
-            case 18:
-                event.key = kbShiftF7;
-                break;
-            case 19:
-                event.key = kbShiftF8;
-                break;
-            case 20:
-                event.key = kbShiftF9;
-                break;
-            case 21:
-                event.key = kbShiftF10;
-                break;
-            case 23:
-                event.key = kbShiftF11;
-                break;
-            case 24:
-                event.key = kbShiftF12;
-                break;
-            default:
-                // Unknown
-                return null;
-            }
+            shift = true;
             break;
-
         case 3:
             // Alt
-            switch (key) {
-            case 1:
-                event.key = kbAltHome;
-                break;
-            case 2:
-                event.key = kbAltIns;
-                break;
-            case 3:
-                event.key = kbAltDel;
-                break;
-            case 4:
-                event.key = kbAltEnd;
-                break;
-            case 5:
-                event.key = kbAltPgUp;
-                break;
-            case 6:
-                event.key = kbAltPgDn;
-                break;
-            case 15:
-                event.key = kbAltF5;
-                break;
-            case 17:
-                event.key = kbAltF6;
-                break;
-            case 18:
-                event.key = kbAltF7;
-                break;
-            case 19:
-                event.key = kbAltF8;
-                break;
-            case 20:
-                event.key = kbAltF9;
-                break;
-            case 21:
-                event.key = kbAltF10;
-                break;
-            case 23:
-                event.key = kbAltF11;
-                break;
-            case 24:
-                event.key = kbAltF12;
-                break;
-            default:
-                // Unknown
-                return null;
-            }
+            alt = true;
             break;
-
         case 5:
             // Ctrl
-            switch (key) {
-            case 1:
-                event.key = kbCtrlHome;
-                break;
-            case 2:
-                event.key = kbCtrlIns;
-                break;
-            case 3:
-                event.key = kbCtrlDel;
-                break;
-            case 4:
-                event.key = kbCtrlEnd;
-                break;
-            case 5:
-                event.key = kbCtrlPgUp;
-                break;
-            case 6:
-                event.key = kbCtrlPgDn;
-                break;
-            case 15:
-                event.key = kbCtrlF5;
-                break;
-            case 17:
-                event.key = kbCtrlF6;
-                break;
-            case 18:
-                event.key = kbCtrlF7;
-                break;
-            case 19:
-                event.key = kbCtrlF8;
-                break;
-            case 20:
-                event.key = kbCtrlF9;
-                break;
-            case 21:
-                event.key = kbCtrlF10;
-                break;
-            case 23:
-                event.key = kbCtrlF11;
-                break;
-            case 24:
-                event.key = kbCtrlF12;
-                break;
-            default:
-                // Unknown
-                return null;
-            }
+            ctrl = true;
             break;
+        default:
+            // Unknown modifier, bail out
+            return null;
+        }
 
+        switch (key) {
+        case 1:
+            return new TKeypressEvent(kbHome, alt, ctrl, shift);
+        case 2:
+            return new TKeypressEvent(kbIns, alt, ctrl, shift);
+        case 3:
+            return new TKeypressEvent(kbDel, alt, ctrl, shift);
+        case 4:
+            return new TKeypressEvent(kbEnd, alt, ctrl, shift);
+        case 5:
+            return new TKeypressEvent(kbPgUp, alt, ctrl, shift);
+        case 6:
+            return new TKeypressEvent(kbPgDn, alt, ctrl, shift);
+        case 15:
+            return new TKeypressEvent(kbF5, alt, ctrl, shift);
+        case 17:
+            return new TKeypressEvent(kbF6, alt, ctrl, shift);
+        case 18:
+            return new TKeypressEvent(kbF7, alt, ctrl, shift);
+        case 19:
+            return new TKeypressEvent(kbF8, alt, ctrl, shift);
+        case 20:
+            return new TKeypressEvent(kbF9, alt, ctrl, shift);
+        case 21:
+            return new TKeypressEvent(kbF10, alt, ctrl, shift);
+        case 23:
+            return new TKeypressEvent(kbF11, alt, ctrl, shift);
+        case 24:
+            return new TKeypressEvent(kbF12, alt, ctrl, shift);
         default:
             // Unknown
             return null;
         }
-
-        // All OK, return a keypress
-        return event;
     }
 
     /**
@@ -693,97 +538,100 @@ public class ECMA48Terminal implements Runnable {
             y = windowResize.getHeight() - 1;
         }
 
-        TMouseEvent event = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN);
-        event.x = x;
-        event.y = y;
-        event.absoluteX = x;
-        event.absoluteY = y;
+        TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN;
+        boolean eventMouse1 = false;
+        boolean eventMouse2 = false;
+        boolean eventMouse3 = false;
+        boolean eventMouseWheelUp = false;
+        boolean eventMouseWheelDown = false;
 
         // System.err.printf("buttons: %04x\r\n", buttons);
 
         switch (buttons) {
         case 0:
-            event.mouse1 = true;
+            eventMouse1 = true;
             mouse1 = true;
             break;
         case 1:
-            event.mouse2 = true;
+            eventMouse2 = true;
             mouse2 = true;
             break;
         case 2:
-            event.mouse3 = true;
+            eventMouse3 = true;
             mouse3 = true;
             break;
         case 3:
             // Release or Move
             if (!mouse1 && !mouse2 && !mouse3) {
-                event.type = TMouseEvent.Type.MOUSE_MOTION;
+                eventType = TMouseEvent.Type.MOUSE_MOTION;
             } else {
-                event.type = TMouseEvent.Type.MOUSE_UP;
+                eventType = TMouseEvent.Type.MOUSE_UP;
             }
             if (mouse1) {
                 mouse1 = false;
-                event.mouse1 = true;
+                eventMouse1 = true;
             }
             if (mouse2) {
                 mouse2 = false;
-                event.mouse2 = true;
+                eventMouse2 = true;
             }
             if (mouse3) {
                 mouse3 = false;
-                event.mouse3 = true;
+                eventMouse3 = true;
             }
             break;
 
         case 32:
             // Dragging with mouse1 down
-            event.mouse1 = true;
+            eventMouse1 = true;
             mouse1 = true;
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
 
         case 33:
             // Dragging with mouse2 down
-            event.mouse2 = true;
+            eventMouse2 = true;
             mouse2 = true;
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
 
         case 34:
             // Dragging with mouse3 down
-            event.mouse3 = true;
+            eventMouse3 = true;
             mouse3 = true;
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
 
         case 96:
             // Dragging with mouse2 down after wheelUp
-            event.mouse2 = true;
+            eventMouse2 = true;
             mouse2 = true;
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
 
         case 97:
             // Dragging with mouse2 down after wheelDown
-            event.mouse2 = true;
+            eventMouse2 = true;
             mouse2 = true;
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
 
         case 64:
-            event.mouseWheelUp = true;
+            eventMouseWheelUp = true;
             break;
 
         case 65:
-            event.mouseWheelDown = true;
+            eventMouseWheelDown = true;
             break;
 
         default:
             // Unknown, just make it motion
-            event.type = TMouseEvent.Type.MOUSE_MOTION;
+            eventType = TMouseEvent.Type.MOUSE_MOTION;
             break;
         }
-        return event;
+        return new TMouseEvent(eventType, x, y, x, y,
+            eventMouse1, eventMouse2, eventMouse3,
+            eventMouseWheelUp, eventMouseWheelDown);
     }
 
     /**
@@ -794,7 +642,9 @@ public class ECMA48Terminal implements Runnable {
     public void getEvents(final List<TInputEvent> queue) {
         synchronized (eventQueue) {
             if (eventQueue.size() > 0) {
-                queue.addAll(eventQueue);
+                synchronized (queue) {
+                    queue.addAll(eventQueue);
+                }
                 eventQueue.clear();
             }
         }
@@ -805,28 +655,34 @@ public class ECMA48Terminal implements Runnable {
      *
      * @param queue list to append new events to
      */
-    public void getIdleEvents(final List<TInputEvent> queue) {
+    private void getIdleEvents(final List<TInputEvent> queue) {
+        Date now = new Date();
 
         // Check for new window size
-        sessionInfo.queryWindowSize();
-        int newWidth = sessionInfo.getWindowWidth();
-        int newHeight = sessionInfo.getWindowHeight();
-        if ((newWidth != windowResize.getWidth())
-            || (newHeight != windowResize.getHeight())
-        ) {
-            TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
-                newWidth, newHeight);
-            windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
-                newWidth, newHeight);
-            synchronized (eventQueue) {
-                eventQueue.add(event);
+        long windowSizeDelay = now.getTime() - windowSizeTime;
+        if (windowSizeDelay > 1000) {
+            sessionInfo.queryWindowSize();
+            int newWidth = sessionInfo.getWindowWidth();
+            int newHeight = sessionInfo.getWindowHeight();
+            if ((newWidth != windowResize.getWidth())
+                || (newHeight != windowResize.getHeight())
+            ) {
+                TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
+                    newWidth, newHeight);
+                windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
+                    newWidth, newHeight);
+                queue.add(event);
             }
+            windowSizeTime = now.getTime();
         }
 
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                queue.addAll(eventQueue);
-                eventQueue.clear();
+        // ESCDELAY type timeout
+        if (state == ParseState.ESCAPE) {
+            long escDelay = now.getTime() - escapeTime;
+            if (escDelay > 100) {
+                // After 0.1 seconds, assume a true escape character
+                queue.add(controlChar((char)0x1B, false));
+                reset();
             }
         }
     }
@@ -840,19 +696,24 @@ public class ECMA48Terminal implements Runnable {
      */
     private void processChar(final List<TInputEvent> events, final char ch) {
 
-        TKeypressEvent keypress;
-        Date now = new Date();
-
         // ESCDELAY type timeout
+        Date now = new Date();
         if (state == ParseState.ESCAPE) {
             long escDelay = now.getTime() - escapeTime;
             if (escDelay > 250) {
                 // After 0.25 seconds, assume a true escape character
-                events.add(controlChar((char)0x1B));
+                events.add(controlChar((char)0x1B, false));
                 reset();
             }
         }
 
+        // TKeypress fields
+        boolean ctrl = false;
+        boolean alt = false;
+        boolean shift = false;
+        char keyCh = ch;
+        TKeypress key;
+
         // System.err.printf("state: %s ch %c\r\n", state, ch);
 
         switch (state) {
@@ -866,17 +727,15 @@ public class ECMA48Terminal implements Runnable {
 
             if (ch <= 0x1F) {
                 // Control character
-                events.add(controlChar(ch));
+                events.add(controlChar(ch, false));
                 reset();
                 return;
             }
 
             if (ch >= 0x20) {
                 // Normal character
-                keypress = new TKeypressEvent();
-                keypress.key.isKey = false;
-                keypress.key.ch = ch;
-                events.add(keypress);
+                events.add(new TKeypressEvent(false, 0, ch,
+                        false, false, false));
                 reset();
                 return;
             }
@@ -886,9 +745,7 @@ public class ECMA48Terminal implements Runnable {
         case ESCAPE:
             if (ch <= 0x1F) {
                 // ALT-Control character
-                keypress = controlChar(ch);
-                keypress.key.alt = true;
-                events.add(keypress);
+                events.add(controlChar(ch, true));
                 reset();
                 return;
             }
@@ -906,39 +763,33 @@ public class ECMA48Terminal implements Runnable {
             }
 
             // Everything else is assumed to be Alt-keystroke
-            keypress = new TKeypressEvent();
-            keypress.key.isKey = false;
-            keypress.key.ch = ch;
-            keypress.key.alt = true;
             if ((ch >= 'A') && (ch <= 'Z')) {
-                keypress.key.shift = true;
+                shift = true;
             }
-            events.add(keypress);
+            alt = true;
+            events.add(new TKeypressEvent(false, 0, ch, alt, ctrl, shift));
             reset();
             return;
 
         case ESCAPE_INTERMEDIATE:
             if ((ch >= 'P') && (ch <= 'S')) {
                 // Function key
-                keypress = new TKeypressEvent();
-                keypress.key.isKey = true;
                 switch (ch) {
                 case 'P':
-                    keypress.key.fnKey = TKeypress.F1;
+                    events.add(new TKeypressEvent(kbF1));
                     break;
                 case 'Q':
-                    keypress.key.fnKey = TKeypress.F2;
+                    events.add(new TKeypressEvent(kbF2));
                     break;
                 case 'R':
-                    keypress.key.fnKey = TKeypress.F3;
+                    events.add(new TKeypressEvent(kbF3));
                     break;
                 case 'S':
-                    keypress.key.fnKey = TKeypress.F4;
+                    events.add(new TKeypressEvent(kbF4));
                     break;
                 default:
                     break;
                 }
-                events.add(keypress);
                 reset();
                 return;
             }
@@ -950,14 +801,14 @@ public class ECMA48Terminal implements Runnable {
         case CSI_ENTRY:
             // Numbers - parameter values
             if ((ch >= '0') && (ch <= '9')) {
-                params.set(paramI, params.get(paramI) + ch);
+                params.set(params.size() - 1,
+                    params.get(params.size() - 1) + ch);
                 state = ParseState.CSI_PARAM;
                 return;
             }
             // Parameter separator
             if (ch == ';') {
-                paramI++;
-                params.set(paramI, "");
+                params.add("");
                 return;
             }
 
@@ -965,102 +816,81 @@ public class ECMA48Terminal implements Runnable {
                 switch (ch) {
                 case 'A':
                     // Up
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.UP;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
                     reset();
                     return;
                 case 'B':
                     // Down
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.DOWN;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
                     reset();
                     return;
                 case 'C':
                     // Right
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.RIGHT;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
                     reset();
                     return;
                 case 'D':
                     // Left
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.LEFT;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
                     reset();
                     return;
                 case 'H':
                     // Home
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.HOME;
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbHome));
                     reset();
                     return;
                 case 'F':
                     // End
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.END;
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbEnd));
                     reset();
                     return;
                 case 'Z':
                     // CBT - Cursor backward X tab stops (default 1)
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.BTAB;
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbBackTab));
                     reset();
                     return;
                 case 'M':
@@ -1079,14 +909,14 @@ public class ECMA48Terminal implements Runnable {
         case CSI_PARAM:
             // Numbers - parameter values
             if ((ch >= '0') && (ch <= '9')) {
-                params.set(paramI, params.get(paramI) + ch);
+                params.set(params.size() - 1,
+                    params.get(params.size() - 1) + ch);
                 state = ParseState.CSI_PARAM;
                 return;
             }
             // Parameter separator
             if (ch == ';') {
-                paramI++;
-                params.set(paramI, "");
+                params.add("");
                 return;
             }
 
@@ -1100,78 +930,66 @@ public class ECMA48Terminal implements Runnable {
                 switch (ch) {
                 case 'A':
                     // Up
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.UP;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
                     reset();
                     return;
                 case 'B':
                     // Down
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.DOWN;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
                     reset();
                     return;
                 case 'C':
                     // Right
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.RIGHT;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
                     reset();
                     return;
                 case 'D':
                     // Left
-                    keypress = new TKeypressEvent();
-                    keypress.key.isKey = true;
-                    keypress.key.fnKey = TKeypress.LEFT;
                     if (params.size() > 1) {
                         if (params.get(1).equals("2")) {
-                            keypress.key.shift = true;
+                            shift = true;
                         }
                         if (params.get(1).equals("5")) {
-                            keypress.key.ctrl = true;
+                            ctrl = true;
                         }
                         if (params.get(1).equals("3")) {
-                            keypress.key.alt = true;
+                            alt = true;
                         }
                     }
-                    events.add(keypress);
+                    events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
                     reset();
                     return;
                 default:
@@ -1184,7 +1002,7 @@ public class ECMA48Terminal implements Runnable {
             return;
 
         case MOUSE:
-            params.set(0, params.get(paramI) + ch);
+            params.set(0, params.get(params.size() - 1) + ch);
             if (params.get(0).length() == 3) {
                 // We have enough to generate a mouse event
                 events.add(parseMouse());
@@ -1619,7 +1437,7 @@ public class ECMA48Terminal implements Runnable {
                         readBuffer = new char[readBuffer.length * 2];
                     }
 
-                    int rc = input.read(readBuffer, 0, n);
+                    int rc = input.read(readBuffer, 0, readBuffer.length);
                     // System.err.printf("read() %d", rc); System.err.flush();
                     if (rc == -1) {
                         // This is EOF
@@ -1634,17 +1452,27 @@ public class ECMA48Terminal implements Runnable {
                                 synchronized (eventQueue) {
                                     eventQueue.addAll(events);
                                 }
-                                // Now wake up the backend
-                                synchronized (this) {
-                                    this.notifyAll();
+                                synchronized (listener) {
+                                    listener.notifyAll();
                                 }
                                 events.clear();
                             }
                         }
                     }
                 } else {
-                    // Wait 5 millis for more data
-                    Thread.sleep(5);
+                    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) {