Fix: Re-create every TermSession when switch tabs
This commit is contained in:
@ -9,6 +9,7 @@ import android.support.v7.widget.Toolbar
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import de.mrapp.android.tabswitcher.*
|
import de.mrapp.android.tabswitcher.*
|
||||||
|
import io.neoterm.tab.TermTab
|
||||||
import io.neoterm.tab.TermTabDecorator
|
import io.neoterm.tab.TermTabDecorator
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +41,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
tab.termSession?.finishIfRunning()
|
||||||
|
tab.viewClient?.termView = null
|
||||||
|
tab.viewClient?.extraKeysView = null
|
||||||
|
tab.sessionCallback?.termView = null
|
||||||
|
tab.termSession = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAllTabsRemoved(tabSwitcher: TabSwitcher, tabs: Array<out Tab>, animation: Animation) {
|
override fun onAllTabsRemoved(tabSwitcher: TabSwitcher, tabs: Array<out Tab>, animation: Animation) {
|
||||||
@ -76,10 +84,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createTab(index: Int): Tab {
|
private fun createTab(index: Int): Tab {
|
||||||
val tab = Tab("Neo Term #" + index)
|
val tab = TermTab("Neo Term #" + index)
|
||||||
tab.isCloseable = true
|
tab.isCloseable = true
|
||||||
tab.parameters = Bundle()
|
tab.parameters = Bundle()
|
||||||
tab.parameters?.putInt("type", TermTabDecorator.TYPE_NEW)
|
|
||||||
tab.setBackgroundColor(ContextCompat.getColor(this, R.color.tab_background_color))
|
tab.setBackgroundColor(ContextCompat.getColor(this, R.color.tab_background_color))
|
||||||
tab.setTitleTextColor(ContextCompat.getColor(this, R.color.tab_title_text_color))
|
tab.setTitleTextColor(ContextCompat.getColor(this, R.color.tab_title_text_color))
|
||||||
return tab
|
return tab
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package io.neoterm.tab
|
||||||
|
|
||||||
|
import io.neoterm.terminal.TerminalSession
|
||||||
|
import io.neoterm.view.TerminalView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
class TermSessionChangedCallback : TerminalSession.SessionChangedCallback {
|
||||||
|
var termView: TerminalView? = null
|
||||||
|
|
||||||
|
override fun onTextChanged(changedSession: TerminalSession?) {
|
||||||
|
termView?.onScreenUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTitleChanged(changedSession: TerminalSession?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClipboardText(session: TerminalSession?, text: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBell(session: TerminalSession?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onColorsChanged(session: TerminalSession?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
app/src/main/java/io/neoterm/tab/TermTab.kt
Normal file
32
app/src/main/java/io/neoterm/tab/TermTab.kt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package io.neoterm.tab
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import de.mrapp.android.tabswitcher.Tab
|
||||||
|
import io.neoterm.terminal.TerminalSession
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TermTab : Tab {
|
||||||
|
var termSession: TerminalSession? = null
|
||||||
|
var sessionCallback: TermSessionChangedCallback? = null
|
||||||
|
var viewClient: TermViewClient? = null
|
||||||
|
|
||||||
|
constructor(title: CharSequence) : super(title)
|
||||||
|
|
||||||
|
private constructor(source: Parcel) : super(source)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CREATOR: Parcelable.Creator<TermTab> = object : Parcelable.Creator<TermTab> {
|
||||||
|
override fun createFromParcel(source: Parcel): TermTab {
|
||||||
|
return TermTab(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<TermTab?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,9 @@ import android.graphics.Color
|
|||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import de.mrapp.android.tabswitcher.Tab
|
import de.mrapp.android.tabswitcher.Tab
|
||||||
import de.mrapp.android.tabswitcher.TabSwitcher
|
import de.mrapp.android.tabswitcher.TabSwitcher
|
||||||
import de.mrapp.android.tabswitcher.TabSwitcherDecorator
|
import de.mrapp.android.tabswitcher.TabSwitcherDecorator
|
||||||
@ -15,7 +16,6 @@ import io.neoterm.R
|
|||||||
import io.neoterm.terminal.TerminalSession
|
import io.neoterm.terminal.TerminalSession
|
||||||
import io.neoterm.view.ExtraKeysView
|
import io.neoterm.view.ExtraKeysView
|
||||||
import io.neoterm.view.TerminalView
|
import io.neoterm.view.TerminalView
|
||||||
import io.neoterm.view.TerminalViewClient
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
@ -42,11 +42,11 @@ class TermTabDecorator(val context: MainActivity) : TabSwitcherDecorator() {
|
|||||||
|
|
||||||
val terminalView = findViewById<TerminalView>(R.id.terminal_view)
|
val terminalView = findViewById<TerminalView>(R.id.terminal_view)
|
||||||
val extraKeysView = findViewById<ExtraKeysView>(R.id.extra_keys)
|
val extraKeysView = findViewById<ExtraKeysView>(R.id.extra_keys)
|
||||||
|
setupTerminalView(tab, terminalView, extraKeysView)
|
||||||
setupTerminalView(terminalView, extraKeysView)
|
terminalView.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTerminalView(view: TerminalView?, extraKeysView: ExtraKeysView?) {
|
private fun setupTerminalView(tab: Tab, view: TerminalView?, extraKeysView: ExtraKeysView?) {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -54,115 +54,34 @@ class TermTabDecorator(val context: MainActivity) : TabSwitcherDecorator() {
|
|||||||
view.textSize = 30
|
view.textSize = 30
|
||||||
view.setTypeface(Typeface.MONOSPACE)
|
view.setTypeface(Typeface.MONOSPACE)
|
||||||
|
|
||||||
val session = TerminalSession("/system/bin/sh", "/",
|
val termTab = tab as TermTab
|
||||||
|
|
||||||
|
// 复用前一次的 TermSession
|
||||||
|
if (termTab.sessionCallback == null) {
|
||||||
|
termTab.sessionCallback = TermSessionChangedCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
termTab.sessionCallback?.termView = view
|
||||||
|
termTab.termSession = termTab.termSession ?: TerminalSession("/system/bin/sh", "/",
|
||||||
arrayOf("/system/bin/sh"),
|
arrayOf("/system/bin/sh"),
|
||||||
arrayOf("TERM=screen", "HOME=" + context.filesDir),
|
arrayOf("TERM=screen", "HOME=" + context.filesDir), termTab.sessionCallback)
|
||||||
object : TerminalSession.SessionChangedCallback {
|
|
||||||
override fun onBell(session: TerminalSession?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClipboardText(session: TerminalSession?, text: String?) {
|
// 复用上一次的 TermViewClient
|
||||||
|
if (termTab.viewClient == null) {
|
||||||
|
termTab.viewClient = TermViewClient(context)
|
||||||
}
|
}
|
||||||
|
termTab.viewClient?.termView = view
|
||||||
|
termTab.viewClient?.extraKeysView = extraKeysView
|
||||||
|
|
||||||
override fun onColorsChanged(session: TerminalSession?) {
|
view.setOnKeyListener(termTab.viewClient)
|
||||||
}
|
view.attachSession(termTab.termSession)
|
||||||
|
|
||||||
override fun onSessionFinished(finishedSession: TerminalSession?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(changedSession: TerminalSession?) {
|
|
||||||
view.onScreenUpdated()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTitleChanged(changedSession: TerminalSession?) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
view.setOnKeyListener(object : TerminalViewClient {
|
|
||||||
internal var mVirtualControlKeyDown: Boolean = false
|
|
||||||
internal var mVirtualFnKeyDown: Boolean = false
|
|
||||||
|
|
||||||
override fun onScale(scale: Float): Float {
|
|
||||||
if (scale < 0.9f || scale > 1.1f) {
|
|
||||||
val increase = scale > 1f
|
|
||||||
val changedSize = (if (increase) 1 else -1) * 2
|
|
||||||
view.textSize = view.textSize + changedSize
|
|
||||||
return 1.0f
|
|
||||||
}
|
|
||||||
return scale
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent?) {
|
|
||||||
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
|
||||||
.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldBackButtonBeMappedToEscape(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun copyModeChanged(copyMode: Boolean) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
|
|
||||||
// TODO
|
|
||||||
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 {
|
|
||||||
// TODO
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
view.attachSession(session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewTypeCount(): Int {
|
override fun getViewTypeCount(): Int {
|
||||||
return 2
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewType(tab: Tab, index: Int): Int {
|
override fun getViewType(tab: Tab, index: Int): Int {
|
||||||
return tab.parameters?.getInt("type")!!
|
return 0
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val TYPE_LOADED = 1
|
|
||||||
val TYPE_NEW = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
89
app/src/main/java/io/neoterm/tab/TermViewClient.kt
Normal file
89
app/src/main/java/io/neoterm/tab/TermViewClient.kt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package io.neoterm.tab
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.InputDevice
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import io.neoterm.terminal.TerminalSession
|
||||||
|
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
|
||||||
|
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
|
||||||
|
val changedSize = (if (increase) 1 else -1) * 2
|
||||||
|
termView!!.textSize = termView!!.textSize + changedSize
|
||||||
|
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 false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyModeChanged(copyMode: Boolean) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
|
||||||
|
// TODO
|
||||||
|
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 {
|
||||||
|
// TODO
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -237,7 +237,7 @@ public class Tab implements Parcelable {
|
|||||||
* The parcel, the tab should be created from, as an instance of the class {@link
|
* The parcel, the tab should be created from, as an instance of the class {@link
|
||||||
* Parcel}. The parcel may not be null
|
* Parcel}. The parcel may not be null
|
||||||
*/
|
*/
|
||||||
private Tab(@NonNull final Parcel source) {
|
protected Tab(@NonNull final Parcel source) {
|
||||||
this.title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
|
this.title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
|
||||||
this.iconId = source.readInt();
|
this.iconId = source.readInt();
|
||||||
this.iconBitmap = source.readParcelable(getClass().getClassLoader());
|
this.iconBitmap = source.readParcelable(getClass().getClassLoader());
|
||||||
|
Reference in New Issue
Block a user