import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import java.util.LinkedList;
public final class ECMA48Terminal extends LogicalScreen
implements TerminalReader, Runnable {
+ // ------------------------------------------------------------------------
+ // Constants --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * States in the input parser.
+ */
+ private enum ParseState {
+ GROUND,
+ ESCAPE,
+ ESCAPE_INTERMEDIATE,
+ CSI_ENTRY,
+ CSI_PARAM,
+ MOUSE,
+ MOUSE_SGR,
+ }
+
+ // ------------------------------------------------------------------------
+ // Variables --------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
/**
* Emit debugging to stderr.
*/
*/
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.
*/
* 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,
- }
+ private List<String> params;
/**
* Current parsing state.
*/
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);
- }
- }
+ // ------------------------------------------------------------------------
+ // Constructors -----------------------------------------------------------
+ // ------------------------------------------------------------------------
/**
- * Call 'stty' to set cooked mode.
+ * Constructor sets up state for getEvent().
*
- * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
+ * @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.
+ * @param windowWidth the number of text columns to start with
+ * @param windowHeight the number of text rows to start with
+ * @throws UnsupportedEncodingException if an exception is thrown when
+ * creating the InputStreamReader
*/
- private void sttyCooked() {
- doStty(false);
- }
+ public ECMA48Terminal(final Object listener, final InputStream input,
+ final OutputStream output, final int windowWidth,
+ final int windowHeight) throws UnsupportedEncodingException {
- /**
- * 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);
- }
+ this(listener, input, output);
- /**
- * 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();
- }
+ // Send dtterm/xterm sequences, which will probably not work because
+ // allowWindowOps is defaulted to false.
+ String resizeString = String.format("\033[8;%d;%dt", windowHeight,
+ windowWidth);
+ this.output.write(resizeString);
+ this.output.flush();
}
/**
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());
if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
doRgbColor = true;
+ } else {
+ doRgbColor = false;
}
}
readerThread = new Thread(this);
readerThread.start();
- // Query the screen size
- setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
-
// Clear the screen
this.output.write(clearAll());
this.output.flush();
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());
if (System.getProperty("jexer.ECMA48.rgbColor") != null) {
if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
doRgbColor = true;
+ } else {
+ doRgbColor = false;
}
}
readerThread = new Thread(this);
readerThread.start();
- // Query the screen size
- setDimensions(sessionInfo.getWindowWidth(),
- sessionInfo.getWindowHeight());
-
// Clear the screen
this.output.write(clearAll());
this.output.flush();
this(listener, input, reader, writer, false);
}
+ // ------------------------------------------------------------------------
+ // LogicalScreen ----------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Set the window title.
+ *
+ * @param title the new title
+ */
+ @Override
+ public void setTitle(final String title) {
+ output.write(getSetTitleString(title));
+ flush();
+ }
+
+ /**
+ * Push the logical screen to the physical device.
+ */
+ @Override
+ public void flushPhysical() {
+ String result = flushString();
+ if ((cursorVisible)
+ && (cursorY >= 0)
+ && (cursorX >= 0)
+ && (cursorY <= height - 1)
+ && (cursorX <= width - 1)
+ ) {
+ result += cursor(true);
+ result += gotoXY(cursorX, cursorY);
+ } else {
+ result += cursor(false);
+ }
+ output.write(result);
+ flush();
+ }
+
+ // ------------------------------------------------------------------------
+ // TerminalReader ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
+
/**
* Restore terminal to normal state.
*/
}
}
+ /**
+ * 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;
+ }
+
+ // ------------------------------------------------------------------------
+ // Runnable ---------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * 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();
+
+ /*
+ System.err.printf("inputStream.available(): %d\n", n);
+ System.err.flush();
+ */
+
+ if (n > 0) {
+ if (readBuffer.length < n) {
+ // The buffer wasn't big enough, make it huger
+ readBuffer = new char[readBuffer.length * 2];
+ }
+
+ // System.err.printf("BEFORE read()\n"); System.err.flush();
+
+ int rc = input.read(readBuffer, 0, readBuffer.length);
+
+ /*
+ System.err.printf("AFTER read() %d\n", 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);
+ }
+ if (listener != null) {
+ synchronized (listener) {
+ listener.notifyAll();
+ }
+ }
+ events.clear();
+ }
+
+ // Wait 20 millis for more data
+ Thread.sleep(20);
+ }
+ // 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();
+ }
+
+ // ------------------------------------------------------------------------
+ // ECMA48Terminal ---------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Getter for sessionInfo.
+ *
+ * @return the SessionInfo
+ */
+ public SessionInfo getSessionInfo() {
+ return sessionInfo;
+ }
+
+ /**
+ * Get the output writer.
+ *
+ * @return the Writer
+ */
+ public PrintWriter getOutput() {
+ return output;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
/**
* Flush output.
*/
* physical screen
*/
private String flushString() {
- if (!dirty) {
- assert (!reallyCleared);
- return "";
- }
-
CellAttributes attr = null;
StringBuilder sb = new StringBuilder();
flushLine(y, sb, attr);
}
- dirty = false;
reallyCleared = false;
String result = sb.toString();
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.
*/
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();
+ long nowTime = System.currentTimeMillis();
// Check for new window size
- long windowSizeDelay = now.getTime() - windowSizeTime;
+ long windowSizeDelay = nowTime - windowSizeTime;
if (windowSizeDelay > 1000) {
sessionInfo.queryWindowSize();
int newWidth = sessionInfo.getWindowWidth();
int newHeight = sessionInfo.getWindowHeight();
+
if ((newWidth != windowResize.getWidth())
|| (newHeight != windowResize.getHeight())
) {
+
+ if (debugToStderr) {
+ System.err.println("Screen size changed, old size " +
+ windowResize);
+ System.err.println(" new size " +
+ newWidth + " x " + newHeight);
+ }
+
TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
newWidth, newHeight);
windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
newWidth, newHeight);
queue.add(event);
}
- windowSizeTime = now.getTime();
+ windowSizeTime = nowTime;
}
// ESCDELAY type timeout
if (state == ParseState.ESCAPE) {
- long escDelay = now.getTime() - escapeTime;
+ long escDelay = nowTime - escapeTime;
if (escDelay > 100) {
// After 0.1 seconds, assume a true escape character
queue.add(controlChar((char)0x1B, false));
private void processChar(final List<TInputEvent> events, final char ch) {
// ESCDELAY type timeout
- Date now = new Date();
+ long nowTime = System.currentTimeMillis();
if (state == ParseState.ESCAPE) {
- long escDelay = now.getTime() - escapeTime;
+ long escDelay = nowTime - escapeTime;
if (escDelay > 250) {
// After 0.25 seconds, assume a true escape character
events.add(controlChar((char)0x1B, false));
if (ch == 0x1B) {
state = ParseState.ESCAPE;
- escapeTime = now.getTime();
+ escapeTime = nowTime;
return;
}
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);
- }
- 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();
- }
-
}