Project: Refactor Terminal Client
This commit is contained in:
parent
e21d7ccf6f
commit
bdc8f60da0
@ -17,8 +17,8 @@ android {
|
|||||||
applicationId "io.neoterm"
|
applicationId "io.neoterm"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 13
|
versionCode 14
|
||||||
versionName "1.2.0-rc2"
|
versionName "1.2.0-rc3"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "zh"
|
resConfigs "zh"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
@ -28,7 +28,7 @@ import java.util.UUID;
|
|||||||
* <p>
|
* <p>
|
||||||
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
* 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. */
|
/** Callback to be invoked when a {@link TerminalSession} changes. */
|
||||||
public interface SessionChangedCallback {
|
public interface SessionChangedCallback {
|
||||||
@ -143,18 +143,16 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
|
|
||||||
private final String mShellPath;
|
private final String mShellPath;
|
||||||
private final String mCwd;
|
private final String mCwd;
|
||||||
private final String mInitialCommand;
|
|
||||||
private final String[] mArgs;
|
private final String[] mArgs;
|
||||||
private final String[] mEnv;
|
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;
|
mChangeCallback = changeCallback;
|
||||||
|
|
||||||
this.mShellPath = shellPath;
|
this.mShellPath = shellPath;
|
||||||
this.mCwd = cwd;
|
this.mCwd = cwd;
|
||||||
this.mArgs = args;
|
this.mArgs = args;
|
||||||
this.mEnv = env;
|
this.mEnv = env;
|
||||||
this.mInitialCommand = initialCommand;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
/** 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));
|
mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode));
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
|
|
||||||
if (mInitialCommand != null && mInitialCommand.length() > 0) {
|
|
||||||
write(mInitialCommand + '\r');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Write data to the shell process. */
|
/** Write data to the shell process. */
|
||||||
|
@ -96,67 +96,6 @@ object NeoPreference {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array<String> {
|
|
||||||
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
|
* TODO
|
||||||
* To print the job name about to be executed in bash:
|
* To print the job name about to be executed in bash:
|
||||||
|
@ -76,8 +76,11 @@ class NeoTermService : Service() {
|
|||||||
val sessions: List<TerminalSession>
|
val sessions: List<TerminalSession>
|
||||||
get() = mTerminalSessions
|
get() = mTerminalSessions
|
||||||
|
|
||||||
fun createTermSession(executablePath: String?, arguments: Array<String>?, cwd: String?, initialCommand: String?, env: Array<String>?, sessionCallback: TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession {
|
fun createTermSession(executablePath: String?, arguments: Array<String>?,
|
||||||
val session = TerminalUtils.createSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell)
|
cwd: String?, initialCommand: String?,
|
||||||
|
env: Array<Pair<String, String>>?, sessionCallback:
|
||||||
|
TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession {
|
||||||
|
val session = TerminalUtils.createShellSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell)
|
||||||
mTerminalSessions.add(session)
|
mTerminalSessions.add(session)
|
||||||
updateNotification()
|
updateNotification()
|
||||||
return session
|
return session
|
||||||
|
199
app/src/main/java/io/neoterm/terminal/ShellTermSession.kt
Normal file
199
app/src/main/java/io/neoterm/terminal/ShellTermSession.kt
Normal file
@ -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<String>, env: Array<String>, 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<String>? = null
|
||||||
|
private var env: MutableList<Pair<String, String>>? = 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<String>?): 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<String, String>?): 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<Pair<String, String>>?): 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<Pair<String, String>>?): Array<String>? {
|
||||||
|
if (env == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
env.mapTo(result, { "${it.first}=${it.second}" })
|
||||||
|
return result.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array<String> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.neoterm.ui.term.tab
|
package io.neoterm.terminal.client
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.neoterm.ui.term.tab
|
package io.neoterm.terminal.client
|
||||||
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
@ -8,43 +8,40 @@ import android.os.Vibrator
|
|||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.view.TerminalView
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
class TermSessionCallback : TerminalSession.SessionChangedCallback {
|
class TermSessionCallback : TerminalSession.SessionChangedCallback {
|
||||||
var termView: TerminalView? = null
|
var termData: TermDataHolder? = null
|
||||||
var termTab: TermTab? = null
|
|
||||||
|
|
||||||
var bellId: Int = 0
|
var bellId: Int = 0
|
||||||
var soundPool: SoundPool? = null
|
var soundPool: SoundPool? = null
|
||||||
|
|
||||||
override fun onTextChanged(changedSession: TerminalSession?) {
|
override fun onTextChanged(changedSession: TerminalSession?) {
|
||||||
termView?.onScreenUpdated()
|
termData?.termView?.onScreenUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTitleChanged(changedSession: TerminalSession?) {
|
override fun onTitleChanged(changedSession: TerminalSession?) {
|
||||||
if (changedSession?.title != null) {
|
if (changedSession?.title != null) {
|
||||||
termTab?.updateTitle(changedSession.title)
|
termData?.termUI?.requireUpdateTitle(changedSession.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
||||||
termTab?.onSessionFinished()
|
termData?.termUI?.requireOnSessionFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClipboardText(session: TerminalSession?, text: String?) {
|
override fun onClipboardText(session: TerminalSession?, text: String?) {
|
||||||
|
val termView = termData?.termView
|
||||||
if (termView != null) {
|
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)
|
clipboard.primaryClip = ClipData.newPlainText("", text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBell(session: TerminalSession?) {
|
override fun onBell(session: TerminalSession?) {
|
||||||
if (termView == null) {
|
val termView = termData?.termView ?: return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) {
|
if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) {
|
||||||
if (soundPool == null) {
|
if (soundPool == null) {
|
||||||
@ -61,8 +58,9 @@ class TermSessionCallback : TerminalSession.SessionChangedCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onColorsChanged(session: TerminalSession?) {
|
override fun onColorsChanged(session: TerminalSession?) {
|
||||||
if (session != null) {
|
val termView = termData?.termView
|
||||||
termView?.onScreenUpdated()
|
if (session != null && termView != null) {
|
||||||
|
termView.onScreenUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package io.neoterm.ui.term.tab
|
package io.neoterm.terminal.client
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
@ -11,8 +11,6 @@ import io.neoterm.backend.KeyHandler
|
|||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.customize.eks.EksKeysManager
|
import io.neoterm.customize.eks.EksKeysManager
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.view.ExtraKeysView
|
|
||||||
import io.neoterm.view.TerminalView
|
|
||||||
import io.neoterm.view.TerminalViewClient
|
import io.neoterm.view.TerminalViewClient
|
||||||
|
|
||||||
|
|
||||||
@ -26,9 +24,7 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
|
|
||||||
var sessionFinished: Boolean = false
|
var sessionFinished: Boolean = false
|
||||||
|
|
||||||
var termTab: TermTab? = null
|
var termData: TermDataHolder? = null
|
||||||
var termView: TerminalView? = null
|
|
||||||
var extraKeysView: ExtraKeysView? = null
|
|
||||||
|
|
||||||
override fun onScale(scale: Float): Float {
|
override fun onScale(scale: Float): Float {
|
||||||
if (scale < 0.9f || scale > 1.1f) {
|
if (scale < 0.9f || scale > 1.1f) {
|
||||||
@ -40,8 +36,11 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent?) {
|
override fun onSingleTapUp(e: MotionEvent?) {
|
||||||
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
val termView = termData?.termView
|
||||||
.showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT)
|
if (termView != null) {
|
||||||
|
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
||||||
|
.showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldBackButtonBeMappedToEscape(): Boolean {
|
override fun shouldBackButtonBeMappedToEscape(): Boolean {
|
||||||
@ -53,10 +52,12 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
|
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
|
||||||
|
val termUI = termData?.termUI
|
||||||
|
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_ENTER -> {
|
KeyEvent.KEYCODE_ENTER -> {
|
||||||
if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) {
|
if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) {
|
||||||
termTab?.requireCloseTab()
|
termUI?.requireClose()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -67,9 +68,9 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
val unicodeChar = e.getUnicodeChar(0).toChar()
|
val unicodeChar = e.getUnicodeChar(0).toChar()
|
||||||
|
|
||||||
if (unicodeChar == 'f'/* full screen */) {
|
if (unicodeChar == 'f'/* full screen */) {
|
||||||
termTab?.requireToggleFullScreen()
|
termUI?.requireToggleFullScreen()
|
||||||
} else if (unicodeChar == 'v') {
|
} else if (unicodeChar == 'v') {
|
||||||
termTab?.requirePaste()
|
termUI?.requirePaste()
|
||||||
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON).toChar() == '+') {
|
} 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 '+',
|
// We also check for the shifted char here since shift may be required to produce '+',
|
||||||
// see https://github.com/termux/termux-api/issues/2
|
// see https://github.com/termux/termux-api/issues/2
|
||||||
@ -86,11 +87,13 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun readControlKey(): Boolean {
|
override fun readControlKey(): Boolean {
|
||||||
return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown
|
val extraKeysView = termData?.extraKeysView
|
||||||
|
return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readAltKey(): Boolean {
|
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 {
|
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) {
|
fun updateSuggestions(title: String?, force: Boolean = false) {
|
||||||
|
val extraKeysView = termData?.extraKeysView
|
||||||
|
|
||||||
if (extraKeysView == null || title == null || title.isEmpty()) {
|
if (extraKeysView == null || title == null || title.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -192,13 +197,17 @@ class TermViewClient(val context: Context) : TerminalViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeSuggestions() {
|
fun removeSuggestions() {
|
||||||
|
val extraKeysView = termData?.extraKeysView
|
||||||
extraKeysView?.clearUserDefinedButton()
|
extraKeysView?.clearUserDefinedButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeFontSize(increase: Boolean) {
|
private fun changeFontSize(increase: Boolean) {
|
||||||
val changedSize = (if (increase) 1 else -1) * 2
|
val termView = termData?.termView
|
||||||
val fontSize = termView!!.textSize + changedSize
|
if (termView != null) {
|
||||||
termView!!.textSize = fontSize
|
val changedSize = (if (increase) 1 else -1) * 2
|
||||||
NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize)
|
val fontSize = termView.textSize + changedSize
|
||||||
|
termView.textSize = fontSize
|
||||||
|
NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ package io.neoterm.ui.customization
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
@ -57,7 +56,7 @@ class CustomizationActivity : AppCompatActivity() {
|
|||||||
viewClient = BasicViewClient(terminalView)
|
viewClient = BasicViewClient(terminalView)
|
||||||
sessionCallback = BasicSessionCallback(terminalView)
|
sessionCallback = BasicSessionCallback(terminalView)
|
||||||
TerminalUtils.setupTerminalView(terminalView, viewClient)
|
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)
|
arrayOf("echo", "Hello NeoTerm."), null, null, null, sessionCallback, false)
|
||||||
terminalView.attachSession(session)
|
terminalView.attachSession(session)
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
|||||||
dialog.setTitle(getString(R.string.done))
|
dialog.setTitle(getString(R.string.done))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.imeEnabled(true)
|
||||||
.show("Installing $packageName")
|
.show("Installing $packageName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,6 +186,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update"))
|
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update"))
|
||||||
|
.imeEnabled(true)
|
||||||
.show("apt update")
|
.show("apt update")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import android.os.Bundle
|
|||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.view.View
|
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.igalata.bubblepicker.BubblePickerListener
|
import com.igalata.bubblepicker.BubblePickerListener
|
||||||
@ -22,6 +21,7 @@ import io.neoterm.customize.setup.BaseFileInstaller
|
|||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.preference.NeoTermPath
|
import io.neoterm.preference.NeoTermPath
|
||||||
import io.neoterm.utils.PackageUtils
|
import io.neoterm.utils.PackageUtils
|
||||||
|
import io.neoterm.utils.TerminalUtils
|
||||||
import io.neoterm.view.TerminalDialog
|
import io.neoterm.view.TerminalDialog
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lateinit var picker: BubblePicker
|
lateinit var picker: BubblePicker
|
||||||
lateinit var nextButton: Button
|
|
||||||
lateinit var toast: Toast
|
lateinit var toast: Toast
|
||||||
var aptUpdated = false
|
var aptUpdated = false
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.ui_setup)
|
setContentView(R.layout.ui_setup)
|
||||||
picker = findViewById(R.id.bubble_picker) as BubblePicker
|
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 {
|
nextButton.setOnClickListener {
|
||||||
if (aptUpdated) {
|
if (aptUpdated) {
|
||||||
val packageList = mutableListOf("apt", "install", "-y")
|
val packageList = mutableListOf("apt", "install", "-y")
|
||||||
@ -101,7 +100,6 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
.setMessage(error.toString())
|
.setMessage(error.toString())
|
||||||
.setNegativeButton(R.string.use_system_shell, { _, _ ->
|
.setNegativeButton(R.string.use_system_shell, { _, _ ->
|
||||||
setResult(Activity.RESULT_CANCELED)
|
setResult(Activity.RESULT_CANCELED)
|
||||||
nextButton.visibility = View.VISIBLE
|
|
||||||
finish()
|
finish()
|
||||||
})
|
})
|
||||||
.setPositiveButton(R.string.retry, { dialog, _ ->
|
.setPositiveButton(R.string.retry, { dialog, _ ->
|
||||||
@ -114,21 +112,25 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun executeAptUpdate() {
|
private fun executeAptUpdate() {
|
||||||
TerminalDialog(this)
|
TerminalUtils.executeApt(this, "update", { exitStatus, dialog ->
|
||||||
.onFinish(object : TerminalDialog.SessionFinishedCallback {
|
if (exitStatus == 0) {
|
||||||
override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) {
|
dialog.dismiss()
|
||||||
nextButton.visibility = View.VISIBLE
|
aptUpdated = true
|
||||||
val exit = finishedSession?.exitStatus ?: 1
|
executeAptUpgrade()
|
||||||
if (exit == 0) {
|
} else {
|
||||||
dialog.dismiss()
|
dialog.setTitle(getString(R.string.error))
|
||||||
aptUpdated = true
|
}
|
||||||
} else {
|
})
|
||||||
dialog.setTitle(getString(R.string.error))
|
}
|
||||||
}
|
|
||||||
}
|
private fun executeAptUpgrade() {
|
||||||
})
|
TerminalUtils.executeApt(this, "upgrade", { exitStatus, dialog ->
|
||||||
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update"))
|
if (exitStatus == 0) {
|
||||||
.show("apt update")
|
dialog.dismiss()
|
||||||
|
} else {
|
||||||
|
dialog.setTitle(getString(R.string.error))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ShowToast")
|
@SuppressLint("ShowToast")
|
||||||
|
@ -33,10 +33,10 @@ import io.neoterm.ui.bonus.BonusActivity
|
|||||||
import io.neoterm.ui.pm.PackageManagerActivity
|
import io.neoterm.ui.pm.PackageManagerActivity
|
||||||
import io.neoterm.ui.settings.SettingActivity
|
import io.neoterm.ui.settings.SettingActivity
|
||||||
import io.neoterm.ui.setup.SetupActivity
|
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.TermTab
|
||||||
import io.neoterm.ui.term.tab.TermTabDecorator
|
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.TabCloseEvent
|
||||||
import io.neoterm.ui.term.tab.event.TitleChangedEvent
|
import io.neoterm.ui.term.tab.event.TitleChangedEvent
|
||||||
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
|
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?) {
|
override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) {
|
||||||
if (selectedTab is TermTab && selectedTab.termSession != null) {
|
if (selectedTab is TermTab && selectedTab.termData.termSession != null) {
|
||||||
NeoPreference.storeCurrentSession(selectedTab.termSession!!)
|
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) {
|
override fun onTabRemoved(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) {
|
||||||
if (tab is TermTab) {
|
if (tab is TermTab) {
|
||||||
tab.termSession?.finishIfRunning()
|
tab.termData.termSession?.finishIfRunning()
|
||||||
removeFinishedSession(tab.termSession)
|
removeFinishedSession(tab.termData.termSession)
|
||||||
tab.cleanup()
|
tab.cleanup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,29 +387,32 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
val tabCount = tabSwitcher.count
|
val tabCount = tabSwitcher.count
|
||||||
(0..(tabCount - 1))
|
(0..(tabCount - 1))
|
||||||
.map { tabSwitcher.getTab(it) }
|
.map { tabSwitcher.getTab(it) }
|
||||||
.filter { it is TermTab && it.termSession == session }
|
.filter { it is TermTab && it.termData.termSession == session }
|
||||||
.forEach { return }
|
.forEach { return }
|
||||||
|
|
||||||
|
val sessionCallback = session.sessionChangedCallback as TermSessionCallback
|
||||||
|
val viewClient = TermViewClient(this)
|
||||||
|
|
||||||
val tab = createTab(session.title) as TermTab
|
val tab = createTab(session.title) as TermTab
|
||||||
tab.sessionCallback = session.sessionChangedCallback as TermSessionCallback
|
tab.termData.initializeSessionWith(session, sessionCallback, viewClient)
|
||||||
tab.viewClient = TermViewClient(this)
|
|
||||||
tab.termSession = session
|
|
||||||
|
|
||||||
addNewTab(tab, createRevealAnimation())
|
addNewTab(tab, createRevealAnimation())
|
||||||
switchToSession(tab)
|
switchToSession(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNewSession(sessionName: String?, systemShell: Boolean, animation: Animation) {
|
private fun addNewSession(sessionName: String?, systemShell: Boolean, animation: Animation) {
|
||||||
val tab = createTab(sessionName) as TermTab
|
val sessionCallback = TermSessionCallback()
|
||||||
tab.sessionCallback = TermSessionCallback()
|
val viewClient = TermViewClient(this)
|
||||||
tab.viewClient = TermViewClient(this)
|
val session = termService!!.createTermSession(null, null,
|
||||||
tab.termSession = termService!!.createTermSession(null, null,
|
null, null, null, sessionCallback, systemShell)
|
||||||
null, null, null, tab.sessionCallback, systemShell)
|
|
||||||
|
|
||||||
if (sessionName != null) {
|
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)
|
addNewTab(tab, animation)
|
||||||
switchToSession(tab)
|
switchToSession(tab)
|
||||||
}
|
}
|
||||||
@ -421,7 +424,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
|
|
||||||
for (i in 0..tabSwitcher.count - 1) {
|
for (i in 0..tabSwitcher.count - 1) {
|
||||||
val tab = tabSwitcher.getTab(i)
|
val tab = tabSwitcher.getTab(i)
|
||||||
if (tab is TermTab && tab.termSession == session) {
|
if (tab is TermTab && tab.termData.termSession == session) {
|
||||||
switchToSession(tab)
|
switchToSession(tab)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import io.neoterm.customize.script.UserScript
|
|||||||
import io.neoterm.customize.script.UserScriptManager
|
import io.neoterm.customize.script.UserScriptManager
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.services.NeoTermService
|
import io.neoterm.services.NeoTermService
|
||||||
import io.neoterm.ui.term.tab.TermSessionCallback
|
import io.neoterm.terminal.client.TermSessionCallback
|
||||||
import io.neoterm.utils.TerminalUtils
|
import io.neoterm.utils.TerminalUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -5,58 +5,31 @@ import android.support.v7.widget.Toolbar
|
|||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import de.mrapp.android.tabswitcher.Tab
|
import de.mrapp.android.tabswitcher.Tab
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
|
||||||
import io.neoterm.customize.color.ColorSchemeManager
|
import io.neoterm.customize.color.ColorSchemeManager
|
||||||
import io.neoterm.preference.NeoPreference
|
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.TabCloseEvent
|
||||||
import io.neoterm.ui.term.tab.event.TitleChangedEvent
|
import io.neoterm.ui.term.tab.event.TitleChangedEvent
|
||||||
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
|
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
|
||||||
import io.neoterm.view.OnAutoCompleteListener
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class TermTab(title: CharSequence) : Tab(title) {
|
class TermTab(title: CharSequence) : Tab(title), TermUiPresenter {
|
||||||
var termSession: TerminalSession? = null
|
var termData = TermDataHolder()
|
||||||
var sessionCallback: TermSessionCallback? = null
|
|
||||||
var viewClient: TermViewClient? = null
|
|
||||||
var onAutoCompleteListener: OnAutoCompleteListener? = null
|
|
||||||
var toolbar: Toolbar? = null
|
var toolbar: Toolbar? = null
|
||||||
|
|
||||||
fun updateColorScheme() {
|
fun updateColorScheme() {
|
||||||
ColorSchemeManager.applyColorScheme(viewClient?.termView, viewClient?.extraKeysView,
|
ColorSchemeManager.applyColorScheme(termData.termView, termData.extraKeysView,
|
||||||
ColorSchemeManager.getCurrentColorScheme())
|
ColorSchemeManager.getCurrentColorScheme())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
onAutoCompleteListener?.onCleanUp()
|
termData.cleanup()
|
||||||
onAutoCompleteListener = null
|
|
||||||
|
|
||||||
viewClient?.termTab = null
|
|
||||||
viewClient?.termView = null
|
|
||||||
viewClient?.extraKeysView = null
|
|
||||||
sessionCallback?.termView = null
|
|
||||||
sessionCallback?.termTab = null
|
|
||||||
toolbar = null
|
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) {
|
fun onFullScreenModeChanged(fullScreen: Boolean) {
|
||||||
@ -64,13 +37,8 @@ class TermTab(title: CharSequence) : Tab(title) {
|
|||||||
resetAutoCompleteStatus()
|
resetAutoCompleteStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requireCloseTab() {
|
|
||||||
requireHideIme()
|
|
||||||
EventBus.getDefault().post(TabCloseEvent(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requireHideIme() {
|
fun requireHideIme() {
|
||||||
val terminalView = viewClient?.termView
|
val terminalView = termData.termView
|
||||||
if (terminalView != null) {
|
if (terminalView != null) {
|
||||||
val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
if (imm.isActive) {
|
if (imm.isActive) {
|
||||||
@ -79,16 +47,37 @@ class TermTab(title: CharSequence) : Tab(title) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requireToggleFullScreen() {
|
override fun requireToggleFullScreen() {
|
||||||
EventBus.getDefault().post(ToggleFullScreenEvent())
|
EventBus.getDefault().post(ToggleFullScreenEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requirePaste() {
|
override fun requirePaste() {
|
||||||
viewClient?.termView?.pasteFromClipboard()
|
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() {
|
fun resetAutoCompleteStatus() {
|
||||||
onAutoCompleteListener?.onCleanUp()
|
termData.onAutoCompleteListener?.onCleanUp()
|
||||||
onAutoCompleteListener = null
|
termData.onAutoCompleteListener = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import io.neoterm.BuildConfig
|
|||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.customize.color.ColorSchemeManager
|
import io.neoterm.customize.color.ColorSchemeManager
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
|
import io.neoterm.terminal.client.TermCompleteListener
|
||||||
import io.neoterm.ui.term.NeoTermActivity
|
import io.neoterm.ui.term.NeoTermActivity
|
||||||
import io.neoterm.utils.TerminalUtils
|
import io.neoterm.utils.TerminalUtils
|
||||||
import io.neoterm.view.ExtraKeysView
|
import io.neoterm.view.ExtraKeysView
|
||||||
@ -58,32 +59,27 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() {
|
|||||||
|
|
||||||
if (tab is TermTab) {
|
if (tab is TermTab) {
|
||||||
val termTab = tab
|
val termTab = tab
|
||||||
|
val termData = tab.termData
|
||||||
|
|
||||||
TerminalUtils.setupTerminalSession(termTab.termSession)
|
TerminalUtils.setupTerminalSession(termData.termSession)
|
||||||
|
|
||||||
// 复用前一次的 TermSessionCallback
|
// 复用前一次的 TermSessionCallback 和 TermViewClient
|
||||||
termTab.sessionCallback?.termView = view
|
termData.initializeViewWith(termTab, view, extraKeysView)
|
||||||
termTab.sessionCallback?.termTab = termTab
|
|
||||||
|
|
||||||
// 复用上一次的 TermViewClient
|
if (termData.termSession != null) {
|
||||||
termTab.viewClient?.termTab = termTab
|
termData.viewClient?.updateSuggestions(termData.termSession?.title, true)
|
||||||
termTab.viewClient?.termView = view
|
|
||||||
termTab.viewClient?.extraKeysView = extraKeysView
|
|
||||||
|
|
||||||
if (termTab.termSession != null) {
|
|
||||||
termTab.viewClient?.updateSuggestions(termTab.termSession?.title, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view.setTerminalViewClient(termTab.viewClient)
|
view.setTerminalViewClient(termData.viewClient)
|
||||||
view.attachSession(termTab.termSession)
|
view.attachSession(termData.termSession)
|
||||||
|
|
||||||
// Still in progress
|
// Still in progress
|
||||||
// Only available for developers.
|
// Only available for developers.
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
if (termTab.onAutoCompleteListener == null) {
|
if (termData.onAutoCompleteListener == null) {
|
||||||
termTab.onAutoCompleteListener = createAutoCompleteListener(view)
|
termData.onAutoCompleteListener = createAutoCompleteListener(view)
|
||||||
}
|
}
|
||||||
view.onAutoCompleteListener = termTab.onAutoCompleteListener
|
view.onAutoCompleteListener = termData.onAutoCompleteListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package io.neoterm.utils
|
package io.neoterm.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.customize.font.FontManager
|
import io.neoterm.customize.font.FontManager
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.preference.NeoTermPath
|
import io.neoterm.preference.NeoTermPath
|
||||||
import io.neoterm.view.BasicViewClient
|
import io.neoterm.terminal.ShellTermSession
|
||||||
import io.neoterm.view.ExtraKeysView
|
import io.neoterm.view.ExtraKeysView
|
||||||
|
import io.neoterm.view.TerminalDialog
|
||||||
import io.neoterm.view.TerminalView
|
import io.neoterm.view.TerminalView
|
||||||
import java.io.File
|
import io.neoterm.view.TerminalViewClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
object TerminalUtils {
|
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?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30)
|
||||||
terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace())
|
terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace())
|
||||||
if (terminalViewClient != null) {
|
if (terminalViewClient != null) {
|
||||||
@ -31,44 +31,23 @@ object TerminalUtils {
|
|||||||
fun setupTerminalSession(session: TerminalSession?) {
|
fun setupTerminalSession(session: TerminalSession?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSession(context: Context, executablePath: String?, arguments: Array<String>?,
|
fun createShellSession(context: Context, executablePath: String?, arguments: Array<String>?,
|
||||||
cwd: String?, initialCommand: String?, env: Array<String>?,
|
cwd: String?, initialCommand: String?, env: Array<Pair<String, String>>?,
|
||||||
sessionCallback: TerminalSession.SessionChangedCallback?,
|
sessionCallback: TerminalSession.SessionChangedCallback?,
|
||||||
systemShell: Boolean): TerminalSession {
|
systemShell: Boolean): TerminalSession {
|
||||||
|
val initCommand = initialCommand ?:
|
||||||
|
NeoPreference.loadString(R.string.key_general_initial_command, "")
|
||||||
|
|
||||||
var executablePath = executablePath
|
val session = ShellTermSession.Builder()
|
||||||
var arguments = arguments
|
.shell(executablePath)
|
||||||
var initialCommand = initialCommand
|
.currentWorkingDirectory(cwd)
|
||||||
var cwd = cwd
|
.callback(sessionCallback)
|
||||||
|
.systemShell(systemShell)
|
||||||
if (cwd == null) {
|
.envArray(env)
|
||||||
cwd = NeoTermPath.HOME_PATH
|
.argArray(arguments)
|
||||||
}
|
.create(context)
|
||||||
|
|
||||||
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<String>(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)
|
|
||||||
setupTerminalSession(session)
|
setupTerminalSession(session)
|
||||||
|
session.initialCommand = initCommand
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,4 +70,17 @@ object TerminalUtils {
|
|||||||
builder.append('"')
|
builder.append('"')
|
||||||
return builder.toString()
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
@ -16,13 +16,12 @@ import io.neoterm.utils.TerminalUtils
|
|||||||
class TerminalDialog(val context: Context) {
|
class TerminalDialog(val context: Context) {
|
||||||
|
|
||||||
interface SessionFinishedCallback {
|
interface SessionFinishedCallback {
|
||||||
fun onSessionFinished(dialog:TerminalDialog, finishedSession: TerminalSession?)
|
fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false)
|
private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false)
|
||||||
private var terminalView: TerminalView
|
private var terminalView: TerminalView
|
||||||
private var terminalViewClient: BasicViewClient
|
|
||||||
private var terminalSessionCallback: BasicSessionCallback
|
private var terminalSessionCallback: BasicSessionCallback
|
||||||
private var dialog: AlertDialog? = null
|
private var dialog: AlertDialog? = null
|
||||||
private var terminalSession: TerminalSession? = null
|
private var terminalSession: TerminalSession? = null
|
||||||
@ -31,10 +30,8 @@ class TerminalDialog(val context: Context) {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView
|
terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView
|
||||||
terminalViewClient = BasicViewClient(terminalView)
|
TerminalUtils.setupTerminalView(terminalView, BasicViewClient(terminalView))
|
||||||
TerminalUtils.setupTerminalView(terminalView, terminalViewClient)
|
|
||||||
|
|
||||||
terminalView.setTerminalViewClient(terminalViewClient)
|
|
||||||
terminalSessionCallback = object : BasicSessionCallback(terminalView) {
|
terminalSessionCallback = object : BasicSessionCallback(terminalView) {
|
||||||
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
||||||
sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession)
|
sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession)
|
||||||
@ -56,7 +53,7 @@ class TerminalDialog(val context: Context) {
|
|||||||
}
|
}
|
||||||
.create()
|
.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)
|
terminalView.attachSession(terminalSession)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -66,12 +63,12 @@ class TerminalDialog(val context: Context) {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTitle(title: String?) : TerminalDialog {
|
fun setTitle(title: String?): TerminalDialog {
|
||||||
dialog?.setTitle(title)
|
dialog?.setTitle(title)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFinish(finishedCallback: SessionFinishedCallback):TerminalDialog {
|
fun onFinish(finishedCallback: SessionFinishedCallback): TerminalDialog {
|
||||||
this.sessionFinishedCallback = finishedCallback
|
this.sessionFinishedCallback = finishedCallback
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -85,4 +82,12 @@ class TerminalDialog(val context: Context) {
|
|||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun imeEnabled(enabled: Boolean): TerminalDialog {
|
||||||
|
if (enabled) {
|
||||||
|
terminalView.isFocusable = true
|
||||||
|
terminalView.isFocusableInTouchMode = true
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
@ -286,7 +286,6 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean commitText(CharSequence text, int newCursorPosition) {
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
||||||
// TODO: AutoComplete
|
|
||||||
if (LOG_KEY_EVENTS) {
|
if (LOG_KEY_EVENTS) {
|
||||||
Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
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"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
Loading…
x
Reference in New Issue
Block a user