X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fio%2FECMA48Terminal.java;h=d7d23915d0c416562d0d00874a86643e2fcc7464;hb=9edb442b712de01d1b7af81d1d57a29c2c6e7871;hp=8d3c0fe6d7901e3bab34d221fd06266802f164bd;hpb=217c61076c63ba87b64921bb072ccad72ecc5298;p=nikiroo-utils.git diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index 8d3c0fe..d7d2391 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -33,6 +33,8 @@ package jexer.io; import java.io.BufferedReader; +import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; @@ -62,13 +64,28 @@ import static jexer.TKeypress.*; * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, * etc. */ -public class ECMA48Terminal { +public class ECMA48Terminal implements Runnable { /** * The session information */ public SessionInfo session; + /** + * The event queue, filled up by a thread reading on input + */ + private List eventQueue; + + /** + * If true, we want the reader thread to exit gracefully. + */ + private boolean stopReaderThread; + + /** + * The reader thread + */ + private Thread readerThread; + /** * Parameters being collected. E.g. if the string is \033[1;3m, then * params[0] will be 1 and params[1] will be 3. @@ -139,11 +156,19 @@ public class ECMA48Terminal { /** * The terminal's input. If an InputStream is not specified in the - * constructor, then this InputReader will be bound to System.in with - * UTF-8 encoding. + * constructor, then this InputStreamReader will be bound to System.in + * with UTF-8 encoding. */ private Reader input; + /** + * The terminal's raw InputStream. If an InputStream is not specified in + * the constructor, then this InputReader will be bound to System.in. + * This is used by run() to see if bytes are available() before calling + * (Reader)input.read(). + */ + private InputStream inputStream; + /** * The terminal's output. If an OutputStream is not specified in the * constructor, then this PrintWriter will be bound to System.out with @@ -168,6 +193,17 @@ public class ECMA48Terminal { return output; } + /** + * Check if there are events in the queue. + * + * @return if true, getEvents() has something to return to the backend + */ + public boolean hasEvents() { + synchronized (eventQueue) { + return (eventQueue.size() > 0); + } + } + /** * Call 'stty cooked' to set cooked mode. */ @@ -189,14 +225,12 @@ public class ECMA48Terminal { */ private void doStty(boolean mode) { String [] cmdRaw = { - "/bin/sh", "-c", "stty raw < /dev/tty" + "/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 cooked < /dev/tty" + "/bin/sh", "-c", "stty sane cooked < /dev/tty" }; try { - System.out.println("spawn stty"); - Process process; if (mode == true) { process = Runtime.getRuntime().exec(cmdRaw); @@ -244,17 +278,21 @@ public class ECMA48Terminal { public ECMA48Terminal(InputStream input, OutputStream output) throws UnsupportedEncodingException { reset(); - mouse1 = false; - mouse2 = false; - mouse3 = false; + mouse1 = false; + mouse2 = false; + mouse3 = false; + stopReaderThread = false; if (input == null) { - this.input = new InputStreamReader(System.in, "UTF-8"); + // inputStream = System.in; + inputStream = new FileInputStream(FileDescriptor.in); sttyRaw(); setRawMode = true; } else { - this.input = new InputStreamReader(input); + inputStream = input; } + this.input = new InputStreamReader(inputStream, "UTF-8"); + // TODO: include TelnetSocket from NIB and have it implement // SessionInfo if (input instanceof SessionInfo) { @@ -283,18 +321,52 @@ public class ECMA48Terminal { // Hang onto the window size windowResize = new TResizeEvent(TResizeEvent.Type.Screen, session.getWindowWidth(), session.getWindowHeight()); + + // Spin up the input reader + eventQueue = new LinkedList(); + readerThread = new Thread(this); + readerThread.start(); } /** * Restore terminal to normal state */ public void shutdown() { + + // 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(); + } } - // Disable mouse reporting and show cursor - output.printf("%s%s%s", mouse(false), cursor(true), normal()); } /** @@ -309,6 +381,7 @@ public class ECMA48Terminal { */ private void reset() { state = ParseState.GROUND; + params = new ArrayList(); paramI = 0; params.clear(); params.add(""); @@ -329,8 +402,12 @@ public class ECMA48Terminal { // System.err.printf("controlChar: %02x\n", ch); switch (ch) { - case '\r': - // ENTER + case 0x0D: + // Carriage return --> ENTER + event.key = kbEnter; + break; + case 0x0A: + // Linefeed --> ENTER event.key = kbEnter; break; case 0x1B: @@ -696,35 +773,57 @@ public class ECMA48Terminal { } /** - * Parses the next character of input to see if an InputEvent is - * fully here. + * Return any events in the IO queue. * - * Params: - * ch = Unicode code point - * noChar = if true, ignore ch. This is currently used to - * return a bare ESC and RESIZE events. + * @param queue list to append new events to + */ + public void getEvents(List queue) { + synchronized (eventQueue) { + if (eventQueue.size() > 0) { + queue.addAll(eventQueue); + eventQueue.clear(); + } + } + } + + /** + * Return any events in the IO queue due to timeout. * - * Returns: - * list of new events (which may be empty) + * @param queue list to append new events to */ - public List getEvents(char ch) { - return getEvents(ch, false); + public void getIdleEvents(List queue) { + + // Check for new window size + session.queryWindowSize(); + int newWidth = session.getWindowWidth(); + int newHeight = session.getWindowHeight(); + if ((newWidth != windowResize.width) || + (newHeight != windowResize.height)) { + TResizeEvent event = new TResizeEvent(TResizeEvent.Type.Screen, + newWidth, newHeight); + windowResize.width = newWidth; + windowResize.height = newHeight; + synchronized (eventQueue) { + eventQueue.add(event); + } + } + + synchronized (eventQueue) { + if (eventQueue.size() > 0) { + queue.addAll(eventQueue); + eventQueue.clear(); + } + } } /** * Parses the next character of input to see if an InputEvent is * fully here. * - * Params: - * ch = Unicode code point - * noChar = if true, ignore ch. This is currently used to - * return a bare ESC and RESIZE events. - * - * Returns: - * list of new events (which may be empty) + * @param events list to append new events to + * @param ch Unicode code point */ - public List getEvents(char ch, boolean noChar) { - List events = new LinkedList(); + private void processChar(List events, char ch) { TKeypressEvent keypress; Date now = new Date(); @@ -739,22 +838,6 @@ public class ECMA48Terminal { } } - if (noChar == true) { - int newWidth = session.getWindowWidth(); - int newHeight = session.getWindowHeight(); - if ((newWidth != windowResize.width) || - (newHeight != windowResize.height)) { - TResizeEvent event = new TResizeEvent(TResizeEvent.Type.Screen, - newWidth, newHeight); - windowResize.width = newWidth; - windowResize.height = newHeight; - events.add(event); - } - - // Nothing else to do, bail out - return events; - } - // System.err.printf("state: %s ch %c\r\n", state, ch); switch (state) { @@ -763,14 +846,14 @@ public class ECMA48Terminal { if (ch == 0x1B) { state = ParseState.ESCAPE; escapeTime = now.getTime(); - return events; + return; } if (ch <= 0x1F) { // Control character events.add(controlChar(ch)); reset(); - return events; + return; } if (ch >= 0x20) { @@ -780,7 +863,7 @@ public class ECMA48Terminal { keypress.key.ch = ch; events.add(keypress); reset(); - return events; + return; } break; @@ -792,19 +875,19 @@ public class ECMA48Terminal { keypress.key.alt = true; events.add(keypress); reset(); - return events; + return; } if (ch == 'O') { // This will be one of the function keys state = ParseState.ESCAPE_INTERMEDIATE; - return events; + return; } // '[' goes to CSI_ENTRY if (ch == '[') { state = ParseState.CSI_ENTRY; - return events; + return; } // Everything else is assumed to be Alt-keystroke @@ -817,7 +900,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case ESCAPE_INTERMEDIATE: if ((ch >= 'P') && (ch <= 'S')) { @@ -842,25 +925,25 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; } // Unknown keystroke, ignore reset(); - return events; + return; case CSI_ENTRY: // Numbers - parameter values if ((ch >= '0') && (ch <= '9')) { params.set(paramI, params.get(paramI) + ch); state = ParseState.CSI_PARAM; - return events; + return; } // Parameter separator if (ch == ';') { paramI++; params.set(paramI, ""); - return events; + return; } if ((ch >= 0x30) && (ch <= 0x7E)) { @@ -883,7 +966,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'B': // Down keypress = new TKeypressEvent(); @@ -902,7 +985,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'C': // Right keypress = new TKeypressEvent(); @@ -921,7 +1004,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'D': // Left keypress = new TKeypressEvent(); @@ -940,7 +1023,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'H': // Home keypress = new TKeypressEvent(); @@ -948,7 +1031,7 @@ public class ECMA48Terminal { keypress.key.fnKey = TKeypress.HOME; events.add(keypress); reset(); - return events; + return; case 'F': // End keypress = new TKeypressEvent(); @@ -956,7 +1039,7 @@ public class ECMA48Terminal { keypress.key.fnKey = TKeypress.END; events.add(keypress); reset(); - return events; + return; case 'Z': // CBT - Cursor backward X tab stops (default 1) keypress = new TKeypressEvent(); @@ -964,11 +1047,11 @@ public class ECMA48Terminal { keypress.key.fnKey = TKeypress.BTAB; events.add(keypress); reset(); - return events; + return; case 'M': // Mouse position state = ParseState.MOUSE; - return events; + return; default: break; } @@ -976,26 +1059,26 @@ public class ECMA48Terminal { // Unknown keystroke, ignore reset(); - return events; + return; case CSI_PARAM: // Numbers - parameter values if ((ch >= '0') && (ch <= '9')) { params.set(paramI, params.get(paramI) + ch); state = ParseState.CSI_PARAM; - return events; + return; } // Parameter separator if (ch == ';') { paramI++; params.set(paramI, ""); - return events; + return; } if (ch == '~') { events.add(csiFnKey()); reset(); - return events; + return; } if ((ch >= 0x30) && (ch <= 0x7E)) { @@ -1018,7 +1101,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'B': // Down keypress = new TKeypressEvent(); @@ -1037,7 +1120,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'C': // Right keypress = new TKeypressEvent(); @@ -1056,7 +1139,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; case 'D': // Left keypress = new TKeypressEvent(); @@ -1075,7 +1158,7 @@ public class ECMA48Terminal { } events.add(keypress); reset(); - return events; + return; default: break; } @@ -1083,7 +1166,7 @@ public class ECMA48Terminal { // Unknown keystroke, ignore reset(); - return events; + return; case MOUSE: params.set(0, params.get(paramI) + ch); @@ -1092,26 +1175,23 @@ public class ECMA48Terminal { events.add(parseMouse()); reset(); } - return events; + return; default: break; } // This "should" be impossible to reach - return events; + return; } /** - * Tell (u)xterm that we want alt- keystrokes to send escape + - * character rather than set the 8th bit. Anyone who wants UTF8 - * should want this enabled. - * - * Params: - * on = if true, enable metaSendsEscape + * Tell (u)xterm that we want alt- keystrokes to send escape + character + * rather than set the 8th bit. Anyone who wants UTF8 should want this + * enabled. * - * Returns: - * the string to emit to xterm + * @param on if true, enable metaSendsEscape + * @return the string to emit to xterm */ static public String xtermMetaSendsEscape(boolean on) { if (on) { @@ -1121,15 +1201,13 @@ public class ECMA48Terminal { } /** - * Convert a list of SGR parameters into a full escape sequence. - * This also eliminates a trailing ';' which would otherwise reset - * everything to white-on-black not-bold. - * - * Params: - * str = string of parameters, e.g. "31;1;" + * Convert a list of SGR parameters into a full escape sequence. This + * also eliminates a trailing ';' which would otherwise reset everything + * to white-on-black not-bold. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;1m" + * @param str string of parameters, e.g. "31;1;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[31;1m" */ static public String addHeaderSGR(String str) { if (str.length() > 0) { @@ -1144,12 +1222,10 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for a single color change. * - * Params: - * color = one of the Color.WHITE, Color.BLUE, etc. constants - * foreground = if true, this is a foreground color - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m" + * @param color one of the Color.WHITE, Color.BLUE, etc. constants + * @param foreground if true, this is a foreground color + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[42m" */ static public String color(Color color, boolean foreground) { return color(color, foreground, true); @@ -1158,14 +1234,12 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for a single color change. * - * Params: - * color = one of the Color.WHITE, Color.BLUE, etc. constants - * foreground = if true, this is a foreground color - * header = if true, make the full header, otherwise just emit - * the color parameter e.g. "42;" - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m" + * @param color one of the Color.WHITE, Color.BLUE, etc. constants + * @param foreground if true, this is a foreground color + * @param header if true, make the full header, otherwise just emit the + * color parameter e.g. "42;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[42m" */ static public String color(Color color, boolean foreground, boolean header) { @@ -1190,12 +1264,10 @@ public class ECMA48Terminal { * Create a SGR parameter sequence for both foreground and * background color change. * - * Params: - * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants - * backColor = one of the Color.WHITE, Color.BLUE, etc. constants - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m" + * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants + * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[31;42m" */ static public String color(Color foreColor, Color backColor) { return color(foreColor, backColor, true); @@ -1205,14 +1277,12 @@ public class ECMA48Terminal { * Create a SGR parameter sequence for both foreground and * background color change. * - * Params: - * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants - * backColor = one of the Color.WHITE, Color.BLUE, etc. constants - * header = if true, make the full header, otherwise just emit - * the color parameter e.g. "31;42;" - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m" + * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants + * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants + * @param header if true, make the full header, otherwise just emit the + * color parameter e.g. "31;42;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[31;42m" */ static public String color(Color foreColor, Color backColor, boolean header) { @@ -1233,19 +1303,17 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for foreground, background, and - * several attributes. This sequence first resets all attributes - * to default, then sets attributes as per the parameters. - * - * Params: - * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants - * backColor = one of the Color.WHITE, Color.BLUE, etc. constants - * bold = if true, set bold - * reverse = if true, set reverse - * blink = if true, set blink - * underline = if true, set underline + * several attributes. This sequence first resets all attributes to + * default, then sets attributes as per the parameters. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0;1;31;42m" + * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants + * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants + * @param bold if true, set bold + * @param reverse if true, set reverse + * @param blink if true, set blink + * @param underline if true, set underline + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[0;1;31;42m" */ static public String color(Color foreColor, Color backColor, boolean bold, boolean reverse, boolean blink, boolean underline) { @@ -1299,11 +1367,9 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling reverse color. * - * Params: - * on = if true, turn on reverse - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[7m" + * @param on if true, turn on reverse + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[7m" */ static public String reverse(boolean on) { if (on) { @@ -1315,8 +1381,8 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence to reset to defaults. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[0m" */ static public String normal() { return normal(true); @@ -1325,12 +1391,10 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence to reset to defaults. * - * Params: - * header = if true, make the full header, otherwise just emit - * the bare parameter e.g. "0;" - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m" + * @param header if true, make the full header, otherwise just emit the + * bare parameter e.g. "0;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[0m" */ static public String normal(boolean header) { if (header) { @@ -1342,11 +1406,9 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling boldface. * - * Params: - * on = if true, turn on bold - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m" + * @param on if true, turn on bold + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[1m" */ static public String bold(boolean on) { return bold(on, true); @@ -1355,13 +1417,11 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling boldface. * - * Params: - * on = if true, turn on bold - * header = if true, make the full header, otherwise just emit - * the bare parameter e.g. "1;" - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m" + * @param on if true, turn on bold + * @param header if true, make the full header, otherwise just emit the + * bare parameter e.g. "1;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[1m" */ static public String bold(boolean on, boolean header) { if (header) { @@ -1379,11 +1439,9 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling blinking text. * - * Params: - * on = if true, turn on blink - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m" + * @param on if true, turn on blink + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[5m" */ static public String blink(boolean on) { return blink(on, true); @@ -1392,13 +1450,11 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling blinking text. * - * Params: - * on = if true, turn on blink - * header = if true, make the full header, otherwise just emit - * the bare parameter e.g. "5;" - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m" + * @param on if true, turn on blink + * @param header if true, make the full header, otherwise just emit the + * bare parameter e.g. "5;" + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[5m" */ static public String blink(boolean on, boolean header) { if (header) { @@ -1414,14 +1470,12 @@ public class ECMA48Terminal { } /** - * Create a SGR parameter sequence for enabling underline / - * underscored text. + * Create a SGR parameter sequence for enabling underline / underscored + * text. * - * Params: - * on = if true, turn on underline - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[4m" + * @param on if true, turn on underline + * @return the string to emit to an ANSI / ECMA-style terminal, + * e.g. "\033[4m" */ static public String underline(boolean on) { if (on) { @@ -1433,11 +1487,8 @@ public class ECMA48Terminal { /** * Create a SGR parameter sequence for enabling the visible cursor. * - * Params: - * on = if true, turn on cursor - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @param on if true, turn on cursor + * @return the string to emit to an ANSI / ECMA-style terminal */ public String cursor(boolean on) { if (on && (cursorOn == false)) { @@ -1455,8 +1506,7 @@ public class ECMA48Terminal { * Clear the entire screen. Because some terminals use back-color-erase, * set the color to white-on-black beforehand. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String clearAll() { return "\033[0;37;40m\033[2J"; @@ -1467,8 +1517,7 @@ public class ECMA48Terminal { * Because some terminals use back-color-erase, set the color to * white-on-black beforehand. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String clearRemainingLine() { return "\033[0;37;40m\033[K"; @@ -1478,8 +1527,7 @@ public class ECMA48Terminal { * Clear the line up the cursor (inclusive). Because some terminals use * back-color-erase, set the color to white-on-black beforehand. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String clearPreceedingLine() { return "\033[0;37;40m\033[1K"; @@ -1489,8 +1537,7 @@ public class ECMA48Terminal { * Clear the line. Because some terminals use back-color-erase, set the * color to white-on-black beforehand. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String clearLine() { return "\033[0;37;40m\033[2K"; @@ -1499,8 +1546,7 @@ public class ECMA48Terminal { /** * Move the cursor to the top-left corner. * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String home() { return "\033[H"; @@ -1509,29 +1555,25 @@ public class ECMA48Terminal { /** * Move the cursor to (x, y). * - * Params: - * x = column coordinate. 0 is the left-most column. - * y = row coordinate. 0 is the top-most row. - * - * Returns: - * the string to emit to an ANSI / ECMA-style terminal + * @param x column coordinate. 0 is the left-most column. + * @param y row coordinate. 0 is the top-most row. + * @return the string to emit to an ANSI / ECMA-style terminal */ static public String gotoXY(int x, int y) { return String.format("\033[%d;%dH", y + 1, x + 1); } /** - * Tell (u)xterm that we want to receive mouse events based on - * "Any event tracking" and UTF-8 coordinates. See + * Tell (u)xterm that we want to receive mouse events based on "Any event + * tracking" and UTF-8 coordinates. See * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking * - * Finally, this sets the alternate screen buffer. + * Note that this also sets the alternate/primary screen buffer. * - * Params: - * on = if true, enable mouse report - * - * Returns: - * the string to emit to xterm + * @param on If true, enable mouse report and use the alternate screen + * buffer. If false disable mouse reporting and use the primary screen + * buffer. + * @return the string to emit to xterm */ static public String mouse(boolean on) { if (on) { @@ -1540,4 +1582,63 @@ public class ECMA48Terminal { return "\033[?1003;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 == false) && (stopReaderThread == false)) { + try { + // We assume that if inputStream has bytes available, then + // input won't block on read(). + int n = inputStream.available(); + if (n > 0) { + if (readBuffer.length < n) { + // The buffer wasn't big enough, make it huger + readBuffer = new char[readBuffer.length * 2]; + } + + int rc = input.read(readBuffer, 0, n); + // System.err.printf("read() %d", rc); System.err.flush(); + if (rc == -1) { + // This is EOF + done = true; + } else { + for (int i = 0; i < rc; i++) { + int ch = readBuffer[i]; + processChar(events, (char)ch); + if (events.size() > 0) { + // Add to the queue for the backend thread to + // be able to obtain. + synchronized (eventQueue) { + eventQueue.addAll(events); + } + // Now wake up the backend + synchronized (this) { + this.notifyAll(); + } + events.clear(); + } + } + } + } else { + // Wait 5 millis for more data + Thread.sleep(5); + } + // 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(); + } + }