params;
/**
- * States in the input parser
+ * States in the input parser.
*/
private enum ParseState {
- GROUND,
- ESCAPE,
- ESCAPE_INTERMEDIATE,
- CSI_ENTRY,
- CSI_PARAM,
- // CSI_INTERMEDIATE,
- MOUSE
+ GROUND,
+ ESCAPE,
+ ESCAPE_INTERMEDIATE,
+ CSI_ENTRY,
+ CSI_PARAM,
+ // CSI_INTERMEDIATE,
+ MOUSE,
+ MOUSE_SGR,
}
/**
- * Current parsing state
+ * Current parsing state.
*/
private ParseState state;
/**
- * The time we entered ESCAPE. If we get a bare escape
- * without a code following it, this is used to return that bare
- * escape.
+ * The time we entered ESCAPE. If we get a bare escape without a code
+ * following it, this is used to return that bare escape.
*/
private long escapeTime;
+ /**
+ * The time we last checked the window size. We try not to spawn stty
+ * more than once per second.
+ */
+ private long windowSizeTime;
+
/**
* true if mouse1 was down. Used to report mouse1 on the release event.
*/
@@ -139,11 +162,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
@@ -152,12 +183,9 @@ public class ECMA48Terminal {
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.
@@ -165,21 +193,38 @@ public class ECMA48Terminal {
* @return the Writer
*/
public PrintWriter getOutput() {
- return output;
+ return output;
}
/**
- * Call 'stty cooked' to set cooked mode.
+ * 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' to set cooked mode.
+ *
+ * Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
*/
private void sttyCooked() {
- doStty(false);
+ doStty(false);
}
/**
- * Call 'stty raw' to set raw mode.
+ * Call 'stty' to set raw mode.
+ *
+ *
Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
+ * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
+ * -parenb cs8 min 1 < /dev/tty'
*/
private void sttyRaw() {
- doStty(true);
+ doStty(true);
}
/**
@@ -187,52 +232,52 @@ public class ECMA48Terminal {
*
* @param mode if true, set raw mode, otherwise set cooked mode
*/
- private void doStty(boolean mode) {
- String [] cmdRaw = {
- "/bin/sh", "-c", "stty raw < /dev/tty"
- };
- String [] cmdCooked = {
- "/bin/sh", "-c", "stty cooked < /dev/tty"
- };
- try {
- System.out.println("spawn stty");
-
- Process process;
- if (mode == true) {
- process = Runtime.getRuntime().exec(cmdRaw);
- } else {
- process = Runtime.getRuntime().exec(cmdCooked);
- }
- BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
- String line = in.readLine();
- if ((line != null) && (line.length() > 0)) {
- System.err.println("WEIRD?! Normal output from stty: " + line);
- }
- while (true) {
- BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
- line = err.readLine();
- if ((line != null) && (line.length() > 0)) {
- System.err.println("Error output from stty: " + line);
- }
- try{
- process.waitFor();
- break;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- int rc = process.exitValue();
- if (rc != 0) {
- System.err.println("stty returned error code: " + rc);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
+ private void doStty(final boolean mode) {
+ String [] cmdRaw = {
+ "/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 sane cooked < /dev/tty"
+ };
+ try {
+ Process process;
+ if (mode) {
+ process = Runtime.getRuntime().exec(cmdRaw);
+ } else {
+ process = Runtime.getRuntime().exec(cmdCooked);
+ }
+ BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
+ String line = in.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ System.err.println("WEIRD?! Normal output from stty: " + line);
+ }
+ while (true) {
+ BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
+ line = err.readLine();
+ if ((line != null) && (line.length() > 0)) {
+ System.err.println("Error output from stty: " + line);
+ }
+ try {
+ process.waitFor();
+ break;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ int rc = process.exitValue();
+ if (rc != 0) {
+ System.err.println("stty returned error code: " + rc);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
/**
- * Constructor sets up state for getEvent()
+ * 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
@@ -240,116 +285,155 @@ public class ECMA48Terminal {
* @param output an OutputStream connected to the remote user, or null
* for System.out. output is always converted to a Writer with UTF-8
* encoding.
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
*/
- public ECMA48Terminal(InputStream input, OutputStream output) throws UnsupportedEncodingException {
-
- reset();
- mouse1 = false;
- mouse2 = false;
- mouse3 = false;
-
- if (input == null) {
- this.input = new InputStreamReader(System.in, "UTF-8");
- sttyRaw();
- setRawMode = true;
- } else {
- this.input = new InputStreamReader(input);
- }
- // TODO: include TelnetSocket from NIB and have it implement
- // SessionInfo
- if (input instanceof SessionInfo) {
- session = (SessionInfo)input;
- }
- if (session == null) {
- if (input == null) {
- // Reading right off the tty
- session = new TTYSessionInfo();
- } else {
- session = new TSessionInfo();
- }
- }
-
- if (output == null) {
- this.output = new PrintWriter(new OutputStreamWriter(System.out,
- "UTF-8"));
- } else {
- this.output = new PrintWriter(new OutputStreamWriter(output,
- "UTF-8"));
- }
-
- // Enable mouse reporting and metaSendsEscape
- this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
-
- // Hang onto the window size
- windowResize = new TResizeEvent(TResizeEvent.Type.Screen,
- session.getWindowWidth(), session.getWindowHeight());
+ public ECMA48Terminal(final Object listener, final InputStream input,
+ final OutputStream output) throws UnsupportedEncodingException {
+
+ reset();
+ mouse1 = false;
+ mouse2 = false;
+ mouse3 = false;
+ stopReaderThread = false;
+ this.listener = listener;
+
+ if (input == null) {
+ // inputStream = System.in;
+ inputStream = new FileInputStream(FileDescriptor.in);
+ sttyRaw();
+ setRawMode = true;
+ } else {
+ inputStream = input;
+ }
+ this.input = new InputStreamReader(inputStream, "UTF-8");
+
+ // TODO: include TelnetSocket from NIB and have it implement
+ // SessionInfo
+ if (input instanceof SessionInfo) {
+ sessionInfo = (SessionInfo) input;
+ }
+ if (sessionInfo == null) {
+ if (input == null) {
+ // Reading right off the tty
+ sessionInfo = new TTYSessionInfo();
+ } else {
+ sessionInfo = new TSessionInfo();
+ }
+ }
+
+ if (output == null) {
+ this.output = new PrintWriter(new OutputStreamWriter(System.out,
+ "UTF-8"));
+ } else {
+ this.output = new PrintWriter(new OutputStreamWriter(output,
+ "UTF-8"));
+ }
+
+ // Enable mouse reporting and metaSendsEscape
+ this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
+
+ // Hang onto the window size
+ windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
+
+ // Spin up the input reader
+ eventQueue = new LinkedList();
+ readerThread = new Thread(this);
+ readerThread.start();
}
/**
- * Restore terminal to normal state
+ * Restore terminal to normal state.
*/
public void shutdown() {
- if (setRawMode) {
- sttyCooked();
- setRawMode = false;
- }
- // Disable mouse reporting and show cursor
- output.printf("%s%s%s", mouse(false), cursor(true), normal());
+
+ // 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();
+ }
+ }
}
/**
- * Flush output
+ * Flush output.
*/
public void flush() {
- output.flush();
+ output.flush();
}
/**
- * Reset keyboard/mouse input parser
+ * Reset keyboard/mouse input parser.
*/
private void reset() {
- state = ParseState.GROUND;
- paramI = 0;
- params.clear();
- params.add("");
+ state = ParseState.GROUND;
+ params = new ArrayList();
+ params.clear();
+ params.add("");
}
/**
* Produce a control character or one of the special ones (ENTER, TAB,
- * etc.)
+ * etc.).
*
* @param ch Unicode code point
- * @return one KEYPRESS event, either a control character (e.g. isKey ==
+ * @param alt if true, set alt on the TKeypress
+ * @return one TKeypress event, either a control character (e.g. isKey ==
* false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
* fnKey == ESC)
*/
- private TKeypressEvent controlChar(char ch) {
- TKeypressEvent event = new TKeypressEvent();
-
- // System.err.printf("controlChar: %02x\n", ch);
-
- switch (ch) {
- case '\r':
- // ENTER
- event.key = kbEnter;
- break;
- case 0x1B:
- // ESC
- event.key = kbEsc;
- break;
- case '\t':
- // TAB
- event.key = kbTab;
- break;
- default:
- // Make all other control characters come back as the alphabetic
- // character with the ctrl field set. So SOH would be 'A' +
- // ctrl.
- event.key = new TKeypress(false, 0, (char)(ch + 0x40),
- false, true, false);
- break;
- }
- return event;
+ private TKeypressEvent controlChar(final char ch, final boolean alt) {
+ // System.err.printf("controlChar: %02x\n", ch);
+
+ switch (ch) {
+ case 0x0D:
+ // Carriage return --> ENTER
+ return new TKeypressEvent(kbEnter, alt, false, false);
+ case 0x0A:
+ // Linefeed --> ENTER
+ return new TKeypressEvent(kbEnter, alt, false, false);
+ case 0x1B:
+ // ESC
+ return new TKeypressEvent(kbEsc, alt, false, false);
+ case '\t':
+ // TAB
+ return new TKeypressEvent(kbTab, alt, false, false);
+ default:
+ // Make all other control characters come back as the alphabetic
+ // character with the ctrl field set. So SOH would be 'A' +
+ // ctrl.
+ return new TKeypressEvent(false, 0, (char)(ch + 0x40),
+ alt, true, false);
+ }
}
/**
@@ -358,228 +442,72 @@ public class ECMA48Terminal {
* @return one KEYPRESS event representing a special key
*/
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));
- }
- TKeypressEvent event = new TKeypressEvent();
-
- switch (modifier) {
- case 0:
- // No modifier
- switch (key) {
- case 1:
- event.key = kbHome;
- break;
- case 2:
- event.key = kbIns;
- break;
- case 3:
- event.key = kbDel;
- break;
- case 4:
- event.key = kbEnd;
- break;
- case 5:
- event.key = kbPgUp;
- break;
- case 6:
- event.key = kbPgDn;
- break;
- case 15:
- event.key = kbF5;
- break;
- case 17:
- event.key = kbF6;
- break;
- case 18:
- event.key = kbF7;
- break;
- case 19:
- event.key = kbF8;
- break;
- case 20:
- event.key = kbF9;
- break;
- case 21:
- event.key = kbF10;
- break;
- case 23:
- event.key = kbF11;
- break;
- case 24:
- event.key = kbF12;
- break;
- default:
- // Unknown
- return null;
- }
-
- break;
- case 2:
- // Shift
- switch (key) {
- case 1:
- event.key = kbShiftHome;
- break;
- case 2:
- event.key = kbShiftIns;
- break;
- case 3:
- event.key = kbShiftDel;
- break;
- case 4:
- event.key = kbShiftEnd;
- break;
- case 5:
- event.key = kbShiftPgUp;
- break;
- case 6:
- event.key = kbShiftPgDn;
- break;
- case 15:
- event.key = kbShiftF5;
- break;
- case 17:
- event.key = kbShiftF6;
- break;
- case 18:
- event.key = kbShiftF7;
- break;
- case 19:
- event.key = kbShiftF8;
- break;
- case 20:
- event.key = kbShiftF9;
- break;
- case 21:
- event.key = kbShiftF10;
- break;
- case 23:
- event.key = kbShiftF11;
- break;
- case 24:
- event.key = kbShiftF12;
- break;
- default:
- // Unknown
- return null;
- }
- break;
-
- case 3:
- // Alt
- switch (key) {
- case 1:
- event.key = kbAltHome;
- break;
- case 2:
- event.key = kbAltIns;
- break;
- case 3:
- event.key = kbAltDel;
- break;
- case 4:
- event.key = kbAltEnd;
- break;
- case 5:
- event.key = kbAltPgUp;
- break;
- case 6:
- event.key = kbAltPgDn;
- break;
- case 15:
- event.key = kbAltF5;
- break;
- case 17:
- event.key = kbAltF6;
- break;
- case 18:
- event.key = kbAltF7;
- break;
- case 19:
- event.key = kbAltF8;
- break;
- case 20:
- event.key = kbAltF9;
- break;
- case 21:
- event.key = kbAltF10;
- break;
- case 23:
- event.key = kbAltF11;
- break;
- case 24:
- event.key = kbAltF12;
- break;
- default:
- // Unknown
- return null;
- }
- break;
-
- case 5:
- // Ctrl
- switch (key) {
- case 1:
- event.key = kbCtrlHome;
- break;
- case 2:
- event.key = kbCtrlIns;
- break;
- case 3:
- event.key = kbCtrlDel;
- break;
- case 4:
- event.key = kbCtrlEnd;
- break;
- case 5:
- event.key = kbCtrlPgUp;
- break;
- case 6:
- event.key = kbCtrlPgDn;
- break;
- case 15:
- event.key = kbCtrlF5;
- break;
- case 17:
- event.key = kbCtrlF6;
- break;
- case 18:
- event.key = kbCtrlF7;
- break;
- case 19:
- event.key = kbCtrlF8;
- break;
- case 20:
- event.key = kbCtrlF9;
- break;
- case 21:
- event.key = kbCtrlF10;
- break;
- case 23:
- event.key = kbCtrlF11;
- break;
- case 24:
- event.key = kbCtrlF12;
- break;
- default:
- // Unknown
- return null;
- }
- break;
-
- default:
- // Unknown
- return null;
- }
-
- // All OK, return a keypress
- return event;
+ 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;
+ }
+
+ switch (key) {
+ case 1:
+ return new TKeypressEvent(kbHome, alt, ctrl, shift);
+ case 2:
+ return new TKeypressEvent(kbIns, alt, ctrl, shift);
+ case 3:
+ return new TKeypressEvent(kbDel, alt, ctrl, shift);
+ case 4:
+ return new TKeypressEvent(kbEnd, alt, ctrl, shift);
+ case 5:
+ return new TKeypressEvent(kbPgUp, alt, ctrl, shift);
+ case 6:
+ return new TKeypressEvent(kbPgDn, alt, ctrl, shift);
+ case 15:
+ return new TKeypressEvent(kbF5, alt, ctrl, shift);
+ case 17:
+ return new TKeypressEvent(kbF6, alt, ctrl, shift);
+ case 18:
+ return new TKeypressEvent(kbF7, alt, ctrl, shift);
+ case 19:
+ return new TKeypressEvent(kbF8, alt, ctrl, shift);
+ case 20:
+ return new TKeypressEvent(kbF9, alt, ctrl, shift);
+ case 21:
+ return new TKeypressEvent(kbF10, alt, ctrl, shift);
+ case 23:
+ return new TKeypressEvent(kbF11, alt, ctrl, shift);
+ case 24:
+ return new TKeypressEvent(kbF12, alt, ctrl, shift);
+ default:
+ // Unknown
+ return null;
+ }
}
/**
@@ -590,954 +518,1108 @@ public class ECMA48Terminal {
* @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
*/
private TInputEvent parseMouse() {
- int buttons = params.get(0).charAt(0) - 32;
- int x = params.get(0).charAt(1) - 32 - 1;
- int y = params.get(0).charAt(2) - 32 - 1;
-
- // Clamp X and Y to the physical screen coordinates.
- if (x >= windowResize.width) {
- x = windowResize.width - 1;
- }
- if (y >= windowResize.height) {
- y = windowResize.height - 1;
- }
-
- TMouseEvent event = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN);
- event.x = x;
- event.y = y;
- event.absoluteX = x;
- event.absoluteY = y;
-
- // System.err.printf("buttons: %04x\r\n", buttons);
-
- switch (buttons) {
- case 0:
- event.mouse1 = true;
- mouse1 = true;
- break;
- case 1:
- event.mouse2 = true;
- mouse2 = true;
- break;
- case 2:
- event.mouse3 = true;
- mouse3 = true;
- break;
- case 3:
- // Release or Move
- if (!mouse1 && !mouse2 && !mouse3) {
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- } else {
- event.type = TMouseEvent.Type.MOUSE_UP;
- }
- if (mouse1) {
- mouse1 = false;
- event.mouse1 = true;
- }
- if (mouse2) {
- mouse2 = false;
- event.mouse2 = true;
- }
- if (mouse3) {
- mouse3 = false;
- event.mouse3 = true;
- }
- break;
-
- case 32:
- // Dragging with mouse1 down
- event.mouse1 = true;
- mouse1 = true;
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
-
- case 33:
- // Dragging with mouse2 down
- event.mouse2 = true;
- mouse2 = true;
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
-
- case 34:
- // Dragging with mouse3 down
- event.mouse3 = true;
- mouse3 = true;
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
-
- case 96:
- // Dragging with mouse2 down after wheelUp
- event.mouse2 = true;
- mouse2 = true;
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
-
- case 97:
- // Dragging with mouse2 down after wheelDown
- event.mouse2 = true;
- mouse2 = true;
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
-
- case 64:
- event.mouseWheelUp = true;
- break;
-
- case 65:
- event.mouseWheelDown = true;
- break;
-
- default:
- // Unknown, just make it motion
- event.type = TMouseEvent.Type.MOUSE_MOTION;
- break;
- }
- return event;
+ int buttons = params.get(0).charAt(0) - 32;
+ int x = params.get(0).charAt(1) - 32 - 1;
+ int y = params.get(0).charAt(2) - 32 - 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;
+
+ // System.err.printf("buttons: %04x\r\n", buttons);
+
+ switch (buttons) {
+ case 0:
+ eventMouse1 = true;
+ mouse1 = true;
+ break;
+ case 1:
+ eventMouse2 = true;
+ mouse2 = true;
+ break;
+ case 2:
+ eventMouse3 = true;
+ mouse3 = true;
+ break;
+ case 3:
+ // Release or Move
+ if (!mouse1 && !mouse2 && !mouse3) {
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ } else {
+ eventType = TMouseEvent.Type.MOUSE_UP;
+ }
+ if (mouse1) {
+ mouse1 = false;
+ eventMouse1 = true;
+ }
+ if (mouse2) {
+ mouse2 = false;
+ eventMouse2 = true;
+ }
+ if (mouse3) {
+ mouse3 = false;
+ eventMouse3 = true;
+ }
+ break;
+
+ case 32:
+ // Dragging with mouse1 down
+ eventMouse1 = true;
+ mouse1 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 33:
+ // Dragging with mouse2 down
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 34:
+ // Dragging with mouse3 down
+ eventMouse3 = true;
+ mouse3 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 96:
+ // Dragging with mouse2 down after wheelUp
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 97:
+ // Dragging with mouse2 down after wheelDown
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 64:
+ eventMouseWheelUp = true;
+ break;
+
+ case 65:
+ eventMouseWheelDown = true;
+ break;
+
+ default:
+ // Unknown, just make it motion
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+ }
+ return new TMouseEvent(eventType, x, y, x, y,
+ eventMouse1, eventMouse2, eventMouse3,
+ eventMouseWheelUp, eventMouseWheelDown);
}
/**
- * Parses the next character of input to see if an InputEvent is
- * fully here.
+ * Produce mouse events based on "Any event tracking" and SGR
+ * coordinates. See
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
*
- * Params:
- * ch = Unicode code point
- * noChar = if true, ignore ch. This is currently used to
- * return a bare ESC and RESIZE events.
+ * @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:
- * 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 getEvents(final List queue) {
+ synchronized (eventQueue) {
+ if (eventQueue.size() > 0) {
+ synchronized (queue) {
+ queue.addAll(eventQueue);
+ }
+ eventQueue.clear();
+ }
+ }
+ }
+
+ /**
+ * Return any events in the IO queue due to timeout.
+ *
+ * @param queue list to append new events to
+ */
+ private void getIdleEvents(final List queue) {
+ Date now = new Date();
+
+ // Check for new window size
+ long windowSizeDelay = now.getTime() - windowSizeTime;
+ if (windowSizeDelay > 1000) {
+ sessionInfo.queryWindowSize();
+ int newWidth = sessionInfo.getWindowWidth();
+ int newHeight = sessionInfo.getWindowHeight();
+ if ((newWidth != windowResize.getWidth())
+ || (newHeight != windowResize.getHeight())
+ ) {
+ TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ newWidth, newHeight);
+ windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ newWidth, newHeight);
+ queue.add(event);
+ }
+ windowSizeTime = now.getTime();
+ }
+
+ // ESCDELAY type timeout
+ if (state == ParseState.ESCAPE) {
+ long escDelay = now.getTime() - escapeTime;
+ if (escDelay > 100) {
+ // After 0.1 seconds, assume a true escape character
+ queue.add(controlChar((char)0x1B, false));
+ reset();
+ }
+ }
}
/**
* 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();
-
- TKeypressEvent keypress;
- Date now = new Date();
-
- // ESCDELAY type timeout
- if (state == ParseState.ESCAPE) {
- long escDelay = now.getTime() - escapeTime;
- if (escDelay > 250) {
- // After 0.25 seconds, assume a true escape character
- events.add(controlChar((char)0x1B));
- reset();
- }
- }
-
- 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) {
- case GROUND:
-
- if (ch == 0x1B) {
- state = ParseState.ESCAPE;
- escapeTime = now.getTime();
- return events;
- }
-
- if (ch <= 0x1F) {
- // Control character
- events.add(controlChar(ch));
- reset();
- return events;
- }
-
- if (ch >= 0x20) {
- // Normal character
- keypress = new TKeypressEvent();
- keypress.key.isKey = false;
- keypress.key.ch = ch;
- events.add(keypress);
- reset();
- return events;
- }
-
- break;
-
- case ESCAPE:
- if (ch <= 0x1F) {
- // ALT-Control character
- keypress = controlChar(ch);
- keypress.key.alt = true;
- events.add(keypress);
- reset();
- return events;
- }
-
- if (ch == 'O') {
- // This will be one of the function keys
- state = ParseState.ESCAPE_INTERMEDIATE;
- return events;
- }
-
- // '[' goes to CSI_ENTRY
- if (ch == '[') {
- state = ParseState.CSI_ENTRY;
- return events;
- }
-
- // Everything else is assumed to be Alt-keystroke
- keypress = new TKeypressEvent();
- keypress.key.isKey = false;
- keypress.key.ch = ch;
- keypress.key.alt = true;
- if ((ch >= 'A') && (ch <= 'Z')) {
- keypress.key.shift = true;
- }
- events.add(keypress);
- reset();
- return events;
-
- case ESCAPE_INTERMEDIATE:
- if ((ch >= 'P') && (ch <= 'S')) {
- // Function key
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- switch (ch) {
- case 'P':
- keypress.key.fnKey = TKeypress.F1;
- break;
- case 'Q':
- keypress.key.fnKey = TKeypress.F2;
- break;
- case 'R':
- keypress.key.fnKey = TKeypress.F3;
- break;
- case 'S':
- keypress.key.fnKey = TKeypress.F4;
- break;
- default:
- break;
- }
- events.add(keypress);
- reset();
- return events;
- }
-
- // Unknown keystroke, ignore
- reset();
- return events;
-
- case CSI_ENTRY:
- // Numbers - parameter values
- if ((ch >= '0') && (ch <= '9')) {
- params.set(paramI, params.get(paramI) + ch);
- state = ParseState.CSI_PARAM;
- return events;
- }
- // Parameter separator
- if (ch == ';') {
- paramI++;
- params.set(paramI, "");
- return events;
- }
-
- if ((ch >= 0x30) && (ch <= 0x7E)) {
- switch (ch) {
- case 'A':
- // Up
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.UP;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'B':
- // Down
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.DOWN;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'C':
- // Right
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.RIGHT;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'D':
- // Left
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.LEFT;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'H':
- // Home
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.HOME;
- events.add(keypress);
- reset();
- return events;
- case 'F':
- // End
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.END;
- events.add(keypress);
- reset();
- return events;
- case 'Z':
- // CBT - Cursor backward X tab stops (default 1)
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.BTAB;
- events.add(keypress);
- reset();
- return events;
- case 'M':
- // Mouse position
- state = ParseState.MOUSE;
- return events;
- default:
- break;
- }
- }
-
- // Unknown keystroke, ignore
- reset();
- return events;
-
- case CSI_PARAM:
- // Numbers - parameter values
- if ((ch >= '0') && (ch <= '9')) {
- params.set(paramI, params.get(paramI) + ch);
- state = ParseState.CSI_PARAM;
- return events;
- }
- // Parameter separator
- if (ch == ';') {
- paramI++;
- params.set(paramI, "");
- return events;
- }
-
- if (ch == '~') {
- events.add(csiFnKey());
- reset();
- return events;
- }
-
- if ((ch >= 0x30) && (ch <= 0x7E)) {
- switch (ch) {
- case 'A':
- // Up
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.UP;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'B':
- // Down
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.DOWN;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'C':
- // Right
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.RIGHT;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- case 'D':
- // Left
- keypress = new TKeypressEvent();
- keypress.key.isKey = true;
- keypress.key.fnKey = TKeypress.LEFT;
- if (params.size() > 1) {
- if (params.get(1).equals("2")) {
- keypress.key.shift = true;
- }
- if (params.get(1).equals("5")) {
- keypress.key.ctrl = true;
- }
- if (params.get(1).equals("3")) {
- keypress.key.alt = true;
- }
- }
- events.add(keypress);
- reset();
- return events;
- default:
- break;
- }
- }
-
- // Unknown keystroke, ignore
- reset();
- return events;
-
- case MOUSE:
- params.set(0, params.get(paramI) + ch);
- if (params.get(0).length() == 3) {
- // We have enough to generate a mouse event
- events.add(parseMouse());
- reset();
- }
- return events;
-
- default:
- break;
- }
-
- // This "should" be impossible to reach
- return events;
+ private void processChar(final List events, final char ch) {
+
+ // ESCDELAY type timeout
+ Date now = new Date();
+ if (state == ParseState.ESCAPE) {
+ long escDelay = now.getTime() - escapeTime;
+ if (escDelay > 250) {
+ // After 0.25 seconds, assume a true escape character
+ events.add(controlChar((char)0x1B, false));
+ reset();
+ }
+ }
+
+ // TKeypress fields
+ boolean ctrl = false;
+ boolean alt = false;
+ boolean shift = false;
+
+ // System.err.printf("state: %s ch %c\r\n", state, ch);
+
+ switch (state) {
+ case GROUND:
+
+ if (ch == 0x1B) {
+ state = ParseState.ESCAPE;
+ escapeTime = now.getTime();
+ return;
+ }
+
+ if (ch <= 0x1F) {
+ // Control character
+ events.add(controlChar(ch, false));
+ reset();
+ return;
+ }
+
+ if (ch >= 0x20) {
+ // Normal character
+ events.add(new TKeypressEvent(false, 0, ch,
+ false, false, false));
+ reset();
+ return;
+ }
+
+ break;
+
+ case ESCAPE:
+ if (ch <= 0x1F) {
+ // ALT-Control character
+ events.add(controlChar(ch, true));
+ reset();
+ return;
+ }
+
+ if (ch == 'O') {
+ // This will be one of the function keys
+ state = ParseState.ESCAPE_INTERMEDIATE;
+ return;
+ }
+
+ // '[' goes to CSI_ENTRY
+ if (ch == '[') {
+ state = ParseState.CSI_ENTRY;
+ return;
+ }
+
+ // Everything else is assumed to be Alt-keystroke
+ if ((ch >= 'A') && (ch <= 'Z')) {
+ shift = true;
+ }
+ alt = true;
+ events.add(new TKeypressEvent(false, 0, ch, alt, ctrl, shift));
+ reset();
+ return;
+
+ case ESCAPE_INTERMEDIATE:
+ if ((ch >= 'P') && (ch <= 'S')) {
+ // Function key
+ switch (ch) {
+ case 'P':
+ events.add(new TKeypressEvent(kbF1));
+ break;
+ case 'Q':
+ events.add(new TKeypressEvent(kbF2));
+ break;
+ case 'R':
+ events.add(new TKeypressEvent(kbF3));
+ break;
+ case 'S':
+ events.add(new TKeypressEvent(kbF4));
+ break;
+ default:
+ break;
+ }
+ reset();
+ return;
+ }
+
+ // Unknown keystroke, ignore
+ reset();
+ return;
+
+ case CSI_ENTRY:
+ // Numbers - parameter values
+ if ((ch >= '0') && (ch <= '9')) {
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ state = ParseState.CSI_PARAM;
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ if ((ch >= 0x30) && (ch <= 0x7E)) {
+ 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;
+ case 'H':
+ // Home
+ events.add(new TKeypressEvent(kbHome));
+ reset();
+ return;
+ case 'F':
+ // End
+ events.add(new TKeypressEvent(kbEnd));
+ reset();
+ return;
+ case 'Z':
+ // CBT - Cursor backward X tab stops (default 1)
+ events.add(new TKeypressEvent(kbBackTab));
+ reset();
+ return;
+ case 'M':
+ // Mouse position
+ state = ParseState.MOUSE;
+ return;
+ case '<':
+ // Mouse position, SGR (1006) coordinates
+ state = ParseState.MOUSE_SGR;
+ return;
+ default:
+ break;
+ }
+ }
+
+ // Unknown keystroke, ignore
+ 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(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ state = ParseState.CSI_PARAM;
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ if (ch == '~') {
+ events.add(csiFnKey());
+ reset();
+ return;
+ }
+
+ if ((ch >= 0x30) && (ch <= 0x7E)) {
+ 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;
+ default:
+ break;
+ }
+ }
+
+ // Unknown keystroke, ignore
+ reset();
+ return;
+
+ case MOUSE:
+ 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());
+ reset();
+ }
+ return;
+
+ default:
+ break;
+ }
+
+ // This "should" be impossible to reach
+ 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.
+ * 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
- *
- * 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) {
- return "\033[?1036h\033[?1034l";
- }
- return "\033[?1036l";
+ private String xtermMetaSendsEscape(final boolean on) {
+ if (on) {
+ return "\033[?1036h\033[?1034l";
+ }
+ return "\033[?1036l";
}
/**
- * 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) {
- // Nix any trailing ';' because that resets all attributes
- while (str.endsWith(":")) {
- str = str.substring(0, str.length() - 1);
- }
- }
- return "\033[" + str + "m";
+ private 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.
- *
- * Params:
- * color = one of the Color.WHITE, Color.BLUE, etc. constants
- * foreground = if true, this is a foreground color
+ * Create a SGR parameter sequence for a single color change. Note
+ * package private access.
*
- * 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);
+ String color(final Color color, final boolean foreground) {
+ return color(color, foreground, true);
}
/**
* 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) {
-
- int ecmaColor = color.value;
-
- // Convert Color.* values to SGR numerics
- if (foreground == true) {
- ecmaColor += 30;
- } else {
- ecmaColor += 40;
- }
-
- if (header) {
- return String.format("\033[%dm", ecmaColor);
- } else {
- return String.format("%d;", ecmaColor);
- }
+ private String color(final Color color, final boolean foreground,
+ final boolean header) {
+
+ int ecmaColor = color.getValue();
+
+ // Convert Color.* values to SGR numerics
+ if (foreground) {
+ ecmaColor += 30;
+ } else {
+ ecmaColor += 40;
+ }
+
+ if (header) {
+ return String.format("\033[%dm", ecmaColor);
+ } else {
+ return String.format("%d;", ecmaColor);
+ }
}
/**
- * 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
+ * Create a SGR parameter sequence for both foreground and background
+ * color change. Note package private access.
*
- * 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);
+ String color(final Color foreColor, final Color backColor) {
+ return color(foreColor, backColor, true);
}
/**
* 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) {
+ private String color(final Color foreColor, final Color backColor,
+ final boolean header) {
- int ecmaForeColor = foreColor.value;
- int ecmaBackColor = backColor.value;
+ int ecmaForeColor = foreColor.getValue();
+ int ecmaBackColor = backColor.getValue();
- // Convert Color.* values to SGR numerics
- ecmaBackColor += 40;
- ecmaForeColor += 30;
+ // Convert Color.* values to SGR numerics
+ ecmaBackColor += 40;
+ ecmaForeColor += 30;
- if (header) {
- return String.format("\033[%d;%dm", ecmaForeColor, ecmaBackColor);
- } else {
- return String.format("%d;%d;", ecmaForeColor, ecmaBackColor);
- }
+ if (header) {
+ return String.format("\033[%d;%dm", ecmaForeColor, ecmaBackColor);
+ } else {
+ return String.format("%d;%d;", ecmaForeColor, ecmaBackColor);
+ }
}
/**
* 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.
+ * several attributes. This sequence first resets all attributes to
+ * default, then sets attributes as per the parameters. Note package
+ * private access.
*
- * 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
- *
- * 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) {
-
- int ecmaForeColor = foreColor.value;
- int ecmaBackColor = backColor.value;
-
- // Convert Color.* values to SGR numerics
- ecmaBackColor += 40;
- ecmaForeColor += 30;
-
- StringBuilder sb = new StringBuilder();
- if ( bold && reverse && blink && !underline ) {
- sb.append("\033[0;1;7;5;");
- } else if ( bold && reverse && !blink && !underline ) {
- sb.append("\033[0;1;7;");
- } else if ( !bold && reverse && blink && !underline ) {
- sb.append("\033[0;7;5;");
- } else if ( bold && !reverse && blink && !underline ) {
- sb.append("\033[0;1;5;");
- } else if ( bold && !reverse && !blink && !underline ) {
- sb.append("\033[0;1;");
- } else if ( !bold && reverse && !blink && !underline ) {
- sb.append("\033[0;7;");
- } else if ( !bold && !reverse && blink && !underline) {
- sb.append("\033[0;5;");
- } else if ( bold && reverse && blink && underline ) {
- sb.append("\033[0;1;7;5;4;");
- } else if ( bold && reverse && !blink && underline ) {
- sb.append("\033[0;1;7;4;");
- } else if ( !bold && reverse && blink && underline ) {
- sb.append("\033[0;7;5;4;");
- } else if ( bold && !reverse && blink && underline ) {
- sb.append("\033[0;1;5;4;");
- } else if ( bold && !reverse && !blink && underline ) {
- sb.append("\033[0;1;4;");
- } else if ( !bold && reverse && !blink && underline ) {
- sb.append("\033[0;7;4;");
- } else if ( !bold && !reverse && blink && underline) {
- sb.append("\033[0;5;4;");
- } else if ( !bold && !reverse && !blink && underline) {
- sb.append("\033[0;4;");
- } else {
- assert(!bold && !reverse && !blink && !underline);
- sb.append("\033[0;");
- }
- sb.append(String.format("%d;%dm", ecmaForeColor, ecmaBackColor));
- return sb.toString();
+ String color(final Color foreColor, final Color backColor,
+ final boolean bold, final boolean reverse, final boolean blink,
+ final boolean underline) {
+
+ int ecmaForeColor = foreColor.getValue();
+ int ecmaBackColor = backColor.getValue();
+
+ // Convert Color.* values to SGR numerics
+ ecmaBackColor += 40;
+ ecmaForeColor += 30;
+
+ StringBuilder sb = new StringBuilder();
+ if ( bold && reverse && blink && !underline ) {
+ sb.append("\033[0;1;7;5;");
+ } else if ( bold && reverse && !blink && !underline ) {
+ sb.append("\033[0;1;7;");
+ } else if ( !bold && reverse && blink && !underline ) {
+ sb.append("\033[0;7;5;");
+ } else if ( bold && !reverse && blink && !underline ) {
+ sb.append("\033[0;1;5;");
+ } else if ( bold && !reverse && !blink && !underline ) {
+ sb.append("\033[0;1;");
+ } else if ( !bold && reverse && !blink && !underline ) {
+ sb.append("\033[0;7;");
+ } else if ( !bold && !reverse && blink && !underline) {
+ sb.append("\033[0;5;");
+ } else if ( bold && reverse && blink && underline ) {
+ sb.append("\033[0;1;7;5;4;");
+ } else if ( bold && reverse && !blink && underline ) {
+ sb.append("\033[0;1;7;4;");
+ } else if ( !bold && reverse && blink && underline ) {
+ sb.append("\033[0;7;5;4;");
+ } else if ( bold && !reverse && blink && underline ) {
+ sb.append("\033[0;1;5;4;");
+ } else if ( bold && !reverse && !blink && underline ) {
+ sb.append("\033[0;1;4;");
+ } else if ( !bold && reverse && !blink && underline ) {
+ sb.append("\033[0;7;4;");
+ } else if ( !bold && !reverse && blink && underline) {
+ sb.append("\033[0;5;4;");
+ } else if ( !bold && !reverse && !blink && underline) {
+ sb.append("\033[0;4;");
+ } else {
+ assert (!bold && !reverse && !blink && !underline);
+ sb.append("\033[0;");
+ }
+ sb.append(String.format("%d;%dm", ecmaForeColor, ecmaBackColor));
+ return sb.toString();
}
/**
* 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) {
- return "\033[7m";
- }
- return "\033[27m";
+ private 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.
*
- * 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);
+ String normal() {
+ return normal(true);
}
/**
* 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) {
- return "\033[0;37;40m";
- }
- return "0;37;40";
+ private String normal(final boolean header) {
+ if (header) {
+ return "\033[0;37;40m";
+ }
+ return "0;37;40";
}
/**
* 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);
+ private String bold(final boolean on) {
+ return bold(on, true);
}
/**
* 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) {
- if (on) {
- return "\033[1m";
- }
- return "\033[22m";
- }
- if (on) {
- return "1;";
- }
- return "22;";
+ private 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.
*
- * 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);
+ private String blink(final boolean on) {
+ return blink(on, true);
}
/**
* 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) {
- if (on) {
- return "\033[5m";
- }
- return "\033[25m";
- }
- if (on) {
- return "5;";
- }
- return "25;";
+ private 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.
- *
- * Params:
- * on = if true, turn on underline
+ * Create a SGR parameter sequence for enabling underline / underscored
+ * text.
*
- * 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) {
- return "\033[4m";
- }
- return "\033[24m";
+ private String underline(final boolean on) {
+ if (on) {
+ return "\033[4m";
+ }
+ return "\033[24m";
}
/**
- * Create a SGR parameter sequence for enabling the visible cursor.
- *
- * Params:
- * on = if true, turn on cursor
+ * Create a SGR parameter sequence for enabling the visible cursor. Note
+ * package private access.
*
- * 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)) {
- cursorOn = true;
- return "\033[?25h";
- }
- if (!on && (cursorOn == true)) {
- cursorOn = false;
- return "\033[?25l";
- }
- return "";
+ String cursor(final boolean on) {
+ if (on && !cursorOn) {
+ cursorOn = true;
+ return "\033[?25h";
+ }
+ if (!on && cursorOn) {
+ cursorOn = false;
+ return "\033[?25l";
+ }
+ return "";
}
/**
* 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";
+ public String clearAll() {
+ return "\033[0;37;40m\033[2J";
}
/**
* 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.
*
- * 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";
+ 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.
*
- * 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";
+ private 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.
*
- * 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";
+ private String clearLine() {
+ return "\033[0;37;40m\033[2K";
}
/**
* 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";
+ private String home() {
+ return "\033[H";
}
/**
- * Move the cursor to (x, y).
+ * Move the cursor to (x, y). Note package private access.
*
- * 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);
+ 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
+ * Tell (u)xterm that we want to receive mouse events based on "Any event
+ * 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
*
- * Finally, this sets the alternate screen buffer.
- *
- * Params:
- * on = if true, enable mouse report
+ * Note that this also sets the alternate/primary screen buffer.
*
- * 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
+ */
+ private String mouse(final boolean on) {
+ if (on) {
+ return "\033[?1003;1005;1006h\033[?1049h";
+ }
+ return "\033[?1003;1006;1005l\033[?1049l";
+ }
+
+ /**
+ * Read function runs on a separate thread.
*/
- static public String mouse(boolean on) {
- if (on) {
- return "\033[?1003;1005h\033[?1049h";
- }
- return "\033[?1003;1005l\033[?1049l";
+ 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 && !stopReaderThread) {
+ 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, readBuffer.length);
+ // 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);
+ }
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ events.clear();
+ }
+ }
+ }
+ } else {
+ getIdleEvents(events);
+ if (events.size() > 0) {
+ synchronized (eventQueue) {
+ eventQueue.addAll(events);
+ }
+ events.clear();
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+
+ // Wait 10 millis for more data
+ Thread.sleep(10);
+ }
+ // 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();
}
}