first screenshot attempt
[fanfix.git] / src / jexer / io / ECMA48Terminal.java
index b608a69f832862c76719201d02d5b33758b9ea5d..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("");
     }
@@ -634,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();
             }
         }
@@ -645,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();
             }
         }
     }
@@ -785,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;
             }
 
@@ -893,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;
             }
 
@@ -986,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());
@@ -1421,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
@@ -1436,15 +1452,25 @@ 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 {
+                    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);
                 }