-/**
+/*
* Jexer - Java Text User Interface
*
- * License: LGPLv3 or later
- *
- * This module is licensed under the GNU Lesser General Public License
- * Version 3. Please see the file "COPYING" in this directory for more
- * information about the GNU Lesser General Public License Version 3.
+ * The MIT License (MIT)
*
- * Copyright (C) 2015 Kevin Lamonte
+ * Copyright (C) 2017 Kevin Lamonte
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
*
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
*
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see
- * http://www.gnu.org/licenses/, or write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
*
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
import java.util.List;
import java.util.LinkedList;
-import jexer.TKeypress;
import jexer.bits.Color;
import jexer.event.TInputEvent;
import jexer.event.TKeypressEvent;
*/
public final class ECMA48Terminal implements Runnable {
+ /**
+ * If true, emit T.416-style RGB colors. This is a) expensive in
+ * bandwidth, and b) potentially terrible looking for non-xterms.
+ */
+ private static boolean doRgbColor = false;
+
/**
* The session information.
*/
*/
private ArrayList<String> params;
- /**
- * params[paramI] is being appended to.
- */
- private int paramI;
-
/**
* States in the input parser.
*/
ESCAPE_INTERMEDIATE,
CSI_ENTRY,
CSI_PARAM,
- // CSI_INTERMEDIATE,
- MOUSE
+ MOUSE,
+ MOUSE_SGR,
}
/**
private PrintWriter output;
/**
- * When true, the terminal is sending non-UTF8 bytes when reporting mouse
- * events.
- *
- * TODO: Add broken mouse detection back into the reader.
+ * The listening object that run() wakes up on new input.
*/
- private boolean brokenTerminalUTFMouse = false;
+ private Object listener;
/**
* Get the output writer.
/**
* Constructor sets up state for getEvent().
*
+ * @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
* @throws UnsupportedEncodingException if an exception is thrown when
* creating the InputStreamReader
*/
- public ECMA48Terminal(final InputStream input,
+ public ECMA48Terminal(final Object listener, final InputStream input,
final OutputStream output) throws UnsupportedEncodingException {
reset();
mouse2 = false;
mouse3 = false;
stopReaderThread = false;
+ this.listener = listener;
if (input == null) {
// inputStream = System.in;
}
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,
+ sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
+
+ // Permit RGB colors only if externally requested
+ if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
+ if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
+ doRgbColor = true;
+ }
+ }
+
+ // Spin up the input reader
+ eventQueue = new LinkedList<TInputEvent>();
+ readerThread = new Thread(this);
+ readerThread.start();
+ }
+
+ /**
+ * 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.
+ * @param setRawMode if true, set System.in into raw mode with stty.
+ * This should in general not be used. It is here solely for Demo3,
+ * which uses System.in.
+ * @throws IllegalArgumentException if input, reader, or writer are null.
+ */
+ public ECMA48Terminal(final Object listener, final InputStream input,
+ final Reader reader, final PrintWriter writer,
+ final boolean setRawMode) {
+
+ if (input == null) {
+ throw new IllegalArgumentException("InputStream must be specified");
+ }
+ if (reader == null) {
+ throw new IllegalArgumentException("Reader must be specified");
+ }
+ if (writer == null) {
+ throw new IllegalArgumentException("Writer must be specified");
+ }
+ reset();
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ stopReaderThread = false;
+ this.listener = listener;
+
+ inputStream = input;
+ this.input = reader;
+
+ if (setRawMode == true) {
+ sttyRaw();
+ }
+ this.setRawMode = setRawMode;
+
+ 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) {
+ if (setRawMode == true) {
+ // Reading right off the tty
+ sessionInfo = new TTYSessionInfo();
+ } else {
+ sessionInfo = new TSessionInfo();
+ }
+ }
+
+ this.output = writer;
+
+ // 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,
sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
+ // Permit RGB colors only if externally requested
+ if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
+ if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
+ doRgbColor = true;
+ }
+ }
+
// Spin up the input reader
eventQueue = new LinkedList<TInputEvent>();
readerThread = new Thread(this);
readerThread.start();
}
+ /**
+ * 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);
+ }
+
/**
* Restore terminal to normal state.
*/
private void reset() {
state = ParseState.GROUND;
params = new ArrayList<String>();
- paramI = 0;
params.clear();
params.add("");
}
*/
private TInputEvent csiFnKey() {
int key = 0;
- int modifier = 0;
if (params.size() > 0) {
key = Integer.parseInt(params.get(0));
}
- if (params.size() > 1) {
- modifier = Integer.parseInt(params.get(1));
- }
boolean alt = false;
boolean ctrl = false;
boolean shift = false;
-
- switch (modifier) {
- case 0:
- // No modifier
- break;
- case 2:
- // Shift
- shift = true;
- break;
- case 3:
- // Alt
- alt = true;
- break;
- case 5:
- // Ctrl
- ctrl = true;
- break;
- default:
- // Unknown modifier, bail out
- return null;
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
}
switch (key) {
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.
*
}
}
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * shift was down.
+ */
+ private boolean csiIsShift(final String x) {
+ if ((x.equals("2"))
+ || (x.equals("4"))
+ || (x.equals("6"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * alt was down.
+ */
+ private boolean csiIsAlt(final String x) {
+ if ((x.equals("3"))
+ || (x.equals("4"))
+ || (x.equals("7"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * ctrl was down.
+ */
+ private boolean csiIsCtrl(final String x) {
+ if ((x.equals("5"))
+ || (x.equals("6"))
+ || (x.equals("7"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Parses the next character of input to see if an InputEvent is
* fully here.
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);
case CSI_ENTRY:
// Numbers - parameter values
if ((ch >= '0') && (ch <= '9')) {
- params.set(paramI, params.get(paramI) + ch);
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
state = ParseState.CSI_PARAM;
return;
}
// Parameter separator
if (ch == ';') {
- paramI++;
params.add("");
return;
}
switch (ch) {
case 'A':
// Up
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
- }
events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
reset();
return;
case 'B':
// Down
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
- }
events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
reset();
return;
case 'C':
// Right
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
- }
events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
reset();
return;
case 'D':
// Left
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
- }
events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
reset();
return;
// 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')) {
- params.set(paramI, params.get(paramI) + ch);
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
state = ParseState.CSI_PARAM;
return;
}
// Parameter separator
if (ch == ';') {
- paramI++;
- params.add(paramI, "");
+ params.add("");
return;
}
case 'A':
// Up
if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
reset();
case 'B':
// Down
if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
reset();
case 'C':
// Right
if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
reset();
case 'D':
// Left
if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- shift = true;
- }
- if (params.get(1).equals("5")) {
- ctrl = true;
- }
- if (params.get(1).equals("3")) {
- alt = true;
- }
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
}
events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
reset();
return;
+ case 'H':
+ // Home
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbHome, alt, ctrl, shift));
+ reset();
+ return;
+ case 'F':
+ // End
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
+ reset();
+ return;
default:
break;
}
return;
case MOUSE:
- params.set(0, params.get(paramI) + ch);
+ params.set(0, params.get(params.size() - 1) + ch);
if (params.get(0).length() == 3) {
// We have enough to generate a mouse event
events.add(parseMouse());
* @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.
+ * Create an xterm OSC sequence to change the window title. Note package
+ * private access.
*
- * @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"
+ * @param title the new title
+ * @return the string to emit to xterm
*/
- 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";
+ String setTitle(final String title) {
+ return "\033]2;" + title + "\007";
}
/**
- * 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 bold if true, set bold
* @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) {
- return color(color, foreground, true);
+ String color(final boolean bold, final Color color,
+ final boolean foreground) {
+ return color(color, foreground, true) +
+ rgbColor(bold, color, foreground);
+ }
+
+ /**
+ * Create a T.416 RGB parameter sequence for a single color change.
+ *
+ * @param bold if true, set bold
+ * @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 xterm terminal with RGB support,
+ * e.g. "\033[38;2;RR;GG;BBm"
+ */
+ private String rgbColor(final boolean bold, final Color color,
+ final boolean foreground) {
+ if (doRgbColor == false) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder("\033[");
+ if (bold) {
+ // Bold implies foreground only
+ sb.append("38;2;");
+ if (color.equals(Color.BLACK)) {
+ sb.append("84;84;84");
+ } else if (color.equals(Color.RED)) {
+ sb.append("252;84;84");
+ } else if (color.equals(Color.GREEN)) {
+ sb.append("84;252;84");
+ } else if (color.equals(Color.YELLOW)) {
+ sb.append("252;252;84");
+ } else if (color.equals(Color.BLUE)) {
+ sb.append("84;84;252");
+ } else if (color.equals(Color.MAGENTA)) {
+ sb.append("252;84;252");
+ } else if (color.equals(Color.CYAN)) {
+ sb.append("84;252;252");
+ } else if (color.equals(Color.WHITE)) {
+ sb.append("252;252;252");
+ }
+ } else {
+ if (foreground) {
+ sb.append("38;2;");
+ } else {
+ sb.append("48;2;");
+ }
+ if (color.equals(Color.BLACK)) {
+ sb.append("0;0;0");
+ } else if (color.equals(Color.RED)) {
+ sb.append("168;0;0");
+ } else if (color.equals(Color.GREEN)) {
+ sb.append("0;168;0");
+ } else if (color.equals(Color.YELLOW)) {
+ sb.append("168;84;0");
+ } else if (color.equals(Color.BLUE)) {
+ sb.append("0;0;168");
+ } else if (color.equals(Color.MAGENTA)) {
+ sb.append("168;0;168");
+ } else if (color.equals(Color.CYAN)) {
+ sb.append("0;168;168");
+ } else if (color.equals(Color.WHITE)) {
+ sb.append("168;168;168");
+ }
+ }
+ sb.append("m");
+ return sb.toString();
+ }
+
+ /**
+ * Create a T.416 RGB parameter sequence for both foreground and
+ * background color change.
+ *
+ * @param bold if true, set bold
+ * @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 xterm terminal with RGB support,
+ * e.g. "\033[38;2;RR;GG;BB;48;2;RR;GG;BBm"
+ */
+ private String rgbColor(final boolean bold, final Color foreColor,
+ final Color backColor) {
+ if (doRgbColor == false) {
+ return "";
+ }
+
+ return rgbColor(bold, foreColor, true) +
+ rgbColor(false, backColor, false);
}
/**
* @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 bold if true, set bold
* @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) {
- return color(foreColor, backColor, true);
+ String color(final boolean bold, final Color foreColor,
+ final Color backColor) {
+ return color(foreColor, backColor, true) +
+ rgbColor(bold, foreColor, backColor);
}
/**
* @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) {
sb.append("\033[0;");
}
sb.append(String.format("%d;%dm", ecmaForeColor, ecmaBackColor));
+ sb.append(rgbColor(bold, foreColor, backColor));
return sb.toString();
}
/**
- * 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() {
- return normal(true);
+ String normal() {
+ return normal(true) + rgbColor(false, Color.WHITE, Color.BLACK);
}
/**
* @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[?1002;1003;1005;1006h\033[?1049h";
}
- return "\033[?1003;1005l\033[?1049l";
+ return "\033[?1002;1003;1006;1005l\033[?1049l";
}
/**
readBuffer = new char[readBuffer.length * 2];
}
- int rc = input.read(readBuffer, 0, n);
+ int rc = input.read(readBuffer, 0, readBuffer.length);
// System.err.printf("read() %d", rc); System.err.flush();
if (rc == -1) {
// This is EOF
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();
+ }
+ 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 {
eventQueue.addAll(events);
}
events.clear();
+ synchronized (listener) {
+ listener.notifyAll();
+ }
}
// Wait 10 millis for more data