X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTTerminalWindow.java;h=754b7a512d6f7581216a78b76c2a8a7be3838dcb;hb=HEAD;hp=818a52f6eb6bbccc20dfc45042cad9653ba1b7be;hpb=5fc7bf09f3c9987287f34f9035b522b0e5e9de13;p=fanfix.git diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index 818a52f..754b7a5 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -28,41 +28,20 @@ */ 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.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.MultiScreen; -import jexer.backend.SwingTerminal; -import jexer.bits.Cell; -import jexer.bits.CellAttributes; +import jexer.menu.TMenu; import jexer.event.TKeypressEvent; import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; -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 window. */ -public class TTerminalWindow extends TScrollableWindow - implements DisplayListener { +public class TTerminalWindow extends TScrollableWindow { /** * Translated strings. @@ -74,76 +53,15 @@ public class TTerminalWindow extends TScrollableWindow // ------------------------------------------------------------------------ /** - * The emulator. - */ - private ECMA48 emulator; - - /** - * The Process created by the shell spawning constructor. + * The terminal. */ - private Process shell; - - /** - * If true, we are using the ptypipe utility to support dynamic window - * resizing. ptypipe is available at - * https://gitlab.com/klamonte/ptypipe . - */ - private boolean ptypipe = false; + private TTerminalWidget terminal; /** * If true, close the window when the shell exits. */ private boolean closeOnExit = false; - /** - * System-dependent Y adjustment for text in the character cell - * (double-height). - */ - private int doubleTextAdjustY = 0; - - /** - * System-dependent X adjustment for text in the character cell - * (double-height). - */ - private int doubleTextAdjustX = 0; - - /** - * Descent of a character cell in pixels (double-height). - */ - private int doubleMaxDescent = 0; - - /** - * Double-width font. - */ - private Font doubleFont = null; - - /** - * Last text width value. - */ - private int lastTextWidth = -1; - - /** - * Last text height value. - */ - private int lastTextHeight = -1; - - /** - * A cache of previously-rendered double-width glyphs. - */ - private Map glyphCache; - - /** - * A cache of previously-rendered double-width glyphs for blinking text, - * when it is not visible. - */ - private Map glyphCacheBlink; - - /** - * The blink state, used only by ECMA48 backend and when double-width - * chars must be drawn. - */ - private boolean blinkState = true; - // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -214,42 +132,28 @@ public class TTerminalWindow extends TScrollableWindow super(application, i18n.getString("windowTitle"), x, y, 80 + 2, 24 + 2, flags); + // Require at least one line for the display. + setMinimumWindowHeight(3); + this.closeOnExit = closeOnExit; + vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); - String [] fullCommand; + // Claim the keystrokes the emulator will need. + addShortcutKeys(); - // Spawn a shell and pass its I/O to the other constructor. - if ((System.getProperty("jexer.TTerminal.ptypipe") != null) - && (System.getProperty("jexer.TTerminal.ptypipe"). - equals("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"; - fullCommand[1] = "/c"; - fullCommand[2] = stringArrayToString(command); - } else if (System.getProperty("os.name").startsWith("Mac")) { - fullCommand = new String[6]; - fullCommand[0] = "script"; - fullCommand[1] = "-q"; - fullCommand[2] = "-F"; - fullCommand[3] = "/dev/null"; - fullCommand[4] = "-c"; - 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); - } - spawnShell(fullCommand); + // Add shortcut text + TStatusBar statusBar = newStatusBar(i18n.getString("statusBarRunning")); + statusBar.addShortcutKeypress(kbF1, cmHelp, + i18n.getString("statusBarHelp")); + statusBar.addShortcutKeypress(kbF10, cmMenu, + i18n.getString("statusBarMenu")); + + // Spin it up + terminal = new TTerminalWidget(this, 0, 0, command, new TAction() { + public void DO() { + onShellExit(); + } + }); } /** @@ -284,38 +188,28 @@ public class TTerminalWindow extends TScrollableWindow super(application, i18n.getString("windowTitle"), x, y, 80 + 2, 24 + 2, flags); - this.closeOnExit = closeOnExit; - - String cmdShellWindows = "cmd.exe"; + // Require at least one line for the display. + setMinimumWindowHeight(3); - // You cannot run a login shell in a bare Process interactively, due - // to libc's behavior of buffering when stdin/stdout aren't a tty. - // Use 'script' instead to run a shell in a pty. And because BSD and - // GNU differ on the '-f' vs '-F' flags, we need two different - // commands. Lovely. - String cmdShellGNU = "script -fqe /dev/null"; - String cmdShellBSD = "script -q -F /dev/null"; + this.closeOnExit = closeOnExit; + vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); - // ptypipe is another solution that permits dynamic window resizing. - String cmdShellPtypipe = "ptypipe /bin/bash --login"; + // Claim the keystrokes the emulator will need. + addShortcutKeys(); - // Spawn a shell and pass its I/O to the other constructor. - if ((System.getProperty("jexer.TTerminal.ptypipe") != null) - && (System.getProperty("jexer.TTerminal.ptypipe"). - equals("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+")); - } else { - // When all else fails, assume GNU. - spawnShell(cmdShellGNU.split("\\s+")); - } + // Add shortcut text + TStatusBar statusBar = newStatusBar(i18n.getString("statusBarRunning")); + statusBar.addShortcutKeypress(kbF1, cmHelp, + i18n.getString("statusBarHelp")); + statusBar.addShortcutKeypress(kbF10, cmMenu, + i18n.getString("statusBarMenu")); + + // Spin it up + terminal = new TTerminalWidget(this, 0, 0, new TAction() { + public void DO() { + onShellExit(); + } + }); } // ------------------------------------------------------------------------ @@ -327,110 +221,11 @@ public class TTerminalWindow extends TScrollableWindow */ @Override public void draw() { - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - - // Update the scroll bars - reflowData(); - - // Draw the box using my superclass - super.draw(); - - List scrollback = emulator.getScrollbackBuffer(); - List display = emulator.getDisplayBuffer(); - - // Put together the visible rows - int visibleHeight = getHeight() - 2; - 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 = 1; - for (DisplayLine line: visibleLines) { - int widthMax = emulator.getWidth(); - if (line.isDoubleWidth()) { - widthMax /= 2; - } - if (widthMax > getWidth() - 2) { - widthMax = getWidth() - 2; - } - for (int i = 0; i < widthMax; i++) { - Cell ch = line.charAt(i); - Cell newCell = new Cell(); - newCell.setTo(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) + 1, row, newCell); - } else { - putCharXY(i + 1, row, newCell); - } - } - row++; - if (row == getHeight() - 1) { - // 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(1, i + row, getWidth() - 2, ' ', background); - } - - } // synchronized (emulator) - - } - - /** - * Handle window close. - */ - @Override - public void onClose() { - emulator.close(); - if (shell != null) { - terminateShellChildProcess(); - shell.destroy(); - shell = null; + if (terminal != null) { + setTitle(terminal.getTitle()); } + reflowData(); + super.draw(); } /** @@ -440,30 +235,17 @@ public class TTerminalWindow extends TScrollableWindow */ @Override public void onResize(final TResizeEvent resize) { - - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - - if (resize.getType() == TResizeEvent.Type.WIDGET) { - // Resize the scroll bars - reflowData(); - placeScrollbars(); - - // Get out of scrollback - setVerticalValue(0); - - if (ptypipe) { - emulator.setWidth(getWidth() - 2); - emulator.setHeight(getHeight() - 2); - - emulator.writeRemote("\033[8;" + (getHeight() - 2) + ";" + - (getWidth() - 2) + "t"); - } + if (resize.getType() == TResizeEvent.Type.WIDGET) { + if (terminal != null) { + terminal.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + getWidth() - 2, getHeight() - 2)); } - return; - } // synchronized (emulator) + // Resize the scroll bars + reflowData(); + placeScrollbars(); + } + return; } /** @@ -471,21 +253,14 @@ public class TTerminalWindow extends TScrollableWindow */ @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() - 2 - - (emulator.getScrollbackBuffer().size() - + emulator.getDisplayBuffer().size())); - setVerticalBigChange(getHeight() - 2); - - } // synchronized (emulator) + // Vertical scrollbar + if (terminal != null) { + terminal.reflowData(); + setTopValue(terminal.getTopValue()); + setBottomValue(terminal.getBottomValue()); + setVerticalBigChange(terminal.getVerticalBigChange()); + setVerticalValue(terminal.getVerticalValue()); + } } /** @@ -495,46 +270,14 @@ public class TTerminalWindow extends TScrollableWindow */ @Override public void onKeypress(final TKeypressEvent keypress) { - - // Scrollback up/down - if (keypress.equals(kbShiftPgUp) - || keypress.equals(kbCtrlPgUp) - || keypress.equals(kbAltPgUp) - ) { - bigVerticalDecrement(); - return; - } - if (keypress.equals(kbShiftPgDn) - || keypress.equals(kbCtrlPgDn) - || keypress.equals(kbAltPgDn) + if ((terminal != null) + && (terminal.isReading()) + && (!inKeyboardResize) ) { - bigVerticalIncrement(); - return; - } - - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - if (emulator.isReading()) { - // Get out of scrollback - setVerticalValue(0); - emulator.keypress(keypress.getKey()); - - // UGLY HACK TIME! cmd.exe needs CRLF, not just CR, so if - // this is kBEnter then also send kbCtrlJ. - if (System.getProperty("os.name").startsWith("Windows")) { - if (keypress.equals(kbEnter)) { - emulator.keypress(kbCtrlJ); - } - } - - readEmulatorState(); - return; - } + terminal.onKeypress(keypress); + } else { + super.onKeypress(keypress); } - - // Process is closed, honor "normal" TUI keystrokes - super.onKeypress(keypress); } /** @@ -550,25 +293,6 @@ public class TTerminalWindow extends TScrollableWindow return; } - if (mouse.isMouseWheelUp()) { - verticalDecrement(); - return; - } - if (mouse.isMouseWheelDown()) { - verticalIncrement(); - return; - } - if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; - } - } - - // Emulator didn't consume it, pass it on super.onMouseDown(mouse); } @@ -585,18 +309,14 @@ public class TTerminalWindow extends TScrollableWindow return; } - if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; + super.onMouseUp(mouse); + + if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { + // Clicked on vertical scrollbar + if (terminal != null) { + terminal.setVerticalValue(getVerticalValue()); } } - - // Emulator didn't consume it, pass it on - super.onMouseUp(mouse); } /** @@ -612,24 +332,45 @@ public class TTerminalWindow extends TScrollableWindow return; } - if (mouseOnEmulator(mouse)) { - synchronized (emulator) { - mouse.setX(mouse.getX() - 1); - mouse.setY(mouse.getY() - 1); - emulator.mouse(mouse); - readEmulatorState(); - return; + super.onMouseMotion(mouse); + + if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { + // Clicked/dragged on vertical scrollbar + if (terminal != null) { + terminal.setVerticalValue(getVerticalValue()); } } + } - // Emulator didn't consume it, pass it on - super.onMouseMotion(mouse); + /** + * Get this window's help topic to load. + * + * @return the topic name + */ + @Override + public String getHelpTopic() { + return "Terminal Window"; } // ------------------------------------------------------------------------ // TTerminalWindow -------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Returns true if this window does not want the application-wide mouse + * cursor drawn over it. + * + * @return true if this window does not want the application-wide mouse + * cursor drawn over it + */ + @Override + public boolean hasHiddenMouse() { + if (terminal != null) { + return terminal.hasHiddenMouse(); + } + return false; + } + /** * Claim the keystrokes the emulator will need. */ @@ -700,149 +441,6 @@ public class TTerminalWindow extends TScrollableWindow addShortcutKeypress(kbAltZ); } - /** - * Convert a string array to a whitespace-separated string. - * - * @param array the string array - * @return a single string - */ - private String stringArrayToString(final String [] array) { - StringBuilder sb = new StringBuilder(array[0].length()); - for (int i = 0; i < array.length; i++) { - sb.append(array[i]); - if (i < array.length - 1) { - sb.append(' '); - } - } - return sb.toString(); - } - - /** - * Spawn the shell. - * - * @param command the command line to execute - */ - private void spawnShell(final String [] command) { - - /* - System.err.printf("spawnShell(): '%s'\n", - stringArrayToString(command)); - */ - - vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); - setBottomValue(0); - - // Assume XTERM - ECMA48.DeviceType deviceType = ECMA48.DeviceType.XTERM; - - try { - ProcessBuilder pb = new ProcessBuilder(command); - Map env = pb.environment(); - env.put("TERM", ECMA48.deviceTypeTerm(deviceType)); - env.put("LANG", ECMA48.deviceTypeLang(deviceType, "en")); - env.put("COLUMNS", "80"); - env.put("LINES", "24"); - pb.redirectErrorStream(true); - shell = pb.start(); - emulator = new ECMA48(deviceType, shell.getInputStream(), - shell.getOutputStream(), this); - } catch (IOException e) { - messageBox(i18n.getString("errorLaunchingShellTitle"), - MessageFormat.format(i18n.getString("errorLaunchingShellText"), - e.getMessage())); - } - - // Setup the scroll bars - onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(), - getHeight())); - - // Claim the keystrokes the emulator will need. - addShortcutKeys(); - - // Add shortcut text - newStatusBar(i18n.getString("statusBarRunning")); - - // Pass the correct text cell width/height to the emulator - int textWidth = 16; - int textHeight = 20; - if (getScreen() instanceof SwingTerminal) { - SwingTerminal terminal = (SwingTerminal) getScreen(); - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - } else if (getScreen() instanceof ECMA48Terminal) { - ECMA48Terminal terminal = (ECMA48Terminal) getScreen(); - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - } - emulator.setTextWidth(textWidth); - emulator.setTextHeight(textHeight); - } - - /** - * Terminate the child of the 'script' process used on POSIX. This may - * or may not work. - */ - private void terminateShellChildProcess() { - int pid = -1; - if (shell.getClass().getName().equals("java.lang.UNIXProcess")) { - /* get the PID on unix/linux systems */ - try { - Field field = shell.getClass().getDeclaredField("pid"); - field.setAccessible(true); - pid = field.getInt(shell); - } catch (Throwable e) { - // SQUASH, this didn't work. Just bail out quietly. - return; - } - } - if (pid != -1) { - // shell.destroy() works successfully at killing this side of - // 'script'. But we need to make sure the other side (child - // process) is also killed. - String [] cmdKillIt = { - "pkill", "-P", Integer.toString(pid) - }; - try { - Runtime.getRuntime().exec(cmdKillIt); - } catch (Throwable e) { - // SQUASH, this didn't work. Just bail out quietly. - return; - } - } - } - - /** - * Called by emulator when fresh data has come in. - */ - public void displayChanged() { - getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); - } - - /** - * Function to call to obtain the display width. - * - * @return the number of columns in the display - */ - public int getDisplayWidth() { - if (ptypipe) { - return getWidth() - 2; - } - return 80; - } - - /** - * Function to call to obtain the display height. - * - * @return the number of rows in the display - */ - public int getDisplayHeight() { - if (ptypipe) { - return getHeight() - 2; - } - return 24; - } - /** * Hook for subclasses to be notified of the shell termination. */ @@ -850,291 +448,33 @@ public class TTerminalWindow extends TScrollableWindow if (closeOnExit) { close(); } + clearShortcutKeypresses(); getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); } /** - * Copy out variables from the emulator that TTerminal has to expose on - * screen. - */ - private void readEmulatorState() { - // Synchronize against the emulator so we don't stomp on its reader - // thread. - synchronized (emulator) { - setHiddenMouse(emulator.hasHiddenMousePointer()); - - setCursorX(emulator.getCursorX() + 1); - setCursorY(emulator.getCursorY() + 1 - + (getHeight() - 2 - emulator.getHeight()) - - getVerticalValue()); - setCursorVisible(emulator.isCursorVisible()); - if (getCursorX() > getWidth() - 2) { - setCursorVisible(false); - } - if ((getCursorY() > getHeight() - 2) || (getCursorY() < 0)) { - setCursorVisible(false); - } - if (emulator.getScreenTitle().length() > 0) { - // Only update the title if the shell is still alive - if (shell != null) { - setTitle(emulator.getScreenTitle()); - } - } - - // Check to see if the shell has died. - if (!emulator.isReading() && (shell != null)) { - try { - int rc = shell.exitValue(); - // The emulator exited on its own, all is fine - setTitle(MessageFormat.format(i18n. - getString("windowTitleCompleted"), getTitle(), rc)); - shell = null; - emulator.close(); - clearShortcutKeypresses(); - statusBar.setText(MessageFormat.format(i18n. - getString("statusBarCompleted"), rc)); - onShellExit(); - } catch (IllegalThreadStateException e) { - // The emulator thread has exited, but the shell Process - // hasn't figured that out yet. Do nothing, we will see - // this in a future tick. - } - } else if (emulator.isReading() && (shell != null)) { - // The shell might be dead, let's check - try { - int rc = shell.exitValue(); - // If we got here, the shell died. - setTitle(MessageFormat.format(i18n. - getString("windowTitleCompleted"), getTitle(), rc)); - shell = null; - emulator.close(); - clearShortcutKeypresses(); - statusBar.setText(MessageFormat.format(i18n. - getString("statusBarCompleted"), rc)); - onShellExit(); - } catch (IllegalThreadStateException e) { - // The shell is still running, do nothing. - } - } - - } // synchronized (emulator) - } - - /** - * Check if a mouse press/release/motion event coordinate is over the - * emulator. - * - * @param mouse a mouse-based event - * @return whether or not the mouse is on the emulator - */ - private boolean mouseOnEmulator(final TMouseEvent mouse) { - - synchronized (emulator) { - if (!emulator.isReading()) { - return false; - } - } - - if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1) - && (mouse.getAbsoluteX() < getAbsoluteX() + getWidth() - 1) - && (mouse.getAbsoluteY() >= getAbsoluteY() + 1) - && (mouse.getAbsoluteY() < getAbsoluteY() + getHeight() - 1) - ) { - return true; - } - return false; - } - - /** - * Draw glyphs for a double-width or double-height VT100 cell to two - * screen cells. + * Wait for a period of time to get output from the launched process. * - * @param line the line this VT100 cell is in - * @param x the X position to draw the left half to - * @param y the Y position to draw to - * @param cell the cell to draw + * @param millis millis to wait for, or 0 to wait forever + * @return true if the launched process has emitted something */ - private void putDoubleWidthCharXY(final DisplayLine line, final int x, - final int y, final Cell cell) { - - int textWidth = 16; - int textHeight = 20; - boolean cursorBlinkVisible = true; - - if (getScreen() instanceof SwingTerminal) { - SwingTerminal terminal = (SwingTerminal) getScreen(); - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - cursorBlinkVisible = terminal.getCursorBlinkVisible(); - } else if (getScreen() instanceof ECMA48Terminal) { - ECMA48Terminal terminal = (ECMA48Terminal) getScreen(); - - if (!terminal.hasSixel()) { - // The backend does not have sixel support, draw this as text - // and bail out. - putCharXY(x, y, cell); - putCharXY(x + 1, y, ' ', cell); - return; - } - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - cursorBlinkVisible = blinkState; - } else { - // We don't know how to dray glyphs to this screen, draw them as - // text and bail out. - putCharXY(x, y, cell); - putCharXY(x + 1, y, ' ', cell); - return; - } - - if ((textWidth != lastTextWidth) || (textHeight != lastTextHeight)) { - // Screen size has changed, reset all fonts. - setupFonts(textHeight); - lastTextWidth = textWidth; - lastTextHeight = textHeight; + public boolean waitForOutput(final int millis) { + if (terminal == null) { + return false; } - assert (doubleFont != null); - - BufferedImage image = null; - if (cell.isBlink() && !cursorBlinkVisible) { - image = glyphCacheBlink.get(cell); - } else { - image = glyphCache.get(cell); - } - if (image == null) { - // Generate glyph and draw it to an image. - image = new BufferedImage(textWidth * 2, textHeight * 2, - BufferedImage.TYPE_INT_ARGB); - Graphics2D gr2 = image.createGraphics(); - gr2.setFont(doubleFont); - - // Draw the background rectangle, then the foreground character. - if (getScreen() instanceof ECMA48Terminal) { - // BUG: the background color is coming in the same as the - // foreground color. For now, don't draw it. - } else { - gr2.setColor(SwingTerminal.attrToBackgroundColor(cell)); - gr2.fillRect(0, 0, image.getWidth(), image.getHeight()); - } - if (!cell.isBlink() - || (cell.isBlink() && cursorBlinkVisible) - ) { - gr2.setColor(SwingTerminal.attrToForegroundColor(cell)); - char [] chars = new char[1]; - chars[0] = cell.getChar(); - gr2.drawChars(chars, 0, 1, doubleTextAdjustX, - (textHeight * 2) - doubleMaxDescent + doubleTextAdjustY); - - if (cell.isUnderline() && (line.getDoubleHeight() != 1)) { - gr2.fillRect(0, textHeight - 2, textWidth, 2); - } - } - gr2.dispose(); - - // Now save this generated image, using a new key that will not - // be mutated by invertCell(). - Cell key = new Cell(); - key.setTo(cell); - if (cell.isBlink() && !cursorBlinkVisible) { - glyphCacheBlink.put(key, image); - } else { - glyphCache.put(key, image); - } - } - - // Now that we have the double-wide glyph drawn, copy the right - // pieces of it to the cells. - Cell left = new Cell(); - Cell right = new Cell(); - left.setTo(cell); - right.setTo(cell); - right.setChar(' '); - BufferedImage leftImage = null; - BufferedImage rightImage = null; - switch (line.getDoubleHeight()) { - case 1: - // Top half double height - leftImage = image.getSubimage(0, 0, textWidth, textHeight); - rightImage = image.getSubimage(textWidth, 0, textWidth, textHeight); - break; - case 2: - // Bottom half double height - leftImage = image.getSubimage(0, textHeight, textWidth, textHeight); - rightImage = image.getSubimage(textWidth, textHeight, - textWidth, textHeight); - break; - default: - // Either single height double-width, or error fallback - BufferedImage wideImage = new BufferedImage(textWidth * 2, - textHeight, BufferedImage.TYPE_INT_ARGB); - Graphics2D grWide = wideImage.createGraphics(); - grWide.drawImage(image, 0, 0, wideImage.getWidth(), - wideImage.getHeight(), null); - grWide.dispose(); - leftImage = wideImage.getSubimage(0, 0, textWidth, textHeight); - rightImage = wideImage.getSubimage(textWidth, 0, textWidth, - textHeight); - break; - } - left.setImage(leftImage); - right.setImage(rightImage); - putCharXY(x, y, left); - putCharXY(x + 1, y, right); + return terminal.waitForOutput(millis); } /** - * Set up the single and double-width fonts. + * Get the exit value for the emulator. * - * @param fontSize the size of font to request for the single-width font. - * The double-width font will be 2x this value. + * @return exit value */ - private void setupFonts(final int fontSize) { - try { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - InputStream in = loader.getResourceAsStream(SwingTerminal.FONTFILE); - Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in); - Font terminusDouble = terminusRoot.deriveFont(Font.PLAIN, - fontSize * 2); - doubleFont = terminusDouble; - } catch (java.awt.FontFormatException e) { - new TExceptionDialog(getApplication(), e); - doubleFont = new Font(Font.MONOSPACED, Font.PLAIN, fontSize * 2); - } catch (java.io.IOException e) { - new TExceptionDialog(getApplication(), e); - doubleFont = new Font(Font.MONOSPACED, Font.PLAIN, fontSize * 2); + public int getExitValue() { + if (terminal == null) { + return -1; } - - // Get font descent. - BufferedImage image = new BufferedImage(fontSize * 10, fontSize * 10, - BufferedImage.TYPE_INT_ARGB); - Graphics2D gr = image.createGraphics(); - gr.setFont(doubleFont); - FontMetrics fm = gr.getFontMetrics(); - doubleMaxDescent = fm.getMaxDescent(); - - gr.dispose(); - - // (Re)create the glyph caches. - glyphCache = new HashMap(); - glyphCacheBlink = new HashMap(); - - // Special case: the ECMA48 backend needs to have a timer to drive - // its blink state. - if (getScreen() instanceof jexer.backend.ECMA48Terminal) { - // Blink every 500 millis. - long millis = 500; - getApplication().addTimer(millis, true, - new TAction() { - public void DO() { - blinkState = !blinkState; - getApplication().doRepaint(); - } - } - ); - } - + return terminal.getExitValue(); } }