mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: telnet character mode
This commit is contained in:
@@ -2,18 +2,15 @@ package app.termora.plugin.internal.telnet
|
|||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
import app.termora.account.AccountOwner
|
import app.termora.account.AccountOwner
|
||||||
import app.termora.keymgr.KeyManager
|
|
||||||
import app.termora.plugin.internal.BasicProxyOption
|
import app.termora.plugin.internal.BasicProxyOption
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatComboBox
|
import com.formdev.flatlaf.extras.components.FlatComboBox
|
||||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||||
import com.jgoodies.forms.builder.FormBuilder
|
import com.jgoodies.forms.builder.FormBuilder
|
||||||
import com.jgoodies.forms.layout.FormLayout
|
import com.jgoodies.forms.layout.FormLayout
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.awt.KeyboardFocusManager
|
import java.awt.KeyboardFocusManager
|
||||||
import java.awt.Window
|
|
||||||
import java.awt.event.ComponentAdapter
|
import java.awt.event.ComponentAdapter
|
||||||
import java.awt.event.ComponentEvent
|
import java.awt.event.ComponentEvent
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
@@ -25,7 +22,6 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
// telnet 不支持代理密码
|
// telnet 不支持代理密码
|
||||||
private val proxyOption = BasicProxyOption(authenticationTypes = listOf())
|
private val proxyOption = BasicProxyOption(authenticationTypes = listOf())
|
||||||
private val terminalOption = TerminalOption()
|
private val terminalOption = TerminalOption()
|
||||||
private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addOption(generalOption)
|
addOption(generalOption)
|
||||||
@@ -69,7 +65,10 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
env = terminalOption.environmentTextArea.text,
|
env = terminalOption.environmentTextArea.text,
|
||||||
startupCommand = terminalOption.startupCommandTextField.text,
|
startupCommand = terminalOption.startupCommandTextField.text,
|
||||||
serialComm = serialComm,
|
serialComm = serialComm,
|
||||||
extras = mutableMapOf("backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name)
|
extras = mutableMapOf(
|
||||||
|
"backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name,
|
||||||
|
"character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return Host(
|
return Host(
|
||||||
@@ -108,6 +107,8 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
terminalOption.startupCommandTextField.text = host.options.startupCommand
|
terminalOption.startupCommandTextField.text = host.options.startupCommand
|
||||||
terminalOption.backspaceComboBox.selectedItem =
|
terminalOption.backspaceComboBox.selectedItem =
|
||||||
Backspace.valueOf(host.options.extras["backspace"] ?: Backspace.Delete.name)
|
Backspace.valueOf(host.options.extras["backspace"] ?: Backspace.Delete.name)
|
||||||
|
terminalOption.characterAtATimeTextField.selectedItem =
|
||||||
|
host.options.extras["character-at-a-time"]?.toBooleanStrictOrNull() ?: false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +187,6 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
val usernameTextField = OutlineTextField(128)
|
val usernameTextField = OutlineTextField(128)
|
||||||
val hostTextField = OutlineTextField(255)
|
val hostTextField = OutlineTextField(255)
|
||||||
val passwordTextField = OutlinePasswordField(255)
|
val passwordTextField = OutlinePasswordField(255)
|
||||||
val publicKeyComboBox = OutlineComboBox<String>()
|
|
||||||
val remarkTextArea = FixedLengthTextArea(512)
|
val remarkTextArea = FixedLengthTextArea(512)
|
||||||
val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
|
val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
|
||||||
|
|
||||||
@@ -198,30 +198,6 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
private fun initView() {
|
private fun initView() {
|
||||||
add(getCenterComponent(), BorderLayout.CENTER)
|
add(getCenterComponent(), BorderLayout.CENTER)
|
||||||
|
|
||||||
publicKeyComboBox.isEditable = false
|
|
||||||
|
|
||||||
publicKeyComboBox.renderer = object : DefaultListCellRenderer() {
|
|
||||||
override fun getListCellRendererComponent(
|
|
||||||
list: JList<*>?,
|
|
||||||
value: Any?,
|
|
||||||
index: Int,
|
|
||||||
isSelected: Boolean,
|
|
||||||
cellHasFocus: Boolean
|
|
||||||
): Component {
|
|
||||||
var text = StringUtils.EMPTY
|
|
||||||
if (value is String) {
|
|
||||||
text = KeyManager.getInstance().getOhKeyPair(value)?.name ?: text
|
|
||||||
}
|
|
||||||
return super.getListCellRendererComponent(
|
|
||||||
list,
|
|
||||||
text,
|
|
||||||
index,
|
|
||||||
isSelected,
|
|
||||||
cellHasFocus
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticationTypeComboBox.renderer = object : DefaultListCellRenderer() {
|
authenticationTypeComboBox.renderer = object : DefaultListCellRenderer() {
|
||||||
override fun getListCellRendererComponent(
|
override fun getListCellRendererComponent(
|
||||||
list: JList<*>?,
|
list: JList<*>?,
|
||||||
@@ -340,6 +316,7 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
val charsetComboBox = JComboBox<String>()
|
val charsetComboBox = JComboBox<String>()
|
||||||
val backspaceComboBox = JComboBox<Backspace>()
|
val backspaceComboBox = JComboBox<Backspace>()
|
||||||
val startupCommandTextField = OutlineTextField()
|
val startupCommandTextField = OutlineTextField()
|
||||||
|
val characterAtATimeTextField = YesOrNoComboBox()
|
||||||
val environmentTextArea = FixedLengthTextArea(2048)
|
val environmentTextArea = FixedLengthTextArea(2048)
|
||||||
|
|
||||||
|
|
||||||
@@ -355,6 +332,7 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
backspaceComboBox.addItem(Backspace.Backspace)
|
backspaceComboBox.addItem(Backspace.Backspace)
|
||||||
backspaceComboBox.addItem(Backspace.VT220)
|
backspaceComboBox.addItem(Backspace.VT220)
|
||||||
|
|
||||||
|
characterAtATimeTextField.selectedItem = false
|
||||||
|
|
||||||
environmentTextArea.setFocusTraversalKeys(
|
environmentTextArea.setFocusTraversalKeys(
|
||||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||||
@@ -409,6 +387,8 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
|||||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows)
|
.add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows)
|
||||||
.add(backspaceComboBox).xy(3, rows).apply { rows += step }
|
.add(backspaceComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.terminal.character-mode")}:").xy(1, rows)
|
||||||
|
.add(characterAtATimeTextField).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora.plugin.internal.telnet
|
package app.termora.plugin.internal.telnet
|
||||||
|
|
||||||
import app.termora.terminal.StreamPtyConnector
|
import app.termora.terminal.StreamPtyConnector
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
import org.apache.commons.net.telnet.TelnetClient
|
import org.apache.commons.net.telnet.TelnetClient
|
||||||
import org.apache.commons.net.telnet.TelnetOption
|
import org.apache.commons.net.telnet.TelnetOption
|
||||||
import org.apache.commons.net.telnet.WindowSizeOptionHandler
|
import org.apache.commons.net.telnet.WindowSizeOptionHandler
|
||||||
@@ -9,18 +10,27 @@ import java.nio.charset.Charset
|
|||||||
|
|
||||||
class TelnetStreamPtyConnector(
|
class TelnetStreamPtyConnector(
|
||||||
private val telnet: TelnetClient,
|
private val telnet: TelnetClient,
|
||||||
private val charset: Charset
|
private val charset: Charset,
|
||||||
) :
|
private val characterMode: Boolean,
|
||||||
StreamPtyConnector(telnet.inputStream, telnet.outputStream) {
|
) : StreamPtyConnector(telnet.inputStream, telnet.outputStream) {
|
||||||
private val reader = InputStreamReader(telnet.inputStream, getCharset())
|
|
||||||
|
private val reader = InputStreamReader(telnet.inputStream, charset)
|
||||||
|
|
||||||
|
|
||||||
override fun read(buffer: CharArray): Int {
|
override fun read(buffer: CharArray): Int {
|
||||||
return reader.read(buffer)
|
return reader.read(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: ByteArray, offset: Int, len: Int) {
|
override fun write(buffer: ByteArray, offset: Int, len: Int) {
|
||||||
output.write(buffer, offset, len)
|
if (characterMode) {
|
||||||
output.flush()
|
for (i in offset until len + offset) {
|
||||||
|
output.write(byteArrayOf(buffer[i]))
|
||||||
|
output.flush()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output.write(buffer, offset, len)
|
||||||
|
output.flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resize(rows: Int, cols: Int) {
|
override fun resize(rows: Int, cols: Int) {
|
||||||
@@ -33,10 +43,13 @@ class TelnetStreamPtyConnector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
IOUtils.closeQuietly(input)
|
||||||
|
IOUtils.closeQuietly(output)
|
||||||
telnet.disconnect()
|
telnet.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCharset(): Charset {
|
override fun getCharset(): Charset {
|
||||||
return charset
|
return charset
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import java.net.InetSocketAddress
|
|||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
class TelnetTerminalTab(
|
class TelnetTerminalTab(
|
||||||
windowScope: WindowScope, host: Host,
|
windowScope: WindowScope, host: Host,
|
||||||
) : PtyHostTerminalTab(windowScope, host) {
|
) : PtyHostTerminalTab(windowScope, host) {
|
||||||
@@ -32,12 +33,18 @@ class TelnetTerminalTab(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val characterMode = host.options.extras["character-at-a-time"]?.toBooleanStrictOrNull() ?: false
|
||||||
|
|
||||||
val termtype = host.options.envs()["TERM"] ?: "xterm-256color"
|
val termtype = host.options.envs()["TERM"] ?: "xterm-256color"
|
||||||
val ttopt = TerminalTypeOptionHandler(termtype, false, false, true, false)
|
val ttopt = TerminalTypeOptionHandler(termtype, false, false, true, false)
|
||||||
val echoopt = EchoOptionHandler(false, true, false, true)
|
val echoopt = EchoOptionHandler(false, true, false, true)
|
||||||
val gaopt = SuppressGAOptionHandler(true, true, true, true)
|
val gaopt = SuppressGAOptionHandler(true, true, true, true)
|
||||||
val wsopt = WindowSizeOptionHandler(winSize.cols, winSize.rows, true, false, true, false)
|
val wsopt = WindowSizeOptionHandler(winSize.cols, winSize.rows, true, false, true, false)
|
||||||
|
val bopt = SimpleOptionHandler(TelnetOption.BINARY, true, false, true, false)
|
||||||
|
val fcopt = SimpleOptionHandler(TelnetOption.REMOTE_FLOW_CONTROL, true, true, false, false)
|
||||||
|
|
||||||
|
telnet.addOptionHandler(bopt)
|
||||||
|
telnet.addOptionHandler(fcopt)
|
||||||
telnet.addOptionHandler(ttopt)
|
telnet.addOptionHandler(ttopt)
|
||||||
telnet.addOptionHandler(echoopt)
|
telnet.addOptionHandler(echoopt)
|
||||||
telnet.addOptionHandler(gaopt)
|
telnet.addOptionHandler(gaopt)
|
||||||
@@ -45,6 +52,7 @@ class TelnetTerminalTab(
|
|||||||
|
|
||||||
telnet.connect(host.host, host.port)
|
telnet.connect(host.host, host.port)
|
||||||
telnet.keepAlive = true
|
telnet.keepAlive = true
|
||||||
|
telnet.tcpNoDelay = characterMode
|
||||||
|
|
||||||
val encoder = terminal.getKeyEncoder()
|
val encoder = terminal.getKeyEncoder()
|
||||||
if (encoder is KeyEncoderImpl) {
|
if (encoder is KeyEncoderImpl) {
|
||||||
@@ -56,7 +64,8 @@ class TelnetTerminalTab(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptyConnectorFactory.decorate(TelnetStreamPtyConnector(telnet, telnet.charset))
|
|
||||||
|
return ptyConnectorFactory.decorate(TelnetStreamPtyConnector(telnet, telnet.charset, characterMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ termora.new-host.proxy=Proxy
|
|||||||
termora.new-host.terminal=${termora.settings.terminal}
|
termora.new-host.terminal=${termora.settings.terminal}
|
||||||
termora.new-host.terminal.encoding=Encoding
|
termora.new-host.terminal.encoding=Encoding
|
||||||
termora.new-host.terminal.backspace=Backspace
|
termora.new-host.terminal.backspace=Backspace
|
||||||
|
termora.new-host.terminal.character-mode=Character-at-a-time
|
||||||
termora.new-host.terminal.heartbeat-interval=Heartbeat Interval
|
termora.new-host.terminal.heartbeat-interval=Heartbeat Interval
|
||||||
termora.new-host.terminal.startup-commands=Startup Command
|
termora.new-host.terminal.startup-commands=Startup Command
|
||||||
termora.new-host.terminal.env=Environment
|
termora.new-host.terminal.env=Environment
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ termora.new-host.proxy=代理
|
|||||||
termora.new-host.terminal=${termora.settings.terminal}
|
termora.new-host.terminal=${termora.settings.terminal}
|
||||||
termora.new-host.terminal.encoding=编码
|
termora.new-host.terminal.encoding=编码
|
||||||
termora.new-host.terminal.backspace=退格键
|
termora.new-host.terminal.backspace=退格键
|
||||||
|
termora.new-host.terminal.character-mode=单字符模式
|
||||||
termora.new-host.terminal.heartbeat-interval=心跳间隔
|
termora.new-host.terminal.heartbeat-interval=心跳间隔
|
||||||
termora.new-host.terminal.startup-commands=启动命令
|
termora.new-host.terminal.startup-commands=启动命令
|
||||||
termora.new-host.terminal.env=环境
|
termora.new-host.terminal.env=环境
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ termora.new-host.proxy=代理
|
|||||||
termora.new-host.terminal=${termora.settings.terminal}
|
termora.new-host.terminal=${termora.settings.terminal}
|
||||||
termora.new-host.terminal.encoding=編碼
|
termora.new-host.terminal.encoding=編碼
|
||||||
termora.new-host.terminal.backspace=退格鍵
|
termora.new-host.terminal.backspace=退格鍵
|
||||||
|
termora.new-host.terminal.character-mode=單字元模式
|
||||||
termora.new-host.terminal.startup-commands=啟動命令
|
termora.new-host.terminal.startup-commands=啟動命令
|
||||||
termora.new-host.terminal.heartbeat-interval=心跳間隔
|
termora.new-host.terminal.heartbeat-interval=心跳間隔
|
||||||
termora.new-host.terminal.env=環境
|
termora.new-host.terminal.env=環境
|
||||||
|
|||||||
Reference in New Issue
Block a user