From 1d99a38f2b0f1f6674a21ba8b17be28bc7a34035 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 16 Aug 2017 21:18:29 -0400 Subject: [PATCH] Support TTerminalWindow resizing via ptypipe --- README.md | 26 +++++++++++------- docs/TODO.md | 4 --- src/jexer/TTerminalWindow.java | 26 +++++++++++++++++- src/jexer/tterminal/ECMA48.java | 47 ++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7e4a0e50..1e2b7e9d 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,14 @@ The following properties control features of Jexer: be faster on slower systems but also more likely to have screen tearing. Default: true. + jexer.TTerminal.ptypipe + ----------------------- + + Used by jexer.TTerminalWindow. If true, spawn shell using the + 'ptypipe' utility rather than 'script'. This permits terminals to + resize with the window. ptypipe is a separate C language utility, + available at https://github.com/klamonte/ptypipe. Default: false. + Known Issues / Arbitrary Decisions @@ -213,19 +221,19 @@ ambiguous. This section describes such issues. input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and SetConsoleMode()). - - TTerminalWindow launches 'script -fqe /dev/null' or 'script -q -F - /dev/null' on non-Windows platforms. This is a workaround for the - C library behavior of checking for a tty: script launches $SHELL - in a pseudo-tty. This works on Linux and Mac but might not on - other Posix-y platforms. + - TTerminalWindow by default launches 'script -fqe /dev/null' or + 'script -q -F /dev/null' on non-Windows platforms. This is a + workaround for the C library behavior of checking for a tty: + script launches $SHELL in a pseudo-tty. This works on Linux and + Mac but might not on other Posix-y platforms. - Closing a TTerminalWindow without exiting the process inside it may result in a zombie 'script' process. - - TTerminalWindow cannot notify the child process of changes in - window size, due to Java's lack of support for forkpty() and - similar. Solving this requires C, and will be pursued only if - sufficient user requests come in. + - TTerminalWindow can only notify the child process of changes in + window size if using the 'ptypipe' utility, due to Java's lack of + support for forkpty() and similar. ptypipe is available at + https://github.com/klamonte/ptypipe. - Java's InputStreamReader as used by the ECMA48 backend requires a valid UTF-8 stream. The default X10 encoding for mouse diff --git a/docs/TODO.md b/docs/TODO.md index 9b90b458..d717d4c6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -80,10 +80,6 @@ Roadmap 1.1.0 Wishlist -------------- -- TTerminal - - Handle resize events (pass to child process). Will need to switch - to forkpty(), or ship a C wrapper process. - - Screen - Allow complex characters in putCharXY() and detect them in putStringXY(). diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index 4b8bec49..89280fd1 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -60,6 +60,13 @@ public class TTerminalWindow extends TScrollableWindow */ private Process shell; + /** + * If true, we are using the ptypipe utility to support dynamic window + * resizing. ptypipe is available at + * https://github.com/klamonte/ptypipe . + */ + private boolean ptypipe = false; + /** * Claim the keystrokes the emulator will need. */ @@ -165,10 +172,19 @@ public class TTerminalWindow extends TScrollableWindow String [] cmdShellBSD = { "script", "-q", "-F", "/dev/null" }; + String [] cmdShellPtypipe = { + "ptypipe", "/bin/bash", "--login" + }; // Spawn a shell and pass its I/O to the other constructor. ProcessBuilder pb; - if (System.getProperty("os.name").startsWith("Windows")) { + if ((System.getProperty("jexer.TTerminal.ptypipe") != null) + && (System.getProperty("jexer.TTerminal.ptypipe"). + equals("true")) + ) { + pb = new ProcessBuilder(cmdShellPtypipe); + ptypipe = true; + } else if (System.getProperty("os.name").startsWith("Windows")) { pb = new ProcessBuilder(cmdShellWindows); } else if (System.getProperty("os.name").startsWith("Mac")) { pb = new ProcessBuilder(cmdShellBSD); @@ -429,6 +445,14 @@ public class TTerminalWindow extends TScrollableWindow // 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"); + } } return; diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 46952da4..5a30d374 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -204,7 +204,7 @@ public class ECMA48 implements Runnable { * * @param str string to send */ - private void writeRemote(final String str) { + public void writeRemote(final String str) { if (stopReaderThread) { // Reader hit EOF, bail out now. close(); @@ -221,6 +221,7 @@ public class ECMA48 implements Runnable { return; } try { + outputStream.flush(); for (int i = 0; i < str.length(); i++) { outputStream.write(str.charAt(i)); } @@ -235,6 +236,7 @@ public class ECMA48 implements Runnable { return; } try { + output.flush(); output.write(str); output.flush(); } catch (IOException e) { @@ -538,6 +540,22 @@ public class ECMA48 implements Runnable { return width; } + /** + * Set the display width. + * + * @param width the new width + */ + public final void setWidth(final int width) { + this.width = width; + rightMargin = width - 1; + if (currentState.cursorX >= width) { + currentState.cursorX = width - 1; + } + if (savedState.cursorX >= width) { + savedState.cursorX = width - 1; + } + } + /** * Physical display height. We start at 80x24, but the user can resize * us bigger/smaller. @@ -553,6 +571,33 @@ public class ECMA48 implements Runnable { return height; } + /** + * Set the display height. + * + * @param height the new height + */ + public final void setHeight(final int height) { + this.height = height; + scrollRegionBottom = height - 1; + if (scrollRegionTop >= scrollRegionBottom) { + scrollRegionTop = 0; + } + if (currentState.cursorY >= height) { + currentState.cursorY = height - 1; + } + if (savedState.cursorY >= height) { + savedState.cursorY = height - 1; + } + while (display.size() < height) { + DisplayLine line = new DisplayLine(currentState.attr); + line.setReverseColor(reverseVideo); + display.add(line); + } + while (display.size() > height) { + scrollback.add(display.remove(0)); + } + } + /** * Top margin of the scrolling region. */ -- 2.27.0