X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fio%2FECMA48Terminal.java;h=1aafa3c41619a4864d0297f1e966818e39e939da;hb=9b1afdde02c30f0d4a80ba330a4bc72384093253;hp=7721330f8d852480d186c424c8965075ca89829d;hpb=bb35d91958450cc7152d2063f1d6cd34c15e2a3d;p=nikiroo-utils.git diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index 7721330..1aafa3c 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -46,7 +46,6 @@ import java.util.Date; import java.util.List; import java.util.LinkedList; -import jexer.TKeypress; import jexer.bits.Color; import jexer.event.TInputEvent; import jexer.event.TKeypressEvent; @@ -108,7 +107,8 @@ public final class ECMA48Terminal implements Runnable { CSI_ENTRY, CSI_PARAM, // CSI_INTERMEDIATE, - MOUSE + MOUSE, + MOUSE_SGR, } /** @@ -187,14 +187,6 @@ public final class ECMA48Terminal implements Runnable { */ private Object listener; - /** - * When true, the terminal is sending non-UTF8 bytes when reporting mouse - * events. - * - * TODO: Add broken mouse detection back into the reader. - */ - private boolean brokenTerminalUTFMouse = false; - /** * Get the output writer. * @@ -316,9 +308,9 @@ public final class ECMA48Terminal implements Runnable { } this.input = new InputStreamReader(inputStream, "UTF-8"); - // TODO: include TelnetSocket from NIB and have it implement - // SessionInfo if (input instanceof SessionInfo) { + // This is a TelnetInputStream that exposes window size and + // environment variables from the telnet layer. sessionInfo = (SessionInfo) input; } if (sessionInfo == null) { @@ -340,6 +332,7 @@ public final class ECMA48Terminal implements Runnable { // Enable mouse reporting and metaSendsEscape this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true)); + this.output.flush(); // Hang onto the window size windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN, @@ -634,6 +627,105 @@ public final class ECMA48Terminal implements Runnable { eventMouseWheelUp, eventMouseWheelDown); } + /** + * Produce mouse events based on "Any event tracking" and SGR + * coordinates. See + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking + * + * @param release if true, this was a release ('m') + * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event + */ + private TInputEvent parseMouseSGR(final boolean release) { + // SGR extended coordinates - mode 1006 + if (params.size() < 3) { + // Invalid position, bail out. + return null; + } + int buttons = Integer.parseInt(params.get(0)); + int x = Integer.parseInt(params.get(1)) - 1; + int y = Integer.parseInt(params.get(2)) - 1; + + // Clamp X and Y to the physical screen coordinates. + if (x >= windowResize.getWidth()) { + x = windowResize.getWidth() - 1; + } + if (y >= windowResize.getHeight()) { + y = windowResize.getHeight() - 1; + } + + TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN; + boolean eventMouse1 = false; + boolean eventMouse2 = false; + boolean eventMouse3 = false; + boolean eventMouseWheelUp = false; + boolean eventMouseWheelDown = false; + + if (release) { + eventType = TMouseEvent.Type.MOUSE_UP; + } + + switch (buttons) { + case 0: + eventMouse1 = true; + break; + case 1: + eventMouse2 = true; + break; + case 2: + eventMouse3 = true; + break; + case 35: + // Motion only, no buttons down + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 32: + // Dragging with mouse1 down + eventMouse1 = true; + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 33: + // Dragging with mouse2 down + eventMouse2 = true; + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 34: + // Dragging with mouse3 down + eventMouse3 = true; + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 96: + // Dragging with mouse2 down after wheelUp + eventMouse2 = true; + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 97: + // Dragging with mouse2 down after wheelDown + eventMouse2 = true; + eventType = TMouseEvent.Type.MOUSE_MOTION; + break; + + case 64: + eventMouseWheelUp = true; + break; + + case 65: + eventMouseWheelDown = true; + break; + + default: + // Unknown, bail out + return null; + } + return new TMouseEvent(eventType, x, y, x, y, + eventMouse1, eventMouse2, eventMouse3, + eventMouseWheelUp, eventMouseWheelDown); + } + /** * Return any events in the IO queue. * @@ -711,8 +803,6 @@ public final class ECMA48Terminal implements Runnable { 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); @@ -897,6 +987,10 @@ public final class ECMA48Terminal implements Runnable { // Mouse position state = ParseState.MOUSE; return; + case '<': + // Mouse position, SGR (1006) coordinates + state = ParseState.MOUSE_SGR; + return; default: break; } @@ -906,6 +1000,44 @@ public final class ECMA48Terminal implements Runnable { reset(); return; + case MOUSE_SGR: + // Numbers - parameter values + if ((ch >= '0') && (ch <= '9')) { + params.set(params.size() - 1, + params.get(params.size() - 1) + ch); + return; + } + // Parameter separator + if (ch == ';') { + params.add(""); + return; + } + + switch (ch) { + case 'M': + // Generate a mouse press event + TInputEvent event = parseMouseSGR(false); + if (event != null) { + events.add(event); + } + reset(); + return; + case 'm': + // Generate a mouse release event + event = parseMouseSGR(true); + if (event != null) { + events.add(event); + } + reset(); + return; + default: + break; + } + + // Unknown keystroke, ignore + reset(); + return; + case CSI_PARAM: // Numbers - parameter values if ((ch >= '0') && (ch <= '9')) { @@ -1026,7 +1158,7 @@ public final class ECMA48Terminal implements Runnable { * @param on if true, enable metaSendsEscape * @return the string to emit to xterm */ - public String xtermMetaSendsEscape(final boolean on) { + private String xtermMetaSendsEscape(final boolean on) { if (on) { return "\033[?1036h\033[?1034l"; } @@ -1042,7 +1174,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[31;1m" */ - public String addHeaderSGR(String str) { + private String addHeaderSGR(String str) { if (str.length() > 0) { // Nix any trailing ';' because that resets all attributes while (str.endsWith(":")) { @@ -1053,14 +1185,15 @@ public final class ECMA48Terminal implements Runnable { } /** - * Create a SGR parameter sequence for a single color change. + * Create a SGR parameter sequence for a single color change. Note + * package private access. * * @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" */ - public String color(final Color color, final boolean foreground) { + String color(final Color color, final boolean foreground) { return color(color, foreground, true); } @@ -1074,7 +1207,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[42m" */ - public String color(final Color color, final boolean foreground, + private String color(final Color color, final boolean foreground, final boolean header) { int ecmaColor = color.getValue(); @@ -1094,15 +1227,15 @@ public final class ECMA48Terminal implements Runnable { } /** - * Create a SGR parameter sequence for both foreground and - * background color change. + * Create a SGR parameter sequence for both foreground and background + * color change. Note package private access. * * @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" */ - public String color(final Color foreColor, final Color backColor) { + String color(final Color foreColor, final Color backColor) { return color(foreColor, backColor, true); } @@ -1117,7 +1250,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[31;42m" */ - public String color(final Color foreColor, final Color backColor, + private String color(final Color foreColor, final Color backColor, final boolean header) { int ecmaForeColor = foreColor.getValue(); @@ -1137,7 +1270,8 @@ public final class ECMA48Terminal implements Runnable { /** * 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. + * default, then sets attributes as per the parameters. Note package + * private access. * * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants @@ -1148,7 +1282,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[0;1;31;42m" */ - public String color(final Color foreColor, final Color backColor, + String color(final Color foreColor, final Color backColor, final boolean bold, final boolean reverse, final boolean blink, final boolean underline) { @@ -1205,7 +1339,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[7m" */ - public String reverse(final boolean on) { + private String reverse(final boolean on) { if (on) { return "\033[7m"; } @@ -1213,12 +1347,13 @@ public final class ECMA48Terminal implements Runnable { } /** - * Create a SGR parameter sequence to reset to defaults. + * Create a SGR parameter sequence to reset to defaults. Note package + * private access. * * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[0m" */ - public String normal() { + String normal() { return normal(true); } @@ -1230,7 +1365,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[0m" */ - public String normal(final boolean header) { + private String normal(final boolean header) { if (header) { return "\033[0;37;40m"; } @@ -1244,7 +1379,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[1m" */ - public String bold(final boolean on) { + private String bold(final boolean on) { return bold(on, true); } @@ -1257,7 +1392,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[1m" */ - public String bold(final boolean on, final boolean header) { + private String bold(final boolean on, final boolean header) { if (header) { if (on) { return "\033[1m"; @@ -1277,7 +1412,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[5m" */ - public String blink(final boolean on) { + private String blink(final boolean on) { return blink(on, true); } @@ -1290,7 +1425,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[5m" */ - public String blink(final boolean on, final boolean header) { + private String blink(final boolean on, final boolean header) { if (header) { if (on) { return "\033[5m"; @@ -1311,7 +1446,7 @@ public final class ECMA48Terminal implements Runnable { * @return the string to emit to an ANSI / ECMA-style terminal, * e.g. "\033[4m" */ - public String underline(final boolean on) { + private String underline(final boolean on) { if (on) { return "\033[4m"; } @@ -1319,12 +1454,13 @@ public final class ECMA48Terminal implements Runnable { } /** - * Create a SGR parameter sequence for enabling the visible cursor. + * Create a SGR parameter sequence for enabling the visible cursor. Note + * package private access. * * @param on if true, turn on cursor * @return the string to emit to an ANSI / ECMA-style terminal */ - public String cursor(final boolean on) { + String cursor(final boolean on) { if (on && !cursorOn) { cursorOn = true; return "\033[?25h"; @@ -1349,11 +1485,11 @@ public final class ECMA48Terminal implements Runnable { /** * Clear the line from the cursor (inclusive) to the end of the screen. * Because some terminals use back-color-erase, set the color to - * white-on-black beforehand. + * white-on-black beforehand. Note package private access. * * @return the string to emit to an ANSI / ECMA-style terminal */ - public String clearRemainingLine() { + String clearRemainingLine() { return "\033[0;37;40m\033[K"; } @@ -1363,7 +1499,7 @@ public final class ECMA48Terminal implements Runnable { * * @return the string to emit to an ANSI / ECMA-style terminal */ - public String clearPreceedingLine() { + private String clearPreceedingLine() { return "\033[0;37;40m\033[1K"; } @@ -1373,7 +1509,7 @@ public final class ECMA48Terminal implements Runnable { * * @return the string to emit to an ANSI / ECMA-style terminal */ - public String clearLine() { + private String clearLine() { return "\033[0;37;40m\033[2K"; } @@ -1382,24 +1518,26 @@ public final class ECMA48Terminal implements Runnable { * * @return the string to emit to an ANSI / ECMA-style terminal */ - public String home() { + private String home() { return "\033[H"; } /** - * Move the cursor to (x, y). + * Move the cursor to (x, y). Note package private access. * * @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 */ - public String gotoXY(final int x, final int y) { + String gotoXY(final int x, final 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 + * tracking", UTF-8 coordinates, and then SGR coordinates. Ideally we + * will end up with SGR coordinates with UTF-8 coordinates as a fallback. + * See * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking * * Note that this also sets the alternate/primary screen buffer. @@ -1409,11 +1547,11 @@ public final class ECMA48Terminal implements Runnable { * buffer. * @return the string to emit to xterm */ - public String mouse(final boolean on) { + private String mouse(final boolean on) { if (on) { - return "\033[?1003;1005h\033[?1049h"; + return "\033[?1003;1005;1006h\033[?1049h"; } - return "\033[?1003;1005l\033[?1049l"; + return "\033[?1003;1006;1005l\033[?1049l"; } /** @@ -1446,17 +1584,18 @@ public final class ECMA48Terminal implements Runnable { 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); - } - synchronized (listener) { - listener.notifyAll(); - } - events.clear(); + } + getIdleEvents(events); + if (events.size() > 0) { + // Add to the queue for the backend thread to + // be able to obtain. + synchronized (eventQueue) { + eventQueue.addAll(events); + } + synchronized (listener) { + listener.notifyAll(); } + events.clear(); } } } else {