+++ /dev/null
-/*
- * Jexer - Java Text User Interface
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2017 Kevin Lamonte
- *
- * 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:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * 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
- */
-package jexer.backend;
-
-import java.io.BufferedReader;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.LinkedList;
-
-import jexer.bits.Cell;
-import jexer.bits.CellAttributes;
-import jexer.bits.Color;
-import jexer.event.TInputEvent;
-import jexer.event.TKeypressEvent;
-import jexer.event.TMouseEvent;
-import jexer.event.TResizeEvent;
-import static jexer.TKeypress.*;
-
-/**
- * This class reads keystrokes and mouse events and emits output to ANSI
- * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, etc.
- */
-public final class ECMA48Terminal extends LogicalScreen
- implements TerminalReader, Runnable {
-
- /**
- * Emit debugging to stderr.
- */
- private boolean debugToStderr = false;
-
- /**
- * 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 SessionInfo sessionInfo;
-
- /**
- * Getter for sessionInfo.
- *
- * @return the SessionInfo
- */
- public SessionInfo getSessionInfo() {
- return sessionInfo;
- }
-
- /**
- * The event queue, filled up by a thread reading on input.
- */
- private List<TInputEvent> eventQueue;
-
- /**
- * If true, we want the reader thread to exit gracefully.
- */
- private boolean stopReaderThread;
-
- /**
- * The reader thread.
- */
- private Thread readerThread;
-
- /**
- * Parameters being collected. E.g. if the string is \033[1;3m, then
- * params[0] will be 1 and params[1] will be 3.
- */
- private ArrayList<String> params;
-
- /**
- * States in the input parser.
- */
- private enum ParseState {
- GROUND,
- ESCAPE,
- ESCAPE_INTERMEDIATE,
- CSI_ENTRY,
- CSI_PARAM,
- MOUSE,
- MOUSE_SGR,
- }
-
- /**
- * 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.
- */
- 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.
- */
- private boolean mouse1;
-
- /**
- * true if mouse2 was down. Used to report mouse2 on the release event.
- */
- private boolean mouse2;
-
- /**
- * true if mouse3 was down. Used to report mouse3 on the release event.
- */
- private boolean mouse3;
-
- /**
- * Cache the cursor visibility value so we only emit the sequence when we
- * need to.
- */
- private boolean cursorOn = true;
-
- /**
- * Cache the last window size to figure out if a TResizeEvent needs to be
- * generated.
- */
- private TResizeEvent windowResize = null;
-
- /**
- * If true, then we changed System.in and need to change it back.
- */
- private boolean setRawMode;
-
- /**
- * The terminal's input. If an InputStream is not specified in the
- * 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
- * UTF-8 encoding.
- */
- private PrintWriter output;
-
- /**
- * The listening object that run() wakes up on new input.
- */
- private Object listener;
-
- /**
- * Set listener to a different Object.
- *
- * @param listener the new listening object that run() wakes up on new
- * input
- */
- public void setListener(final Object listener) {
- this.listener = listener;
- }
-
- /**
- * Get the output writer.
- *
- * @return the Writer
- */
- public PrintWriter getOutput() {
- return output;
- }
-
- /**
- * 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.
- *
- * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
- */
- private void sttyCooked() {
- doStty(false);
- }
-
- /**
- * Call 'stty' to set raw mode.
- *
- * <p>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);
- }
-
- /**
- * Call 'stty' to set raw or cooked mode.
- *
- * @param mode if true, set raw mode, otherwise set cooked mode
- */
- 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().
- *
- * @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
- * mode. input is always converted to a Reader with UTF-8 encoding.
- * @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(final Object listener, final InputStream input,
- final OutputStream output) throws UnsupportedEncodingException {
-
- resetParser();
- 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");
-
- 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 (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));
- this.output.flush();
-
- // Query the screen size
- sessionInfo.queryWindowSize();
- setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
-
- // 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;
- } else {
- doRgbColor = false;
- }
- }
-
- // Spin up the input reader
- eventQueue = new LinkedList<TInputEvent>();
- readerThread = new Thread(this);
- readerThread.start();
-
- // Clear the screen
- this.output.write(clearAll());
- this.output.flush();
- }
-
- /**
- * 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");
- }
- resetParser();
- 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();
-
- // Query the screen size
- sessionInfo.queryWindowSize();
- setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
-
- // 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;
- } else {
- doRgbColor = false;
- }
- }
-
- // Spin up the input reader
- eventQueue = new LinkedList<TInputEvent>();
- readerThread = new Thread(this);
- readerThread.start();
-
- // Clear the screen
- this.output.write(clearAll());
- this.output.flush();
- }
-
- /**
- * 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.
- */
- public void closeTerminal() {
-
- // 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.
- */
- public void flush() {
- output.flush();
- }
-
- /**
- * Perform a somewhat-optimal rendering of a line.
- *
- * @param y row coordinate. 0 is the top-most row.
- * @param sb StringBuilder to write escape sequences to
- * @param lastAttr cell attributes from the last call to flushLine
- */
- private void flushLine(final int y, final StringBuilder sb,
- CellAttributes lastAttr) {
-
- int lastX = -1;
- int textEnd = 0;
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- if (!lCell.isBlank()) {
- textEnd = x;
- }
- }
- // Push textEnd to first column beyond the text area
- textEnd++;
-
- // DEBUG
- // reallyCleared = true;
-
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- if (!lCell.equals(pCell) || reallyCleared) {
-
- if (debugToStderr) {
- System.err.printf("\n--\n");
- System.err.printf(" Y: %d X: %d\n", y, x);
- System.err.printf(" lCell: %s\n", lCell);
- System.err.printf(" pCell: %s\n", pCell);
- System.err.printf(" ==== \n");
- }
-
- if (lastAttr == null) {
- lastAttr = new CellAttributes();
- sb.append(normal());
- }
-
- // Place the cell
- if ((lastX != (x - 1)) || (lastX == -1)) {
- // Advancing at least one cell, or the first gotoXY
- sb.append(gotoXY(x, y));
- }
-
- assert (lastAttr != null);
-
- if ((x == textEnd) && (textEnd < width - 1)) {
- assert (lCell.isBlank());
-
- for (int i = x; i < width; i++) {
- assert (logical[i][y].isBlank());
- // Physical is always updated
- physical[i][y].reset();
- }
-
- // Clear remaining line
- sb.append(clearRemainingLine());
- lastAttr.reset();
- return;
- }
-
- // Now emit only the modified attributes
- if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
- // Both colors changed, attributes the same
- sb.append(color(lCell.isBold(),
- lCell.getForeColor(), lCell.getBackColor()));
-
- if (debugToStderr) {
- System.err.printf("1 Change only fore/back colors\n");
- }
- } else if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() != lastAttr.isBold())
- && (lCell.isReverse() != lastAttr.isReverse())
- && (lCell.isUnderline() != lastAttr.isUnderline())
- && (lCell.isBlink() != lastAttr.isBlink())
- ) {
- // Everything is different
- sb.append(color(lCell.getForeColor(),
- lCell.getBackColor(),
- lCell.isBold(), lCell.isReverse(),
- lCell.isBlink(),
- lCell.isUnderline()));
-
- if (debugToStderr) {
- System.err.printf("2 Set all attributes\n");
- }
- } else if ((lCell.getForeColor() != lastAttr.getForeColor())
- && (lCell.getBackColor() == lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
-
- // Attributes same, foreColor different
- sb.append(color(lCell.isBold(),
- lCell.getForeColor(), true));
-
- if (debugToStderr) {
- System.err.printf("3 Change foreColor\n");
- }
- } else if ((lCell.getForeColor() == lastAttr.getForeColor())
- && (lCell.getBackColor() != lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
- // Attributes same, backColor different
- sb.append(color(lCell.isBold(),
- lCell.getBackColor(), false));
-
- if (debugToStderr) {
- System.err.printf("4 Change backColor\n");
- }
- } else if ((lCell.getForeColor() == lastAttr.getForeColor())
- && (lCell.getBackColor() == lastAttr.getBackColor())
- && (lCell.isBold() == lastAttr.isBold())
- && (lCell.isReverse() == lastAttr.isReverse())
- && (lCell.isUnderline() == lastAttr.isUnderline())
- && (lCell.isBlink() == lastAttr.isBlink())
- ) {
-
- // All attributes the same, just print the char
- // NOP
-
- if (debugToStderr) {
- System.err.printf("5 Only emit character\n");
- }
- } else {
- // Just reset everything again
- sb.append(color(lCell.getForeColor(),
- lCell.getBackColor(),
- lCell.isBold(),
- lCell.isReverse(),
- lCell.isBlink(),
- lCell.isUnderline()));
-
- if (debugToStderr) {
- System.err.printf("6 Change all attributes\n");
- }
- }
- // Emit the character
- sb.append(lCell.getChar());
-
- // Save the last rendered cell
- lastX = x;
- lastAttr.setTo(lCell);
-
- // Physical is always updated
- physical[x][y].setTo(lCell);
-
- } // if (!lCell.equals(pCell) || (reallyCleared == true))
-
- } // for (int x = 0; x < width; x++)
- }
-
- /**
- * Render the screen to a string that can be emitted to something that
- * knows how to process ECMA-48/ANSI X3.64 escape sequences.
- *
- * @return escape sequences string that provides the updates to the
- * physical screen
- */
- private String flushString() {
- if (!dirty) {
- assert (!reallyCleared);
- return "";
- }
-
- CellAttributes attr = null;
-
- StringBuilder sb = new StringBuilder();
- if (reallyCleared) {
- attr = new CellAttributes();
- sb.append(clearAll());
- }
-
- for (int y = 0; y < height; y++) {
- flushLine(y, sb, attr);
- }
-
- dirty = false;
- reallyCleared = false;
-
- String result = sb.toString();
- if (debugToStderr) {
- System.err.printf("flushString(): %s\n", result);
- }
- return result;
- }
-
- /**
- * Push the logical screen to the physical device.
- */
- @Override
- public void flushPhysical() {
- String result = flushString();
- if ((cursorVisible)
- && (cursorY <= height - 1)
- && (cursorX <= width - 1)
- ) {
- result += cursor(true);
- result += gotoXY(cursorX, cursorY);
- } else {
- result += cursor(false);
- }
- output.write(result);
- flush();
- }
-
- /**
- * Set the window title.
- *
- * @param title the new title
- */
- public void setTitle(final String title) {
- output.write(getSetTitleString(title));
- flush();
- }
-
- /**
- * Reset keyboard/mouse input parser.
- */
- private void resetParser() {
- state = ParseState.GROUND;
- params = new ArrayList<String>();
- params.clear();
- params.add("");
- }
-
- /**
- * Produce a control character or one of the special ones (ENTER, TAB,
- * etc.).
- *
- * @param ch Unicode code point
- * @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(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);
- }
- }
-
- /**
- * Produce special key from CSI Pn ; Pm ; ... ~
- *
- * @return one KEYPRESS event representing a special key
- */
- private TInputEvent csiFnKey() {
- int key = 0;
- if (params.size() > 0) {
- key = Integer.parseInt(params.get(0));
- }
- boolean alt = false;
- boolean ctrl = false;
- boolean shift = false;
- if (params.size() > 1) {
- shift = csiIsShift(params.get(1));
- alt = csiIsAlt(params.get(1));
- ctrl = csiIsCtrl(params.get(1));
- }
-
- 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;
- }
- }
-
- /**
- * Produce mouse events based on "Any event tracking" and UTF-8
- * coordinates. See
- * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
- *
- * @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.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);
- }
-
- /**
- * 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.
- *
- * @param queue list to append new events to
- */
- public void getEvents(final List<TInputEvent> 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<TInputEvent> 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));
- resetParser();
- }
- }
- }
-
- /**
- * 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.
- *
- * @param events list to append new events to
- * @param ch Unicode code point
- */
- private void processChar(final List<TInputEvent> 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));
- resetParser();
- }
- }
-
- // 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));
- resetParser();
- return;
- }
-
- if (ch >= 0x20) {
- // Normal character
- events.add(new TKeypressEvent(false, 0, ch,
- false, false, false));
- resetParser();
- return;
- }
-
- break;
-
- case ESCAPE:
- if (ch <= 0x1F) {
- // ALT-Control character
- events.add(controlChar(ch, true));
- resetParser();
- 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));
- resetParser();
- 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;
- }
- resetParser();
- return;
- }
-
- // Unknown keystroke, ignore
- resetParser();
- 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
- events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
- resetParser();
- return;
- case 'B':
- // Down
- events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
- resetParser();
- return;
- case 'C':
- // Right
- events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
- resetParser();
- return;
- case 'D':
- // Left
- events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
- resetParser();
- return;
- case 'H':
- // Home
- events.add(new TKeypressEvent(kbHome));
- resetParser();
- return;
- case 'F':
- // End
- events.add(new TKeypressEvent(kbEnd));
- resetParser();
- return;
- case 'Z':
- // CBT - Cursor backward X tab stops (default 1)
- events.add(new TKeypressEvent(kbBackTab));
- resetParser();
- 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
- resetParser();
- 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);
- }
- resetParser();
- return;
- case 'm':
- // Generate a mouse release event
- event = parseMouseSGR(true);
- if (event != null) {
- events.add(event);
- }
- resetParser();
- return;
- default:
- break;
- }
-
- // Unknown keystroke, ignore
- resetParser();
- 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());
- resetParser();
- return;
- }
-
- if ((ch >= 0x30) && (ch <= 0x7E)) {
- switch (ch) {
- case 'A':
- // Up
- if (params.size() > 1) {
- shift = csiIsShift(params.get(1));
- alt = csiIsAlt(params.get(1));
- ctrl = csiIsCtrl(params.get(1));
- }
- events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
- resetParser();
- return;
- case 'B':
- // Down
- if (params.size() > 1) {
- shift = csiIsShift(params.get(1));
- alt = csiIsAlt(params.get(1));
- ctrl = csiIsCtrl(params.get(1));
- }
- events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
- resetParser();
- return;
- case 'C':
- // Right
- if (params.size() > 1) {
- shift = csiIsShift(params.get(1));
- alt = csiIsAlt(params.get(1));
- ctrl = csiIsCtrl(params.get(1));
- }
- events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
- resetParser();
- return;
- case 'D':
- // Left
- if (params.size() > 1) {
- shift = csiIsShift(params.get(1));
- alt = csiIsAlt(params.get(1));
- ctrl = csiIsCtrl(params.get(1));
- }
- events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
- resetParser();
- 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));
- resetParser();
- 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));
- resetParser();
- return;
- default:
- break;
- }
- }
-
- // Unknown keystroke, ignore
- resetParser();
- 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());
- resetParser();
- }
- 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.
- *
- * @param on if true, enable metaSendsEscape
- * @return the string to emit to xterm
- */
- private String xtermMetaSendsEscape(final boolean on) {
- if (on) {
- return "\033[?1036h\033[?1034l";
- }
- return "\033[?1036l";
- }
-
- /**
- * Create an xterm OSC sequence to change the window title.
- *
- * @param title the new title
- * @return the string to emit to xterm
- */
- private String getSetTitleString(final String title) {
- return "\033]2;" + title + "\007";
- }
-
- /**
- * Create a SGR 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 ANSI / ECMA-style terminal,
- * e.g. "\033[42m"
- */
- private 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);
- }
-
- /**
- * Create a SGR parameter sequence for a single color change.
- *
- * @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"
- */
- 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.
- *
- * @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"
- */
- private String color(final boolean bold, final Color foreColor,
- final Color backColor) {
- return color(foreColor, backColor, true) +
- rgbColor(bold, foreColor, backColor);
- }
-
- /**
- * Create a SGR parameter sequence for both foreground and
- * background color change.
- *
- * @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"
- */
- private String color(final Color foreColor, final Color backColor,
- final boolean header) {
-
- int ecmaForeColor = foreColor.getValue();
- int ecmaBackColor = backColor.getValue();
-
- // 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);
- }
- }
-
- /**
- * 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.
- *
- * @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"
- */
- private 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));
- sb.append(rgbColor(bold, foreColor, backColor));
- return sb.toString();
- }
-
- /**
- * Create a SGR parameter sequence to reset to defaults.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal,
- * e.g. "\033[0m"
- */
- private String normal() {
- return normal(true) + rgbColor(false, Color.WHITE, Color.BLACK);
- }
-
- /**
- * Create a SGR parameter sequence to reset to defaults.
- *
- * @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"
- */
- private String normal(final boolean header) {
- if (header) {
- return "\033[0;37;40m";
- }
- return "0;37;40";
- }
-
- /**
- * Create a SGR parameter sequence for enabling the visible cursor.
- *
- * @param on if true, turn on cursor
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- private 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.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- private 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.
- *
- * @return the string to emit to an ANSI / ECMA-style terminal
- */
- private String clearRemainingLine() {
- return "\033[0;37;40m\033[K";
- }
-
- /**
- * Move the cursor to (x, y).
- *
- * @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
- */
- private 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", 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.
- *
- * @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[?1002;1003;1005;1006h\033[?1049h";
- }
- return "\033[?1002;1003;1006;1005l\033[?1049l";
- }
-
- /**
- * Read function runs on a separate thread.
- */
- 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<TInputEvent> events = new LinkedList<TInputEvent>();
-
- 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);
- }
- getIdleEvents(events);
- if (events.size() > 0) {
- // Add to the queue for the backend thread to
- // be able to obtain.
- synchronized (eventQueue) {
- eventQueue.addAll(events);
- }
- if (listener != null) {
- synchronized (listener) {
- listener.notifyAll();
- }
- }
- events.clear();
- }
- }
- } else {
- getIdleEvents(events);
- if (events.size() > 0) {
- synchronized (eventQueue) {
- eventQueue.addAll(events);
- }
- events.clear();
- if (listener != null) {
- 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();
- }
-
-}