Project: Add app icon

This commit is contained in:
zt515 2017-07-22 06:15:33 +08:00
parent b611b0eaf4
commit 53fd024b14
5 changed files with 545 additions and 0 deletions

View File

@ -0,0 +1,178 @@
package io.neoterm.backend
import android.content.Context
import android.widget.Toast
import io.neoterm.R
import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath
import io.neoterm.ui.term.tab.TermSessionCallback
import java.io.File
/**
* @author kiva
*/
open class ShellTermSession : TerminalSession {
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)
}
}
return this
}
fun args(vararg args: String?): Builder {
if (args.isEmpty()) {
this.args = null
return this
}
args.forEach { arg(it) }
return this
}
fun env(env: Pair<String, String>): Builder {
if (this.env == null) {
this.env = mutableListOf(env)
} else {
this.env!!.add(env)
}
return this
}
fun envs(vararg env: Pair<String, String>): Builder {
if (env.isEmpty()) {
this.env = null
return this
}
env.forEach { env(it) }
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()
}
}
private constructor(shellPath: String, cwd: String,
args: Array<String>, env: Array<String>,
changeCallback: SessionChangedCallback)
: super(shellPath, cwd, args, env, changeCallback)
}

View File

@ -0,0 +1,93 @@
package io.neoterm.terminal
import android.util.Log
import android.view.KeyEvent
import io.neoterm.customize.completion.AutoCompleteManager
import io.neoterm.customize.completion.CompleteCandidate
import io.neoterm.view.AutoCompletePopupWindow
import io.neoterm.view.OnAutoCompleteListener
import io.neoterm.view.TerminalView
import java.util.*
/**
* @author kiva
*/
class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener {
private val inputStack = Stack<Char>()
private val popupWindow = AutoCompletePopupWindow(terminalView!!.context)
override fun onKeyCode(keyCode: Int, keyMod: Int) {
when (keyCode) {
KeyEvent.KEYCODE_DEL -> {
Log.e("NeoTerm-AC", "BackSpace")
popChar()
activateAutoCompletion()
}
KeyEvent.KEYCODE_ENTER -> {
Log.e("NeoTerm-AC", "Clear Chars")
clearChars()
popupWindow.dismiss()
}
}
}
override fun onAutoComplete(newText: String?) {
if (newText == null || newText.isEmpty()) {
return
}
newText.toCharArray().forEach { pushChar(it) }
activateAutoCompletion()
}
override fun onCleanUp() {
popupWindow.cleanup()
terminalView = null
}
private fun activateAutoCompletion() {
val text = getCurrentEditingText()
if (text.isEmpty()) {
return
}
val candidates = AutoCompleteManager.filter(text)
Log.e("NeoTerm-AC", "Completing for $text")
candidates.forEach {
Log.e("NeoTerm-AC", " Candidate: ${it.completeString}")
}
if (candidates.isNotEmpty()) {
showAutoCompleteCandidates(candidates)
}
}
private fun showAutoCompleteCandidates(candidates: List<CompleteCandidate>) {
popupWindow.candidates = candidates
popupWindow.show(terminalView!!)
}
private fun getCurrentEditingText(): String {
val builder = StringBuilder()
val size = inputStack.size
(0..(size - 1))
.map { inputStack[it] }
.takeWhile { !(it == 0.toChar() || it == ' ') }
.forEach { builder.append(it) }
return builder.toString()
}
private fun clearChars() {
inputStack.clear()
}
private fun popChar() {
if (inputStack.isNotEmpty()) {
inputStack.pop()
}
}
private fun pushChar(char: Char) {
inputStack.push(char)
}
}

View File

@ -0,0 +1,69 @@
package io.neoterm.terminal
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.media.SoundPool
import android.os.Vibrator
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.preference.NeoPreference
import io.neoterm.ui.term.tab.TermTab
import io.neoterm.view.TerminalView
/**
* @author kiva
*/
class TermSessionCallback : TerminalSession.SessionChangedCallback {
var termView: TerminalView? = null
var termTab: TermTab? = null
var bellId: Int = 0
var soundPool: SoundPool? = null
override fun onTextChanged(changedSession: TerminalSession?) {
termView?.onScreenUpdated()
}
override fun onTitleChanged(changedSession: TerminalSession?) {
if (changedSession?.title != null) {
termTab?.updateTitle(changedSession.title)
}
}
override fun onSessionFinished(finishedSession: TerminalSession?) {
termTab?.onSessionFinished()
}
override fun onClipboardText(session: TerminalSession?, text: String?) {
if (termView != null) {
val clipboard = termView!!.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText("", text)
}
}
override fun onBell(session: TerminalSession?) {
if (termView == null) {
return
}
if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) {
if (soundPool == null) {
soundPool = SoundPool.Builder().setMaxStreams(1).build()
bellId = soundPool!!.load(termView!!.context, R.raw.bell, 1)
}
soundPool?.play(bellId, 1f, 1f, 0, 0, 1f)
}
if (NeoPreference.loadBoolean(R.string.key_general_vibrate, false)) {
val vibrator = termView!!.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(100)
}
}
override fun onColorsChanged(session: TerminalSession?) {
if (session != null) {
termView?.onScreenUpdated()
}
}
}

