From 6405180cb8d84bb650f9bbcbfe5839dbd09b0481 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Tue, 9 Feb 2016 11:24:05 +0100 Subject: [PATCH] Wait for terminal size before starting process This fixes https://github.com/termux/termux-widget/issues/2, which was caused by the terminal launching the terminal session process before the terminal size was known. Also remove the built JNI libraries from source control. --- README.md | 2 +- .../main/java/com/termux/terminal/JNI.java | 2 +- .../com/termux/terminal/TerminalSession.java | 30 +++++++++++++----- app/src/main/jni/termux.c | 26 ++++++++++++--- app/src/main/jniLibs/armeabi-v7a/libtermux.so | Bin 13288 -> 0 bytes app/src/main/jniLibs/x86/libtermux.so | Bin 9084 -> 0 bytes 6 files changed, 45 insertions(+), 15 deletions(-) delete mode 100755 app/src/main/jniLibs/armeabi-v7a/libtermux.so delete mode 100755 app/src/main/jniLibs/x86/libtermux.so diff --git a/README.md b/README.md index 43d1cc36..e191f557 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Conta Building JNI libraries ====================== -For ease of use, the JNI libraries are checked into version control. Execute the `build-jnilibs.sh` script to rebuild them. +Execute the `build-jnilibs.sh` script to build the required JNI libraries. Terminal resources ================== diff --git a/app/src/main/java/com/termux/terminal/JNI.java b/app/src/main/java/com/termux/terminal/JNI.java index 423541a6..c0cff30a 100644 --- a/app/src/main/java/com/termux/terminal/JNI.java +++ b/app/src/main/java/com/termux/terminal/JNI.java @@ -28,7 +28,7 @@ final class JNI { * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr. */ - public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId); + public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns); /** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */ public static native void setPtyWindowSize(int fd, int rows, int cols); diff --git a/app/src/main/java/com/termux/terminal/TerminalSession.java b/app/src/main/java/com/termux/terminal/TerminalSession.java index e55d78ba..1ed9af7d 100644 --- a/app/src/main/java/com/termux/terminal/TerminalSession.java +++ b/app/src/main/java/com/termux/terminal/TerminalSession.java @@ -82,14 +82,17 @@ public final class TerminalSession extends TerminalOutput { /** Callback which gets notified when a session finishes or changes title. */ final SessionChangedCallback mChangeCallback; - /** The pid of the shell process or -1 if not running. */ + /** The pid of the shell process. 0 if not started and -1 if finished running. */ int mShellPid; - int mShellExitStatus = -1; + + /** The exit status of the shell process. Only valid if ${@link #mShellPid} is -1. */ + int mShellExitStatus; + /** * The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling * {@link JNI#createSubprocess(String, String, String[], String[], int[])}. */ - final int mTerminalFileDescriptor; + private int mTerminalFileDescriptor; /** Set by the application for user identification of session, not by terminal. */ public String mSessionName; @@ -128,20 +131,26 @@ public final class TerminalSession extends TerminalOutput { } }; + private final String mShellPath; + private final String mCwd; + private final String[] mArgs; + private final String[] mEnv; + public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) { mChangeCallback = changeCallback; - int[] processId = new int[1]; - mTerminalFileDescriptor = JNI.createSubprocess(shellPath, cwd, args, env, processId); - mShellPid = processId[0]; + this.mShellPath = shellPath; + this.mCwd = cwd; + this.mArgs = args; + this.mEnv = env; } /** Inform the attached pty of the new size and reflow or initialize the emulator. */ public void updateSize(int columns, int rows) { - JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns); if (mEmulator == null) { initializeEmulator(columns, rows); } else { + JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns); mEmulator.resize(columns, rows); } } @@ -161,6 +170,11 @@ public final class TerminalSession extends TerminalOutput { */ public void initializeEmulator(int columns, int rows) { mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */5000); + + int[] processId = new int[1]; + mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns); + mShellPid = processId[0]; + final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor); new Thread("TermSessionInputReader[pid=" + mShellPid + "]") { @@ -204,7 +218,7 @@ public final class TerminalSession extends TerminalOutput { /** Write data to the shell process. */ @Override public void write(byte[] data, int offset, int count) { - mTerminalToProcessIOQueue.write(data, offset, count); + if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count); } /** Write the Unicode code point to the terminal encoded in UTF-8. */ diff --git a/app/src/main/jni/termux.c b/app/src/main/jni/termux.c index ff3a31e0..6c5d8068 100644 --- a/app/src/main/jni/termux.c +++ b/app/src/main/jni/termux.c @@ -22,7 +22,14 @@ static int throw_runtime_exception(JNIEnv* env, char const* message) return -1; } -static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char* const argv[], char** envp, int* pProcessId) +static int create_subprocess(JNIEnv* env, + char const* cmd, + char const* cwd, + char* const argv[], + char** envp, + int* pProcessId, + jint rows, + jint columns) { int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC); if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx"); @@ -49,8 +56,8 @@ static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char tios.c_iflag &= ~(IXON | IXOFF); tcsetattr(ptm, TCSANOW, &tios); - /** Set initial winsize (better too small than too large). */ - struct winsize sz = { .ws_row = 20, .ws_col = 20 }; + /** Set initial winsize. */ + struct winsize sz = { .ws_row = rows, .ws_col = columns }; ioctl(ptm, TIOCSWINSZ, &sz); pid_t pid = fork(); @@ -105,7 +112,16 @@ static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char } } -JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(JNIEnv* env, jclass TERMUX_UNUSED(clazz), jstring cmd, jstring cwd, jobjectArray args, jobjectArray envVars, jintArray processIdArray) +JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess( + JNIEnv* env, + jclass TERMUX_UNUSED(clazz), + jstring cmd, + jstring cwd, + jobjectArray args, + jobjectArray envVars, + jintArray processIdArray, + jint rows, + jint columns) { jsize size = args ? (*env)->GetArrayLength(env, args) : 0; char** argv = NULL; @@ -140,7 +156,7 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(JNIEnv* env int procId = 0; char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL); char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL); - int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId); + int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns); (*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8); (*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd); diff --git a/app/src/main/jniLibs/armeabi-v7a/libtermux.so b/app/src/main/jniLibs/armeabi-v7a/libtermux.so deleted file mode 100755 index 3e66676607907f93431ea172ddc067ff76ea0b5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13288 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 diff --git a/app/src/main/jniLibs/x86/libtermux.so b/app/src/main/jniLibs/x86/libtermux.so deleted file mode 100755 index aef93ad0902b1e9dce7a279617a09af5fbfe921a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9084 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