From b5f2a6dbf2f2b925007a85f68665103ec3514f84 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sun, 22 Mar 2015 08:56:11 -0400 Subject: [PATCH] blinking cursor and SGR 1006 mode --- README.md | 69 +++++++--- src/jexer/io/ECMA48Terminal.java | 216 ++++++++++++++++++++++++++----- src/jexer/io/SwingScreen.java | 107 +++++++++++++-- src/jexer/tterminal/ECMA48.java | 112 +++++++++++++--- 4 files changed, 416 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 24b437a..1e1bb54 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ Two backends are available: * System.in/out to a command-line ECMA-48 / ANSI X3.64 type terminal (tested on Linux + xterm). I/O is handled through terminal escape sequences generated by the library itself: ncurses is not required - or linked to. xterm mouse tracking using UTF8 coordinates is - supported. For the demo application, this is the default backend on - non-Windows platforms. + or linked to. xterm mouse tracking using UTF8 and SGR coordinates + are supported. For the demo application, this is the default + backend on non-Windows platforms. * Java Swing UI. This backend can be selected by setting jexer.Swing=true. The default window size for Swing is 132x40, @@ -110,15 +110,52 @@ ambiguous. This section describes such issues. input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and SetConsoleMode()). + - TTerminalWindow launches 'script -fqe /dev/null' on non-Windows + platforms. This is a workaround for the C library behavior of + checking for a tty: script launches $SHELL in a pseudo-tty. This + works on Linux but might not on other Posix-y platforms. + ECMA48 Backend -------------- - - - Mouse support for BackendType.ECMA48/XTERM currently requires UTF-8 - coordinates (1005 mode). Terminals that support UTF-8 mouse coordinates - include xterm, rxvt-unicode, gnome-terminal, and konsole. Due to Java's - InputStreamReader requirement of a valid UTF-8 stream, one must assume - the terminal will always generate correct UTF-8 bytes. Mode 1006 (SGR) - will be supported in a future release. + + - Java's InputStreamReader requires a valid UTF-8 stream. The + default X10 encoding for mouse coordinates outside (160,94) can + corrupt that stream, at best putting garbage keyboard events in + the input queue but at worst causing the backend reader thread to + throw an Exception and exit and make the entire UI unusable. + Mouse support therefore requires a terminal that can deliver + either UTF-8 coordinates (1005 mode) or SGR coordinates (1006 + mode). Most modern terminals can do this. + + Use of 'stty' + ------------- + + - jexer.session.TTYSession calls 'stty size' once every second to + check the current window size, performing the same function as + ioctl(TIOCGWINSZ) but without requiring a native library. + + - jexer.io.ECMA48Terminal calls 'stty' to perform the equivalent of + cfmakeraw(). The terminal is (blindly!) put back in 'stty sane + cooked' mode when exiting. + + +System Properties +----------------- + +The following properties control features of Jexer: + + jexer.Swing + ----------- + + Used only by jexer.demos.Demo1. If true, use the Swing interface + for the demo application. Default: true on Windows platforms + (os.name starts with "Windows"), false on non-Windows platforms. + + jexer.Swing.cursorStyle + ----------------------- + + Used by jexer.io.SwingScreen. Selects the cursor style to draw. + Valid values are: underline, block, outline. Default: underline. @@ -129,15 +166,7 @@ Many tasks remain before calling this version 1.0: 0.0.2: STABILIZE EXISTING -- TTerminalWindow - - Expose shell commands as properties -- Swing: - - Blinking cursor - - Block cursor - ECMA48Backend running on socket -- TFileOpen -- Document any properties used - - Expose use of 'stty' 0.0.3: FINISH PORTING @@ -145,6 +174,7 @@ Many tasks remain before calling this version 1.0: - Also add keyboard navigation - TDirectoryList - Also add keyboard navigation +- TFileOpen 0.0.4: NEW STUFF @@ -157,8 +187,6 @@ Many tasks remain before calling this version 1.0: 0.0.5: BUG HUNT - TSubMenu keyboard mnemonic not working -- ECMA48Terminal - - Mode 1006 mouse coordinates 0.1.0: BETA RELEASE @@ -188,4 +216,3 @@ Screenshots ![Several Windows Open Including A Terminal](/screenshots/screenshot1.png?raw=true "Several Windows Open Including A Terminal") ![Yo Dawg...](/screenshots/yodawg.png?raw=true "Yo Dawg, I heard you like text windowing systems, so I ran a text windowing system inside your text windowing system so you can have a terminal in your terminal.") - diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index b496d8c..a4c2861 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -107,7 +107,8 @@ public final class ECMA48Terminal implements Runnable { CSI_ENTRY, CSI_PARAM, // CSI_INTERMEDIATE, - MOUSE + MOUSE, + MOUSE_SGR, } /** @@ -625,6 +626,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. * @@ -886,6 +986,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; } @@ -895,6 +999,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')) { @@ -1015,7 +1157,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"; } @@ -1031,7 +1173,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(":")) { @@ -1042,14 +1184,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); } @@ -1063,7 +1206,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(); @@ -1083,15 +1226,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); } @@ -1106,7 +1249,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(); @@ -1126,7 +1269,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 @@ -1137,7 +1281,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) { @@ -1194,7 +1338,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"; } @@ -1202,12 +1346,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); } @@ -1219,7 +1364,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"; } @@ -1233,7 +1378,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); } @@ -1246,7 +1391,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"; @@ -1266,7 +1411,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); } @@ -1279,7 +1424,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"; @@ -1300,7 +1445,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"; } @@ -1308,12 +1453,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"; @@ -1338,11 +1484,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"; } @@ -1352,7 +1498,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"; } @@ -1362,7 +1508,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"; } @@ -1371,24 +1517,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. @@ -1398,11 +1546,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"; } /** diff --git a/src/jexer/io/SwingScreen.java b/src/jexer/io/SwingScreen.java index bdd74a7..2a96122 100644 --- a/src/jexer/io/SwingScreen.java +++ b/src/jexer/io/SwingScreen.java @@ -30,10 +30,6 @@ */ package jexer.io; -import jexer.bits.Cell; -import jexer.bits.CellAttributes; -import jexer.session.SwingSessionInfo; - import java.awt.Color; import java.awt.Cursor; import java.awt.Font; @@ -46,14 +42,39 @@ import java.awt.Toolkit; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.InputStream; +import java.util.Date; import javax.swing.JFrame; import javax.swing.SwingUtilities; +import jexer.bits.Cell; +import jexer.bits.CellAttributes; +import jexer.session.SwingSessionInfo; + /** * This Screen implementation draws to a Java Swing JFrame. */ public final class SwingScreen extends Screen { + /** + * Cursor style to draw. + */ + public enum CursorStyle { + /** + * Use an underscore for the cursor. + */ + UNDERLINE, + + /** + * Use a solid block for the cursor. + */ + BLOCK, + + /** + * Use an outlined block for the cursor. + */ + OUTLINE + } + private static Color MYBLACK; private static Color MYRED; private static Color MYGREEN; @@ -137,15 +158,38 @@ public final class SwingScreen extends Screen { private int maxDescent = 0; /** - * Top pixel value. + * Top pixel absolute location. */ private int top = 30; /** - * Left pixel value. + * Left pixel absolute location. */ private int left = 30; + /** + * The cursor style to draw. + */ + private CursorStyle cursorStyle = CursorStyle.UNDERLINE; + + /** + * The number of millis to wait before switching the blink from + * visible to invisible. + */ + private long blinkMillis = 500; + + /** + * If true, the cursor should be visible right now based on the blink + * time. + */ + private boolean cursorBlinkVisible = true; + + /** + * The time that the blink last flipped from visible to invisible or + * from invisible to visible. + */ + private long lastBlinkTime = 0; + /** * Convert a CellAttributes foreground color to an Swing Color. * @@ -241,11 +285,20 @@ public final class SwingScreen extends Screen { this.screen = screen; setDOSColors(); + // Figure out my cursor style + String cursorStyleString = System.getProperty("jexer.Swing.cursorStyle", + "underline").toLowerCase(); + + if (cursorStyleString.equals("underline")) { + cursorStyle = CursorStyle.UNDERLINE; + } else if (cursorStyleString.equals("outline")) { + cursorStyle = CursorStyle.OUTLINE; + } else if (cursorStyleString.equals("block")) { + cursorStyle = CursorStyle.BLOCK; + } + setTitle("Jexer Application"); setBackground(Color.black); - // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - // setFont(new Font("Liberation Mono", Font.BOLD, 16)); - // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16)); try { // Always try to use Terminus, the one decent font. @@ -334,6 +387,13 @@ public final class SwingScreen extends Screen { return; } + // See if it is time to flip the blink time. + long nowTime = (new Date()).getTime(); + if (nowTime > blinkMillis + lastBlinkTime) { + lastBlinkTime = nowTime; + cursorBlinkVisible = !cursorBlinkVisible; + } + int xCellMin = 0; int xCellMax = screen.width; int yCellMin = 0; @@ -404,15 +464,28 @@ public final class SwingScreen extends Screen { } // Draw the cursor if it is visible - if ((cursorVisible) + if (cursorVisible && (cursorY <= screen.height - 1) && (cursorX <= screen.width - 1) + && cursorBlinkVisible ) { int xPixel = cursorX * textWidth + left; int yPixel = cursorY * textHeight + top; Cell lCell = screen.logical[cursorX][cursorY]; gr.setColor(attrToForegroundColor(lCell)); - gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2); + switch (cursorStyle) { + case UNDERLINE: + gr.fillRect(xPixel, yPixel + textHeight - 2, + textWidth, 2); + break; + case BLOCK: + gr.fillRect(xPixel, yPixel, textWidth, textHeight); + break; + case OUTLINE: + gr.drawRect(xPixel, yPixel, textWidth - 1, + textHeight - 1); + break; + } } dirty = false; @@ -543,7 +616,17 @@ public final class SwingScreen extends Screen { */ @Override public void putCursor(final boolean visible, final int x, final int y) { - if ((cursorVisible) + + if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) { + // See if it is time to flip the blink time. + long nowTime = (new Date()).getTime(); + if (nowTime < frame.blinkMillis + frame.lastBlinkTime) { + // Nothing has changed, so don't do anything. + return; + } + } + + if (cursorVisible && (cursorY <= height - 1) && (cursorX <= width - 1) ) { diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 7b41594..0918e9b 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -522,7 +522,8 @@ public class ECMA48 implements Runnable { */ private enum MouseEncoding { X10, - UTF8 + UTF8, + SGR } /** @@ -1110,7 +1111,7 @@ public class ECMA48 implements Runnable { mouseProtocol, mouseEncoding, mouse); */ - if (mouseEncoding != MouseEncoding.UTF8) { + if (mouseEncoding == MouseEncoding.X10) { // We will support X10 but only for (160,94) and smaller. if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) { return; @@ -1163,27 +1164,83 @@ public class ECMA48 implements Runnable { // Now encode the event StringBuilder sb = new StringBuilder(6); - sb.append((char) 0x1B); - sb.append('['); - sb.append('M'); - if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { - sb.append((char) (0x03 + 32)); - } else if (mouse.isMouse1()) { - sb.append((char) (0x00 + 32)); - } else if (mouse.isMouse2()) { - sb.append((char) (0x01 + 32)); - } else if (mouse.isMouse3()) { - sb.append((char) (0x02 + 32)); - } else if (mouse.isMouseWheelUp()) { - sb.append((char) (0x04 + 64)); - } else if (mouse.isMouseWheelDown()) { - sb.append((char) (0x05 + 64)); + if (mouseEncoding == MouseEncoding.SGR) { + sb.append((char) 0x1B); + sb.append("[<"); + + if (mouse.isMouse1()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append("32;"); + } else { + sb.append("0;"); + } + } else if (mouse.isMouse2()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append("33;"); + } else { + sb.append("1;"); + } + } else if (mouse.isMouse3()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append("34;"); + } else { + sb.append("2;"); + } + } else if (mouse.isMouseWheelUp()) { + sb.append("64;"); + } else if (mouse.isMouseWheelDown()) { + sb.append("65;"); + } else { + // This is motion with no buttons down. + sb.append("35;"); + } + + sb.append(String.format("%d;%d", mouse.getX() + 1, + mouse.getY() + 1)); + + if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { + sb.append("m"); + } else { + sb.append("M"); + } + } else { - sb.append((char) (0x03 + 32)); - } + // X10 and UTF8 encodings + sb.append((char) 0x1B); + sb.append('['); + sb.append('M'); + if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { + sb.append((char) (0x03 + 32)); + } else if (mouse.isMouse1()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append((char) (0x00 + 32 + 32)); + } else { + sb.append((char) (0x00 + 32)); + } + } else if (mouse.isMouse2()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append((char) (0x01 + 32 + 32)); + } else { + sb.append((char) (0x01 + 32)); + } + } else if (mouse.isMouse3()) { + if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { + sb.append((char) (0x02 + 32 + 32)); + } else { + sb.append((char) (0x02 + 32)); + } + } else if (mouse.isMouseWheelUp()) { + sb.append((char) (0x04 + 64)); + } else if (mouse.isMouseWheelDown()) { + sb.append((char) (0x05 + 64)); + } else { + // This is motion with no buttons down. + sb.append((char) (0x03 + 32)); + } - sb.append((char) (mouse.getX() + 33)); - sb.append((char) (mouse.getY() + 33)); + sb.append((char) (mouse.getX() + 33)); + sb.append((char) (mouse.getY() + 33)); + } // System.err.printf("Would write: \'%s\'\n", sb.toString()); writeRemote(sb.toString()); @@ -2441,6 +2498,19 @@ public class ECMA48 implements Runnable { } break; + case 1006: + if ((type == DeviceType.XTERM) + && (decPrivateModeFlag == true) + ) { + // Mouse: SGR coordinates + if (value == true) { + mouseEncoding = MouseEncoding.SGR; + } else { + mouseEncoding = MouseEncoding.X10; + } + } + break; + default: break; -- 2.27.0