From 7f1317a9a741f4190f71de20e36a18794e8dad90 Mon Sep 17 00:00:00 2001 From: hstyi Date: Mon, 14 Jul 2025 11:02:21 +0800 Subject: [PATCH] chore: improve terminal options --- plugins/serial/build.gradle.kts | 2 +- .../plugins/serial/SerialHostOptionsPane.kt | 74 +------ .../kotlin/app/termora/PtyHostTerminalTab.kt | 13 ++ .../termora/plugin/internal/AltKeyModifier.kt | 6 + .../plugin/internal/BasicTerminalOption.kt | 191 ++++++++++++++++++ .../internal/local/LocalHostOptionsPane.kt | 106 ++-------- .../plugin/internal/ssh/SSHHostOptionsPane.kt | 129 ++---------- .../internal/telnet/TelnetHostOptionsPane.kt | 121 ++--------- .../plugin/internal/wsl/WSLHostOptionsPane.kt | 97 ++------- .../kotlin/app/termora/terminal/DataKey.kt | 6 + .../terminal/panel/TerminalPanelKeyAdapter.kt | 6 +- src/main/resources/i18n/messages.properties | 3 + .../resources/i18n/messages_zh_CN.properties | 3 + .../resources/i18n/messages_zh_TW.properties | 3 + 14 files changed, 311 insertions(+), 449 deletions(-) create mode 100644 src/main/kotlin/app/termora/plugin/internal/AltKeyModifier.kt create mode 100644 src/main/kotlin/app/termora/plugin/internal/BasicTerminalOption.kt diff --git a/plugins/serial/build.gradle.kts b/plugins/serial/build.gradle.kts index f9b4db1..2d7e6a8 100644 --- a/plugins/serial/build.gradle.kts +++ b/plugins/serial/build.gradle.kts @@ -4,7 +4,7 @@ plugins { -project.version = "0.0.1" +project.version = "0.0.2" dependencies { diff --git a/plugins/serial/src/main/kotlin/app/termora/plugins/serial/SerialHostOptionsPane.kt b/plugins/serial/src/main/kotlin/app/termora/plugins/serial/SerialHostOptionsPane.kt index bed0769..9c35a62 100644 --- a/plugins/serial/src/main/kotlin/app/termora/plugins/serial/SerialHostOptionsPane.kt +++ b/plugins/serial/src/main/kotlin/app/termora/plugins/serial/SerialHostOptionsPane.kt @@ -1,7 +1,9 @@ package app.termora.plugins.serial import app.termora.* +import app.termora.plugin.internal.AltKeyModifier import app.termora.plugin.internal.BasicGeneralOption +import app.termora.plugin.internal.BasicTerminalOption import com.fazecast.jSerialComm.SerialPort import com.formdev.flatlaf.FlatClientProperties import com.jgoodies.forms.builder.FormBuilder @@ -15,12 +17,15 @@ import java.awt.BorderLayout import java.awt.Component import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent -import java.nio.charset.Charset import javax.swing.* class SerialHostOptionsPane : OptionsPane() { private val generalOption = BasicGeneralOption() - private val terminalOption = TerminalOption() + private val terminalOption = BasicTerminalOption().apply { + showCharsetComboBox = true + showStartupCommandTextField = true + init() + } private val serialCommOption = SerialCommOption() init { @@ -48,6 +53,10 @@ class SerialHostOptionsPane : OptionsPane() { encoding = terminalOption.charsetComboBox.selectedItem as String, startupCommand = terminalOption.startupCommandTextField.text, serialComm = serialComm, + extras = mutableMapOf( + "altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString() + ?: AltKeyModifier.EightBit.name), + ) ) return Host( @@ -128,67 +137,6 @@ class SerialHostOptionsPane : OptionsPane() { } - protected inner class TerminalOption : JPanel(BorderLayout()), Option { - val charsetComboBox = JComboBox() - val startupCommandTextField = OutlineTextField() - - - init { - initView() - initEvents() - } - - private fun initView() { - add(getCenterComponent(), BorderLayout.CENTER) - - - for (e in Charset.availableCharsets()) { - charsetComboBox.addItem(e.key) - } - - charsetComboBox.selectedItem = "UTF-8" - - } - - private fun initEvents() { - - } - - - override fun getIcon(isSelected: Boolean): Icon { - return Icons.terminal - } - - override fun getTitle(): String { - return I18n.getString("termora.new-host.terminal") - } - - override fun getJComponent(): JComponent { - return this - } - - private fun getCenterComponent(): JComponent { - val layout = FormLayout( - "left:pref, $FORM_MARGIN, default:grow", - "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" - ) - - var rows = 1 - val step = 2 - val panel = FormBuilder.create().layout(layout) - .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) - .add(charsetComboBox).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) - .add(startupCommandTextField).xy(3, rows).apply { rows += step } - .apply { rows += step } - .build() - - - return panel - } - } - - protected inner class SerialCommOption : JPanel(BorderLayout()), Option { val serialPortComboBox = OutlineComboBox() val baudRateComboBox = OutlineComboBox() diff --git a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt index 7f2a816..6503505 100644 --- a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt +++ b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt @@ -1,6 +1,7 @@ package app.termora import app.termora.actions.DataProviders +import app.termora.plugin.internal.AltKeyModifier import app.termora.terminal.* import kotlinx.coroutines.* import kotlinx.coroutines.swing.Swing @@ -46,6 +47,9 @@ abstract class PtyHostTerminalTab( // 开启 reader startPtyConnectorReader() + // 修饰 + terminalKeyModifiers() + // 启动命令 if (host.options.startupCommand.isNotBlank()) { coroutineScope.launch(Dispatchers.IO) { @@ -155,6 +159,15 @@ abstract class PtyHostTerminalTab( ptyConnector.write(bytes) } + open fun terminalKeyModifiers() { + val altModifier = host.options.extras["altModifier"] + if (altModifier == AltKeyModifier.CharactersPrecededByESC.name) { + terminalModel.setData(DataKey.AltModifier, AltKeyModifier.CharactersPrecededByESC) + } else { + terminalModel.setData(DataKey.AltModifier, AltKeyModifier.EightBit) + } + } + override fun canReconnect(): Boolean { return true } diff --git a/src/main/kotlin/app/termora/plugin/internal/AltKeyModifier.kt b/src/main/kotlin/app/termora/plugin/internal/AltKeyModifier.kt new file mode 100644 index 0000000..52a887a --- /dev/null +++ b/src/main/kotlin/app/termora/plugin/internal/AltKeyModifier.kt @@ -0,0 +1,6 @@ +package app.termora.plugin.internal + +enum class AltKeyModifier { + EightBit, + CharactersPrecededByESC, +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/BasicTerminalOption.kt b/src/main/kotlin/app/termora/plugin/internal/BasicTerminalOption.kt new file mode 100644 index 0000000..8455b25 --- /dev/null +++ b/src/main/kotlin/app/termora/plugin/internal/BasicTerminalOption.kt @@ -0,0 +1,191 @@ +package app.termora.plugin.internal + +import app.termora.* +import app.termora.OptionsPane.Companion.FORM_MARGIN +import app.termora.OptionsPane.Option +import app.termora.plugin.internal.telnet.TelnetHostOptionsPane.Backspace +import com.formdev.flatlaf.extras.components.FlatTabbedPane +import com.formdev.flatlaf.ui.FlatTextBorder +import com.jgoodies.forms.builder.FormBuilder +import com.jgoodies.forms.layout.FormLayout +import java.awt.BorderLayout +import java.awt.Component +import java.awt.KeyboardFocusManager +import java.nio.charset.Charset +import javax.swing.* + +class BasicTerminalOption() : JPanel(BorderLayout()), Option { + + var showCharsetComboBox: Boolean = false + var showStartupCommandTextField: Boolean = false + var showHeartbeatIntervalTextField: Boolean = false + var showEnvironmentTextArea: Boolean = false + var showLoginScripts: Boolean = false + var showBackspaceComboBox: Boolean = false + var showCharacterAtATimeTextField: Boolean = false + var showAltModifierComboBox: Boolean = true + + val charsetComboBox = JComboBox() + val startupCommandTextField = OutlineTextField() + val heartbeatIntervalTextField = IntSpinner(30, minimum = 3, maximum = Int.MAX_VALUE) + val environmentTextArea = FixedLengthTextArea(2048) + val loginScripts = mutableListOf() + val backspaceComboBox = JComboBox() + val altModifierComboBox = JComboBox() + val characterAtATimeTextField = YesOrNoComboBox() + + + private val loginScriptPanel = LoginScriptPanel(loginScripts) + private val tabbed = FlatTabbedPane() + + fun init() { + initView() + initEvents() + } + + private fun initView() { + + if (showLoginScripts) { + tabbed.styleMap = mapOf( + "focusColor" to DynamicColor("TabbedPane.background"), + "hoverColor" to DynamicColor("TabbedPane.background"), + ) + tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4 + putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder()) + tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent()) + tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel) + add(tabbed, BorderLayout.CENTER) + } else { + add(getCenterComponent(), BorderLayout.CENTER) + } + + if (showAltModifierComboBox) { + altModifierComboBox.addItem(AltKeyModifier.EightBit) + altModifierComboBox.addItem(AltKeyModifier.CharactersPrecededByESC) + + altModifierComboBox.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component? { + var text = value?.toString() ?: value + if (value == AltKeyModifier.CharactersPrecededByESC) { + text = I18n.getString("termora.new-host.terminal.alt-modifier.by-esc") + } else if (value == AltKeyModifier.EightBit) { + text = I18n.getString("termora.new-host.terminal.alt-modifier.eight-bit") + } + return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus) + } + } + } + + + if (showBackspaceComboBox) { + backspaceComboBox.addItem(Backspace.Delete) + backspaceComboBox.addItem(Backspace.Backspace) + backspaceComboBox.addItem(Backspace.VT220) + } + + if (showCharacterAtATimeTextField) { + characterAtATimeTextField.selectedItem = false + } + + environmentTextArea.setFocusTraversalKeys( + KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) + ) + environmentTextArea.setFocusTraversalKeys( + KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) + ) + + environmentTextArea.rows = 8 + environmentTextArea.lineWrap = true + environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) + + for (e in Charset.availableCharsets()) { + charsetComboBox.addItem(e.key) + } + + charsetComboBox.selectedItem = "UTF-8" + + } + + private fun initEvents() { + + } + + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.terminal + } + + override fun getTitle(): String { + return I18n.getString("termora.new-host.terminal") + } + + override fun getJComponent(): JComponent { + return this + } + + private fun getCenterComponent(): JComponent { + val layout = FormLayout( + "left:pref, $FORM_MARGIN, default:grow", + "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" + ) + + var rows = 1 + val step = 2 + val builder = FormBuilder.create().layout(layout) + if (showLoginScripts) { + builder.border(BorderFactory.createEmptyBorder(6, 8, 6, 8)) + } + + if (showCharsetComboBox) { + builder.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) + .add(charsetComboBox).xy(3, rows).apply { rows += step } + } + + if (showAltModifierComboBox) { + builder.add("${I18n.getString("termora.new-host.terminal.alt-modifier")}:").xy(1, rows) + .add(altModifierComboBox).xy(3, rows).apply { rows += step } + } + + if (showBackspaceComboBox) { + builder.add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows) + .add(backspaceComboBox).xy(3, rows).apply { rows += step } + } + + if (showCharacterAtATimeTextField) { + builder + .add("${I18n.getString("termora.new-host.terminal.character-mode")}:").xy(1, rows) + .add(characterAtATimeTextField).xy(3, rows).apply { rows += step } + } + + if (showHeartbeatIntervalTextField) { + builder.add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows) + .add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step } + } + + if (showStartupCommandTextField) { + builder.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) + .add(startupCommandTextField).xy(3, rows).apply { rows += step } + } + + + if (showEnvironmentTextArea) { + builder.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) + .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) + .apply { rows += step } + } + + + return builder.build() + } + +} diff --git a/src/main/kotlin/app/termora/plugin/internal/local/LocalHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/local/LocalHostOptionsPane.kt index 2db72f5..a2427a2 100644 --- a/src/main/kotlin/app/termora/plugin/internal/local/LocalHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/local/LocalHostOptionsPane.kt @@ -1,20 +1,25 @@ package app.termora.plugin.internal.local -import app.termora.* +import app.termora.Host +import app.termora.Options +import app.termora.OptionsPane +import app.termora.SerialComm +import app.termora.plugin.internal.AltKeyModifier import app.termora.plugin.internal.BasicGeneralOption +import app.termora.plugin.internal.BasicTerminalOption import com.formdev.flatlaf.FlatClientProperties -import com.formdev.flatlaf.ui.FlatTextBorder -import com.jgoodies.forms.builder.FormBuilder -import com.jgoodies.forms.layout.FormLayout -import java.awt.BorderLayout -import java.awt.KeyboardFocusManager import java.awt.Window -import java.nio.charset.Charset -import javax.swing.* +import javax.swing.JTextField +import javax.swing.SwingUtilities internal open class LocalHostOptionsPane : OptionsPane() { protected val generalOption = BasicGeneralOption() - protected val terminalOption = TerminalOption() + private val terminalOption = BasicTerminalOption().apply { + showCharsetComboBox = true + showEnvironmentTextArea = true + showStartupCommandTextField = true + init() + } protected val owner: Window get() = SwingUtilities.getWindowAncestor(this) init { @@ -35,6 +40,10 @@ internal open class LocalHostOptionsPane : OptionsPane() { env = terminalOption.environmentTextArea.text, startupCommand = terminalOption.startupCommandTextField.text, serialComm = serialComm, + extras = mutableMapOf( + "altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString() + ?: AltKeyModifier.EightBit.name), + ) ) return Host( @@ -77,83 +86,4 @@ internal open class LocalHostOptionsPane : OptionsPane() { textField.requestFocusInWindow() } - protected inner class TerminalOption : JPanel(BorderLayout()), Option { - val charsetComboBox = JComboBox() - val startupCommandTextField = OutlineTextField() - val environmentTextArea = FixedLengthTextArea(2048) - - - init { - initView() - initEvents() - } - - private fun initView() { - add(getCenterComponent(), BorderLayout.CENTER) - - - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) - ) - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) - ) - - environmentTextArea.rows = 8 - environmentTextArea.lineWrap = true - environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) - - for (e in Charset.availableCharsets()) { - charsetComboBox.addItem(e.key) - } - - charsetComboBox.selectedItem = "UTF-8" - - } - - private fun initEvents() { - - } - - - override fun getIcon(isSelected: Boolean): Icon { - return Icons.terminal - } - - override fun getTitle(): String { - return I18n.getString("termora.new-host.terminal") - } - - override fun getJComponent(): JComponent { - return this - } - - private fun getCenterComponent(): JComponent { - val layout = FormLayout( - "left:pref, $FORM_MARGIN, default:grow", - "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" - ) - - var rows = 1 - val step = 2 - val panel = FormBuilder.create().layout(layout) - .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) - .add(charsetComboBox).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) - .add(startupCommandTextField).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) - .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) - .apply { rows += step } - .build() - - - return panel - } - } - - } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt index bf34e56..e7c111d 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt @@ -4,13 +4,14 @@ import app.termora.* import app.termora.account.AccountOwner import app.termora.keymgr.KeyManager import app.termora.keymgr.KeyManagerDialog +import app.termora.plugin.internal.AltKeyModifier import app.termora.plugin.internal.BasicProxyOption +import app.termora.plugin.internal.BasicTerminalOption import app.termora.tree.Filter import app.termora.tree.HostTreeNode import app.termora.tree.NewHostTreeDialog import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.extras.components.FlatComboBox -import com.formdev.flatlaf.extras.components.FlatTabbedPane import com.formdev.flatlaf.ui.FlatTextBorder import com.formdev.flatlaf.util.SystemInfo import com.jgoodies.forms.builder.FormBuilder @@ -21,20 +22,26 @@ import org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixDomainSocket import org.eclipse.jgit.internal.transport.sshd.agent.connector.WinPipeConnector import java.awt.* import java.awt.event.* -import java.nio.charset.Charset import javax.swing.* import javax.swing.table.DefaultTableCellRenderer import javax.swing.table.DefaultTableModel @Suppress("CascadeIf") -open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() { - protected val tunnelingOption = TunnelingOption() - protected val generalOption = GeneralOption() - protected val proxyOption = BasicProxyOption() - protected val terminalOption = TerminalOption() - protected val jumpHostsOption = JumpHostsOption() - protected val sftpOption = SFTPOption() - protected val owner: Window get() = SwingUtilities.getWindowAncestor(this) +internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() { + private val tunnelingOption = TunnelingOption() + private val generalOption = GeneralOption() + private val proxyOption = BasicProxyOption() + private val terminalOption = BasicTerminalOption().apply { + showCharsetComboBox = true + showLoginScripts = true + showEnvironmentTextArea = true + showStartupCommandTextField = true + showHeartbeatIntervalTextField = true + init() + } + private val jumpHostsOption = JumpHostsOption() + private val sftpOption = SFTPOption() + private val owner: Window get() = SwingUtilities.getWindowAncestor(this) init { addOption(generalOption) @@ -47,7 +54,7 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP } - open fun getHost(): Host { + fun getHost(): Host { val name = generalOption.nameTextField.text val protocol = SSHProtocolProvider.PROTOCOL val host = generalOption.hostTextField.text @@ -98,6 +105,10 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP enableX11Forwarding = tunnelingOption.x11ForwardingCheckBox.isSelected, x11Forwarding = tunnelingOption.x11ServerTextField.text, loginScripts = terminalOption.loginScripts, + extras = mutableMapOf( + "altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString() + ?: AltKeyModifier.EightBit.name), + ) ) return Host( @@ -486,102 +497,6 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP } - protected inner class TerminalOption : JPanel(BorderLayout()), Option { - val charsetComboBox = JComboBox() - val startupCommandTextField = OutlineTextField() - val heartbeatIntervalTextField = IntSpinner(30, minimum = 3, maximum = Int.MAX_VALUE) - val environmentTextArea = FixedLengthTextArea(2048) - val loginScripts = mutableListOf() - - private val loginScriptPanel = LoginScriptPanel(loginScripts) - private val tabbed = FlatTabbedPane() - - init { - initView() - initEvents() - } - - private fun initView() { - - - tabbed.styleMap = mapOf( - "focusColor" to DynamicColor("TabbedPane.background"), - "hoverColor" to DynamicColor("TabbedPane.background"), - ) - tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4 - putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder()) - tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent()) - tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel) - add(tabbed, BorderLayout.CENTER) - - - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) - ) - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) - ) - - environmentTextArea.rows = 8 - environmentTextArea.lineWrap = true - environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) - - for (e in Charset.availableCharsets()) { - charsetComboBox.addItem(e.key) - } - - charsetComboBox.selectedItem = "UTF-8" - - } - - private fun initEvents() { - - } - - - override fun getIcon(isSelected: Boolean): Icon { - return Icons.terminal - } - - override fun getTitle(): String { - return I18n.getString("termora.new-host.terminal") - } - - override fun getJComponent(): JComponent { - return this - } - - private fun getCenterComponent(): JComponent { - val layout = FormLayout( - "left:pref, $FORM_MARGIN, default:grow", - "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" - ) - - var rows = 1 - val step = 2 - val panel = FormBuilder.create().layout(layout) - .border(BorderFactory.createEmptyBorder(6, 8, 6, 8)) - .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) - .add(charsetComboBox).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows) - .add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) - .add(startupCommandTextField).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) - .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) - .apply { rows += step } - .build() - - - return panel - } - - } - protected inner class SFTPOption : JPanel(BorderLayout()), Option { val defaultDirectoryField = OutlineTextField(255) diff --git a/src/main/kotlin/app/termora/plugin/internal/telnet/TelnetHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/telnet/TelnetHostOptionsPane.kt index e9a4096..a5a351a 100644 --- a/src/main/kotlin/app/termora/plugin/internal/telnet/TelnetHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/telnet/TelnetHostOptionsPane.kt @@ -2,9 +2,10 @@ package app.termora.plugin.internal.telnet import app.termora.* import app.termora.account.AccountOwner +import app.termora.plugin.internal.AltKeyModifier import app.termora.plugin.internal.BasicProxyOption +import app.termora.plugin.internal.BasicTerminalOption import com.formdev.flatlaf.FlatClientProperties -import com.formdev.flatlaf.extras.components.FlatTabbedPane import com.formdev.flatlaf.ui.FlatTextBorder import com.jgoodies.forms.builder.FormBuilder import com.jgoodies.forms.layout.FormLayout @@ -12,7 +13,6 @@ import java.awt.BorderLayout import java.awt.KeyboardFocusManager import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent -import java.nio.charset.Charset import javax.swing.* class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() { @@ -20,7 +20,16 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan // telnet 不支持代理密码 private val proxyOption = BasicProxyOption(authenticationTypes = listOf()) - private val terminalOption = TerminalOption() + private val terminalOption = BasicTerminalOption().apply { + showCharsetComboBox = true + showBackspaceComboBox = true + showStartupCommandTextField = true + showCharacterAtATimeTextField = true + showEnvironmentTextArea = true + showLoginScripts = true + init() + } + init { addOption(generalOption) @@ -58,7 +67,9 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan serialComm = serialComm, extras = mutableMapOf( "backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name, - "character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false") + "character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false"), + "altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString() + ?: AltKeyModifier.EightBit.name), ) ) @@ -226,108 +237,6 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan } - private inner class TerminalOption : JPanel(BorderLayout()), Option { - val charsetComboBox = JComboBox() - val backspaceComboBox = JComboBox() - val startupCommandTextField = OutlineTextField() - val characterAtATimeTextField = YesOrNoComboBox() - val environmentTextArea = FixedLengthTextArea(2048) - val loginScripts = mutableListOf() - - private val loginScriptPanel = LoginScriptPanel(loginScripts) - - init { - initView() - initEvents() - } - - private fun initView() { - - backspaceComboBox.addItem(Backspace.Delete) - backspaceComboBox.addItem(Backspace.Backspace) - backspaceComboBox.addItem(Backspace.VT220) - - characterAtATimeTextField.selectedItem = false - - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) - ) - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) - ) - - environmentTextArea.rows = 8 - environmentTextArea.lineWrap = true - environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) - - for (e in Charset.availableCharsets()) { - charsetComboBox.addItem(e.key) - } - - charsetComboBox.selectedItem = "UTF-8" - - val tabbed = FlatTabbedPane() - tabbed.styleMap = mapOf( - "focusColor" to DynamicColor("TabbedPane.background"), - "hoverColor" to DynamicColor("TabbedPane.background"), - ) - tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4 - putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder()) - tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent()) - tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel) - add(tabbed, BorderLayout.CENTER) - - } - - private fun initEvents() { - - } - - - override fun getIcon(isSelected: Boolean): Icon { - return Icons.terminal - } - - override fun getTitle(): String { - return I18n.getString("termora.new-host.terminal") - } - - override fun getJComponent(): JComponent { - return this - } - - private fun getCenterComponent(): JComponent { - val layout = FormLayout( - "left:pref, $FORM_MARGIN, default:grow", - "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" - ) - - var rows = 1 - val step = 2 - val panel = FormBuilder.create().layout(layout) - .border(BorderFactory.createEmptyBorder(6, 8, 6, 8)) - .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) - .add(charsetComboBox).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows) - .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(startupCommandTextField).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) - .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) - .apply { rows += step } - .build() - - - return panel - } - } - enum class Backspace { /** * 0x08 diff --git a/src/main/kotlin/app/termora/plugin/internal/wsl/WSLHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/wsl/WSLHostOptionsPane.kt index 2cb5be8..ca8a5fb 100644 --- a/src/main/kotlin/app/termora/plugin/internal/wsl/WSLHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/wsl/WSLHostOptionsPane.kt @@ -1,6 +1,8 @@ package app.termora.plugin.internal.wsl import app.termora.* +import app.termora.plugin.internal.AltKeyModifier +import app.termora.plugin.internal.BasicTerminalOption import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.ui.FlatTextBorder import com.jgoodies.forms.builder.FormBuilder @@ -12,12 +14,17 @@ import java.awt.KeyboardFocusManager import java.awt.Window import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent -import java.nio.charset.Charset import javax.swing.* internal open class WSLHostOptionsPane : OptionsPane() { protected val generalOption = GeneralOption() - protected val terminalOption = TerminalOption() + protected val terminalOption = BasicTerminalOption().apply { + showCharsetComboBox = true + showStartupCommandTextField = true + showEnvironmentTextArea = true + init() + } + protected val owner: Window get() = SwingUtilities.getWindowAncestor(this) init { @@ -36,7 +43,11 @@ internal open class WSLHostOptionsPane : OptionsPane() { encoding = terminalOption.charsetComboBox.selectedItem as String, env = terminalOption.environmentTextArea.text, startupCommand = terminalOption.startupCommandTextField.text, - extras = mutableMapOf("wsl-guid" to wsl.guid, "wsl-flavor" to wsl.flavor) + extras = mutableMapOf( + "wsl-guid" to wsl.guid, "wsl-flavor" to wsl.flavor, + "altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString() + ?: AltKeyModifier.EightBit.name), + ) ) return Host( @@ -216,85 +227,5 @@ internal open class WSLHostOptionsPane : OptionsPane() { } - protected inner class TerminalOption : JPanel(BorderLayout()), Option { - val charsetComboBox = JComboBox() - val startupCommandTextField = OutlineTextField() - val environmentTextArea = FixedLengthTextArea(2048) - - - init { - initView() - initEvents() - } - - private fun initView() { - add(getCenterComponent(), BorderLayout.CENTER) - - startupCommandTextField.placeholderText = "--cd ~" - - - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) - ) - environmentTextArea.setFocusTraversalKeys( - KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) - ) - - environmentTextArea.rows = 8 - environmentTextArea.lineWrap = true - environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) - - for (e in Charset.availableCharsets()) { - charsetComboBox.addItem(e.key) - } - - charsetComboBox.selectedItem = "UTF-8" - - } - - private fun initEvents() { - - } - - - override fun getIcon(isSelected: Boolean): Icon { - return Icons.terminal - } - - override fun getTitle(): String { - return I18n.getString("termora.new-host.terminal") - } - - override fun getJComponent(): JComponent { - return this - } - - private fun getCenterComponent(): JComponent { - val layout = FormLayout( - "left:pref, $FORM_MARGIN, default:grow", - "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" - ) - - var rows = 1 - val step = 2 - val panel = FormBuilder.create().layout(layout) - .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) - .add(charsetComboBox).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) - .add(startupCommandTextField).xy(3, rows).apply { rows += step } - .add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) - .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) - .apply { rows += step } - .build() - - - return panel - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/terminal/DataKey.kt b/src/main/kotlin/app/termora/terminal/DataKey.kt index 18c3382..9053583 100644 --- a/src/main/kotlin/app/termora/terminal/DataKey.kt +++ b/src/main/kotlin/app/termora/terminal/DataKey.kt @@ -1,5 +1,6 @@ package app.termora.terminal +import app.termora.plugin.internal.AltKeyModifier import kotlin.reflect.KClass @@ -192,6 +193,11 @@ class DataKey(val clazz: KClass) { * TerminalWriter */ val TerminalWriter = DataKey(app.termora.terminal.panel.TerminalWriter::class) + + /** + * [app.termora.plugin.internal.AltKeyModifier] + */ + val AltModifier = DataKey(AltKeyModifier::class) } } diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt index 90cb434..c7c9556 100644 --- a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt @@ -2,7 +2,9 @@ package app.termora.terminal.panel import app.termora.keymap.KeyShortcut import app.termora.keymap.KeymapManager +import app.termora.plugin.internal.AltKeyModifier import app.termora.terminal.ControlCharacters +import app.termora.terminal.DataKey import app.termora.terminal.Terminal import com.formdev.flatlaf.util.SystemInfo import org.slf4j.LoggerFactory @@ -89,8 +91,10 @@ class TerminalPanelKeyAdapter( return } + // https://github.com/TermoraDev/termora/issues/865 + val modifier = terminal.getTerminalModel().getData(DataKey.AltModifier, AltKeyModifier.EightBit) // https://github.com/TermoraDev/termora/issues/331 - if (isAltPressedOnly(e) && Character.isDefined(e.keyChar)) { + if (isAltPressedOnly(e) && Character.isDefined(e.keyChar) && modifier == AltKeyModifier.CharactersPrecededByESC) { val c = String(charArrayOf(ASCII_ESC, simpleMapKeyCodeToChar(e))) writer.write(TerminalWriter.WriteRequest.fromBytes(c.toByteArray(writer.getCharset()))) // scroll to bottom diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 6255361..cb568eb 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -186,6 +186,9 @@ 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.startup-commands=Startup Command +termora.new-host.terminal.alt-modifier=Alt modifier +termora.new-host.terminal.alt-modifier.eight-bit=8-bit characters +termora.new-host.terminal.alt-modifier.by-esc=Characters preceded by ESC termora.new-host.terminal.env=Environment termora.new-host.terminal.login-scripts=Login Scripts termora.new-host.terminal.expect=Expect diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index e78f10f..a5f26c9 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -178,6 +178,9 @@ termora.new-host.terminal.backspace=退格键 termora.new-host.terminal.character-mode=单字符模式 termora.new-host.terminal.heartbeat-interval=心跳间隔 termora.new-host.terminal.startup-commands=启动命令 +termora.new-host.terminal.alt-modifier=Alt 键修饰 +termora.new-host.terminal.alt-modifier.eight-bit=8 位字符 +termora.new-host.terminal.alt-modifier.by-esc=ESC 键作为前缀 termora.new-host.terminal.env=环境 termora.new-host.terminal.login-scripts=登录脚本 termora.new-host.terminal.expect=预期 diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index 80c6201..9b97a34 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -175,6 +175,9 @@ termora.new-host.terminal.encoding=編碼 termora.new-host.terminal.backspace=退格鍵 termora.new-host.terminal.character-mode=單字元模式 termora.new-host.terminal.startup-commands=啟動命令 +termora.new-host.terminal.alt-modifier=Alt 鍵修飾 +termora.new-host.terminal.alt-modifier.eight-bit=8 位元字符 +termora.new-host.terminal.alt-modifier.by-esc=ESC 鍵作為前綴 termora.new-host.terminal.heartbeat-interval=心跳間隔 termora.new-host.terminal.env=環境 termora.new-host.terminal.login-scripts=登入腳本