From bd18e116d3edb6fd897a49d8c315d91b345bbfb0 Mon Sep 17 00:00:00 2001 From: dingyi222666 Date: Sat, 18 Jun 2022 03:19:50 +0800 Subject: [PATCH] feat: support for input of basic characters in terminal (termios support - work in progress: 4/?) --- .idea/misc.xml | 7 + app/build.gradle.kts | 3 +- .../dingyi/myluaapp/build/GradleSupport.kt | 2 +- .../com/dingyi/myluaapp/build/MainActivity.kt | 334 +++++++----------- .../myluaapp/build/TerminalExtension.kt | 74 ++++ app/src/main/res/layout/main.xml | 5 + .../META-INF/shell-lib/gradle.properties | 1 - .../shell-plugins/com.dingyi.terminal.support | 1 + .../VirtualProcessConsoleMetaData.java | 4 +- .../VirtualProcessEnvironment.java | 12 +- .../services/NativeServices.java | 4 +- terminal-view/build.gradle.kts | 1 - .../terminal/emulator/TerminalSession.java | 19 +- .../shared/TerminalSessionClientBase.java | 95 +++++ .../shared/TerminalViewClientBase.java | 130 +++++++ terminal-view/src/test/java/TestFile.java | 10 +- virtual-process/build.gradle.kts | 1 - .../{util => }/ByteArrayBuffer.java | 2 +- .../terminal/virtualprocess/ByteQueue.java | 110 ++++++ ...tStream.java => ByteQueueInputStream.java} | 70 ++-- ...Stream.java => ByteQueueOutputStream.java} | 69 ++-- .../virtualprocess/SimpleTermiosSupport.java | 104 +++++- .../virtualprocess/VirtualExecutable.java | 2 - .../VirtualExecutableExecutor.java | 4 +- .../VirtualExecutableService.java | 80 +++++ .../VirtualExecutableSystem.java | 72 ---- .../virtualprocess/VirtualProcess.java | 14 +- .../VirtualProcessEnvironment.java | 42 ++- ...System.java => VirtualProcessService.java} | 18 +- .../VirtualTerminalEnvironment.java | 46 +-- .../virtualprocess/lib/EchoSupport.java | 12 +- .../virtualprocess/lib/ShellSupport.java | 28 ++ .../META-INF/shell-lib/echo.properties | 1 - .../shell-plugins/com.dingyi.terminal.support | 2 + 34 files changed, 907 insertions(+), 472 deletions(-) create mode 100644 app/src/main/java/com/dingyi/myluaapp/build/TerminalExtension.kt delete mode 100644 app/src/main/resources/META-INF/shell-lib/gradle.properties create mode 100644 app/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support create mode 100644 terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalSessionClientBase.java create mode 100644 terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalViewClientBase.java rename virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/{util => }/ByteArrayBuffer.java (98%) create mode 100644 virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueue.java rename virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/{stream/QueueInputStream.java => ByteQueueInputStream.java} (54%) rename virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/{stream/QueueOutputStream.java => ByteQueueOutputStream.java} (55%) create mode 100644 virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableService.java delete mode 100644 virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableSystem.java rename virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/{VirtualProcessSystem.java => VirtualProcessService.java} (89%) create mode 100644 virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/ShellSupport.java delete mode 100644 virtual-process/src/main/resources/META-INF/shell-lib/echo.properties create mode 100644 virtual-process/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support diff --git a/.idea/misc.xml b/.idea/misc.xml index 2a4d5b52..a753e058 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,12 @@ + + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 25527bb0..52e79069 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -57,12 +57,13 @@ android { } packagingOptions { resources.excludes.addAll(arrayOf("xsd/*", "license/*","**.dylib","**.dll")) - resources.pickFirsts.addAll(arrayOf("kotlin/**","META-INF/**")) + // resources.pickFirsts.addAll(arrayOf("kotlin/**","META-INF/**")) if (isBuildForAndroid()) { resources .excludes .addAll(arrayOf("org/fusesource/**")) } + resources.merges.add("META-INF/**") } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/app/src/main/java/com/dingyi/myluaapp/build/GradleSupport.kt b/app/src/main/java/com/dingyi/myluaapp/build/GradleSupport.kt index 1a1cbc6a..4b6fdcfe 100644 --- a/app/src/main/java/com/dingyi/myluaapp/build/GradleSupport.kt +++ b/app/src/main/java/com/dingyi/myluaapp/build/GradleSupport.kt @@ -29,7 +29,7 @@ class GradleSupport(processChannel: VirtualProcessEnvironment) : VirtualExecutab it.isBuildCacheEnabled = true it.isBuildCacheDebugLogging = true it.projectDir = projectPath - it.currentDir = File(mProcessEnvironment.currentWorkDir) + it.currentDir = projectPath it.gradleUserHomeDir = projectPath.resolve(".gradle_home") it.projectCacheDir = projectPath.resolve(".gradle") it.isRefreshDependencies = true diff --git a/app/src/main/java/com/dingyi/myluaapp/build/MainActivity.kt b/app/src/main/java/com/dingyi/myluaapp/build/MainActivity.kt index 5b531d2d..2124f9e1 100644 --- a/app/src/main/java/com/dingyi/myluaapp/build/MainActivity.kt +++ b/app/src/main/java/com/dingyi/myluaapp/build/MainActivity.kt @@ -1,16 +1,17 @@ package com.dingyi.myluaapp.build +import android.content.ClipData +import android.content.ClipboardManager import android.os.Bundle -import android.view.KeyEvent import android.view.MotionEvent import androidx.appcompat.app.AppCompatActivity -import com.dingyi.myluaapp.build.api.R +import com.dingyi.myluaapp.build.api.databinding.MainBinding +import com.dingyi.terminal.emulator.KeyHandler import com.dingyi.terminal.emulator.TerminalSession -import com.dingyi.terminal.emulator.TerminalSessionClient -import com.dingyi.terminal.view.TerminalView -import com.dingyi.terminal.view.TerminalViewClient +import com.dingyi.terminal.emulator.TextStyle +import com.dingyi.terminal.shared.TerminalSessionClientBase +import com.dingyi.terminal.shared.TerminalViewClientBase import java.io.File -import java.lang.Exception import java.util.zip.ZipFile import kotlin.concurrent.thread @@ -18,16 +19,22 @@ import kotlin.concurrent.thread class MainActivity : AppCompatActivity() { - private lateinit var terminalView: TerminalView + private lateinit var viewBinding: MainBinding + + + private lateinit var terminalFontSize: FontSize + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.main) + viewBinding = MainBinding.inflate(layoutInflater) + setContentView(viewBinding.root) - terminalView = findViewById(R.id.terminalView) - terminalView.setBackgroundColor(0xff000000.toInt()) + terminalFontSize = FontSize(this.getDefaultFontSizes()) + + viewBinding.terminalView.setBackgroundColor(0xff000000.toInt()) thread { @@ -42,206 +49,20 @@ class MainActivity : AppCompatActivity() { private fun createTerminal() { val session = TerminalSession( - "gradle", + "shell", File(getDefaultProjectDir(), "TestProject").path, - arrayOf("help"), + arrayOf(""), arrayOfNulls(0), 500, - object : - TerminalSessionClient { - override fun onTextChanged(changedSession: TerminalSession) { - terminalView - .onScreenUpdated() - } - - override fun onTitleChanged(changedSession: TerminalSession) { - terminalView - .onScreenUpdated() - } - - override fun onSessionFinished(finishedSession: TerminalSession) { - - } - - override fun onCopyTextToClipboard( - session: TerminalSession, - text: String? - ) { - - } - - override fun onPasteTextFromClipboard(session: TerminalSession?) { - - } - - override fun onBell(session: TerminalSession) { - terminalView - .onScreenUpdated() - } - - override fun onColorsChanged(session: TerminalSession) { - terminalView - .onScreenUpdated() - } - - override fun onTerminalCursorStateChange(state: Boolean) { - terminalView - .onScreenUpdated() - } - - override fun setTerminalShellPid( - session: TerminalSession, - pid: Int - ) { - - } - - override fun getTerminalCursorStyle(): Int { - return 0 - } - - override fun logError(tag: String?, message: String?) { - - } - - override fun logWarn(tag: String?, message: String?) { - - } - - override fun logInfo(tag: String?, message: String?) { - - } - - override fun logDebug(tag: String?, message: String?) { - - } - - override fun logVerbose(tag: String?, message: String?) { - - } - - override fun logStackTraceWithMessage( - tag: String?, - message: String?, - e: Exception? - ) { - - } - - override fun logStackTrace(tag: String?, e: Exception?) { - - } - - } + TestTerminalSessionClient() ) + viewBinding.terminalView.setTextSize(terminalFontSize.getFontSize()) - terminalView.setTextSize(30) - terminalView.setTerminalViewClient(object : - TerminalViewClient { - override fun onScale(scale: Float): Float { - terminalView.updateSize() - return 0f - } - - override fun onSingleTapUp(e: MotionEvent) { - - } - - override fun shouldBackButtonBeMappedToEscape(): Boolean { - return false - } + viewBinding.terminalView.setTerminalViewClient(TestTerminalViewClient()) - override fun shouldEnforceCharBasedInput(): Boolean { - return false - } - - override fun shouldUseCtrlSpaceWorkaround(): Boolean { - return false - } - - override fun isTerminalViewSelected(): Boolean { - return false - } - - override fun copyModeChanged(copyMode: Boolean) { - - } - - override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { - return false - } - - override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { - return false - } - - override fun onLongPress(event: MotionEvent?): Boolean { - return false - } - - override fun readControlKey(): Boolean { - return false - } - - override fun readAltKey(): Boolean { - return false - } - - override fun readShiftKey(): Boolean { - return false - } - - override fun readFnKey(): Boolean { - return false - } - - override fun onCodePoint( - codePoint: Int, - ctrlDown: Boolean, - session: TerminalSession? - ): Boolean { - return false - } - - override fun onEmulatorSet() { - - } - - override fun logError(tag: String?, message: String?) { - - } - - override fun logWarn(tag: String?, message: String?) { - - } - - override fun logInfo(tag: String?, message: String?) { - - } - - override fun logDebug(tag: String?, message: String?) { - - } - - override fun logVerbose(tag: String?, message: String?) { - - } - - override fun logStackTraceWithMessage(tag: String?, message: String?, e: Exception?) { - - } - - override fun logStackTrace(tag: String?, e: Exception?) { - - } - - }) - - - terminalView.attachSession(session) - terminalView.updateSize() + viewBinding.terminalView.attachSession(session) /* terminalView @@ -280,5 +101,114 @@ class MainActivity : AppCompatActivity() { } + inner class TestTerminalViewClient : TerminalViewClientBase() { + + private val LOG_TAG = "TestTerminalViewClient" + + private var mTerminalCursorBlinkerStateAlreadySet = false + + override fun onEmulatorSet() { + if (!mTerminalCursorBlinkerStateAlreadySet) { + // Start terminal cursor blinking if enabled + // We need to wait for the first session to be attached that's set in + // TermuxActivity.onServiceConnected() and then the multiple calls to TerminalView.updateSize() + // where the final one eventually sets the mEmulator when width/height is not 0. Otherwise + // blinker will not start again if TermuxActivity is started again after exiting it with + // double back press. Check TerminalView.setTerminalCursorBlinkerState(). + setTerminalCursorBlinkerState(true); + mTerminalCursorBlinkerStateAlreadySet = true; + } + } + + private fun setTerminalCursorBlinkerState(start: Boolean) { + if (start) { + // If set/update the cursor blinking rate is successful, then enable cursor blinker + if (viewBinding.terminalView.setTerminalCursorBlinkerRate(500)) { + viewBinding.terminalView + .setTerminalCursorBlinkerState(true, true) + } else { + logError(LOG_TAG, "Failed to start cursor blinker") + } + } else { + // Disable cursor blinker + viewBinding.terminalView.setTerminalCursorBlinkerState(false, true) + } + } + + override fun onScale(scale: Float): Float { + if (scale < 0.9f || scale > 1.1f) { + val increase = scale > 1f + changeFontSize(increase) + return 1.0f + } + return scale + } + + override fun onSingleTapUp(e: MotionEvent?) { + if (!this@MainActivity.areDisableSoftKeyboardFlagsSet()) { + this@MainActivity.showSoftKeyboard(viewBinding.terminalView) + } else { + logVerbose( + LOG_TAG, + "Not showing soft keyboard onSingleTapUp since its disabled" + ); + } + } + + + private fun changeFontSize(increase: Boolean) { + terminalFontSize.changeFontSize(increase) + viewBinding.terminalView.setTextSize(terminalFontSize.getFontSize()) + } + + } + + inner class TestTerminalSessionClient : TerminalSessionClientBase() { + override fun onTextChanged(changedSession: TerminalSession) { + viewBinding.terminalView.onScreenUpdated() + } + + override fun onCopyTextToClipboard(session: TerminalSession, text: String?) { + + val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + clipboardManager.setPrimaryClip(ClipData.newPlainText("text", text)) + } + + override fun onPasteTextFromClipboard(session: TerminalSession?) { + val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + val text = clipboardManager.primaryClip + ?.getItemAt(0) + ?.coerceToText(this@MainActivity) + .toString() + + session?.write(text) + + } + + + override fun onSessionFinished(finishedSession: TerminalSession) { + viewBinding.terminalView.setTerminalCursorBlinkerState(false, true) + } + + override fun onColorsChanged(session: TerminalSession) { + /* val colors = session.emulator.mColors + viewBinding + .terminalView + .setBackgroundColor(colors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]) + */ + } + + + override fun onTerminalCursorStateChange(state: Boolean) { + viewBinding + .terminalView + .setTerminalCursorBlinkerState(state, false) + } + } + + override fun onStop() { + super.onStop() + viewBinding.terminalView.setTerminalCursorBlinkerState(false, true) + } } \ No newline at end of file diff --git a/app/src/main/java/com/dingyi/myluaapp/build/TerminalExtension.kt b/app/src/main/java/com/dingyi/myluaapp/build/TerminalExtension.kt new file mode 100644 index 00000000..a2b4e219 --- /dev/null +++ b/app/src/main/java/com/dingyi/myluaapp/build/TerminalExtension.kt @@ -0,0 +1,74 @@ +package com.dingyi.myluaapp.build + +import android.app.Activity +import android.content.Context +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import kotlin.math.roundToInt + + +fun Context.getDefaultFontSizes(): IntArray { + val dipInPixels = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 1f, + this.resources.displayMetrics + ) + + val sizes = IntArray(3) + + // This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size + // to prevent invisible text due to zoom be mistake: + sizes[1] = (4f * dipInPixels).toInt() // min + + // http://www.google.com/design/spec/style/typography.html#typography-line-height + var defaultFontSize = (12 * dipInPixels).roundToInt() + // Make it divisible by 2 since that is the minimal adjustment step: + if (defaultFontSize % 2 == 1) defaultFontSize-- + sizes[0] = defaultFontSize // default + sizes[2] = 256 // max + return sizes +} + +/** + * If value is not in the range [min, max], set it to either min or max. + */ +fun Int.clamp(min: Int, max: Int): Int { + return this.coerceAtLeast(min).coerceAtMost(max) +} + +class FontSize(defaultSizes: IntArray) { + + private val defaultSize = defaultSizes[0] + private val minSize = defaultSizes[1] + private val maxSize = defaultSizes[2] + private var currentSize = defaultSize + + fun getFontSize(): Int { + return currentSize.clamp(minSize, maxSize) + } + + fun setFontSize(size: Int) { + currentSize = size + } + + fun changeFontSize(increase: Boolean) { + var currentFontSize = getFontSize() + currentFontSize += (if (increase) 1 else -1) * 2 + currentFontSize = minSize.coerceAtLeast(currentFontSize.coerceAtMost(maxSize)) + setFontSize(currentFontSize) + } + +} + +fun Activity.areDisableSoftKeyboardFlagsSet(): Boolean { + return if (window == null) false else + window.attributes.flags and WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM !== 0 +} + +fun Activity.showSoftKeyboard(view: View) { + view.requestFocus() + val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput(view, 0) +} \ No newline at end of file diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 3225e755..b2574728 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -5,6 +5,11 @@ \ No newline at end of file diff --git a/app/src/main/resources/META-INF/shell-lib/gradle.properties b/app/src/main/resources/META-INF/shell-lib/gradle.properties deleted file mode 100644 index a741efc3..00000000 --- a/app/src/main/resources/META-INF/shell-lib/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -implement-class=com.dingyi.myluaapp.build.GradleSupport \ No newline at end of file diff --git a/app/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support b/app/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support new file mode 100644 index 00000000..24c2a295 --- /dev/null +++ b/app/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support @@ -0,0 +1 @@ +gradle:com.dingyi.myluaapp.build.GradleSupport \ No newline at end of file diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/VirtualProcessConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/VirtualProcessConsoleMetaData.java index 1b41cb8f..a0d1e804 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/VirtualProcessConsoleMetaData.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/VirtualProcessConsoleMetaData.java @@ -1,14 +1,14 @@ package org.gradle.internal.nativeintegration.console; import com.dingyi.terminal.virtualprocess.VirtualProcess; -import com.dingyi.terminal.virtualprocess.VirtualProcessSystem; +import com.dingyi.terminal.virtualprocess.VirtualProcessService; public class VirtualProcessConsoleMetaData implements ConsoleMetaData { private final VirtualProcess virtualProcess; public VirtualProcessConsoleMetaData() { - this.virtualProcess = VirtualProcessSystem.currentProcess(); + this.virtualProcess = VirtualProcessService.currentProcess(); } @Override diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/processenvironment/VirtualProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/processenvironment/VirtualProcessEnvironment.java index 68530d0b..54095ccf 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/processenvironment/VirtualProcessEnvironment.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/processenvironment/VirtualProcessEnvironment.java @@ -3,7 +3,7 @@ import android.os.Process; import com.dingyi.terminal.virtualprocess.VirtualProcess; -import com.dingyi.terminal.virtualprocess.VirtualProcessSystem; +import com.dingyi.terminal.virtualprocess.VirtualProcessService; import org.gradle.internal.nativeintegration.NativeIntegrationException; @@ -16,7 +16,7 @@ public class VirtualProcessEnvironment extends AbstractProcessEnvironment { public VirtualProcessEnvironment() { - currentProcess = VirtualProcessSystem.currentProcess(); + currentProcess = VirtualProcessService.currentProcess(); } private File processDir; @@ -24,7 +24,7 @@ public VirtualProcessEnvironment() { @Override public File getProcessDir() throws NativeIntegrationException { if (processDir == null) { - processDir = new File(VirtualProcessSystem.currentProcess().getProcessEnvironment() + processDir = new File(VirtualProcessService.currentProcess().getProcessEnvironment() .getCurrentWorkDir()); } @@ -47,7 +47,7 @@ public void detachProcess() { @Override protected void removeNativeEnvironmentVariable(String name) { - VirtualProcessSystem + VirtualProcessService .currentProcess() .getProcessEnvironment() .removeEnvironment(name); @@ -55,7 +55,7 @@ protected void removeNativeEnvironmentVariable(String name) { @Override protected void setNativeEnvironmentVariable(String name, String value) { - VirtualProcessSystem + VirtualProcessService .currentProcess() .getProcessEnvironment() .putEnvironment(name,value); @@ -63,7 +63,7 @@ protected void setNativeEnvironmentVariable(String name, String value) { @Override protected void setNativeProcessDir(File processDir) { - VirtualProcessSystem + VirtualProcessService .currentProcess() .getProcessEnvironment() .setCurrentWorkDir(processDir.getAbsolutePath()); diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/services/NativeServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/services/NativeServices.java index 3cfb669c..4ae5622a 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/services/NativeServices.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/services/NativeServices.java @@ -274,7 +274,7 @@ protected ProcessEnvironment createProcessEnvironment(OperatingSystem operatingS } //dingyi modify: add VirtualProcessEnvironment support try { - Class.forName("com.dingyi.terminal.virtualprocess.VirtualProcessSystem", false, getClass().getClassLoader()); + Class.forName("com.dingyi.terminal.virtualprocess.VirtualProcessService", false, getClass().getClassLoader()); return new VirtualProcessEnvironment(); } catch (ClassNotFoundException e) { //ignore @@ -314,7 +314,7 @@ private ConsoleDetector backingConsoleDetector(OperatingSystem operatingSystem) } try { - Class.forName("com.dingyi.terminal.virtualprocess.VirtualProcessSystem", false, getClass().getClassLoader()); + Class.forName("com.dingyi.terminal.virtualprocess.VirtualProcessService", false, getClass().getClassLoader()); return new VirtualProcessConsoleDetector(); } catch (ClassNotFoundException e) { //ignore diff --git a/terminal-view/build.gradle.kts b/terminal-view/build.gradle.kts index b47ee564..8c3f0241 100644 --- a/terminal-view/build.gradle.kts +++ b/terminal-view/build.gradle.kts @@ -24,7 +24,6 @@ android { dependencies { testImplementation("junit:junit:4.13.2") - implementation("androidx.annotation:annotation:1.3.0") api(project(":virtual-process")) } \ No newline at end of file diff --git a/terminal-view/src/main/java/com/dingyi/terminal/emulator/TerminalSession.java b/terminal-view/src/main/java/com/dingyi/terminal/emulator/TerminalSession.java index f02aef8c..f6ac86f8 100644 --- a/terminal-view/src/main/java/com/dingyi/terminal/emulator/TerminalSession.java +++ b/terminal-view/src/main/java/com/dingyi/terminal/emulator/TerminalSession.java @@ -5,7 +5,7 @@ import android.os.Message; import com.dingyi.terminal.virtualprocess.VirtualProcess; -import com.dingyi.terminal.virtualprocess.VirtualProcessSystem; +import com.dingyi.terminal.virtualprocess.VirtualProcessService; import com.dingyi.terminal.virtualprocess.VirtualTerminalEnvironment; import java.io.IOException; @@ -137,8 +137,9 @@ public void initializeEmulator(int columns, int rows) { //dingyi modify: use virtual process - mProcess = VirtualProcessSystem + mProcess = VirtualProcessService .createProcess(mShellPath, mCwd, mArgs, mEnv); + mShellPid = mProcess.getProcessId(); mClient.setTerminalShellPid(this, mShellPid); @@ -148,11 +149,15 @@ public void initializeEmulator(int columns, int rows) { e.printStackTrace(); return; } + mProcess.setProcessEnvironment(terminalChannel.getProcessEnvironment()); + mProcess.getProcessEnvironment() .getTermiosSupport() .setSize(columns, rows); - mProcess.start(); + + + new Thread("TermSessionInputReader[pid=" + mShellPid + "]") { @Override public void run() { @@ -200,6 +205,7 @@ public void run() { }.start(); + mProcess.start(); } /** Write data to the shell process. */ @@ -263,10 +269,10 @@ public void reset() { /** Finish this terminal session by sending SIGKILL to the shell. */ public void finishIfRunning() { if (isRunning()) { - VirtualProcessSystem.killProcess(mShellPid); try { + VirtualProcessService.killProcess(mShellPid); terminalChannel.destroy(); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } /*Os.kill(mShellPid, OsConstants.SIGKILL);*/ @@ -345,7 +351,8 @@ public String getCwd() { return outputPath; }*/ //dingyi modify: replace to use virtual process - return VirtualProcessSystem.getProcess(mShellPid).getProcessEnvironment() + return VirtualProcessService.getProcess(mShellPid) + .getProcessEnvironment() .getCurrentWorkDir(); } catch (SecurityException e) { Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Error getting current directory", e); diff --git a/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalSessionClientBase.java b/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalSessionClientBase.java new file mode 100644 index 00000000..4f4369f8 --- /dev/null +++ b/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalSessionClientBase.java @@ -0,0 +1,95 @@ +package com.dingyi.terminal.shared; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.dingyi.terminal.emulator.Logger; +import com.dingyi.terminal.emulator.TerminalSession; +import com.dingyi.terminal.emulator.TerminalSessionClient; + +public class TerminalSessionClientBase implements TerminalSessionClient { + @Override + public void onTextChanged(@NonNull TerminalSession changedSession) { + + } + + @Override + public void onTitleChanged(@NonNull TerminalSession changedSession) { + + } + + @Override + public void onSessionFinished(@NonNull TerminalSession finishedSession) { + + } + + @Override + public void onCopyTextToClipboard(@NonNull TerminalSession session, String text) { + + } + + @Override + public void onPasteTextFromClipboard(@Nullable TerminalSession session) { + + } + + @Override + public void onBell(@NonNull TerminalSession session) { + + } + + @Override + public void onColorsChanged(@NonNull TerminalSession session) { + + } + + @Override + public void onTerminalCursorStateChange(boolean state) { + + } + + @Override + public void setTerminalShellPid(@NonNull TerminalSession session, int pid) { + + } + + @Override + public Integer getTerminalCursorStyle() { + return null; + } + + @Override + public void logError(String tag, String message) { + Logger.logError(null, tag, message); + } + + @Override + public void logWarn(String tag, String message) { + Logger.logWarn(null, tag, message); + } + + @Override + public void logInfo(String tag, String message) { + Logger.logInfo(null, tag, message); + } + + @Override + public void logDebug(String tag, String message) { + Logger.logDebug(null, tag, message); + } + + @Override + public void logVerbose(String tag, String message) { + Logger.logVerbose(null, tag, message); + } + + @Override + public void logStackTraceWithMessage(String tag, String message, Exception e) { + Logger.logStackTraceWithMessage(null, tag, message, e); + } + + @Override + public void logStackTrace(String tag, Exception e) { + logStackTraceWithMessage(tag, e.getMessage(), e); + } +} diff --git a/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalViewClientBase.java b/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalViewClientBase.java new file mode 100644 index 00000000..13e07c62 --- /dev/null +++ b/terminal-view/src/main/java/com/dingyi/terminal/shared/TerminalViewClientBase.java @@ -0,0 +1,130 @@ +package com.dingyi.terminal.shared; + +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.dingyi.terminal.emulator.Logger; +import com.dingyi.terminal.emulator.TerminalSession; +import com.dingyi.terminal.emulator.TerminalSessionClient; +import com.dingyi.terminal.view.TerminalViewClient; + +public class TerminalViewClientBase implements TerminalViewClient { + + @Override + public float onScale(float scale) { + return 0; + } + + @Override + public void onSingleTapUp(MotionEvent e) { + + } + + @Override + public boolean shouldBackButtonBeMappedToEscape() { + return false; + } + + @Override + public boolean shouldEnforceCharBasedInput() { + return false; + } + + @Override + public boolean shouldUseCtrlSpaceWorkaround() { + return false; + } + + @Override + public boolean isTerminalViewSelected() { + return true; + } + + @Override + public void copyModeChanged(boolean copyMode) { + + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent e) { + return false; + } + + @Override + public boolean onLongPress(MotionEvent event) { + return false; + } + + @Override + public boolean readControlKey() { + return false; + } + + @Override + public boolean readAltKey() { + return false; + } + + @Override + public boolean readShiftKey() { + return false; + } + + @Override + public boolean readFnKey() { + return false; + } + + @Override + public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { + return false; + } + + @Override + public void onEmulatorSet() { + + } + + @Override + public void logError(String tag, String message) { + Logger.logError(null, tag, message); + } + + @Override + public void logWarn(String tag, String message) { + Logger.logWarn(null, tag, message); + } + + @Override + public void logInfo(String tag, String message) { + Logger.logInfo(null, tag, message); + } + + @Override + public void logDebug(String tag, String message) { + Logger.logDebug(null, tag, message); + } + + @Override + public void logVerbose(String tag, String message) { + Logger.logVerbose(null, tag, message); + } + + @Override + public void logStackTraceWithMessage(String tag, String message, Exception e) { + Logger.logStackTraceWithMessage(null, tag, message, e); + } + + @Override + public void logStackTrace(String tag, Exception e) { + logStackTraceWithMessage(tag, e.getMessage(), e); + } +} diff --git a/terminal-view/src/test/java/TestFile.java b/terminal-view/src/test/java/TestFile.java index d69e588d..89c56265 100644 --- a/terminal-view/src/test/java/TestFile.java +++ b/terminal-view/src/test/java/TestFile.java @@ -1,9 +1,9 @@ import com.dingyi.terminal.virtualprocess.VirtualExecutable; import com.dingyi.terminal.virtualprocess.VirtualExecutableExecutorPool; -import com.dingyi.terminal.virtualprocess.VirtualExecutableSystem; +import com.dingyi.terminal.virtualprocess.VirtualExecutableService; import com.dingyi.terminal.virtualprocess.VirtualProcess; import com.dingyi.terminal.virtualprocess.VirtualProcessEnvironment; -import com.dingyi.terminal.virtualprocess.VirtualProcessSystem; +import com.dingyi.terminal.virtualprocess.VirtualProcessService; import org.junit.Test; @@ -14,10 +14,10 @@ public class TestFile { @Test public void test() { - VirtualExecutableSystem.getInstance() - .registerBinary("echo", echo.class); + VirtualExecutableService.getInstance() + .registerExecutable("echo", echo.class); - VirtualProcess process= VirtualProcessSystem + VirtualProcess process= VirtualProcessService .createProcess("echo", "Hello", "VirtualProcess"); process.start(); diff --git a/virtual-process/build.gradle.kts b/virtual-process/build.gradle.kts index 6353a0ab..90cd5a26 100644 --- a/virtual-process/build.gradle.kts +++ b/virtual-process/build.gradle.kts @@ -9,5 +9,4 @@ java { dependencies { compileOnly(project(":android-stubs")) - implementation("commons-io:commons-io:2.11.0") } \ No newline at end of file diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/util/ByteArrayBuffer.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteArrayBuffer.java similarity index 98% rename from virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/util/ByteArrayBuffer.java rename to virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteArrayBuffer.java index 3be2963a..6422ed92 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/util/ByteArrayBuffer.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteArrayBuffer.java @@ -29,7 +29,7 @@ * */ -package com.dingyi.terminal.virtualprocess.util; +package com.dingyi.terminal.virtualprocess; /** * A resizable byte array. diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueue.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueue.java new file mode 100644 index 00000000..98cce6ef --- /dev/null +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueue.java @@ -0,0 +1,110 @@ +package com.dingyi.terminal.virtualprocess; + +/** + * A circular byte buffer allowing one producer and one consumer thread. + */ +public 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,int offset, 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; + 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/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueInputStream.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueInputStream.java similarity index 54% rename from virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueInputStream.java rename to virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueInputStream.java index 277bd2c1..885a860a 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueInputStream.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueInputStream.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.dingyi.terminal.virtualprocess.stream; +package com.dingyi.terminal.virtualprocess; import java.io.IOException; @@ -24,44 +24,21 @@ import java.util.Objects; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; /** - * Simple alternative to JDK {@link PipedInputStream}; queue input stream provides what's written in queue - * output stream. - * - *

- * Example usage: - *

- *
- * QueueInputStream inputStream = new QueueInputStream();
- * QueueOutputStream outputStream = inputStream.newQueueOutputStream();
- *
- * outputStream.write("hello world".getBytes(UTF_8));
- * inputStream.read();
- * 
- *

- * Unlike JDK {@link PipedInputStream} and {@link PipedOutputStream}, queue input/output streams may be used safely in a - * single thread or multiple threads. Also, unlike JDK classes, no special meaning is attached to initial or current - * thread. Instances can be used longer after initial threads exited. - *

- *

- * Closing a {@code QueueInputStream} has no effect. The methods in this class can be called after the stream has been - * closed without generating an {@code IOException}. - *

- * - * @see QueueOutputStream + * Simple alternative to JDK {@link PipedInputStream}; + * @see ByteQueueOutputStream * @since 2.9.0 */ -public class QueueInputStream extends InputStream { +public class ByteQueueInputStream extends InputStream { - private final BlockingQueue blockingQueue; + final ByteQueue blockingQueue; /** * Constructs a new instance with no limit to its internal buffer size. */ - public QueueInputStream() { - this(new LinkedBlockingQueue<>()); + public ByteQueueInputStream() { + this(new ByteQueue(1024)); } /** @@ -69,7 +46,7 @@ public QueueInputStream() { * * @param blockingQueue backing queue for the stream */ - public QueueInputStream(final BlockingQueue blockingQueue) { + public ByteQueueInputStream(final ByteQueue blockingQueue) { this.blockingQueue = Objects.requireNonNull(blockingQueue, "blockingQueue"); } @@ -79,12 +56,13 @@ public QueueInputStream(final BlockingQueue blockingQueue) { * * @return QueueOutputStream connected to this stream */ - public QueueOutputStream newQueueOutputStream() { - return new QueueOutputStream(blockingQueue); + public ByteQueueOutputStream newQueueOutputStream() { + return new ByteQueueOutputStream(blockingQueue); } private static final int EOF = -1; + private final byte[] readBuffer = new byte[1]; /** * Reads and returns a single byte. @@ -93,22 +71,22 @@ public QueueOutputStream newQueueOutputStream() { */ @Override public int read() throws IOException { + return read(readBuffer); + } + + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + + @Override + public int read(byte[] b, int off, int len) throws IOException { if (isClose) { throw new IOException("Stream is closed"); } - int value; - try { - //read a byte - Integer integerValue = blockingQueue.take(); - if (integerValue == null) { - return EOF; - } - value = integerValue & 0xFF; - } catch (InterruptedException e) { - throw new IOException("Stream is closed", e); - } - - return value; + return blockingQueue.read(b,off,true); } private boolean isClose = false; diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueOutputStream.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueOutputStream.java similarity index 55% rename from virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueOutputStream.java rename to virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueOutputStream.java index 1a105835..6f5ee629 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/stream/QueueOutputStream.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/ByteQueueOutputStream.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.dingyi.terminal.virtualprocess.stream; +package com.dingyi.terminal.virtualprocess; import java.io.IOException; @@ -27,40 +27,20 @@ import java.util.concurrent.LinkedBlockingQueue; /** - * Simple alternative to JDK {@link PipedOutputStream}; queue input stream provides what's written in queue - * output stream. - *

- * Example usage: - *

- * - *
- * QueueOutputStream outputStream = new QueueOutputStream();
- * QueueInputStream inputStream = outputStream.newPipeInputStream();
- *
- * outputStream.write("hello world".getBytes(UTF_8));
- * inputStream.read();
- * 
- *

- * Unlike JDK {@link PipedInputStream} and {@link PipedOutputStream}, queue input/output streams may be used safely in a - * single thread or multiple threads. Also, unlike JDK classes, no special meaning is attached to initial or current - * thread. Instances can be used longer after initial threads exited. - *

- * Closing a {@code QueueOutputStream} has no effect. The methods in this class can be called after the stream has been - * closed without generating an {@code IOException}. - *

- * - * @see QueueInputStream + * Simple alternative to JDK {@link PipedOutputStream}; + * @see ByteQueueInputStream * @since 2.9.0 */ -public class QueueOutputStream extends OutputStream { +public class ByteQueueOutputStream extends OutputStream { + + final ByteQueue blockingQueue; - private final BlockingQueue blockingQueue; /** * Constructs a new instance with no limit to internal buffer size. */ - public QueueOutputStream() { - this(new LinkedBlockingQueue<>()); + public ByteQueueOutputStream() { + this(new ByteQueue(1024)); } /** @@ -68,7 +48,7 @@ public QueueOutputStream() { * * @param blockingQueue backing queue for the stream */ - public QueueOutputStream(final BlockingQueue blockingQueue) { + public ByteQueueOutputStream(final ByteQueue blockingQueue) { this.blockingQueue = Objects.requireNonNull(blockingQueue, "blockingQueue"); } @@ -78,13 +58,15 @@ public QueueOutputStream(final BlockingQueue blockingQueue) { * * @return QueueInputStream connected to this stream */ - public QueueInputStream newQueueInputStream() { - return new QueueInputStream(blockingQueue); + public ByteQueueInputStream newQueueInputStream() { + return new ByteQueueInputStream(blockingQueue); } private boolean isClose = false; + private final byte[] writeBuffer = new byte[1]; + /** * Writes a single byte. * @@ -92,17 +74,28 @@ public QueueInputStream newQueueInputStream() { */ @Override public void write(final int b) throws IOException { + writeBuffer[0] = (byte) (b); + write(writeBuffer); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + + @Override + public void write(byte[] b, int off, int len) throws IOException { if (isClose) { throw new IOException("Stream is closed"); } - try { - blockingQueue.put(0xFF & b); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - final InterruptedIOException interruptedIoException = new InterruptedIOException(); - interruptedIoException.initCause(e); - throw interruptedIoException; + + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException("off: " + off + ", len: " + len + ", b.length: " + b.length); } + + blockingQueue.write(b, off, len); + } @Override diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/SimpleTermiosSupport.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/SimpleTermiosSupport.java index a88f9c59..68edde60 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/SimpleTermiosSupport.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/SimpleTermiosSupport.java @@ -1,13 +1,12 @@ package com.dingyi.terminal.virtualprocess; -import com.dingyi.terminal.virtualprocess.util.ByteArrayBuffer; - import java.io.FilterInputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Arrays; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; //TODO:Termios Support public class SimpleTermiosSupport { @@ -34,10 +33,10 @@ public SimpleTermiosSupport( void doWrapper() { processEnvironment - .processOutputStream = new TermiosOutputStream(processEnvironment.processOutputStream); - processEnvironment - .processInputStream = new TermiosInputStream(processEnvironment.processInputStream); - + .processOutputStream = new ProcessTermiosOutputStream(processEnvironment.processOutputStream); + terminalEnvironment + .terminalOutputStream = new TerminalTermiosOutputStream(terminalEnvironment.terminalOutputStream, terminalEnvironment + .terminalInputStream.blockingQueue); } @@ -69,9 +68,74 @@ public int getRow() { } - public class TermiosOutputStream extends FilterOutputStream { + //TODO:Termios Support + static class TerminalTermiosOutputStream extends FilterOutputStream { + + private final ByteQueue writeQueue; + private final BlockingQueue blockingQueue; + + private final SafeQueueWriteThread queueReadThread; + private final byte[] writeBuffer = new byte[1]; + + public TerminalTermiosOutputStream(OutputStream out, ByteQueue queue) { + super(out); + this.writeQueue = queue; + this.blockingQueue = new LinkedBlockingQueue<>(1024); + queueReadThread = new SafeQueueWriteThread(blockingQueue, writeQueue); + queueReadThread.start(); + } + + @Override + public void write(int b) throws IOException { + writeBuffer[0] = (byte) (b); + write(writeBuffer); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (int i = off; i < off + len; i++) { + blockingQueue.offer(b[i] & 0xFF); + } + out.write(b, off, len); + } + + + + @Override + public void close() throws IOException { + super.close(); + queueReadThread.interrupt(); + } + + static class SafeQueueWriteThread extends Thread { + + private final ByteQueue writerQueue; + private final BlockingQueue readQueue; + + SafeQueueWriteThread(BlockingQueue queue, ByteQueue readQueue) { + this.readQueue = queue; + this.writerQueue = readQueue; + } + + @Override + public void run() { + byte[] buffer = new byte[1]; + while (isAlive() || !isInterrupted()) { + try { + int data = readQueue.take(); + buffer[0] = (byte) (data & 0xFF); + writerQueue.write(buffer, 0, 1); + } catch (Exception e) { + return; + } + } + } + } + } + + class ProcessTermiosOutputStream extends FilterOutputStream { - public TermiosOutputStream(OutputStream out) { + public ProcessTermiosOutputStream(OutputStream out) { super(out); buffer = new ByteArrayBuffer(8); } @@ -83,7 +147,7 @@ private boolean check_OPOST() { } - private void checkAndAppendByte(int b) throws IOException { + private void checkByte(int b) throws IOException { boolean needFlush = false; if (b == TermiosStruct.CR) { if (TermiosStruct.isSet(termiosStruct.c_oflag, TermiosStruct.ONLCR)) { @@ -101,10 +165,8 @@ private void checkAndAppendByte(int b) throws IOException { } else { buffer.append(b); } - }/* else if (TermiosStruct.isSet(termiosStruct.c_lflag, TermiosStruct.ECHO)) { + } else { buffer.append(b); - } */else { - buffer.append(0); } if (needFlush || buffer.isFull()) { @@ -116,12 +178,22 @@ private void checkAndAppendByte(int b) throws IOException { @Override public void write(int b) throws IOException { if (check_OPOST()) { - checkAndAppendByte(b); + checkByte(b); } else { out.write(b); } } + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (check_OPOST()) { + for (int i = off; i < off + len; i++) { + checkByte(b[i]); + } + } else { + out.write(b, off, len); + } + } @Override public void flush() throws IOException { @@ -130,13 +202,13 @@ public void flush() throws IOException { } byte[] b = buffer.toByteArray(); out.write(b, 0, buffer.length()); + /* out.flush();*/ buffer.clear(); - out.flush(); + } } public class TermiosInputStream extends FilterInputStream { - protected TermiosInputStream(InputStream in) { super(in); } diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutable.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutable.java index fc15ba01..e88b94b3 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutable.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutable.java @@ -12,8 +12,6 @@ public VirtualExecutable(VirtualProcessEnvironment processChannel) { } - - protected abstract int start(String[] args) throws Exception; } diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableExecutor.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableExecutor.java index 9949e79e..403c87d3 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableExecutor.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableExecutor.java @@ -20,7 +20,7 @@ class VirtualExecutableExecutor implements Runnable { public void run() { int ret; - VirtualProcessSystem + VirtualProcessService .getInstance() .putProcessWithThread(Thread.currentThread(), mProcess.getProcessId()); @@ -47,7 +47,7 @@ public void run() { e.printStackTrace(); } - VirtualProcessSystem + VirtualProcessService .getInstance() .deleteProcessWithThread(Thread.currentThread()); diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableService.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableService.java new file mode 100644 index 00000000..9b3ecac9 --- /dev/null +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableService.java @@ -0,0 +1,80 @@ +package com.dingyi.terminal.virtualprocess; + + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +public class VirtualExecutableService { + + + private static VirtualExecutableService INSTANCE; + + private VirtualExecutableService() { + tryLoadBinaryForResources(this.getClass().getClassLoader()); + } + + + public static VirtualExecutableService getInstance() { + synchronized (VirtualExecutableService.class) { + if (INSTANCE == null) { + INSTANCE = new VirtualExecutableService(); + } + } + return INSTANCE; + } + + + private final Map> mExecutableClasses = new HashMap>(); + + public void registerExecutable(String name, Class clazz) { + mExecutableClasses.put(name, clazz); + } + + public VirtualExecutable createExecutable(String name, VirtualProcessEnvironment processChannel) { + Class clazz = mExecutableClasses.get(name); + if (clazz == null) { + return null; + } + mExecutableClasses.putIfAbsent(name, clazz); + try { + return (VirtualExecutable) clazz.getConstructor(VirtualProcessEnvironment.class) + .newInstance(processChannel); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void tryLoadBinaryForResources(ClassLoader classLoader) { + String path = "META-INF/shell-plugins/com.dingyi.terminal.support"; + try { + InputStream stream = classLoader + .getResourceAsStream(path); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + String line; + while ((line = reader.readLine()) != null) { + String[] split = line.split(":"); + if (split.length == 2) { + String name = split[0]; + String className = split[1]; + Class clazz = Class.forName(className); + registerExecutable(name, clazz); + } + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean unregisterExecutable(String name) { + return mExecutableClasses.remove(name) != null; + } + + +} diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableSystem.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableSystem.java deleted file mode 100644 index 70898df6..00000000 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualExecutableSystem.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.dingyi.terminal.virtualprocess; - - -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -public class VirtualExecutableSystem { - - - private static VirtualExecutableSystem INSTANCE; - - private VirtualExecutableSystem() { - - } - - - public static VirtualExecutableSystem getInstance() { - synchronized (VirtualExecutableSystem.class) { - if (INSTANCE == null) { - INSTANCE = new VirtualExecutableSystem(); - } - } - return INSTANCE; - } - - - private Map> mBinaryClasses = new HashMap>(); - - public void registerBinary(String name, Class clazz) { - mBinaryClasses.put(name, clazz); - } - - public VirtualExecutable createBinary(String name, VirtualProcessEnvironment processChannel) { - Class clazz = mBinaryClasses.get(name); - if (clazz == null) { - clazz = tryLoadBinaryForResources(name); - if (clazz == null) { - return null; - } - } - mBinaryClasses.putIfAbsent(name, clazz); - try { - return (VirtualExecutable) clazz.getConstructor(VirtualProcessEnvironment.class) - .newInstance(processChannel); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - private Class tryLoadBinaryForResources(String name) { - String path = "META-INF/shell-lib/" + name + ".properties"; - try { - InputStream stream = getClass().getClassLoader() - .getResourceAsStream(path); - - Properties properties = new Properties(); - properties.load(stream); - return Class.forName(properties.getProperty("implement-class")); - } catch (Exception e) { - return null; - } - } - - public boolean unregisterBinary(String name) { - return mBinaryClasses.remove(name) != null; - } - - -} diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcess.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcess.java index a1640d96..11045fe0 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcess.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcess.java @@ -104,12 +104,10 @@ public void destroy() throws IOException { } } - public void killProcess() { - try { - destroy(); - } catch (IOException e) { - e.printStackTrace(); - } + public void killProcess() throws Exception { + + destroy(); + } public void setProcessEnvironment(VirtualProcessEnvironment processChannel) { @@ -131,9 +129,9 @@ public void start() { .setCurrentWorkDir(cwd); processChannel.putEnvironments(env); processChannel.setArguments(args); - VirtualExecutable binary = VirtualExecutableSystem.getInstance().createBinary(cmd, processChannel); + VirtualExecutable binary = VirtualExecutableService.getInstance().createExecutable(cmd, processChannel); if (binary == null) { - throw new RuntimeException("Can't find binary"); + throw new RuntimeException("Can't find Executable for " + cmd); } binaryExecutor = new VirtualExecutableExecutor( binary, this diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessEnvironment.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessEnvironment.java index 4d902b60..940799be 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessEnvironment.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessEnvironment.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -12,6 +14,7 @@ public class VirtualProcessEnvironment { InputStream processInputStream; OutputStream processOutputStream; + OutputStream processErrorStream; @@ -27,6 +30,8 @@ public class VirtualProcessEnvironment { Integer exitValue = null; + private ByteArrayBuffer readBuffer = new ByteArrayBuffer(1024); + public VirtualProcessEnvironment(InputStream processInputStream, OutputStream processOutputStream, OutputStream processErrorStream) { this.processInputStream = processInputStream; this.processOutputStream = processOutputStream; @@ -124,6 +129,7 @@ public void destroy() throws IOException { processInputStream.close(); processOutputStream.close(); processErrorStream.close(); + } public InputStream getInputStream() { @@ -139,24 +145,40 @@ public OutputStream getErrorStream() { } - public void write(byte[] b) throws IOException { - processOutputStream.write(b); - processOutputStream.flush(); + public void print(String s) throws IOException { + processOutputStream.write(s.getBytes(StandardCharsets.UTF_8)); + flush(); } - void writeError(byte[] b) throws IOException { - processErrorStream.write(b); + public void println(String s) throws IOException { + processOutputStream.write(s.getBytes(StandardCharsets.UTF_8)); + processOutputStream.write('\n'); + flush(); } - public void flush() throws IOException { - processOutputStream.flush(); - } + public String readLine() throws IOException { - void read(byte[] b) throws IOException { - processInputStream.read(b); + while (true) { + int b = processInputStream.read(); + if (b == -1) { + return null; + } + if (b == '\n') { + break; + } + readBuffer.append((byte) b); + } + String line = new String(readBuffer.toByteArray()); + readBuffer.clear(); + return line; } + public void flush() throws IOException { + processOutputStream.flush(); + processErrorStream.flush(); + } + /** * This method only set the exit value of the process. * And not destroy the process. diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessSystem.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessService.java similarity index 89% rename from virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessSystem.java rename to virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessService.java index 4e941391..f60a8927 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessSystem.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualProcessService.java @@ -5,17 +5,17 @@ import java.io.IOException; -public class VirtualProcessSystem { +public class VirtualProcessService { - private static VirtualProcessSystem INSTANCE; + private static VirtualProcessService INSTANCE; - private VirtualProcessSystem() { + private VirtualProcessService() { } - public static VirtualProcessSystem getInstance() { - synchronized (VirtualProcessSystem.class) { + public static VirtualProcessService getInstance() { + synchronized (VirtualProcessService.class) { if (INSTANCE == null) { - INSTANCE = new VirtualProcessSystem(); + INSTANCE = new VirtualProcessService(); } } return INSTANCE; @@ -129,14 +129,14 @@ public void waitForImpl(int processId) throws InterruptedException { } } - public static boolean killProcess(int processId) { + public static boolean killProcess(int processId) throws Exception { return getInstance().killProcessFor(processId); } - public boolean killProcessFor(int processId) { + public boolean killProcessFor(int processId) throws Exception { VirtualProcess process = mProcesses.get(processId); try { - process.destroy(); + process.killProcess(); } catch (IOException e) { return false; } diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualTerminalEnvironment.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualTerminalEnvironment.java index 407c1940..9989a45c 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualTerminalEnvironment.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/VirtualTerminalEnvironment.java @@ -1,56 +1,44 @@ package com.dingyi.terminal.virtualprocess; -import android.graphics.BlendModeColorFilter; -import android.icu.util.Output; - -import com.dingyi.terminal.virtualprocess.VirtualProcessEnvironment; -import com.dingyi.terminal.virtualprocess.stream.QueueInputStream; -import com.dingyi.terminal.virtualprocess.stream.QueueOutputStream; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.DelayQueue; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.SynchronousQueue; public class VirtualTerminalEnvironment { - final QueueInputStream terminalInputStream; - final QueueOutputStream terminalOutputStream; - final QueueInputStream terminalErrorStream; + final ByteQueueInputStream terminalInputStream; + OutputStream terminalOutputStream; + final ByteQueueInputStream terminalErrorStream; - final BlockingQueue processInputQueue; - final BlockingQueue terminalInputQueue; - final BlockingQueue terminalErrorQueue; + final ByteQueue processInputQueue; + final ByteQueue terminalInputQueue; + final ByteQueue terminalErrorQueue; VirtualProcessEnvironment processChannel; public VirtualTerminalEnvironment() throws IOException { - processInputQueue = new LinkedBlockingQueue<>(); - terminalInputQueue = new LinkedBlockingQueue<>(); - terminalErrorQueue = new LinkedBlockingQueue<>(); - - terminalErrorStream = new QueueInputStream(terminalErrorQueue); + processInputQueue = new ByteQueue(4096); + terminalInputQueue = new ByteQueue(4096); + terminalErrorQueue = new ByteQueue(4096); - terminalInputStream = new QueueInputStream(terminalInputQueue); + terminalErrorStream = new ByteQueueInputStream(); - terminalOutputStream = new QueueOutputStream(processInputQueue); + terminalInputStream = new ByteQueueInputStream(); - QueueInputStream processInputStream = terminalOutputStream.newQueueInputStream(); - QueueOutputStream processOutputStream = terminalInputStream.newQueueOutputStream(); - QueueOutputStream processErrorStream = terminalErrorStream.newQueueOutputStream(); + ByteQueueOutputStream _terminalOutputStream = new ByteQueueOutputStream(); + terminalOutputStream = _terminalOutputStream; + ByteQueueInputStream processInputStream = _terminalOutputStream.newQueueInputStream(); + ByteQueueOutputStream processOutputStream = terminalInputStream.newQueueOutputStream(); + ByteQueueOutputStream processErrorStream = terminalErrorStream.newQueueOutputStream(); processChannel = new VirtualProcessEnvironment(processInputStream, processOutputStream, processErrorStream); SimpleTermiosSupport termiosSupport = new SimpleTermiosSupport(this, processChannel); - //termiosSupport.doWrapper(); + termiosSupport.doWrapper(); processChannel.termiosSupport = termiosSupport; diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/EchoSupport.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/EchoSupport.java index 360638e4..d46a6388 100644 --- a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/EchoSupport.java +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/EchoSupport.java @@ -15,18 +15,10 @@ public EchoSupport(VirtualProcessEnvironment currentProcess) { protected int start(String[] args) throws IOException { for (String a : args) { mProcessEnvironment - .getOutputStream() - .write(a.getBytes(StandardCharsets.UTF_8)); - - mProcessEnvironment - .getOutputStream() - .write(" ".getBytes(StandardCharsets.UTF_8)); - mProcessEnvironment - .getOutputStream() - .flush(); + .print(a + " "); } mProcessEnvironment - .write("\n".getBytes(StandardCharsets.UTF_8)); + .print("\n"); return 0; } } diff --git a/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/ShellSupport.java b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/ShellSupport.java new file mode 100644 index 00000000..ff42504c --- /dev/null +++ b/virtual-process/src/main/java/com/dingyi/terminal/virtualprocess/lib/ShellSupport.java @@ -0,0 +1,28 @@ +package com.dingyi.terminal.virtualprocess.lib; + +import com.dingyi.terminal.virtualprocess.VirtualExecutable; +import com.dingyi.terminal.virtualprocess.VirtualProcessEnvironment; + +public class ShellSupport extends VirtualExecutable { + + + public ShellSupport(VirtualProcessEnvironment processChannel) { + super(processChannel); + } + + @Override + protected int start(String[] args) throws Exception { + + while (true) { + mProcessEnvironment + .print("$ MyShell> "); + String line = mProcessEnvironment.readLine(); + if (line == null) { + break; + } + System.out.println(line); + } + + return 0; + } +} diff --git a/virtual-process/src/main/resources/META-INF/shell-lib/echo.properties b/virtual-process/src/main/resources/META-INF/shell-lib/echo.properties deleted file mode 100644 index b0252fd2..00000000 --- a/virtual-process/src/main/resources/META-INF/shell-lib/echo.properties +++ /dev/null @@ -1 +0,0 @@ -implement-class=com.dingyi.terminal.virtual.lib.EchoSupport \ No newline at end of file diff --git a/virtual-process/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support b/virtual-process/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support new file mode 100644 index 00000000..2b0c9494 --- /dev/null +++ b/virtual-process/src/main/resources/META-INF/shell-plugins/com.dingyi.terminal.support @@ -0,0 +1,2 @@ +echo:com.dingyi.terminal.virtualprocess.lib.EchoSupport +shell:com.dingyi.terminal.virtualprocess.lib.ShellSupport \ No newline at end of file