X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fbackend%2FECMA48Terminal.java;h=360994a1841bea9bdf820d0d74bf9f5b5e7d631c;hb=d36057dfab8def933a64be042b039d76708ac5ba;hp=56c2c7c348943479a46c85d449d11932a4ec66a3;hpb=85c07c5e6db3a5e74f5ba2bd6e7ee2656d5b63a0;p=nikiroo-utils.git diff --git a/src/jexer/backend/ECMA48Terminal.java b/src/jexer/backend/ECMA48Terminal.java index 56c2c7c..360994a 100644 --- a/src/jexer/backend/ECMA48Terminal.java +++ b/src/jexer/backend/ECMA48Terminal.java @@ -59,6 +59,27 @@ import static jexer.TKeypress.*; public final class ECMA48Terminal extends LogicalScreen implements TerminalReader, Runnable { + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * States in the input parser. + */ + private enum ParseState { + GROUND, + ESCAPE, + ESCAPE_INTERMEDIATE, + CSI_ENTRY, + CSI_PARAM, + MOUSE, + MOUSE_SGR, + } + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Emit debugging to stderr. */ @@ -75,15 +96,6 @@ public final class ECMA48Terminal extends LogicalScreen */ private SessionInfo sessionInfo; - /** - * Getter for sessionInfo. - * - * @return the SessionInfo - */ - public SessionInfo getSessionInfo() { - return sessionInfo; - } - /** * The event queue, filled up by a thread reading on input. */ @@ -103,20 +115,7 @@ public final class ECMA48Terminal extends LogicalScreen * Parameters being collected. E.g. if the string is \033[1;3m, then * params[0] will be 1 and params[1] will be 3. */ - private ArrayList params; - - /** - * States in the input parser. - */ - private enum ParseState { - GROUND, - ESCAPE, - ESCAPE_INTERMEDIATE, - CSI_ENTRY, - CSI_PARAM, - MOUSE, - MOUSE_SGR, - } + private List params; /** * Current parsing state. @@ -194,100 +193,39 @@ public final class ECMA48Terminal extends LogicalScreen */ private Object listener; - /** - * Set listener to a different Object. - * - * @param listener the new listening object that run() wakes up on new - * input - */ - public void setListener(final Object listener) { - this.listener = listener; - } + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ /** - * Get the output writer. - * - * @return the Writer - */ - public PrintWriter getOutput() { - return output; - } - - /** - * Check if there are events in the queue. + * Constructor sets up state for getEvent(). * - * @return if true, getEvents() has something to return to the backend + * @param listener the object this backend needs to wake up when new + * input comes in + * @param input an InputStream connected to the remote user, or null for + * System.in. If System.in is used, then on non-Windows systems it will + * be put in raw mode; shutdown() will (blindly!) put System.in in cooked + * mode. input is always converted to a Reader with UTF-8 encoding. + * @param output an OutputStream connected to the remote user, or null + * for System.out. output is always converted to a Writer with UTF-8 + * encoding. + * @param windowWidth the number of text columns to start with + * @param windowHeight the number of text rows to start with + * @throws UnsupportedEncodingException if an exception is thrown when + * creating the InputStreamReader */ - public boolean hasEvents() { - synchronized (eventQueue) { - return (eventQueue.size() > 0); - } - } + public ECMA48Terminal(final Object listener, final InputStream input, + final OutputStream output, final int windowWidth, + final int windowHeight) throws UnsupportedEncodingException { - /** - * Call 'stty' to set cooked mode. - * - *

Actually executes '/bin/sh -c stty sane cooked < /dev/tty' - */ - private void sttyCooked() { - doStty(false); - } + this(listener, input, output); - /** - * Call 'stty' to set raw mode. - * - *

Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip - * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten - * -parenb cs8 min 1 < /dev/tty' - */ - private void sttyRaw() { - doStty(true); - } - - /** - * Call 'stty' to set raw or cooked mode. - * - * @param mode if true, set raw mode, otherwise set cooked mode - */ - private void doStty(final boolean mode) { - String [] cmdRaw = { - "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty" - }; - String [] cmdCooked = { - "/bin/sh", "-c", "stty sane cooked < /dev/tty" - }; - try { - Process process; - if (mode) { - process = Runtime.getRuntime().exec(cmdRaw); - } else { - process = Runtime.getRuntime().exec(cmdCooked); - } - BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); - String line = in.readLine(); - if ((line != null) && (line.length() > 0)) { - System.err.println("WEIRD?! Normal output from stty: " + line); - } - while (true) { - BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); - line = err.readLine(); - if ((line != null) && (line.length() > 0)) { - System.err.println("Error output from stty: " + line); - } - try { - process.waitFor(); - break; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - int rc = process.exitValue(); - if (rc != 0) { - System.err.println("stty returned error code: " + rc); - } - } catch (IOException e) { - e.printStackTrace(); - } + // Send dtterm/xterm sequences, which will probably not work because + // allowWindowOps is defaulted to false. + String resizeString = String.format("\033[8;%d;%dt", windowHeight, + windowWidth); + this.output.write(resizeString); + this.output.flush(); } /** @@ -457,73 +395,329 @@ public final class ECMA48Terminal extends LogicalScreen } else { doRgbColor = false; } - } + } + + // Spin up the input reader + eventQueue = new LinkedList(); + readerThread = new Thread(this); + readerThread.start(); + + // Clear the screen + this.output.write(clearAll()); + this.output.flush(); + } + + /** + * Constructor sets up state for getEvent(). + * + * @param listener the object this backend needs to wake up when new + * input comes in + * @param input the InputStream underlying 'reader'. Its available() + * method is used to determine if reader.read() will block or not. + * @param reader a Reader connected to the remote user. + * @param writer a PrintWriter connected to the remote user. + * @throws IllegalArgumentException if input, reader, or writer are null. + */ + public ECMA48Terminal(final Object listener, final InputStream input, + final Reader reader, final PrintWriter writer) { + + this(listener, input, reader, writer, false); + } + + // ------------------------------------------------------------------------ + // LogicalScreen ---------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Set the window title. + * + * @param title the new title + */ + @Override + public void setTitle(final String title) { + output.write(getSetTitleString(title)); + flush(); + } + + /** + * Push the logical screen to the physical device. + */ + @Override + public void flushPhysical() { + String result = flushString(); + if ((cursorVisible) + && (cursorY >= 0) + && (cursorX >= 0) + && (cursorY <= height - 1) + && (cursorX <= width - 1) + ) { + result += cursor(true); + result += gotoXY(cursorX, cursorY); + } else { + result += cursor(false); + } + output.write(result); + flush(); + } + + // ------------------------------------------------------------------------ + // TerminalReader --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Check if there are events in the queue. + * + * @return if true, getEvents() has something to return to the backend + */ + public boolean hasEvents() { + synchronized (eventQueue) { + return (eventQueue.size() > 0); + } + } + + /** + * Return any events in the IO queue. + * + * @param queue list to append new events to + */ + public void getEvents(final List queue) { + synchronized (eventQueue) { + if (eventQueue.size() > 0) { + synchronized (queue) { + queue.addAll(eventQueue); + } + eventQueue.clear(); + } + } + } + + /** + * Restore terminal to normal state. + */ + public void closeTerminal() { + + // System.err.println("=== shutdown() ==="); System.err.flush(); + + // Tell the reader thread to stop looking at input + stopReaderThread = true; + try { + readerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Disable mouse reporting and show cursor + output.printf("%s%s%s", mouse(false), cursor(true), normal()); + output.flush(); + + if (setRawMode) { + sttyCooked(); + setRawMode = false; + // We don't close System.in/out + } else { + // Shut down the streams, this should wake up the reader thread + // and make it exit. + try { + if (input != null) { + input.close(); + input = null; + } + if (output != null) { + output.close(); + output = null; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Set listener to a different Object. + * + * @param listener the new listening object that run() wakes up on new + * input + */ + public void setListener(final Object listener) { + this.listener = listener; + } + + // ------------------------------------------------------------------------ + // Runnable --------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Read function runs on a separate thread. + */ + public void run() { + boolean done = false; + // available() will often return > 1, so we need to read in chunks to + // stay caught up. + char [] readBuffer = new char[128]; + List events = new LinkedList(); + + while (!done && !stopReaderThread) { + try { + // We assume that if inputStream has bytes available, then + // input won't block on read(). + int n = inputStream.available(); + + /* + System.err.printf("inputStream.available(): %d\n", n); + System.err.flush(); + */ + + if (n > 0) { + if (readBuffer.length < n) { + // The buffer wasn't big enough, make it huger + readBuffer = new char[readBuffer.length * 2]; + } + + // System.err.printf("BEFORE read()\n"); System.err.flush(); + + int rc = input.read(readBuffer, 0, readBuffer.length); + + /* + System.err.printf("AFTER read() %d\n", rc); + System.err.flush(); + */ + + if (rc == -1) { + // This is EOF + done = true; + } else { + for (int i = 0; i < rc; i++) { + int ch = readBuffer[i]; + processChar(events, (char)ch); + } + getIdleEvents(events); + if (events.size() > 0) { + // Add to the queue for the backend thread to + // be able to obtain. + synchronized (eventQueue) { + eventQueue.addAll(events); + } + if (listener != null) { + synchronized (listener) { + listener.notifyAll(); + } + } + events.clear(); + } + } + } else { + getIdleEvents(events); + if (events.size() > 0) { + synchronized (eventQueue) { + eventQueue.addAll(events); + } + if (listener != null) { + synchronized (listener) { + listener.notifyAll(); + } + } + events.clear(); + } + + // Wait 20 millis for more data + Thread.sleep(20); + } + // System.err.println("end while loop"); System.err.flush(); + } catch (InterruptedException e) { + // SQUASH + } catch (IOException e) { + e.printStackTrace(); + done = true; + } + } // while ((done == false) && (stopReaderThread == false)) + // System.err.println("*** run() exiting..."); System.err.flush(); + } - // Spin up the input reader - eventQueue = new LinkedList(); - readerThread = new Thread(this); - readerThread.start(); + // ------------------------------------------------------------------------ + // ECMA48Terminal --------------------------------------------------------- + // ------------------------------------------------------------------------ - // Clear the screen - this.output.write(clearAll()); - this.output.flush(); + /** + * Getter for sessionInfo. + * + * @return the SessionInfo + */ + public SessionInfo getSessionInfo() { + return sessionInfo; } /** - * Constructor sets up state for getEvent(). + * Get the output writer. * - * @param listener the object this backend needs to wake up when new - * input comes in - * @param input the InputStream underlying 'reader'. Its available() - * method is used to determine if reader.read() will block or not. - * @param reader a Reader connected to the remote user. - * @param writer a PrintWriter connected to the remote user. - * @throws IllegalArgumentException if input, reader, or writer are null. + * @return the Writer */ - public ECMA48Terminal(final Object listener, final InputStream input, - final Reader reader, final PrintWriter writer) { - - this(listener, input, reader, writer, false); + public PrintWriter getOutput() { + return output; } /** - * Restore terminal to normal state. + * Call 'stty' to set cooked mode. + * + *

Actually executes '/bin/sh -c stty sane cooked < /dev/tty' */ - public void closeTerminal() { + private void sttyCooked() { + doStty(false); + } - // System.err.println("=== shutdown() ==="); System.err.flush(); + /** + * Call 'stty' to set raw mode. + * + *

Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip + * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten + * -parenb cs8 min 1 < /dev/tty' + */ + private void sttyRaw() { + doStty(true); + } - // Tell the reader thread to stop looking at input - stopReaderThread = true; + /** + * Call 'stty' to set raw or cooked mode. + * + * @param mode if true, set raw mode, otherwise set cooked mode + */ + private void doStty(final boolean mode) { + String [] cmdRaw = { + "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty" + }; + String [] cmdCooked = { + "/bin/sh", "-c", "stty sane cooked < /dev/tty" + }; try { - readerThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Disable mouse reporting and show cursor - output.printf("%s%s%s", mouse(false), cursor(true), normal()); - output.flush(); - - if (setRawMode) { - sttyCooked(); - setRawMode = false; - // We don't close System.in/out - } else { - // Shut down the streams, this should wake up the reader thread - // and make it exit. - try { - if (input != null) { - input.close(); - input = null; + Process process; + if (mode) { + process = Runtime.getRuntime().exec(cmdRaw); + } else { + process = Runtime.getRuntime().exec(cmdCooked); + } + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); + String line = in.readLine(); + if ((line != null) && (line.length() > 0)) { + System.err.println("WEIRD?! Normal output from stty: " + line); + } + while (true) { + BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); + line = err.readLine(); + if ((line != null) && (line.length() > 0)) { + System.err.println("Error output from stty: " + line); } - if (output != null) { - output.close(); - output = null; + try { + process.waitFor(); + break; + } catch (InterruptedException e) { + e.printStackTrace(); } - } catch (IOException e) { - e.printStackTrace(); } + int rc = process.exitValue(); + if (rc != 0) { + System.err.println("stty returned error code: " + rc); + } + } catch (IOException e) { + e.printStackTrace(); } } @@ -732,37 +926,6 @@ public final class ECMA48Terminal extends LogicalScreen return result; } - /** - * Push the logical screen to the physical device. - */ - @Override - public void flushPhysical() { - String result = flushString(); - if ((cursorVisible) - && (cursorY >= 0) - && (cursorX >= 0) - && (cursorY <= height - 1) - && (cursorX <= width - 1) - ) { - result += cursor(true); - result += gotoXY(cursorX, cursorY); - } else { - result += cursor(false); - } - output.write(result); - flush(); - } - - /** - * Set the window title. - * - * @param title the new title - */ - public void setTitle(final String title) { - output.write(getSetTitleString(title)); - flush(); - } - /** * Reset keyboard/mouse input parser. */ @@ -1077,22 +1240,6 @@ public final class ECMA48Terminal extends LogicalScreen eventMouseWheelUp, eventMouseWheelDown); } - /** - * Return any events in the IO queue. - * - * @param queue list to append new events to - */ - public void getEvents(final List queue) { - synchronized (eventQueue) { - if (eventQueue.size() > 0) { - synchronized (queue) { - queue.addAll(eventQueue); - } - eventQueue.clear(); - } - } - } - /** * Return any events in the IO queue due to timeout. * @@ -1864,91 +2011,4 @@ public final class ECMA48Terminal extends LogicalScreen return "\033[?1002;1003;1006;1005l\033[?1049l"; } - /** - * Read function runs on a separate thread. - */ - public void run() { - boolean done = false; - // available() will often return > 1, so we need to read in chunks to - // stay caught up. - char [] readBuffer = new char[128]; - List events = new LinkedList(); - - while (!done && !stopReaderThread) { - try { - // We assume that if inputStream has bytes available, then - // input won't block on read(). - int n = inputStream.available(); - - /* - System.err.printf("inputStream.available(): %d\n", n); - System.err.flush(); - */ - - if (n > 0) { - if (readBuffer.length < n) { - // The buffer wasn't big enough, make it huger - readBuffer = new char[readBuffer.length * 2]; - } - - // System.err.printf("BEFORE read()\n"); System.err.flush(); - - int rc = input.read(readBuffer, 0, readBuffer.length); - - /* - System.err.printf("AFTER read() %d\n", rc); - System.err.flush(); - */ - - if (rc == -1) { - // This is EOF - done = true; - } else { - for (int i = 0; i < rc; i++) { - int ch = readBuffer[i]; - processChar(events, (char)ch); - } - getIdleEvents(events); - if (events.size() > 0) { - // Add to the queue for the backend thread to - // be able to obtain. - synchronized (eventQueue) { - eventQueue.addAll(events); - } - if (listener != null) { - synchronized (listener) { - listener.notifyAll(); - } - } - events.clear(); - } - } - } else { - getIdleEvents(events); - if (events.size() > 0) { - synchronized (eventQueue) { - eventQueue.addAll(events); - } - if (listener != null) { - synchronized (listener) { - listener.notifyAll(); - } - } - events.clear(); - } - - // Wait 20 millis for more data - Thread.sleep(20); - } - // System.err.println("end while loop"); System.err.flush(); - } catch (InterruptedException e) { - // SQUASH - } catch (IOException e) { - e.printStackTrace(); - done = true; - } - } // while ((done == false) && (stopReaderThread == false)) - // System.err.println("*** run() exiting..."); System.err.flush(); - } - }