diff --git a/terminal-emulator/build.gradle b/terminal-emulator/build.gradle deleted file mode 100644 index afc57e9f7e..0000000000 --- a/terminal-emulator/build.gradle +++ /dev/null @@ -1,75 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'maven-publish' - -android { - compileSdkVersion project.properties.compileSdkVersion.toInteger() - ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion - - defaultConfig { - minSdkVersion project.properties.minSdkVersion.toInteger() - targetSdkVersion project.properties.targetSdkVersion.toInteger() - - externalNativeBuild { - ndkBuild { - cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections" - } - } - - ndk { - abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - - externalNativeBuild { - ndkBuild { - path "src/main/jni/Android.mk" - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - testOptions { - unitTests.returnDefaultValues = true - } -} - -tasks.withType(Test) { - testLogging { - events "started", "passed", "skipped", "failed" - } -} - -dependencies { - implementation "androidx.annotation:annotation:1.3.0" - testImplementation "junit:junit:4.13.2" -} - -task sourceJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier "sources" -} - -afterEvaluate { - publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - from components.release - groupId = 'com.termux' - artifactId = 'terminal-emulator' - version = '0.118.0' - artifact(sourceJar) - } - } - } -} diff --git a/terminal-emulator/proguard-rules.pro b/terminal-emulator/proguard-rules.pro deleted file mode 100644 index 2e56e600a3..0000000000 --- a/terminal-emulator/proguard-rules.pro +++ /dev/null @@ -1,25 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/terminal-emulator/src/main/AndroidManifest.xml b/terminal-emulator/src/main/AndroidManifest.xml deleted file mode 100644 index a293cb643b..0000000000 --- a/terminal-emulator/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/terminal-emulator/src/main/java/com/termux/terminal/ByteQueue.java b/terminal-emulator/src/main/java/com/termux/terminal/ByteQueue.java deleted file mode 100644 index adfdfa8641..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/ByteQueue.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.termux.terminal; - -/** A circular byte buffer allowing one producer and one consumer thread. */ -final class ByteQueue { - - private final byte[] mBuffer; - private int mHead; - private int mStoredBytes; - private boolean mOpen = true; - - public ByteQueue(int size) { - mBuffer = new byte[size]; - } - - public synchronized void close() { - mOpen = false; - notify(); - } - - public synchronized int read(byte[] buffer, boolean block) { - while (mStoredBytes == 0 && mOpen) { - if (block) { - try { - wait(); - } catch (InterruptedException e) { - // Ignore. - } - } else { - return 0; - } - } - if (!mOpen) return -1; - - int totalRead = 0; - int bufferLength = mBuffer.length; - boolean wasFull = bufferLength == mStoredBytes; - int length = buffer.length; - int offset = 0; - while (length > 0 && mStoredBytes > 0) { - int oneRun = Math.min(bufferLength - mHead, mStoredBytes); - int bytesToCopy = Math.min(length, oneRun); - System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); - mHead += bytesToCopy; - if (mHead >= bufferLength) mHead = 0; - mStoredBytes -= bytesToCopy; - length -= bytesToCopy; - offset += bytesToCopy; - totalRead += bytesToCopy; - } - if (wasFull) notify(); - return totalRead; - } - - /** - * Attempt to write the specified portion of the provided buffer to the queue. - *

- * Returns whether the output was totally written, false if it was closed before. - */ - public boolean write(byte[] buffer, int offset, int lengthToWrite) { - if (lengthToWrite + offset > buffer.length) { - throw new IllegalArgumentException("length + offset > buffer.length"); - } else if (lengthToWrite <= 0) { - throw new IllegalArgumentException("length <= 0"); - } - - final int bufferLength = mBuffer.length; - - synchronized (this) { - while (lengthToWrite > 0) { - while (bufferLength == mStoredBytes && mOpen) { - try { - wait(); - } catch (InterruptedException e) { - // Ignore. - } - } - if (!mOpen) return false; - final boolean wasEmpty = mStoredBytes == 0; - int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes); - lengthToWrite -= bytesToWriteBeforeWaiting; - - while (bytesToWriteBeforeWaiting > 0) { - int tail = mHead + mStoredBytes; - int oneRun; - if (tail >= bufferLength) { - // Buffer: [.............] - // ________________H_______T - // => - // Buffer: [.............] - // ___________T____H - // onRun= _____----_ - tail = tail - bufferLength; - oneRun = mHead - tail; - } else { - oneRun = bufferLength - tail; - } - int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting); - System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); - offset += bytesToCopy; - bytesToWriteBeforeWaiting -= bytesToCopy; - mStoredBytes += bytesToCopy; - } - if (wasEmpty) notify(); - } - } - return true; - } -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/JNI.java b/terminal-emulator/src/main/java/com/termux/terminal/JNI.java deleted file mode 100644 index e2fc477b91..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/JNI.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.termux.terminal; - -/** - * Native methods for creating and managing pseudoterminal subprocesses. C code is in jni/termux.c. - */ -final class JNI { - - static { - System.loadLibrary("termux"); - } - - /** - * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the - * subprocess. - *

- * Callers are responsible for calling {@link #close(int)} on the returned file descriptor. - * - * @param cmd The command to execute - * @param cwd The current working directory for the executed command - * @param args An array of arguments to the command - * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process - * @param processId A one-element array to which the process ID of the started process will be written. - * @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, int rows, int columns, int cellWidth, int cellHeight); - - /** 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, int cellWidth, int cellHeight); - - /** - * Causes the calling thread to wait for the process associated with the receiver to finish executing. - * - * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated. - */ - public static native int waitFor(int processId); - - /** Close a file descriptor through the close(2) system call. */ - public static native void close(int fileDescriptor); - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/KeyHandler.java b/terminal-emulator/src/main/java/com/termux/terminal/KeyHandler.java deleted file mode 100644 index e6323c5992..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/KeyHandler.java +++ /dev/null @@ -1,373 +0,0 @@ -package com.termux.terminal; - -import java.util.HashMap; -import java.util.Map; - -import static android.view.KeyEvent.KEYCODE_BACK; -import static android.view.KeyEvent.KEYCODE_BREAK; -import static android.view.KeyEvent.KEYCODE_DEL; -import static android.view.KeyEvent.KEYCODE_DPAD_CENTER; -import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; -import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; -import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; -import static android.view.KeyEvent.KEYCODE_DPAD_UP; -import static android.view.KeyEvent.KEYCODE_ENTER; -import static android.view.KeyEvent.KEYCODE_ESCAPE; -import static android.view.KeyEvent.KEYCODE_F1; -import static android.view.KeyEvent.KEYCODE_F10; -import static android.view.KeyEvent.KEYCODE_F11; -import static android.view.KeyEvent.KEYCODE_F12; -import static android.view.KeyEvent.KEYCODE_F2; -import static android.view.KeyEvent.KEYCODE_F3; -import static android.view.KeyEvent.KEYCODE_F4; -import static android.view.KeyEvent.KEYCODE_F5; -import static android.view.KeyEvent.KEYCODE_F6; -import static android.view.KeyEvent.KEYCODE_F7; -import static android.view.KeyEvent.KEYCODE_F8; -import static android.view.KeyEvent.KEYCODE_F9; -import static android.view.KeyEvent.KEYCODE_FORWARD_DEL; -import static android.view.KeyEvent.KEYCODE_INSERT; -import static android.view.KeyEvent.KEYCODE_MOVE_END; -import static android.view.KeyEvent.KEYCODE_MOVE_HOME; -import static android.view.KeyEvent.KEYCODE_NUMPAD_0; -import static android.view.KeyEvent.KEYCODE_NUMPAD_1; -import static android.view.KeyEvent.KEYCODE_NUMPAD_2; -import static android.view.KeyEvent.KEYCODE_NUMPAD_3; -import static android.view.KeyEvent.KEYCODE_NUMPAD_4; -import static android.view.KeyEvent.KEYCODE_NUMPAD_5; -import static android.view.KeyEvent.KEYCODE_NUMPAD_6; -import static android.view.KeyEvent.KEYCODE_NUMPAD_7; -import static android.view.KeyEvent.KEYCODE_NUMPAD_8; -import static android.view.KeyEvent.KEYCODE_NUMPAD_9; -import static android.view.KeyEvent.KEYCODE_NUMPAD_ADD; -import static android.view.KeyEvent.KEYCODE_NUMPAD_COMMA; -import static android.view.KeyEvent.KEYCODE_NUMPAD_DIVIDE; -import static android.view.KeyEvent.KEYCODE_NUMPAD_DOT; -import static android.view.KeyEvent.KEYCODE_NUMPAD_ENTER; -import static android.view.KeyEvent.KEYCODE_NUMPAD_EQUALS; -import static android.view.KeyEvent.KEYCODE_NUMPAD_MULTIPLY; -import static android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT; -import static android.view.KeyEvent.KEYCODE_NUM_LOCK; -import static android.view.KeyEvent.KEYCODE_PAGE_DOWN; -import static android.view.KeyEvent.KEYCODE_PAGE_UP; -import static android.view.KeyEvent.KEYCODE_SPACE; -import static android.view.KeyEvent.KEYCODE_SYSRQ; -import static android.view.KeyEvent.KEYCODE_TAB; - -public final class KeyHandler { - - public static final int KEYMOD_ALT = 0x80000000; - public static final int KEYMOD_CTRL = 0x40000000; - public static final int KEYMOD_SHIFT = 0x20000000; - public static final int KEYMOD_NUM_LOCK = 0x10000000; - - private static final Map TERMCAP_TO_KEYCODE = new HashMap<>(); - - static { - // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html - // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html - TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT); - TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home - TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT); - TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key - - TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1); - TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2); - TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3); - TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4); - TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5); - TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6); - TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7); - TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8); - TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9); - TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10); - TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11); - TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12); - TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1); - TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2); - TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3); - TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4); - TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5); - TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6); - TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7); - TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8); - TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9); - TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10); - TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11); - TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12); - - TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key - - TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key - TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME); - TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT); - TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT); - - // K1=Upper left of keypad: - // t_K1 keypad home key - // t_K3 keypad page-up key - // t_K4 keypad end key - // t_K5 keypad page-down key - TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME); - TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP); - TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END); - TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN); - - TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP); - - TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab - TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key - TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down - TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key - TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT); - TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP); - TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN); - TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key - TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up - - TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END); - TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER); - } - - static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) { - Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap); - if (keyCodeAndMod == null) return null; - int keyCode = keyCodeAndMod; - int keyMod = 0; - if ((keyCode & KEYMOD_SHIFT) != 0) { - keyMod |= KEYMOD_SHIFT; - keyCode &= ~KEYMOD_SHIFT; - } - if ((keyCode & KEYMOD_CTRL) != 0) { - keyMod |= KEYMOD_CTRL; - keyCode &= ~KEYMOD_CTRL; - } - if ((keyCode & KEYMOD_ALT) != 0) { - keyMod |= KEYMOD_ALT; - keyCode &= ~KEYMOD_ALT; - } - if ((keyCode & KEYMOD_NUM_LOCK) != 0) { - keyMod |= KEYMOD_NUM_LOCK; - keyCode &= ~KEYMOD_NUM_LOCK; - } - return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication); - } - - public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) { - boolean numLockOn = (keyMode & KEYMOD_NUM_LOCK) != 0; - keyMode &= ~KEYMOD_NUM_LOCK; - switch (keyCode) { - case KEYCODE_DPAD_CENTER: - return "\015"; - - case KEYCODE_DPAD_UP: - return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A'); - case KEYCODE_DPAD_DOWN: - return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B'); - case KEYCODE_DPAD_RIGHT: - return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C'); - case KEYCODE_DPAD_LEFT: - return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D'); - - case KEYCODE_MOVE_HOME: - // Note that KEYCODE_HOME is handled by the system and never delivered to applications. - // On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow. - return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H'); - case KEYCODE_MOVE_END: - return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F'); - - // An xterm can send function keys F1 to F4 in two modes: vt100 compatible or - // not. Because Vim may not know what the xterm is sending, both types of keys - // are recognized. The same happens for the and keys. - // normal vt100 ~ - // t_k1 [11~ OP *-xterm* - // t_k2 [12~ OQ *-xterm* - // t_k3 [13~ OR *-xterm* - // t_k4 [14~ OS *-xterm* - // t_kh [7~ OH *-xterm* - // t_@7 [4~ OF *-xterm* - case KEYCODE_F1: - return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P'); - case KEYCODE_F2: - return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q'); - case KEYCODE_F3: - return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R'); - case KEYCODE_F4: - return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S'); - case KEYCODE_F5: - return transformForModifiers("\033[15", keyMode, '~'); - case KEYCODE_F6: - return transformForModifiers("\033[17", keyMode, '~'); - case KEYCODE_F7: - return transformForModifiers("\033[18", keyMode, '~'); - case KEYCODE_F8: - return transformForModifiers("\033[19", keyMode, '~'); - case KEYCODE_F9: - return transformForModifiers("\033[20", keyMode, '~'); - case KEYCODE_F10: - return transformForModifiers("\033[21", keyMode, '~'); - case KEYCODE_F11: - return transformForModifiers("\033[23", keyMode, '~'); - case KEYCODE_F12: - return transformForModifiers("\033[24", keyMode, '~'); - - case KEYCODE_SYSRQ: - return "\033[32~"; // Sys Request / Print - // Is this Scroll lock? case Cancel: return "\033[33~"; - case KEYCODE_BREAK: - return "\033[34~"; // Pause/Break - - case KEYCODE_ESCAPE: - case KEYCODE_BACK: - return "\033"; - - case KEYCODE_INSERT: - return transformForModifiers("\033[2", keyMode, '~'); - case KEYCODE_FORWARD_DEL: - return transformForModifiers("\033[3", keyMode, '~'); - - case KEYCODE_PAGE_UP: - return transformForModifiers("\033[5", keyMode, '~'); - case KEYCODE_PAGE_DOWN: - return transformForModifiers("\033[6", keyMode, '~'); - case KEYCODE_DEL: - String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033"; - // Just do what xterm and gnome-terminal does: - return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008"); - case KEYCODE_NUM_LOCK: - if (keypadApplication) { - return "\033OP"; - } else { - return null; - } - case KEYCODE_SPACE: - // If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a - // combining accent to be written): - return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0"; - case KEYCODE_TAB: - // This is back-tab when shifted: - return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z"; - case KEYCODE_ENTER: - return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r"; - - case KEYCODE_NUMPAD_ENTER: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n"; - case KEYCODE_NUMPAD_MULTIPLY: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*"; - case KEYCODE_NUMPAD_ADD: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+"; - case KEYCODE_NUMPAD_COMMA: - return ","; - case KEYCODE_NUMPAD_DOT: - if (numLockOn) { - return keypadApplication ? "\033On" : "."; - } else { - // DELETE - return transformForModifiers("\033[3", keyMode, '~'); - } - case KEYCODE_NUMPAD_SUBTRACT: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-"; - case KEYCODE_NUMPAD_DIVIDE: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/"; - case KEYCODE_NUMPAD_0: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0"; - } else { - // INSERT - return transformForModifiers("\033[2", keyMode, '~'); - } - case KEYCODE_NUMPAD_1: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1"; - } else { - // END - return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F'); - } - case KEYCODE_NUMPAD_2: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2"; - } else { - // DOWN - return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B'); - } - case KEYCODE_NUMPAD_3: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3"; - } else { - // PGDN - return "\033[6~"; - } - case KEYCODE_NUMPAD_4: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4"; - } else { - // LEFT - return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D'); - } - case KEYCODE_NUMPAD_5: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5"; - case KEYCODE_NUMPAD_6: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6"; - } else { - // RIGHT - return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C'); - } - case KEYCODE_NUMPAD_7: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7"; - } else { - // HOME - return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H'); - } - case KEYCODE_NUMPAD_8: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8"; - } else { - // UP - return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A'); - } - case KEYCODE_NUMPAD_9: - if (numLockOn) { - return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9"; - } else { - // PGUP - return "\033[5~"; - } - case KEYCODE_NUMPAD_EQUALS: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "="; - } - - return null; - } - - private static String transformForModifiers(String start, int keymod, char lastChar) { - int modifier; - switch (keymod) { - case KEYMOD_SHIFT: - modifier = 2; - break; - case KEYMOD_ALT: - modifier = 3; - break; - case (KEYMOD_SHIFT | KEYMOD_ALT): - modifier = 4; - break; - case KEYMOD_CTRL: - modifier = 5; - break; - case KEYMOD_SHIFT | KEYMOD_CTRL: - modifier = 6; - break; - case KEYMOD_ALT | KEYMOD_CTRL: - modifier = 7; - break; - case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL: - modifier = 8; - break; - default: - return start + lastChar; - } - return start + (";" + modifier) + lastChar; - } -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/Logger.java b/terminal-emulator/src/main/java/com/termux/terminal/Logger.java deleted file mode 100644 index d4d502e0e4..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/Logger.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.termux.terminal; - -import android.util.Log; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -public class Logger { - - public static void logError(TerminalSessionClient client, String logTag, String message) { - if (client != null) - client.logError(logTag, message); - else - Log.e(logTag, message); - } - - public static void logWarn(TerminalSessionClient client, String logTag, String message) { - if (client != null) - client.logWarn(logTag, message); - else - Log.w(logTag, message); - } - - public static void logInfo(TerminalSessionClient client, String logTag, String message) { - if (client != null) - client.logInfo(logTag, message); - else - Log.i(logTag, message); - } - - public static void logDebug(TerminalSessionClient client, String logTag, String message) { - if (client != null) - client.logDebug(logTag, message); - else - Log.d(logTag, message); - } - - public static void logVerbose(TerminalSessionClient client, String logTag, String message) { - if (client != null) - client.logVerbose(logTag, message); - else - Log.v(logTag, message); - } - - public static void logStackTraceWithMessage(TerminalSessionClient client, String tag, String message, Throwable throwable) { - logError(client, tag, getMessageAndStackTraceString(message, throwable)); - } - - public static String getMessageAndStackTraceString(String message, Throwable throwable) { - if (message == null && throwable == null) - return null; - else if (message != null && throwable != null) - return message + ":\n" + getStackTraceString(throwable); - else if (throwable == null) - return message; - else - return getStackTraceString(throwable); - } - - public static String getStackTraceString(Throwable throwable) { - if (throwable == null) return null; - - String stackTraceString = null; - - try { - StringWriter errors = new StringWriter(); - PrintWriter pw = new PrintWriter(errors); - throwable.printStackTrace(pw); - pw.close(); - stackTraceString = errors.toString(); - errors.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - return stackTraceString; - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java deleted file mode 100644 index 21d6518785..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java +++ /dev/null @@ -1,497 +0,0 @@ -package com.termux.terminal; - -import java.util.Arrays; - -/** - * A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll - * history. - *

- * See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices. - */ -public final class TerminalBuffer { - - TerminalRow[] mLines; - /** The length of {@link #mLines}. */ - int mTotalRows; - /** The number of rows and columns visible on the screen. */ - int mScreenRows, mColumns; - /** The number of rows kept in history. */ - private int mActiveTranscriptRows = 0; - /** The index in the circular buffer where the visible screen starts. */ - private int mScreenFirstRow = 0; - - /** - * Create a transcript screen. - * - * @param columns the width of the screen in characters. - * @param totalRows the height of the entire text area, in rows of text. - * @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off - * the top of the screen. - */ - public TerminalBuffer(int columns, int totalRows, int screenRows) { - mColumns = columns; - mTotalRows = totalRows; - mScreenRows = screenRows; - mLines = new TerminalRow[totalRows]; - - blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL); - } - - public String getTranscriptText() { - return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim(); - } - - public String getTranscriptTextWithoutJoinedLines() { - return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, false).trim(); - } - - public String getTranscriptTextWithFullLinesJoined() { - return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, true, true).trim(); - } - - public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { - return getSelectedText(selX1, selY1, selX2, selY2, true); - } - - public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) { - return getSelectedText(selX1, selY1, selX2, selY2, joinBackLines, false); - } - - public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines, boolean joinFullLines) { - final StringBuilder builder = new StringBuilder(); - final int columns = mColumns; - - if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows(); - if (selY2 >= mScreenRows) selY2 = mScreenRows - 1; - - for (int row = selY1; row <= selY2; row++) { - int x1 = (row == selY1) ? selX1 : 0; - int x2; - if (row == selY2) { - x2 = selX2 + 1; - if (x2 > columns) x2 = columns; - } else { - x2 = columns; - } - TerminalRow lineObject = mLines[externalToInternalRow(row)]; - int x1Index = lineObject.findStartOfColumn(x1); - int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed(); - if (x2Index == x1Index) { - // Selected the start of a wide character. - x2Index = lineObject.findStartOfColumn(x2 + 1); - } - char[] line = lineObject.mText; - int lastPrintingCharIndex = -1; - int i; - boolean rowLineWrap = getLineWrap(row); - if (rowLineWrap && x2 == columns) { - // If the line was wrapped, we shouldn't lose trailing space: - lastPrintingCharIndex = x2Index - 1; - } else { - for (i = x1Index; i < x2Index; ++i) { - char c = line[i]; - if (c != ' ') lastPrintingCharIndex = i; - } - } - - int len = lastPrintingCharIndex - x1Index + 1; - if (lastPrintingCharIndex != -1 && len > 0) - builder.append(line, x1Index, len); - - boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1; - if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth) - && row < selY2 && row < mScreenRows - 1) builder.append('\n'); - } - return builder.toString(); - } - - public String getWordAtLocation(int x, int y) { - // Set y1 and y2 to the lines where the wrapped line starts and ends. - // I.e. if a line that is wrapped to 3 lines starts at line 4, and this - // is called with y=5, then y1 would be set to 4 and y2 would be set to 6. - int y1 = y; - int y2 = y; - while (y1 > 0 && !getSelectedText(0, y1 - 1, mColumns, y, true, true).contains("\n")) { - y1--; - } - while (y2 < mScreenRows && !getSelectedText(0, y, mColumns, y2 + 1, true, true).contains("\n")) { - y2++; - } - - // Get the text for the whole wrapped line - String text = getSelectedText(0, y1, mColumns, y2, true, true); - // The index of x in text - int textOffset = (y - y1) * mColumns + x; - - if (textOffset >= text.length()) { - // The click was to the right of the last word on the line, so - // there's no word to return - return ""; - } - - // Set x1 and x2 to the indices of the last space before x and the - // first space after x in text respectively - int x1 = text.lastIndexOf(' ', textOffset); - int x2 = text.indexOf(' ', textOffset); - if (x2 == -1) { - x2 = text.length(); - } - - if (x1 == x2) { - // The click was on a space, so there's no word to return - return ""; - } - return text.substring(x1 + 1, x2); - } - - public int getActiveTranscriptRows() { - return mActiveTranscriptRows; - } - - public int getActiveRows() { - return mActiveTranscriptRows + mScreenRows; - } - - /** - * Convert a row value from the public external coordinate system to our internal private coordinate system. - * - *

-     * - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
-     * - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
-     *   mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
-     *
-     * External ↔ Internal:
-     *
-     * [ ...                            ]     [ ...                                     ]
-     * [ -mActiveTranscriptRows         ]     [ mScreenFirstRow - mActiveTranscriptRows ]
-     * [ ...                            ]     [ ...                                     ]
-     * [ 0 (visible screen starts here) ]  ↔  [ mScreenFirstRow                         ]
-     * [ ...                            ]     [ ...                                     ]
-     * [ mScreenRows-1                  ]     [ mScreenFirstRow + mScreenRows-1         ]
-     * 
- * - * @param externalRow a row in the external coordinate system. - * @return The row corresponding to the input argument in the private coordinate system. - */ - public int externalToInternalRow(int externalRow) { - if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows) - throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows); - final int internalRow = mScreenFirstRow + externalRow; - return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows); - } - - public void setLineWrap(int row) { - mLines[externalToInternalRow(row)].mLineWrap = true; - } - - public boolean getLineWrap(int row) { - return mLines[externalToInternalRow(row)].mLineWrap; - } - - public void clearLineWrap(int row) { - mLines[externalToInternalRow(row)].mLineWrap = false; - } - - /** - * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not - * change or the rows expand (that is, it only works when shrinking the number of rows). - * - * @param newColumns The number of columns the screen should have. - * @param newRows The number of rows the screen should have. - * @param cursor An int[2] containing the (column, row) cursor location. - */ - public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) { - // newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000): - if (newColumns == mColumns && newRows <= mTotalRows) { - // Fast resize where just the rows changed. - int shiftDownOfTopRow = mScreenRows - newRows; - if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) { - // Shrinking. Check if we can skip blank rows at bottom below cursor. - for (int i = mScreenRows - 1; i > 0; i--) { - if (cursor[1] >= i) break; - int r = externalToInternalRow(i); - if (mLines[r] == null || mLines[r].isBlank()) { - if (--shiftDownOfTopRow == 0) break; - } - } - } else if (shiftDownOfTopRow < 0) { - // Negative shift down = expanding. Only move screen up if there is transcript to show: - int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows); - if (shiftDownOfTopRow != actualShift) { - // The new lines revealed by the resizing are not all from the transcript. Blank the below ones. - for (int i = 0; i < actualShift - shiftDownOfTopRow; i++) - allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle); - shiftDownOfTopRow = actualShift; - } - } - mScreenFirstRow += shiftDownOfTopRow; - mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows); - mTotalRows = newTotalRows; - mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow); - cursor[1] -= shiftDownOfTopRow; - mScreenRows = newRows; - } else { - // Copy away old state and update new: - TerminalRow[] oldLines = mLines; - mLines = new TerminalRow[newTotalRows]; - for (int i = 0; i < newTotalRows; i++) - mLines[i] = new TerminalRow(newColumns, currentStyle); - - final int oldActiveTranscriptRows = mActiveTranscriptRows; - final int oldScreenFirstRow = mScreenFirstRow; - final int oldScreenRows = mScreenRows; - final int oldTotalRows = mTotalRows; - mTotalRows = newTotalRows; - mScreenRows = newRows; - mActiveTranscriptRows = mScreenFirstRow = 0; - mColumns = newColumns; - - int newCursorRow = -1; - int newCursorColumn = -1; - int oldCursorRow = cursor[1]; - int oldCursorColumn = cursor[0]; - boolean newCursorPlaced = false; - - int currentOutputExternalRow = 0; - int currentOutputExternalColumn = 0; - - // Loop over every character in the initial state. - // Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we - // keep track how many blank lines we have skipped if we later on find a non-blank line. - int skippedBlankLines = 0; - for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) { - // Do what externalToInternalRow() does but for the old state: - int internalOldRow = oldScreenFirstRow + externalOldRow; - internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows); - - TerminalRow oldLine = oldLines[internalOldRow]; - boolean cursorAtThisRow = externalOldRow == oldCursorRow; - // The cursor may only be on a non-null line, which we should not skip: - if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) { - skippedBlankLines++; - continue; - } else if (skippedBlankLines > 0) { - // After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines. - for (int i = 0; i < skippedBlankLines; i++) { - if (currentOutputExternalRow == mScreenRows - 1) { - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - skippedBlankLines = 0; - } - - int lastNonSpaceIndex = 0; - boolean justToCursor = false; - if (cursorAtThisRow || oldLine.mLineWrap) { - // Take the whole line, either because of cursor on it, or if line wrapping. - lastNonSpaceIndex = oldLine.getSpaceUsed(); - if (cursorAtThisRow) justToCursor = true; - } else { - for (int i = 0; i < oldLine.getSpaceUsed(); i++) - // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices - if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) - lastNonSpaceIndex = i + 1; - } - - int currentOldCol = 0; - long styleAtCol = 0; - for (int i = 0; i < lastNonSpaceIndex; i++) { - // Note that looping over java character, not cells. - char c = oldLine.mText[i]; - int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c; - int displayWidth = WcWidth.width(codePoint); - // Use the last style if this is a zero-width character: - if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol); - - // Line wrap as necessary: - if (currentOutputExternalColumn + displayWidth > mColumns) { - setLineWrap(currentOutputExternalRow); - if (currentOutputExternalRow == mScreenRows - 1) { - if (newCursorPlaced) newCursorRow--; - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - - int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0); - int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar; - setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol); - - if (displayWidth > 0) { - if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) { - newCursorColumn = currentOutputExternalColumn; - newCursorRow = currentOutputExternalRow; - newCursorPlaced = true; - } - currentOldCol += displayWidth; - currentOutputExternalColumn += displayWidth; - if (justToCursor && newCursorPlaced) break; - } - } - // Old row has been copied. Check if we need to insert newline if old line was not wrapping: - if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) { - if (currentOutputExternalRow == mScreenRows - 1) { - if (newCursorPlaced) newCursorRow--; - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - } - - cursor[0] = newCursorColumn; - cursor[1] = newCursorRow; - } - - // Handle cursor scrolling off screen: - if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0; - } - - /** - * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound - * into account. - * - * @param srcInternal The first line to be copied. - * @param len The number of lines to be copied. - */ - private void blockCopyLinesDown(int srcInternal, int len) { - if (len == 0) return; - int totalRows = mTotalRows; - - int start = len - 1; - // Save away line to be overwritten: - TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows]; - // Do the copy from bottom to top. - for (int i = start; i >= 0; --i) - mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows]; - // Put back overwritten line, now above the block: - mLines[(srcInternal) % totalRows] = lineToBeOverWritten; - } - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - * @param style the style for the newly exposed line. - */ - public void scrollDownOneLine(int topMargin, int bottomMargin, long style) { - if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows) - throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows); - - // Copy the fixed topMargin lines one line down so that they remain on screen in same position: - blockCopyLinesDown(mScreenFirstRow, topMargin); - // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same - // position: - blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin); - - // Update the screen location in the ring buffer: - mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows; - // Note that the history has grown if not already full: - if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++; - - // Blank the newly revealed line above the bottom margin: - int blankRow = externalToInternalRow(bottomMargin - 1); - if (mLines[blankRow] == null) { - mLines[blankRow] = new TerminalRow(mColumns, style); - } else { - mLines[blankRow].clear(style); - } - } - - /** - * Block copy characters from one position in the screen to another. The two positions can overlap. All characters - * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will - * be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { - if (w == 0) return; - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows) - throw new IllegalArgumentException(); - boolean copyingUp = sy > dy; - for (int y = 0; y < h; y++) { - int y2 = copyingUp ? y : (h - (y + 1)); - TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2)); - allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx); - } - } - - /** - * Block set characters. All characters must be within the bounds of the screen, or else and - * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block - * of characters. - */ - public void blockSet(int sx, int sy, int w, int h, int val, long style) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { - throw new IllegalArgumentException( - "Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")"); - } - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - setChar(sx + x, sy + y, val, style); - } - - public TerminalRow allocateFullLineIfNecessary(int row) { - return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row]; - } - - public void setChar(int column, int row, int codePoint, long style) { - if (row < 0 || row >= mScreenRows || column < 0 || column >= mColumns) - throw new IllegalArgumentException("TerminalBuffer.setChar(): row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns); - row = externalToInternalRow(row); - allocateFullLineIfNecessary(row).setChar(column, codePoint, style); - } - - public long getStyleAt(int externalRow, int column) { - return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column); - } - - /** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */ - public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left, - int bottom, int right) { - for (int y = top; y < bottom; y++) { - TerminalRow line = mLines[externalToInternalRow(y)]; - int startOfLine = (rectangular || y == top) ? left : leftMargin; - int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin; - for (int x = startOfLine; x < endOfLine; x++) { - long currentStyle = line.getStyle(x); - int foreColor = TextStyle.decodeForeColor(currentStyle); - int backColor = TextStyle.decodeBackColor(currentStyle); - int effect = TextStyle.decodeEffect(currentStyle); - if (reverse) { - // Clear out the bits to reverse and add them back in reversed: - effect = (effect & ~bits) | (bits & ~effect); - } else if (setOrClear) { - effect |= bits; - } else { - effect &= ~bits; - } - line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect); - } - } - } - - public void clearTranscript() { - if (mScreenFirstRow < mActiveTranscriptRows) { - Arrays.fill(mLines, mTotalRows + mScreenFirstRow - mActiveTranscriptRows, mTotalRows, null); - Arrays.fill(mLines, 0, mScreenFirstRow, null); - } else { - Arrays.fill(mLines, mScreenFirstRow - mActiveTranscriptRows, mScreenFirstRow, null); - } - mActiveTranscriptRows = 0; - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalColorScheme.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalColorScheme.java deleted file mode 100644 index 4088781805..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalColorScheme.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.termux.terminal; - -import java.util.Map; -import java.util.Properties; - -/** - * Color scheme for a terminal with default colors, which may be overridden (and then reset) from the shell using - * Operating System Control (OSC) sequences. - * - * @see TerminalColors - */ -public final class TerminalColorScheme { - - /** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */ - private static final int[] DEFAULT_COLORSCHEME = { - // 16 original colors. First 8 are dim. - 0xff000000, // black - 0xffcd0000, // dim red - 0xff00cd00, // dim green - 0xffcdcd00, // dim yellow - 0xff6495ed, // dim blue - 0xffcd00cd, // dim magenta - 0xff00cdcd, // dim cyan - 0xffe5e5e5, // dim white - // Second 8 are bright: - 0xff7f7f7f, // medium grey - 0xffff0000, // bright red - 0xff00ff00, // bright green - 0xffffff00, // bright yellow - 0xff5c5cff, // light blue - 0xffff00ff, // bright magenta - 0xff00ffff, // bright cyan - 0xffffffff, // bright white - - // 216 color cube, six shades of each color: - 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff, - 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, - 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff, - 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, - 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, - 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff, - 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, - 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff, - 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff, - 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, - 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff, - 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, - 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff, - 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff, - 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, - 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff, - 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, - 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff, - - // 24 grey scale ramp: - 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676, - 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, - - // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR: - 0xffffffff, 0xff000000, 0xffffffff}; - - public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS]; - - public TerminalColorScheme() { - reset(); - } - - private void reset() { - System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS); - } - - public void updateWith(Properties props) { - reset(); - boolean cursorPropExists = false; - for (Map.Entry entries : props.entrySet()) { - String key = (String) entries.getKey(); - String value = (String) entries.getValue(); - int colorIndex; - - if (key.equals("foreground")) { - colorIndex = TextStyle.COLOR_INDEX_FOREGROUND; - } else if (key.equals("background")) { - colorIndex = TextStyle.COLOR_INDEX_BACKGROUND; - } else if (key.equals("cursor")) { - colorIndex = TextStyle.COLOR_INDEX_CURSOR; - cursorPropExists = true; - } else if (key.startsWith("color")) { - try { - colorIndex = Integer.parseInt(key.substring(5)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid property: '" + key + "'"); - } - } else { - throw new IllegalArgumentException("Invalid property: '" + key + "'"); - } - - int colorValue = TerminalColors.parse(value); - if (colorValue == 0) - throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'"); - - mDefaultColors[colorIndex] = colorValue; - } - - if (!cursorPropExists) - setCursorColorForBackground(); - } - - /** - * If the "cursor" color is not set by user, we need to decide on the appropriate color that will - * be visible on the current terminal background. White will not be visible on light backgrounds - * and black won't be visible on dark backgrounds. So we find the perceived brightness of the - * background color and if its below the threshold (too dark), we use white cursor and if its - * above (too bright), we use black cursor. - */ - public void setCursorColorForBackground() { - int backgroundColor = mDefaultColors[TextStyle.COLOR_INDEX_BACKGROUND]; - int brightness = TerminalColors.getPerceivedBrightnessOfColor(backgroundColor); - if (brightness > 0) { - if (brightness < 130) - mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xffffffff; - else - mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xff000000; - } - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalColors.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalColors.java deleted file mode 100644 index 25135a2fab..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalColors.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.termux.terminal; - -import android.graphics.Color; - -/** Current terminal colors (if different from default). */ -public final class TerminalColors { - - /** Static data - a bit ugly but ok for now. */ - public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme(); - - /** - * The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC - * 4 control sequence. - */ - public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS]; - - /** Create a new instance with default colors from the theme. */ - public TerminalColors() { - reset(); - } - - /** Reset a particular indexed color with the default color from the color theme. */ - public void reset(int index) { - mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index]; - } - - /** Reset all indexed colors with the default color from the color theme. */ - public void reset() { - System.arraycopy(COLOR_SCHEME.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS); - } - - /** - * Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html - *

- * Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed. - */ - static int parse(String c) { - try { - int skipInitial, skipBetween; - if (c.charAt(0) == '#') { - // #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits. - skipInitial = 1; - skipBetween = 0; - } else if (c.startsWith("rgb:")) { - // rgb:// where , , := h | hh | hhh | hhhh. Scaled. - skipInitial = 4; - skipBetween = 1; - } else { - return 0; - } - int charsForColors = c.length() - skipInitial - 2 * skipBetween; - if (charsForColors % 3 != 0) return 0; // Unequal lengths. - int componentLength = charsForColors / 3; - double mult = 255 / (Math.pow(2, componentLength * 4) - 1); - - int currentPosition = skipInitial; - String rString = c.substring(currentPosition, currentPosition + componentLength); - currentPosition += componentLength + skipBetween; - String gString = c.substring(currentPosition, currentPosition + componentLength); - currentPosition += componentLength + skipBetween; - String bString = c.substring(currentPosition, currentPosition + componentLength); - - int r = (int) (Integer.parseInt(rString, 16) * mult); - int g = (int) (Integer.parseInt(gString, 16) * mult); - int b = (int) (Integer.parseInt(bString, 16) * mult); - return 0xFF << 24 | r << 16 | g << 8 | b; - } catch (NumberFormatException | IndexOutOfBoundsException e) { - return 0; - } - } - - /** Try parse a color from a text parameter and into a specified index. */ - public void tryParseColor(int intoIndex, String textParameter) { - int c = parse(textParameter); - if (c != 0) mCurrentColors[intoIndex] = c; - } - - /** - * Get the perceived brightness of the color based on its RGB components. - * - * https://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx - * http://alienryderflex.com/hsp.html - * - * @param color The color code int. - * @return Returns value between 0-255. - */ - public static int getPerceivedBrightnessOfColor(int color) { - return (int) - Math.floor(Math.sqrt( - Math.pow(Color.red(color), 2) * 0.241 + - Math.pow(Color.green(color), 2) * 0.691 + - Math.pow(Color.blue(color), 2) * 0.068 - )); - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java deleted file mode 100644 index 4e5a833c39..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java +++ /dev/null @@ -1,2574 +0,0 @@ -package com.termux.terminal; - -import android.util.Base64; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Locale; -import java.util.Objects; -import java.util.Stack; - -/** - * Renders text into a screen. Contains all the terminal-specific knowledge and state. Emulates a subset of the X Window - * System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal. - *

- * References: - *

- */ -public final class TerminalEmulator { - - /** Log unknown or unimplemented escape sequences received from the shell process. */ - private static final boolean LOG_ESCAPE_SEQUENCES = false; - - public static final int MOUSE_LEFT_BUTTON = 0; - - /** Mouse moving while having left mouse button pressed. */ - public static final int MOUSE_LEFT_BUTTON_MOVED = 32; - public static final int MOUSE_WHEELUP_BUTTON = 64; - public static final int MOUSE_WHEELDOWN_BUTTON = 65; - - /** Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */ - public static final int UNICODE_REPLACEMENT_CHAR = 0xFFFD; - - /** Escape processing: Not currently in an escape sequence. */ - private static final int ESC_NONE = 0; - /** Escape processing: Have seen an ESC character - proceed to {@link #doEsc(int)} */ - private static final int ESC = 1; - /** Escape processing: Have seen ESC POUND */ - private static final int ESC_POUND = 2; - /** Escape processing: Have seen ESC and a character-set-select ( char */ - private static final int ESC_SELECT_LEFT_PAREN = 3; - /** Escape processing: Have seen ESC and a character-set-select ) char */ - private static final int ESC_SELECT_RIGHT_PAREN = 4; - /** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */ - private static final int ESC_CSI = 6; - /** Escape processing: ESC [ ? */ - private static final int ESC_CSI_QUESTIONMARK = 7; - /** Escape processing: ESC [ $ */ - private static final int ESC_CSI_DOLLAR = 8; - /** Escape processing: ESC % */ - private static final int ESC_PERCENT = 9; - /** Escape processing: ESC ] (AKA OSC - Operating System Controls) */ - private static final int ESC_OSC = 10; - /** Escape processing: ESC ] (AKA OSC - Operating System Controls) ESC */ - private static final int ESC_OSC_ESC = 11; - /** Escape processing: ESC [ > */ - private static final int ESC_CSI_BIGGERTHAN = 12; - /** Escape procession: "ESC P" or Device Control String (DCS) */ - private static final int ESC_P = 13; - /** Escape processing: CSI > */ - private static final int ESC_CSI_QUESTIONMARK_ARG_DOLLAR = 14; - /** Escape processing: CSI $ARGS ' ' */ - private static final int ESC_CSI_ARGS_SPACE = 15; - /** Escape processing: CSI $ARGS '*' */ - private static final int ESC_CSI_ARGS_ASTERIX = 16; - /** Escape processing: CSI " */ - private static final int ESC_CSI_DOUBLE_QUOTE = 17; - /** Escape processing: CSI ' */ - private static final int ESC_CSI_SINGLE_QUOTE = 18; - /** Escape processing: CSI ! */ - private static final int ESC_CSI_EXCLAMATION = 19; - /** Escape processing: "ESC _" or Application Program Command (APC). */ - private static final int ESC_APC = 20; - /** Escape processing: "ESC _" or Application Program Command (APC), followed by Escape. */ - private static final int ESC_APC_ESCAPE = 21; - - /** The number of parameter arguments including colon separated sub-parameters. */ - private static final int MAX_ESCAPE_PARAMETERS = 32; - - /** Needs to be large enough to contain reasonable OSC 52 pastes. */ - private static final int MAX_OSC_STRING_LENGTH = 8192; - - /** DECSET 1 - application cursor keys. */ - private static final int DECSET_BIT_APPLICATION_CURSOR_KEYS = 1; - private static final int DECSET_BIT_REVERSE_VIDEO = 1 << 1; - /** - * http://www.vt100.net/docs/vt510-rm/DECOM: "When DECOM is set, the home cursor position is at the upper-left - * corner of the screen, within the margins. The starting point for line numbers depends on the current top margin - * setting. The cursor cannot move outside of the margins. When DECOM is reset, the home cursor position is at the - * upper-left corner of the screen. The starting point for line numbers is independent of the margins. The cursor - * can move outside of the margins." - */ - private static final int DECSET_BIT_ORIGIN_MODE = 1 << 2; - /** - * http://www.vt100.net/docs/vt510-rm/DECAWM: "If the DECAWM function is set, then graphic characters received when - * the cursor is at the right border of the page appear at the beginning of the next line. Any text on the page - * scrolls up if the cursor is at the end of the scrolling region. If the DECAWM function is reset, then graphic - * characters received when the cursor is at the right border of the page replace characters already on the page." - */ - private static final int DECSET_BIT_AUTOWRAP = 1 << 3; - /** DECSET 25 - if the cursor should be enabled, {@link #isCursorEnabled()}. */ - private static final int DECSET_BIT_CURSOR_ENABLED = 1 << 4; - private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5; - /** DECSET 1000 - if to report mouse press&release events. */ - private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6; - /** DECSET 1002 - like 1000, but report moving mouse while pressed. */ - private static final int DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT = 1 << 7; - /** DECSET 1004 - NOT implemented. */ - private static final int DECSET_BIT_SEND_FOCUS_EVENTS = 1 << 8; - /** DECSET 1006 - SGR-like mouse protocol (the modern sane choice). */ - private static final int DECSET_BIT_MOUSE_PROTOCOL_SGR = 1 << 9; - /** DECSET 2004 - see {@link #paste(String)} */ - private static final int DECSET_BIT_BRACKETED_PASTE_MODE = 1 << 10; - /** Toggled with DECLRMM - http://www.vt100.net/docs/vt510-rm/DECLRMM */ - private static final int DECSET_BIT_LEFTRIGHT_MARGIN_MODE = 1 << 11; - /** Not really DECSET bit... - http://www.vt100.net/docs/vt510-rm/DECSACE */ - private static final int DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 << 12; - - - private String mTitle; - private final Stack mTitleStack = new Stack<>(); - - /** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */ - private int mCursorRow, mCursorCol; - - /** The number of character rows and columns in the terminal screen. */ - public int mRows, mColumns; - - /** Size of a terminal cell in pixels. */ - private int mCellWidthPixels, mCellHeightPixels; - - /** The number of terminal transcript rows that can be scrolled back to. */ - public static final int TERMINAL_TRANSCRIPT_ROWS_MIN = 100; - public static final int TERMINAL_TRANSCRIPT_ROWS_MAX = 50000; - public static final int DEFAULT_TERMINAL_TRANSCRIPT_ROWS = 2000; - - - /* The supported terminal cursor styles. */ - - public static final int TERMINAL_CURSOR_STYLE_BLOCK = 0; - public static final int TERMINAL_CURSOR_STYLE_UNDERLINE = 1; - public static final int TERMINAL_CURSOR_STYLE_BAR = 2; - public static final int DEFAULT_TERMINAL_CURSOR_STYLE = TERMINAL_CURSOR_STYLE_BLOCK; - public static final Integer[] TERMINAL_CURSOR_STYLES_LIST = new Integer[]{TERMINAL_CURSOR_STYLE_BLOCK, TERMINAL_CURSOR_STYLE_UNDERLINE, TERMINAL_CURSOR_STYLE_BAR}; - - /** The terminal cursor styles. */ - private int mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE; - - - /** The normal screen buffer. Stores the characters that appear on the screen of the emulated terminal. */ - private final TerminalBuffer mMainBuffer; - /** - * The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when - * the alternate screen buffer is active, you cannot scroll back to view saved lines). - *

- * See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer - */ - final TerminalBuffer mAltBuffer; - /** The current screen buffer, pointing at either {@link #mMainBuffer} or {@link #mAltBuffer}. */ - private TerminalBuffer mScreen; - - /** The terminal session this emulator is bound to. */ - private final TerminalOutput mSession; - - TerminalSessionClient mClient; - - /** Keeps track of the current argument of the current escape sequence. Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. */ - private int mArgIndex; - /** Holds the arguments of the current escape sequence. */ - private final int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; - /** Holds the bit flags which arguments are sub parameters (after a colon) - bit N is set if mArgs[N] is a sub parameter. */ - private int mArgsSubParamsBitSet = 0; - - /** Holds OSC and device control arguments, which can be strings. */ - private final StringBuilder mOSCOrDeviceControlArgs = new StringBuilder(); - - /** - * True if the current escape sequence should continue, false if the current escape sequence should be terminated. - * Used when parsing a single character. - */ - private boolean mContinueSequence; - - /** The current state of the escape sequence state machine. One of the ESC_* constants. */ - private int mEscapeState; - - private final SavedScreenState mSavedStateMain = new SavedScreenState(); - private final SavedScreenState mSavedStateAlt = new SavedScreenState(); - - /** http://www.vt100.net/docs/vt102-ug/table5-15.html */ - private boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; - - /** - * @see TerminalEmulator#mapDecSetBitToInternalBit(int) - */ - private int mCurrentDecSetFlags, mSavedDecSetFlags; - - /** - * If insert mode (as opposed to replace mode) is active. In insert mode new characters are inserted, pushing - * existing text to the right. Characters moved past the right margin are lost. - */ - private boolean mInsertMode; - - /** An array of tab stops. mTabStop[i] is true if there is a tab stop set for column i. */ - private boolean[] mTabStop; - - /** - * Top margin of screen for scrolling ranges from 0 to mRows-2. Bottom margin ranges from mTopMargin + 2 to mRows - * (Defines the first row after the scrolling region). Left/right margin in [0, mColumns]. - */ - private int mTopMargin, mBottomMargin, mLeftMargin, mRightMargin; - - /** - * If the next character to be emitted will be automatically wrapped to the next line. Used to disambiguate the case - * where the cursor is positioned on the last column (mColumns-1). When standing there, a written character will be - * output in the last column, the cursor not moving but this flag will be set. When outputting another character - * this will move to the next line. - */ - private boolean mAboutToAutoWrap; - - /** - * If the cursor blinking is enabled. It requires cursor itself to be enabled, which is controlled - * byt whether {@link #DECSET_BIT_CURSOR_ENABLED} bit is set or not. - */ - private boolean mCursorBlinkingEnabled; - - /** - * If currently cursor should be in a visible state or not if {@link #mCursorBlinkingEnabled} - * is {@code true}. - */ - private boolean mCursorBlinkState; - - /** - * Current foreground, background and underline colors. Can either be a color index in [0,259] or a truecolor (24-bit) value. - * For a 24-bit value the top byte (0xff000000) is set. - * - *

Note that the underline color is currently parsed but not yet used during rendering. - * - * @see TextStyle - */ - int mForeColor, mBackColor, mUnderlineColor; - - /** Current {@link TextStyle} effect. */ - int mEffect; - - /** - * The number of scrolled lines since last calling {@link #clearScrollCounter()}. Used for moving selection up along - * with the scrolling text. - */ - private int mScrollCounter = 0; - - /** If automatic scrolling of terminal is disabled */ - private boolean mAutoScrollDisabled; - - private byte mUtf8ToFollow, mUtf8Index; - private final byte[] mUtf8InputBuffer = new byte[4]; - private int mLastEmittedCodePoint = -1; - - public final TerminalColors mColors = new TerminalColors(); - - private static final String LOG_TAG = "TerminalEmulator"; - - private boolean isDecsetInternalBitSet(int bit) { - return (mCurrentDecSetFlags & bit) != 0; - } - - private void setDecsetinternalBit(int internalBit, boolean set) { - if (set) { - // The mouse modes are mutually exclusive. - if (internalBit == DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) { - setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT, false); - } else if (internalBit == DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT) { - setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE, false); - } - } - if (set) { - mCurrentDecSetFlags |= internalBit; - } else { - mCurrentDecSetFlags &= ~internalBit; - } - } - - static int mapDecSetBitToInternalBit(int decsetBit) { - switch (decsetBit) { - case 1: - return DECSET_BIT_APPLICATION_CURSOR_KEYS; - case 5: - return DECSET_BIT_REVERSE_VIDEO; - case 6: - return DECSET_BIT_ORIGIN_MODE; - case 7: - return DECSET_BIT_AUTOWRAP; - case 25: - return DECSET_BIT_CURSOR_ENABLED; - case 66: - return DECSET_BIT_APPLICATION_KEYPAD; - case 69: - return DECSET_BIT_LEFTRIGHT_MARGIN_MODE; - case 1000: - return DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE; - case 1002: - return DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT; - case 1004: - return DECSET_BIT_SEND_FOCUS_EVENTS; - case 1006: - return DECSET_BIT_MOUSE_PROTOCOL_SGR; - case 2004: - return DECSET_BIT_BRACKETED_PASTE_MODE; - default: - return -1; - // throw new IllegalArgumentException("Unsupported decset: " + decsetBit); - } - } - - public TerminalEmulator(TerminalOutput session, int columns, int rows, int cellWidthPixels, int cellHeightPixels, Integer transcriptRows, TerminalSessionClient client) { - mSession = session; - mScreen = mMainBuffer = new TerminalBuffer(columns, getTerminalTranscriptRows(transcriptRows), rows); - mAltBuffer = new TerminalBuffer(columns, rows, rows); - mClient = client; - mRows = rows; - mColumns = columns; - mCellWidthPixels = cellWidthPixels; - mCellHeightPixels = cellHeightPixels; - mTabStop = new boolean[mColumns]; - reset(); - } - - public void updateTerminalSessionClient(TerminalSessionClient client) { - mClient = client; - setCursorStyle(); - setCursorBlinkState(true); - } - - public TerminalBuffer getScreen() { - return mScreen; - } - - public boolean isAlternateBufferActive() { - return mScreen == mAltBuffer; - } - - private int getTerminalTranscriptRows(Integer transcriptRows) { - if (transcriptRows == null || transcriptRows < TERMINAL_TRANSCRIPT_ROWS_MIN || transcriptRows > TERMINAL_TRANSCRIPT_ROWS_MAX) - return DEFAULT_TERMINAL_TRANSCRIPT_ROWS; - else - return transcriptRows; - } - - /** - * @param mouseButton one of the MOUSE_* constants of this class. - */ - public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) { - if (column < 1) column = 1; - if (column > mColumns) column = mColumns; - if (row < 1) row = 1; - if (row > mRows) row = mRows; - - if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) { - // Do not send tracking. - } else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) { - mSession.write(String.format("\033[<%d;%d;%d" + (pressed ? 'M' : 'm'), mouseButton, column, row)); - } else { - mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons. - // Clip to screen, and clip to the limits of 8-bit data. - boolean out_of_bounds = column > 255 - 32 || row > 255 - 32; - if (!out_of_bounds) { - byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)}; - mSession.write(data, 0, data.length); - } - } - } - - public void resize(int columns, int rows, int cellWidthPixels, int cellHeightPixels) { - this.mCellWidthPixels = cellWidthPixels; - this.mCellHeightPixels = cellHeightPixels; - - if (mRows == rows && mColumns == columns) { - return; - } else if (columns < 2 || rows < 2) { - throw new IllegalArgumentException("rows=" + rows + ", columns=" + columns); - } - - if (mRows != rows) { - mRows = rows; - mTopMargin = 0; - mBottomMargin = mRows; - } - if (mColumns != columns) { - int oldColumns = mColumns; - mColumns = columns; - boolean[] oldTabStop = mTabStop; - mTabStop = new boolean[mColumns]; - setDefaultTabStops(); - int toTransfer = Math.min(oldColumns, columns); - System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); - mLeftMargin = 0; - mRightMargin = mColumns; - } - - resizeScreen(); - } - - private void resizeScreen() { - final int[] cursor = {mCursorCol, mCursorRow}; - int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows; - mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive()); - mCursorCol = cursor[0]; - mCursorRow = cursor[1]; - } - - public int getCursorRow() { - return mCursorRow; - } - - public int getCursorCol() { - return mCursorCol; - } - - /** Get the terminal cursor style. It will be one of {@link #TERMINAL_CURSOR_STYLES_LIST} */ - public int getCursorStyle() { - return mCursorStyle; - } - - /** Set the terminal cursor style. */ - public void setCursorStyle() { - Integer cursorStyle = null; - - if (mClient != null) - cursorStyle = mClient.getTerminalCursorStyle(); - - if (cursorStyle == null || !Arrays.asList(TERMINAL_CURSOR_STYLES_LIST).contains(cursorStyle)) - mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE; - else - mCursorStyle = cursorStyle; - } - - public boolean isReverseVideo() { - return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO); - } - - - - public boolean isCursorEnabled() { - return isDecsetInternalBitSet(DECSET_BIT_CURSOR_ENABLED); - } - public boolean shouldCursorBeVisible() { - if (!isCursorEnabled()) - return false; - else - return mCursorBlinkingEnabled ? mCursorBlinkState : true; - } - - public void setCursorBlinkingEnabled(boolean cursorBlinkingEnabled) { - this.mCursorBlinkingEnabled = cursorBlinkingEnabled; - } - - public void setCursorBlinkState(boolean cursorBlinkState) { - this.mCursorBlinkState = cursorBlinkState; - } - - - - public boolean isKeypadApplicationMode() { - return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD); - } - - public boolean isCursorKeysApplicationMode() { - return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS); - } - - /** If mouse events are being sent as escape codes to the terminal. */ - public boolean isMouseTrackingActive() { - return isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) || isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT); - } - - private void setDefaultTabStops() { - for (int i = 0; i < mColumns; i++) - mTabStop[i] = (i & 7) == 0 && i != 0; - } - - /** - * Accept bytes (typically from the pseudo-teletype) and process them. - * - * @param buffer a byte array containing the bytes to be processed - * @param length the number of bytes in the array to process - */ - public void append(byte[] buffer, int length) { - for (int i = 0; i < length; i++) - processByte(buffer[i]); - } - - private void processByte(byte byteToProcess) { - if (mUtf8ToFollow > 0) { - if ((byteToProcess & 0b11000000) == 0b10000000) { - // 10xxxxxx, a continuation byte. - mUtf8InputBuffer[mUtf8Index++] = byteToProcess; - if (--mUtf8ToFollow == 0) { - byte firstByteMask = (byte) (mUtf8Index == 2 ? 0b00011111 : (mUtf8Index == 3 ? 0b00001111 : 0b00000111)); - int codePoint = (mUtf8InputBuffer[0] & firstByteMask); - for (int i = 1; i < mUtf8Index; i++) - codePoint = ((codePoint << 6) | (mUtf8InputBuffer[i] & 0b00111111)); - if (((codePoint <= 0b1111111) && mUtf8Index > 1) || (codePoint < 0b11111111111 && mUtf8Index > 2) - || (codePoint < 0b1111111111111111 && mUtf8Index > 3)) { - // Overlong encoding. - codePoint = UNICODE_REPLACEMENT_CHAR; - } - - mUtf8Index = mUtf8ToFollow = 0; - - if (codePoint >= 0x80 && codePoint <= 0x9F) { - // Sequence decoded to a C1 control character which we ignore. They are - // not used nowadays and increases the risk of messing up the terminal state - // on binary input. XTerm does not allow them in utf-8: - // "It is not possible to use a C1 control obtained from decoding the - // UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - } else { - switch (Character.getType(codePoint)) { - case Character.UNASSIGNED: - case Character.SURROGATE: - codePoint = UNICODE_REPLACEMENT_CHAR; - } - processCodePoint(codePoint); - } - } - } else { - // Not a UTF-8 continuation byte so replace the entire sequence up to now with the replacement char: - mUtf8Index = mUtf8ToFollow = 0; - emitCodePoint(UNICODE_REPLACEMENT_CHAR); - // The Unicode Standard Version 6.2 – Core Specification - // (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf): - // "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first - // byte, but which does not continue with valid successor bytes (see Table 3-7), it must not consume the - // successor bytes as part of the ill-formed subsequence - // whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit - // subsequence." - processByte(byteToProcess); - } - } else { - if ((byteToProcess & 0b10000000) == 0) { // The leading bit is not set so it is a 7-bit ASCII character. - processCodePoint(byteToProcess); - return; - } else if ((byteToProcess & 0b11100000) == 0b11000000) { // 110xxxxx, a two-byte sequence. - mUtf8ToFollow = 1; - } else if ((byteToProcess & 0b11110000) == 0b11100000) { // 1110xxxx, a three-byte sequence. - mUtf8ToFollow = 2; - } else if ((byteToProcess & 0b11111000) == 0b11110000) { // 11110xxx, a four-byte sequence. - mUtf8ToFollow = 3; - } else { - // Not a valid UTF-8 sequence start, signal invalid data: - processCodePoint(UNICODE_REPLACEMENT_CHAR); - return; - } - mUtf8InputBuffer[mUtf8Index++] = byteToProcess; - } - } - - public void processCodePoint(int b) { - // The Application Program-Control (APC) string might be arbitrary non-printable characters, so handle that early. - if (mEscapeState == ESC_APC) { - doApc(b); - return; - } else if (mEscapeState == ESC_APC_ESCAPE) { - doApcEscape(b); - return; - } - - switch (b) { - case 0: // Null character (NUL, ^@). Do nothing. - break; - case 7: // Bell (BEL, ^G, \a). If in an OSC sequence, BEL may terminate a string; otherwise signal bell. - if (mEscapeState == ESC_OSC) - doOsc(b); - else - mSession.onBell(); - break; - case 8: // Backspace (BS, ^H). - if (mLeftMargin == mCursorCol) { - // Jump to previous line if it was auto-wrapped. - int previousRow = mCursorRow - 1; - if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) { - mScreen.clearLineWrap(previousRow); - setCursorRowCol(previousRow, mRightMargin - 1); - } - } else { - setCursorCol(mCursorCol - 1); - } - break; - case 9: // Horizontal tab (HT, \t) - move to next tab stop, but not past edge of screen - // XXX: Should perhaps use color if writing to new cells. Try with - // printf "\033[41m\tXX\033[0m\n" - // The OSX Terminal.app colors the spaces from the tab red, but xterm does not. - // Note that Terminal.app only colors on new cells, in e.g. - // printf "\033[41m\t\r\033[42m\tXX\033[0m\n" - // the first cells are created with a red background, but when tabbing over - // them again with a green background they are not overwritten. - mCursorCol = nextTabStop(1); - break; - case 10: // Line feed (LF, \n). - case 11: // Vertical tab (VT, \v). - case 12: // Form feed (FF, \f). - doLinefeed(); - break; - case 13: // Carriage return (CR, \r). - setCursorCol(mLeftMargin); - break; - case 14: // Shift Out (Ctrl-N, SO) → Switch to Alternate Character Set. This invokes the G1 character set. - mUseLineDrawingUsesG0 = false; - break; - case 15: // Shift In (Ctrl-O, SI) → Switch to Standard Character Set. This invokes the G0 character set. - mUseLineDrawingUsesG0 = true; - break; - case 24: // CAN. - case 26: // SUB. - if (mEscapeState != ESC_NONE) { - // FIXME: What is this?? - mEscapeState = ESC_NONE; - emitCodePoint(127); - } - break; - case 27: // ESC - // Starts an escape sequence unless we're parsing a string - if (mEscapeState == ESC_P) { - // XXX: Ignore escape when reading device control sequence, since it may be part of string terminator. - return; - } else if (mEscapeState != ESC_OSC) { - startEscapeSequence(); - } else { - doOsc(b); - } - break; - default: - mContinueSequence = false; - switch (mEscapeState) { - case ESC_NONE: - if (b >= 32) emitCodePoint(b); - break; - case ESC: - doEsc(b); - break; - case ESC_POUND: - doEscPound(b); - break; - case ESC_SELECT_LEFT_PAREN: // Designate G0 Character Set (ISO 2022, VT100). - mUseLineDrawingG0 = (b == '0'); - break; - case ESC_SELECT_RIGHT_PAREN: // Designate G1 Character Set (ISO 2022, VT100). - mUseLineDrawingG1 = (b == '0'); - break; - case ESC_CSI: - doCsi(b); - break; - case ESC_CSI_EXCLAMATION: - if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR). - reset(); - } else { - unknownSequence(b); - } - break; - case ESC_CSI_QUESTIONMARK: - doCsiQuestionMark(b); - break; - case ESC_CSI_BIGGERTHAN: - doCsiBiggerThan(b); - break; - case ESC_CSI_DOLLAR: - boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); - int effectiveTopMargin = originMode ? mTopMargin : 0; - int effectiveBottomMargin = originMode ? mBottomMargin : mRows; - int effectiveLeftMargin = originMode ? mLeftMargin : 0; - int effectiveRightMargin = originMode ? mRightMargin : mColumns; - switch (b) { - case 'v': // ${CSI}${SRC_TOP}${SRC_LEFT}${SRC_BOTTOM}${SRC_RIGHT}${SRC_PAGE}${DST_TOP}${DST_LEFT}${DST_PAGE}$v" - // Copy rectangular area (DECCRA - http://vt100.net/docs/vt510-rm/DECCRA): - // "If Pbs is greater than Pts, or Pls is greater than Prs, the terminal ignores DECCRA. - // The coordinates of the rectangular area are affected by the setting of origin mode (DECOM). - // DECCRA is not affected by the page margins. - // The copied text takes on the line attributes of the destination area. - // If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, then the value - // is treated as the width or height of that page. - // If the destination area is partially off the page, then DECCRA clips the off-page data. - // DECCRA does not change the active cursor position." - int topSource = Math.min(getArg(0, 1, true) - 1 + effectiveTopMargin, mRows); - int leftSource = Math.min(getArg(1, 1, true) - 1 + effectiveLeftMargin, mColumns); - // Inclusive, so do not subtract one: - int bottomSource = Math.min(Math.max(getArg(2, mRows, true) + effectiveTopMargin, topSource), mRows); - int rightSource = Math.min(Math.max(getArg(3, mColumns, true) + effectiveLeftMargin, leftSource), mColumns); - // int sourcePage = getArg(4, 1, true); - int destionationTop = Math.min(getArg(5, 1, true) - 1 + effectiveTopMargin, mRows); - int destinationLeft = Math.min(getArg(6, 1, true) - 1 + effectiveLeftMargin, mColumns); - // int destinationPage = getArg(7, 1, true); - int heightToCopy = Math.min(mRows - destionationTop, bottomSource - topSource); - int widthToCopy = Math.min(mColumns - destinationLeft, rightSource - leftSource); - mScreen.blockCopy(leftSource, topSource, widthToCopy, heightToCopy, destinationLeft, destionationTop); - break; - case '{': // ${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${" - // Selective erase rectangular area (DECSERA - http://www.vt100.net/docs/vt510-rm/DECSERA). - case 'x': // ${CSI}${CHAR};${TOP}${LEFT}${BOTTOM}${RIGHT}$x" - // Fill rectangular area (DECFRA - http://www.vt100.net/docs/vt510-rm/DECFRA). - case 'z': // ${CSI}$${TOP}${LEFT}${BOTTOM}${RIGHT}$z" - // Erase rectangular area (DECERA - http://www.vt100.net/docs/vt510-rm/DECERA). - boolean erase = b != 'x'; - boolean selective = b == '{'; - // Only DECSERA keeps visual attributes, DECERA does not: - boolean keepVisualAttributes = erase && selective; - int argIndex = 0; - int fillChar = erase ? ' ' : getArg(argIndex++, -1, true); - // "Pch can be any value from 32 to 126 or from 160 to 255. If Pch is not in this range, then the - // terminal ignores the DECFRA command": - if ((fillChar >= 32 && fillChar <= 126) || (fillChar >= 160 && fillChar <= 255)) { - // "If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, the value - // is treated as the width or height of that page." - int top = Math.min(getArg(argIndex++, 1, true) + effectiveTopMargin, effectiveBottomMargin + 1); - int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1); - int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin); - int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin); - long style = getStyle(); - for (int row = top - 1; row < bottom; row++) - for (int col = left - 1; col < right; col++) - if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) - mScreen.setChar(col, row, fillChar, keepVisualAttributes ? mScreen.getStyleAt(row, col) : style); - } - break; - case 'r': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$r" - // Change attributes in rectangular area (DECCARA - http://vt100.net/docs/vt510-rm/DECCARA). - case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t" - // Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA). - boolean reverse = b == 't'; - // FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)". - int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin; - int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin; - int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin; - int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin; - if (mArgIndex >= 4) { - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 4; i <= mArgIndex; i++) { - int bits = 0; - boolean setOrClear = true; // True if setting, false if clearing. - switch (getArg(i, 0, false)) { - case 0: // Attributes off (no bold, no underline, no blink, positive image). - bits = (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE | TextStyle.CHARACTER_ATTRIBUTE_BLINK - | TextStyle.CHARACTER_ATTRIBUTE_INVERSE); - if (!reverse) setOrClear = false; - break; - case 1: // Bold. - bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - break; - case 4: // Underline. - bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - break; - case 5: // Blink. - bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; - break; - case 7: // Negative image. - bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - break; - case 22: // No bold. - bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - setOrClear = false; - break; - case 24: // No underline. - bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - setOrClear = false; - break; - case 25: // No blink. - bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; - setOrClear = false; - break; - case 27: // Positive image. - bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - setOrClear = false; - break; - } - if (reverse && !setOrClear) { - // Reverse attributes in rectangular area ignores non-(1,4,5,7) bits. - } else { - mScreen.setOrClearEffect(bits, setOrClear, reverse, isDecsetInternalBitSet(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE), - effectiveLeftMargin, effectiveRightMargin, top, left, bottom, right); - } - } - } else { - // Do nothing. - } - break; - default: - unknownSequence(b); - } - break; - case ESC_CSI_DOUBLE_QUOTE: - if (b == 'q') { - // http://www.vt100.net/docs/vt510-rm/DECSCA - int arg = getArg0(0); - if (arg == 0 || arg == 2) { - // DECSED and DECSEL can erase characters. - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; - } else if (arg == 1) { - // DECSED and DECSEL cannot erase characters. - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; - } else { - unknownSequence(b); - } - } else { - unknownSequence(b); - } - break; - case ESC_CSI_SINGLE_QUOTE: - if (b == '}') { // Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. - int columnsAfterCursor = mRightMargin - mCursorCol; - int columnsToInsert = Math.min(getArg0(1), columnsAfterCursor); - int columnsToMove = columnsAfterCursor - columnsToInsert; - mScreen.blockCopy(mCursorCol, 0, columnsToMove, mRows, mCursorCol + columnsToInsert, 0); - blockClear(mCursorCol, 0, columnsToInsert, mRows); - } else if (b == '~') { // Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. - int columnsAfterCursor = mRightMargin - mCursorCol; - int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor); - int columnsToMove = columnsAfterCursor - columnsToDelete; - mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0); - } else { - unknownSequence(b); - } - break; - case ESC_PERCENT: - break; - case ESC_OSC: - doOsc(b); - break; - case ESC_OSC_ESC: - doOscEsc(b); - break; - case ESC_P: - doDeviceControl(b); - break; - case ESC_CSI_QUESTIONMARK_ARG_DOLLAR: - if (b == 'p') { - // Request DEC private mode (DECRQM). - int mode = getArg0(0); - int value; - if (mode == 47 || mode == 1047 || mode == 1049) { - // This state is carried by mScreen pointer. - value = (mScreen == mAltBuffer) ? 1 : 2; - } else { - int internalBit = mapDecSetBitToInternalBit(mode); - if (internalBit != -1) { - value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset. - } else { - Logger.logError(mClient, LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode); - value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset - } - } - mSession.write(String.format(Locale.US, "\033[?%d;%d$y", mode, value)); - } else { - unknownSequence(b); - } - break; - case ESC_CSI_ARGS_SPACE: - int arg = getArg0(0); - switch (b) { - case 'q': // "${CSI}${STYLE} q" - set cursor style (http://www.vt100.net/docs/vt510-rm/DECSCUSR). - switch (arg) { - case 0: // Blinking block. - case 1: // Blinking block. - case 2: // Steady block. - mCursorStyle = TERMINAL_CURSOR_STYLE_BLOCK; - break; - case 3: // Blinking underline. - case 4: // Steady underline. - mCursorStyle = TERMINAL_CURSOR_STYLE_UNDERLINE; - break; - case 5: // Blinking bar (xterm addition). - case 6: // Steady bar (xterm addition). - mCursorStyle = TERMINAL_CURSOR_STYLE_BAR; - break; - } - break; - case 't': - case 'u': - // Set margin-bell volume - ignore. - break; - default: - unknownSequence(b); - } - break; - case ESC_CSI_ARGS_ASTERIX: - int attributeChangeExtent = getArg0(0); - if (b == 'x' && (attributeChangeExtent >= 0 && attributeChangeExtent <= 2)) { - // Select attribute change extent (DECSACE - http://www.vt100.net/docs/vt510-rm/DECSACE). - setDecsetinternalBit(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE, attributeChangeExtent == 2); - } else { - unknownSequence(b); - } - break; - default: - unknownSequence(b); - break; - } - if (!mContinueSequence) mEscapeState = ESC_NONE; - break; - } - } - - /** When in {@link #ESC_P} ("device control") sequence. */ - private void doDeviceControl(int b) { - switch (b) { - case (byte) '\\': // End of ESC \ string Terminator - { - String dcs = mOSCOrDeviceControlArgs.toString(); - // DCS $ q P t ST. Request Status String (DECRQSS) - if (dcs.startsWith("$q")) { - if (dcs.equals("$q\"p")) { - // DECSCL, conformance level, http://www.vt100.net/docs/vt510-rm/DECSCL: - String csiString = "64;1\"p"; - mSession.write("\033P1$r" + csiString + "\033\\"); - } else { - finishSequenceAndLogError("Unrecognized DECRQSS string: '" + dcs + "'"); - } - } else if (dcs.startsWith("+q")) { - // Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in - // hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key - // names. - // Two special features are also recognized, which are not key names: Co for termcap colors (or colors - // for terminfo colors), and TN for termcap name (or name for terminfo name). - // xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the - // corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are - // encoded in hexadecimal (2 digits per character). - // Example: - // :kr=\EOC: ks=\E[?1h\E=: ku=\EOA: le=^H:mb=\E[5m:md=\E[1m:\ - // where - // kd=down-arrow key - // kl=left-arrow key - // kr=right-arrow key - // ku=up-arrow key - // #2=key_shome, "shifted home" - // #4=key_sleft, "shift arrow left" - // %i=key_sright, "shift arrow right" - // *7=key_send, "shifted end" - // k1=F1 function key - - // Example: Request for ku is "ESC P + q 6 b 7 5 ESC \", where 6b7d=ku in hexadecimal. - // Xterm response in normal cursor mode: - // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x5B 0x41 = 27 91 65 = ESC [ A - // Xterm response in application cursor mode: - // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x4F 0x41 = 27 91 65 = ESC 0 A - - // #4 is "shift arrow left": - // *** Device Control (DCS) for '#4'- 'ESC P + q 23 34 ESC \' - // Response: <27> P 1 + r 2 3 3 4 = 1 B 5 B 3 1 3 B 3 2 4 4 <27> \ - // where 0x1B 0x5B 0x31 0x3B 0x32 0x44 = ESC [ 1 ; 2 D - // which we find in: TermKeyListener.java: KEY_MAP.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D"); - - // See http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40G_HTML/MAN/MAN4/0178____.HTM for what to - // respond, as well as http://www.freebsd.org/cgi/man.cgi?query=termcap&sektion=5#CAPABILITIES for - // the meaning of e.g. "ku", "kd", "kr", "kl" - - for (String part : dcs.substring(2).split(";")) { - if (part.length() % 2 == 0) { - StringBuilder transBuffer = new StringBuilder(); - char c; - for (int i = 0; i < part.length(); i += 2) { - try { - c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue(); - } catch (NumberFormatException e) { - Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Invalid device termcap/terminfo encoded name \"" + part + "\"", e); - continue; - } - transBuffer.append(c); - } - - String trans = transBuffer.toString(); - String responseValue; - switch (trans) { - case "Co": - case "colors": - responseValue = "256"; // Number of colors. - break; - case "TN": - case "name": - responseValue = "xterm"; - break; - default: - responseValue = KeyHandler.getCodeFromTermcap(trans, isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS), - isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD)); - break; - } - if (responseValue == null) { - switch (trans) { - case "%1": // Help key - ignore - case "&8": // Undo key - ignore. - break; - default: - Logger.logWarn(mClient, LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'"); - } - // Respond with invalid request: - mSession.write("\033P0+r" + part + "\033\\"); - } else { - StringBuilder hexEncoded = new StringBuilder(); - for (int j = 0; j < responseValue.length(); j++) { - hexEncoded.append(String.format("%02X", (int) responseValue.charAt(j))); - } - mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\"); - } - } else { - Logger.logError(mClient, LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part); - } - } - } else { - if (LOG_ESCAPE_SEQUENCES) - Logger.logError(mClient, LOG_TAG, "Unrecognized device control string: " + dcs); - } - finishSequence(); - } - break; - default: - if (mOSCOrDeviceControlArgs.length() > MAX_OSC_STRING_LENGTH) { - // Too long. - mOSCOrDeviceControlArgs.setLength(0); - finishSequence(); - } else { - mOSCOrDeviceControlArgs.appendCodePoint(b); - continueSequence(mEscapeState); - } - } - } - - /** - * When in {@link #ESC_APC} (APC, Application Program Command) sequence. - */ - private void doApc(int b) { - if (b == 27) { - continueSequence(ESC_APC_ESCAPE); - } - // Eat APC sequences silently for now. - } - - /** - * When in {@link #ESC_APC} (APC, Application Program Command) sequence. - */ - private void doApcEscape(int b) { - if (b == '\\') { - // A String Terminator (ST), ending the APC escape sequence. - finishSequence(); - } else { - // The Escape character was not the start of a String Terminator (ST), - // but instead just data inside of the APC escape sequence. - continueSequence(ESC_APC); - } - } - - private int nextTabStop(int numTabs) { - for (int i = mCursorCol + 1; i < mColumns; i++) - if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin); - return mRightMargin - 1; - } - - /** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */ - private void doCsiQuestionMark(int b) { - switch (b) { - case 'J': // Selective erase in display (DECSED) - http://www.vt100.net/docs/vt510-rm/DECSED. - case 'K': // Selective erase in line (DECSEL) - http://vt100.net/docs/vt510-rm/DECSEL. - mAboutToAutoWrap = false; - int fillChar = ' '; - int startCol = -1; - int startRow = -1; - int endCol = -1; - int endRow = -1; - boolean justRow = (b == 'K'); - switch (getArg0(0)) { - case 0: // Erase from the active position to the end, inclusive (default). - startCol = mCursorCol; - startRow = mCursorRow; - endCol = mColumns; - endRow = justRow ? (mCursorRow + 1) : mRows; - break; - case 1: // Erase from start to the active position, inclusive. - startCol = 0; - startRow = justRow ? mCursorRow : 0; - endCol = mCursorCol + 1; - endRow = mCursorRow + 1; - break; - case 2: // Erase all of the display/line. - startCol = 0; - startRow = justRow ? mCursorRow : 0; - endCol = mColumns; - endRow = justRow ? (mCursorRow + 1) : mRows; - break; - default: - unknownSequence(b); - break; - } - long style = getStyle(); - for (int row = startRow; row < endRow; row++) { - for (int col = startCol; col < endCol; col++) { - if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) - mScreen.setChar(col, row, fillChar, style); - } - } - break; - case 'h': - case 'l': - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) - doDecSetOrReset(b == 'h', mArgs[i]); - break; - case 'n': // Device Status Report (DSR, DEC-specific). - switch (getArg0(-1)) { - case 6: - // Extended Cursor Position (DECXCPR - http://www.vt100.net/docs/vt510-rm/DECXCPR). Page=1. - mSession.write(String.format(Locale.US, "\033[?%d;%d;1R", mCursorRow + 1, mCursorCol + 1)); - break; - default: - finishSequence(); - return; - } - break; - case 'r': - case 's': - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) { - int externalBit = mArgs[i]; - int internalBit = mapDecSetBitToInternalBit(externalBit); - if (internalBit == -1) { - Logger.logWarn(mClient, LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit); - } else { - if (b == 's') { - mSavedDecSetFlags |= internalBit; - } else { - doDecSetOrReset((mSavedDecSetFlags & internalBit) != 0, externalBit); - } - } - } - break; - case '$': - continueSequence(ESC_CSI_QUESTIONMARK_ARG_DOLLAR); - return; - default: - parseArg(b); - } - } - - public void doDecSetOrReset(boolean setting, int externalBit) { - int internalBit = mapDecSetBitToInternalBit(externalBit); - if (internalBit != -1) { - setDecsetinternalBit(internalBit, setting); - } - switch (externalBit) { - case 1: // Application Cursor Keys (DECCKM). - break; - case 3: // Set: 132 column mode (. Reset: 80 column mode. ANSI name: DECCOLM. - // We don't actually set/reset 132 cols, but we do want the side effects - // (FIXME: Should only do this if the 95 DECSET bit (DECNCSM) is set, and if changing value?): - // Sets the left, right, top and bottom scrolling margins to their default positions, which is important for - // the "reset" utility to really reset the terminal: - mLeftMargin = mTopMargin = 0; - mBottomMargin = mRows; - mRightMargin = mColumns; - // "DECCOLM resets vertical split screen mode (DECLRMM) to unavailable": - setDecsetinternalBit(DECSET_BIT_LEFTRIGHT_MARGIN_MODE, false); - // "Erases all data in page memory": - blockClear(0, 0, mColumns, mRows); - setCursorRowCol(0, 0); - break; - case 4: // DECSCLM-Scrolling Mode. Ignore. - break; - case 5: // Reverse video. No action. - break; - case 6: // Set: Origin Mode. Reset: Normal Cursor Mode. Ansi name: DECOM. - if (setting) setCursorPosition(0, 0); - break; - case 7: // Wrap-around bit, not specific action. - case 8: // Auto-repeat Keys (DECARM). Do not implement. - case 9: // X10 mouse reporting - outdated. Do not implement. - case 12: // Control cursor blinking - ignore. - case 25: // Hide/show cursor - no action needed, renderer will check with shouldCursorBeVisible(). - if (mClient != null) - mClient.onTerminalCursorStateChange(setting); - break; - case 40: // Allow 80 => 132 Mode, ignore. - case 45: // TODO: Reverse wrap-around. Implement??? - case 66: // Application keypad (DECNKM). - break; - case 69: // Left and right margin mode (DECLRMM). - if (!setting) { - mLeftMargin = 0; - mRightMargin = mColumns; - } - break; - case 1000: - case 1001: - case 1002: - case 1003: - case 1004: - case 1005: // UTF-8 mouse mode, ignore. - case 1006: // SGR Mouse Mode - case 1015: - case 1034: // Interpret "meta" key, sets eighth bit. - break; - case 1048: // Set: Save cursor as in DECSC. Reset: Restore cursor as in DECRC. - if (setting) - saveCursor(); - else - restoreCursor(); - break; - case 47: - case 1047: - case 1049: { - // Set: Save cursor as in DECSC and use Alternate Screen Buffer, clearing it first. - // Reset: Use Normal Screen Buffer and restore cursor as in DECRC. - TerminalBuffer newScreen = setting ? mAltBuffer : mMainBuffer; - if (newScreen != mScreen) { - boolean resized = !(newScreen.mColumns == mColumns && newScreen.mScreenRows == mRows); - if (setting) saveCursor(); - mScreen = newScreen; - if (!setting) { - int col = mSavedStateMain.mSavedCursorCol; - int row = mSavedStateMain.mSavedCursorRow; - restoreCursor(); - if (resized) { - // Restore cursor position _not_ clipped to current screen (let resizeScreen() handle that): - mCursorCol = col; - mCursorRow = row; - } - } - // Check if buffer size needs to be updated: - if (resized) resizeScreen(); - // Clear new screen if alt buffer: - if (newScreen == mAltBuffer) - newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle()); - } - break; - } - case 2004: - // Bracketed paste mode - setting bit is enough. - break; - default: - unknownParameter(externalBit); - break; - } - } - - private void doCsiBiggerThan(int b) { - switch (b) { - case 'c': // "${CSI}>c" or "${CSI}>c". Secondary Device Attributes (DA2). - // Originally this was used for the terminal to respond with "identification code, firmware version level, - // and hardware options" (http://vt100.net/docs/vt510-rm/DA2), with the first "41" meaning the VT420 - // terminal type. This is not used anymore, but the second version level field has been changed by xterm - // to mean it's release number ("patch numbers" listed at http://invisible-island.net/xterm/xterm.log.html), - // and some applications use it as a feature check: - // * tmux used to have a "xterm won't reach version 500 for a while so set that as the upper limit" check, - // and then check "xterm_version > 270" if rectangular area operations such as DECCRA could be used. - // * vim checks xterm version number >140 for "Request termcap/terminfo string" functionality >276 for SGR - // mouse report. - // The third number is a keyboard identifier not used nowadays. - mSession.write("\033[>41;320;0c"); - break; - case 'm': - // https://bugs.launchpad.net/gnome-terminal/+bug/96676/comments/25 - // Depending on the first number parameter, this can set one of the xterm resources - // modifyKeyboard, modifyCursorKeys, modifyFunctionKeys and modifyOtherKeys. - // http://invisible-island.net/xterm/manpage/xterm.html#RESOURCES - - // * modifyKeyboard (parameter=1): - // Normally xterm makes a special case regarding modifiers (shift, control, etc.) to handle special keyboard - // layouts (legacy and vt220). This is done to provide compatible keyboards for DEC VT220 and related - // terminals that implement user-defined keys (UDK). - // The bits of the resource value selectively enable modification of the given category when these keyboards - // are selected. The default is "0": - // (0) The legacy/vt220 keyboards interpret only the Control-modifier when constructing numbered - // function-keys. Other special keys are not modified. - // (1) allows modification of the numeric keypad - // (2) allows modification of the editing keypad - // (4) allows modification of function-keys, overrides use of Shift-modifier for UDK. - // (8) allows modification of other special keys - - // * modifyCursorKeys (parameter=2): - // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a - // parameter to the escape sequence returned by a cursor-key. The default is "2". - // - Set it to -1 to disable it. - // - Set it to 0 to use the old/obsolete behavior. - // - Set it to 1 to prefix modified sequences with CSI. - // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. - // - Set it to 3 to mark the sequence with a ">" to hint that it is private. - - // * modifyFunctionKeys (parameter=3): - // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a - // parameter to the escape sequence returned by a (numbered) function- - // key. The default is "2". The resource values are similar to modifyCursorKeys: - // Set it to -1 to permit the user to use shift- and control-modifiers to construct function-key strings - // using the normal encoding scheme. - // - Set it to 0 to use the old/obsolete behavior. - // - Set it to 1 to prefix modified sequences with CSI. - // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. - // - Set it to 3 to mark the sequence with a ">" to hint that it is private. - // If modifyFunctionKeys is zero, xterm uses Control- and Shift-modifiers to allow the user to construct - // numbered function-keys beyond the set provided by the keyboard: - // (Control) adds the value given by the ctrlFKeys resource. - // (Shift) adds twice the value given by the ctrlFKeys resource. - // (Control/Shift) adds three times the value given by the ctrlFKeys resource. - // - // As a special case, legacy (when oldFunctionKeys is true) or vt220 (when sunKeyboard is true) - // keyboards interpret only the Control-modifier when constructing numbered function-keys. - // This is done to provide compatible keyboards for DEC VT220 and related terminals that - // implement user-defined keys (UDK). - - // * modifyOtherKeys (parameter=4): - // Like modifyCursorKeys, tells xterm to construct an escape sequence for other keys (such as "2") when - // modified by Control-, Alt- or Meta-modifiers. This feature does not apply to function keys and - // well-defined keys such as ESC or the control keys. The default is "0". - // (0) disables this feature. - // (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and - // some special control character cases, e.g., Control-Space to make a NUL. - // (2) enables this feature for keys including the exceptions listed. - Logger.logError(mClient, LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1)); - break; - default: - parseArg(b); - break; - } - } - - private void startEscapeSequence() { - mEscapeState = ESC; - mArgIndex = 0; - Arrays.fill(mArgs, -1); - mArgsSubParamsBitSet = 0; - } - - private void doLinefeed() { - boolean belowScrollingRegion = mCursorRow >= mBottomMargin; - int newCursorRow = mCursorRow + 1; - if (belowScrollingRegion) { - // Move down (but not scroll) as long as we are above the last row. - if (mCursorRow != mRows - 1) { - setCursorRow(newCursorRow); - } - } else { - if (newCursorRow == mBottomMargin) { - scrollDownOneLine(); - newCursorRow = mBottomMargin - 1; - } - setCursorRow(newCursorRow); - } - } - - private void continueSequence(int state) { - mEscapeState = state; - mContinueSequence = true; - } - - private void doEscPound(int b) { - switch (b) { - case '8': // Esc # 8 - DEC screen alignment test - fill screen with E's. - mScreen.blockSet(0, 0, mColumns, mRows, 'E', getStyle()); - break; - default: - unknownSequence(b); - break; - } - } - - /** Encountering a character in the {@link #ESC} state. */ - private void doEsc(int b) { - switch (b) { - case '#': - continueSequence(ESC_POUND); - break; - case '(': - continueSequence(ESC_SELECT_LEFT_PAREN); - break; - case ')': - continueSequence(ESC_SELECT_RIGHT_PAREN); - break; - case '6': // Back index (http://www.vt100.net/docs/vt510-rm/DECBI). Move left, insert blank column if start. - if (mCursorCol > mLeftMargin) { - mCursorCol--; - } else { - int rows = mBottomMargin - mTopMargin; - mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin + 1, mTopMargin); - mScreen.blockSet(mLeftMargin, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); - } - break; - case '7': // DECSC save cursor - http://www.vt100.net/docs/vt510-rm/DECSC - saveCursor(); - break; - case '8': // DECRC restore cursor - http://www.vt100.net/docs/vt510-rm/DECRC - restoreCursor(); - break; - case '9': // Forward Index (http://www.vt100.net/docs/vt510-rm/DECFI). Move right, insert blank column if end. - if (mCursorCol < mRightMargin - 1) { - mCursorCol++; - } else { - int rows = mBottomMargin - mTopMargin; - mScreen.blockCopy(mLeftMargin + 1, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin, mTopMargin); - mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); - } - break; - case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS). - reset(); - mMainBuffer.clearTranscript(); - blockClear(0, 0, mColumns, mRows); - setCursorPosition(0, 0); - break; - case 'D': // INDEX - doLinefeed(); - break; - case 'E': // Next line (http://www.vt100.net/docs/vt510-rm/NEL). - setCursorCol(isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE) ? mLeftMargin : 0); - doLinefeed(); - break; - case 'F': // Cursor to lower-left corner of screen - setCursorRowCol(0, mBottomMargin - 1); - break; - case 'H': // Tab set - mTabStop[mCursorCol] = true; - break; - case 'M': // "${ESC}M" - reverse index (RI). - // http://www.vt100.net/docs/vt100-ug/chapter3.html: "Move the active position to the same horizontal - // position on the preceding line. If the active position is at the top margin, a scroll down is performed". - if (mCursorRow <= mTopMargin) { - mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, mBottomMargin - (mTopMargin + 1), mLeftMargin, mTopMargin + 1); - blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin); - } else { - mCursorRow--; - } - break; - case 'N': // SS2, ignore. - case '0': // SS3, ignore. - break; - case 'P': // Device control string - mOSCOrDeviceControlArgs.setLength(0); - continueSequence(ESC_P); - break; - case '[': - continueSequence(ESC_CSI); - break; - case '=': // DECKPAM - setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true); - break; - case ']': // OSC - mOSCOrDeviceControlArgs.setLength(0); - continueSequence(ESC_OSC); - break; - case '>': // DECKPNM - setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false); - break; - case '_': // APC - Application Program Command. - continueSequence(ESC_APC); - break; - default: - unknownSequence(b); - break; - } - } - - /** DECSC save cursor - http://www.vt100.net/docs/vt510-rm/DECSC . See {@link #restoreCursor()}. */ - private void saveCursor() { - SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; - state.mSavedCursorRow = mCursorRow; - state.mSavedCursorCol = mCursorCol; - state.mSavedEffect = mEffect; - state.mSavedForeColor = mForeColor; - state.mSavedBackColor = mBackColor; - state.mSavedDecFlags = mCurrentDecSetFlags; - state.mUseLineDrawingG0 = mUseLineDrawingG0; - state.mUseLineDrawingG1 = mUseLineDrawingG1; - state.mUseLineDrawingUsesG0 = mUseLineDrawingUsesG0; - } - - /** DECRS restore cursor - http://www.vt100.net/docs/vt510-rm/DECRC. See {@link #saveCursor()}. */ - private void restoreCursor() { - SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; - setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol); - mEffect = state.mSavedEffect; - mForeColor = state.mSavedForeColor; - mBackColor = state.mSavedBackColor; - int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE); - mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask); - mUseLineDrawingG0 = state.mUseLineDrawingG0; - mUseLineDrawingG1 = state.mUseLineDrawingG1; - mUseLineDrawingUsesG0 = state.mUseLineDrawingUsesG0; - } - - /** Following a CSI - Control Sequence Introducer, "\033[". {@link #ESC_CSI}. */ - private void doCsi(int b) { - switch (b) { - case '!': - continueSequence(ESC_CSI_EXCLAMATION); - break; - case '"': - continueSequence(ESC_CSI_DOUBLE_QUOTE); - break; - case '\'': - continueSequence(ESC_CSI_SINGLE_QUOTE); - break; - case '$': - continueSequence(ESC_CSI_DOLLAR); - break; - case '*': - continueSequence(ESC_CSI_ARGS_ASTERIX); - break; - case '@': { - // "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH. - mAboutToAutoWrap = false; - int columnsAfterCursor = mColumns - mCursorCol; - int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor); - int charsToMove = columnsAfterCursor - spacesToInsert; - mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, mCursorCol + spacesToInsert, mCursorRow); - blockClear(mCursorCol, mCursorRow, spacesToInsert); - } - break; - case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows. - setCursorRow(Math.max(0, mCursorRow - getArg0(1))); - break; - case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows. - setCursorRow(Math.min(mRows - 1, mCursorRow + getArg0(1))); - break; - case 'C': // "CSI${n}C" - Cursor forward (CUF). - case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48. - setCursorCol(Math.min(mRightMargin - 1, mCursorCol + getArg0(1))); - break; - case 'D': // "CSI${n}D" - Cursor backward (CUB) ${n} columns. - setCursorCol(Math.max(mLeftMargin, mCursorCol - getArg0(1))); - break; - case 'E': // "CSI{n}E - Cursor Next Line (CNL). From ISO-6428/ECMA-48. - setCursorPosition(0, mCursorRow + getArg0(1)); - break; - case 'F': // "CSI{n}F - Cursor Previous Line (CPL). From ISO-6428/ECMA-48. - setCursorPosition(0, mCursorRow - getArg0(1)); - break; - case 'G': // "CSI${n}G" - Cursor horizontal absolute (CHA) to column ${n}. - setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); - break; - case 'H': // "${CSI}${ROW};${COLUMN}H" - Cursor position (CUP). - case 'f': // "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). - setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); - break; - case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward. - setCursorCol(nextTabStop(getArg0(1))); - break; - case 'J': // "${CSI}${0,1,2,3}J" - Erase in Display (ED) - // ED ignores the scrolling margins. - switch (getArg0(0)) { - case 0: // Erase from the active position to the end of the screen, inclusive (default). - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - blockClear(0, mCursorRow + 1, mColumns, mRows - (mCursorRow + 1)); - break; - case 1: // Erase from start of the screen to the active position, inclusive. - blockClear(0, 0, mColumns, mCursorRow); - blockClear(0, mCursorRow, mCursorCol + 1); - break; - case 2: // Erase all of the display - all lines are erased, changed to single-width, and the cursor does not - // move.. - blockClear(0, 0, mColumns, mRows); - break; - case 3: // Delete all lines saved in the scrollback buffer (xterm etc) - mMainBuffer.clearTranscript(); - break; - default: - unknownSequence(b); - return; - } - mAboutToAutoWrap = false; - break; - case 'K': // "CSI{n}K" - Erase in line (EL). - switch (getArg0(0)) { - case 0: // Erase from the cursor to the end of the line, inclusive (default) - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - break; - case 1: // Erase from the start of the screen to the cursor, inclusive. - blockClear(0, mCursorRow, mCursorCol + 1); - break; - case 2: // Erase all of the line. - blockClear(0, mCursorRow, mColumns); - break; - default: - unknownSequence(b); - return; - } - mAboutToAutoWrap = false; - break; - case 'L': // "${CSI}{N}L" - insert ${N} lines (IL). - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToInsert = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToInsert; - mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, mCursorRow + linesToInsert); - blockClear(0, mCursorRow, mColumns, linesToInsert); - } - break; - case 'M': // "${CSI}${N}M" - delete N lines (DL). - { - mAboutToAutoWrap = false; - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToDelete = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToDelete; - mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, linesToMove, 0, mCursorRow); - blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); - } - break; - case 'P': // "${CSI}{N}P" - delete ${N} characters (DCH). - { - // http://www.vt100.net/docs/vt510-rm/DCH: "If ${N} is greater than the number of characters between the - // cursor and the right margin, then DCH only deletes the remaining characters. - // As characters are deleted, the remaining characters between the cursor and right margin move to the left. - // Character attributes move with the characters. The terminal adds blank spaces with no visual character - // attributes at the right margin. DCH has no effect outside the scrolling margins." - mAboutToAutoWrap = false; - int cellsAfterCursor = mColumns - mCursorCol; - int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor); - int cellsToMove = cellsAfterCursor - cellsToDelete; - mScreen.blockCopy(mCursorCol + cellsToDelete, mCursorRow, cellsToMove, 1, mCursorCol, mCursorRow); - blockClear(mCursorCol + cellsToMove, mCursorRow, cellsToDelete); - } - break; - case 'S': { // "${CSI}${N}S" - scroll up ${N} lines (default = 1) (SU). - final int linesToScroll = getArg0(1); - for (int i = 0; i < linesToScroll; i++) - scrollDownOneLine(); - break; - } - case 'T': - if (mArgIndex == 0) { - // "${CSI}${N}T" - Scroll down N lines (default = 1) (SD). - // http://vt100.net/docs/vt510-rm/SD: "N is the number of lines to move the user window up in page - // memory. N new lines appear at the top of the display. N old lines disappear at the bottom of the - // display. You cannot pan past the top margin of the current page". - final int linesToScrollArg = getArg0(1); - final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin; - final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg); - mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesBetweenTopAndBottomMargins - linesToScroll, mLeftMargin, mTopMargin + linesToScroll); - blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesToScroll); - } else { - // "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking. - unimplementedSequence(b); - } - break; - case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes? - mAboutToAutoWrap = false; - mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle()); - break; - case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward. - int numberOfTabs = getArg0(1); - int newCol = mLeftMargin; - for (int i = mCursorCol - 1; i >= 0; i--) - if (mTabStop[i]) { - if (--numberOfTabs == 0) { - newCol = Math.max(i, mLeftMargin); - break; - } - } - mCursorCol = newCol; - break; - case '?': // Esc [ ? -- start of a private mode set - continueSequence(ESC_CSI_QUESTIONMARK); - break; - case '>': // "Esc [ >" -- - continueSequence(ESC_CSI_BIGGERTHAN); - break; - case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA). - setCursorColRespectingOriginMode(getArg0(1) - 1); - break; - case 'b': // Repeat the preceding graphic character Ps times (REP). - if (mLastEmittedCodePoint == -1) break; - final int numRepeat = getArg0(1); - for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint); - break; - case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero. - // The important part that may still be used by some (tmux stores this value but does not currently use it) - // is the first response parameter identifying the terminal service class, where we send 64 for "vt420". - // This is followed by a list of attributes which is probably unused by applications. Send like xterm. - if (getArg0(0) == 0) mSession.write("\033[?64;1;2;6;9;15;18;21;22c"); - break; - case 'd': // ESC [ Pn d - Vert Position Absolute - setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); - break; - case 'e': // Vertical Position Relative (VPR). From ISO-6429 (ECMA-48). - setCursorPosition(mCursorCol, mCursorRow + getArg0(1)); - break; - // case 'f': "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). Grouped with case 'H'. - case 'g': // Clear tab stop - switch (getArg0(0)) { - case 0: - mTabStop[mCursorCol] = false; - break; - case 3: - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = false; - } - break; - default: - // Specified to have no effect. - break; - } - break; - case 'h': // Set Mode - doSetMode(true); - break; - case 'l': // Reset Mode - doSetMode(false); - break; - case 'm': // Esc [ Pn m - character attributes. (can have up to 16 numerical arguments) - selectGraphicRendition(); - break; - case 'n': // Esc [ Pn n - ECMA-48 Status Report Commands - // sendDeviceAttributes() - switch (getArg0(0)) { - case 5: // Device status report (DSR): - // Answer is ESC [ 0 n (Terminal OK). - byte[] dsr = {(byte) 27, (byte) '[', (byte) '0', (byte) 'n'}; - mSession.write(dsr, 0, dsr.length); - break; - case 6: // Cursor position report (CPR): - // Answer is ESC [ y ; x R, where x,y is - // the cursor location. - mSession.write(String.format(Locale.US, "\033[%d;%dR", mCursorRow + 1, mCursorCol + 1)); - break; - default: - break; - } - break; - case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM). - { - // https://vt100.net/docs/vt510-rm/DECSTBM.html - // The top margin defaults to 1, the bottom margin defaults to mRows. - // The escape sequence numbers top 1..23, but we number top 0..22. - // The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering - // scheme, but we store the first line below the bottom-most scrolling line. - // As a result, we adjust the top line by -1, but we leave the bottom line alone. - // Also require that top + 2 <= bottom. - mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); - mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows)); - - // DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode. - setCursorPosition(0, 0); - } - break; - case 's': - if (isDecsetInternalBitSet(DECSET_BIT_LEFTRIGHT_MARGIN_MODE)) { - // Set left and right margins (DECSLRM - http://www.vt100.net/docs/vt510-rm/DECSLRM). - mLeftMargin = Math.min(getArg0(1) - 1, mColumns - 2); - mRightMargin = Math.max(mLeftMargin + 1, Math.min(getArg1(mColumns), mColumns)); - // DECSLRM moves the cursor to column 1, line 1 of the page. - setCursorPosition(0, 0); - } else { - // Save cursor (ANSI.SYS), available only when DECLRMM is disabled. - saveCursor(); - } - break; - case 't': // Window manipulation (from dtterm, as well as extensions) - switch (getArg0(0)) { - case 11: // Report xterm window state. If the xterm window is open (non-iconified), it returns CSI 1 t . - mSession.write("\033[1t"); - break; - case 13: // Report xterm window position. Result is CSI 3 ; x ; y t - mSession.write("\033[3;0;0t"); - break; - case 14: // Report xterm window in pixels. Result is CSI 4 ; height ; width t - mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * mCellHeightPixels, mColumns * mCellWidthPixels)); - break; - case 16: // Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t - mSession.write(String.format(Locale.US, "\033[6;%d;%dt", mCellHeightPixels, mCellWidthPixels)); - break; - case 18: // Report the size of the text area in characters. Result is CSI 8 ; height ; width t - mSession.write(String.format(Locale.US, "\033[8;%d;%dt", mRows, mColumns)); - break; - case 19: // Report the size of the screen in characters. Result is CSI 9 ; height ; width t - // We report the same size as the view, since it's the view really isn't resizable from the shell. - mSession.write(String.format(Locale.US, "\033[9;%d;%dt", mRows, mColumns)); - break; - case 20: // Report xterm windows icon label. Result is OSC L label ST. Disabled due to security concerns: - mSession.write("\033]LIconLabel\033\\"); - break; - case 21: // Report xterm windows title. Result is OSC l label ST. Disabled due to security concerns: - mSession.write("\033]l\033\\"); - break; - case 22: - // 22;0 -> Save xterm icon and window title on stack. - // 22;1 -> Save xterm icon title on stack. - // 22;2 -> Save xterm window title on stack. - mTitleStack.push(mTitle); - if (mTitleStack.size() > 20) { - // Limit size - mTitleStack.remove(0); - } - break; - case 23: // Like 22 above but restore from stack. - if (!mTitleStack.isEmpty()) setTitle(mTitleStack.pop()); - break; - default: - // Ignore window manipulation. - break; - } - break; - case 'u': // Restore cursor (ANSI.SYS). - restoreCursor(); - break; - case ' ': - continueSequence(ESC_CSI_ARGS_SPACE); - break; - default: - parseArg(b); - break; - } - } - - /** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */ - private void selectGraphicRendition() { - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) { - // Skip leading sub parameters: - if ((mArgsSubParamsBitSet & (1 << i)) != 0) { - continue; - } - - int code = getArg(i, 0, false); - if (code < 0) { - if (mArgIndex > 0) { - continue; - } else { - code = 0; - } - } - if (code == 0) { // reset - mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - mEffect = 0; - } else if (code == 1) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BOLD; - } else if (code == 2) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_DIM; - } else if (code == 3) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_ITALIC; - } else if (code == 4) { - if (i + 1 <= mArgIndex && ((mArgsSubParamsBitSet & (1 << (i + 1))) != 0)) { - // Sub parameter, see https://sw.kovidgoyal.net/kitty/underlines/ - i++; - if (mArgs[i] == 0) { - // No underline. - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } else { - // Different variations of underlines: https://sw.kovidgoyal.net/kitty/underlines/ - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } - } else { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } - } else if (code == 5) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BLINK; - } else if (code == 7) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - } else if (code == 8) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; - } else if (code == 9) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; - } else if (code == 10) { - // Exit alt charset (TERM=linux) - ignore. - } else if (code == 11) { - // Enter alt charset (TERM=linux) - ignore. - } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint. - mEffect &= ~(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_DIM); - } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_ITALIC; - } else if (code == 24) { // underline: none - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } else if (code == 25) { // blink: none - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_BLINK; - } else if (code == 27) { // image: positive - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - } else if (code == 28) { - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; - } else if (code == 29) { - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; - } else if (code >= 30 && code <= 37) { - mForeColor = code - 30; - } else if (code == 38 || code == 48 || code == 58) { - // Extended set foreground(38)/background(48)/underline(58) color. - // This is followed by either "2;$R;$G;$B" to set a 24-bit color or - // "5;$INDEX" to set an indexed color. - if (i + 2 > mArgIndex) continue; - int firstArg = mArgs[i + 1]; - if (firstArg == 2) { - if (i + 4 > mArgIndex) { - Logger.logWarn(mClient, LOG_TAG, "Too few CSI" + code + ";2 RGB arguments"); - } else { - int red = getArg(i + 2, 0, false); - int green = getArg(i + 3, 0, false); - int blue = getArg(i + 4, 0, false); - - if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) { - finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue); - } else { - int argbColor = 0xff_00_00_00 | (red << 16) | (green << 8) | blue; - switch (code) { - case 38: mForeColor = argbColor; break; - case 48: mBackColor = argbColor; break; - case 58: mUnderlineColor = argbColor; break; - } - } - i += 4; // "2;P_r;P_g;P_r" - } - } else if (firstArg == 5) { - int color = getArg(i + 2, 0, false); - i += 2; // "5;P_s" - if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) { - switch (code) { - case 38: mForeColor = color; break; - case 48: mBackColor = color; break; - case 58: mUnderlineColor = color; break; - } - } else { - if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, "Invalid color index: " + color); - } - } else { - finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg); - } - } else if (code == 39) { // Set default foreground color. - mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - } else if (code >= 40 && code <= 47) { // Set background color. - mBackColor = code - 40; - } else if (code == 49) { // Set default background color. - mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - } else if (code == 59) { // Set default underline color. - mUnderlineColor = TextStyle.COLOR_INDEX_FOREGROUND; - } else if (code >= 90 && code <= 97) { // Bright foreground colors (aixterm codes). - mForeColor = code - 90 + 8; - } else if (code >= 100 && code <= 107) { // Bright background color (aixterm codes). - mBackColor = code - 100 + 8; - } else { - if (LOG_ESCAPE_SEQUENCES) - Logger.logWarn(mClient, LOG_TAG, String.format("SGR unknown code %d", code)); - } - } - } - - private void doOsc(int b) { - switch (b) { - case 7: // Bell. - doOscSetTextParameters("\007"); - break; - case 27: // Escape. - continueSequence(ESC_OSC_ESC); - break; - default: - collectOSCArgs(b); - break; - } - } - - private void doOscEsc(int b) { - switch (b) { - case '\\': - doOscSetTextParameters("\033\\"); - break; - default: - // The ESC character was not followed by a \, so insert the ESC and - // the current character in arg buffer. - collectOSCArgs(27); - collectOSCArgs(b); - continueSequence(ESC_OSC); - break; - } - } - - /** An Operating System Controls (OSC) Set Text Parameters. May come here from BEL or ST. */ - private void doOscSetTextParameters(String bellOrStringTerminator) { - int value = -1; - String textParameter = ""; - // Extract initial $value from initial "$value;..." string. - for (int mOSCArgTokenizerIndex = 0; mOSCArgTokenizerIndex < mOSCOrDeviceControlArgs.length(); mOSCArgTokenizerIndex++) { - char b = mOSCOrDeviceControlArgs.charAt(mOSCArgTokenizerIndex); - if (b == ';') { - textParameter = mOSCOrDeviceControlArgs.substring(mOSCArgTokenizerIndex + 1); - break; - } else if (b >= '0' && b <= '9') { - value = ((value < 0) ? 0 : value * 10) + (b - '0'); - } else { - unknownSequence(b); - return; - } - } - - switch (value) { - case 0: // Change icon name and window title to T. - case 1: // Change icon name to T. - case 2: // Change window title to T. - setTitle(textParameter); - break; - case 4: - // P s = 4 ; c ; spec → Change Color Number c to the color specified by spec. This can be a name or RGB - // specification as per XParseColor. Any number of c name pairs may be given. The color numbers correspond - // to the ANSI colors 0-7, their bright versions 8-15, and if supported, the remainder of the 88-color or - // 256-color table. - // If a "?" is given rather than a name or RGB specification, xterm replies with a control sequence of the - // same form which can be used to set the corresponding color. Because more than one pair of color number - // and specification can be given in one control sequence, xterm can make more than one reply. - int colorIndex = -1; - int parsingPairStart = -1; - for (int i = 0; ; i++) { - boolean endOfInput = i == textParameter.length(); - char b = endOfInput ? ';' : textParameter.charAt(i); - if (b == ';') { - if (parsingPairStart < 0) { - parsingPairStart = i + 1; - } else { - if (colorIndex < 0 || colorIndex > 255) { - unknownSequence(b); - return; - } else { - mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i)); - mSession.onColorsChanged(); - colorIndex = -1; - parsingPairStart = -1; - } - } - } else if (parsingPairStart >= 0) { - // We have passed a color index and are now going through color spec. - } else if (parsingPairStart < 0 && (b >= '0' && b <= '9')) { - colorIndex = ((colorIndex < 0) ? 0 : colorIndex * 10) + (b - '0'); - } else { - unknownSequence(b); - return; - } - if (endOfInput) break; - } - break; - case 10: // Set foreground color. - case 11: // Set background color. - case 12: // Set cursor color. - int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10); - int lastSemiIndex = 0; - for (int charIndex = 0; ; charIndex++) { - boolean endOfInput = charIndex == textParameter.length(); - if (endOfInput || textParameter.charAt(charIndex) == ';') { - try { - String colorSpec = textParameter.substring(lastSemiIndex, charIndex); - if ("?".equals(colorSpec)) { - // Report current color in the same format xterm and gnome-terminal does. - int rgb = mColors.mCurrentColors[specialIndex]; - int r = (65535 * ((rgb & 0x00FF0000) >> 16)) / 255; - int g = (65535 * ((rgb & 0x0000FF00) >> 8)) / 255; - int b = (65535 * ((rgb & 0x000000FF))) / 255; - mSession.write("\033]" + value + ";rgb:" + String.format(Locale.US, "%04x", r) + "/" + String.format(Locale.US, "%04x", g) + "/" - + String.format(Locale.US, "%04x", b) + bellOrStringTerminator); - } else { - mColors.tryParseColor(specialIndex, colorSpec); - mSession.onColorsChanged(); - } - specialIndex++; - if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) - break; - lastSemiIndex = charIndex; - } catch (NumberFormatException e) { - // Ignore. - } - } - } - break; - case 52: // Manipulate Selection Data. Skip the optional first selection parameter(s). - int startIndex = textParameter.indexOf(";") + 1; - try { - String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8); - mSession.onCopyTextToClipboard(clipboardText); - } catch (Exception e) { - Logger.logError(mClient, LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + ""); - } - break; - case 104: - // "104;$c" → Reset Color Number $c. It is reset to the color specified by the corresponding X - // resource. Any number of c parameters may be given. These parameters correspond to the ANSI colors 0-7, - // their bright versions 8-15, and if supported, the remainder of the 88-color or 256-color table. If no - // parameters are given, the entire table will be reset. - if (textParameter.isEmpty()) { - mColors.reset(); - mSession.onColorsChanged(); - } else { - int lastIndex = 0; - for (int charIndex = 0; ; charIndex++) { - boolean endOfInput = charIndex == textParameter.length(); - if (endOfInput || textParameter.charAt(charIndex) == ';') { - try { - int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex)); - mColors.reset(colorToReset); - mSession.onColorsChanged(); - if (endOfInput) break; - charIndex++; - lastIndex = charIndex; - } catch (NumberFormatException e) { - // Ignore. - } - } - } - } - break; - case 110: // Reset foreground color. - case 111: // Reset background color. - case 112: // Reset cursor color. - mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110)); - mSession.onColorsChanged(); - break; - case 119: // Reset highlight color. - break; - default: - unknownParameter(value); - break; - } - finishSequence(); - } - - private void blockClear(int sx, int sy, int w) { - blockClear(sx, sy, w, 1); - } - - private void blockClear(int sx, int sy, int w, int h) { - mScreen.blockSet(sx, sy, w, h, ' ', getStyle()); - } - - private long getStyle() { - return TextStyle.encode(mForeColor, mBackColor, mEffect); - } - - /** "CSI P_m h" for set or "CSI P_m l" for reset ANSI mode. */ - private void doSetMode(boolean newValue) { - int modeBit = getArg0(0); - switch (modeBit) { - case 4: // Set="Insert Mode". Reset="Replace Mode". (IRM). - mInsertMode = newValue; - break; - case 20: // Normal Linefeed (LNM). - unknownParameter(modeBit); - // http://www.vt100.net/docs/vt510-rm/LNM - break; - case 34: - // Normal cursor visibility - when using TERM=screen, see - // http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html - break; - default: - unknownParameter(modeBit); - break; - } - } - - /** - * NOTE: The parameters of this function respect the {@link #DECSET_BIT_ORIGIN_MODE}. Use - * {@link #setCursorRowCol(int, int)} for absolute pos. - */ - private void setCursorPosition(int x, int y) { - boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); - int effectiveTopMargin = originMode ? mTopMargin : 0; - int effectiveBottomMargin = originMode ? mBottomMargin : mRows; - int effectiveLeftMargin = originMode ? mLeftMargin : 0; - int effectiveRightMargin = originMode ? mRightMargin : mColumns; - int newRow = Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, effectiveBottomMargin - 1)); - int newCol = Math.max(effectiveLeftMargin, Math.min(effectiveLeftMargin + x, effectiveRightMargin - 1)); - setCursorRowCol(newRow, newCol); - } - - private void scrollDownOneLine() { - mScrollCounter++; - long currentStyle = getStyle(); - if (mLeftMargin != 0 || mRightMargin != mColumns) { - // Horizontal margin: Do not put anything into scroll history, just non-margin part of screen up. - mScreen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin); - // .. and blank bottom row between margins: - mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', currentStyle); - } else { - mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, currentStyle); - } - } - - /** - * Process the next ASCII character of a parameter. - * - *

You must use the ; character to separate parameters and : to separate sub-parameters. - * - *

Parameter characters modify the action or interpretation of the sequence. Originally - * you can use up to 16 parameters per sequence, but following at least xterm and alacritty - * we use a common space for parameters and sub-parameters, allowing 32 in total. - * - *

All parameters are unsigned, positive decimal integers, with the most significant - * digit sent first. Any parameter greater than 9999 (decimal) is set to 9999 - * (decimal). If you do not specify a value, a 0 value is assumed. A 0 value - * or omitted parameter indicates a default value for the sequence. For most - * sequences, the default value is 1. - * - *

References: - * VT510 Video Terminal Programmer Information: Control Sequences - * alacritty/vte: Implement colon separated CSI parameters - * */ - private void parseArg(int b) { - if (b >= '0' && b <= '9') { - if (mArgIndex < mArgs.length) { - int oldValue = mArgs[mArgIndex]; - int thisDigit = b - '0'; - int value; - if (oldValue >= 0) { - value = oldValue * 10 + thisDigit; - } else { - value = thisDigit; - } - if (value > 9999) - value = 9999; - mArgs[mArgIndex] = value; - } - continueSequence(mEscapeState); - } else if (b == ';' || b == ':') { - if (mArgIndex + 1 < mArgs.length) { - mArgIndex++; - if (b == ':') { - mArgsSubParamsBitSet |= 1 << mArgIndex; - } - } else { - logError("Too many parameters when in state: " + mEscapeState); - } - continueSequence(mEscapeState); - } else { - unknownSequence(b); - } - } - - private int getArg0(int defaultValue) { - return getArg(0, defaultValue, true); - } - - private int getArg1(int defaultValue) { - return getArg(1, defaultValue, true); - } - - private int getArg(int index, int defaultValue, boolean treatZeroAsDefault) { - int result = mArgs[index]; - if (result < 0 || (result == 0 && treatZeroAsDefault)) { - result = defaultValue; - } - return result; - } - - private void collectOSCArgs(int b) { - if (mOSCOrDeviceControlArgs.length() < MAX_OSC_STRING_LENGTH) { - mOSCOrDeviceControlArgs.appendCodePoint(b); - continueSequence(mEscapeState); - } else { - unknownSequence(b); - } - } - - private void unimplementedSequence(int b) { - logError("Unimplemented sequence char '" + (char) b + "' (U+" + String.format("%04x", b) + ")"); - finishSequence(); - } - - private void unknownSequence(int b) { - logError("Unknown sequence char '" + (char) b + "' (numeric value=" + b + ")"); - finishSequence(); - } - - private void unknownParameter(int parameter) { - logError("Unknown parameter: " + parameter); - finishSequence(); - } - - private void logError(String errorType) { - if (LOG_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append(errorType); - buf.append(", escapeState="); - buf.append(mEscapeState); - boolean firstArg = true; - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) { - int value = mArgs[i]; - if (value >= 0) { - if (firstArg) { - firstArg = false; - buf.append(", args={"); - } else { - buf.append(','); - } - buf.append(value); - } - } - if (!firstArg) buf.append('}'); - finishSequenceAndLogError(buf.toString()); - } - } - - private void finishSequenceAndLogError(String error) { - if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, error); - finishSequence(); - } - - private void finishSequence() { - mEscapeState = ESC_NONE; - } - - /** - * Send a Unicode code point to the screen. - * - * @param codePoint The code point of the character to display - */ - private void emitCodePoint(int codePoint) { - mLastEmittedCodePoint = codePoint; - if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) { - // http://www.vt100.net/docs/vt102-ug/table5-15.html. - switch (codePoint) { - case '_': - codePoint = ' '; // Blank. - break; - case '`': - codePoint = '◆'; // Diamond. - break; - case '0': - codePoint = '█'; // Solid block; - break; - case 'a': - codePoint = '▒'; // Checker board. - break; - case 'b': - codePoint = '␉'; // Horizontal tab. - break; - case 'c': - codePoint = '␌'; // Form feed. - break; - case 'd': - codePoint = '\r'; // Carriage return. - break; - case 'e': - codePoint = '␊'; // Linefeed. - break; - case 'f': - codePoint = '°'; // Degree. - break; - case 'g': - codePoint = '±'; // Plus-minus. - break; - case 'h': - codePoint = '\n'; // Newline. - break; - case 'i': - codePoint = '␋'; // Vertical tab. - break; - case 'j': - codePoint = '┘'; // Lower right corner. - break; - case 'k': - codePoint = '┐'; // Upper right corner. - break; - case 'l': - codePoint = '┌'; // Upper left corner. - break; - case 'm': - codePoint = '└'; // Left left corner. - break; - case 'n': - codePoint = '┼'; // Crossing lines. - break; - case 'o': - codePoint = '⎺'; // Horizontal line - scan 1. - break; - case 'p': - codePoint = '⎻'; // Horizontal line - scan 3. - break; - case 'q': - codePoint = '─'; // Horizontal line - scan 5. - break; - case 'r': - codePoint = '⎼'; // Horizontal line - scan 7. - break; - case 's': - codePoint = '⎽'; // Horizontal line - scan 9. - break; - case 't': - codePoint = '├'; // T facing rightwards. - break; - case 'u': - codePoint = '┤'; // T facing leftwards. - break; - case 'v': - codePoint = '┴'; // T facing upwards. - break; - case 'w': - codePoint = '┬'; // T facing downwards. - break; - case 'x': - codePoint = '│'; // Vertical line. - break; - case 'y': - codePoint = '≤'; // Less than or equal to. - break; - case 'z': - codePoint = '≥'; // Greater than or equal to. - break; - case '{': - codePoint = 'π'; // Pi. - break; - case '|': - codePoint = '≠'; // Not equal to. - break; - case '}': - codePoint = '£'; // UK pound. - break; - case '~': - codePoint = '·'; // Centered dot. - break; - } - } - - final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP); - final int displayWidth = WcWidth.width(codePoint); - final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1; - - if (autoWrap) { - if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) { - mScreen.setLineWrap(mCursorRow); - mCursorCol = mLeftMargin; - if (mCursorRow + 1 < mBottomMargin) { - mCursorRow++; - } else { - scrollDownOneLine(); - } - } - } else if (cursorInLastColumn && displayWidth == 2) { - // The behaviour when a wide character is output with cursor in the last column when - // autowrap is disabled is not obvious - it's ignored here. - return; - } - - if (mInsertMode && displayWidth > 0) { - // Move character to right one space. - int destCol = mCursorCol + displayWidth; - if (destCol < mRightMargin) - mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow); - } - - int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0); - int column = mCursorCol - offsetDueToCombiningChar; - - // Fix TerminalRow.setChar() ArrayIndexOutOfBoundsException index=-1 exception reported - // The offsetDueToCombiningChar would never be 1 if mCursorCol was 0 to get column/index=-1, - // so was mCursorCol changed after the offsetDueToCombiningChar conditional by another thread? - // TODO: Check if there are thread synchronization issues with mCursorCol and mCursorRow, possibly causing others bugs too. - if (column < 0) column = 0; - mScreen.setChar(column, mCursorRow, codePoint, getStyle()); - - if (autoWrap && displayWidth > 0) - mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth); - - mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1); - } - - private void setCursorRow(int row) { - mCursorRow = row; - mAboutToAutoWrap = false; - } - - private void setCursorCol(int col) { - mCursorCol = col; - mAboutToAutoWrap = false; - } - - /** Set the cursor mode, but limit it to margins if {@link #DECSET_BIT_ORIGIN_MODE} is enabled. */ - private void setCursorColRespectingOriginMode(int col) { - setCursorPosition(col, mCursorRow); - } - - /** TODO: Better name, distinguished from {@link #setCursorPosition(int, int)} by not regarding origin mode. */ - private void setCursorRowCol(int row, int col) { - mCursorRow = Math.max(0, Math.min(row, mRows - 1)); - mCursorCol = Math.max(0, Math.min(col, mColumns - 1)); - mAboutToAutoWrap = false; - } - - public int getScrollCounter() { - return mScrollCounter; - } - - public void clearScrollCounter() { - mScrollCounter = 0; - } - - public boolean isAutoScrollDisabled() { - return mAutoScrollDisabled; - } - - public void toggleAutoScrollDisabled() { - mAutoScrollDisabled = !mAutoScrollDisabled; - } - - - /** Reset terminal state so user can interact with it regardless of present state. */ - public void reset() { - setCursorStyle(); - mArgIndex = 0; - mContinueSequence = false; - mEscapeState = ESC_NONE; - mInsertMode = false; - mTopMargin = mLeftMargin = 0; - mBottomMargin = mRows; - mRightMargin = mColumns; - mAboutToAutoWrap = false; - mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - setDefaultTabStops(); - - mUseLineDrawingG0 = mUseLineDrawingG1 = false; - mUseLineDrawingUsesG0 = true; - - mSavedStateMain.mSavedCursorRow = mSavedStateMain.mSavedCursorCol = mSavedStateMain.mSavedEffect = mSavedStateMain.mSavedDecFlags = 0; - mSavedStateAlt.mSavedCursorRow = mSavedStateAlt.mSavedCursorCol = mSavedStateAlt.mSavedEffect = mSavedStateAlt.mSavedDecFlags = 0; - mCurrentDecSetFlags = 0; - // Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen: - setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true); - setDecsetinternalBit(DECSET_BIT_CURSOR_ENABLED, true); - mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags; - - // XXX: Should we set terminal driver back to IUTF8 with termios? - mUtf8Index = mUtf8ToFollow = 0; - - mColors.reset(); - mSession.onColorsChanged(); - } - - public String getSelectedText(int x1, int y1, int x2, int y2) { - return mScreen.getSelectedText(x1, y1, x2, y2); - } - - /** Get the terminal session's title (null if not set). */ - public String getTitle() { - return mTitle; - } - - /** Change the terminal session's title. */ - private void setTitle(String newTitle) { - String oldTitle = mTitle; - mTitle = newTitle; - if (!Objects.equals(oldTitle, newTitle)) { - mSession.titleChanged(oldTitle, newTitle); - } - } - - /** If DECSET 2004 is set, prefix paste with "\033[200~" and suffix with "\033[201~". */ - public void paste(String text) { - // First: Always remove escape key and C1 control characters [0x80,0x9F]: - text = text.replaceAll("(\u001B|[\u0080-\u009F])", ""); - // Second: Replace all newlines (\n) or CRLF (\r\n) with carriage returns (\r). - text = text.replaceAll("\r?\n", "\r"); - - // Then: Implement bracketed paste mode if enabled: - boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE); - if (bracketed) mSession.write("\033[200~"); - mSession.write(text); - if (bracketed) mSession.write("\033[201~"); - } - - /** http://www.vt100.net/docs/vt510-rm/DECSC */ - static final class SavedScreenState { - /** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */ - int mSavedCursorRow, mSavedCursorCol; - int mSavedEffect, mSavedForeColor, mSavedBackColor; - int mSavedDecFlags; - boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; - } - - @Override - public String toString() { - return "TerminalEmulator[size=" + mScreen.mColumns + "x" + mScreen.mScreenRows + ", margins={" + mTopMargin + "," + mRightMargin + "," + mBottomMargin - + "," + mLeftMargin + "}]"; - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalOutput.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalOutput.java deleted file mode 100644 index 305082a52d..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalOutput.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.termux.terminal; - -import java.nio.charset.StandardCharsets; - -/** A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. */ -public abstract class TerminalOutput { - - /** Write a string using the UTF-8 encoding to the terminal client. */ - public final void write(String data) { - if (data == null) return; - byte[] bytes = data.getBytes(StandardCharsets.UTF_8); - write(bytes, 0, bytes.length); - } - - /** Write bytes to the terminal client. */ - public abstract void write(byte[] data, int offset, int count); - - /** Notify the terminal client that the terminal title has changed. */ - public abstract void titleChanged(String oldTitle, String newTitle); - - /** Notify the terminal client that text should be copied to clipboard. */ - public abstract void onCopyTextToClipboard(String text); - - /** Notify the terminal client that text should be pasted from clipboard. */ - public abstract void onPasteTextFromClipboard(); - - /** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */ - public abstract void onBell(); - - public abstract void onColorsChanged(); - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalRow.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalRow.java deleted file mode 100644 index d68dc32623..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalRow.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.termux.terminal; - -import java.util.Arrays; - -/** - * A row in a terminal, composed of a fixed number of cells. - *

- * The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering. - */ -public final class TerminalRow { - - private static final float SPARE_CAPACITY_FACTOR = 1.5f; - - /** - * Max combining characters that can exist in a column, that are separate from the base character - * itself. Any additional combining characters will be ignored and not added to the column. - * - * There does not seem to be limit in unicode standard for max number of combination characters - * that can be combined but such characters are primarily under 10. - * - * "Section 3.6 Combination" of unicode standard contains combining characters info. - * - https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf - * - https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges - * - https://stackoverflow.com/questions/71237212/what-is-the-maximum-number-of-unicode-combined-characters-that-may-be-needed-to - * - * UAX15-D3 Stream-Safe Text Format limits to max 30 combining characters. - * > The value of 30 is chosen to be significantly beyond what is required for any linguistic or technical usage. - * > While it would have been feasible to chose a smaller number, this value provides a very wide margin, - * > yet is well within the buffer size limits of practical implementations. - * - https://unicode.org/reports/tr15/#Stream_Safe_Text_Format - * - https://stackoverflow.com/a/11983435/14686958 - * - * We choose the value 15 because it should be enough for terminal based applications and keep - * the memory usage low for a terminal row, won't affect performance or cause terminal to - * lag or hang, and will keep malicious applications from causing harm. The value can be - * increased if ever needed for legitimate applications. - */ - private static final int MAX_COMBINING_CHARACTERS_PER_COLUMN = 15; - - /** The number of columns in this terminal row. */ - private final int mColumns; - /** The text filling this terminal row. */ - public char[] mText; - /** The number of java chars used in {@link #mText}. */ - private short mSpaceUsed; - /** If this row has been line wrapped due to text output at the end of line. */ - boolean mLineWrap; - /** The style bits of each cell in the row. See {@link TextStyle}. */ - final long[] mStyle; - /** If this row might contain chars with width != 1, used for deactivating fast path */ - boolean mHasNonOneWidthOrSurrogateChars; - - /** Construct a blank row (containing only whitespace, ' ') with a specified style. */ - public TerminalRow(int columns, long style) { - mColumns = columns; - mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)]; - mStyle = new long[columns]; - clear(style); - } - - /** NOTE: The sourceX2 is exclusive. */ - public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) { - mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars; - final int x1 = line.findStartOfColumn(sourceX1); - final int x2 = line.findStartOfColumn(sourceX2); - boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1)); - final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText; - int latestNonCombiningWidth = 0; - for (int i = x1; i < x2; i++) { - char sourceChar = sourceChars[i]; - int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar; - if (startingFromSecondHalfOfWideChar) { - // Just treat copying second half of wide char as copying whitespace. - codePoint = ' '; - startingFromSecondHalfOfWideChar = false; - } - int w = WcWidth.width(codePoint); - if (w > 0) { - destinationX += latestNonCombiningWidth; - sourceX1 += latestNonCombiningWidth; - latestNonCombiningWidth = w; - } - setChar(destinationX, codePoint, line.getStyle(sourceX1)); - } - } - - public int getSpaceUsed() { - return mSpaceUsed; - } - - /** Note that the column may end of second half of wide character. */ - public int findStartOfColumn(int column) { - if (column == mColumns) return getSpaceUsed(); - - int currentColumn = 0; - int currentCharIndex = 0; - while (true) { // 0<2 1 < 2 - int newCharIndex = currentCharIndex; - char c = mText[newCharIndex++]; // cci=1, cci=2 - boolean isHigh = Character.isHighSurrogate(c); - int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c; - int wcwidth = WcWidth.width(codePoint); // 1, 2 - if (wcwidth > 0) { - currentColumn += wcwidth; - if (currentColumn == column) { - while (newCharIndex < mSpaceUsed) { - // Skip combining chars. - if (Character.isHighSurrogate(mText[newCharIndex])) { - if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) { - newCharIndex += 2; - } else { - break; - } - } else if (WcWidth.width(mText[newCharIndex]) <= 0) { - newCharIndex++; - } else { - break; - } - } - return newCharIndex; - } else if (currentColumn > column) { - // Wide column going past end. - return currentCharIndex; - } - } - currentCharIndex = newCharIndex; - } - } - - private boolean wideDisplayCharacterStartingAt(int column) { - for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) { - char c = mText[currentCharIndex++]; - int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c; - int wcwidth = WcWidth.width(codePoint); - if (wcwidth > 0) { - if (currentColumn == column && wcwidth == 2) return true; - currentColumn += wcwidth; - if (currentColumn > column) return false; - } - } - return false; - } - - public void clear(long style) { - Arrays.fill(mText, ' '); - Arrays.fill(mStyle, style); - mSpaceUsed = (short) mColumns; - mHasNonOneWidthOrSurrogateChars = false; - } - - // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26 - public void setChar(int columnToSet, int codePoint, long style) { - if (columnToSet < 0 || columnToSet >= mStyle.length) - throw new IllegalArgumentException("TerminalRow.setChar(): columnToSet=" + columnToSet + ", codePoint=" + codePoint + ", style=" + style); - - mStyle[columnToSet] = style; - - final int newCodePointDisplayWidth = WcWidth.width(codePoint); - - // Fast path when we don't have any chars with width != 1 - if (!mHasNonOneWidthOrSurrogateChars) { - if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) { - mHasNonOneWidthOrSurrogateChars = true; - } else { - mText[columnToSet] = (char) codePoint; - return; - } - } - - final boolean newIsCombining = newCodePointDisplayWidth <= 0; - - boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1); - - if (newIsCombining) { - // When standing at second half of wide character and inserting combining: - if (wasExtraColForWideChar) columnToSet--; - } else { - // Check if we are overwriting the second half of a wide character starting at the previous column: - if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style); - // Check if we are overwriting the first half of a wide character starting at the next column: - boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1); - if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style); - } - - char[] text = mText; - final int oldStartOfColumnIndex = findStartOfColumn(columnToSet); - final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex); - - // Get the number of elements in the mText array this column uses now - int oldCharactersUsedForColumn; - if (columnToSet + oldCodePointDisplayWidth < mColumns) { - int oldEndOfColumnIndex = findStartOfColumn(columnToSet + oldCodePointDisplayWidth); - oldCharactersUsedForColumn = oldEndOfColumnIndex - oldStartOfColumnIndex; - } else { - // Last character. - oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex; - } - - // If MAX_COMBINING_CHARACTERS_PER_COLUMN already exist in column, then ignore adding additional combining characters. - if (newIsCombining) { - int combiningCharsCount = WcWidth.zeroWidthCharsCount(mText, oldStartOfColumnIndex, oldStartOfColumnIndex + oldCharactersUsedForColumn); - if (combiningCharsCount >= MAX_COMBINING_CHARACTERS_PER_COLUMN) - return; - } - - // Find how many chars this column will need - int newCharactersUsedForColumn = Character.charCount(codePoint); - if (newIsCombining) { - // Combining characters are added to the contents of the column instead of overwriting them, so that they - // modify the existing contents. - // FIXME: Unassigned characters also get width=0. - newCharactersUsedForColumn += oldCharactersUsedForColumn; - } - - int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn; - int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn; - - final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn; - if (javaCharDifference > 0) { - // Shift the rest of the line right. - int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex; - if (mSpaceUsed + javaCharDifference > text.length) { - // We need to grow the array - char[] newText = new char[text.length + mColumns]; - System.arraycopy(text, 0, newText, 0, oldNextColumnIndex); - System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn); - mText = text = newText; - } else { - System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn); - } - } else if (javaCharDifference < 0) { - // Shift the rest of the line left. - System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex); - } - mSpaceUsed += javaCharDifference; - - // Store char. A combining character is stored at the end of the existing contents so that it modifies them: - //noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used. - Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0)); - - if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) { - // Replace second half of wide char with a space. Which mean that we actually add a ' ' java character. - if (mSpaceUsed + 1 > text.length) { - char[] newText = new char[text.length + mColumns]; - System.arraycopy(text, 0, newText, 0, newNextColumnIndex); - System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); - mText = text = newText; - } else { - System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); - } - text[newNextColumnIndex] = ' '; - - ++mSpaceUsed; - } else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) { - if (columnToSet == mColumns - 1) { - throw new IllegalArgumentException("Cannot put wide character in last column"); - } else if (columnToSet == mColumns - 2) { - // Truncate the line to the second part of this wide char: - mSpaceUsed = (short) newNextColumnIndex; - } else { - // Overwrite the contents of the next column, which mean we actually remove java characters. Due to the - // check at the beginning of this method we know that we are not overwriting a wide char. - int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1); - int nextLen = newNextNextColumnIndex - newNextColumnIndex; - - // Shift the array leftwards. - System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex); - mSpaceUsed -= nextLen; - } - } - } - - boolean isBlank() { - for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++) - if (mText[charIndex] != ' ') return false; - return true; - } - - public final long getStyle(int column) { - return mStyle[column]; - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSession.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalSession.java deleted file mode 100644 index b068be203b..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSession.java +++ /dev/null @@ -1,373 +0,0 @@ -package com.termux.terminal; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Message; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.UUID; - -/** - * A terminal session, consisting of a process coupled to a terminal interface. - *

- * The subprocess will be executed by the constructor, and when the size is made known by a call to - * {@link #updateSize(int, int, int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O. - * All terminal emulation and callback methods will be performed on the main thread. - *

- * The child process may be exited forcefully by using the {@link #finishIfRunning()} method. - *

- * NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks! - */ -public final class TerminalSession extends TerminalOutput { - - private static final int MSG_NEW_INPUT = 1; - private static final int MSG_PROCESS_EXITED = 4; - - public final String mHandle = UUID.randomUUID().toString(); - - TerminalEmulator mEmulator; - - /** - * A queue written to from a separate thread when the process outputs, and read by main thread to process by - * terminal emulator. - */ - final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096); - /** - * A queue written to from the main thread due to user interaction, and read by another thread which forwards by - * writing to the {@link #mTerminalFileDescriptor}. - */ - final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096); - /** Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue */ - private final byte[] mUtf8InputBuffer = new byte[5]; - - /** Callback which gets notified when a session finishes or changes title. */ - TerminalSessionClient mClient; - - /** The pid of the shell process. 0 if not started and -1 if finished running. */ - int mShellPid; - - /** 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[], int, int, int, int)}. - */ - private int mTerminalFileDescriptor; - - /** Set by the application for user identification of session, not by terminal. */ - public String mSessionName; - - final Handler mMainThreadHandler = new MainThreadHandler(); - - private final String mShellPath; - private final String mCwd; - private final String[] mArgs; - private final String[] mEnv; - private final Integer mTranscriptRows; - - - private static final String LOG_TAG = "TerminalSession"; - - public TerminalSession(String shellPath, String cwd, String[] args, String[] env, Integer transcriptRows, TerminalSessionClient client) { - this.mShellPath = shellPath; - this.mCwd = cwd; - this.mArgs = args; - this.mEnv = env; - this.mTranscriptRows = transcriptRows; - this.mClient = client; - } - - /** - * @param client The {@link TerminalSessionClient} interface implementation to allow - * for communication between {@link TerminalSession} and its client. - */ - public void updateTerminalSessionClient(TerminalSessionClient client) { - mClient = client; - - if (mEmulator != null) - mEmulator.updateTerminalSessionClient(client); - } - - /** Inform the attached pty of the new size and reflow or initialize the emulator. */ - public void updateSize(int columns, int rows, int cellWidthPixels, int cellHeightPixels) { - if (mEmulator == null) { - initializeEmulator(columns, rows, cellWidthPixels, cellHeightPixels); - } else { - JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns, cellWidthPixels, cellHeightPixels); - mEmulator.resize(columns, rows, cellWidthPixels, cellHeightPixels); - } - } - - /** The terminal title as set through escape sequences or null if none set. */ - public String getTitle() { - return (mEmulator == null) ? null : mEmulator.getTitle(); - } - - /** - * Set the terminal emulator's window size and start terminal emulation. - * - * @param columns The number of columns in the terminal window. - * @param rows The number of rows in the terminal window. - */ - public void initializeEmulator(int columns, int rows, int cellWidthPixels, int cellHeightPixels) { - mEmulator = new TerminalEmulator(this, columns, rows, cellWidthPixels, cellHeightPixels, mTranscriptRows, mClient); - - int[] processId = new int[1]; - mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns, cellWidthPixels, cellHeightPixels); - mShellPid = processId[0]; - mClient.setTerminalShellPid(this, mShellPid); - - final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor, mClient); - - new Thread("TermSessionInputReader[pid=" + mShellPid + "]") { - @Override - public void run() { - try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) { - final byte[] buffer = new byte[4096]; - while (true) { - int read = termIn.read(buffer); - if (read == -1) return; - if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return; - mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT); - } - } catch (Exception e) { - // Ignore, just shutting down. - } - } - }.start(); - - new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") { - @Override - public void run() { - final byte[] buffer = new byte[4096]; - try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) { - while (true) { - int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true); - if (bytesToWrite == -1) return; - termOut.write(buffer, 0, bytesToWrite); - } - } catch (IOException e) { - // Ignore. - } - } - }.start(); - - new Thread("TermSessionWaiter[pid=" + mShellPid + "]") { - @Override - public void run() { - int processExitCode = JNI.waitFor(mShellPid); - mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode)); - } - }.start(); - - } - - /** Write data to the shell process. */ - @Override - public void write(byte[] data, int offset, int count) { - if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count); - } - - /** Write the Unicode code point to the terminal encoded in UTF-8. */ - public void writeCodePoint(boolean prependEscape, int codePoint) { - if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { - // 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range. - throw new IllegalArgumentException("Invalid code point: " + codePoint); - } - - int bufferPosition = 0; - if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27; - - if (codePoint <= /* 7 bits */0b1111111) { - mUtf8InputBuffer[bufferPosition++] = (byte) codePoint; - } else if (codePoint <= /* 11 bits */0b11111111111) { - /* 110xxxxx leading byte with leading 5 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } else if (codePoint <= /* 16 bits */0b1111111111111111) { - /* 1110xxxx leading byte with leading 4 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */ - /* 11110xxx leading byte with leading 3 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } - write(mUtf8InputBuffer, 0, bufferPosition); - } - - public TerminalEmulator getEmulator() { - return mEmulator; - } - - /** Notify the {@link #mClient} that the screen has changed. */ - protected void notifyScreenUpdate() { - mClient.onTextChanged(this); - } - - /** Reset state for terminal emulator state. */ - public void reset() { - mEmulator.reset(); - notifyScreenUpdate(); - } - - /** Finish this terminal session by sending SIGKILL to the shell. */ - public void finishIfRunning() { - if (isRunning()) { - try { - Os.kill(mShellPid, OsConstants.SIGKILL); - } catch (ErrnoException e) { - Logger.logWarn(mClient, LOG_TAG, "Failed sending SIGKILL: " + e.getMessage()); - } - } - } - - /** Cleanup resources when the process exits. */ - void cleanupResources(int exitStatus) { - synchronized (this) { - mShellPid = -1; - mShellExitStatus = exitStatus; - } - - // Stop the reader and writer threads, and close the I/O streams - mTerminalToProcessIOQueue.close(); - mProcessToTerminalIOQueue.close(); - JNI.close(mTerminalFileDescriptor); - } - - @Override - public void titleChanged(String oldTitle, String newTitle) { - mClient.onTitleChanged(this); - } - - public synchronized boolean isRunning() { - return mShellPid != -1; - } - - /** Only valid if not {@link #isRunning()}. */ - public synchronized int getExitStatus() { - return mShellExitStatus; - } - - @Override - public void onCopyTextToClipboard(String text) { - mClient.onCopyTextToClipboard(this, text); - } - - @Override - public void onPasteTextFromClipboard() { - mClient.onPasteTextFromClipboard(this); - } - - @Override - public void onBell() { - mClient.onBell(this); - } - - @Override - public void onColorsChanged() { - mClient.onColorsChanged(this); - } - - public int getPid() { - return mShellPid; - } - - /** Returns the shell's working directory or null if it was unavailable. */ - public String getCwd() { - if (mShellPid < 1) { - return null; - } - try { - final String cwdSymlink = String.format("/proc/%s/cwd/", mShellPid); - String outputPath = new File(cwdSymlink).getCanonicalPath(); - String outputPathWithTrailingSlash = outputPath; - if (!outputPath.endsWith("/")) { - outputPathWithTrailingSlash += '/'; - } - if (!cwdSymlink.equals(outputPathWithTrailingSlash)) { - return outputPath; - } - } catch (IOException | SecurityException e) { - Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Error getting current directory", e); - } - return null; - } - - private static FileDescriptor wrapFileDescriptor(int fileDescriptor, TerminalSessionClient client) { - FileDescriptor result = new FileDescriptor(); - try { - Field descriptorField; - try { - descriptorField = FileDescriptor.class.getDeclaredField("descriptor"); - } catch (NoSuchFieldException e) { - // For desktop java: - descriptorField = FileDescriptor.class.getDeclaredField("fd"); - } - descriptorField.setAccessible(true); - descriptorField.set(result, fileDescriptor); - } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { - Logger.logStackTraceWithMessage(client, LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e); - System.exit(1); - } - return result; - } - - @SuppressLint("HandlerLeak") - class MainThreadHandler extends Handler { - - final byte[] mReceiveBuffer = new byte[4 * 1024]; - - @Override - public void handleMessage(Message msg) { - int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false); - if (bytesRead > 0) { - mEmulator.append(mReceiveBuffer, bytesRead); - notifyScreenUpdate(); - } - - if (msg.what == MSG_PROCESS_EXITED) { - int exitCode = (Integer) msg.obj; - cleanupResources(exitCode); - - String exitDescription = "\r\n[Process completed"; - if (exitCode > 0) { - // Non-zero process exit. - exitDescription += " (code " + exitCode + ")"; - } else if (exitCode < 0) { - // Negated signal. - exitDescription += " (signal " + (-exitCode) + ")"; - } - exitDescription += " - press Enter]"; - - byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8); - mEmulator.append(bytesToWrite, bytesToWrite.length); - notifyScreenUpdate(); - - mClient.onSessionFinished(TerminalSession.this); - } - } - - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java deleted file mode 100644 index fbd8e5509a..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.termux.terminal; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * The interface for communication between {@link TerminalSession} and its client. It is used to - * send callbacks to the client when {@link TerminalSession} changes or for sending other - * back data to the client like logs. - */ -public interface TerminalSessionClient { - - void onTextChanged(@NonNull TerminalSession changedSession); - - void onTitleChanged(@NonNull TerminalSession changedSession); - - void onSessionFinished(@NonNull TerminalSession finishedSession); - - void onCopyTextToClipboard(@NonNull TerminalSession session, String text); - - void onPasteTextFromClipboard(@Nullable TerminalSession session); - - void onBell(@NonNull TerminalSession session); - - void onColorsChanged(@NonNull TerminalSession session); - - void onTerminalCursorStateChange(boolean state); - - void setTerminalShellPid(@NonNull TerminalSession session, int pid); - - - - Integer getTerminalCursorStyle(); - - - - void logError(String tag, String message); - - void logWarn(String tag, String message); - - void logInfo(String tag, String message); - - void logDebug(String tag, String message); - - void logVerbose(String tag, String message); - - void logStackTraceWithMessage(String tag, String message, Exception e); - - void logStackTrace(String tag, Exception e); - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TextStyle.java b/terminal-emulator/src/main/java/com/termux/terminal/TextStyle.java deleted file mode 100644 index 173d6ae94e..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/TextStyle.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.termux.terminal; - -/** - *

- * Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal - * row in {@link TerminalRow#mStyle}. - *

- *

- * The bit layout is: - *

- * - 16 flags (11 currently used). - * - 24 for foreground color (only 9 first bits if a color index). - * - 24 for background color (only 9 first bits if a color index). - */ -public final class TextStyle { - - public final static int CHARACTER_ATTRIBUTE_BOLD = 1; - public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1; - public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2; - public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3; - public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4; - public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5; - public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; - /** - * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. - *

- * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that - * come after it as erasable from the screen. - *

- */ - public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; - /** Dim colors. Also known as faint or half intensity. */ - public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8; - /** If true (24-bit) color is used for the cell for foreground. */ - private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9; - /** If true (24-bit) color is used for the cell for foreground. */ - private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10; - - public final static int COLOR_INDEX_FOREGROUND = 256; - public final static int COLOR_INDEX_BACKGROUND = 257; - public final static int COLOR_INDEX_CURSOR = 258; - - /** The 256 standard color entries and the three special (foreground, background and cursor) ones. */ - public final static int NUM_INDEXED_COLORS = 259; - - /** Normal foreground and background colors and no effects. */ - final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0); - - static long encode(int foreColor, int backColor, int effect) { - long result = effect & 0b111111111; - if ((0xff000000 & foreColor) == 0xff000000) { - // 24-bit color. - result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L); - } else { - // Indexed color. - result |= (foreColor & 0b111111111L) << 40; - } - if ((0xff000000 & backColor) == 0xff000000) { - // 24-bit color. - result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L); - } else { - // Indexed color. - result |= (backColor & 0b111111111L) << 16L; - } - - return result; - } - - public static int decodeForeColor(long style) { - if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) { - return (int) ((style >>> 40) & 0b111111111L); - } else { - return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL); - } - - } - - public static int decodeBackColor(long style) { - if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) { - return (int) ((style >>> 16) & 0b111111111L); - } else { - return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL); - } - } - - public static int decodeEffect(long style) { - return (int) (style & 0b11111111111); - } - -} diff --git a/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java b/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java deleted file mode 100644 index d71cc277b4..0000000000 --- a/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java +++ /dev/null @@ -1,566 +0,0 @@ -package com.termux.terminal; - -/** - * Implementation of wcwidth(3) for Unicode 15. - * - * Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters. - * - * IMPORTANT: - * Must be kept in sync with the following: - * https://github.com/termux/wcwidth - * https://github.com/termux/libandroid-support - * https://github.com/termux/termux-packages/tree/master/packages/libandroid-support - */ -public final class WcWidth { - - // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py - // from https://github.com/jquast/wcwidth/pull/64 - // at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16): - private static final int[][] ZERO_WIDTH = { - {0x00300, 0x0036f}, // Combining Grave Accent ..Combining Latin Small Le - {0x00483, 0x00489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli - {0x00591, 0x005bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg - {0x005bf, 0x005bf}, // Hebrew Point Rafe ..Hebrew Point Rafe - {0x005c1, 0x005c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot - {0x005c4, 0x005c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot - {0x005c7, 0x005c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata - {0x00610, 0x0061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra - {0x0064b, 0x0065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below - {0x00670, 0x00670}, // Arabic Letter Superscrip..Arabic Letter Superscrip - {0x006d6, 0x006dc}, // Arabic Small High Ligatu..Arabic Small High Seen - {0x006df, 0x006e4}, // Arabic Small High Rounde..Arabic Small High Madda - {0x006e7, 0x006e8}, // Arabic Small High Yeh ..Arabic Small High Noon - {0x006ea, 0x006ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem - {0x00711, 0x00711}, // Syriac Letter Superscrip..Syriac Letter Superscrip - {0x00730, 0x0074a}, // Syriac Pthaha Above ..Syriac Barrekh - {0x007a6, 0x007b0}, // Thaana Abafili ..Thaana Sukun - {0x007eb, 0x007f3}, // Nko Combining Short High..Nko Combining Double Dot - {0x007fd, 0x007fd}, // Nko Dantayalan ..Nko Dantayalan - {0x00816, 0x00819}, // Samaritan Mark In ..Samaritan Mark Dagesh - {0x0081b, 0x00823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A - {0x00825, 0x00827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U - {0x00829, 0x0082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa - {0x00859, 0x0085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark - {0x00898, 0x0089f}, // Arabic Small High Word A..Arabic Half Madda Over M - {0x008ca, 0x008e1}, // Arabic Small High Farsi ..Arabic Small High Sign S - {0x008e3, 0x00902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara - {0x0093a, 0x0093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe - {0x0093c, 0x0093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta - {0x00941, 0x00948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai - {0x0094d, 0x0094d}, // Devanagari Sign Virama ..Devanagari Sign Virama - {0x00951, 0x00957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu - {0x00962, 0x00963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo - {0x00981, 0x00981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu - {0x009bc, 0x009bc}, // Bengali Sign Nukta ..Bengali Sign Nukta - {0x009c1, 0x009c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal - {0x009cd, 0x009cd}, // Bengali Sign Virama ..Bengali Sign Virama - {0x009e2, 0x009e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal - {0x009fe, 0x009fe}, // Bengali Sandhi Mark ..Bengali Sandhi Mark - {0x00a01, 0x00a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi - {0x00a3c, 0x00a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta - {0x00a41, 0x00a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu - {0x00a47, 0x00a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai - {0x00a4b, 0x00a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama - {0x00a51, 0x00a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat - {0x00a70, 0x00a71}, // Gurmukhi Tippi ..Gurmukhi Addak - {0x00a75, 0x00a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash - {0x00a81, 0x00a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara - {0x00abc, 0x00abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta - {0x00ac1, 0x00ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand - {0x00ac7, 0x00ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai - {0x00acd, 0x00acd}, // Gujarati Sign Virama ..Gujarati Sign Virama - {0x00ae2, 0x00ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca - {0x00afa, 0x00aff}, // Gujarati Sign Sukun ..Gujarati Sign Two-circle - {0x00b01, 0x00b01}, // Oriya Sign Candrabindu ..Oriya Sign Candrabindu - {0x00b3c, 0x00b3c}, // Oriya Sign Nukta ..Oriya Sign Nukta - {0x00b3f, 0x00b3f}, // Oriya Vowel Sign I ..Oriya Vowel Sign I - {0x00b41, 0x00b44}, // Oriya Vowel Sign U ..Oriya Vowel Sign Vocalic - {0x00b4d, 0x00b4d}, // Oriya Sign Virama ..Oriya Sign Virama - {0x00b55, 0x00b56}, // Oriya Sign Overline ..Oriya Ai Length Mark - {0x00b62, 0x00b63}, // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic - {0x00b82, 0x00b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara - {0x00bc0, 0x00bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii - {0x00bcd, 0x00bcd}, // Tamil Sign Virama ..Tamil Sign Virama - {0x00c00, 0x00c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca - {0x00c04, 0x00c04}, // Telugu Sign Combining An..Telugu Sign Combining An - {0x00c3c, 0x00c3c}, // Telugu Sign Nukta ..Telugu Sign Nukta - {0x00c3e, 0x00c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii - {0x00c46, 0x00c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai - {0x00c4a, 0x00c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama - {0x00c55, 0x00c56}, // Telugu Length Mark ..Telugu Ai Length Mark - {0x00c62, 0x00c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali - {0x00c81, 0x00c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu - {0x00cbc, 0x00cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta - {0x00cbf, 0x00cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I - {0x00cc6, 0x00cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E - {0x00ccc, 0x00ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama - {0x00ce2, 0x00ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal - {0x00d00, 0x00d01}, // Malayalam Sign Combining..Malayalam Sign Candrabin - {0x00d3b, 0x00d3c}, // Malayalam Sign Vertical ..Malayalam Sign Circular - {0x00d41, 0x00d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc - {0x00d4d, 0x00d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama - {0x00d62, 0x00d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc - {0x00d81, 0x00d81}, // Sinhala Sign Candrabindu..Sinhala Sign Candrabindu - {0x00dca, 0x00dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna - {0x00dd2, 0x00dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti - {0x00dd6, 0x00dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga - {0x00e31, 0x00e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a - {0x00e34, 0x00e3a}, // Thai Character Sara I ..Thai Character Phinthu - {0x00e47, 0x00e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan - {0x00eb1, 0x00eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan - {0x00eb4, 0x00ebc}, // Lao Vowel Sign I ..Lao Semivowel Sign Lo - {0x00ec8, 0x00ece}, // Lao Tone Mai Ek ..(nil) - {0x00f18, 0x00f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig - {0x00f35, 0x00f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung - {0x00f37, 0x00f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung - {0x00f39, 0x00f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru - {0x00f71, 0x00f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga - {0x00f80, 0x00f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta - {0x00f86, 0x00f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags - {0x00f8d, 0x00f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter - {0x00f99, 0x00fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter - {0x00fc6, 0x00fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda - {0x0102d, 0x01030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu - {0x01032, 0x01037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below - {0x01039, 0x0103a}, // Myanmar Sign Virama ..Myanmar Sign Asat - {0x0103d, 0x0103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M - {0x01058, 0x01059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal - {0x0105e, 0x01060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M - {0x01071, 0x01074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah - {0x01082, 0x01082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S - {0x01085, 0x01086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan - {0x0108d, 0x0108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci - {0x0109d, 0x0109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton - {0x0135d, 0x0135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin - {0x01712, 0x01714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama - {0x01732, 0x01733}, // Hanunoo Vowel Sign I ..Hanunoo Vowel Sign U - {0x01752, 0x01753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U - {0x01772, 0x01773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U - {0x017b4, 0x017b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa - {0x017b7, 0x017bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua - {0x017c6, 0x017c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit - {0x017c9, 0x017d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat - {0x017dd, 0x017dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan - {0x0180b, 0x0180d}, // Mongolian Free Variation..Mongolian Free Variation - {0x0180f, 0x0180f}, // Mongolian Free Variation..Mongolian Free Variation - {0x01885, 0x01886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal - {0x018a9, 0x018a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal - {0x01920, 0x01922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U - {0x01927, 0x01928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O - {0x01932, 0x01932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv - {0x01939, 0x0193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i - {0x01a17, 0x01a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U - {0x01a1b, 0x01a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae - {0x01a56, 0x01a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign - {0x01a58, 0x01a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign - {0x01a60, 0x01a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot - {0x01a62, 0x01a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai - {0x01a65, 0x01a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B - {0x01a73, 0x01a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue - {0x01a7f, 0x01a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt - {0x01ab0, 0x01ace}, // Combining Doubled Circum..Combining Latin Small Le - {0x01b00, 0x01b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang - {0x01b34, 0x01b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan - {0x01b36, 0x01b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R - {0x01b3c, 0x01b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L - {0x01b42, 0x01b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe - {0x01b6b, 0x01b73}, // Balinese Musical Symbol ..Balinese Musical Symbol - {0x01b80, 0x01b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar - {0x01ba2, 0x01ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan - {0x01ba8, 0x01ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan - {0x01bab, 0x01bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign - {0x01be6, 0x01be6}, // Batak Sign Tompi ..Batak Sign Tompi - {0x01be8, 0x01be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee - {0x01bed, 0x01bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O - {0x01bef, 0x01bf1}, // Batak Vowel Sign U For S..Batak Consonant Sign H - {0x01c2c, 0x01c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T - {0x01c36, 0x01c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta - {0x01cd0, 0x01cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha - {0x01cd4, 0x01ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash - {0x01ce2, 0x01ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda - {0x01ced, 0x01ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak - {0x01cf4, 0x01cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above - {0x01cf8, 0x01cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A - {0x01dc0, 0x01dff}, // Combining Dotted Grave A..Combining Right Arrowhea - {0x020d0, 0x020f0}, // Combining Left Harpoon A..Combining Asterisk Above - {0x02cef, 0x02cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu - {0x02d7f, 0x02d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine - {0x02de0, 0x02dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette - {0x0302a, 0x0302d}, // Ideographic Level Tone M..Ideographic Entering Ton - {0x03099, 0x0309a}, // Combining Katakana-hirag..Combining Katakana-hirag - {0x0a66f, 0x0a672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous - {0x0a674, 0x0a67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer - {0x0a69e, 0x0a69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette - {0x0a6f0, 0x0a6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk - {0x0a802, 0x0a802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva - {0x0a806, 0x0a806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant - {0x0a80b, 0x0a80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva - {0x0a825, 0x0a826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign - {0x0a82c, 0x0a82c}, // Syloti Nagri Sign Altern..Syloti Nagri Sign Altern - {0x0a8c4, 0x0a8c5}, // Saurashtra Sign Virama ..Saurashtra Sign Candrabi - {0x0a8e0, 0x0a8f1}, // Combining Devanagari Dig..Combining Devanagari Sig - {0x0a8ff, 0x0a8ff}, // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay - {0x0a926, 0x0a92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop - {0x0a947, 0x0a951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R - {0x0a980, 0x0a982}, // Javanese Sign Panyangga ..Javanese Sign Layar - {0x0a9b3, 0x0a9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu - {0x0a9b6, 0x0a9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku - {0x0a9bc, 0x0a9bd}, // Javanese Vowel Sign Pepe..Javanese Consonant Sign - {0x0a9e5, 0x0a9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw - {0x0aa29, 0x0aa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe - {0x0aa31, 0x0aa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue - {0x0aa35, 0x0aa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa - {0x0aa43, 0x0aa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina - {0x0aa4c, 0x0aa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina - {0x0aa7c, 0x0aa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T - {0x0aab0, 0x0aab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang - {0x0aab2, 0x0aab4}, // Tai Viet Vowel I ..Tai Viet Vowel U - {0x0aab7, 0x0aab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia - {0x0aabe, 0x0aabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek - {0x0aac1, 0x0aac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho - {0x0aaec, 0x0aaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0x0aaf6, 0x0aaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama - {0x0abe5, 0x0abe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0x0abe8, 0x0abe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0x0abed, 0x0abed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek - {0x0fb1e, 0x0fb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani - {0x0fe00, 0x0fe0f}, // Variation Selector-1 ..Variation Selector-16 - {0x0fe20, 0x0fe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo - {0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi - {0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M - {0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let - {0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo - {0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O - {0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga - {0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo - {0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama - {0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation - {0x10d24, 0x10d27}, // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas - {0x10eab, 0x10eac}, // Yezidi Combining Hamza M..Yezidi Combining Madda M - {0x10efd, 0x10eff}, // (nil) ..(nil) - {0x10f46, 0x10f50}, // Sogdian Combining Dot Be..Sogdian Combining Stroke - {0x10f82, 0x10f85}, // Old Uyghur Combining Dot..Old Uyghur Combining Two - {0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara - {0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama - {0x11070, 0x11070}, // Brahmi Sign Old Tamil Vi..Brahmi Sign Old Tamil Vi - {0x11073, 0x11074}, // Brahmi Vowel Sign Old Ta..Brahmi Vowel Sign Old Ta - {0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara - {0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai - {0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta - {0x110c2, 0x110c2}, // Kaithi Vowel Sign Vocali..Kaithi Vowel Sign Vocali - {0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga - {0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu - {0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa - {0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta - {0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara - {0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O - {0x111c9, 0x111cc}, // Sharada Sandhi Mark ..Sharada Extra Short Vowe - {0x111cf, 0x111cf}, // Sharada Sign Inverted Ca..Sharada Sign Inverted Ca - {0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai - {0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara - {0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda - {0x1123e, 0x1123e}, // Khojki Sign Sukun ..Khojki Sign Sukun - {0x11241, 0x11241}, // (nil) ..(nil) - {0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara - {0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama - {0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu - {0x1133b, 0x1133c}, // Combining Bindu Below ..Grantha Sign Nukta - {0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii - {0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit - {0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter - {0x11438, 0x1143f}, // Newa Vowel Sign U ..Newa Vowel Sign Ai - {0x11442, 0x11444}, // Newa Sign Virama ..Newa Sign Anusvara - {0x11446, 0x11446}, // Newa Sign Nukta ..Newa Sign Nukta - {0x1145e, 0x1145e}, // Newa Sandhi Mark ..Newa Sandhi Mark - {0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal - {0x114ba, 0x114ba}, // Tirhuta Vowel Sign Short..Tirhuta Vowel Sign Short - {0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara - {0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta - {0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal - {0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara - {0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta - {0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter - {0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai - {0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara - {0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra - {0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara - {0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa - {0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au - {0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta - {0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi - {0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu - {0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer - {0x1182f, 0x11837}, // Dogra Vowel Sign U ..Dogra Sign Anusvara - {0x11839, 0x1183a}, // Dogra Sign Virama ..Dogra Sign Nukta - {0x1193b, 0x1193c}, // Dives Akuru Sign Anusvar..Dives Akuru Sign Candrab - {0x1193e, 0x1193e}, // Dives Akuru Virama ..Dives Akuru Virama - {0x11943, 0x11943}, // Dives Akuru Sign Nukta ..Dives Akuru Sign Nukta - {0x119d4, 0x119d7}, // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V - {0x119da, 0x119db}, // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A - {0x119e0, 0x119e0}, // Nandinagari Sign Virama ..Nandinagari Sign Virama - {0x11a01, 0x11a0a}, // Zanabazar Square Vowel S..Zanabazar Square Vowel L - {0x11a33, 0x11a38}, // Zanabazar Square Final C..Zanabazar Square Sign An - {0x11a3b, 0x11a3e}, // Zanabazar Square Cluster..Zanabazar Square Cluster - {0x11a47, 0x11a47}, // Zanabazar Square Subjoin..Zanabazar Square Subjoin - {0x11a51, 0x11a56}, // Soyombo Vowel Sign I ..Soyombo Vowel Sign Oe - {0x11a59, 0x11a5b}, // Soyombo Vowel Sign Vocal..Soyombo Vowel Length Mar - {0x11a8a, 0x11a96}, // Soyombo Final Consonant ..Soyombo Sign Anusvara - {0x11a98, 0x11a99}, // Soyombo Gemination Mark ..Soyombo Subjoiner - {0x11c30, 0x11c36}, // Bhaiksuki Vowel Sign I ..Bhaiksuki Vowel Sign Voc - {0x11c38, 0x11c3d}, // Bhaiksuki Vowel Sign E ..Bhaiksuki Sign Anusvara - {0x11c3f, 0x11c3f}, // Bhaiksuki Sign Virama ..Bhaiksuki Sign Virama - {0x11c92, 0x11ca7}, // Marchen Subjoined Letter..Marchen Subjoined Letter - {0x11caa, 0x11cb0}, // Marchen Subjoined Letter..Marchen Vowel Sign Aa - {0x11cb2, 0x11cb3}, // Marchen Vowel Sign U ..Marchen Vowel Sign E - {0x11cb5, 0x11cb6}, // Marchen Sign Anusvara ..Marchen Sign Candrabindu - {0x11d31, 0x11d36}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign - {0x11d3a, 0x11d3a}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign - {0x11d3c, 0x11d3d}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign - {0x11d3f, 0x11d45}, // Masaram Gondi Vowel Sign..Masaram Gondi Virama - {0x11d47, 0x11d47}, // Masaram Gondi Ra-kara ..Masaram Gondi Ra-kara - {0x11d90, 0x11d91}, // Gunjala Gondi Vowel Sign..Gunjala Gondi Vowel Sign - {0x11d95, 0x11d95}, // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv - {0x11d97, 0x11d97}, // Gunjala Gondi Virama ..Gunjala Gondi Virama - {0x11ef3, 0x11ef4}, // Makasar Vowel Sign I ..Makasar Vowel Sign U - {0x11f00, 0x11f01}, // (nil) ..(nil) - {0x11f36, 0x11f3a}, // (nil) ..(nil) - {0x11f40, 0x11f40}, // (nil) ..(nil) - {0x11f42, 0x11f42}, // (nil) ..(nil) - {0x13440, 0x13440}, // (nil) ..(nil) - {0x13447, 0x13455}, // (nil) ..(nil) - {0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High - {0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta - {0x16f4f, 0x16f4f}, // Miao Sign Consonant Modi..Miao Sign Consonant Modi - {0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below - {0x16fe4, 0x16fe4}, // Khitan Small Script Fill..Khitan Small Script Fill - {0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark - {0x1cf00, 0x1cf2d}, // Znamenny Combining Mark ..Znamenny Combining Mark - {0x1cf30, 0x1cf46}, // Znamenny Combining Tonal..Znamenny Priznak Modifie - {0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical - {0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking - {0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement - {0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T - {0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea - {0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie - {0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod - {0x1e000, 0x1e006}, // Combining Glagolitic Let..Combining Glagolitic Let - {0x1e008, 0x1e018}, // Combining Glagolitic Let..Combining Glagolitic Let - {0x1e01b, 0x1e021}, // Combining Glagolitic Let..Combining Glagolitic Let - {0x1e023, 0x1e024}, // Combining Glagolitic Let..Combining Glagolitic Let - {0x1e026, 0x1e02a}, // Combining Glagolitic Let..Combining Glagolitic Let - {0x1e08f, 0x1e08f}, // (nil) ..(nil) - {0x1e130, 0x1e136}, // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T - {0x1e2ae, 0x1e2ae}, // Toto Sign Rising Tone ..Toto Sign Rising Tone - {0x1e2ec, 0x1e2ef}, // Wancho Tone Tup ..Wancho Tone Koini - {0x1e4ec, 0x1e4ef}, // (nil) ..(nil) - {0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining - {0x1e944, 0x1e94a}, // Adlam Alif Lengthener ..Adlam Nukta - {0xe0100, 0xe01ef}, // Variation Selector-17 ..Variation Selector-256 - }; - - // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py - // from https://github.com/jquast/wcwidth/pull/64 - // at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16): - private static final int[][] WIDE_EASTASIAN = { - {0x01100, 0x0115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler - {0x0231a, 0x0231b}, // Watch ..Hourglass - {0x02329, 0x0232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra - {0x023e9, 0x023ec}, // Black Right-pointing Dou..Black Down-pointing Doub - {0x023f0, 0x023f0}, // Alarm Clock ..Alarm Clock - {0x023f3, 0x023f3}, // Hourglass With Flowing S..Hourglass With Flowing S - {0x025fd, 0x025fe}, // White Medium Small Squar..Black Medium Small Squar - {0x02614, 0x02615}, // Umbrella With Rain Drops..Hot Beverage - {0x02648, 0x02653}, // Aries ..Pisces - {0x0267f, 0x0267f}, // Wheelchair Symbol ..Wheelchair Symbol - {0x02693, 0x02693}, // Anchor ..Anchor - {0x026a1, 0x026a1}, // High Voltage Sign ..High Voltage Sign - {0x026aa, 0x026ab}, // Medium White Circle ..Medium Black Circle - {0x026bd, 0x026be}, // Soccer Ball ..Baseball - {0x026c4, 0x026c5}, // Snowman Without Snow ..Sun Behind Cloud - {0x026ce, 0x026ce}, // Ophiuchus ..Ophiuchus - {0x026d4, 0x026d4}, // No Entry ..No Entry - {0x026ea, 0x026ea}, // Church ..Church - {0x026f2, 0x026f3}, // Fountain ..Flag In Hole - {0x026f5, 0x026f5}, // Sailboat ..Sailboat - {0x026fa, 0x026fa}, // Tent ..Tent - {0x026fd, 0x026fd}, // Fuel Pump ..Fuel Pump - {0x02705, 0x02705}, // White Heavy Check Mark ..White Heavy Check Mark - {0x0270a, 0x0270b}, // Raised Fist ..Raised Hand - {0x02728, 0x02728}, // Sparkles ..Sparkles - {0x0274c, 0x0274c}, // Cross Mark ..Cross Mark - {0x0274e, 0x0274e}, // Negative Squared Cross M..Negative Squared Cross M - {0x02753, 0x02755}, // Black Question Mark Orna..White Exclamation Mark O - {0x02757, 0x02757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S - {0x02795, 0x02797}, // Heavy Plus Sign ..Heavy Division Sign - {0x027b0, 0x027b0}, // Curly Loop ..Curly Loop - {0x027bf, 0x027bf}, // Double Curly Loop ..Double Curly Loop - {0x02b1b, 0x02b1c}, // Black Large Square ..White Large Square - {0x02b50, 0x02b50}, // White Medium Star ..White Medium Star - {0x02b55, 0x02b55}, // Heavy Large Circle ..Heavy Large Circle - {0x02e80, 0x02e99}, // Cjk Radical Repeat ..Cjk Radical Rap - {0x02e9b, 0x02ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified - {0x02f00, 0x02fd5}, // Kangxi Radical One ..Kangxi Radical Flute - {0x02ff0, 0x02ffb}, // Ideographic Description ..Ideographic Description - {0x03000, 0x0303e}, // Ideographic Space ..Ideographic Variation In - {0x03041, 0x03096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke - {0x03099, 0x030ff}, // Combining Katakana-hirag..Katakana Digraph Koto - {0x03105, 0x0312f}, // Bopomofo Letter B ..Bopomofo Letter Nn - {0x03131, 0x0318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae - {0x03190, 0x031e3}, // Ideographic Annotation L..Cjk Stroke Q - {0x031f0, 0x0321e}, // Katakana Letter Small Ku..Parenthesized Korean Cha - {0x03220, 0x03247}, // Parenthesized Ideograph ..Circled Ideograph Koto - {0x03250, 0x04dbf}, // Partnership Sign ..Cjk Unified Ideograph-4d - {0x04e00, 0x0a48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr - {0x0a490, 0x0a4c6}, // Yi Radical Qot ..Yi Radical Ke - {0x0a960, 0x0a97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo - {0x0ac00, 0x0d7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih - {0x0f900, 0x0faff}, // Cjk Compatibility Ideogr..(nil) - {0x0fe10, 0x0fe19}, // Presentation Form For Ve..Presentation Form For Ve - {0x0fe30, 0x0fe52}, // Presentation Form For Ve..Small Full Stop - {0x0fe54, 0x0fe66}, // Small Semicolon ..Small Equals Sign - {0x0fe68, 0x0fe6b}, // Small Reverse Solidus ..Small Commercial At - {0x0ff01, 0x0ff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa - {0x0ffe0, 0x0ffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign - {0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..Khitan Small Script Fill - {0x16ff0, 0x16ff1}, // Vietnamese Alternate Rea..Vietnamese Alternate Rea - {0x17000, 0x187f7}, // (nil) ..(nil) - {0x18800, 0x18cd5}, // Tangut Component-001 ..Khitan Small Script Char - {0x18d00, 0x18d08}, // (nil) ..(nil) - {0x1aff0, 0x1aff3}, // Katakana Letter Minnan T..Katakana Letter Minnan T - {0x1aff5, 0x1affb}, // Katakana Letter Minnan T..Katakana Letter Minnan N - {0x1affd, 0x1affe}, // Katakana Letter Minnan N..Katakana Letter Minnan N - {0x1b000, 0x1b122}, // Katakana Letter Archaic ..Katakana Letter Archaic - {0x1b132, 0x1b132}, // (nil) ..(nil) - {0x1b150, 0x1b152}, // Hiragana Letter Small Wi..Hiragana Letter Small Wo - {0x1b155, 0x1b155}, // (nil) ..(nil) - {0x1b164, 0x1b167}, // Katakana Letter Small Wi..Katakana Letter Small N - {0x1b170, 0x1b2fb}, // Nushu Character-1b170 ..Nushu Character-1b2fb - {0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon - {0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker - {0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab - {0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs - {0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa - {0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..Squared Cjk Unified Ideo - {0x1f240, 0x1f248}, // Tortoise Shell Bracketed..Tortoise Shell Bracketed - {0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept - {0x1f260, 0x1f265}, // Rounded Symbol For Fu ..Rounded Symbol For Cai - {0x1f300, 0x1f320}, // Cyclone ..Shooting Star - {0x1f32d, 0x1f335}, // Hot Dog ..Cactus - {0x1f337, 0x1f37c}, // Tulip ..Baby Bottle - {0x1f37e, 0x1f393}, // Bottle With Popping Cork..Graduation Cap - {0x1f3a0, 0x1f3ca}, // Carousel Horse ..Swimmer - {0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And - {0x1f3e0, 0x1f3f0}, // House Building ..European Castle - {0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag - {0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints - {0x1f440, 0x1f440}, // Eyes ..Eyes - {0x1f442, 0x1f4fc}, // Ear ..Videocassette - {0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red - {0x1f54b, 0x1f54e}, // Kaaba ..Menorah With Nine Branch - {0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty - {0x1f57a, 0x1f57a}, // Man Dancing ..Man Dancing - {0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be - {0x1f5a4, 0x1f5a4}, // Black Heart ..Black Heart - {0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands - {0x1f680, 0x1f6c5}, // Rocket ..Left Luggage - {0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation - {0x1f6d0, 0x1f6d2}, // Place Of Worship ..Shopping Trolley - {0x1f6d5, 0x1f6d7}, // Hindu Temple ..Elevator - {0x1f6dc, 0x1f6df}, // (nil) ..Ring Buoy - {0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving - {0x1f6f4, 0x1f6fc}, // Scooter ..Roller Skate - {0x1f7e0, 0x1f7eb}, // Large Orange Circle ..Large Brown Square - {0x1f7f0, 0x1f7f0}, // Heavy Equals Sign ..Heavy Equals Sign - {0x1f90c, 0x1f93a}, // Pinched Fingers ..Fencer - {0x1f93c, 0x1f945}, // Wrestlers ..Goal Net - {0x1f947, 0x1f9ff}, // First Place Medal ..Nazar Amulet - {0x1fa70, 0x1fa7c}, // Ballet Shoes ..Crutch - {0x1fa80, 0x1fa88}, // Yo-yo ..(nil) - {0x1fa90, 0x1fabd}, // Ringed Planet ..(nil) - {0x1fabf, 0x1fac5}, // (nil) ..Person With Crown - {0x1face, 0x1fadb}, // (nil) ..(nil) - {0x1fae0, 0x1fae8}, // Melting Face ..(nil) - {0x1faf0, 0x1faf8}, // Hand With Index Finger A..(nil) - {0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..(nil) - {0x30000, 0x3fffd}, // Cjk Unified Ideograph-30..(nil) - }; - - - private static boolean intable(int[][] table, int c) { - // First quick check f|| Latin1 etc. characters. - if (c < table[0][0]) return false; - - // Binary search in table. - int bot = 0; - int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1); - while (top >= bot) { - int mid = (bot + top) / 2; - if (table[mid][1] < c) { - bot = mid + 1; - } else if (table[mid][0] > c) { - top = mid - 1; - } else { - return true; - } - } - return false; - } - - /** Return the terminal display width of a code point: 0, 1 || 2. */ - public static int width(int ucs) { - if (ucs == 0 || - ucs == 0x034F || - (0x200B <= ucs && ucs <= 0x200F) || - ucs == 0x2028 || - ucs == 0x2029 || - (0x202A <= ucs && ucs <= 0x202E) || - (0x2060 <= ucs && ucs <= 0x2063)) { - return 0; - } - - // C0/C1 control characters - // Termux change: Return 0 instead of -1. - if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0; - - // combining characters with zero width - if (intable(ZERO_WIDTH, ucs)) return 0; - - return intable(WIDE_EASTASIAN, ucs) ? 2 : 1; - } - - /** The width at an index position in a java char array. */ - public static int width(char[] chars, int index) { - char c = chars[index]; - return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c); - } - - /** - * The zero width characters count like combining characters in the `chars` array from start - * index to end index (exclusive). - */ - public static int zeroWidthCharsCount(char[] chars, int start, int end) { - if (start < 0 || start >= chars.length) - return 0; - - int count = 0; - for (int i = start; i < end && i < chars.length;) { - if (Character.isHighSurrogate(chars[i])) { - if (width(Character.toCodePoint(chars[i], chars[i + 1])) <= 0) { - count++; - } - i += 2; - } else { - if (width(chars[i]) <= 0) { - count++; - } - i++; - } - } - return count; - } - -} diff --git a/terminal-emulator/src/main/jni/Android.mk b/terminal-emulator/src/main/jni/Android.mk deleted file mode 100644 index 6c6f8b2299..0000000000 --- a/terminal-emulator/src/main/jni/Android.mk +++ /dev/null @@ -1,5 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE:= libtermux -LOCAL_SRC_FILES:= termux.c -include $(BUILD_SHARED_LIBRARY) diff --git a/terminal-emulator/src/main/jni/termux.c b/terminal-emulator/src/main/jni/termux.c deleted file mode 100644 index 8cd5e7891d..0000000000 --- a/terminal-emulator/src/main/jni/termux.c +++ /dev/null @@ -1,218 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TERMUX_UNUSED(x) x __attribute__((__unused__)) -#ifdef __APPLE__ -# define LACKS_PTSNAME_R -#endif - -static int throw_runtime_exception(JNIEnv* env, char const* message) -{ - jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException"); - (*env)->ThrowNew(env, exClass, message); - return -1; -} - -static int create_subprocess(JNIEnv* env, - char const* cmd, - char const* cwd, - char* const argv[], - char** envp, - int* pProcessId, - jint rows, - jint columns, - jint cell_width, - jint cell_height) -{ - int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC); - if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx"); - -#ifdef LACKS_PTSNAME_R - char* devname; -#else - char devname[64]; -#endif - if (grantpt(ptm) || unlockpt(ptm) || -#ifdef LACKS_PTSNAME_R - (devname = ptsname(ptm)) == NULL -#else - ptsname_r(ptm, devname, sizeof(devname)) -#endif - ) { - return throw_runtime_exception(env, "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx"); - } - - // Enable UTF-8 mode and disable flow control to prevent Ctrl+S from locking up the display. - struct termios tios; - tcgetattr(ptm, &tios); - tios.c_iflag |= IUTF8; - tios.c_iflag &= ~(IXON | IXOFF); - tcsetattr(ptm, TCSANOW, &tios); - - /** Set initial winsize. */ - struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns, .ws_xpixel = (unsigned short) (columns * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height)}; - ioctl(ptm, TIOCSWINSZ, &sz); - - pid_t pid = fork(); - if (pid < 0) { - return throw_runtime_exception(env, "Fork failed"); - } else if (pid > 0) { - *pProcessId = (int) pid; - return ptm; - } else { - // Clear signals which the Android java process may have blocked: - sigset_t signals_to_unblock; - sigfillset(&signals_to_unblock); - sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0); - - close(ptm); - setsid(); - - int pts = open(devname, O_RDWR); - if (pts < 0) exit(-1); - - dup2(pts, 0); - dup2(pts, 1); - dup2(pts, 2); - - DIR* self_dir = opendir("/proc/self/fd"); - if (self_dir != NULL) { - int self_dir_fd = dirfd(self_dir); - struct dirent* entry; - while ((entry = readdir(self_dir)) != NULL) { - int fd = atoi(entry->d_name); - if (fd > 2 && fd != self_dir_fd) close(fd); - } - closedir(self_dir); - } - - clearenv(); - if (envp) for (; *envp; ++envp) putenv(*envp); - - if (chdir(cwd) != 0) { - char* error_message; - // No need to free asprintf()-allocated memory since doing execvp() or exit() below. - if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()"; - perror(error_message); - fflush(stderr); - } - execvp(cmd, argv); - // Show terminal output about failing exec() call: - char* error_message; - if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()"; - perror(error_message); - _exit(1); - } -} - -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, - jint cell_width, - jint cell_height) -{ - jsize size = args ? (*env)->GetArrayLength(env, args) : 0; - char** argv = NULL; - if (size > 0) { - argv = (char**) malloc((size + 1) * sizeof(char*)); - if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array"); - for (int i = 0; i < size; ++i) { - jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i); - char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL); - if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv"); - argv[i] = strdup(arg_utf8); - (*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8); - } - argv[size] = NULL; - } - - size = envVars ? (*env)->GetArrayLength(env, envVars) : 0; - char** envp = NULL; - if (size > 0) { - envp = (char**) malloc((size + 1) * sizeof(char *)); - if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed"); - for (int i = 0; i < size; ++i) { - jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i); - char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0); - if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env"); - envp[i] = strdup(env_utf8); - (*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8); - } - envp[size] = NULL; - } - - 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, rows, columns, cell_width, cell_height); - (*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8); - (*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd); - - if (argv) { - for (char** tmp = argv; *tmp; ++tmp) free(*tmp); - free(argv); - } - if (envp) { - for (char** tmp = envp; *tmp; ++tmp) free(*tmp); - free(envp); - } - - int* pProcId = (int*) (*env)->GetPrimitiveArrayCritical(env, processIdArray, NULL); - if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed"); - - *pProcId = procId; - (*env)->ReleasePrimitiveArrayCritical(env, processIdArray, pProcId, 0); - - return ptm; -} - -JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols, jint cell_width, jint cell_height) -{ - struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols, .ws_xpixel = (unsigned short) (cols * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height) }; - ioctl(fd, TIOCSWINSZ, &sz); -} - -JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd) -{ - struct termios tios; - tcgetattr(fd, &tios); - if ((tios.c_iflag & IUTF8) == 0) { - tios.c_iflag |= IUTF8; - tcsetattr(fd, TCSANOW, &tios); - } -} - -JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid) -{ - int status; - waitpid(pid, &status, 0); - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - return -WTERMSIG(status); - } else { - // Should never happen - waitpid(2) says "One of the first three macros will evaluate to a non-zero (true) value". - return 0; - } -} - -JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor) -{ - close(fileDescriptor); -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java deleted file mode 100644 index 4f6292aaa9..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.termux.terminal; - -public class ApcTest extends TerminalTestCase { - - public void testApcConsumed() { - // At time of writing this is part of what yazi sends for probing for kitty graphics protocol support: - // https://github.com/sxyazi/yazi/blob/0cdaff98d0b3723caff63eebf1974e7907a43a2c/yazi-adapter/src/emulator.rs#L129 - // This should not result in anything being written to the screen: If kitty graphics protocol support - // is implemented it should instead result in an error code on stdin, and if not it should be consumed - // silently just as xterm does. See https://sw.kovidgoyal.net/kitty/graphics-protocol/. - withTerminalSized(2, 2) - .enterString("\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\") - .assertLinesAre(" ", " "); - - // It is ok for the APC content to be non printable characters: - withTerminalSized(12, 2) - .enterString("hello \033_some\023\033_\\apc#end\033\\ world") - .assertLinesAre("hello world", " "); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ByteQueueTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ByteQueueTest.java deleted file mode 100644 index 44a605bddc..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ByteQueueTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.termux.terminal; - -import junit.framework.TestCase; - -public class ByteQueueTest extends TestCase { - - private static void assertArrayEquals(byte[] expected, byte[] actual) { - if (expected.length != actual.length) { - fail("Difference array length"); - } - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - fail("Inequals at index=" + i + ", expected=" + (int) expected[i] + ", actual=" + (int) actual[i]); - } - } - } - - public void testCompleteWrites() throws Exception { - ByteQueue q = new ByteQueue(10); - assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3)); - - byte[] arr = new byte[10]; - assertEquals(3, q.read(arr, true)); - assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]}); - - assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10)); - assertEquals(10, q.read(arr, true)); - assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr); - } - - public void testQueueWraparound() throws Exception { - ByteQueue q = new ByteQueue(10); - - byte[] origArray = new byte[]{1, 2, 3, 4, 5, 6}; - byte[] readArray = new byte[origArray.length]; - for (int i = 0; i < 20; i++) { - q.write(origArray, 0, origArray.length); - assertEquals(origArray.length, q.read(readArray, true)); - assertArrayEquals(origArray, readArray); - } - } - - public void testWriteNotesClosing() throws Exception { - ByteQueue q = new ByteQueue(10); - q.close(); - assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3)); - } - - public void testReadNonBlocking() throws Exception { - ByteQueue q = new ByteQueue(10); - assertEquals(0, q.read(new byte[128], false)); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ControlSequenceIntroducerTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ControlSequenceIntroducerTest.java deleted file mode 100644 index 9f123bc1d1..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ControlSequenceIntroducerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.termux.terminal; - -import java.util.List; - -/** "\033[" is the Control Sequence Introducer char sequence (CSI). */ -public class ControlSequenceIntroducerTest extends TerminalTestCase { - - /** CSI Ps P Scroll down Ps lines (default = 1) (SD). */ - public void testCsiT() { - withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[2Tyo\r\nA\r\nB").assertLinesAre(" ", " ", "1 ", "2 yo", "A ", - "Bi "); - // Default value (1): - withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[Tyo\r\nA\r\nB").assertLinesAre(" ", "1 ", "2 ", "3 yo", "Ai ", - "B "); - } - - /** CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public void testCsiS() { - // The behaviour here is a bit inconsistent between terminals - this is how the OS X Terminal.app does it: - withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[2Sy").assertLinesAre("3 ", "hi ", " ", " y"); - // Default value (1): - withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[Sy").assertLinesAre("2 ", "3 ", "hi ", " y"); - } - - /** CSI Ps X Erase Ps Character(s) (default = 1) (ECH). */ - public void testCsiX() { - // See https://code.google.com/p/chromium/issues/detail?id=212712 where test was extraced from. - withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[X").assertLinesAre("abcdefg ijkl ", " "); - withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[1X").assertLinesAre("abcdefg ijkl ", " "); - withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[2X").assertLinesAre("abcdefg jkl ", " "); - withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " "); - } - - /** CSI Pm m Set SGR parameter(s) from semicolon-separated list Pm. */ - public void testCsiSGRParameters() { - // Set more parameters (19) than supported (16). Additional parameters should be silently consumed. - withTerminalSized(3, 2).enterString("\033[0;38;2;255;255;255;48;2;0;0;0;1;2;3;4;5;7;8;9mabc").assertLinesAre("abc", " "); - } - - /** CSI Ps b Repeat the preceding graphic character Ps times (REP). */ - public void testRepeat() { - withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " "); - withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " "); - // When no char has been output we ignore REP: - withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " "); - // This shows that REP outputs the last emitted code point and not the one relative to the - // current cursor position: - withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " "); - } - - /** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */ - public void testCsi3J() { - withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd"); - assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText()); - enterString("\033[3J"); - assertEquals("c\nd", mTerminal.getScreen().getTranscriptText()); - - withTerminalSized(3, 2).enterString("Lorem_ipsum"); - assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText()); - enterString("\033[3J"); - assertEquals("ipsum", mTerminal.getScreen().getTranscriptText()); - - withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l"); - assertEquals("y\nz", mTerminal.getScreen().getTranscriptText()); - } - - public void testReportPixelSize() { - int columns = 3; - int rows = 3; - withTerminalSized(columns, rows); - int cellWidth = TerminalTest.INITIAL_CELL_WIDTH_PIXELS; - int cellHeight = TerminalTest.INITIAL_CELL_HEIGHT_PIXELS; - assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t"); - assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t"); - columns = 23; - rows = 33; - resize(columns, rows); - assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t"); - assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t"); - cellWidth = 8; - cellHeight = 18; - mTerminal.resize(columns, rows, cellWidth, cellHeight); - assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t"); - assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t"); - } - - /** - * See Colored and styled underlines: - * - *
-     *  [4:0m  # no underline
-     *  [4:1m  # straight underline
-     *  [4:2m  # double underline
-     *  [4:3m  # curly underline
-     *  [4:4m  # dotted underline
-     *  [4:5m  # dashed underline
-     *  [4m    # straight underline (for backwards compat)
-     *  [24m   # no underline (for backwards compat)
-     * 
- *

- * We currently parse the variants, but map them to normal/no underlines as appropriate - */ - public void testUnderlineVariants() { - for (String suffix : List.of("", ":1", ":2", ":3", ":4", ":5")) { - for (String stop : List.of("24", "4:0")) { - withTerminalSized(3, 3); - enterString("\033[4" + suffix + "m").assertLinesAre(" ", " ", " "); - assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect); - enterString("\033[4;1m").assertLinesAre(" ", " ", " "); - assertEquals(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect); - enterString("\033[" + stop + "m").assertLinesAre(" ", " ", " "); - assertEquals(TextStyle.CHARACTER_ATTRIBUTE_BOLD, mTerminal.mEffect); - } - } - } - - public void testManyParameters() { - StringBuilder b = new StringBuilder("\033["); - for (int i = 0; i < 30; i++) { - b.append("0;"); - } - b.append("4:2"); - // This clearing of underline should be ignored as the parameters pass the threshold for too many parameters: - b.append("4:0m"); - withTerminalSized(3, 3) - .enterString(b.toString()) - .assertLinesAre(" ", " ", " "); - assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/CursorAndScreenTest.java b/terminal-emulator/src/test/java/com/termux/terminal/CursorAndScreenTest.java deleted file mode 100644 index 8a65e66d00..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/CursorAndScreenTest.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.termux.terminal; - -import org.junit.Assert; - -public class CursorAndScreenTest extends TerminalTestCase { - - public void testDeleteLinesKeepsStyles() { - int cols = 5, rows = 5; - withTerminalSized(cols, rows); - for (int row = 0; row < 5; row++) { - for (int col = 0; col < 5; col++) { - // Foreground color to col, background to row: - enterString("\033[38;5;" + col + "m"); - enterString("\033[48;5;" + row + "m"); - enterString(Character.toString((char) ('A' + col + row * 5))); - } - } - assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY"); - for (int row = 0; row < 5; row++) { - for (int col = 0; col < 5; col++) { - long s = getStyleAt(row, col); - Assert.assertEquals(col, TextStyle.decodeForeColor(s)); - Assert.assertEquals(row, TextStyle.decodeBackColor(s)); - } - } - // "${CSI}H" - place cursor at 1,1, then "${CSI}2M" to delete two lines. - enterString("\033[H\033[2M"); - assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " "); - for (int row = 0; row < 3; row++) { - for (int col = 0; col < 5; col++) { - long s = getStyleAt(row, col); - Assert.assertEquals(col, TextStyle.decodeForeColor(s)); - Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s)); - } - } - // Set default fg and background for the new blank lines: - enterString("\033[38;5;98m"); - enterString("\033[48;5;99m"); - // "${CSI}B" to go down one line, then "${CSI}2L" to insert two lines: - enterString("\033[B\033[2L"); - assertLinesAre("KLMNO", " ", " ", "PQRST", "UVWXY"); - for (int row = 0; row < 5; row++) { - for (int col = 0; col < 5; col++) { - int wantedForeground = (row == 1 || row == 2) ? 98 : col; - int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row); - long s = getStyleAt(row, col); - Assert.assertEquals(wantedForeground, TextStyle.decodeForeColor(s)); - Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(s)); - } - } - } - - public void testDeleteCharacters() { - withTerminalSized(5, 2).enterString("枝ce").assertLinesAre("枝ce ", " "); - withTerminalSized(5, 2).enterString("a枝ce").assertLinesAre("a枝ce", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[P").assertLinesAre("ice ", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[2P").assertLinesAre("ce ", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[2P").assertLinesAre("ne ", " "); - // "${CSI}${n}P, the delete characters (DCH) sequence should cap characters to delete. - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[99P").assertLinesAre(" ", " "); - // With combining char U+0302. - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[2P").assertLinesAre("ce ", " "); - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[P").assertLinesAre("ice ", " "); - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[2G\033[2P").assertLinesAre("n\u0302e ", " "); - // With wide 枝 char, checking that putting char at part replaces other with whitespace: - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[Ga").assertLinesAre("a ce ", " "); - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2Ga").assertLinesAre(" ace ", " "); - // With wide 枝 char, deleting either part replaces other with whitespace: - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[G\033[P").assertLinesAre(" ce ", " "); - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2G\033[P").assertLinesAre(" ce ", " "); - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2G\033[2P").assertLinesAre(" e ", " "); - withTerminalSized(5, 2).enterString("枝ce").enterString("\033[G\033[2P").assertLinesAre("ce ", " "); - withTerminalSized(5, 2).enterString("a枝ce").enterString("\033[G\033[P").assertLinesAre("枝ce ", " "); - } - - public void testInsertMode() { - // "${CSI}4h" enables insert mode. - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4hA").assertLinesAre("Anice", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[4hA").assertLinesAre("nAice", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4hABC").assertLinesAre("ABCni", " "); - // With combining char U+0302. - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[4hA").assertLinesAre("An\u0302ice", " "); - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[4hAB").assertLinesAre("ABn\u0302ic", " "); - withTerminalSized(5, 2).enterString("n\u0302ic\u0302e").enterString("\033[2G\033[4hA").assertLinesAre("n\u0302Aic\u0302e", " "); - // ... but without insert mode, combining char should be overwritten: - withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[GA").assertLinesAre("Aice ", " "); - // ... also with two combining: - withTerminalSized(5, 2).enterString("n\u0302\u0302i\u0302ce").enterString("\033[GA").assertLinesAre("Ai\u0302ce ", " "); - // ... and in last column: - withTerminalSized(5, 2).enterString("n\u0302\u0302ice!\u0302").enterString("\033[5GA").assertLinesAre("n\u0302\u0302iceA", " "); - withTerminalSized(5, 2).enterString("nic\u0302e!\u0302").enterString("\033[4G枝").assertLinesAre("nic\u0302枝", " "); - withTerminalSized(5, 2).enterString("nic枝\u0302").enterString("\033[3GA").assertLinesAre("niA枝\u0302", " "); - withTerminalSized(5, 2).enterString("nic枝\u0302").enterString("\033[3GA").assertLinesAre("niA枝\u0302", " "); - // With wide 枝 char. - withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4h枝").assertLinesAre("枝nic", " "); - withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[4h枝").assertLinesAre("n枝ic", " "); - withTerminalSized(5, 2).enterString("n枝ce").enterString("\033[G\033[4ha").assertLinesAre("an枝c", " "); - } - - /** HPA—Horizontal Position Absolute (http://www.vt100.net/docs/vt510-rm/HPA) */ - public void testCursorHorizontalPositionAbsolute() { - withTerminalSized(4, 4).enterString("ABC\033[`").assertCursorAt(0, 0); - enterString("\033[1`").assertCursorAt(0, 0).enterString("\033[2`").assertCursorAt(0, 1); - enterString("\r\n\033[3`").assertCursorAt(1, 2).enterString("\033[22`").assertCursorAt(1, 3); - // Enable and configure right and left margins, first without origin mode: - enterString("\033[?69h\033[2;3s\033[`").assertCursorAt(0, 0).enterString("\033[22`").assertCursorAt(0, 3); - // .. now with origin mode: - enterString("\033[?6h\033[`").assertCursorAt(0, 1).enterString("\033[22`").assertCursorAt(0, 2); - } - - public void testCursorForward() { - // "${CSI}${N:=1}C" moves cursor forward N columns: - withTerminalSized(6, 2).enterString("A\033[CB\033[2CC").assertLinesAre("A B C", " "); - // If an attempt is made to move the cursor to the right of the right margin, the cursor stops at the right margin: - withTerminalSized(6, 2).enterString("A\033[44CB").assertLinesAre("A B", " "); - // Enable right margin and verify that CUF ends at the set right margin: - withTerminalSized(6, 2).enterString("\033[?69h\033[1;3s\033[44CAB").assertLinesAre(" A ", "B "); - } - - public void testCursorBack() { - // "${CSI}${N:=1}D" moves cursor back N columns: - withTerminalSized(3, 2).enterString("A\033[DB").assertLinesAre("B ", " "); - withTerminalSized(3, 2).enterString("AB\033[2DC").assertLinesAre("CB ", " "); - // If an attempt is made to move the cursor to the left of the left margin, the cursor stops at the left margin: - withTerminalSized(3, 2).enterString("AB\033[44DC").assertLinesAre("CB ", " "); - // Enable left margin and verify that CUB ends at the set left margin: - withTerminalSized(6, 2).enterString("ABCD\033[?69h\033[2;6s\033[44DE").assertLinesAre("AECD ", " "); - } - - public void testCursorUp() { - // "${CSI}${N:=1}A" moves cursor up N rows: - withTerminalSized(3, 3).enterString("ABCDEFG\033[AH").assertLinesAre("ABC", "DHF", "G "); - withTerminalSized(3, 3).enterString("ABCDEFG\033[2AH").assertLinesAre("AHC", "DEF", "G "); - // If an attempt is made to move the cursor above the top margin, the cursor stops at the top margin: - withTerminalSized(3, 3).enterString("ABCDEFG\033[44AH").assertLinesAre("AHC", "DEF", "G "); - } - - public void testCursorDown() { - // "${CSI}${N:=1}B" moves cursor down N rows: - withTerminalSized(3, 3).enterString("AB\033[BC").assertLinesAre("AB ", " C", " "); - withTerminalSized(3, 3).enterString("AB\033[2BC").assertLinesAre("AB ", " ", " C"); - // If an attempt is made to move the cursor below the bottom margin, the cursor stops at the bottom margin: - withTerminalSized(3, 3).enterString("AB\033[44BC").assertLinesAre("AB ", " ", " C"); - } - - public void testReportCursorPosition() { - withTerminalSized(10, 10); - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - enterString("\033[" + (i + 1) + ";" + (j + 1) + "H"); // CUP cursor position. - assertCursorAt(i, j); - // Device Status Report (DSR): - assertEnteringStringGivesResponse("\033[6n", "\033[" + (i + 1) + ";" + (j + 1) + "R"); - // DECXCPR — Extended Cursor Position. Note that http://www.vt100.net/docs/vt510-rm/DECXCPR says - // the response is "${CSI}${LINE};${COLUMN};${PAGE}R" while xterm (http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) - // drops the question mark. Expect xterm behaviour here. - assertEnteringStringGivesResponse("\033[?6n", "\033[?" + (i + 1) + ";" + (j + 1) + ";1R"); - } - } - } - - /** - * See comments on horizontal tab handling in TerminalEmulator.java. - *

- * We do not want to color already written cells when tabbing over them. - */ - public void DISABLED_testHorizontalTabColorsBackground() { - withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t"); - assertCursorAt(0, 8); - for (int i = 0; i < 10; i++) { - int expectedColor = i < 8 ? 15 : TextStyle.COLOR_INDEX_BACKGROUND; - assertEquals(expectedColor, TextStyle.decodeBackColor(getStyleAt(0, i))); - } - } - - /** - * Test interactions between the cursor overflow bit and various escape sequences. - *

- * Adapted from hterm: - * https://chromium.googlesource.com/chromiumos/platform/assets/+/2337afa5c063127d5ce40ec7fec9b602d096df86%5E%21/#F2 - */ - public void testClearingOfAutowrap() { - // Fill a row with the last hyphen wrong, then run a command that - // modifies the screen, then add a hyphen. The wrap bit should be - // cleared, so the extra hyphen can fix the row. - withTerminalSized(15, 6); - - enterString("----- 1 ----X"); - enterString("\033[K-"); // EL - - enterString("----- 2 ----X"); - enterString("\033[J-"); // ED - - enterString("----- 3 ----X"); - enterString("\033[@-"); // ICH - - enterString("----- 4 ----X"); - enterString("\033[P-"); // DCH - - enterString("----- 5 ----X"); - enterString("\033[X-"); // ECH - - // DL will delete the entire line but clear the wrap bit, so we - // expect a hyphen at the end and nothing else. - enterString("XXXXXXXXXXXXXXX"); - enterString("\033[M-"); // DL - - assertLinesAre( - "----- 1 -----", - "----- 2 -----", - "----- 3 -----", - "----- 4 -----", - "----- 5 -----", - " -"); - } - - public void testBackspaceAcrossWrappedLines() { - // Backspace should not go to previous line if not auto-wrapped: - withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " "); - // Backspace should go to previous line if auto-wrapped: - withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " "); - // Initial backspace should do nothing: - withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " "); - } - - public void testCursorSaveRestoreLocation() { - // DEC save/restore - withTerminalSized(4, 2).enterString("t\0337est\r\nme\0338ry ").assertLinesAre("try ", "me "); - // ANSI.SYS save/restore - withTerminalSized(4, 2).enterString("t\033[sest\r\nme\033[ury ").assertLinesAre("try ", "me "); - // Alternate screen enter/exit - withTerminalSized(4, 2).enterString("t\033[?1049h\033[Hest\r\nme").assertLinesAre("est ", "me ").enterString("\033[?1049lry").assertLinesAre("try ", " "); - } - - public void testCursorSaveRestoreTextStyle() { - long s; - - // DEC save/restore - withTerminalSized(4, 2).enterString("\033[31;42;4m..\0337\033[36;47;24m\0338.."); - s = getStyleAt(0, 3); - Assert.assertEquals(1, TextStyle.decodeForeColor(s)); - Assert.assertEquals(2, TextStyle.decodeBackColor(s)); - Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s)); - - // ANSI.SYS save/restore - withTerminalSized(4, 2).enterString("\033[31;42;4m..\033[s\033[36;47;24m\033[u.."); - s = getStyleAt(0, 3); - Assert.assertEquals(1, TextStyle.decodeForeColor(s)); - Assert.assertEquals(2, TextStyle.decodeBackColor(s)); - Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s)); - - // Alternate screen enter/exit - withTerminalSized(4, 2); - enterString("\033[31;42;4m..\033[?1049h\033[H\033[36;47;24m."); - s = getStyleAt(0, 0); - Assert.assertEquals(6, TextStyle.decodeForeColor(s)); - Assert.assertEquals(7, TextStyle.decodeBackColor(s)); - Assert.assertEquals(0, TextStyle.decodeEffect(s)); - enterString("\033[?1049l.."); - s = getStyleAt(0, 3); - Assert.assertEquals(1, TextStyle.decodeForeColor(s)); - Assert.assertEquals(2, TextStyle.decodeBackColor(s)); - Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s)); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java b/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java deleted file mode 100644 index f31f1fb833..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.termux.terminal; - -/** - *

- * "CSI ? Pm h", DEC Private Mode Set (DECSET)
- * 
- *

- * and - *

- *

- * "CSI ? Pm l", DEC Private Mode Reset (DECRST)
- * 
- *

- * controls various aspects of the terminal - */ -public class DecSetTest extends TerminalTestCase { - - /** DECSET 25, DECTCEM, controls visibility of the cursor. */ - public void testEnableDisableCursor() { - withTerminalSized(3, 3); - assertTrue("Initially the cursor should be enabled", mTerminal.isCursorEnabled()); - enterString("\033[?25l"); // Disable Cursor (DECTCEM). - assertFalse(mTerminal.isCursorEnabled()); - enterString("\033[?25h"); // Enable Cursor (DECTCEM). - assertTrue(mTerminal.isCursorEnabled()); - - enterString("\033[?25l"); // Disable Cursor (DECTCEM), again. - assertFalse(mTerminal.isCursorEnabled()); - mTerminal.reset(); - assertTrue("Resetting the terminal should enable the cursor", mTerminal.isCursorEnabled()); - - enterString("\033[?25l"); - assertFalse(mTerminal.isCursorEnabled()); - enterString("\033c"); // RIS resetting should enabled cursor. - assertTrue(mTerminal.isCursorEnabled()); - } - - /** DECSET 2004, controls bracketed paste mode. */ - public void testBracketedPasteMode() { - withTerminalSized(3, 3); - - mTerminal.paste("a"); - assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear()); - - enterString("\033[?2004h"); // Enable bracketed paste mode. - mTerminal.paste("a"); - assertEquals("Pasting when in bracketed paste mode should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear()); - - enterString("\033[?2004l"); // Disable bracketed paste mode. - mTerminal.paste("a"); - assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear()); - - enterString("\033[?2004h"); // Enable bracketed paste mode, again. - mTerminal.paste("a"); - assertEquals("Pasting when in bracketed paste mode again should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear()); - - mTerminal.paste("\033ab\033cd\033"); - assertEquals("Pasting an escape character should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear()); - mTerminal.paste("\u0081ab\u0081cd\u009F"); - assertEquals("Pasting C1 control codes should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear()); - - mTerminal.reset(); - mTerminal.paste("a"); - assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear()); - } - - /** DECSET 7, DECAWM, controls wraparound mode. */ - public void testWrapAroundMode() { - // Default with wraparound: - withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " "); - // With wraparound disabled: - withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " "); - enterString("efg").assertLinesAre("abg", " ", " "); - // Re-enabling wraparound: - enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " "); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/DeviceControlStringTest.java b/terminal-emulator/src/test/java/com/termux/terminal/DeviceControlStringTest.java deleted file mode 100644 index f8899355fa..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/DeviceControlStringTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.termux.terminal; - -/** - * "\033P" is a device control string. - */ -public class DeviceControlStringTest extends TerminalTestCase { - - private static String hexEncode(String s) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < s.length(); i++) - result.append(String.format("%02X", (int) s.charAt(i))); - return result.toString(); - } - - private void assertCapabilityResponse(String cap, String expectedResponse) { - String input = "\033P+q" + hexEncode(cap) + "\033\\"; - assertEnteringStringGivesResponse(input, "\033P1+r" + hexEncode(cap) + "=" + hexEncode(expectedResponse) + "\033\\"); - } - - public void testReportColorsAndName() { - // Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in - // hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key - // names. - // Two special features are also recognized, which are not key names: Co for termcap colors (or colors - // for terminfo colors), and TN for termcap name (or name for terminfo name). - // xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the - // corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are - // encoded in hexadecimal (2 digits per character). - withTerminalSized(3, 3).enterString("A"); - assertCapabilityResponse("Co", "256"); - assertCapabilityResponse("colors", "256"); - assertCapabilityResponse("TN", "xterm"); - assertCapabilityResponse("name", "xterm"); - enterString("B").assertLinesAre("AB ", " ", " "); - } - - public void testReportKeys() { - withTerminalSized(3, 3); - assertCapabilityResponse("kB", "\033[Z"); - } - - public void testReallyLongDeviceControlString() { - withTerminalSized(3, 3).enterString("\033P"); - for (int i = 0; i < 10000; i++) { - enterString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - } - // The terminal should ignore the overlong DCS sequence and continue printing "aaa." and fill at least the first two lines with - // them: - assertLineIs(0, "aaa"); - assertLineIs(1, "aaa"); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/HistoryTest.java b/terminal-emulator/src/test/java/com/termux/terminal/HistoryTest.java deleted file mode 100644 index 17f8111205..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/HistoryTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.termux.terminal; - - -public class HistoryTest extends TerminalTestCase { - - public void testHistory() { - final int rows = 3; - final int cols = 3; - withTerminalSized(cols, rows).enterString("111222333444555666777888999"); - assertCursorAt(2, 2); - assertLinesAre("777", "888", "999"); - assertHistoryStartsWith("666", "555"); - - resize(cols, 2); - assertHistoryStartsWith("777", "666", "555"); - - resize(cols, 3); - assertHistoryStartsWith("666", "555"); - } - - public void testHistoryWithScrollRegion() { - // "CSI P_s ; P_s r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). - withTerminalSized(3, 4).enterString("111222333444"); - assertLinesAre("111", "222", "333", "444"); - enterString("\033[2;3r"); - // NOTE: "DECSTBM moves the cursor to column 1, line 1 of the page." - assertCursorAt(0, 0); - enterString("\nCDEFGH").assertLinesAre("111", "CDE", "FGH", "444"); - enterString("IJK").assertLinesAre("111", "FGH", "IJK", "444").assertHistoryStartsWith("CDE"); - enterString("LMN").assertLinesAre("111", "IJK", "LMN", "444").assertHistoryStartsWith("FGH", "CDE"); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/KeyHandlerTest.java b/terminal-emulator/src/test/java/com/termux/terminal/KeyHandlerTest.java deleted file mode 100644 index 15b23761a8..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/KeyHandlerTest.java +++ /dev/null @@ -1,203 +0,0 @@ -package com.termux.terminal; - -import android.view.KeyEvent; - -import junit.framework.TestCase; - -public class KeyHandlerTest extends TestCase { - - private static String stringToHex(String s) { - if (s == null) return null; - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - if (buffer.length() > 0) { - buffer.append(" "); - } - buffer.append("0x"); - buffer.append(Integer.toHexString(s.charAt(i))); - } - return buffer.toString(); - } - - private static void assertKeysEquals(String expected, String actual) { - if (!expected.equals(actual)) { - assertEquals(stringToHex(expected), stringToHex(actual)); - } - } - - /** See http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html */ - public void testTermCaps() { - // Backspace. - assertKeysEquals("\u007f", KeyHandler.getCodeFromTermcap("kb", false, false)); - - // Back tab. - assertKeysEquals("\033[Z", KeyHandler.getCodeFromTermcap("kB", false, false)); - - // Arrow keys (up/down/right/left): - assertKeysEquals("\033[A", KeyHandler.getCodeFromTermcap("ku", false, false)); - assertKeysEquals("\033[B", KeyHandler.getCodeFromTermcap("kd", false, false)); - assertKeysEquals("\033[C", KeyHandler.getCodeFromTermcap("kr", false, false)); - assertKeysEquals("\033[D", KeyHandler.getCodeFromTermcap("kl", false, false)); - // .. shifted: - assertKeysEquals("\033[1;2A", KeyHandler.getCodeFromTermcap("kUP", false, false)); - assertKeysEquals("\033[1;2B", KeyHandler.getCodeFromTermcap("kDN", false, false)); - assertKeysEquals("\033[1;2C", KeyHandler.getCodeFromTermcap("%i", false, false)); - assertKeysEquals("\033[1;2D", KeyHandler.getCodeFromTermcap("#4", false, false)); - - // Home/end keys: - assertKeysEquals("\033[H", KeyHandler.getCodeFromTermcap("kh", false, false)); - assertKeysEquals("\033[F", KeyHandler.getCodeFromTermcap("@7", false, false)); - // ... shifted: - assertKeysEquals("\033[1;2H", KeyHandler.getCodeFromTermcap("#2", false, false)); - assertKeysEquals("\033[1;2F", KeyHandler.getCodeFromTermcap("*7", false, false)); - - // The traditional keyboard keypad: - // [Insert] [Home] [Page Up ] - // [Delete] [End] [Page Down] - // - // Termcap names (with xterm response in parenthesis): - // K1=Upper left of keypad (xterm sends same "[H" = Home). - // K2=Center of keypad (xterm sends invalid response). - // K3=Upper right of keypad (xterm sends "[5~" = Page Up). - // K4=Lower left of keypad (xterm sends "[F" = End key). - // K5=Lower right of keypad (xterm sends "[6~" = Page Down). - // - // vim/neovim (runtime/doc/term.txt): - // t_K1 keypad home key - // t_K3 keypad page-up key - // t_K4 keypad end key - // t_K5 keypad page-down key - // - assertKeysEquals("\033[H", KeyHandler.getCodeFromTermcap("K1", false, false)); - assertKeysEquals("\033OH", KeyHandler.getCodeFromTermcap("K1", true, false)); - assertKeysEquals("\033[5~", KeyHandler.getCodeFromTermcap("K3", false, false)); - assertKeysEquals("\033[F", KeyHandler.getCodeFromTermcap("K4", false, false)); - assertKeysEquals("\033OF", KeyHandler.getCodeFromTermcap("K4", true, false)); - assertKeysEquals("\033[6~", KeyHandler.getCodeFromTermcap("K5", false, false)); - - // Function keys F1-F12: - assertKeysEquals("\033OP", KeyHandler.getCodeFromTermcap("k1", false, false)); - assertKeysEquals("\033OQ", KeyHandler.getCodeFromTermcap("k2", false, false)); - assertKeysEquals("\033OR", KeyHandler.getCodeFromTermcap("k3", false, false)); - assertKeysEquals("\033OS", KeyHandler.getCodeFromTermcap("k4", false, false)); - assertKeysEquals("\033[15~", KeyHandler.getCodeFromTermcap("k5", false, false)); - assertKeysEquals("\033[17~", KeyHandler.getCodeFromTermcap("k6", false, false)); - assertKeysEquals("\033[18~", KeyHandler.getCodeFromTermcap("k7", false, false)); - assertKeysEquals("\033[19~", KeyHandler.getCodeFromTermcap("k8", false, false)); - assertKeysEquals("\033[20~", KeyHandler.getCodeFromTermcap("k9", false, false)); - assertKeysEquals("\033[21~", KeyHandler.getCodeFromTermcap("k;", false, false)); - assertKeysEquals("\033[23~", KeyHandler.getCodeFromTermcap("F1", false, false)); - assertKeysEquals("\033[24~", KeyHandler.getCodeFromTermcap("F2", false, false)); - // Function keys F13-F24 (same as shifted F1-F12): - assertKeysEquals("\033[1;2P", KeyHandler.getCodeFromTermcap("F3", false, false)); - assertKeysEquals("\033[1;2Q", KeyHandler.getCodeFromTermcap("F4", false, false)); - assertKeysEquals("\033[1;2R", KeyHandler.getCodeFromTermcap("F5", false, false)); - assertKeysEquals("\033[1;2S", KeyHandler.getCodeFromTermcap("F6", false, false)); - assertKeysEquals("\033[15;2~", KeyHandler.getCodeFromTermcap("F7", false, false)); - assertKeysEquals("\033[17;2~", KeyHandler.getCodeFromTermcap("F8", false, false)); - assertKeysEquals("\033[18;2~", KeyHandler.getCodeFromTermcap("F9", false, false)); - assertKeysEquals("\033[19;2~", KeyHandler.getCodeFromTermcap("FA", false, false)); - assertKeysEquals("\033[20;2~", KeyHandler.getCodeFromTermcap("FB", false, false)); - assertKeysEquals("\033[21;2~", KeyHandler.getCodeFromTermcap("FC", false, false)); - assertKeysEquals("\033[23;2~", KeyHandler.getCodeFromTermcap("FD", false, false)); - assertKeysEquals("\033[24;2~", KeyHandler.getCodeFromTermcap("FE", false, false)); - } - - public void testKeyCodes() { - // Return sends carriage return (\r), which normally gets translated by the device driver to newline (\n) unless the ICRNL termios - // flag has been set. - assertKeysEquals("\r", KeyHandler.getCode(KeyEvent.KEYCODE_ENTER, 0, false, false)); - - // Backspace. - assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false)); - - // Space. - assertNull(KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, 0, false, false)); - assertKeysEquals("\u0000", KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, KeyHandler.KEYMOD_CTRL, false, false)); - - // Back tab. - assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false)); - - // Arrow keys (up/down/right/left): - assertKeysEquals("\033[A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, 0, false, false)); - assertKeysEquals("\033[B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, 0, false, false)); - assertKeysEquals("\033[C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, 0, false, false)); - assertKeysEquals("\033[D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, 0, false, false)); - // .. shifted: - assertKeysEquals("\033[1;2A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, KeyHandler.KEYMOD_SHIFT, false, false)); - // .. ctrl:ed: - assertKeysEquals("\033[1;5A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, KeyHandler.KEYMOD_CTRL, false, false)); - assertKeysEquals("\033[1;5B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, KeyHandler.KEYMOD_CTRL, false, false)); - assertKeysEquals("\033[1;5C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, KeyHandler.KEYMOD_CTRL, false, false)); - assertKeysEquals("\033[1;5D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, KeyHandler.KEYMOD_CTRL, false, false)); - // .. ctrl:ed and shifted: - int mod = KeyHandler.KEYMOD_CTRL | KeyHandler.KEYMOD_SHIFT; - assertKeysEquals("\033[1;6A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, mod, false, false)); - assertKeysEquals("\033[1;6B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, mod, false, false)); - assertKeysEquals("\033[1;6C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, mod, false, false)); - assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false)); - - // Home/end keys: - assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, 0, false, false)); - assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false)); - // ... shifted: - assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false)); - - // Function keys F1-F12: - assertKeysEquals("\033OP", KeyHandler.getCode(KeyEvent.KEYCODE_F1, 0, false, false)); - assertKeysEquals("\033OQ", KeyHandler.getCode(KeyEvent.KEYCODE_F2, 0, false, false)); - assertKeysEquals("\033OR", KeyHandler.getCode(KeyEvent.KEYCODE_F3, 0, false, false)); - assertKeysEquals("\033OS", KeyHandler.getCode(KeyEvent.KEYCODE_F4, 0, false, false)); - assertKeysEquals("\033[15~", KeyHandler.getCode(KeyEvent.KEYCODE_F5, 0, false, false)); - assertKeysEquals("\033[17~", KeyHandler.getCode(KeyEvent.KEYCODE_F6, 0, false, false)); - assertKeysEquals("\033[18~", KeyHandler.getCode(KeyEvent.KEYCODE_F7, 0, false, false)); - assertKeysEquals("\033[19~", KeyHandler.getCode(KeyEvent.KEYCODE_F8, 0, false, false)); - assertKeysEquals("\033[20~", KeyHandler.getCode(KeyEvent.KEYCODE_F9, 0, false, false)); - assertKeysEquals("\033[21~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, 0, false, false)); - assertKeysEquals("\033[23~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, 0, false, false)); - assertKeysEquals("\033[24~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, 0, false, false)); - // Function keys F13-F24 (same as shifted F1-F12): - assertKeysEquals("\033[1;2P", KeyHandler.getCode(KeyEvent.KEYCODE_F1, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2Q", KeyHandler.getCode(KeyEvent.KEYCODE_F2, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2R", KeyHandler.getCode(KeyEvent.KEYCODE_F3, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[1;2S", KeyHandler.getCode(KeyEvent.KEYCODE_F4, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[15;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F5, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[17;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F6, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[18;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F7, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[19;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F8, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[20;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F9, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[21;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[23;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, KeyHandler.KEYMOD_SHIFT, false, false)); - assertKeysEquals("\033[24;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, KeyHandler.KEYMOD_SHIFT, false, false)); - - assertKeysEquals("0", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, KeyHandler.KEYMOD_NUM_LOCK, false, false)); - - assertKeysEquals("\033[2~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false)); - assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false)); - assertKeysEquals("\033[B", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false)); - assertKeysEquals("\033[6~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false)); - assertKeysEquals("\033[D", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false)); - assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, 0, false, false)); - assertKeysEquals("\033[C", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false)); - assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false)); - assertKeysEquals("\033[A", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false)); - assertKeysEquals("\033[5~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false)); - assertKeysEquals("\033[3~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false)); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/OperatingSystemControlTest.java b/terminal-emulator/src/test/java/com/termux/terminal/OperatingSystemControlTest.java deleted file mode 100644 index 13377c0873..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/OperatingSystemControlTest.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.termux.terminal; - -import android.util.Base64; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** "ESC ]" is the Operating System Command. */ -public class OperatingSystemControlTest extends TerminalTestCase { - - public void testSetTitle() throws Exception { - List expectedTitleChanges = new ArrayList<>(); - - withTerminalSized(10, 10); - enterString("\033]0;Hello, world\007"); - assertEquals("Hello, world", mTerminal.getTitle()); - expectedTitleChanges.add(new ChangedTitle(null, "Hello, world")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033]0;Goodbye, world\007"); - assertEquals("Goodbye, world", mTerminal.getTitle()); - expectedTitleChanges.add(new ChangedTitle("Hello, world", "Goodbye, world")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033]0;Goodbye, \u00F1 world\007"); - assertEquals("Goodbye, \uu00F1 world", mTerminal.getTitle()); - expectedTitleChanges.add(new ChangedTitle("Goodbye, world", "Goodbye, \uu00F1 world")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - // 2 should work as well (0 sets both title and icon). - enterString("\033]2;Updated\007"); - assertEquals("Updated", mTerminal.getTitle()); - expectedTitleChanges.add(new ChangedTitle("Goodbye, \uu00F1 world", "Updated")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033[22;0t"); - enterString("\033]0;FIRST\007"); - expectedTitleChanges.add(new ChangedTitle("Updated", "FIRST")); - assertEquals("FIRST", mTerminal.getTitle()); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033[22;0t"); - enterString("\033]0;SECOND\007"); - assertEquals("SECOND", mTerminal.getTitle()); - - expectedTitleChanges.add(new ChangedTitle("FIRST", "SECOND")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033[23;0t"); - assertEquals("FIRST", mTerminal.getTitle()); - - expectedTitleChanges.add(new ChangedTitle("SECOND", "FIRST")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033[23;0t"); - expectedTitleChanges.add(new ChangedTitle("FIRST", "Updated")); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - - enterString("\033[22;0t"); - enterString("\033[22;0t"); - enterString("\033[22;0t"); - // Popping to same title should not cause changes. - enterString("\033[23;0t"); - enterString("\033[23;0t"); - enterString("\033[23;0t"); - assertEquals(expectedTitleChanges, mOutput.titleChanges); - } - - public void testTitleStack() throws Exception { - // echo -ne '\e]0;BEFORE\007' # set title - // echo -ne '\e[22t' # push to stack - // echo -ne '\e]0;AFTER\007' # set new title - // echo -ne '\e[23t' # retrieve from stack - - withTerminalSized(10, 10); - enterString("\033]0;InitialTitle\007"); - assertEquals("InitialTitle", mTerminal.getTitle()); - enterString("\033[22t"); - assertEquals("InitialTitle", mTerminal.getTitle()); - enterString("\033]0;UpdatedTitle\007"); - assertEquals("UpdatedTitle", mTerminal.getTitle()); - enterString("\033[23t"); - assertEquals("InitialTitle", mTerminal.getTitle()); - enterString("\033[23t\033[23t\033[23t"); - assertEquals("InitialTitle", mTerminal.getTitle()); - } - - public void testSetColor() throws Exception { - // "OSC 4; $INDEX; $COLORSPEC BEL" => Change color $INDEX to the color specified by $COLORSPEC. - withTerminalSized(4, 4).enterString("\033]4;5;#00FF00\007"); - assertEquals(Integer.toHexString(0xFF00FF00), Integer.toHexString(mTerminal.mColors.mCurrentColors[5])); - enterString("\033]4;5;#00FFAB\007"); - assertEquals(mTerminal.mColors.mCurrentColors[5], 0xFF00FFAB); - enterString("\033]4;255;#ABFFAB\007"); - assertEquals(mTerminal.mColors.mCurrentColors[255], 0xFFABFFAB); - // Two indexed colors at once: - enterString("\033]4;7;#00FF00;8;#0000FF\007"); - assertEquals(mTerminal.mColors.mCurrentColors[7], 0xFF00FF00); - assertEquals(mTerminal.mColors.mCurrentColors[8], 0xFF0000FF); - } - - void assertIndexColorsMatch(int[] expected) { - for (int i = 0; i < 255; i++) - assertEquals("index=" + i, expected[i], mTerminal.mColors.mCurrentColors[i]); - } - - public void testResetColor() throws Exception { - withTerminalSized(4, 4); - int[] initialColors = new int[TextStyle.NUM_INDEXED_COLORS]; - System.arraycopy(mTerminal.mColors.mCurrentColors, 0, initialColors, 0, initialColors.length); - int[] expectedColors = new int[initialColors.length]; - System.arraycopy(mTerminal.mColors.mCurrentColors, 0, expectedColors, 0, expectedColors.length); - Random rand = new Random(); - for (int endType = 0; endType < 3; endType++) { - // Both BEL (7) and ST (ESC \) can end an OSC sequence. - String ender = (endType == 0) ? "\007" : "\033\\"; - for (int i = 0; i < 255; i++) { - expectedColors[i] = 0xFF000000 + (rand.nextInt() & 0xFFFFFF); - int r = (expectedColors[i] >> 16) & 0xFF; - int g = (expectedColors[i] >> 8) & 0xFF; - int b = expectedColors[i] & 0xFF; - String rgbHex = String.format("%02x", r) + String.format("%02x", g) + String.format("%02x", b); - enterString("\033]4;" + i + ";#" + rgbHex + ender); - assertEquals(expectedColors[i], mTerminal.mColors.mCurrentColors[i]); - } - } - - enterString("\033]104;0\007"); - expectedColors[0] = TerminalColors.COLOR_SCHEME.mDefaultColors[0]; - assertIndexColorsMatch(expectedColors); - enterString("\033]104;1;2\007"); - expectedColors[1] = TerminalColors.COLOR_SCHEME.mDefaultColors[1]; - expectedColors[2] = TerminalColors.COLOR_SCHEME.mDefaultColors[2]; - assertIndexColorsMatch(expectedColors); - enterString("\033]104\007"); // Reset all colors. - assertIndexColorsMatch(TerminalColors.COLOR_SCHEME.mDefaultColors); - } - - public void disabledTestSetClipboard() { - // Cannot run this as a unit test since Base64 is a android.util class. - enterString("\033]52;c;" + Base64.encodeToString("Hello, world".getBytes(), 0) + "\007"); - } - - public void testResettingTerminalResetsColor() throws Exception { - // "OSC 4; $INDEX; $COLORSPEC BEL" => Change color $INDEX to the color specified by $COLORSPEC. - withTerminalSized(4, 4).enterString("\033]4;5;#00FF00\007"); - enterString("\033]4;5;#00FFAB\007").assertColor(5, 0xFF00FFAB); - enterString("\033]4;255;#ABFFAB\007").assertColor(255, 0xFFABFFAB); - mTerminal.reset(); - assertIndexColorsMatch(TerminalColors.COLOR_SCHEME.mDefaultColors); - } - - public void testSettingDynamicColors() { - // "${OSC}${DYNAMIC};${COLORSPEC}${BEL_OR_STRINGTERMINATOR}" => Change ${DYNAMIC} color to the color specified by $COLORSPEC where: - // DYNAMIC=10: Text foreground color. - // DYNAMIC=11: Text background color. - // DYNAMIC=12: Text cursor color. - withTerminalSized(3, 3).enterString("\033]10;#ABCD00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFABCD00); - enterString("\033]11;#0ABCD0\007").assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF0ABCD0); - enterString("\033]12;#00ABCD\007").assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00ABCD); - // Two special colors at once - // ("Each successive parameter changes the next color in the list. The value of P s tells the starting point in the list"): - enterString("\033]10;#FF0000;#00FF00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000); - assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF00FF00); - // Three at once: - enterString("\033]10;#0000FF;#00FF00;#FF0000\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFF0000FF); - assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF00FF00).assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFFFF0000); - - // Without ending semicolon: - enterString("\033]10;#FF0000\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000); - // For background and cursor: - enterString("\033]11;#FFFF00;\007").assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFFFFFF00); - enterString("\033]12;#00FFFF;\007").assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00FFFF); - - // Using string terminator: - String stringTerminator = "\033\\"; - enterString("\033]10;#FF0000" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000); - // For background and cursor: - enterString("\033]11;#FFFF00;" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFFFFFF00); - enterString("\033]12;#00FFFF;" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00FFFF); - } - - public void testReportSpecialColors() { - // "${OSC}${DYNAMIC};?${BEL}" => Terminal responds with the control sequence which would set the current color. - // Both xterm and libvte (gnome-terminal and others) use the longest color representation, which means that - // the response is "${OSC}rgb:RRRR/GGGG/BBBB" - withTerminalSized(3, 3).enterString("\033]10;#ABCD00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFABCD00); - assertEnteringStringGivesResponse("\033]10;?\007", "\033]10;rgb:abab/cdcd/0000\007"); - // Same as above but with string terminator. xterm uses the same string terminator in the response, which - // e.g. script posted at http://superuser.com/questions/157563/programmatic-access-to-current-xterm-background-color - // relies on: - assertEnteringStringGivesResponse("\033]10;?\033\\", "\033]10;rgb:abab/cdcd/0000\033\\"); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/RectangularAreasTest.java b/terminal-emulator/src/test/java/com/termux/terminal/RectangularAreasTest.java deleted file mode 100644 index 8709c2c38f..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/RectangularAreasTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.termux.terminal; - -public class RectangularAreasTest extends TerminalTestCase { - - /** http://www.vt100.net/docs/vt510-rm/DECFRA */ - public void testFillRectangularArea() { - withTerminalSized(3, 3).enterString("\033[88$x").assertLinesAre("XXX", "XXX", "XXX"); - withTerminalSized(3, 3).enterString("\033[88;1;1;2;10$x").assertLinesAre("XXX", "XXX", " "); - withTerminalSized(3, 3).enterString("\033[88;2;1;3;10$x").assertLinesAre(" ", "XXX", "XXX"); - withTerminalSized(3, 3).enterString("\033[88;1;1;100;1$x").assertLinesAre("X ", "X ", "X "); - withTerminalSized(3, 3).enterString("\033[88;1;1;100;2$x").assertLinesAre("XX ", "XX ", "XX "); - withTerminalSized(3, 3).enterString("\033[88;100;1;100;2$x").assertLinesAre(" ", " ", " "); - } - - /** http://www.vt100.net/docs/vt510-rm/DECERA */ - public void testEraseRectangularArea() { - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[$z").assertLinesAre(" ", " ", " "); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;10$z").assertLinesAre(" ", " ", "GHI"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;1;3;10$z").assertLinesAre("ABC", " ", " "); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;1$z").assertLinesAre(" BC", " EF", " HI"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;2$z").assertLinesAre(" C", " F", " I"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[100;1;100;2$z").assertLinesAre("ABC", "DEF", "GHI"); - - withTerminalSized(3, 3).enterString("A\033[$zBC").assertLinesAre(" BC", " ", " "); - } - - /** http://www.vt100.net/docs/vt510-rm/DECSED */ - public void testSelectiveEraseInDisplay() { - // ${CSI}1"q enables protection, ${CSI}0"q disables it. - // ${CSI}?${0,1,2}J" erases (0=cursor to end, 1=start to cursor, 2=complete display). - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[?2J").assertLinesAre(" ", " ", " "); - withTerminalSized(3, 3).enterString("ABC\033[1\"qDE\033[0\"qFGHI\033[?2J").assertLinesAre(" ", "DE ", " "); - withTerminalSized(3, 3).enterString("\033[1\"qABCDE\033[0\"qFGHI\033[?2J").assertLinesAre("ABC", "DE ", " "); - } - - /** http://vt100.net/docs/vt510-rm/DECSEL */ - public void testSelectiveEraseInLine() { - // ${CSI}1"q enables protection, ${CSI}0"q disables it. - // ${CSI}?${0,1,2}K" erases (0=cursor to end, 1=start to cursor, 2=complete line). - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[?2K").assertLinesAre("ABC", "DEF", " "); - withTerminalSized(3, 3).enterString("ABCDE\033[?0KFGHI").assertLinesAre("ABC", "DEF", "GHI"); - withTerminalSized(3, 3).enterString("ABCDE\033[?1KFGHI").assertLinesAre("ABC", " F", "GHI"); - withTerminalSized(3, 3).enterString("ABCDE\033[?2KFGHI").assertLinesAre("ABC", " F", "GHI"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;2H\033[?0K").assertLinesAre("ABC", "D ", "GHI"); - withTerminalSized(3, 3).enterString("ABC\033[1\"qD\033[0\"qE\033[?2KFGHI").assertLinesAre("ABC", "D F", "GHI"); - } - - /** http://www.vt100.net/docs/vt510-rm/DECSERA */ - public void testSelectiveEraseInRectangle() { - // ${CSI}1"q enables protection, ${CSI}0"q disables it. - // ${CSI}?${TOP};${LEFT};${BOTTOM};${RIGHT}${" erases. - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[${").assertLinesAre(" ", " ", " "); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;10${").assertLinesAre(" ", " ", "GHI"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;1;3;10${").assertLinesAre("ABC", " ", " "); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;1${").assertLinesAre(" BC", " EF", " HI"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;2${").assertLinesAre(" C", " F", " I"); - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[100;1;100;2${").assertLinesAre("ABC", "DEF", "GHI"); - - withTerminalSized(3, 3).enterString("ABCD\033[1\"qE\033[0\"qFGHI\033[${").assertLinesAre(" ", " E ", " "); - withTerminalSized(3, 3).enterString("ABCD\033[1\"qE\033[0\"qFGHI\033[1;1;2;10${").assertLinesAre(" ", " E ", "GHI"); - } - - /** http://vt100.net/docs/vt510-rm/DECCRA */ - public void testRectangularCopy() { - // "${CSI}${SRC_TOP};${SRC_LEFT};${SRC_BOTTOM};${SRC_RIGHT};${SRC_PAGE};${DST_TOP};${DST_LEFT};${DST_PAGE}\$v" - withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;2;2;1;2;5;1$v").assertLinesAre("ABC ", "DEF AB ", "GHI DE "); - withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;3;3;1;1;4;1$v").assertLinesAre("ABCABC ", "DEFDEF ", "GHIGHI "); - withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;3;3;1;1;3;1$v").assertLinesAre("ABABC ", "DEDEF ", "GHGHI "); - withTerminalSized(7, 3).enterString(" ABC\r\n DEF\r\n GHI\033[1;4;3;6;1;1;1;1$v").assertLinesAre("ABCABC ", "DEFDEF ", - "GHIGHI "); - withTerminalSized(7, 3).enterString(" ABC\r\n DEF\r\n GHI\033[1;4;3;6;1;1;2;1$v").assertLinesAre(" ABCBC ", " DEFEF ", - " GHIHI "); - withTerminalSized(3, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;2;2;1;2;2;1$v").assertLinesAre("ABC", "DAB", "GDE"); - - // Enable ${CSI}?6h origin mode (DECOM) and ${CSI}?69h for left/right margin (DECLRMM) enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s - // for DECSLRM margin setting. - withTerminalSized(5, 5).enterString("\033[?6h\033[?69h\033[2;4s"); - enterString("ABCDEFGHIJK").assertLinesAre(" ABC ", " DEF ", " GHI ", " JK ", " "); - enterString("\033[1;1;2;2;1;2;2;1$v").assertLinesAre(" ABC ", " DAB ", " GDE ", " JK ", " "); - } - - /** http://vt100.net/docs/vt510-rm/DECCARA */ - public void testChangeAttributesInRectangularArea() { - final int b = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - // "${CSI}${TOP};${LEFT};${BOTTOM};${RIGHT};${ATTRIBUTES}\$r" - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;2;1$r").assertLinesAre("ABC", "DEF", "GHI"); - assertEffectAttributesSet(effectLine(b, b, b), effectLine(b, b, 0), effectLine(0, 0, 0)); - - // Now with http://www.vt100.net/docs/vt510-rm/DECSACE ("${CSI}2*x") specifying rectangle: - withTerminalSized(3, 3).enterString("\033[2*xABCDEFGHI\033[1;1;2;2;1$r").assertLinesAre("ABC", "DEF", "GHI"); - assertEffectAttributesSet(effectLine(b, b, 0), effectLine(b, b, 0), effectLine(0, 0, 0)); - } - - /** http://vt100.net/docs/vt510-rm/DECCARA */ - public void testReverseAttributesInRectangularArea() { - final int b = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - final int u = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - final int bu = TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - // "${CSI}${TOP};${LEFT};${BOTTOM};${RIGHT};${ATTRIBUTES}\$t" - withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI"); - assertEffectAttributesSet(effectLine(b, b, b), effectLine(b, b, 0), effectLine(0, 0, 0)); - - // Now with http://www.vt100.net/docs/vt510-rm/DECSACE ("${CSI}2*x") specifying rectangle: - withTerminalSized(3, 3).enterString("\033[2*xABCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI"); - assertEffectAttributesSet(effectLine(b, b, 0), effectLine(b, b, 0), effectLine(0, 0, 0)); - - // Check reversal by initially bolding the B: - withTerminalSized(3, 3).enterString("\033[2*xA\033[1mB\033[0mCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI"); - assertEffectAttributesSet(effectLine(b, 0, 0), effectLine(b, b, 0), effectLine(0, 0, 0)); - - // Check reversal by initially underlining A, bolding B, then reversing both bold and underline: - withTerminalSized(3, 3).enterString("\033[2*x\033[4mA\033[0m\033[1mB\033[0mCDEFGHI\033[1;1;2;2;1;4$t").assertLinesAre("ABC", "DEF", - "GHI"); - assertEffectAttributesSet(effectLine(b, u, 0), effectLine(bu, bu, 0), effectLine(0, 0, 0)); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ResizeTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ResizeTest.java deleted file mode 100644 index 6c32d663b4..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ResizeTest.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.termux.terminal; - -public class ResizeTest extends TerminalTestCase { - - public void testResizeWhenHasHistory() { - final int cols = 3; - withTerminalSized(cols, 3).enterString("111222333444555666777888999").assertCursorAt(2, 2).assertLinesAre("777", "888", "999"); - resize(cols, 5).assertCursorAt(4, 2).assertLinesAre("555", "666", "777", "888", "999"); - resize(cols, 3).assertCursorAt(2, 2).assertLinesAre("777", "888", "999"); - } - - public void testResizeWhenInAltBuffer() { - final int rows = 3, cols = 3; - withTerminalSized(cols, rows).enterString("a\r\ndef$").assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1); - - // Resize and back again while in main buffer: - resize(cols, 5).assertLinesAre("a ", "def", "$ ", " ", " ").assertCursorAt(2, 1); - resize(cols, rows).assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1); - - // Switch to alt buffer: - enterString("\033[?1049h").assertLinesAre(" ", " ", " ").assertCursorAt(2, 1); - enterString("h").assertLinesAre(" ", " ", " h ").assertCursorAt(2, 2); - - resize(cols, 5).resize(cols, rows); - - // Switch from alt buffer: - enterString("\033[?1049l").assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1); - } - - public void testShrinkingInAltBuffer() { - final int rows = 5; - final int cols = 3; - withTerminalSized(cols, rows).enterString("A\r\nB\r\nC\r\nD\r\nE").assertLinesAre("A ", "B ", "C ", "D ", "E "); - enterString("\033[?1049h").assertLinesAre(" ", " ", " ", " ", " "); - resize(3, 3).enterString("\033[?1049lF").assertLinesAre("C ", "D ", "EF "); - } - - public void testResizeAfterNewlineWhenInAltBuffer() { - final int rows = 3; - final int cols = 3; - withTerminalSized(cols, rows); - enterString("a\r\nb\r\nc\r\nd\r\ne\r\nf\r\n").assertLinesAre("e ", "f ", " ").assertCursorAt(2, 0); - assertLineWraps(false, false, false); - - // Switch to alt buffer: - enterString("\033[?1049h").assertLinesAre(" ", " ", " ").assertCursorAt(2, 0); - enterString("h").assertLinesAre(" ", " ", "h ").assertCursorAt(2, 1); - - // Grow by two rows: - resize(cols, 5).assertLinesAre(" ", " ", "h ", " ", " ").assertCursorAt(2, 1); - resize(cols, rows).assertLinesAre(" ", " ", "h ").assertCursorAt(2, 1); - - // Switch from alt buffer: - enterString("\033[?1049l").assertLinesAre("e ", "f ", " ").assertCursorAt(2, 0); - } - - public void testResizeAfterHistoryWraparound() { - final int rows = 3; - final int cols = 10; - withTerminalSized(cols, rows); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < 1000; i++) { - String s = Integer.toString(i); - enterString(s); - buffer.setLength(0); - buffer.append(s); - while (buffer.length() < cols) - buffer.append(' '); - if (i > rows) { - assertLineIs(rows - 1, buffer.toString()); - } - enterString("\r\n"); - } - assertLinesAre("998 ", "999 ", " "); - resize(cols, 2); - assertLinesAre("999 ", " "); - resize(cols, 5); - assertLinesAre("996 ", "997 ", "998 ", "999 ", " "); - resize(cols, rows); - assertLinesAre("998 ", "999 ", " "); - } - - public void testVerticalResize() { - final int rows = 5; - final int cols = 3; - - withTerminalSized(cols, rows); - // Foreground color to 119: - enterString("\033[38;5;119m"); - // Background color to 129: - enterString("\033[48;5;129m"); - // Clear with ED, Erase in Display: - enterString("\033[2J"); - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals(119, TextStyle.decodeForeColor(style)); - assertEquals(129, TextStyle.decodeBackColor(style)); - } - } - enterString("11\r\n22"); - assertLinesAre("11 ", "22 ", " ", " ", " ").assertLineWraps(false, false, false, false, false); - resize(cols, rows - 2).assertLinesAre("11 ", "22 ", " "); - - // After resize, screen should still be same color: - for (int r = 0; r < rows - 2; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals(119, TextStyle.decodeForeColor(style)); - assertEquals(129, TextStyle.decodeBackColor(style)); - } - } - - // Background color to 200 and grow back size (which should be cleared to the new background color): - enterString("\033[48;5;200m"); - resize(cols, rows); - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals(119, TextStyle.decodeForeColor(style)); - assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style)); - } - } - } - - public void testHorizontalResize() { - final int rows = 5; - final int cols = 5; - - withTerminalSized(cols, rows); - // Background color to 129: - // enterString("\033[48;5;129m").assertLinesAre(" ", " ", " ", " ", " "); - enterString("1111\r\n2222\r\n3333\r\n4444\r\n5555").assertCursorAt(4, 4); - // assertEquals(129, TextStyle.decodeBackColor(getStyleAt(2, 2))); - assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertLineWraps(false, false, false, false, false); - resize(cols + 2, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4); - assertLineWraps(false, false, false, false, false); - resize(cols, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4); - assertLineWraps(false, false, false, false, false); - resize(cols - 1, rows).assertLinesAre("2222", "3333", "4444", "5555", " ").assertCursorAt(4, 0); - assertLineWraps(false, false, false, true, false); - resize(cols - 2, rows).assertLinesAre("3 ", "444", "4 ", "555", "5 ").assertCursorAt(4, 1); - assertLineWraps(false, true, false, true, false); - // Back to original size: - resize(cols, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4); - assertLineWraps(false, false, false, false, false); - } - - public void testLineWrap() { - final int rows = 3, cols = 5; - withTerminalSized(cols, rows).enterString("111111").assertLinesAre("11111", "1 ", " "); - assertCursorAt(1, 1).assertLineWraps(true, false, false); - - resize(7, rows).assertCursorAt(0, 6).assertLinesAre("111111 ", " ", " ").assertLineWraps(false, false, false); - resize(cols, rows).assertCursorAt(1, 1).assertLinesAre("11111", "1 ", " ").assertLineWraps(true, false, false); - - enterString("2").assertLinesAre("11111", "12 ", " ").assertLineWraps(true, false, false); - enterString("123").assertLinesAre("11111", "12123", " ").assertLineWraps(true, false, false); - enterString("W").assertLinesAre("11111", "12123", "W ").assertLineWraps(true, true, false); - - withTerminalSized(cols, rows).enterString("1234512345"); - assertLinesAre("12345", "12345", " ").assertLineWraps(true, false, false); - enterString("W").assertLinesAre("12345", "12345", "W ").assertLineWraps(true, true, false); - } - - public void testCursorPositionWhenShrinking() { - final int rows = 5, cols = 3; - withTerminalSized(cols, rows).enterString("$ ").assertLinesAre("$ ", " ", " ", " ", " ").assertCursorAt(0, 2); - resize(3, 3).assertLinesAre("$ ", " ", " ").assertCursorAt(0, 2); - resize(cols, rows).assertLinesAre("$ ", " ", " ", " ", " ").assertCursorAt(0, 2); - } - - public void testResizeWithCombiningCharInLastColumn() { - withTerminalSized(3, 3).enterString("ABC\u0302DEF").assertLinesAre("ABC\u0302", "DEF", " "); - resize(4, 3).assertLinesAre("ABC\u0302D", "EF ", " "); - - // Same as above but with colors: - withTerminalSized(3, 3).enterString("\033[37mA\033[35mB\033[33mC\u0302\033[32mD\033[31mE\033[34mF").assertLinesAre("ABC\u0302", - "DEF", " "); - resize(4, 3).assertLinesAre("ABC\u0302D", "EF ", " "); - assertForegroundIndices(effectLine(7, 5, 3, 2), effectLine(1, 4, 4, 4), effectLine(4, 4, 4, 4)); - } - - public void testResizeWithLineWrappingContinuing() { - withTerminalSized(5, 3).enterString("\r\nAB DE").assertLinesAre(" ", "AB DE", " "); - resize(4, 3).assertLinesAre("AB D", "E ", " "); - resize(3, 3).assertLinesAre("AB ", "DE ", " "); - resize(5, 3).assertLinesAre(" ", "AB DE", " "); - } - - public void testResizeWithWideChars() { - final int rows = 3, cols = 4; - String twoCharsWidthOne = new String(Character.toChars(TerminalRowTest.TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1)); - withTerminalSized(cols, rows).enterString(twoCharsWidthOne).enterString("\r\n"); - enterString(twoCharsWidthOne).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + " ", " "); - resize(3, 3).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + " ", " "); - enterString(twoCharsWidthOne).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + twoCharsWidthOne + " ", " "); - } - - public void testResizeWithMoreWideChars() { - final int rows = 4, cols = 5; - - withTerminalSized(cols, rows).enterString("qqrr").assertLinesAre("qqrr ", " ", " ", " "); - resize(2, rows).assertLinesAre("qq", "rr", " ", " "); - resize(5, rows).assertLinesAre("qqrr ", " ", " ", " "); - - withTerminalSized(cols, rows).enterString("QR").assertLinesAre("QR ", " ", " ", " "); - resize(2, rows).assertLinesAre("Q", "R", " ", " "); - resize(5, rows).assertLinesAre("QR ", " ", " ", " "); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java deleted file mode 100644 index 9a8f115ef2..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.termux.terminal; - -public class ScreenBufferTest extends TerminalTestCase { - - public void testBasics() { - TerminalBuffer screen = new TerminalBuffer(5, 3, 3); - assertEquals("", screen.getTranscriptText()); - screen.setChar(0, 0, 'a', 0); - assertEquals("a", screen.getTranscriptText()); - screen.setChar(0, 0, 'b', 0); - assertEquals("b", screen.getTranscriptText()); - screen.setChar(2, 0, 'c', 0); - assertEquals("b c", screen.getTranscriptText()); - screen.setChar(2, 2, 'f', 0); - assertEquals("b c\n\n f", screen.getTranscriptText()); - screen.blockSet(0, 0, 2, 2, 'X', 0); - } - - public void testBlockSet() { - TerminalBuffer screen = new TerminalBuffer(5, 3, 3); - screen.blockSet(0, 0, 2, 2, 'X', 0); - assertEquals("XX\nXX", screen.getTranscriptText()); - screen.blockSet(1, 1, 2, 2, 'Y', 0); - assertEquals("XX\nXYY\n YY", screen.getTranscriptText()); - } - - public void testGetSelectedText() { - withTerminalSized(5, 3).enterString("ABCDEFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); - assertEquals("AB", mTerminal.getSelectedText(0, 0, 1, 0)); - assertEquals("BC", mTerminal.getSelectedText(1, 0, 2, 0)); - assertEquals("CDE", mTerminal.getSelectedText(2, 0, 4, 0)); - assertEquals("FG", mTerminal.getSelectedText(0, 1, 1, 1)); - assertEquals("GH", mTerminal.getSelectedText(1, 1, 2, 1)); - assertEquals("HIJ", mTerminal.getSelectedText(2, 1, 4, 1)); - - assertEquals("ABCDEFG", mTerminal.getSelectedText(0, 0, 1, 1)); - withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); - assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1)); - } - - public void testGetSelectedTextJoinFullLines() { - withTerminalSized(5, 3).enterString("ABCDE\r\nFG"); - assertEquals("ABCDEFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); - - withTerminalSized(5, 3).enterString("ABC\r\nFG"); - assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); - } - - public void testGetWordAtLocation() { - withTerminalSized(5, 3).enterString("ABCDEFGHIJ\r\nKLMNO"); - assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(0, 0)); - assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 1)); - assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 2)); - - withTerminalSized(5, 3).enterString("ABC DEF GHI "); - assertEquals("ABC", mTerminal.getScreen().getWordAtLocation(0, 0)); - assertEquals("", mTerminal.getScreen().getWordAtLocation(3, 0)); - assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(4, 0)); - assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(0, 1)); - assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(1, 1)); - assertEquals("GHI", mTerminal.getScreen().getWordAtLocation(0, 2)); - assertEquals("", mTerminal.getScreen().getWordAtLocation(1, 2)); - assertEquals("", mTerminal.getScreen().getWordAtLocation(2, 2)); - } -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ScrollRegionTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ScrollRegionTest.java deleted file mode 100644 index 039605a144..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/ScrollRegionTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.termux.terminal; - -/** - * ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). - *

- * "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM). - */ -public class ScrollRegionTest extends TerminalTestCase { - - public void testScrollRegionTop() { - withTerminalSized(3, 4).enterString("111222333444").assertLinesAre("111", "222", "333", "444"); - enterString("\033[2r").assertCursorAt(0, 0); - enterString("\r\n\r\n\r\n\r\nCDEFGH").assertLinesAre("111", "444", "CDE", "FGH").assertHistoryStartsWith("333"); - enterString("IJK").assertLinesAre("111", "CDE", "FGH", "IJK").assertHistoryStartsWith("444"); - // Reset scroll region and enter line: - enterString("\033[r").enterString("\r\n\r\n\r\n").enterString("LMNOPQ").assertLinesAre("CDE", "FGH", "LMN", "OPQ"); - } - - public void testScrollRegionBottom() { - withTerminalSized(3, 4).enterString("111222333444"); - assertLinesAre("111", "222", "333", "444"); - enterString("\033[1;3r").assertCursorAt(0, 0); - enterString("\r\n\r\nCDEFGH").assertLinesAre("222", "CDE", "FGH", "444").assertHistoryStartsWith("111"); - // Reset scroll region and enter line: - enterString("\033[r").enterString("\r\n\r\n\r\n").enterString("IJKLMN").assertLinesAre("CDE", "FGH", "IJK", "LMN"); - } - - public void testScrollRegionResetWithOriginMode() { - withTerminalSized(3, 4).enterString("111222333444"); - assertLinesAre("111", "222", "333", "444"); - // "\033[?6h" sets origin mode, so that the later DECSTBM resets cursor to below margin: - enterString("\033[?6h\033[2r").assertCursorAt(1, 0); - } - - public void testScrollRegionLeft() { - // ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting. - withTerminalSized(3, 3).enterString("\033[?69h\033[2sABCDEFG").assertLinesAre("ABC", " DE", " FG"); - enterString("HI").assertLinesAre("ADE", " FG", " HI").enterString("JK").assertLinesAre("AFG", " HI", " JK"); - enterString("\n").assertLinesAre("AHI", " JK", " "); - } - - public void testScrollRegionRight() { - // ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting. - withTerminalSized(3, 3).enterString("YYY\033[?69h\033[1;2sABCDEF").assertLinesAre("ABY", "CD ", "EF "); - enterString("GH").assertLinesAre("CDY", "EF ", "GH ").enterString("IJ").assertLinesAre("EFY", "GH ", "IJ "); - enterString("\n").assertLinesAre("GHY", "IJ ", " "); - } - - public void testScrollRegionOnAllSides() { - // ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting. - withTerminalSized(4, 4).enterString("ABCDEFGHIJKLMNOP").assertLinesAre("ABCD", "EFGH", "IJKL", "MNOP"); - // http://www.vt100.net/docs/vt510-rm/DECOM - enterString("\033[?6h\033[2;3r").assertCursorAt(1, 0); - enterString("\033[?69h\033[2;3s").assertCursorAt(1, 1); - enterString("QRST").assertLinesAre("ABCD", "EQRH", "ISTL", "MNOP"); - enterString("UV").assertLinesAre("ABCD", "ESTH", "IUVL", "MNOP"); - } - - public void testDECCOLMResetsScrollMargin() { - // DECCOLM — Select 80 or 132 Columns per Page (http://www.vt100.net/docs/vt510-rm/DECCOLM) has the important - // side effect to clear scroll margins, which is useful for e.g. the "reset" utility to clear scroll margins. - withTerminalSized(3, 4).enterString("111222333444").assertLinesAre("111", "222", "333", "444"); - enterString("\033[2r\033[?3h\r\nABCDEFGHIJKL").assertLinesAre("ABC", "DEF", "GHI", "JKL"); - } - - public void testScrollOutsideVerticalRegion() { - withTerminalSized(3, 4).enterString("\033[0;2rhi\033[4;0Hyou").assertLinesAre("hi ", " ", " ", "you"); - //enterString("see").assertLinesAre("hi ", " ", " ", "see"); - } - - public void testNELRespectsLeftMargin() { - // vttest "Menu 11.3.2: VT420 Cursor-Movement Test", select "10. Test other movement (CR/HT/LF/FF) within margins". - // The NEL (ESC E) sequence moves cursor to first position on next line, where first position depends on origin mode and margin. - withTerminalSized(3, 3).enterString("\033[?69h\033[2sABC\033ED").assertLinesAre("ABC", "D ", " "); - withTerminalSized(3, 3).enterString("\033[?69h\033[2sABC\033[?6h\033ED").assertLinesAre("ABC", " D ", " "); - } - - public void testRiRespectsLeftMargin() { - // Reverse Index (RI), ${ESC}M, should respect horizontal margins: - withTerminalSized(4, 3).enterString("ABCD\033[?69h\033[2;3s\033[?6h\033M").assertLinesAre("A D", " BC ", " "); - } - - public void testSdRespectsLeftMargin() { - // Scroll Down (SD), ${CSI}${N}T, should respect horizontal margins: - withTerminalSized(4, 3).enterString("ABCD\033[?69h\033[2;3s\033[?6h\033[2T").assertLinesAre("A D", " ", " BC "); - } - - public void testBackwardIndex() { - // vttest "Menu 11.3.2: VT420 Cursor-Movement Test", test 7. - // Without margins: - withTerminalSized(3, 3).enterString("ABCDEF\0336H").assertLinesAre("ABC", "DHF", " "); - enterString("\0336\0336I").assertLinesAre("ABC", "IHF", " "); - enterString("\0336\0336").assertLinesAre(" AB", " IH", " "); - // With left margin: - withTerminalSized(3, 3).enterString("\033[?69h\033[2sABCDEF\0336\0336").assertLinesAre("A B", " D", " F"); - } - - public void testForwardIndex() { - // vttest "Menu 11.3.2: VT420 Cursor-Movement Test", test 8. - // Without margins: - withTerminalSized(3, 3).enterString("ABCD\0339E").assertLinesAre("ABC", "D E", " "); - enterString("\0339").assertLinesAre("BC ", " E ", " "); - // With right margin: - withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " "); - } - - public void testScrollDownWithScrollRegion() { - withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 "); - enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 "); - } - - public void testScrollDownBelowScrollRegion() { - withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 "); - enterString("\033[1;3r"); // DECSTBM margins. - enterString("\033[4;1H"); // Place cursor just below bottom margin. - enterString("QQ\r\nRR\r\n\r\n\r\nYY"); - assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY"); - } - - /** See https://github.com/termux/termux-app/issues/1340 */ - public void testScrollRegionDoesNotLimitCursorMovement() { - withTerminalSized(6, 4) - .enterString("\033[4;7r\033[3;1Haaa\033[Axxx") - .assertLinesAre( - " ", - " xxx", - "aaa ", - " " - ); - - withTerminalSized(6, 4) - .enterString("\033[1;3r\033[3;1Haaa\033[Bxxx") - .assertLinesAre( - " ", - " ", - "aaa ", - " xxx" - ); - } - - /** - * See reported issue. - */ - public void testClearingWhenScrollingWithMargins() { - int newForeground = 2; - int newBackground = 3; - int size = 3; - TerminalTestCase terminal = withTerminalSized(size, size) - // Enable horizontal margin and set left margin to 1: - .enterString("\033[?69h\033[2s") - // Set foreground and background color: - .enterString("\033[" + (30 + newForeground) + ";" + (40 + newBackground) + "m") - // Enter newlines to scroll down: - .enterString("\r\n\r\n\r\n\r\n\r\n"); - for (int row = 0; row < size; row++) { - for (int col = 0; col < size; col++) { - // The first column (outside of the scrolling area, due to us setting a left scroll - // margin of 1) should be unmodified, the others should use the current style: - int expectedForeground = col == 0 ? TextStyle.COLOR_INDEX_FOREGROUND : newForeground; - int expectedBackground = col == 0 ? TextStyle.COLOR_INDEX_BACKGROUND : newBackground; - terminal.assertForegroundColorAt(row, col, expectedForeground); - terminal.assertBackgroundColorAt(row, col, expectedBackground); - } - } - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/TerminalRowTest.java b/terminal-emulator/src/test/java/com/termux/terminal/TerminalRowTest.java deleted file mode 100644 index 043d60876b..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/TerminalRowTest.java +++ /dev/null @@ -1,432 +0,0 @@ -package com.termux.terminal; - -import junit.framework.TestCase; - -import java.util.Arrays; -import java.util.Random; - -public class TerminalRowTest extends TestCase { - - /** The properties of these code points are validated in {@link #testStaticConstants()}. */ - private static final int ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1 = 0x679C; - private static final int ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2 = 0x679D; - private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1 = 0x2070E; - private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2 = 0x20731; - - /** Unicode Character 'MUSICAL SYMBOL G CLEF' (U+1D11E). Two java chars required for this. */ - static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1 = 0x1D11E; - /** Unicode Character 'MUSICAL SYMBOL G CLEF OTTAVA ALTA' (U+1D11F). Two java chars required for this. */ - private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2 = 0x1D11F; - - private final int COLUMNS = 80; - - /** A combining character. */ - private static final int DIARESIS_CODEPOINT = 0x0308; - - private TerminalRow row; - - @Override - protected void setUp() throws Exception { - super.setUp(); - row = new TerminalRow(COLUMNS, TextStyle.NORMAL); - } - - private void assertLineStartsWith(int... codePoints) { - char[] chars = row.mText; - int charIndex = 0; - for (int i = 0; i < codePoints.length; i++) { - int lineCodePoint = chars[charIndex++]; - if (Character.isHighSurrogate((char) lineCodePoint)) { - lineCodePoint = Character.toCodePoint((char) lineCodePoint, chars[charIndex++]); - } - assertEquals("Differing a code point index=" + i, codePoints[i], lineCodePoint); - } - } - - private void assertColumnCharIndicesStartsWith(int... indices) { - for (int i = 0; i < indices.length; i++) { - int expected = indices[i]; - int actual = row.findStartOfColumn(i); - assertEquals("At index=" + i, expected, actual); - } - } - - public void testSimpleDiaresis() { - row.setChar(0, DIARESIS_CODEPOINT, 0); - assertEquals(81, row.getSpaceUsed()); - row.setChar(0, DIARESIS_CODEPOINT, 0); - assertEquals(82, row.getSpaceUsed()); - assertLineStartsWith(' ', DIARESIS_CODEPOINT, DIARESIS_CODEPOINT, ' '); - } - - public void testStaticConstants() { - assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1)); - assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2)); - assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1)); - assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2)); - - assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1)); - assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2)); - assertEquals(1, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1)); - assertEquals(1, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2)); - - assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1)); - assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2)); - assertEquals(2, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1)); - assertEquals(2, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2)); - - assertEquals(1, Character.charCount(DIARESIS_CODEPOINT)); - assertEquals(0, WcWidth.width(DIARESIS_CODEPOINT)); - } - - public void testOneColumn() { - assertEquals(0, row.findStartOfColumn(0)); - row.setChar(0, 'a', 0); - assertEquals(0, row.findStartOfColumn(0)); - } - - public void testAscii() { - assertEquals(0, row.findStartOfColumn(0)); - row.setChar(0, 'a', 0); - assertLineStartsWith('a', ' ', ' '); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(80, row.getSpaceUsed()); - row.setChar(0, 'b', 0); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(2, row.findStartOfColumn(2)); - assertEquals(80, row.getSpaceUsed()); - assertColumnCharIndicesStartsWith(0, 1, 2, 3); - - char[] someChars = new char[]{'a', 'c', 'e', '4', '5', '6', '7', '8'}; - - char[] rawLine = new char[80]; - Arrays.fill(rawLine, ' '); - Random random = new Random(); - for (int i = 0; i < 1000; i++) { - int lineIndex = random.nextInt(rawLine.length); - int charIndex = random.nextInt(someChars.length); - rawLine[lineIndex] = someChars[charIndex]; - row.setChar(lineIndex, someChars[charIndex], 0); - } - char[] lineChars = row.mText; - for (int i = 0; i < rawLine.length; i++) { - assertEquals(rawLine[i], lineChars[i]); - } - } - - public void testUnicode() { - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(80, row.getSpaceUsed()); - - row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0); - assertEquals(81, row.getSpaceUsed()); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(2, row.findStartOfColumn(1)); - assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, ' ', ' '); - assertColumnCharIndicesStartsWith(0, 2, 3, 4); - - row.setChar(0, 'a', 0); - assertEquals(80, row.getSpaceUsed()); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertLineStartsWith('a', ' ', ' '); - assertColumnCharIndicesStartsWith(0, 1, 2, 3); - - row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0); - row.setChar(1, 'a', 0); - assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 'a', ' '); - - row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0); - row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2, 0); - assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2, ' '); - assertColumnCharIndicesStartsWith(0, 2, 4, 5); - assertEquals(82, row.getSpaceUsed()); - } - - public void testDoubleWidth() { - row.setChar(0, 'a', 0); - row.setChar(1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0); - assertLineStartsWith('a', ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, ' '); - assertColumnCharIndicesStartsWith(0, 1, 1, 2); - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertLineStartsWith(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, ' ', ' '); - assertColumnCharIndicesStartsWith(0, 0, 1, 2); - - row.setChar(0, ' ', 0); - assertLineStartsWith(' ', ' ', ' ', ' '); - assertColumnCharIndicesStartsWith(0, 1, 2, 3, 4); - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - row.setChar(2, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0); - assertLineStartsWith(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2); - assertColumnCharIndicesStartsWith(0, 0, 1, 1, 2); - row.setChar(0, 'a', 0); - assertLineStartsWith('a', ' ', ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, ' '); - } - - /** Just as {@link #testDoubleWidth()} but requires a surrogate pair. */ - public void testDoubleWidthSurrogage() { - row.setChar(0, 'a', 0); - assertColumnCharIndicesStartsWith(0, 1, 2, 3, 4); - - row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, 0); - assertColumnCharIndicesStartsWith(0, 1, 1, 3, 4); - assertLineStartsWith('a', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' '); - row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, 0); - assertColumnCharIndicesStartsWith(0, 0, 2, 3, 4); - assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, ' ', ' ', ' '); - - row.setChar(0, ' ', 0); - assertLineStartsWith(' ', ' ', ' ', ' '); - row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, 0); - row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, 0); - assertLineStartsWith(' ', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' '); - row.setChar(0, 'a', 0); - assertLineStartsWith('a', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' '); - } - - public void testReplacementChar() { - row.setChar(0, TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 0); - row.setChar(1, 'Y', 0); - assertLineStartsWith(TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'Y', ' ', ' '); - } - - public void testSurrogateCharsWithNormalDisplayWidth() { - // These requires a UTF-16 surrogate pair, and has a display width of one. - int first = 0x1D306; - int second = 0x1D307; - // Assert the above statement: - assertEquals(2, Character.toChars(first).length); - assertEquals(2, Character.toChars(second).length); - - row.setChar(0, second, 0); - assertEquals(second, Character.toCodePoint(row.mText[0], row.mText[1])); - assertEquals(' ', row.mText[2]); - assertEquals(2, row.findStartOfColumn(1)); - - row.setChar(0, first, 0); - assertEquals(first, Character.toCodePoint(row.mText[0], row.mText[1])); - assertEquals(' ', row.mText[2]); - assertEquals(2, row.findStartOfColumn(1)); - - row.setChar(1, second, 0); - row.setChar(2, 'a', 0); - assertEquals(first, Character.toCodePoint(row.mText[0], row.mText[1])); - assertEquals(second, Character.toCodePoint(row.mText[2], row.mText[3])); - assertEquals('a', row.mText[4]); - assertEquals(' ', row.mText[5]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(2, row.findStartOfColumn(1)); - assertEquals(4, row.findStartOfColumn(2)); - assertEquals(5, row.findStartOfColumn(3)); - assertEquals(6, row.findStartOfColumn(4)); - - row.setChar(0, ' ', 0); - assertEquals(' ', row.mText[0]); - assertEquals(second, Character.toCodePoint(row.mText[1], row.mText[2])); - assertEquals('a', row.mText[3]); - assertEquals(' ', row.mText[4]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(3, row.findStartOfColumn(2)); - assertEquals(4, row.findStartOfColumn(3)); - assertEquals(5, row.findStartOfColumn(4)); - - for (int i = 0; i < 80; i++) { - row.setChar(i, i % 2 == 0 ? first : second, 0); - } - for (int i = 0; i < 80; i++) { - int idx = row.findStartOfColumn(i); - assertEquals(i % 2 == 0 ? first : second, Character.toCodePoint(row.mText[idx], row.mText[idx + 1])); - } - for (int i = 0; i < 80; i++) { - row.setChar(i, i % 2 == 0 ? 'a' : 'b', 0); - } - for (int i = 0; i < 80; i++) { - int idx = row.findStartOfColumn(i); - assertEquals(i, idx); - assertEquals(i % 2 == 0 ? 'a' : 'b', row.mText[i]); - } - } - - public void testOverwritingDoubleDisplayWidthWithNormalDisplayWidth() { - // Initial "OO " - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(0, row.findStartOfColumn(1)); - assertEquals(1, row.findStartOfColumn(2)); - - // Setting first column to a clears second: "a " - row.setChar(0, 'a', 0); - assertEquals('a', row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(2, row.findStartOfColumn(2)); - - // Back to initial "OO " - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(0, row.findStartOfColumn(1)); - assertEquals(1, row.findStartOfColumn(2)); - - // Setting first column to a clears first: " a " - row.setChar(1, 'a', 0); - assertEquals(' ', row.mText[0]); - assertEquals('a', row.mText[1]); - assertEquals(' ', row.mText[2]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(2, row.findStartOfColumn(2)); - } - - public void testOverwritingDoubleDisplayWidthWithSelf() { - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(0, row.findStartOfColumn(1)); - assertEquals(1, row.findStartOfColumn(2)); - } - - public void testNormalCharsWithDoubleDisplayWidth() { - // These fit in one java char, and has a display width of two. - assertTrue(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1 != ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2); - assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1)); - assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2)); - assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1)); - assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2)); - - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals(0, row.findStartOfColumn(1)); - assertEquals(' ', row.mText[1]); - - row.setChar(0, 'a', 0); - assertEquals('a', row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals(1, row.findStartOfColumn(1)); - - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - // The first character fills both first columns. - assertEquals(0, row.findStartOfColumn(1)); - row.setChar(2, 'a', 0); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals('a', row.mText[1]); - assertEquals(1, row.findStartOfColumn(2)); - - row.setChar(0, 'c', 0); - assertEquals('c', row.mText[0]); - assertEquals(' ', row.mText[1]); - assertEquals('a', row.mText[2]); - assertEquals(' ', row.mText[3]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(2, row.findStartOfColumn(2)); - } - - public void testNormalCharsWithDoubleDisplayWidthOverlapping() { - // These fit in one java char, and has a display width of two. - row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0); - row.setChar(2, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0); - row.setChar(4, 'a', 0); - // O = ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO - // A = ANOTHER_JAVA_CHAR_DISPLAY_WIDTH_TWO - // "OOAAa " - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(0, row.findStartOfColumn(1)); - assertEquals(1, row.findStartOfColumn(2)); - assertEquals(1, row.findStartOfColumn(3)); - assertEquals(2, row.findStartOfColumn(4)); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, row.mText[1]); - assertEquals('a', row.mText[2]); - assertEquals(' ', row.mText[3]); - - row.setChar(1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0); - // " AA a " - assertEquals(' ', row.mText[0]); - assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, row.mText[1]); - assertEquals(' ', row.mText[2]); - assertEquals('a', row.mText[3]); - assertEquals(' ', row.mText[4]); - assertEquals(0, row.findStartOfColumn(0)); - assertEquals(1, row.findStartOfColumn(1)); - assertEquals(1, row.findStartOfColumn(2)); - assertEquals(2, row.findStartOfColumn(3)); - assertEquals(3, row.findStartOfColumn(4)); - } - - // https://github.com/jackpal/Android-Terminal-Emulator/issues/145 - public void testCrashATE145() { - // 0xC2541 is unassigned, use display width 1 for UNICODE_REPLACEMENT_CHAR. - // assertEquals(1, WcWidth.width(0xC2541)); - assertEquals(2, Character.charCount(0xC2541)); - - assertEquals(2, WcWidth.width(0x73EE)); - assertEquals(1, Character.charCount(0x73EE)); - - assertEquals(0, WcWidth.width(0x009F)); - assertEquals(1, Character.charCount(0x009F)); - - int[] points = new int[]{0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z'}; - // int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, - // 'B', 0x009B, 0x61C9, 'Z' }; - int currentColumn = 0; - for (int point : points) { - row.setChar(currentColumn, point, 0); - currentColumn += WcWidth.width(point); - } - // assertLineStartsWith(points); - // assertEquals(Character.highSurrogate(0xC2541), line.mText[0]); - // assertEquals(Character.lowSurrogate(0xC2541), line.mText[1]); - // assertEquals('a', line.mText[2]); - // assertEquals('8', line.mText[3]); - // assertEquals(Character.highSurrogate(0x73EE), line.mText[4]); - // assertEquals(Character.lowSurrogate(0x73EE), line.mText[5]); - // - // char[] chars = line.mText; - // int charIndex = 0; - // for (int i = 0; i < points.length; i++) { - // char c = chars[charIndex]; - // charIndex++; - // int thisPoint = (int) c; - // if (Character.isHighSurrogate(c)) { - // thisPoint = Character.toCodePoint(c, chars[charIndex]); - // charIndex++; - // } - // assertEquals("At index=" + i + ", charIndex=" + charIndex + ", char=" + (char) thisPoint, points[i], thisPoint); - // } - } - - public void testNormalization() { - // int lowerCaseN = 0x006E; - // int combiningTilde = 0x0303; - // int combined = 0x00F1; - row.setChar(0, 0x006E, 0); - assertEquals(80, row.getSpaceUsed()); - row.setChar(0, 0x0303, 0); - assertEquals(81, row.getSpaceUsed()); - // assertEquals("\u00F1 ", new String(term.getScreen().getLine(0))); - assertLineStartsWith(0x006E, 0x0303, ' '); - } - - public void testInsertWideAtLastColumn() { - row.setChar(COLUMNS - 2, 'Z', 0); - row.setChar(COLUMNS - 1, 'a', 0); - assertEquals('Z', row.mText[row.findStartOfColumn(COLUMNS - 2)]); - assertEquals('a', row.mText[row.findStartOfColumn(COLUMNS - 1)]); - row.setChar(COLUMNS - 1, 'ö', 0); - assertEquals('Z', row.mText[row.findStartOfColumn(COLUMNS - 2)]); - assertEquals('ö', row.mText[row.findStartOfColumn(COLUMNS - 1)]); - // line.setChar(COLUMNS - 1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1); - // assertEquals('Z', line.mText[line.findStartOfColumn(COLUMNS - 2)]); - // assertEquals(' ', line.mText[line.findStartOfColumn(COLUMNS - 1)]); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/TerminalTest.java b/terminal-emulator/src/test/java/com/termux/terminal/TerminalTest.java deleted file mode 100644 index 3e1f824265..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/TerminalTest.java +++ /dev/null @@ -1,350 +0,0 @@ -package com.termux.terminal; - -import java.io.UnsupportedEncodingException; - -public class TerminalTest extends TerminalTestCase { - - public void testCursorPositioning() throws Exception { - withTerminalSized(10, 10).placeCursorAndAssert(1, 2).placeCursorAndAssert(3, 5).placeCursorAndAssert(2, 2).enterString("A") - .assertCursorAt(2, 3); - } - - public void testScreen() throws UnsupportedEncodingException { - withTerminalSized(3, 3); - assertLinesAre(" ", " ", " "); - - assertEquals("", mTerminal.getScreen().getTranscriptText()); - enterString("hi").assertLinesAre("hi ", " ", " "); - assertEquals("hi", mTerminal.getScreen().getTranscriptText()); - enterString("\r\nu"); - assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText()); - mTerminal.reset(); - assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText()); - - withTerminalSized(3, 3).enterString("hello"); - assertEquals("hello", mTerminal.getScreen().getTranscriptText()); - enterString("\r\nworld"); - assertEquals("hello\nworld", mTerminal.getScreen().getTranscriptText()); - } - - public void testScrollDownInAltBuffer() { - withTerminalSized(3, 3).enterString("\033[?1049h"); - enterString("\033[38;5;111m1\r\n"); - enterString("\033[38;5;112m2\r\n"); - enterString("\033[38;5;113m3\r\n"); - enterString("\033[38;5;114m4\r\n"); - enterString("\033[38;5;115m5"); - assertLinesAre("3 ", "4 ", "5 "); - assertForegroundColorAt(0, 0, 113); - assertForegroundColorAt(1, 0, 114); - assertForegroundColorAt(2, 0, 115); - } - - public void testMouseClick() throws Exception { - withTerminalSized(10, 10); - assertFalse(mTerminal.isMouseTrackingActive()); - enterString("\033[?1000h"); - assertTrue(mTerminal.isMouseTrackingActive()); - enterString("\033[?1000l"); - assertFalse(mTerminal.isMouseTrackingActive()); - enterString("\033[?1000h"); - assertTrue(mTerminal.isMouseTrackingActive()); - - enterString("\033[?1006h"); - mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, true); - assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear()); - mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false); - assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear()); - - // When the client says that a click is outside (which could happen when pixels are outside - // the terminal area, see https://github.com/termux/termux-app/issues/501) the terminal - // sends a click at the edge. - mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 0, 0, true); - assertEquals("\033[<0;1;1M", mOutput.getOutputAndClear()); - mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 11, 11, false); - assertEquals("\033[<0;10;10m", mOutput.getOutputAndClear()); - } - - public void testNormalization() throws UnsupportedEncodingException { - // int lowerCaseN = 0x006E; - // int combiningTilde = 0x0303; - // int combined = 0x00F1; - withTerminalSized(3, 3).assertLinesAre(" ", " ", " "); - enterString("\u006E\u0303"); - assertEquals(1, WcWidth.width("\u006E\u0303".toCharArray(), 0)); - // assertEquals("\u00F1 ", new String(mTerminal.getScreen().getLine(0))); - assertLinesAre("\u006E\u0303 ", " ", " "); - } - - /** On "\e[18t" xterm replies with "\e[8;${HEIGHT};${WIDTH}t" */ - public void testReportTerminalSize() throws Exception { - withTerminalSized(5, 5); - assertEnteringStringGivesResponse("\033[18t", "\033[8;5;5t"); - for (int width = 3; width < 12; width++) { - for (int height = 3; height < 12; height++) { - resize(width, height); - assertEnteringStringGivesResponse("\033[18t", "\033[8;" + height + ";" + width + "t"); - } - } - } - - /** Device Status Report (DSR) and Report Cursor Position (CPR). */ - public void testDeviceStatusReport() throws Exception { - withTerminalSized(5, 5); - assertEnteringStringGivesResponse("\033[5n", "\033[0n"); - - assertEnteringStringGivesResponse("\033[6n", "\033[1;1R"); - enterString("AB"); - assertEnteringStringGivesResponse("\033[6n", "\033[1;3R"); - enterString("\r\n"); - assertEnteringStringGivesResponse("\033[6n", "\033[2;1R"); - } - - /** Test the cursor shape changes using DECSCUSR. */ - public void testSetCursorStyle() throws Exception { - withTerminalSized(5, 5); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle()); - enterString("\033[3 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle()); - enterString("\033[5 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle()); - enterString("\033[0 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle()); - enterString("\033[6 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle()); - enterString("\033[4 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle()); - enterString("\033[1 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle()); - enterString("\033[4 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle()); - enterString("\033[2 q"); - assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle()); - } - - public void testPaste() { - withTerminalSized(5, 5); - mTerminal.paste("hi"); - assertEquals("hi", mOutput.getOutputAndClear()); - - enterString("\033[?2004h"); - mTerminal.paste("hi"); - assertEquals("\033[200~" + "hi" + "\033[201~", mOutput.getOutputAndClear()); - - enterString("\033[?2004l"); - mTerminal.paste("hi"); - assertEquals("hi", mOutput.getOutputAndClear()); - } - - public void testSelectGraphics() { - selectGraphicsTestRun(';'); - selectGraphicsTestRun(':'); - } - - public void selectGraphicsTestRun(char separator) { - withTerminalSized(5, 5); - enterString("\033[31m"); - assertEquals(mTerminal.mForeColor, 1); - enterString("\033[32m"); - assertEquals(mTerminal.mForeColor, 2); - enterString("\033[43m"); - assertEquals(2, mTerminal.mForeColor); - assertEquals(3, mTerminal.mBackColor); - - // SGR 0 should reset both foreground and background color. - enterString("\033[0m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - - // Test CSI resetting to default if sequence starts with ; or has sequential ;; - // Check TerminalEmulator.parseArg() - enterString("\033[31m\033[m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31m\033[;m".replace(';', separator)); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31m\033[0m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31m\033[0;m".replace(';', separator)); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31;;m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31::m"); - assertEquals(1, mTerminal.mForeColor); - enterString("\033[31;m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - enterString("\033[31:m"); - assertEquals(1, mTerminal.mForeColor); - enterString("\033[31;;41m"); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - assertEquals(1, mTerminal.mBackColor); - enterString("\033[0m"); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - - // 256 colors: - enterString("\033[38;5;119m".replace(';', separator)); - assertEquals(119, mTerminal.mForeColor); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - enterString("\033[48;5;129m".replace(';', separator)); - assertEquals(119, mTerminal.mForeColor); - assertEquals(129, mTerminal.mBackColor); - - // Invalid parameter: - enterString("\033[48;8;129m".replace(';', separator)); - assertEquals(119, mTerminal.mForeColor); - assertEquals(129, mTerminal.mBackColor); - - // Multiple parameters at once: - enterString("\033[38;5;178".replace(';', separator) + ";" + "48;5;179m".replace(';', separator)); - assertEquals(178, mTerminal.mForeColor); - assertEquals(179, mTerminal.mBackColor); - - // Omitted parameter means zero: - enterString("\033[38;5;m".replace(';', separator)); - assertEquals(0, mTerminal.mForeColor); - assertEquals(179, mTerminal.mBackColor); - enterString("\033[48;5;m".replace(';', separator)); - assertEquals(0, mTerminal.mForeColor); - assertEquals(0, mTerminal.mBackColor); - - // 24 bit colors: - enterString(("\033[0m")); // Reset fg and bg colors. - enterString("\033[38;2;255;127;2m".replace(';', separator)); - int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2; - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - enterString("\033[48;2;1;2;254m".replace(';', separator)); - int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254; - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(expectedBackground, mTerminal.mBackColor); - - // 24 bit colors, set fg and bg at once: - enterString(("\033[0m")); // Reset fg and bg colors. - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - enterString("\033[38;2;255;127;2".replace(';', separator) + ";" + "48;2;1;2;254m".replace(';', separator)); - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(expectedBackground, mTerminal.mBackColor); - - // 24 bit colors, invalid input: - enterString("\033[38;2;300;127;2;48;2;1;300;254m".replace(';', separator)); - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(expectedBackground, mTerminal.mBackColor); - - // 24 bit colors, omitted parameter means zero: - enterString("\033[38;2;255;127;m".replace(';', separator)); - expectedForeground = 0xff000000 | (255 << 16) | (127 << 8); - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(expectedBackground, mTerminal.mBackColor); - enterString("\033[38;2;123;;77m".replace(';', separator)); - expectedForeground = 0xff000000 | (123 << 16) | 77; - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(expectedBackground, mTerminal.mBackColor); - - // 24 bit colors, extra sub-parameters are skipped: - expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2; - enterString("\033[0;38:2:255:127:2:48:2:1:2:254m"); - assertEquals(expectedForeground, mTerminal.mForeColor); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); - } - - public void testBackgroundColorErase() { - final int rows = 3; - final int cols = 3; - withTerminalSized(cols, rows); - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style)); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style)); - } - } - // Foreground color to 119: - enterString("\033[38;5;119m"); - // Background color to 129: - enterString("\033[48;5;129m"); - // Clear with ED, Erase in Display: - enterString("\033[2J"); - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals(119, TextStyle.decodeForeColor(style)); - assertEquals(129, TextStyle.decodeBackColor(style)); - } - } - // Background color to 139: - enterString("\033[48;5;139m"); - // Insert two blank lines. - enterString("\033[2L"); - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - long style = getStyleAt(r, c); - assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style)); - } - } - - withTerminalSized(cols, rows); - // Background color to 129: - enterString("\033[48;5;129m"); - // Erase two characters, filling them with background color: - enterString("\033[2X"); - assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 0))); - assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 1))); - assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(getStyleAt(0, 2))); - } - - public void testParseColor() { - assertEquals(0xFF0000FA, TerminalColors.parse("#0000FA")); - assertEquals(0xFF000000, TerminalColors.parse("#000000")); - assertEquals(0xFF000000, TerminalColors.parse("#000")); - assertEquals(0xFF000000, TerminalColors.parse("#000000000")); - assertEquals(0xFF53186f, TerminalColors.parse("#53186f")); - - assertEquals(0xFFFF00FF, TerminalColors.parse("rgb:F/0/F")); - assertEquals(0xFF0000FA, TerminalColors.parse("rgb:00/00/FA")); - assertEquals(0xFF53186f, TerminalColors.parse("rgb:53/18/6f")); - - assertEquals(0, TerminalColors.parse("invalid_0000FA")); - assertEquals(0, TerminalColors.parse("#3456")); - } - - /** The ncurses library still uses this. */ - public void testLineDrawing() { - // 016 - shift out / G1. 017 - shift in / G0. "ESC ) 0" - use line drawing for G1 - withTerminalSized(4, 2).enterString("q\033)0q\016q\017q").assertLinesAre("qq─q", " "); - // "\0337", saving cursor should save G0, G1 and invoked charset and "ESC 8" should restore. - withTerminalSized(4, 2).enterString("\033)0\016qqq\0337\017\0338q").assertLinesAre("────", " "); - } - - public void testSoftTerminalReset() { - // See http://vt100.net/docs/vt510-rm/DECSTR and https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=650304 - // "\033[?7l" is DECRST to disable wrap-around, and DECSTR ("\033[!p") should reset it. - withTerminalSized(3, 3).enterString("\033[?7lABCD").assertLinesAre("ABD", " ", " "); - enterString("\033[!pEF").assertLinesAre("ABE", "F ", " "); - } - - public void testBel() { - withTerminalSized(3, 3); - assertEquals(0, mOutput.bellsRung); - enterString("\07"); - assertEquals(1, mOutput.bellsRung); - enterString("hello\07"); - assertEquals(2, mOutput.bellsRung); - enterString("\07hello"); - assertEquals(3, mOutput.bellsRung); - enterString("hello\07world"); - assertEquals(4, mOutput.bellsRung); - } - - public void testAutomargins() throws UnsupportedEncodingException { - withTerminalSized(3, 3).enterString("abc").assertLinesAre("abc", " ", " ").assertCursorAt(0, 2); - enterString("d").assertLinesAre("abc", "d ", " ").assertCursorAt(1, 1); - - withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1); - } - - public void testTab() { - withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " "); - withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " "); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/TerminalTestCase.java b/terminal-emulator/src/test/java/com/termux/terminal/TerminalTestCase.java deleted file mode 100644 index 4490207932..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/TerminalTestCase.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.termux.terminal; - -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -public abstract class TerminalTestCase extends TestCase { - - public static final int INITIAL_CELL_WIDTH_PIXELS = 13; - public static final int INITIAL_CELL_HEIGHT_PIXELS = 15; - - public static class MockTerminalOutput extends TerminalOutput { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - public final List titleChanges = new ArrayList<>(); - public final List clipboardPuts = new ArrayList<>(); - public int bellsRung = 0; - public int colorsChanged = 0; - - @Override - public void write(byte[] data, int offset, int count) { - baos.write(data, offset, count); - } - - public String getOutputAndClear() { - String result = new String(baos.toByteArray(), StandardCharsets.UTF_8); - baos.reset(); - return result; - } - - @Override - public void titleChanged(String oldTitle, String newTitle) { - titleChanges.add(new ChangedTitle(oldTitle, newTitle)); - } - - @Override - public void onCopyTextToClipboard(String text) { - clipboardPuts.add(text); - } - - @Override - public void onPasteTextFromClipboard() { - } - - @Override - public void onBell() { - bellsRung++; - } - - @Override - public void onColorsChanged() { - colorsChanged++; - } - } - - public TerminalEmulator mTerminal; - public MockTerminalOutput mOutput; - - public static final class ChangedTitle { - final String oldTitle; - final String newTitle; - - public ChangedTitle(String oldTitle, String newTitle) { - this.oldTitle = oldTitle; - this.newTitle = newTitle; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof ChangedTitle)) return false; - ChangedTitle other = (ChangedTitle) o; - return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle); - } - - @Override - public int hashCode() { - return Objects.hash(oldTitle, newTitle); - } - - @Override - public String toString() { - return "ChangedTitle[oldTitle=" + oldTitle + ", newTitle=" + newTitle + "]"; - } - - } - - public TerminalTestCase enterString(String s) { - byte[] bytes = s.getBytes(StandardCharsets.UTF_8); - mTerminal.append(bytes, bytes.length); - assertInvariants(); - return this; - } - - public void assertEnteringStringGivesResponse(String input, String expectedResponse) { - enterString(input); - String response = mOutput.getOutputAndClear(); - assertEquals(expectedResponse, response); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - mOutput = new MockTerminalOutput(); - } - - protected TerminalTestCase withTerminalSized(int columns, int rows) { - // The tests aren't currently using the client, so a null client will suffice, a dummy client should be implemented if needed - mTerminal = new TerminalEmulator(mOutput, columns, rows, INITIAL_CELL_WIDTH_PIXELS, INITIAL_CELL_HEIGHT_PIXELS, rows * 2, null); - return this; - } - - public void assertHistoryStartsWith(String... rows) { - assertTrue("About to check " + rows.length + " lines, but only " + mTerminal.getScreen().getActiveTranscriptRows() + " in history", - mTerminal.getScreen().getActiveTranscriptRows() >= rows.length); - for (int i = 0; i < rows.length; i++) { - assertLineIs(-i - 1, rows[i]); - } - } - - private static final class LineWrapper { - final TerminalRow mLine; - - public LineWrapper(TerminalRow line) { - mLine = line; - } - - @Override - public int hashCode() { - return System.identityHashCode(mLine); - } - - @Override - public boolean equals(Object o) { - return o instanceof LineWrapper && ((LineWrapper) o).mLine == mLine; - } - } - - protected TerminalTestCase assertInvariants() { - TerminalBuffer screen = mTerminal.getScreen(); - TerminalRow[] lines = screen.mLines; - - Set linesSet = new HashSet<>(); - for (int i = 0; i < lines.length; i++) { - if (lines[i] == null) continue; - assertTrue("Line exists at multiple places: " + i, linesSet.add(new LineWrapper(lines[i]))); - char[] text = lines[i].mText; - int usedChars = lines[i].getSpaceUsed(); - int currentColumn = 0; - for (int j = 0; j < usedChars; j++) { - char c = text[j]; - int codePoint; - if (Character.isHighSurrogate(c)) { - char lowSurrogate = text[++j]; - assertTrue("High surrogate without following low surrogate", Character.isLowSurrogate(lowSurrogate)); - codePoint = Character.toCodePoint(c, lowSurrogate); - } else { - assertFalse("Low surrogate without preceding high surrogate", Character.isLowSurrogate(c)); - codePoint = c; - } - assertFalse("Screen should never contain unassigned characters", Character.getType(codePoint) == Character.UNASSIGNED); - int width = WcWidth.width(codePoint); - assertFalse("The first column should not start with combining character", currentColumn == 0 && width < 0); - if (width > 0) currentColumn += width; - } - assertEquals("Line whose width does not match screens. line=" + new String(lines[i].mText, 0, lines[i].getSpaceUsed()), - screen.mColumns, currentColumn); - } - - assertEquals("The alt buffer should have have no history", mTerminal.mAltBuffer.mTotalRows, mTerminal.mAltBuffer.mScreenRows); - if (mTerminal.isAlternateBufferActive()) { - assertEquals("The alt buffer should be the same size as the screen", mTerminal.mRows, mTerminal.mAltBuffer.mTotalRows); - } - - return this; - } - - protected void assertLineIs(int line, String expected) { - TerminalRow l = mTerminal.getScreen().allocateFullLineIfNecessary(mTerminal.getScreen().externalToInternalRow(line)); - char[] chars = l.mText; - int textLen = l.getSpaceUsed(); - if (textLen != expected.length()) fail("Expected '" + expected + "' (len=" + expected.length() + "), was='" - + new String(chars, 0, textLen) + "' (len=" + textLen + ")"); - for (int i = 0; i < textLen; i++) { - if (expected.charAt(i) != chars[i]) - fail("Expected '" + expected + "', was='" + new String(chars, 0, textLen) + "' - first different at index=" + i); - } - } - - public TerminalTestCase assertLinesAre(String... lines) { - assertEquals(lines.length, mTerminal.getScreen().mScreenRows); - for (int i = 0; i < lines.length; i++) - try { - assertLineIs(i, lines[i]); - } catch (AssertionFailedError e) { - throw new AssertionFailedError("Line: " + i + " - " + e.getMessage()); - } - return this; - } - - public TerminalTestCase resize(int cols, int rows) { - mTerminal.resize(cols, rows, INITIAL_CELL_WIDTH_PIXELS, INITIAL_CELL_HEIGHT_PIXELS); - assertInvariants(); - return this; - } - - public TerminalTestCase assertLineWraps(boolean... lines) { - for (int i = 0; i < lines.length; i++) - assertEquals("line=" + i, lines[i], mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(i)].mLineWrap); - return this; - } - - protected TerminalTestCase assertLineStartsWith(int line, int... codePoints) { - char[] chars = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(line)].mText; - int charIndex = 0; - for (int i = 0; i < codePoints.length; i++) { - int lineCodePoint = chars[charIndex++]; - if (Character.isHighSurrogate((char) lineCodePoint)) { - lineCodePoint = Character.toCodePoint((char) lineCodePoint, chars[charIndex++]); - } - assertEquals("Differing a code point index=" + i, codePoints[i], lineCodePoint); - } - return this; - } - - protected TerminalTestCase placeCursorAndAssert(int row, int col) { - // +1 due to escape sequence being one based. - enterString("\033[" + (row + 1) + ";" + (col + 1) + "H"); - assertCursorAt(row, col); - return this; - } - - public TerminalTestCase assertCursorAt(int row, int col) { - int actualRow = mTerminal.getCursorRow(); - int actualCol = mTerminal.getCursorCol(); - if (!(row == actualRow && col == actualCol)) - fail("Expected cursor at (row,col)=(" + row + ", " + col + ") but was (" + actualRow + ", " + actualCol + ")"); - return this; - } - - /** For testing only. Encoded style according to {@link TextStyle}. */ - public long getStyleAt(int externalRow, int column) { - return mTerminal.getScreen().getStyleAt(externalRow, column); - } - - public static class EffectLine { - final int[] styles; - - public EffectLine(int[] styles) { - this.styles = styles; - } - } - - protected EffectLine effectLine(int... bits) { - return new EffectLine(bits); - } - - public TerminalTestCase assertEffectAttributesSet(EffectLine... lines) { - assertEquals(lines.length, mTerminal.getScreen().mScreenRows); - for (int i = 0; i < lines.length; i++) { - int[] line = lines[i].styles; - for (int j = 0; j < line.length; j++) { - int effectsAtCell = TextStyle.decodeEffect(getStyleAt(i, j)); - int attributes = line[j]; - if ((effectsAtCell & attributes) != attributes) fail("Line=" + i + ", column=" + j + ", expected " - + describeStyle(attributes) + " set, was " + describeStyle(effectsAtCell)); - } - } - return this; - } - - public TerminalTestCase assertForegroundIndices(EffectLine... lines) { - assertEquals(lines.length, mTerminal.getScreen().mScreenRows); - for (int i = 0; i < lines.length; i++) { - int[] line = lines[i].styles; - for (int j = 0; j < line.length; j++) { - int actualColor = TextStyle.decodeForeColor(getStyleAt(i, j)); - int expectedColor = line[j]; - if (actualColor != expectedColor) fail("Line=" + i + ", column=" + j + ", expected color " - + Integer.toHexString(expectedColor) + " set, was " + Integer.toHexString(actualColor)); - } - } - return this; - } - - private static String describeStyle(int styleBits) { - return "'" + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_BLINK) != 0 ? ":BLINK:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_BOLD) != 0 ? ":BOLD:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_INVERSE) != 0 ? ":INVERSE:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) != 0 ? ":INVISIBLE:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0 ? ":ITALIC:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0 ? ":PROTECTED:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0 ? ":STRIKETHROUGH:" : "") - + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0 ? ":UNDERLINE:" : "") + "'"; - } - - public void assertForegroundColorAt(int externalRow, int column, int color) { - long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column); - assertEquals(color, TextStyle.decodeForeColor(style)); - } - - public void assertBackgroundColorAt(int externalRow, int column, int color) { - long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column); - assertEquals(color, TextStyle.decodeBackColor(style)); - } - - public TerminalTestCase assertColor(int colorIndex, int expected) { - int actual = mTerminal.mColors.mCurrentColors[colorIndex]; - if (expected != actual) { - fail("Color index=" + colorIndex + ", expected=" + Integer.toHexString(expected) + ", was=" + Integer.toHexString(actual)); - } - return this; - } -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/TextStyleTest.java b/terminal-emulator/src/test/java/com/termux/terminal/TextStyleTest.java deleted file mode 100644 index 6ec7a34e3d..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/TextStyleTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.termux.terminal; - -import junit.framework.TestCase; - -public class TextStyleTest extends TestCase { - - private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC, - TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE, - TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED, - TextStyle.CHARACTER_ATTRIBUTE_DIM}; - - public void testEncodingSingle() { - for (int fx : ALL_EFFECTS) { - for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) { - for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) { - long encoded = TextStyle.encode(fg, bg, fx); - assertEquals(fg, TextStyle.decodeForeColor(encoded)); - assertEquals(bg, TextStyle.decodeBackColor(encoded)); - assertEquals(fx, TextStyle.decodeEffect(encoded)); - } - } - } - } - - public void testEncoding24Bit() { - int[] values = {255, 240, 127, 1, 0}; - for (int red : values) { - for (int green : values) { - for (int blue : values) { - int argb = 0xFF000000 | (red << 16) | (green << 8) | blue; - long encoded = TextStyle.encode(argb, 0, 0); - assertEquals(argb, TextStyle.decodeForeColor(encoded)); - encoded = TextStyle.encode(0, argb, 0); - assertEquals(argb, TextStyle.decodeBackColor(encoded)); - } - } - } - } - - - public void testEncodingCombinations() { - for (int f1 : ALL_EFFECTS) { - for (int f2 : ALL_EFFECTS) { - int combined = f1 | f2; - assertEquals(combined, TextStyle.decodeEffect(TextStyle.encode(0, 0, combined))); - } - } - } - - public void testEncodingStrikeThrough() { - long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, - TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); - assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0); - } - - public void testEncodingProtected() { - long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, - TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); - assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED)); - encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, - TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED); - assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/UnicodeInputTest.java b/terminal-emulator/src/test/java/com/termux/terminal/UnicodeInputTest.java deleted file mode 100644 index 2733190a88..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/UnicodeInputTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.termux.terminal; - -import java.io.UnsupportedEncodingException; - -public class UnicodeInputTest extends TerminalTestCase { - - public void testIllFormedUtf8SuccessorByteNotConsumed() throws Exception { - // The Unicode Standard Version 6.2 – Core Specification (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf): - // "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first byte, but which does not - // continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed - // subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence." - withTerminalSized(5, 5); - mTerminal.append(new byte[]{(byte) 0b11101111, (byte) 'a'}, 2); - assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a "); - - // https://code.google.com/p/chromium/issues/detail?id=212704 - byte[] input = new byte[]{ - (byte) 0x61, (byte) 0xF1, - (byte) 0x80, (byte) 0x80, - (byte) 0xe1, (byte) 0x80, - (byte) 0xc2, (byte) 0x62, - (byte) 0x80, (byte) 0x63, - (byte) 0x80, (byte) 0xbf, - (byte) 0x64 - }; - withTerminalSized(10, 2); - mTerminal.append(input, input.length); - assertLinesAre("a\uFFFD\uFFFD\uFFFDb\uFFFDc\uFFFD\uFFFDd", " "); - - // Surrogate pairs. - withTerminalSized(5, 2); - input = new byte[]{ - (byte) 0xed, (byte) 0xa0, - (byte) 0x80, (byte) 0xed, - (byte) 0xad, (byte) 0xbf, - (byte) 0xed, (byte) 0xae, - (byte) 0x80, (byte) 0xed, - (byte) 0xbf, (byte) 0xbf - }; - mTerminal.append(input, input.length); - assertLinesAre("\uFFFD\uFFFD\uFFFD\uFFFD ", " "); - - // https://bugzilla.mozilla.org/show_bug.cgi?id=746900: "with this patch 0xe0 0x80 is decoded as two U+FFFDs, - // but 0xe0 0xa0 is decoded as a single U+FFFD, and this is correct according to the "Best Practices", but IE - // and Chrome (Version 22.0.1229.94) decode both of them as two U+FFFDs. Opera 12.11 decodes both of them as - // one U+FFFD". - withTerminalSized(5, 2); - input = new byte[]{(byte) 0xe0, (byte) 0xa0, ' '}; - mTerminal.append(input, input.length); - assertLinesAre("\uFFFD ", " "); - - // withTerminalSized(5, 2); - // input = new byte[]{(byte) 0xe0, (byte) 0x80, 'a'}; - // mTerminal.append(input, input.length); - // assertLinesAre("\uFFFD\uFFFDa ", " "); - } - - public void testUnassignedCodePoint() throws UnsupportedEncodingException { - withTerminalSized(3, 3); - // UTF-8 for U+C2541, an unassigned code point: - byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81}; - mTerminal.append(b, b.length); - enterString("Y"); - assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR)); - assertLineStartsWith(0, TerminalEmulator.UNICODE_REPLACEMENT_CHAR, (int) 'Y', ' '); - } - - public void testStuff() { - withTerminalSized(80, 24); - byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f, - (byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4, - (byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b, - (byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a}; - mTerminal.append(b, b.length); - } - - public void testSimpleCombining() throws Exception { - withTerminalSized(3, 2).enterString(" a\u0302 ").assertLinesAre(" a\u0302 ", " "); - } - - public void testCombiningCharacterInFirstColumn() throws Exception { - withTerminalSized(5, 3).enterString("test\r\nhi\r\n").assertLinesAre("test ", "hi ", " "); - - // U+0302 is COMBINING CIRCUMFLEX ACCENT. Test case from mosh (http://mosh.mit.edu/). - withTerminalSized(5, 5).enterString("test\r\nabc\r\n\u0302\r\ndef\r\n"); - assertLinesAre("test ", "abc ", " \u0302 ", "def ", " "); - } - - public void testCombiningCharacterInLastColumn() throws Exception { - withTerminalSized(3, 2).enterString(" a\u0302").assertLinesAre(" a\u0302", " "); - withTerminalSized(3, 2).enterString(" à̲").assertLinesAre(" à̲", " "); - withTerminalSized(3, 2).enterString("Aà̲F").assertLinesAre("Aà̲F", " "); - } - - public void testWideCharacterInLastColumn() throws Exception { - withTerminalSized(3, 2).enterString(" 枝\u0302").assertLinesAre(" ", "枝\u0302 "); - - withTerminalSized(3, 2).enterString(" 枝").assertLinesAre(" 枝", " ").assertCursorAt(0, 2); - enterString("a").assertLinesAre(" 枝", "a "); - } - - public void testWideCharacterDeletion() throws Exception { - // CSI Ps D Cursor Backward Ps Times - withTerminalSized(3, 2).enterString("枝\033[Da").assertLinesAre(" a ", " "); - withTerminalSized(3, 2).enterString("枝\033[2Da").assertLinesAre("a ", " "); - withTerminalSized(3, 2).enterString("枝\033[2D枝").assertLinesAre("枝 ", " "); - withTerminalSized(3, 2).enterString("枝\033[1D枝").assertLinesAre(" 枝", " "); - withTerminalSized(5, 2).enterString(" 枝 \033[Da").assertLinesAre(" 枝a ", " "); - withTerminalSized(5, 2).enterString("a \033[D\u0302").assertLinesAre("a\u0302 ", " "); - withTerminalSized(5, 2).enterString("枝 \033[D\u0302").assertLinesAre("枝\u0302 ", " "); - enterString("Z").assertLinesAre("枝\u0302Z ", " "); - enterString("\033[D ").assertLinesAre("枝\u0302 ", " "); - // Go back two columns, standing at the second half of the wide character: - enterString("\033[2DU").assertLinesAre(" U ", " "); - } - - public void testWideCharOverwriting() { - withTerminalSized(3, 2).enterString("abc\033[3D枝").assertLinesAre("枝c", " "); - } - - public void testOverlongUtf8Encoding() throws Exception { - // U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding - // so should be replaced with the replacement char U+FFFD. - withTerminalSized(5, 5).mTerminal.append(new byte[]{(byte) 0xc0, (byte) 0xa0, 'Y'}, 3); - assertLineIs(0, "\uFFFDY "); - } - - public void testWideCharacterWithoutWrapping() throws Exception { - // With wraparound disabled. The behaviour when a wide character is output with cursor in - // the last column when autowrap is disabled is not obvious, but we expect the wide - // character to be ignored here. - withTerminalSized(3, 3).enterString("\033[?7l").enterString("枝枝枝").assertLinesAre("枝 ", " ", " "); - enterString("a枝").assertLinesAre("枝a", " ", " "); - } - -} diff --git a/terminal-emulator/src/test/java/com/termux/terminal/WcWidthTest.java b/terminal-emulator/src/test/java/com/termux/terminal/WcWidthTest.java deleted file mode 100644 index c0d9a0bcad..0000000000 --- a/terminal-emulator/src/test/java/com/termux/terminal/WcWidthTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.termux.terminal; - -import junit.framework.TestCase; - -public class WcWidthTest extends TestCase { - - private static void assertWidthIs(int expectedWidth, int codePoint) { - int wcWidth = WcWidth.width(codePoint); - assertEquals(expectedWidth, wcWidth); - } - - public void testPrintableAscii() { - for (int i = 0x20; i <= 0x7E; i++) { - assertWidthIs(1, i); - } - } - - public void testSomeWidthOne() { - assertWidthIs(1, 'å'); - assertWidthIs(1, 'ä'); - assertWidthIs(1, 'ö'); - assertWidthIs(1, 0x23F2); - } - - public void testSomeWide() { - assertWidthIs(2, 'A'); - assertWidthIs(2, 'B'); - assertWidthIs(2, 'C'); - assertWidthIs(2, '中'); - assertWidthIs(2, '文'); - - assertWidthIs(2, 0x679C); - assertWidthIs(2, 0x679D); - - assertWidthIs(2, 0x2070E); - assertWidthIs(2, 0x20731); - - assertWidthIs(1, 0x1F781); - } - - public void testSomeNonWide() { - assertWidthIs(1, 0x1D11E); - assertWidthIs(1, 0x1D11F); - } - - public void testCombining() { - assertWidthIs(0, 0x0302); - assertWidthIs(0, 0x0308); - assertWidthIs(0, 0xFE0F); - } - - public void testWordJoiner() { - // https://en.wikipedia.org/wiki/Word_joiner - // The word joiner (WJ) is a code point in Unicode used to separate words when using scripts - // that do not use explicit spacing. It is encoded since Unicode version 3.2 - // (released in 2002) as U+2060 WORD JOINER (HTML ⁠). - // The word joiner does not produce any space, and prohibits a line break at its position. - assertWidthIs(0, 0x2060); - } - - public void testSofthyphen() { - // http://osdir.com/ml/internationalization.linux/2003-05/msg00006.html: - // "Existing implementation practice in terminals is that the SOFT HYPHEN is - // a spacing graphical character, and the purpose of my wcwidth() was to - // predict the advancement of the cursor position after a string is sent to - // a terminal. Hence, I have no choice but to keep wcwidth(SOFT HYPHEN) = 1. - // VT100-style terminals do not hyphenate." - assertWidthIs(1, 0x00AD); - } - - public void testHangul() { - assertWidthIs(1, 0x11A3); - } - - public void testEmojis() { - assertWidthIs(2, 0x1F428); // KOALA. - assertWidthIs(2, 0x231a); // WATCH. - assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8). - } - -}