obviously expected behavior did not happen or when a specification was
ambiguous. This section describes such issues.
- - TTerminalWindow will hang on input from the remote if the
- TApplication is exited before the TTerminalWindow's process has
- closed on its own. This is due to a Java limitation/interaction
- between blocking reads (which is necessary to get UTF8 translation
- correct) and file streams.
-
- See jexer.tterminal.ECMA48 for more specifics of terminal
emulation limitations.
checking for a tty: script launches $SHELL in a pseudo-tty. This
works on Linux but might not on other Posix-y platforms.
+ - Closing a TTerminalWindow without exiting the process inside it
+ may result in a zombie 'script' process.
+
- Java's InputStreamReader as used by the ECMA48 backend requires a
valid UTF-8 stream. The default X10 encoding for mouse
coordinates outside (160,94) can corrupt that stream, at best
<project name="jexer" basedir="." default="jar">
- <property name="build.compiler" value="gcj"/>
+ <!-- <property name="build.compiler" value="gcj"/> -->
<property name="src.dir" value="src"/>
<property name="resources.dir" value="resources"/>
if (event instanceof TKeypressEvent) {
TKeypressEvent keypress = (TKeypressEvent) event;
- // See if this key matches an accelerator, and if so dispatch the
- // menu event.
- TKeypress keypressLowercase = keypress.getKey().toLowerCase();
- TMenuItem item = null;
- synchronized (accelerators) {
- item = accelerators.get(keypressLowercase);
+ // See if this key matches an accelerator, and is not being
+ // shortcutted by the active window, and if so dispatch the menu
+ // event.
+ boolean windowWillShortcut = false;
+ for (TWindow window: windows) {
+ if (window.isActive()) {
+ if (window.isShortcutKeypress(keypress.getKey())) {
+ // We do not process this key, it will be passed to
+ // the window instead.
+ windowWillShortcut = true;
+ }
+ }
}
- if (item != null) {
- if (item.isEnabled()) {
- // Let the menu item dispatch
- item.dispatch();
- return;
+
+ if (!windowWillShortcut) {
+ TKeypress keypressLowercase = keypress.getKey().toLowerCase();
+ TMenuItem item = null;
+ synchronized (accelerators) {
+ item = accelerators.get(keypressLowercase);
+ }
+ if (item != null) {
+ if (item.isEnabled()) {
+ // Let the menu item dispatch
+ item.dispatch();
+ return;
+ }
}
}
+
// Handle the keypress
if (onKeypress(keypress)) {
return;
*
* @param event new event to add to the queue
*/
- public final void addMenuEvent(final TInputEvent event) {
+ public final void postMenuEvent(final TInputEvent event) {
synchronized (fillEventQueue) {
fillEventQueue.add(event);
}
if (activeMenu != null) {
return;
}
-
- synchronized (windows) {
- for (TWindow window: windows) {
- closeWindow(window);
- }
+ while (windows.size() > 0) {
+ closeWindow(windows.get(0));
}
}
*/
private TVScroller vScroller;
+ /**
+ * Claim the keystrokes the emulator will need.
+ */
+ private void addShortcutKeys() {
+ addShortcutKeypress(kbCtrlA);
+ addShortcutKeypress(kbCtrlB);
+ addShortcutKeypress(kbCtrlC);
+ addShortcutKeypress(kbCtrlD);
+ addShortcutKeypress(kbCtrlE);
+ addShortcutKeypress(kbCtrlF);
+ addShortcutKeypress(kbCtrlG);
+ addShortcutKeypress(kbCtrlH);
+ addShortcutKeypress(kbCtrlU);
+ addShortcutKeypress(kbCtrlJ);
+ addShortcutKeypress(kbCtrlK);
+ addShortcutKeypress(kbCtrlL);
+ addShortcutKeypress(kbCtrlM);
+ addShortcutKeypress(kbCtrlN);
+ addShortcutKeypress(kbCtrlO);
+ addShortcutKeypress(kbCtrlP);
+ addShortcutKeypress(kbCtrlQ);
+ addShortcutKeypress(kbCtrlR);
+ addShortcutKeypress(kbCtrlS);
+ addShortcutKeypress(kbCtrlT);
+ addShortcutKeypress(kbCtrlU);
+ addShortcutKeypress(kbCtrlV);
+ addShortcutKeypress(kbCtrlW);
+ addShortcutKeypress(kbCtrlX);
+ addShortcutKeypress(kbCtrlY);
+ addShortcutKeypress(kbCtrlZ);
+ addShortcutKeypress(kbF1);
+ addShortcutKeypress(kbF2);
+ addShortcutKeypress(kbF3);
+ addShortcutKeypress(kbF4);
+ addShortcutKeypress(kbF5);
+ addShortcutKeypress(kbF6);
+ addShortcutKeypress(kbF7);
+ addShortcutKeypress(kbF8);
+ addShortcutKeypress(kbF9);
+ addShortcutKeypress(kbF10);
+ addShortcutKeypress(kbF11);
+ addShortcutKeypress(kbF12);
+ addShortcutKeypress(kbAltA);
+ addShortcutKeypress(kbAltB);
+ addShortcutKeypress(kbAltC);
+ addShortcutKeypress(kbAltD);
+ addShortcutKeypress(kbAltE);
+ addShortcutKeypress(kbAltF);
+ addShortcutKeypress(kbAltG);
+ addShortcutKeypress(kbAltH);
+ addShortcutKeypress(kbAltU);
+ addShortcutKeypress(kbAltJ);
+ addShortcutKeypress(kbAltK);
+ addShortcutKeypress(kbAltL);
+ addShortcutKeypress(kbAltM);
+ addShortcutKeypress(kbAltN);
+ addShortcutKeypress(kbAltO);
+ addShortcutKeypress(kbAltP);
+ addShortcutKeypress(kbAltQ);
+ addShortcutKeypress(kbAltR);
+ addShortcutKeypress(kbAltS);
+ addShortcutKeypress(kbAltT);
+ addShortcutKeypress(kbAltU);
+ addShortcutKeypress(kbAltV);
+ addShortcutKeypress(kbAltW);
+ addShortcutKeypress(kbAltX);
+ addShortcutKeypress(kbAltY);
+ addShortcutKeypress(kbAltZ);
+ }
+
/**
* Public constructor spawns a shell.
*
// Setup the scroll bars
onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
getHeight()));
+
+ // Claim the keystrokes the emulator will need.
+ addShortcutKeys();
}
/**
onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
getHeight()));
+ // Claim the keystrokes the emulator will need.
+ addShortcutKeys();
}
/**
* Handle window close.
*/
@Override public void onClose() {
+ emulator.close();
if (shell != null) {
+ // System.err.println("shell.destroy()");
shell.destroy();
shell = null;
- } else {
- emulator.close();
}
}
getTitle(), rc));
shell = null;
emulator.close();
+ clearShortcutKeypresses();
} catch (IllegalThreadStateException e) {
// The emulator thread has exited, but the shell Process
// hasn't figured that out yet. Do nothing, we will see
getTitle(), rc));
shell = null;
emulator.close();
+ clearShortcutKeypresses();
} catch (IllegalThreadStateException e) {
// The shell is still running, do nothing.
}
/**
* Convenience method used by TWindowLoggerOutput.
*
- * @param line
- * new line to add
+ * @param line new line to add
*/
public void addLine(final String line) {
if (text.length() == 0) {
* the final string with a newline. Note that interior newlines are
* converted to spaces.
*
- * @param str
- * the string
- * @param n
- * the maximum number of characters in a line
+ * @param str the string
+ * @param n the maximum number of characters in a line
* @return the wrapped string
*/
private String wrap(final String str, final int n) {
/**
* Public constructor.
*
- * @param parent
- * parent widget
- * @param text
- * text on the screen
- * @param x
- * column relative to parent
- * @param y
- * row relative to parent
- * @param width
- * width of text area
- * @param height
- * height of text area
+ * @param parent parent widget
+ * @param text text on the screen
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
*/
public TText(final TWidget parent, final String text, final int x,
final int y, final int width, final int height) {
/**
* Public constructor.
*
- * @param parent
- * parent widget
- * @param text
- * text on the screen
- * @param x
- * column relative to parent
- * @param y
- * row relative to parent
- * @param width
- * width of text area
- * @param height
- * height of text area
- * @param colorKey
- * ColorTheme key color to use for foreground text. Default is
- * "ttext"
+ * @param parent parent widget
+ * @param text text on the screen
+ * @param x column relative to parent
+ * @param y row relative to parent
+ * @param width width of text area
+ * @param height height of text area
+ * @param colorKey ColorTheme key color to use for foreground
+ * text. Default is "ttext".
*/
public TText(final TWidget parent, final String text, final int x,
final int y, final int width, final int height,
/**
* Handle mouse press events.
*
- * @param mouse
- * mouse button press event
+ * @param mouse mouse button press event
*/
@Override
public void onMouseDown(final TMouseEvent mouse) {
/**
* Handle keystrokes.
*
- * @param keypress
- * keystroke event
+ * @param keypress keystroke event
*/
@Override
public void onKeypress(final TKeypressEvent keypress) {
*/
package jexer;
+import java.util.HashSet;
+
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
this.z = z;
}
+ /**
+ * Window's keyboard shortcuts. Any key in this set will be passed to
+ * the window directly rather than processed through the menu
+ * accelerators.
+ */
+ private HashSet<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
+
+ /**
+ * Add a keypress to be overridden for this window.
+ *
+ * @param key the key to start taking control of
+ */
+ protected void addShortcutKeypress(final TKeypress key) {
+ keyboardShortcuts.add(key);
+ }
+
+ /**
+ * Remove a keypress to be overridden for this window.
+ *
+ * @param key the key to stop taking control of
+ */
+ protected void removeShortcutKeypress(final TKeypress key) {
+ keyboardShortcuts.remove(key);
+ }
+
+ /**
+ * Remove all keypresses to be overridden for this window.
+ */
+ protected void clearShortcutKeypresses() {
+ keyboardShortcuts.clear();
+ }
+
+ /**
+ * Determine if a keypress is overridden for this window.
+ *
+ * @param key the key to check
+ * @return true if this window wants to process this key on its own
+ */
+ public boolean isShortcutKeypress(final TKeypress key) {
+ return keyboardShortcuts.contains(key);
+ }
+
/**
* If true, then the user clicked on the title bar and is moving the
* window.
for (int i = x; i < width; i++) {
assert (logical[i][y].isBlank());
- // Physical is always updatesd
+ // Physical is always updated
physical[i][y].reset();
}
--- /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.io;
+
+import java.io.IOException;
+
+/**
+ * ReadTimeoutException is thrown by TimeoutInputStream.read() when bytes are
+ * not available within the timeout specified.
+ */
+public class ReadTimeoutException extends IOException {
+
+ /**
+ * Construct an instance with a message.
+ *
+ * @param msg exception text
+ */
+ public ReadTimeoutException(String msg) {
+ super(msg);
+ }
+}
--- /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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class provides an optional millisecond timeout on its read()
+ * operations. This permits callers to bail out rather than block.
+ */
+public class TimeoutInputStream extends InputStream {
+
+ /**
+ * The wrapped stream.
+ */
+ private InputStream stream;
+
+ /**
+ * The timeout value in millis. If it takes longer than this for bytes
+ * to be available for read then a ReadTimeoutException is thrown. A
+ * value of 0 means to block as a normal InputStream would.
+ */
+ private int timeoutMillis;
+
+ /**
+ * If true, the current read() will timeout soon.
+ */
+ private volatile boolean cancel = false;
+
+ /**
+ * Request that the current read() operation timeout immediately.
+ */
+ public synchronized void cancelRead() {
+ cancel = true;
+ }
+
+ /**
+ * Public constructor, at the default timeout of 10000 millis (10
+ * seconds).
+ *
+ * @param stream the wrapped InputStream
+ */
+ public TimeoutInputStream(final InputStream stream) {
+ this.stream = stream;
+ this.timeoutMillis = 10000;
+ }
+
+ /**
+ * Public constructor, using the default 10 bits per byte.
+ *
+ * @param stream the wrapped InputStream
+ * @param timeoutMillis the timeout value in millis. If it takes longer
+ * than this for bytes to be available for read then a
+ * ReadTimeoutException is thrown. A value of 0 means to block as a
+ * normal InputStream would.
+ */
+ public TimeoutInputStream(final InputStream stream,
+ final int timeoutMillis) {
+
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("Invalid timeoutMillis value, " +
+ "must be >= 0");
+ }
+
+ this.stream = stream;
+ this.timeoutMillis = timeoutMillis;
+ }
+
+ /**
+ * Reads the next byte of data from the input stream.
+ *
+ * @return the next byte of data, or -1 if there is no more data because
+ * the end of the stream has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read() throws IOException {
+
+ if (timeoutMillis == 0) {
+ // Block on the read().
+ return stream.read();
+ }
+
+ if (stream.available() > 0) {
+ // A byte is available now, return it.
+ return stream.read();
+ }
+
+ // We will wait up to timeoutMillis to see if a byte is available.
+ // If not, we throw ReadTimeoutException.
+ long checkTime = System.currentTimeMillis();
+ while (stream.available() == 0) {
+ long now = System.currentTimeMillis();
+ synchronized (this) {
+ if ((now - checkTime > timeoutMillis) || (cancel == true)) {
+ if (cancel == true) {
+ cancel = false;
+ }
+ throw new ReadTimeoutException("Timeout on read(): " +
+ (int) (now - checkTime) + " millis and still no data");
+ }
+ }
+ try {
+ // How long do we sleep for, eh? For now we will go with 2
+ // millis.
+ Thread.currentThread().sleep(2);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ }
+
+ if (stream.available() > 0) {
+ // A byte is available now, return it.
+ return stream.read();
+ }
+
+ throw new IOException("InputStream claimed a byte was available, but " +
+ "now it is not. What is going on?");
+ }
+
+ /**
+ * Reads some number of bytes from the input stream and stores them into
+ * the buffer array b.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or -1 if there
+ * is no more data because the end of the stream has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(final byte[] b) throws IOException {
+ if (timeoutMillis == 0) {
+ // Block on the read().
+ return stream.read(b);
+ }
+
+ int remaining = b.length;
+
+ if (stream.available() >= remaining) {
+ // Enough bytes are available now, return them.
+ return stream.read(b);
+ }
+
+ while (remaining > 0) {
+
+ // We will wait up to timeoutMillis to see if a byte is
+ // available. If not, we throw ReadTimeoutException.
+ long checkTime = System.currentTimeMillis();
+ while (stream.available() == 0) {
+ long now = System.currentTimeMillis();
+
+ synchronized (this) {
+ if ((now - checkTime > timeoutMillis) || (cancel == true)) {
+ if (cancel == true) {
+ cancel = false;
+ }
+ throw new ReadTimeoutException("Timeout on read(): " +
+ (int) (now - checkTime) + " millis and still no " +
+ "data");
+ }
+ }
+ try {
+ // How long do we sleep for, eh? For now we will go with
+ // 2 millis.
+ Thread.currentThread().sleep(2);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ }
+
+ if (stream.available() > 0) {
+ // At least one byte is available now, read it.
+ int n = stream.available();
+ if (remaining < n) {
+ n = remaining;
+ }
+ int rc = stream.read(b, b.length - remaining, n);
+ if (rc == -1) {
+ // This shouldn't happen.
+ throw new IOException("InputStream claimed bytes were " +
+ "available, but read() returned -1. What is going " +
+ "on?");
+ }
+ remaining -= rc;
+ return rc;
+ }
+ }
+
+ throw new IOException("InputStream claimed all bytes were available, " +
+ "but now it is not. What is going on?");
+ }
+
+ /**
+ * Reads up to len bytes of data from the input stream into an array of
+ * bytes.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in array b at which the data is written.
+ * @param len the maximum number of bytes to read.
+ * @return the total number of bytes read into the buffer, or -1 if there
+ * is no more data because the end of the stream has been reached.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int read(final byte[] b, final int off,
+ final int len) throws IOException {
+
+ if (timeoutMillis == 0) {
+ // Block on the read().
+ return stream.read(b);
+ }
+
+ int remaining = len;
+
+ if (stream.available() >= remaining) {
+ // Enough bytes are available now, return them.
+ return stream.read(b, off, remaining);
+ }
+
+ while (remaining > 0) {
+
+ // We will wait up to timeoutMillis to see if a byte is
+ // available. If not, we throw ReadTimeoutException.
+ long checkTime = System.currentTimeMillis();
+ while (stream.available() == 0) {
+ long now = System.currentTimeMillis();
+ synchronized (this) {
+ if ((now - checkTime > timeoutMillis) || (cancel == true)) {
+ if (cancel == true) {
+ cancel = false;
+ }
+ throw new ReadTimeoutException("Timeout on read(): " +
+ (int) (now - checkTime) + " millis and still no " +
+ "data");
+ }
+ }
+ try {
+ // How long do we sleep for, eh? For now we will go with
+ // 2 millis.
+ Thread.currentThread().sleep(2);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ }
+
+ if (stream.available() > 0) {
+ // At least one byte is available now, read it.
+ int n = stream.available();
+ if (remaining < n) {
+ n = remaining;
+ }
+ int rc = stream.read(b, off + len - remaining, n);
+ if (rc == -1) {
+ // This shouldn't happen.
+ throw new IOException("InputStream claimed bytes were " +
+ "available, but read() returned -1. What is going " +
+ "on?");
+ }
+ return rc;
+ }
+ }
+
+ throw new IOException("InputStream claimed all bytes were available, " +
+ "but now it is not. What is going on?");
+ }
+
+ /**
+ * Returns an estimate of the number of bytes that can be read (or
+ * skipped over) from this input stream without blocking by the next
+ * invocation of a method for this input stream.
+ *
+ * @return an estimate of the number of bytes that can be read (or
+ * skipped over) from this input stream without blocking or 0 when it
+ * reaches the end of the input stream.
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public int available() throws IOException {
+ return stream.available();
+ }
+
+ /**
+ * Closes this input stream and releases any system resources associated
+ * with the stream.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+
+ /**
+ * Marks the current position in this input stream.
+ *
+ * @param readLimit the maximum limit of bytes that can be read before
+ * the mark position becomes invalid
+ */
+ @Override
+ public void mark(final int readLimit) {
+ stream.mark(readLimit);
+ }
+
+ /**
+ * Tests if this input stream supports the mark and reset methods.
+ *
+ * @return true if this stream instance supports the mark and reset
+ * methods; false otherwise
+ */
+ @Override
+ public boolean markSupported() {
+ return stream.markSupported();
+ }
+
+ /**
+ * Repositions this stream to the position at the time the mark method
+ * was last called on this input stream.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void reset() throws IOException {
+ stream.reset();
+ }
+
+ /**
+ * Skips over and discards n bytes of data from this input stream.
+ *
+ * @param n the number of bytes to be skipped
+ * @return the actual number of bytes skipped
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public long skip(final long n) throws IOException {
+ return stream.skip(n);
+ }
+
+}
break;
case MID_WINDOW_CLOSE:
label = "&Close";
- // key = kbCtrlW;
+ key = kbCtrlW;
break;
default:
public void dispatch() {
assert (isEnabled());
- getApplication().addMenuEvent(new TMenuEvent(id));
+ getApplication().postMenuEvent(new TMenuEvent(id));
if (checkable) {
checked = !checked;
}
import jexer.bits.Color;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
+import jexer.io.ReadTimeoutException;
+import jexer.io.TimeoutInputStream;
import static jexer.TKeypress.*;
/**
*/
public final void close() {
- // Synchronize so we don't stomp on the reader thread.
- synchronized (this) {
-
- // Close the input stream
- switch (type) {
- case VT100:
- case VT102:
- case VT220:
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- // SQUASH
- }
- inputStream = null;
- }
- break;
- case XTERM:
- if (input != null) {
- try {
- input.close();
- } catch (IOException e) {
- // SQUASH
- }
- input = null;
- inputStream = null;
- }
- break;
+ // Tell the reader thread to stop looking at input. It will close
+ // the input streams.
+ if (stopReaderThread == false) {
+ stopReaderThread = true;
+ try {
+ readerThread.join(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
}
+ }
- // Tell the reader thread to stop looking at input.
- if (stopReaderThread == false) {
- stopReaderThread = true;
+ // Now close the output stream.
+ switch (type) {
+ case VT100:
+ case VT102:
+ case VT220:
+ if (outputStream != null) {
try {
- readerThread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
+ outputStream.close();
+ } catch (IOException e) {
+ // SQUASH
}
+ outputStream = null;
}
-
- // Close the output stream.
- switch (type) {
- case VT100:
- case VT102:
- case VT220:
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- // SQUASH
- }
- outputStream = null;
+ break;
+ case XTERM:
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ // SQUASH
}
- break;
- case XTERM:
- if (output != null) {
- try {
- output.close();
- } catch (IOException e) {
- // SQUASH
- }
- output = null;
+ outputStream = null;
+ }
+ if (output != null) {
+ try {
+ output.close();
+ } catch (IOException e) {
+ // SQUASH
}
- break;
- default:
- throw new IllegalArgumentException("Invalid device type: "
- + type);
+ output = null;
}
- } // synchronized (this)
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid device type: " +
+ type);
+ }
}
/**
/**
* The terminal's raw InputStream. This is used for type != XTERM.
*/
- private volatile InputStream inputStream;
+ private volatile TimeoutInputStream inputStream;
/**
* The terminal's output. For type == XTERM, this wraps an
display = new LinkedList<DisplayLine>();
this.type = type;
- this.inputStream = inputStream;
+ if (inputStream instanceof TimeoutInputStream) {
+ this.inputStream = (TimeoutInputStream)inputStream;
+ } else {
+ this.inputStream = new TimeoutInputStream(inputStream, 2000);
+ }
if (type == DeviceType.XTERM) {
- this.input = new InputStreamReader(inputStream, "UTF-8");
+ this.input = new InputStreamReader(this.inputStream, "UTF-8");
this.output = new OutputStreamWriter(outputStream, "UTF-8");
this.outputStream = null;
} else {
while (!done && !stopReaderThread) {
try {
int n = inputStream.available();
+
// System.err.printf("available() %d\n", n); System.err.flush();
if (utf8) {
if (readBufferUTF8.length < n) {
// The buffer wasn't big enough, make it huger
- int newSizeHalf = Math.max(readBufferUTF8.length, n);
+ int newSizeHalf = Math.max(readBufferUTF8.length,
+ n);
readBufferUTF8 = new char[newSizeHalf * 2];
}
readBuffer = new byte[newSizeHalf * 2];
}
}
+ if (n == 0) {
+ try {
+ Thread.sleep(2);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ continue;
+ }
int rc = -1;
- if (utf8) {
- rc = input.read(readBufferUTF8, 0,
- readBufferUTF8.length);
- } else {
- rc = inputStream.read(readBuffer, 0,
- readBuffer.length);
+ try {
+ if (utf8) {
+ rc = input.read(readBufferUTF8, 0,
+ readBufferUTF8.length);
+ } else {
+ rc = inputStream.read(readBuffer, 0,
+ readBuffer.length);
+ }
+ } catch (ReadTimeoutException e) {
+ rc = 0;
}
+
// System.err.printf("read() %d\n", rc); System.err.flush();
if (rc == -1) {
// This is EOF
} else {
ch = readBuffer[i];
}
- // Don't step on UI events
+
synchronized (this) {
+ // Don't step on UI events
consume((char)ch);
}
}
e.printStackTrace();
done = true;
}
+
} // while ((done == false) && (stopReaderThread == false))
// Let the rest of the world know that I am done.
stopReaderThread = true;
+ try {
+ inputStream.cancelRead();
+ inputStream.close();
+ inputStream = null;
+ } catch (IOException e) {
+ // SQUASH
+ }
+ try {
+ input.close();
+ input = null;
+ } catch (IOException e) {
+ // SQUASH
+ }
+
// System.err.println("*** run() exiting..."); System.err.flush();
}