-/**
+/*
* Jexer - Java Text User Interface
*
* License: LGPLv3 or later
import java.util.List;
import java.util.LinkedList;
-import jexer.TKeypress;
import jexer.bits.Color;
import jexer.event.TInputEvent;
import jexer.event.TKeypressEvent;
CSI_ENTRY,
CSI_PARAM,
// CSI_INTERMEDIATE,
- MOUSE
+ MOUSE,
+ MOUSE_SGR,
}
/**
*/
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.
*
}
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) {
// 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,
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.
*
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);
// Mouse position
state = ParseState.MOUSE;
return;
+ case '<':
+ // Mouse position, SGR (1006) coordinates
+ state = ParseState.MOUSE_SGR;
+ return;
default:
break;
}
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')) {
* @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";
}
}
/**
- * 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.
- *
- * @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"
- */
- public String addHeaderSGR(String str) {
- if (str.length() > 0) {
- // Nix any trailing ';' because that resets all attributes
- while (str.endsWith(":")) {
- str = str.substring(0, str.length() - 1);
- }
- }
- return "\033[" + str + "m";
- }
-
- /**
- * 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);
}
* @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();
}
/**
- * 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);
}
* @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();
/**
* 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
* @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) {
}
/**
- * Create a SGR parameter sequence for enabling reverse color.
- *
- * @param on if true, turn on reverse
- * @return the string to emit to an ANSI / ECMA-style terminal,
- * e.g. "\033[7m"
- */
- public String reverse(final boolean on) {
- if (on) {
- return "\033[7m";
- }
- return "\033[27m";
- }
-
- /**
- * 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);
}
* @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";
}
}
/**
- * Create a SGR parameter sequence for enabling boldface.
- *
- * @param on if true, turn on bold
- * @return the string to emit to an ANSI / ECMA-style terminal,
- * e.g. "\033[1m"
- */
- public String bold(final boolean on) {
- return bold(on, true);
- }
-
- /**
- * Create a SGR parameter sequence for enabling boldface.
- *
- * @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"
- */
- public String bold(final boolean on, final boolean header) {
- if (header) {
- if (on) {
- return "\033[1m";
- }
- return "\033[22m";
- }
- if (on) {
- return "1;";
- }
- return "22;";
- }
-
- /**
- * Create a SGR parameter sequence for enabling blinking text.
- *
- * @param on if true, turn on blink
- * @return the string to emit to an ANSI / ECMA-style terminal,
- * e.g. "\033[5m"
- */
- public String blink(final boolean on) {
- return blink(on, true);
- }
-
- /**
- * Create a SGR parameter sequence for enabling blinking text.
- *
- * @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"
- */
- public String blink(final boolean on, final boolean header) {
- if (header) {
- if (on) {
- return "\033[5m";
- }
- return "\033[25m";
- }
- if (on) {
- return "5;";
- }
- return "25;";
- }
-
- /**
- * Create a SGR parameter sequence for enabling underline / underscored
- * text.
- *
- * @param on if true, turn on underline
- * @return the string to emit to an ANSI / ECMA-style terminal,
- * e.g. "\033[4m"
- */
- public String underline(final boolean on) {
- if (on) {
- return "\033[4m";
- }
- return "\033[24m";
- }
-
- /**
- * 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";
/**
* 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";
}
/**
- * Clear the line up the cursor (inclusive). Because some terminals use
- * back-color-erase, set the color to white-on-black beforehand.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- public String clearPreceedingLine() {
- return "\033[0;37;40m\033[1K";
- }
-
- /**
- * Clear the line. Because some terminals use back-color-erase, set the
- * color to white-on-black beforehand.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- public String clearLine() {
- return "\033[0;37;40m\033[2K";
- }
-
- /**
- * Move the cursor to the top-left corner.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- public 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.
* 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";
}
/**
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 {