View File

@ -0,0 +1,205 @@
package io.neoterm.terminal
import android.content.Context
import android.media.AudioManager
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.inputmethod.InputMethodManager
import io.neoterm.R
import io.neoterm.backend.KeyHandler
import io.neoterm.backend.TerminalSession
import io.neoterm.customize.eks.EksKeysManager
import io.neoterm.preference.NeoPreference
import io.neoterm.ui.term.tab.TermTab
import io.neoterm.view.ExtraKeysView
import io.neoterm.view.TerminalView
import io.neoterm.view.TerminalViewClient
/**
* @author kiva
*/
class TermViewClient(val context: Context) : TerminalViewClient {
private var mVirtualControlKeyDown: Boolean = false
private var mVirtualFnKeyDown: Boolean = false
private var lastTitle: String = ""
var sessionFinished: Boolean = false
var termTab: TermTab? = null
var termView: TerminalView? = null
var extraKeysView: ExtraKeysView? = null
override fun onScale(scale: Float): Float {
if (scale < 0.9f || scale > 1.1f) {
val increase = scale > 1f
changeFontSize(increase)
return 1.0f
}
return scale
}
override fun onSingleTapUp(e: MotionEvent?) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT)
}
override fun shouldBackButtonBeMappedToEscape(): Boolean {
return NeoPreference.loadBoolean(R.string.key_generaL_backspace_map_to_esc, false)
}
override fun copyModeChanged(copyMode: Boolean) {
// TODO
}
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_ENTER -> {
if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) {
termTab?.requireCloseTab()
return true
}
return false
}
}
if (e != null && e.isCtrlPressed && e.isAltPressed) {
// Get the unmodified code point:
val unicodeChar = e.getUnicodeChar(0).toChar()
if (unicodeChar == 'f'/* full screen */) {
termTab?.requireToggleFullScreen()
} else if (unicodeChar == 'v') {
termTab?.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
changeFontSize(true)
} else if (unicodeChar == '-') {
changeFontSize(false)
}
}
return false
}
override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean {
return handleVirtualKeys(keyCode, e, false)
}
override fun readControlKey(): Boolean {
return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown
}
override fun readAltKey(): Boolean {
return (extraKeysView != null && extraKeysView!!.readAltButton()) || mVirtualFnKeyDown
}
override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean {
if (mVirtualFnKeyDown) {
var resultingKeyCode: Int = -1
var resultingCodePoint: Int = -1
var altDown = false
val lowerCase = Character.toLowerCase(codePoint)
when (lowerCase.toChar()) {
// Arrow keys.
'w' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP
'a' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT
's' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN
'd' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT
// Page up and down.
'p' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP
'n' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN
// Some special keys:
't' -> resultingKeyCode = KeyEvent.KEYCODE_TAB
'i' -> resultingKeyCode = KeyEvent.KEYCODE_INSERT
'h' -> resultingCodePoint = '~'.toInt()
// Special characters to input.
'u' -> resultingCodePoint = '_'.toInt()
'l' -> resultingCodePoint = '|'.toInt()
// Function keys.
'1', '2', '3', '4', '5', '6', '7', '8', '9' -> resultingKeyCode = codePoint - '1'.toInt() + KeyEvent.KEYCODE_F1
'0' -> resultingKeyCode = KeyEvent.KEYCODE_F10
// Other special keys.
'e' -> resultingCodePoint = 27 /*Escape*/
'.' -> resultingCodePoint = 28 /*^.*/
'b' // alt+b, jumping backward in readline.
, 'f' // alf+f, jumping forward in readline.
, 'x' // alt+x, common in emacs.
-> {
resultingCodePoint = lowerCase
altDown = true
}
// Volume control.
'v' -> {
resultingCodePoint = -1
val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI)
}
}
if (resultingKeyCode != -1) {
if (session != null) {
val term = session.emulator
session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode, term.isKeypadApplicationMode))
}
} else if (resultingCodePoint != -1) {
session?.writeCodePoint(altDown, resultingCodePoint)
}
return true
}
return false
}
override fun onLongPress(event: MotionEvent?): Boolean {
// TODO
return false
}
private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean {
if (event == null) {
return false
}
val inputDevice = event.device
if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
return false
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down
return true
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down
return true
}
return false
}
fun updateSuggestions(title: String?, force: Boolean = false) {
if (extraKeysView == null || title == null || title.isEmpty()) {
return
}
if (lastTitle != title || force) {
removeSuggestions()
EksKeysManager.showShortcutKeys(title, extraKeysView)
extraKeysView?.updateButtons()
lastTitle = title
}
}
fun removeSuggestions() {
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)
}
}

BIN
icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB