diff --git a/app/build.gradle b/app/build.gradle index 854eacf..63ddbfe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "io.neoterm" minSdkVersion 21 targetSdkVersion 25 - versionCode 13 - versionName "1.2.0-rc2" + versionCode 14 + versionName "1.2.0-rc3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" resConfigs "zh" externalNativeBuild { diff --git a/app/src/main/java/io/neoterm/backend/TerminalSession.java b/app/src/main/java/io/neoterm/backend/TerminalSession.java index a3da597..0969a43 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalSession.java +++ b/app/src/main/java/io/neoterm/backend/TerminalSession.java @@ -28,7 +28,7 @@ import java.util.UUID; *

* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks! */ -public final class TerminalSession extends TerminalOutput { +public class TerminalSession extends TerminalOutput { /** Callback to be invoked when a {@link TerminalSession} changes. */ public interface SessionChangedCallback { @@ -143,18 +143,16 @@ public final class TerminalSession extends TerminalOutput { private final String mShellPath; private final String mCwd; - private final String mInitialCommand; private final String[] mArgs; private final String[] mEnv; - public TerminalSession(String shellPath, String cwd, String initialCommand, String[] args, String[] env, SessionChangedCallback changeCallback) { + public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) { mChangeCallback = changeCallback; this.mShellPath = shellPath; this.mCwd = cwd; this.mArgs = args; this.mEnv = env; - this.mInitialCommand = initialCommand; } /** Inform the attached pty of the new size and reflow or initialize the emulator. */ @@ -227,10 +225,6 @@ public final class TerminalSession extends TerminalOutput { mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode)); } }.start(); - - if (mInitialCommand != null && mInitialCommand.length() > 0) { - write(mInitialCommand + '\r'); - } } /** Write data to the shell process. */ diff --git a/app/src/main/java/io/neoterm/preference/NeoPreference.kt b/app/src/main/java/io/neoterm/preference/NeoPreference.kt index c355490..6b9913e 100644 --- a/app/src/main/java/io/neoterm/preference/NeoPreference.kt +++ b/app/src/main/java/io/neoterm/preference/NeoPreference.kt @@ -96,67 +96,6 @@ object NeoPreference { return null } - fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array { - var cwd = cwd - File(NeoTermPath.HOME_PATH).mkdirs() - - if (cwd == null) cwd = NeoTermPath.HOME_PATH - - val termEnv = "TERM=xterm-256color" - val homeEnv = "HOME=" + NeoTermPath.HOME_PATH - val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT") - val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA") - val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE") - - if (systemShell) { - val pathEnv = "PATH=" + System.getenv("PATH") - return arrayOf(termEnv, homeEnv, androidRootEnv, androidDataEnv, externalStorageEnv, pathEnv) - - } else { - val ps1Env = "PS1=$ " - val langEnv = "LANG=en_US.UTF-8" - val pathEnv = "PATH=" + buildPathEnv() - val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv() - val pwdEnv = "PWD=" + cwd - val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp" - - return arrayOf(termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv) - } - } - - private fun buildLdLibraryEnv(): String { - val builder = StringBuilder("${NeoTermPath.USR_PATH}/lib") - - val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, VALUE_NEOTERM_ONLY) - val systemPath = System.getenv("LD_LIBRARY_PATH") - - if (programSelection != VALUE_NEOTERM_ONLY) { - builder.append(":$systemPath") - } - - return builder.toString() - } - - private fun buildPathEnv(): String { - val builder = StringBuilder() - val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, VALUE_NEOTERM_ONLY) - val basePath = "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets" - val systemPath = System.getenv("PATH") - - when (programSelection) { - VALUE_NEOTERM_ONLY -> { - builder.append(basePath) - } - VALUE_NEOTERM_FIRST -> { - builder.append("$basePath:$systemPath") - } - VALUE_SYSTEM_FIRST -> { - builder.append("$systemPath:$basePath") - } - } - return builder.toString() - } - /** * TODO * To print the job name about to be executed in bash: diff --git a/app/src/main/java/io/neoterm/services/NeoTermService.kt b/app/src/main/java/io/neoterm/services/NeoTermService.kt index a0b5c31..7afa37a 100644 --- a/app/src/main/java/io/neoterm/services/NeoTermService.kt +++ b/app/src/main/java/io/neoterm/services/NeoTermService.kt @@ -76,8 +76,11 @@ class NeoTermService : Service() { val sessions: List get() = mTerminalSessions - fun createTermSession(executablePath: String?, arguments: Array?, cwd: String?, initialCommand: String?, env: Array?, sessionCallback: TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession { - val session = TerminalUtils.createSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell) + fun createTermSession(executablePath: String?, arguments: Array?, + cwd: String?, initialCommand: String?, + env: Array>?, sessionCallback: + TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession { + val session = TerminalUtils.createShellSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell) mTerminalSessions.add(session) updateNotification() return session diff --git a/app/src/main/java/io/neoterm/terminal/ShellTermSession.kt b/app/src/main/java/io/neoterm/terminal/ShellTermSession.kt new file mode 100644 index 0000000..ab15adc --- /dev/null +++ b/app/src/main/java/io/neoterm/terminal/ShellTermSession.kt @@ -0,0 +1,199 @@ +package io.neoterm.terminal + +import android.content.Context +import android.widget.Toast +import io.neoterm.R +import io.neoterm.backend.TerminalSession +import io.neoterm.preference.NeoPreference +import io.neoterm.preference.NeoTermPath +import io.neoterm.terminal.client.TermSessionCallback +import java.io.File + +/** + * @author kiva + */ +open class ShellTermSession private constructor(shellPath: String, cwd: String, args: Array, env: Array, changeCallback: SessionChangedCallback) : TerminalSession(shellPath, cwd, args, env, changeCallback) { + var initialCommand : String? = null + + override fun initializeEmulator(columns: Int, rows: Int) { + super.initializeEmulator(columns, rows) + + val initCommand = initialCommand + if (initCommand != null && initCommand.isNotEmpty()) { + write(initCommand + '\r') + } + } + + class Builder { + private var shell: String? = null + private var cwd: String? = null + private var args: MutableList? = null + private var env: MutableList>? = null + private var changeCallback: SessionChangedCallback? = null + private var systemShell = false + + fun shell(shell: String?): Builder { + this.shell = shell + return this + } + + fun currentWorkingDirectory(cwd: String?): Builder { + this.cwd = cwd + return this + } + + fun arg(arg: String?): Builder { + if (arg != null) { + if (args == null) { + args = mutableListOf(arg) + } else { + args!!.add(arg) + } + } else { + this.args = null + } + return this + } + + fun argArray(args: Array?): Builder { + if (args != null) { + if (args.isEmpty()) { + this.args = null + return this + } + args.forEach { arg(it) } + } else { + this.args = null + } + return this + } + + fun env(env: Pair?): Builder { + if (env != null) { + if (this.env == null) { + this.env = mutableListOf(env) + } else { + this.env!!.add(env) + } + } else { + this.env = null + } + return this + } + + fun envArray(env: Array>?): Builder { + if (env != null) { + if (env.isEmpty()) { + this.env = null + return this + } + env.forEach { env(it) } + } else { + this.env = null + } + return this + } + + fun callback(callback: SessionChangedCallback?): Builder { + this.changeCallback = callback + return this + } + + fun systemShell(systemShell: Boolean): Builder { + this.systemShell = systemShell + return this + } + + fun create(context: Context): ShellTermSession { + val cwd = this.cwd ?: NeoTermPath.HOME_PATH + + var shell = this.shell ?: + if (systemShell) + "/system/bin/sh" + else + NeoTermPath.USR_PATH + "/bin/" + NeoPreference.loadString(R.string.key_general_shell, "sh") + + if (!File(shell).exists()) { + Toast.makeText(context, context.getString(R.string.shell_not_found, shell), Toast.LENGTH_LONG).show() + shell = NeoTermPath.USR_PATH + "/bin/sh" + } + + val args = this.args ?: mutableListOf(shell) + val env = transformEnvironment(this.env) ?: buildEnvironment(cwd, systemShell, shell) + val callback = changeCallback ?: TermSessionCallback() + return ShellTermSession(shell, cwd, args.toTypedArray(), env, callback) + } + + private fun transformEnvironment(env: MutableList>?): Array? { + if (env == null) { + return null + } + + val result = mutableListOf() + env.mapTo(result, { "${it.first}=${it.second}" }) + return result.toTypedArray() + } + + + private fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array { + var cwd = cwd + File(NeoTermPath.HOME_PATH).mkdirs() + + if (cwd == null) cwd = NeoTermPath.HOME_PATH + + val termEnv = "TERM=xterm-256color" + val homeEnv = "HOME=" + NeoTermPath.HOME_PATH + val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT") + val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA") + val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE") + + if (systemShell) { + val pathEnv = "PATH=" + System.getenv("PATH") + return arrayOf(termEnv, homeEnv, androidRootEnv, androidDataEnv, externalStorageEnv, pathEnv) + + } else { + val ps1Env = "PS1=$ " + val langEnv = "LANG=en_US.UTF-8" + val pathEnv = "PATH=" + buildPathEnv() + val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv() + val pwdEnv = "PWD=" + cwd + val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp" + + return arrayOf(termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv) + } + } + + private fun buildLdLibraryEnv(): String { + val builder = StringBuilder("${NeoTermPath.USR_PATH}/lib") + + val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, NeoPreference.VALUE_NEOTERM_ONLY) + val systemPath = System.getenv("LD_LIBRARY_PATH") + + if (programSelection != NeoPreference.VALUE_NEOTERM_ONLY) { + builder.append(":$systemPath") + } + + return builder.toString() + } + + private fun buildPathEnv(): String { + val builder = StringBuilder() + val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, NeoPreference.VALUE_NEOTERM_ONLY) + val basePath = "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets" + val systemPath = System.getenv("PATH") + + when (programSelection) { + NeoPreference.VALUE_NEOTERM_ONLY -> { + builder.append(basePath) + } + NeoPreference.VALUE_NEOTERM_FIRST -> { + builder.append("$basePath:$systemPath") + } + NeoPreference.VALUE_SYSTEM_FIRST -> { + builder.append("$systemPath:$basePath") + } + } + return builder.toString() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermCompleteListener.kt b/app/src/main/java/io/neoterm/terminal/client/TermCompleteListener.kt similarity index 98% rename from app/src/main/java/io/neoterm/ui/term/tab/TermCompleteListener.kt rename to app/src/main/java/io/neoterm/terminal/client/TermCompleteListener.kt index cf922b1..93699fd 100644 --- a/app/src/main/java/io/neoterm/ui/term/tab/TermCompleteListener.kt +++ b/app/src/main/java/io/neoterm/terminal/client/TermCompleteListener.kt @@ -1,4 +1,4 @@ -package io.neoterm.ui.term.tab +package io.neoterm.terminal.client import android.util.Log import android.view.KeyEvent diff --git a/app/src/main/java/io/neoterm/terminal/client/TermDataHolder.kt b/app/src/main/java/io/neoterm/terminal/client/TermDataHolder.kt new file mode 100644 index 0000000..74c67e3 --- /dev/null +++ b/app/src/main/java/io/neoterm/terminal/client/TermDataHolder.kt @@ -0,0 +1,47 @@ +package io.neoterm.terminal.client + +import io.neoterm.backend.TerminalSession +import io.neoterm.view.ExtraKeysView +import io.neoterm.view.OnAutoCompleteListener +import io.neoterm.view.TerminalView + +/** + * @author kiva + */ +class TermDataHolder { + var termSession: TerminalSession? = null + var sessionCallback: TermSessionCallback? = null + var viewClient: TermViewClient? = null + var onAutoCompleteListener: OnAutoCompleteListener? = null + + var termUI: TermUiPresenter? = null + var termView: TerminalView? = null + var extraKeysView: ExtraKeysView? = null + + fun cleanup() { + onAutoCompleteListener?.onCleanUp() + onAutoCompleteListener = null + + sessionCallback?.termData = null + viewClient?.termData = null + + termUI = null + termView = null + extraKeysView = null + termSession = null + } + + fun initializeSessionWith(session: TerminalSession, sessionCallback: TermSessionCallback?, viewClient: TermViewClient?) { + this.termSession = session + this.sessionCallback = sessionCallback + this.viewClient = viewClient + sessionCallback?.termData = this + viewClient?.termData = this + } + + fun initializeViewWith(termUI: TermUiPresenter?, termView: TerminalView?, eks: ExtraKeysView?) { + this.termUI = termUI + this.termView = termView + this.extraKeysView = eks + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermSessionCallback.kt b/app/src/main/java/io/neoterm/terminal/client/TermSessionCallback.kt similarity index 73% rename from app/src/main/java/io/neoterm/ui/term/tab/TermSessionCallback.kt rename to app/src/main/java/io/neoterm/terminal/client/TermSessionCallback.kt index 7942dbd..5c675b4 100644 --- a/app/src/main/java/io/neoterm/ui/term/tab/TermSessionCallback.kt +++ b/app/src/main/java/io/neoterm/terminal/client/TermSessionCallback.kt @@ -1,4 +1,4 @@ -package io.neoterm.ui.term.tab +package io.neoterm.terminal.client import android.content.ClipData import android.content.ClipboardManager @@ -8,43 +8,40 @@ import android.os.Vibrator import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.preference.NeoPreference -import io.neoterm.view.TerminalView /** * @author kiva */ class TermSessionCallback : TerminalSession.SessionChangedCallback { - var termView: TerminalView? = null - var termTab: TermTab? = null + var termData: TermDataHolder? = null var bellId: Int = 0 var soundPool: SoundPool? = null override fun onTextChanged(changedSession: TerminalSession?) { - termView?.onScreenUpdated() + termData?.termView?.onScreenUpdated() } override fun onTitleChanged(changedSession: TerminalSession?) { if (changedSession?.title != null) { - termTab?.updateTitle(changedSession.title) + termData?.termUI?.requireUpdateTitle(changedSession.title) } } override fun onSessionFinished(finishedSession: TerminalSession?) { - termTab?.onSessionFinished() + termData?.termUI?.requireOnSessionFinished() } override fun onClipboardText(session: TerminalSession?, text: String?) { + val termView = termData?.termView if (termView != null) { - val clipboard = termView!!.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboard = termView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager clipboard.primaryClip = ClipData.newPlainText("", text) } } override fun onBell(session: TerminalSession?) { - if (termView == null) { - return - } + val termView = termData?.termView ?: return if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) { if (soundPool == null) { @@ -61,8 +58,9 @@ class TermSessionCallback : TerminalSession.SessionChangedCallback { } override fun onColorsChanged(session: TerminalSession?) { - if (session != null) { - termView?.onScreenUpdated() + val termView = termData?.termView + if (session != null && termView != null) { + termView.onScreenUpdated() } } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/terminal/client/TermUiPresenter.kt b/app/src/main/java/io/neoterm/terminal/client/TermUiPresenter.kt new file mode 100644 index 0000000..ba2acec --- /dev/null +++ b/app/src/main/java/io/neoterm/terminal/client/TermUiPresenter.kt @@ -0,0 +1,12 @@ +package io.neoterm.terminal.client + +/** + * @author kiva + */ +interface TermUiPresenter { + fun requireClose() + fun requireToggleFullScreen() + fun requirePaste() + fun requireUpdateTitle(title: String?) + fun requireOnSessionFinished() +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt b/app/src/main/java/io/neoterm/terminal/client/TermViewClient.kt similarity index 84% rename from app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt rename to app/src/main/java/io/neoterm/terminal/client/TermViewClient.kt index a420144..7b67c2f 100644 --- a/app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt +++ b/app/src/main/java/io/neoterm/terminal/client/TermViewClient.kt @@ -1,4 +1,4 @@ -package io.neoterm.ui.term.tab +package io.neoterm.terminal.client import android.content.Context import android.media.AudioManager @@ -11,8 +11,6 @@ import io.neoterm.backend.KeyHandler import io.neoterm.backend.TerminalSession import io.neoterm.customize.eks.EksKeysManager import io.neoterm.preference.NeoPreference -import io.neoterm.view.ExtraKeysView -import io.neoterm.view.TerminalView import io.neoterm.view.TerminalViewClient @@ -26,9 +24,7 @@ class TermViewClient(val context: Context) : TerminalViewClient { var sessionFinished: Boolean = false - var termTab: TermTab? = null - var termView: TerminalView? = null - var extraKeysView: ExtraKeysView? = null + var termData: TermDataHolder? = null override fun onScale(scale: Float): Float { if (scale < 0.9f || scale > 1.1f) { @@ -40,8 +36,11 @@ class TermViewClient(val context: Context) : TerminalViewClient { } override fun onSingleTapUp(e: MotionEvent?) { - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) + val termView = termData?.termView + if (termView != null) { + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) + } } override fun shouldBackButtonBeMappedToEscape(): Boolean { @@ -53,10 +52,12 @@ class TermViewClient(val context: Context) : TerminalViewClient { } override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { + val termUI = termData?.termUI + when (keyCode) { KeyEvent.KEYCODE_ENTER -> { if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) { - termTab?.requireCloseTab() + termUI?.requireClose() return true } return false @@ -67,9 +68,9 @@ class TermViewClient(val context: Context) : TerminalViewClient { val unicodeChar = e.getUnicodeChar(0).toChar() if (unicodeChar == 'f'/* full screen */) { - termTab?.requireToggleFullScreen() + termUI?.requireToggleFullScreen() } else if (unicodeChar == 'v') { - termTab?.requirePaste() + termUI?.requirePaste() } else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON).toChar() == '+') { // We also check for the shifted char here since shift may be required to produce '+', // see https://github.com/termux/termux-api/issues/2 @@ -86,11 +87,13 @@ class TermViewClient(val context: Context) : TerminalViewClient { } override fun readControlKey(): Boolean { - return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown + val extraKeysView = termData?.extraKeysView + return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown } override fun readAltKey(): Boolean { - return (extraKeysView != null && extraKeysView!!.readAltButton()) || mVirtualFnKeyDown + val extraKeysView = termData?.extraKeysView + return (extraKeysView != null && extraKeysView.readAltButton()) || mVirtualFnKeyDown } override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { @@ -179,6 +182,8 @@ class TermViewClient(val context: Context) : TerminalViewClient { } fun updateSuggestions(title: String?, force: Boolean = false) { + val extraKeysView = termData?.extraKeysView + if (extraKeysView == null || title == null || title.isEmpty()) { return } @@ -192,13 +197,17 @@ class TermViewClient(val context: Context) : TerminalViewClient { } fun removeSuggestions() { + val extraKeysView = termData?.extraKeysView extraKeysView?.clearUserDefinedButton() } private fun changeFontSize(increase: Boolean) { - val changedSize = (if (increase) 1 else -1) * 2 - val fontSize = termView!!.textSize + changedSize - termView!!.textSize = fontSize - NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) + val termView = termData?.termView + if (termView != null) { + val changedSize = (if (increase) 1 else -1) * 2 + val fontSize = termView.textSize + changedSize + termView.textSize = fontSize + NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) + } } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/customization/CustomizationActivity.kt b/app/src/main/java/io/neoterm/ui/customization/CustomizationActivity.kt index f5a610c..66261ff 100644 --- a/app/src/main/java/io/neoterm/ui/customization/CustomizationActivity.kt +++ b/app/src/main/java/io/neoterm/ui/customization/CustomizationActivity.kt @@ -2,7 +2,6 @@ package io.neoterm.ui.customization import android.app.Activity import android.content.Intent -import android.graphics.Typeface import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar @@ -57,7 +56,7 @@ class CustomizationActivity : AppCompatActivity() { viewClient = BasicViewClient(terminalView) sessionCallback = BasicSessionCallback(terminalView) TerminalUtils.setupTerminalView(terminalView, viewClient) - session = TerminalUtils.createSession(this, "${NeoTermPath.USR_PATH}/bin/applets/echo", + session = TerminalUtils.createShellSession(this, "${NeoTermPath.USR_PATH}/bin/applets/echo", arrayOf("echo", "Hello NeoTerm."), null, null, null, sessionCallback, false) terminalView.attachSession(session) diff --git a/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt b/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt index a546089..ae927f3 100644 --- a/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt +++ b/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt @@ -97,6 +97,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen dialog.setTitle(getString(R.string.done)) } }) + .imeEnabled(true) .show("Installing $packageName") } } @@ -185,6 +186,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen } }) .execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update")) + .imeEnabled(true) .show("apt update") } diff --git a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt index 95d9e83..bd5472a 100644 --- a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt +++ b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.support.v4.content.ContextCompat import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity -import android.view.View import android.widget.Button import android.widget.Toast import com.igalata.bubblepicker.BubblePickerListener @@ -22,6 +21,7 @@ import io.neoterm.customize.setup.BaseFileInstaller import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoTermPath import io.neoterm.utils.PackageUtils +import io.neoterm.utils.TerminalUtils import io.neoterm.view.TerminalDialog import java.util.* @@ -38,7 +38,6 @@ class SetupActivity : AppCompatActivity() { } lateinit var picker: BubblePicker - lateinit var nextButton: Button lateinit var toast: Toast var aptUpdated = false @@ -46,7 +45,7 @@ class SetupActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.ui_setup) picker = findViewById(R.id.bubble_picker) as BubblePicker - nextButton = findViewById(R.id.setup_next) as Button + val nextButton = findViewById(R.id.setup_next) as Button nextButton.setOnClickListener { if (aptUpdated) { val packageList = mutableListOf("apt", "install", "-y") @@ -101,7 +100,6 @@ class SetupActivity : AppCompatActivity() { .setMessage(error.toString()) .setNegativeButton(R.string.use_system_shell, { _, _ -> setResult(Activity.RESULT_CANCELED) - nextButton.visibility = View.VISIBLE finish() }) .setPositiveButton(R.string.retry, { dialog, _ -> @@ -114,21 +112,25 @@ class SetupActivity : AppCompatActivity() { } private fun executeAptUpdate() { - TerminalDialog(this) - .onFinish(object : TerminalDialog.SessionFinishedCallback { - override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) { - nextButton.visibility = View.VISIBLE - val exit = finishedSession?.exitStatus ?: 1 - if (exit == 0) { - dialog.dismiss() - aptUpdated = true - } else { - dialog.setTitle(getString(R.string.error)) - } - } - }) - .execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update")) - .show("apt update") + TerminalUtils.executeApt(this, "update", { exitStatus, dialog -> + if (exitStatus == 0) { + dialog.dismiss() + aptUpdated = true + executeAptUpgrade() + } else { + dialog.setTitle(getString(R.string.error)) + } + }) + } + + private fun executeAptUpgrade() { + TerminalUtils.executeApt(this, "upgrade", { exitStatus, dialog -> + if (exitStatus == 0) { + dialog.dismiss() + } else { + dialog.setTitle(getString(R.string.error)) + } + }) } @SuppressLint("ShowToast") diff --git a/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt b/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt index a03507f..3a3273c 100644 --- a/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt +++ b/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt @@ -33,10 +33,10 @@ import io.neoterm.ui.bonus.BonusActivity import io.neoterm.ui.pm.PackageManagerActivity import io.neoterm.ui.settings.SettingActivity import io.neoterm.ui.setup.SetupActivity -import io.neoterm.ui.term.tab.TermSessionCallback +import io.neoterm.terminal.client.TermSessionCallback import io.neoterm.ui.term.tab.TermTab import io.neoterm.ui.term.tab.TermTabDecorator -import io.neoterm.ui.term.tab.TermViewClient +import io.neoterm.terminal.client.TermViewClient import io.neoterm.ui.term.tab.event.TabCloseEvent import io.neoterm.ui.term.tab.event.TitleChangedEvent import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent @@ -189,8 +189,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) { - if (selectedTab is TermTab && selectedTab.termSession != null) { - NeoPreference.storeCurrentSession(selectedTab.termSession!!) + if (selectedTab is TermTab && selectedTab.termData.termSession != null) { + NeoPreference.storeCurrentSession(selectedTab.termData.termSession!!) } } @@ -199,8 +199,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference override fun onTabRemoved(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) { if (tab is TermTab) { - tab.termSession?.finishIfRunning() - removeFinishedSession(tab.termSession) + tab.termData.termSession?.finishIfRunning() + removeFinishedSession(tab.termData.termSession) tab.cleanup() } } @@ -387,29 +387,32 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference val tabCount = tabSwitcher.count (0..(tabCount - 1)) .map { tabSwitcher.getTab(it) } - .filter { it is TermTab && it.termSession == session } + .filter { it is TermTab && it.termData.termSession == session } .forEach { return } + val sessionCallback = session.sessionChangedCallback as TermSessionCallback + val viewClient = TermViewClient(this) + val tab = createTab(session.title) as TermTab - tab.sessionCallback = session.sessionChangedCallback as TermSessionCallback - tab.viewClient = TermViewClient(this) - tab.termSession = session + tab.termData.initializeSessionWith(session, sessionCallback, viewClient) addNewTab(tab, createRevealAnimation()) switchToSession(tab) } private fun addNewSession(sessionName: String?, systemShell: Boolean, animation: Animation) { - val tab = createTab(sessionName) as TermTab - tab.sessionCallback = TermSessionCallback() - tab.viewClient = TermViewClient(this) - tab.termSession = termService!!.createTermSession(null, null, - null, null, null, tab.sessionCallback, systemShell) + val sessionCallback = TermSessionCallback() + val viewClient = TermViewClient(this) + val session = termService!!.createTermSession(null, null, + null, null, null, sessionCallback, systemShell) if (sessionName != null) { - tab.termSession!!.mSessionName = sessionName + session.mSessionName = sessionName } + val tab = createTab(sessionName) as TermTab + tab.termData.initializeSessionWith(session, sessionCallback, viewClient) + addNewTab(tab, animation) switchToSession(tab) } @@ -421,7 +424,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference for (i in 0..tabSwitcher.count - 1) { val tab = tabSwitcher.getTab(i) - if (tab is TermTab && tab.termSession == session) { + if (tab is TermTab && tab.termData.termSession == session) { switchToSession(tab) break } diff --git a/app/src/main/java/io/neoterm/ui/term/NeoTermRemoteInterface.kt b/app/src/main/java/io/neoterm/ui/term/NeoTermRemoteInterface.kt index ad644ec..9b3f92e 100644 --- a/app/src/main/java/io/neoterm/ui/term/NeoTermRemoteInterface.kt +++ b/app/src/main/java/io/neoterm/ui/term/NeoTermRemoteInterface.kt @@ -16,7 +16,7 @@ import io.neoterm.customize.script.UserScript import io.neoterm.customize.script.UserScriptManager import io.neoterm.preference.NeoPreference import io.neoterm.services.NeoTermService -import io.neoterm.ui.term.tab.TermSessionCallback +import io.neoterm.terminal.client.TermSessionCallback import io.neoterm.utils.TerminalUtils import java.io.File diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt index 52b8ae2..e33e7a1 100644 --- a/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt @@ -5,58 +5,31 @@ import android.support.v7.widget.Toolbar import android.view.inputmethod.InputMethodManager import de.mrapp.android.tabswitcher.Tab import io.neoterm.R -import io.neoterm.backend.TerminalSession import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.preference.NeoPreference +import io.neoterm.terminal.client.TermDataHolder +import io.neoterm.terminal.client.TermUiPresenter import io.neoterm.ui.term.tab.event.TabCloseEvent import io.neoterm.ui.term.tab.event.TitleChangedEvent import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent -import io.neoterm.view.OnAutoCompleteListener import org.greenrobot.eventbus.EventBus /** * @author kiva */ -class TermTab(title: CharSequence) : Tab(title) { - var termSession: TerminalSession? = null - var sessionCallback: TermSessionCallback? = null - var viewClient: TermViewClient? = null - var onAutoCompleteListener: OnAutoCompleteListener? = null +class TermTab(title: CharSequence) : Tab(title), TermUiPresenter { + var termData = TermDataHolder() var toolbar: Toolbar? = null fun updateColorScheme() { - ColorSchemeManager.applyColorScheme(viewClient?.termView, viewClient?.extraKeysView, + ColorSchemeManager.applyColorScheme(termData.termView, termData.extraKeysView, ColorSchemeManager.getCurrentColorScheme()) } fun cleanup() { - onAutoCompleteListener?.onCleanUp() - onAutoCompleteListener = null - - viewClient?.termTab = null - viewClient?.termView = null - viewClient?.extraKeysView = null - sessionCallback?.termView = null - sessionCallback?.termTab = null + termData.cleanup() toolbar = null - termSession = null - } - - fun updateTitle(title: String) { - if (title.isNotEmpty()) { - this.title = title - EventBus.getDefault().post(TitleChangedEvent(title)) - if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) { - viewClient?.updateSuggestions(title) - } else { - viewClient?.removeSuggestions() - } - } - } - - fun onSessionFinished() { - viewClient?.sessionFinished = true } fun onFullScreenModeChanged(fullScreen: Boolean) { @@ -64,13 +37,8 @@ class TermTab(title: CharSequence) : Tab(title) { resetAutoCompleteStatus() } - fun requireCloseTab() { - requireHideIme() - EventBus.getDefault().post(TabCloseEvent(this)) - } - fun requireHideIme() { - val terminalView = viewClient?.termView + val terminalView = termData.termView if (terminalView != null) { val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager if (imm.isActive) { @@ -79,16 +47,37 @@ class TermTab(title: CharSequence) : Tab(title) { } } - fun requireToggleFullScreen() { + override fun requireToggleFullScreen() { EventBus.getDefault().post(ToggleFullScreenEvent()) } - fun requirePaste() { - viewClient?.termView?.pasteFromClipboard() + override fun requirePaste() { + termData.termView?.pasteFromClipboard() + } + + override fun requireClose() { + requireHideIme() + EventBus.getDefault().post(TabCloseEvent(this)) + } + + override fun requireUpdateTitle(title: String?) { + if (title != null && title.isNotEmpty()) { + this.title = title + EventBus.getDefault().post(TitleChangedEvent(title)) + if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) { + termData.viewClient?.updateSuggestions(title) + } else { + termData.viewClient?.removeSuggestions() + } + } + } + + override fun requireOnSessionFinished() { + termData.viewClient?.sessionFinished = true } fun resetAutoCompleteStatus() { - onAutoCompleteListener?.onCleanUp() - onAutoCompleteListener = null + termData.onAutoCompleteListener?.onCleanUp() + termData.onAutoCompleteListener = null } } diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt index 63eeccf..bd50712 100644 --- a/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt @@ -12,6 +12,7 @@ import io.neoterm.BuildConfig import io.neoterm.R import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.preference.NeoPreference +import io.neoterm.terminal.client.TermCompleteListener import io.neoterm.ui.term.NeoTermActivity import io.neoterm.utils.TerminalUtils import io.neoterm.view.ExtraKeysView @@ -58,32 +59,27 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() { if (tab is TermTab) { val termTab = tab + val termData = tab.termData - TerminalUtils.setupTerminalSession(termTab.termSession) + TerminalUtils.setupTerminalSession(termData.termSession) - // 复用前一次的 TermSessionCallback - termTab.sessionCallback?.termView = view - termTab.sessionCallback?.termTab = termTab + // 复用前一次的 TermSessionCallback 和 TermViewClient + termData.initializeViewWith(termTab, view, extraKeysView) - // 复用上一次的 TermViewClient - termTab.viewClient?.termTab = termTab - termTab.viewClient?.termView = view - termTab.viewClient?.extraKeysView = extraKeysView - - if (termTab.termSession != null) { - termTab.viewClient?.updateSuggestions(termTab.termSession?.title, true) + if (termData.termSession != null) { + termData.viewClient?.updateSuggestions(termData.termSession?.title, true) } - view.setTerminalViewClient(termTab.viewClient) - view.attachSession(termTab.termSession) + view.setTerminalViewClient(termData.viewClient) + view.attachSession(termData.termSession) // Still in progress // Only available for developers. if (BuildConfig.DEBUG) { - if (termTab.onAutoCompleteListener == null) { - termTab.onAutoCompleteListener = createAutoCompleteListener(view) + if (termData.onAutoCompleteListener == null) { + termData.onAutoCompleteListener = createAutoCompleteListener(view) } - view.onAutoCompleteListener = termTab.onAutoCompleteListener + view.onAutoCompleteListener = termData.onAutoCompleteListener } } } diff --git a/app/src/main/java/io/neoterm/utils/TerminalUtils.kt b/app/src/main/java/io/neoterm/utils/TerminalUtils.kt index 6b94132..49bb490 100644 --- a/app/src/main/java/io/neoterm/utils/TerminalUtils.kt +++ b/app/src/main/java/io/neoterm/utils/TerminalUtils.kt @@ -1,22 +1,22 @@ package io.neoterm.utils import android.content.Context -import android.widget.Toast import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.customize.font.FontManager import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoTermPath -import io.neoterm.view.BasicViewClient +import io.neoterm.terminal.ShellTermSession import io.neoterm.view.ExtraKeysView +import io.neoterm.view.TerminalDialog import io.neoterm.view.TerminalView -import java.io.File +import io.neoterm.view.TerminalViewClient /** * @author kiva */ object TerminalUtils { - fun setupTerminalView(terminalView: TerminalView?, terminalViewClient: BasicViewClient? = null) { + fun setupTerminalView(terminalView: TerminalView?, terminalViewClient: TerminalViewClient? = null) { terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30) terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace()) if (terminalViewClient != null) { @@ -31,44 +31,23 @@ object TerminalUtils { fun setupTerminalSession(session: TerminalSession?) { } - fun createSession(context: Context, executablePath: String?, arguments: Array?, - cwd: String?, initialCommand: String?, env: Array?, - sessionCallback: TerminalSession.SessionChangedCallback?, - systemShell: Boolean): TerminalSession { + fun createShellSession(context: Context, executablePath: String?, arguments: Array?, + cwd: String?, initialCommand: String?, env: Array>?, + sessionCallback: TerminalSession.SessionChangedCallback?, + systemShell: Boolean): TerminalSession { + val initCommand = initialCommand ?: + NeoPreference.loadString(R.string.key_general_initial_command, "") - var executablePath = executablePath - var arguments = arguments - var initialCommand = initialCommand - var cwd = cwd - - if (cwd == null) { - cwd = NeoTermPath.HOME_PATH - } - - if (executablePath == null) { - executablePath = if (systemShell) - "/system/bin/sh" - else - NeoTermPath.USR_PATH + "/bin/" + NeoPreference.loadString(R.string.key_general_shell, "sh") - - if (!File(executablePath).exists()) { - Toast.makeText(context, context.getString(R.string.shell_not_found, executablePath), Toast.LENGTH_LONG).show() - executablePath = NeoTermPath.USR_PATH + "/bin/sh" - } - } - - if (arguments == null) { - arguments = arrayOf(executablePath) - } - - if (initialCommand == null) { - initialCommand = NeoPreference.loadString(R.string.key_general_initial_command, "") - } - - val session = TerminalSession(executablePath, cwd, initialCommand, arguments, - env ?: NeoPreference.buildEnvironment(cwd, systemShell, executablePath), - sessionCallback) + val session = ShellTermSession.Builder() + .shell(executablePath) + .currentWorkingDirectory(cwd) + .callback(sessionCallback) + .systemShell(systemShell) + .envArray(env) + .argArray(arguments) + .create(context) setupTerminalSession(session) + session.initialCommand = initCommand return session } @@ -91,4 +70,17 @@ object TerminalUtils { builder.append('"') return builder.toString() } + + fun executeApt(context: Context, subCommand: String, callback: (Int, TerminalDialog) -> Unit) { + TerminalDialog(context) + .onFinish(object : TerminalDialog.SessionFinishedCallback { + override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) { + val exit = finishedSession?.exitStatus ?: 1 + callback(exit, dialog) + } + }) + .imeEnabled(true) + .execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", subCommand)) + .show("apt $subCommand") + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/TerminalDialog.kt b/app/src/main/java/io/neoterm/view/TerminalDialog.kt index 4e10c5e..9397896 100644 --- a/app/src/main/java/io/neoterm/view/TerminalDialog.kt +++ b/app/src/main/java/io/neoterm/view/TerminalDialog.kt @@ -16,13 +16,12 @@ import io.neoterm.utils.TerminalUtils class TerminalDialog(val context: Context) { interface SessionFinishedCallback { - fun onSessionFinished(dialog:TerminalDialog, finishedSession: TerminalSession?) + fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) } @SuppressLint("InflateParams") private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false) private var terminalView: TerminalView - private var terminalViewClient: BasicViewClient private var terminalSessionCallback: BasicSessionCallback private var dialog: AlertDialog? = null private var terminalSession: TerminalSession? = null @@ -31,10 +30,8 @@ class TerminalDialog(val context: Context) { init { terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView - terminalViewClient = BasicViewClient(terminalView) - TerminalUtils.setupTerminalView(terminalView, terminalViewClient) + TerminalUtils.setupTerminalView(terminalView, BasicViewClient(terminalView)) - terminalView.setTerminalViewClient(terminalViewClient) terminalSessionCallback = object : BasicSessionCallback(terminalView) { override fun onSessionFinished(finishedSession: TerminalSession?) { sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession) @@ -56,7 +53,7 @@ class TerminalDialog(val context: Context) { } .create() - terminalSession = TerminalUtils.createSession(context, executablePath, arguments, null, null, null, terminalSessionCallback, false) + terminalSession = TerminalUtils.createShellSession(context, executablePath, arguments, null, null, null, terminalSessionCallback, false) terminalView.attachSession(terminalSession) return this } @@ -66,12 +63,12 @@ class TerminalDialog(val context: Context) { return this } - fun setTitle(title: String?) : TerminalDialog { + fun setTitle(title: String?): TerminalDialog { dialog?.setTitle(title) return this } - fun onFinish(finishedCallback: SessionFinishedCallback):TerminalDialog { + fun onFinish(finishedCallback: SessionFinishedCallback): TerminalDialog { this.sessionFinishedCallback = finishedCallback return this } @@ -85,4 +82,12 @@ class TerminalDialog(val context: Context) { dialog?.dismiss() return this } + + fun imeEnabled(enabled: Boolean): TerminalDialog { + if (enabled) { + terminalView.isFocusable = true + terminalView.isFocusableInTouchMode = true + } + return this + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/TerminalView.java b/app/src/main/java/io/neoterm/view/TerminalView.java index 1dacf6b..4e9d3d7 100755 --- a/app/src/main/java/io/neoterm/view/TerminalView.java +++ b/app/src/main/java/io/neoterm/view/TerminalView.java @@ -286,7 +286,6 @@ public final class TerminalView extends View { @Override public boolean commitText(CharSequence text, int newCursorPosition) { - // TODO: AutoComplete if (LOG_KEY_EVENTS) { Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); } diff --git a/build.gradle b/build.gradle index 99537d0..f4b19a5 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha5' + classpath 'com.android.tools.build:gradle:3.0.0-alpha7' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong