X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fjexer%2FTTerminalWidget.java;h=bf51e6b5c2fd67b78b55d6e280e44f68d3b335aa;hp=36966a0cb80805adcfd1a68e9482a96739a5af3b;hb=HEAD;hpb=c7a75ad30c309a84077cc29baace48a001af0fec diff --git a/src/jexer/TTerminalWidget.java b/src/jexer/TTerminalWidget.java index 36966a0..bf51e6b 100644 --- a/src/jexer/TTerminalWidget.java +++ b/src/jexer/TTerminalWidget.java @@ -28,27 +28,21 @@ */ package jexer; -import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; - -import java.io.InputStream; +import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import jexer.backend.ECMA48Terminal; import jexer.backend.GlyphMaker; -import jexer.backend.MultiScreen; import jexer.backend.SwingTerminal; import jexer.bits.Cell; -import jexer.bits.CellAttributes; +import jexer.event.TCommandEvent; import jexer.event.TKeypressEvent; import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; @@ -57,13 +51,14 @@ import jexer.menu.TMenu; import jexer.tterminal.DisplayLine; import jexer.tterminal.DisplayListener; import jexer.tterminal.ECMA48; +import static jexer.TCommand.*; import static jexer.TKeypress.*; /** - * TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a widget. + * TTerminalWidget exposes a ECMA-48 / ANSI X3.64 style terminal in a widget. */ public class TTerminalWidget extends TScrollableWidget - implements DisplayListener { + implements DisplayListener, EditMenuUser { /** * Translated strings. @@ -84,6 +79,11 @@ public class TTerminalWidget extends TScrollableWidget */ private Process shell; + /** + * If true, something called 'ptypipe' is on the PATH and executable. + */ + private static boolean ptypipeOnPath = false; + /** * If true, we are using the ptypipe utility to support dynamic window * resizing. ptypipe is available at @@ -119,12 +119,7 @@ public class TTerminalWidget extends TScrollableWidget private boolean haveTimer = false; /** - * The last seen scrollback lines. - */ - private List scrollback; - - /** - * The last seen display lines. + * The last seen visible display. */ private List display; @@ -159,10 +154,22 @@ public class TTerminalWidget extends TScrollableWidget */ private String title = ""; + /** + * Action to perform when the terminal exits. + */ + private TAction closeAction = null; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Static constructor. + */ + static { + checkForPtypipe(); + } + /** * Public constructor spawns a custom command line. * @@ -171,8 +178,8 @@ public class TTerminalWidget extends TScrollableWidget * @param y row relative to parent * @param commandLine the command line to execute */ - public TTerminalWidget(final TWidget parent, final int x, - final int y, final String commandLine) { + public TTerminalWidget(final TWidget parent, final int x, final int y, + final String commandLine) { this(parent, x, y, commandLine.split("\\s+")); } @@ -185,10 +192,45 @@ public class TTerminalWidget extends TScrollableWidget * @param y row relative to parent * @param command the command line to execute */ - public TTerminalWidget(final TWidget parent, final int x, - final int y, final String [] command) { + public TTerminalWidget(final TWidget parent, final int x, final int y, + final String [] command) { - super(parent, x, y, 80, 24); + this(parent, x, y, command, null); + } + + /** + * Public constructor spawns a custom command line. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param command the command line to execute + * @param closeAction action to perform when the shell exits + */ + public TTerminalWidget(final TWidget parent, final int x, final int y, + final String [] command, final TAction closeAction) { + + this(parent, x, y, 80, 24, command, closeAction); + } + + /** + * Public constructor spawns a custom command line. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width width of widget + * @param height height of widget + * @param command the command line to execute + * @param closeAction action to perform when the shell exits + */ + public TTerminalWidget(final TWidget parent, final int x, final int y, + final int width, final int height, final String [] command, + final TAction closeAction) { + + super(parent, x, y, width, height); + + this.closeAction = closeAction; String [] fullCommand; @@ -201,6 +243,14 @@ public class TTerminalWidget extends TScrollableWidget fullCommand = new String[command.length + 1]; fullCommand[0] = "ptypipe"; System.arraycopy(command, 0, fullCommand, 1, command.length); + } else if (System.getProperty("jexer.TTerminal.ptypipe", + "auto").equals("auto") + && (ptypipeOnPath == true) + ) { + ptypipe = true; + fullCommand = new String[command.length + 1]; + fullCommand[0] = "ptypipe"; + System.arraycopy(command, 0, fullCommand, 1, command.length); } else if (System.getProperty("os.name").startsWith("Windows")) { fullCommand = new String[3]; fullCommand[0] = "cmd"; @@ -216,12 +266,24 @@ public class TTerminalWidget extends TScrollableWidget fullCommand[5] = stringArrayToString(command); } else { // Default: behave like Linux - fullCommand = new String[5]; - fullCommand[0] = "script"; - fullCommand[1] = "-fqe"; - fullCommand[2] = "/dev/null"; - fullCommand[3] = "-c"; - fullCommand[4] = stringArrayToString(command); + if (System.getProperty("jexer.TTerminal.setsid", + "true").equals("false") + ) { + fullCommand = new String[5]; + fullCommand[0] = "script"; + fullCommand[1] = "-fqe"; + fullCommand[2] = "/dev/null"; + fullCommand[3] = "-c"; + fullCommand[4] = stringArrayToString(command); + } else { + fullCommand = new String[6]; + fullCommand[0] = "setsid"; + fullCommand[1] = "script"; + fullCommand[2] = "-fqe"; + fullCommand[3] = "/dev/null"; + fullCommand[4] = "-c"; + fullCommand[5] = stringArrayToString(command); + } } spawnShell(fullCommand); } @@ -234,8 +296,39 @@ public class TTerminalWidget extends TScrollableWidget * @param y row relative to parent */ public TTerminalWidget(final TWidget parent, final int x, final int y) { + this(parent, x, y, (TAction) null); + } + + /** + * Public constructor spawns a shell. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param closeAction action to perform when the shell exits + */ + public TTerminalWidget(final TWidget parent, final int x, final int y, + final TAction closeAction) { + + this(parent, x, y, 80, 24, closeAction); + } + + /** + * Public constructor spawns a shell. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width width of widget + * @param height height of widget + * @param closeAction action to perform when the shell exits + */ + public TTerminalWidget(final TWidget parent, final int x, final int y, + final int width, final int height, final TAction closeAction) { - super(parent, x, y, 80, 24); + super(parent, x, y, width, height); + + this.closeAction = closeAction; if (System.getProperty("jexer.TTerminal.shell") != null) { String shell = System.getProperty("jexer.TTerminal.shell"); @@ -254,6 +347,7 @@ public class TTerminalWidget extends TScrollableWidget // GNU differ on the '-f' vs '-F' flags, we need two different // commands. Lovely. String cmdShellGNU = "script -fqe /dev/null"; + String cmdShellGNUSetsid = "setsid script -fqe /dev/null"; String cmdShellBSD = "script -q -F /dev/null"; // ptypipe is another solution that permits dynamic window resizing. @@ -266,12 +360,24 @@ public class TTerminalWidget extends TScrollableWidget ) { ptypipe = true; spawnShell(cmdShellPtypipe.split("\\s+")); + } else if (System.getProperty("jexer.TTerminal.ptypipe", + "auto").equals("auto") + && (ptypipeOnPath == true) + ) { + ptypipe = true; + spawnShell(cmdShellPtypipe.split("\\s+")); } else if (System.getProperty("os.name").startsWith("Windows")) { spawnShell(cmdShellWindows.split("\\s+")); } else if (System.getProperty("os.name").startsWith("Mac")) { spawnShell(cmdShellBSD.split("\\s+")); } else if (System.getProperty("os.name").startsWith("Linux")) { - spawnShell(cmdShellGNU.split("\\s+")); + if (System.getProperty("jexer.TTerminal.setsid", + "true").equals("false") + ) { + spawnShell(cmdShellGNU.split("\\s+")); + } else { + spawnShell(cmdShellGNUSetsid.split("\\s+")); + } } else { // When all else fails, assume GNU. spawnShell(cmdShellGNU.split("\\s+")); @@ -279,147 +385,9 @@ public class TTerminalWidget extends TScrollableWidget } // ------------------------------------------------------------------------ - // TScrollableWidget ------------------------------------------------------ + // Event handlers --------------------------------------------------------- // ------------------------------------------------------------------------ - /** - * Draw the display buffer. - */ - @Override - public void draw() { - int width = getDisplayWidth(); - boolean syncEmulator = false; - if ((System.currentTimeMillis() - lastUpdateTime >= 25) - && (dirty == true) - ) { - // Too much time has passed, draw it all. - syncEmulator = true; - } else if (emulator.isReading() && (dirty == false)) { - // Wait until the emulator has brought more data in. - syncEmulator = false; - } else if (!emulator.isReading() && (dirty == true)) { - // The emulator won't receive more data, update the display. - syncEmulator = true; - } - - if ((syncEmulator == true) - || (scrollback == null) - || (display == null) - ) { - // We want to minimize the amount of time we have the emulator - // locked. Grab a copy of its display. - synchronized (emulator) { - // Update the scroll bars - reflowData(); - - if ((scrollback == null) || emulator.isReading()) { - scrollback = copyBuffer(emulator.getScrollbackBuffer()); - display = copyBuffer(emulator.getDisplayBuffer()); - } - width = emulator.getWidth(); - } - dirty = false; - } - - // Draw the box using my superclass - super.draw(); - - // Put together the visible rows - int visibleHeight = getHeight(); - int visibleBottom = scrollback.size() + display.size() - + getVerticalValue(); - assert (visibleBottom >= 0); - - List preceedingBlankLines = new ArrayList(); - int visibleTop = visibleBottom - visibleHeight; - if (visibleTop < 0) { - for (int i = visibleTop; i < 0; i++) { - preceedingBlankLines.add(emulator.getBlankDisplayLine()); - } - visibleTop = 0; - } - assert (visibleTop >= 0); - - List displayLines = new ArrayList(); - displayLines.addAll(scrollback); - displayLines.addAll(display); - - List visibleLines = new ArrayList(); - visibleLines.addAll(preceedingBlankLines); - visibleLines.addAll(displayLines.subList(visibleTop, - visibleBottom)); - - visibleHeight -= visibleLines.size(); - assert (visibleHeight >= 0); - - // Now draw the emulator screen - int row = 0; - for (DisplayLine line: visibleLines) { - int widthMax = width; - if (line.isDoubleWidth()) { - widthMax /= 2; - } - if (widthMax > getWidth()) { - widthMax = getWidth(); - } - for (int i = 0; i < widthMax; i++) { - Cell ch = line.charAt(i); - - if (ch.isImage()) { - putCharXY(i, row, ch); - continue; - } - - Cell newCell = new Cell(ch); - boolean reverse = line.isReverseColor() ^ ch.isReverse(); - newCell.setReverse(false); - if (reverse) { - if (ch.getForeColorRGB() < 0) { - newCell.setBackColor(ch.getForeColor()); - newCell.setBackColorRGB(-1); - } else { - newCell.setBackColorRGB(ch.getForeColorRGB()); - } - if (ch.getBackColorRGB() < 0) { - newCell.setForeColor(ch.getBackColor()); - newCell.setForeColorRGB(-1); - } else { - newCell.setForeColorRGB(ch.getBackColorRGB()); - } - } - if (line.isDoubleWidth()) { - putDoubleWidthCharXY(line, (i * 2), row, newCell); - } else { - putCharXY(i, row, newCell); - } - } - row++; - if (row == getHeight()) { - // Don't overwrite the box edge - break; - } - } - CellAttributes background = new CellAttributes(); - // Fill in the blank lines on bottom - for (int i = 0; i < visibleHeight; i++) { - hLineXY(0, i + row, getWidth(), ' ', background); - } - - } - - /** - * Handle window close. - */ - @Override - public void close() { - emulator.close(); - if (shell != null) { - terminateShellChildProcess(); - shell.destroy(); - shell = null; - } - } - /** * Handle window/screen resize events. * @@ -427,6 +395,12 @@ public class TTerminalWidget extends TScrollableWidget */ @Override public void onResize(final TResizeEvent resize) { + // Let TWidget set my size. + super.onResize(resize); + + if (emulator == null) { + return; + } // Synchronize against the emulator so we don't stomp on its reader // thread. @@ -459,28 +433,6 @@ public class TTerminalWidget extends TScrollableWidget } // synchronized (emulator) } - /** - * Resize scrollbars for a new width/height. - */ - @Override - public void reflowData() { - - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - - // Pull cursor information - readEmulatorState(); - - // Vertical scrollbar - setTopValue(getHeight() - - (emulator.getScrollbackBuffer().size() - + emulator.getDisplayBuffer().size())); - setVerticalBigChange(getHeight()); - - } // synchronized (emulator) - } - /** * Handle keystrokes. * @@ -498,6 +450,7 @@ public class TTerminalWidget extends TScrollableWidget || keypress.equals(kbAltPgUp) ) { bigVerticalDecrement(); + dirty = true; return; } if (keypress.equals(kbShiftPgDn) @@ -505,10 +458,11 @@ public class TTerminalWidget extends TScrollableWidget || keypress.equals(kbAltPgDn) ) { bigVerticalIncrement(); + dirty = true; return; } - if (emulator.isReading()) { + if ((emulator != null) && (emulator.isReading())) { // Get out of scrollback setVerticalValue(0); emulator.addUserEvent(keypress); @@ -543,23 +497,27 @@ public class TTerminalWidget extends TScrollableWidget typingHidMouse = false; } - // If the emulator is tracking mouse buttons, it needs to see wheel - // events. - if (emulator.getMouseProtocol() == ECMA48.MouseProtocol.OFF) { - if (mouse.isMouseWheelUp()) { - verticalDecrement(); - return; + if (emulator != null) { + // If the emulator is tracking mouse buttons, it needs to see + // wheel events. + if (emulator.getMouseProtocol() == ECMA48.MouseProtocol.OFF) { + if (mouse.isMouseWheelUp()) { + verticalDecrement(); + dirty = true; + return; + } + if (mouse.isMouseWheelDown()) { + verticalIncrement(); + dirty = true; + return; + } } - if (mouse.isMouseWheelDown()) { - verticalIncrement(); + if (mouseOnEmulator(mouse)) { + emulator.addUserEvent(mouse); + readEmulatorState(); return; } } - if (mouseOnEmulator(mouse)) { - emulator.addUserEvent(mouse); - readEmulatorState(); - return; - } // Emulator didn't consume it, pass it on super.onMouseDown(mouse); @@ -576,7 +534,7 @@ public class TTerminalWidget extends TScrollableWidget typingHidMouse = false; } - if (mouseOnEmulator(mouse)) { + if ((emulator != null) && (mouseOnEmulator(mouse))) { emulator.addUserEvent(mouse); readEmulatorState(); return; @@ -597,7 +555,7 @@ public class TTerminalWidget extends TScrollableWidget typingHidMouse = false; } - if (mouseOnEmulator(mouse)) { + if ((emulator != null) && (mouseOnEmulator(mouse))) { emulator.addUserEvent(mouse); readEmulatorState(); return; @@ -607,10 +565,273 @@ public class TTerminalWidget extends TScrollableWidget super.onMouseMotion(mouse); } + /** + * Handle posted command events. + * + * @param command command event + */ + @Override + public void onCommand(final TCommandEvent command) { + if (emulator == null) { + return; + } + + if (command.equals(cmPaste)) { + // Paste text from clipboard. + String text = getClipboard().pasteText(); + if (text != null) { + for (int i = 0; i < text.length(); ) { + int ch = text.codePointAt(i); + emulator.addUserEvent(new TKeypressEvent(false, 0, ch, + false, false, false)); + i += Character.charCount(ch); + } + } + return; + } + } + + // ------------------------------------------------------------------------ + // TScrollableWidget ------------------------------------------------------ + // ------------------------------------------------------------------------ + + /** + * Draw the display buffer. + */ + @Override + public void draw() { + if (emulator == null) { + return; + } + + int width = getDisplayWidth(); + + boolean syncEmulator = false; + if (System.currentTimeMillis() - lastUpdateTime >= 50) { + // Too much time has passed, draw it all. + syncEmulator = true; + } else if (emulator.isReading() && (dirty == false)) { + // Wait until the emulator has brought more data in. + syncEmulator = false; + } else if (!emulator.isReading() && (dirty == true)) { + // The emulator won't receive more data, update the display. + syncEmulator = true; + } + + if ((syncEmulator == true) + || (display == null) + ) { + // We want to minimize the amount of time we have the emulator + // locked. Grab a copy of its display. + synchronized (emulator) { + // Update the scroll bars + reflowData(); + + if (!isDrawable()) { + // We lost the connection, onShellExit() called an action + // that ultimately removed this widget from the UI + // hierarchy, so no one cares if we update the display. + // Bail out. + return; + } + + if ((display == null) || emulator.isReading()) { + display = emulator.getVisibleDisplay(getHeight(), + -getVerticalValue()); + assert (display.size() == getHeight()); + } + width = emulator.getWidth(); + } + dirty = false; + } + + // Now draw the emulator screen + int row = 0; + for (DisplayLine line: display) { + int widthMax = width; + if (line.isDoubleWidth()) { + widthMax /= 2; + } + if (widthMax > getWidth()) { + widthMax = getWidth(); + } + for (int i = 0; i < widthMax; i++) { + Cell ch = line.charAt(i); + + if (ch.isImage()) { + putCharXY(i, row, ch); + continue; + } + + Cell newCell = new Cell(ch); + boolean reverse = line.isReverseColor() ^ ch.isReverse(); + newCell.setReverse(false); + if (reverse) { + if (ch.getForeColorRGB() < 0) { + newCell.setBackColor(ch.getForeColor()); + newCell.setBackColorRGB(-1); + } else { + newCell.setBackColorRGB(ch.getForeColorRGB()); + } + if (ch.getBackColorRGB() < 0) { + newCell.setForeColor(ch.getBackColor()); + newCell.setForeColorRGB(-1); + } else { + newCell.setForeColorRGB(ch.getBackColorRGB()); + } + } + if (line.isDoubleWidth()) { + putDoubleWidthCharXY(line, (i * 2), row, newCell); + } else { + putCharXY(i, row, newCell); + } + } + row++; + } + } + + /** + * Set current value of the vertical scroll. + * + * @param value the new scroll value + */ + @Override + public void setVerticalValue(final int value) { + super.setVerticalValue(value); + dirty = true; + } + + /** + * Perform a small step change up. + */ + @Override + public void verticalDecrement() { + super.verticalDecrement(); + dirty = true; + } + + /** + * Perform a small step change down. + */ + @Override + public void verticalIncrement() { + super.verticalIncrement(); + dirty = true; + } + + /** + * Perform a big step change up. + */ + public void bigVerticalDecrement() { + super.bigVerticalDecrement(); + dirty = true; + } + + /** + * Perform a big step change down. + */ + public void bigVerticalIncrement() { + super.bigVerticalIncrement(); + dirty = true; + } + + /** + * Go to the top edge of the vertical scroller. + */ + public void toTop() { + super.toTop(); + dirty = true; + } + + /** + * Go to the bottom edge of the vertical scroller. + */ + public void toBottom() { + super.toBottom(); + dirty = true; + } + + /** + * Handle widget close. + */ + @Override + public void close() { + if (emulator != null) { + emulator.close(); + } + if (shell != null) { + terminateShellChildProcess(); + shell.destroy(); + shell = null; + } + } + + /** + * Resize scrollbars for a new width/height. + */ + @Override + public void reflowData() { + if (emulator == null) { + return; + } + + // Synchronize against the emulator so we don't stomp on its reader + // thread. + synchronized (emulator) { + + // Pull cursor information + readEmulatorState(); + + // Vertical scrollbar + setTopValue(getHeight() + - (emulator.getScrollbackBuffer().size() + + emulator.getDisplayBuffer().size())); + setVerticalBigChange(getHeight()); + + } // synchronized (emulator) + } + // ------------------------------------------------------------------------ // TTerminalWidget -------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Check for 'ptypipe' on the path. If available, set ptypipeOnPath. + */ + private static void checkForPtypipe() { + String systemPath = System.getenv("PATH"); + if (systemPath == null) { + return; + } + + String [] paths = systemPath.split(File.pathSeparator); + if (paths == null) { + return; + } + if (paths.length == 0) { + return; + } + for (int i = 0; i < paths.length; i++) { + File path = new File(paths[i]); + if (path.exists() && path.isDirectory()) { + File [] files = path.listFiles(); + if (files == null) { + continue; + } + if (files.length == 0) { + continue; + } + for (int j = 0; j < files.length; j++) { + File file = files[j]; + if (file.canExecute() && file.getName().equals("ptypipe")) { + ptypipeOnPath = true; + return; + } + } + } + } + } + /** * Get the desired window title. * @@ -621,13 +842,16 @@ public class TTerminalWidget extends TScrollableWidget } /** - * Returns true if this window does not want the application-wide mouse + * Returns true if this widget does not want the application-wide mouse * cursor drawn over it. * - * @return true if this window does not want the application-wide mouse + * @return true if this widget does not want the application-wide mouse * cursor drawn over it */ public boolean hasHiddenMouse() { + if (emulator == null) { + return false; + } return (emulator.hasHiddenMousePointer() || typingHidMouse); } @@ -638,6 +862,9 @@ public class TTerminalWidget extends TScrollableWidget * side */ public boolean isReading() { + if (emulator == null) { + return false; + } return emulator.isReading(); } @@ -747,10 +974,19 @@ public class TTerminalWidget extends TScrollableWidget * Hook for subclasses to be notified of the shell termination. */ public void onShellExit() { - if (getParent() instanceof TTerminalWindow) { - ((TTerminalWindow) getParent()).onShellExit(); + TApplication app = getApplication(); + if (app != null) { + if (closeAction != null) { + // We have to put this action inside invokeLater() because it + // could be executed during draw() when syncing with ECMA48. + app.invokeLater(new Runnable() { + public void run() { + closeAction.DO(TTerminalWidget.this); + } + }); + } + app.doRepaint(); } - getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); } /** @@ -758,6 +994,10 @@ public class TTerminalWidget extends TScrollableWidget * screen. */ private void readEmulatorState() { + if (emulator == null) { + return; + } + // Synchronize against the emulator so we don't stomp on its reader // thread. synchronized (emulator) { @@ -815,6 +1055,19 @@ public class TTerminalWidget extends TScrollableWidget } // synchronized (emulator) } + /** + * Wait for a period of time to get output from the launched process. + * + * @param millis millis to wait for, or 0 to wait forever + * @return true if the launched process has emitted something + */ + public boolean waitForOutput(final int millis) { + if (emulator == null) { + return false; + } + return emulator.waitForOutput(millis); + } + /** * Check if a mouse press/release/motion event coordinate is over the * emulator. @@ -823,6 +1076,9 @@ public class TTerminalWidget extends TScrollableWidget * @return whether or not the mouse is on the emulator */ private boolean mouseOnEmulator(final TMouseEvent mouse) { + if (emulator == null) { + return false; + } if (!emulator.isReading()) { return false; @@ -838,20 +1094,6 @@ public class TTerminalWidget extends TScrollableWidget return false; } - /** - * Copy a display buffer. - * - * @param buffer the buffer to copy - * @return a deep copy of the buffer's data - */ - private List copyBuffer(final List buffer) { - ArrayList result = new ArrayList(buffer.size()); - for (DisplayLine line: buffer) { - result.add(new DisplayLine(line)); - } - return result; - } - /** * Draw glyphs for a double-width or double-height VT100 cell to two * screen cells. @@ -994,7 +1236,17 @@ public class TTerminalWidget extends TScrollableWidget * Called by emulator when fresh data has come in. */ public void displayChanged() { - dirty = true; + if (emulator != null) { + // Force sync here: EMCA48.run() thread might be setting + // dirty=true while TTerminalWdiget.draw() is setting + // dirty=false. If these writes start interleaving, the display + // stops getting updated. + synchronized (emulator) { + dirty = true; + } + } else { + dirty = true; + } getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); } @@ -1022,4 +1274,53 @@ public class TTerminalWidget extends TScrollableWidget return 24; } + /** + * Get the exit value for the emulator. + * + * @return exit value + */ + public int getExitValue() { + return exitValue; + } + + // ------------------------------------------------------------------------ + // EditMenuUser ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Check if the cut menu item should be enabled. + * + * @return true if the cut menu item should be enabled + */ + public boolean isEditMenuCut() { + return false; + } + + /** + * Check if the copy menu item should be enabled. + * + * @return true if the copy menu item should be enabled + */ + public boolean isEditMenuCopy() { + return false; + } + + /** + * Check if the paste menu item should be enabled. + * + * @return true if the paste menu item should be enabled + */ + public boolean isEditMenuPaste() { + return true; + } + + /** + * Check if the clear menu item should be enabled. + * + * @return true if the clear menu item should be enabled + */ + public boolean isEditMenuClear() { + return false; + } + }