mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: serial comm (#141)
This commit is contained in:
@@ -241,3 +241,7 @@ https://github.com/mixpanel/mixpanel-java/blob/master/LICENSE
|
|||||||
json-20231013
|
json-20231013
|
||||||
Public Domain.
|
Public Domain.
|
||||||
https://github.com/stleary/JSON-java/blob/master/LICENSE
|
https://github.com/stleary/JSON-java/blob/master/LICENSE
|
||||||
|
|
||||||
|
jSerialComm 2.11.0
|
||||||
|
Apache License 2.0
|
||||||
|
https://github.com/Fazecast/jSerialComm/blob/master/LICENSE-APACHE-2.0
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import org.gradle.internal.jvm.Jvm
|
import org.gradle.internal.jvm.Jvm
|
||||||
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
||||||
|
import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
|
||||||
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
||||||
import org.jetbrains.kotlin.org.apache.commons.io.FileUtils
|
import org.jetbrains.kotlin.org.apache.commons.io.FileUtils
|
||||||
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
||||||
@@ -17,7 +18,7 @@ group = "app.termora"
|
|||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
|
||||||
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
||||||
val arch: Architecture = DefaultNativePlatform.getCurrentArchitecture()
|
val arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
||||||
|
|
||||||
// macOS 签名信息
|
// macOS 签名信息
|
||||||
val macOSSignUsername = System.getenv("TERMORA_MAC_SIGN_USER_NAME") ?: StringUtils.EMPTY
|
val macOSSignUsername = System.getenv("TERMORA_MAC_SIGN_USER_NAME") ?: StringUtils.EMPTY
|
||||||
@@ -104,6 +105,7 @@ dependencies {
|
|||||||
implementation(libs.bip39)
|
implementation(libs.bip39)
|
||||||
implementation(libs.colorpicker)
|
implementation(libs.colorpicker)
|
||||||
implementation(libs.mixpanel)
|
implementation(libs.mixpanel)
|
||||||
|
implementation(libs.jSerialComm)
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@@ -148,6 +150,8 @@ tasks.register<Copy>("copy-dependencies") {
|
|||||||
val jna = libs.jna.asProvider().get()
|
val jna = libs.jna.asProvider().get()
|
||||||
val dylib = dir.get().dir("dylib").asFile
|
val dylib = dir.get().dir("dylib").asFile
|
||||||
val pty4j = libs.pty4j.get()
|
val pty4j = libs.pty4j.get()
|
||||||
|
val jSerialComm = libs.jSerialComm.get()
|
||||||
|
|
||||||
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
|
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
|
||||||
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
|
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
|
||||||
val targetDir = File(dylib, jna.name)
|
val targetDir = File(dylib, jna.name)
|
||||||
@@ -172,6 +176,21 @@ tasks.register<Copy>("copy-dependencies") {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
// 删除所有二进制类库
|
// 删除所有二进制类库
|
||||||
exec { commandLine("zip", "-d", file.absolutePath, "resources/*") }
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*") }
|
||||||
|
} else if ("${jSerialComm.name}-${jSerialComm.version}" == file.nameWithoutExtension) {
|
||||||
|
val archName = if (arch.isArm) "aarch64" else "x86_64"
|
||||||
|
val targetDir = FileUtils.getFile(dylib, jSerialComm.name, "OSX", archName)
|
||||||
|
FileUtils.forceMkdir(targetDir)
|
||||||
|
// @formatter:off
|
||||||
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "OSX/${archName}/*", "-d", targetDir.absolutePath) }
|
||||||
|
// @formatter:on
|
||||||
|
// 删除所有二进制类库
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "Android/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "FreeBSD/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "Linux/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "OpenBSD/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "OSX/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "Solaris/*") }
|
||||||
|
exec { commandLine("zip", "-d", file.absolutePath, "Windows/*") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ rhino = "1.7.15"
|
|||||||
delight-rhino-sandbox = "0.0.17"
|
delight-rhino-sandbox = "0.0.17"
|
||||||
testcontainers = "1.20.4"
|
testcontainers = "1.20.4"
|
||||||
mixpanel = "1.5.3"
|
mixpanel = "1.5.3"
|
||||||
|
jSerialComm="2.11.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
@@ -97,6 +98,7 @@ rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
|
|||||||
delight-rhino-sandbox = { module = "org.javadelight:delight-rhino-sandbox", version.ref = "delight-rhino-sandbox" }
|
delight-rhino-sandbox = { module = "org.javadelight:delight-rhino-sandbox", version.ref = "delight-rhino-sandbox" }
|
||||||
colorpicker = { module = "org.drjekyll:colorpicker", version.ref = "colorpicker" }
|
colorpicker = { module = "org.drjekyll:colorpicker", version.ref = "colorpicker" }
|
||||||
mixpanel = { module = "com.mixpanel:mixpanel-java", version.ref = "mixpanel" }
|
mixpanel = { module = "com.mixpanel:mixpanel-java", version.ref = "mixpanel" }
|
||||||
|
jSerialComm = { module = "com.fazecast:jSerialComm", version.ref = "jSerialComm" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ class ChannelShellPtyConnector(
|
|||||||
output.flush()
|
output.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: String) {
|
|
||||||
write(buffer.toByteArray(charset))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resize(rows: Int, cols: Int) {
|
override fun resize(rows: Int, cols: Int) {
|
||||||
channel.sendWindowChange(cols, rows)
|
channel.sendWindowChange(cols, rows)
|
||||||
}
|
}
|
||||||
@@ -38,4 +34,8 @@ class ChannelShellPtyConnector(
|
|||||||
override fun close() {
|
override fun close() {
|
||||||
channel.close(true)
|
channel.close(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCharset(): Charset {
|
||||||
|
return charset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,16 @@ class EditHostOptionsPane(private val host: Host) : HostOptionsPane() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jumpHostsOption.filter = { it.id != host.id }
|
jumpHostsOption.filter = { it.id != host.id }
|
||||||
|
|
||||||
|
val serialComm = host.options.serialComm
|
||||||
|
if (serialComm.port.isNotBlank()) {
|
||||||
|
serialCommOption.serialPortComboBox.selectedItem = serialComm.port
|
||||||
|
}
|
||||||
|
serialCommOption.baudRateComboBox.selectedItem = serialComm.baudRate
|
||||||
|
serialCommOption.dataBitsComboBox.selectedItem = serialComm.dataBits
|
||||||
|
serialCommOption.parityComboBox.selectedItem = serialComm.parity
|
||||||
|
serialCommOption.stopBitsComboBox.selectedItem = serialComm.stopBits
|
||||||
|
serialCommOption.flowControlComboBox.selectedItem = serialComm.flowControl
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHost(): Host {
|
override fun getHost(): Host {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum class Protocol {
|
|||||||
Folder,
|
Folder,
|
||||||
SSH,
|
SSH,
|
||||||
Local,
|
Local,
|
||||||
|
Serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -39,6 +40,53 @@ data class Authentication(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SerialCommParity {
|
||||||
|
None,
|
||||||
|
Even,
|
||||||
|
Odd,
|
||||||
|
Mark,
|
||||||
|
Space
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SerialCommFlowControl {
|
||||||
|
None,
|
||||||
|
RTS_CTS,
|
||||||
|
XON_XOFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SerialComm(
|
||||||
|
/**
|
||||||
|
* 串口
|
||||||
|
*/
|
||||||
|
val port: String = StringUtils.EMPTY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 波特率
|
||||||
|
*/
|
||||||
|
val baudRate: Int = 9600,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据位:5、6、7、8
|
||||||
|
*/
|
||||||
|
val dataBits: Int = 8,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止位: 1、1.5、2
|
||||||
|
*/
|
||||||
|
val stopBits: String = "1",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验位
|
||||||
|
*/
|
||||||
|
val parity: SerialCommParity = SerialCommParity.None,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流控
|
||||||
|
*/
|
||||||
|
val flowControl: SerialCommFlowControl = SerialCommFlowControl.None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Options(
|
data class Options(
|
||||||
@@ -61,7 +109,12 @@ data class Options(
|
|||||||
/**
|
/**
|
||||||
* SSH 心跳间隔
|
* SSH 心跳间隔
|
||||||
*/
|
*/
|
||||||
val heartbeatInterval: Int = 30
|
val heartbeatInterval: Int = 30,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 串口配置
|
||||||
|
*/
|
||||||
|
val serialComm: SerialComm = SerialComm(),
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val Default = Options()
|
val Default = Options()
|
||||||
|
|||||||
@@ -67,37 +67,53 @@ class HostDialog(owner: Window, host: Host? = null) : DialogWrapper(owner) {
|
|||||||
|
|
||||||
private suspend fun testConnection(host: Host) {
|
private suspend fun testConnection(host: Host) {
|
||||||
val owner = this
|
val owner = this
|
||||||
if (host.protocol != Protocol.SSH) {
|
if (host.protocol == Protocol.Local) {
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
OptionPane.showMessageDialog(owner, I18n.getString("termora.new-host.test-connection-successful"))
|
OptionPane.showMessageDialog(owner, I18n.getString("termora.new-host.test-connection-successful"))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (host.protocol == Protocol.SSH) {
|
||||||
|
testSSH(host)
|
||||||
|
} else if (host.protocol == Protocol.Serial) {
|
||||||
|
testSerial(host)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
owner, ExceptionUtils.getMessage(e),
|
||||||
|
messageType = JOptionPane.ERROR_MESSAGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
owner,
|
||||||
|
I18n.getString("termora.new-host.test-connection-successful")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testSSH(host: Host) {
|
||||||
var client: SshClient? = null
|
var client: SshClient? = null
|
||||||
var session: ClientSession? = null
|
var session: ClientSession? = null
|
||||||
try {
|
try {
|
||||||
client = SshClients.openClient(host)
|
client = SshClients.openClient(host)
|
||||||
client.userInteraction = TerminalUserInteraction(owner)
|
client.userInteraction = TerminalUserInteraction(owner)
|
||||||
session = SshClients.openSession(host, client)
|
session = SshClients.openSession(host, client)
|
||||||
withContext(Dispatchers.Swing) {
|
|
||||||
OptionPane.showMessageDialog(
|
|
||||||
owner,
|
|
||||||
I18n.getString("termora.new-host.test-connection-successful")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Swing) {
|
|
||||||
OptionPane.showMessageDialog(
|
|
||||||
owner, ExceptionUtils.getRootCauseMessage(e),
|
|
||||||
messageType = JOptionPane.ERROR_MESSAGE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
session?.close()
|
session?.close()
|
||||||
client?.close()
|
client?.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testSerial(host: Host) {
|
||||||
|
Serials.openPort(host).closePort()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doOKAction() {
|
override fun doOKAction() {
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ package app.termora
|
|||||||
|
|
||||||
import app.termora.keymgr.KeyManager
|
import app.termora.keymgr.KeyManager
|
||||||
import app.termora.keymgr.KeyManagerDialog
|
import app.termora.keymgr.KeyManagerDialog
|
||||||
|
import com.fazecast.jSerialComm.SerialPort
|
||||||
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 kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.swing.Swing
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.*
|
import java.awt.event.*
|
||||||
@@ -22,6 +28,7 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
protected val proxyOption = ProxyOption()
|
protected val proxyOption = ProxyOption()
|
||||||
protected val terminalOption = TerminalOption()
|
protected val terminalOption = TerminalOption()
|
||||||
protected val jumpHostsOption = JumpHostsOption()
|
protected val jumpHostsOption = JumpHostsOption()
|
||||||
|
protected val serialCommOption = SerialCommOption()
|
||||||
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -30,6 +37,7 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
addOption(tunnelingOption)
|
addOption(tunnelingOption)
|
||||||
addOption(jumpHostsOption)
|
addOption(jumpHostsOption)
|
||||||
addOption(terminalOption)
|
addOption(terminalOption)
|
||||||
|
addOption(serialCommOption)
|
||||||
|
|
||||||
setContentBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8))
|
setContentBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8))
|
||||||
}
|
}
|
||||||
@@ -43,6 +51,7 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
var authentication = Authentication.No
|
var authentication = Authentication.No
|
||||||
var proxy = Proxy.No
|
var proxy = Proxy.No
|
||||||
|
|
||||||
|
|
||||||
if (generalOption.authenticationTypeComboBox.selectedItem == AuthenticationType.Password) {
|
if (generalOption.authenticationTypeComboBox.selectedItem == AuthenticationType.Password) {
|
||||||
authentication = authentication.copy(
|
authentication = authentication.copy(
|
||||||
type = AuthenticationType.Password,
|
type = AuthenticationType.Password,
|
||||||
@@ -66,12 +75,23 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val serialComm = SerialComm(
|
||||||
|
port = serialCommOption.serialPortComboBox.selectedItem?.toString() ?: StringUtils.EMPTY,
|
||||||
|
baudRate = serialCommOption.baudRateComboBox.selectedItem?.toString()?.toIntOrNull() ?: 9600,
|
||||||
|
dataBits = serialCommOption.dataBitsComboBox.selectedItem as Int? ?: 8,
|
||||||
|
stopBits = serialCommOption.stopBitsComboBox.selectedItem as String? ?: "1",
|
||||||
|
parity = serialCommOption.parityComboBox.selectedItem as SerialCommParity,
|
||||||
|
flowControl = serialCommOption.flowControlComboBox.selectedItem as SerialCommFlowControl
|
||||||
|
)
|
||||||
|
|
||||||
val options = Options.Default.copy(
|
val options = Options.Default.copy(
|
||||||
encoding = terminalOption.charsetComboBox.selectedItem as String,
|
encoding = terminalOption.charsetComboBox.selectedItem as String,
|
||||||
env = terminalOption.environmentTextArea.text,
|
env = terminalOption.environmentTextArea.text,
|
||||||
startupCommand = terminalOption.startupCommandTextField.text,
|
startupCommand = terminalOption.startupCommandTextField.text,
|
||||||
heartbeatInterval = (terminalOption.heartbeatIntervalTextField.value ?: 30) as Int,
|
heartbeatInterval = (terminalOption.heartbeatIntervalTextField.value ?: 30) as Int,
|
||||||
jumpHosts = jumpHostsOption.jumpHosts.map { it.id }
|
jumpHosts = jumpHostsOption.jumpHosts.map { it.id },
|
||||||
|
serialComm = serialComm
|
||||||
)
|
)
|
||||||
|
|
||||||
return Host(
|
return Host(
|
||||||
@@ -103,6 +123,12 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
if (validateField(generalOption.usernameTextField)) {
|
if (validateField(generalOption.usernameTextField)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
} else if (host.protocol == Protocol.Serial) {
|
||||||
|
if (validateField(serialCommOption.serialPortComboBox)
|
||||||
|
|| validateField(serialCommOption.baudRateComboBox)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.authentication.type == AuthenticationType.Password) {
|
if (host.authentication.type == AuthenticationType.Password) {
|
||||||
@@ -152,7 +178,8 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
* 返回 true 表示有错误
|
* 返回 true 表示有错误
|
||||||
*/
|
*/
|
||||||
private fun validateField(comboBox: JComboBox<*>): Boolean {
|
private fun validateField(comboBox: JComboBox<*>): Boolean {
|
||||||
if (comboBox.isEnabled && comboBox.selectedItem == null) {
|
val selectedItem = comboBox.selectedItem
|
||||||
|
if (comboBox.isEnabled && (selectedItem == null || (selectedItem is String && selectedItem.isBlank()))) {
|
||||||
selectOptionJComponent(comboBox)
|
selectOptionJComponent(comboBox)
|
||||||
comboBox.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
comboBox.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
||||||
comboBox.requestFocusInWindow()
|
comboBox.requestFocusInWindow()
|
||||||
@@ -259,6 +286,7 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
protocolTypeComboBox.addItem(Protocol.SSH)
|
protocolTypeComboBox.addItem(Protocol.SSH)
|
||||||
protocolTypeComboBox.addItem(Protocol.Local)
|
protocolTypeComboBox.addItem(Protocol.Local)
|
||||||
|
protocolTypeComboBox.addItem(Protocol.Serial)
|
||||||
|
|
||||||
authenticationTypeComboBox.addItem(AuthenticationType.No)
|
authenticationTypeComboBox.addItem(AuthenticationType.No)
|
||||||
authenticationTypeComboBox.addItem(AuthenticationType.Password)
|
authenticationTypeComboBox.addItem(AuthenticationType.Password)
|
||||||
@@ -328,7 +356,9 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
passwordTextField.isEnabled = true
|
passwordTextField.isEnabled = true
|
||||||
chooseKeyBtn.isEnabled = true
|
chooseKeyBtn.isEnabled = true
|
||||||
|
|
||||||
if (protocolTypeComboBox.selectedItem == Protocol.Local) {
|
if (protocolTypeComboBox.selectedItem == Protocol.Local
|
||||||
|
|| protocolTypeComboBox.selectedItem == Protocol.Serial
|
||||||
|
) {
|
||||||
hostTextField.isEnabled = false
|
hostTextField.isEnabled = false
|
||||||
portTextField.isEnabled = false
|
portTextField.isEnabled = false
|
||||||
usernameTextField.isEnabled = false
|
usernameTextField.isEnabled = false
|
||||||
@@ -901,6 +931,127 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected inner class SerialCommOption : JPanel(BorderLayout()), Option {
|
||||||
|
val serialPortComboBox = OutlineComboBox<String>()
|
||||||
|
val baudRateComboBox = OutlineComboBox<Int>()
|
||||||
|
val dataBitsComboBox = OutlineComboBox<Int>()
|
||||||
|
val parityComboBox = OutlineComboBox<SerialCommParity>()
|
||||||
|
val stopBitsComboBox = OutlineComboBox<String>()
|
||||||
|
val flowControlComboBox = OutlineComboBox<SerialCommFlowControl>()
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
|
||||||
|
serialPortComboBox.isEditable = true
|
||||||
|
|
||||||
|
baudRateComboBox.isEditable = true
|
||||||
|
baudRateComboBox.addItem(9600)
|
||||||
|
baudRateComboBox.addItem(19200)
|
||||||
|
baudRateComboBox.addItem(38400)
|
||||||
|
baudRateComboBox.addItem(57600)
|
||||||
|
baudRateComboBox.addItem(115200)
|
||||||
|
|
||||||
|
dataBitsComboBox.addItem(5)
|
||||||
|
dataBitsComboBox.addItem(6)
|
||||||
|
dataBitsComboBox.addItem(7)
|
||||||
|
dataBitsComboBox.addItem(8)
|
||||||
|
dataBitsComboBox.selectedItem = 8
|
||||||
|
|
||||||
|
parityComboBox.addItem(SerialCommParity.None)
|
||||||
|
parityComboBox.addItem(SerialCommParity.Even)
|
||||||
|
parityComboBox.addItem(SerialCommParity.Odd)
|
||||||
|
parityComboBox.addItem(SerialCommParity.Mark)
|
||||||
|
parityComboBox.addItem(SerialCommParity.Space)
|
||||||
|
|
||||||
|
stopBitsComboBox.addItem("1")
|
||||||
|
stopBitsComboBox.addItem("1.5")
|
||||||
|
stopBitsComboBox.addItem("2")
|
||||||
|
stopBitsComboBox.selectedItem = "1"
|
||||||
|
|
||||||
|
flowControlComboBox.addItem(SerialCommFlowControl.None)
|
||||||
|
flowControlComboBox.addItem(SerialCommFlowControl.RTS_CTS)
|
||||||
|
flowControlComboBox.addItem(SerialCommFlowControl.XON_XOFF)
|
||||||
|
|
||||||
|
flowControlComboBox.renderer = object : DefaultListCellRenderer() {
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<*>?,
|
||||||
|
value: Any?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component {
|
||||||
|
val text = value?.toString() ?: StringUtils.EMPTY
|
||||||
|
return super.getListCellRendererComponent(
|
||||||
|
list,
|
||||||
|
text.replace('_', '/'),
|
||||||
|
index,
|
||||||
|
isSelected,
|
||||||
|
cellHasFocus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(getCenterComponent(), BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
addComponentListener(object : ComponentAdapter() {
|
||||||
|
override fun componentShown(e: ComponentEvent) {
|
||||||
|
removeComponentListener(this)
|
||||||
|
@Suppress("OPT_IN_USAGE")
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
for (commPort in SerialPort.getCommPorts()) {
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
serialPortComboBox.addItem(commPort.systemPortName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(isSelected: Boolean): Icon {
|
||||||
|
return Icons.plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTitle(): String {
|
||||||
|
return I18n.getString("termora.new-host.serial")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getJComponent(): JComponent {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCenterComponent(): JComponent {
|
||||||
|
val layout = FormLayout(
|
||||||
|
"left:pref, $formMargin, default:grow, $formMargin",
|
||||||
|
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rows = 1
|
||||||
|
val step = 2
|
||||||
|
val panel = FormBuilder.create().layout(layout)
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.port")}:").xy(1, rows)
|
||||||
|
.add(serialPortComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.baud-rate")}:").xy(1, rows)
|
||||||
|
.add(baudRateComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.data-bits")}:").xy(1, rows)
|
||||||
|
.add(dataBitsComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.parity")}:").xy(1, rows)
|
||||||
|
.add(parityComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.stop-bits")}:").xy(1, rows)
|
||||||
|
.add(stopBitsComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.new-host.serial.flow-control")}:").xy(1, rows)
|
||||||
|
.add(flowControlComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.build()
|
||||||
|
return panel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected inner class JumpHostsOption : JPanel(BorderLayout()), Option {
|
protected inner class JumpHostsOption : JPanel(BorderLayout()), Option {
|
||||||
val jumpHosts = mutableListOf<Host>()
|
val jumpHosts = mutableListOf<Host>()
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class HostTree : JTree(), Disposable {
|
|||||||
icon = if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon()
|
icon = if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon()
|
||||||
} else if (host.protocol == Protocol.SSH || host.protocol == Protocol.Local) {
|
} else if (host.protocol == Protocol.SSH || host.protocol == Protocol.Local) {
|
||||||
icon = if (sel && this@HostTree.hasFocus()) Icons.terminal.dark else Icons.terminal
|
icon = if (sel && this@HostTree.hasFocus()) Icons.terminal.dark else Icons.terminal
|
||||||
|
} else if (host.protocol == Protocol.Serial) {
|
||||||
|
icon = if (sel && this@HostTree.hasFocus()) Icons.plugin.dark else Icons.plugin
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app.termora
|
|||||||
object Icons {
|
object Icons {
|
||||||
val bulletList by lazy { DynamicIcon("icons/bulletList.svg", "icons/bulletList_dark.svg") }
|
val bulletList by lazy { DynamicIcon("icons/bulletList.svg", "icons/bulletList_dark.svg") }
|
||||||
val up by lazy { DynamicIcon("icons/up.svg", "icons/up_dark.svg") }
|
val up by lazy { DynamicIcon("icons/up.svg", "icons/up_dark.svg") }
|
||||||
|
val plugin by lazy { DynamicIcon("icons/plugin.svg", "icons/plugin_dark.svg") }
|
||||||
val moveUp by lazy { DynamicIcon("icons/moveUp.svg", "icons/moveUp_dark.svg") }
|
val moveUp by lazy { DynamicIcon("icons/moveUp.svg", "icons/moveUp_dark.svg") }
|
||||||
val down by lazy { DynamicIcon("icons/down.svg", "icons/down_dark.svg") }
|
val down by lazy { DynamicIcon("icons/down.svg", "icons/down_dark.svg") }
|
||||||
val moveDown by lazy { DynamicIcon("icons/moveDown.svg", "icons/moveDown_dark.svg") }
|
val moveDown by lazy { DynamicIcon("icons/moveDown.svg", "icons/moveDown_dark.svg") }
|
||||||
|
|||||||
@@ -41,4 +41,9 @@ private fun setupNativeLibraries() {
|
|||||||
if (pty4j.exists()) {
|
if (pty4j.exists()) {
|
||||||
System.setProperty(PtyUtil.PREFERRED_NATIVE_FOLDER_KEY, pty4j.absolutePath)
|
System.setProperty(PtyUtil.PREFERRED_NATIVE_FOLDER_KEY, pty4j.absolutePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val jSerialComm = FileUtils.getFile(dylib, "jSerialComm")
|
||||||
|
if (jSerialComm.exists()) {
|
||||||
|
System.setProperty("jSerialComm.library.path", jSerialComm.absolutePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +53,12 @@ abstract class PtyHostTerminalTab(
|
|||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
delay(250.milliseconds)
|
delay(250.milliseconds)
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
ptyConnector.write(host.options.startupCommand)
|
val charset = ptyConnector.getCharset()
|
||||||
ptyConnector.write(terminal.getKeyEncoder().encode(TerminalKeyEvent(KeyEvent.VK_ENTER)))
|
ptyConnector.write(host.options.startupCommand.toByteArray(charset))
|
||||||
|
ptyConnector.write(
|
||||||
|
terminal.getKeyEncoder().encode(TerminalKeyEvent(KeyEvent.VK_ENTER))
|
||||||
|
.toByteArray(charset)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/main/kotlin/app/termora/SerialPortPtyConnector.kt
Normal file
61
src/main/kotlin/app/termora/SerialPortPtyConnector.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.terminal.PtyConnector
|
||||||
|
import com.fazecast.jSerialComm.SerialPort
|
||||||
|
import com.fazecast.jSerialComm.SerialPortDataListener
|
||||||
|
import com.fazecast.jSerialComm.SerialPortEvent
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class SerialPortPtyConnector(
|
||||||
|
private val serialPort: SerialPort,
|
||||||
|
private val charset: Charset = Charsets.UTF_8
|
||||||
|
) : PtyConnector, SerialPortDataListener {
|
||||||
|
|
||||||
|
private val queue = LinkedBlockingQueue<Char>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
serialPort.addDataListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(buffer: CharArray): Int {
|
||||||
|
buffer[0] = queue.poll(1, TimeUnit.SECONDS) ?: return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteArray, offset: Int, len: Int) {
|
||||||
|
serialPort.writeBytes(buffer, len, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resize(rows: Int, cols: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun waitFor(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
queue.clear()
|
||||||
|
serialPort.closePort()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getListeningEvents(): Int {
|
||||||
|
return SerialPort.LISTENING_EVENT_DATA_RECEIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialEvent(event: SerialPortEvent) {
|
||||||
|
if (event.eventType == SerialPort.LISTENING_EVENT_DATA_RECEIVED) {
|
||||||
|
val data = event.receivedData
|
||||||
|
if (data.isEmpty()) return
|
||||||
|
for (c in String(data, charset).toCharArray()) {
|
||||||
|
queue.add(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCharset(): Charset {
|
||||||
|
return charset
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/kotlin/app/termora/SerialTerminalTab.kt
Normal file
20
src/main/kotlin/app/termora/SerialTerminalTab.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.terminal.PtyConnector
|
||||||
|
import org.apache.commons.io.Charsets
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
class SerialTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminalTab(windowScope, host) {
|
||||||
|
override suspend fun openPtyConnector(): PtyConnector {
|
||||||
|
val serialPort = Serials.openPort(host)
|
||||||
|
return SerialPortPtyConnector(
|
||||||
|
serialPort,
|
||||||
|
Charsets.toCharset(host.options.encoding, StandardCharsets.UTF_8)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(): Icon {
|
||||||
|
return Icons.plugin
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/main/kotlin/app/termora/Serials.kt
Normal file
38
src/main/kotlin/app/termora/Serials.kt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import com.fazecast.jSerialComm.SerialPort
|
||||||
|
|
||||||
|
object Serials {
|
||||||
|
fun openPort(host: Host): SerialPort {
|
||||||
|
val serialComm = host.options.serialComm
|
||||||
|
val serialPort = SerialPort.getCommPort(serialComm.port)
|
||||||
|
serialPort.setBaudRate(serialComm.baudRate)
|
||||||
|
serialPort.setNumDataBits(serialComm.dataBits)
|
||||||
|
|
||||||
|
when (serialComm.parity) {
|
||||||
|
SerialCommParity.None -> serialPort.setParity(SerialPort.NO_PARITY)
|
||||||
|
SerialCommParity.Mark -> serialPort.setParity(SerialPort.MARK_PARITY)
|
||||||
|
SerialCommParity.Even -> serialPort.setParity(SerialPort.EVEN_PARITY)
|
||||||
|
SerialCommParity.Odd -> serialPort.setParity(SerialPort.ODD_PARITY)
|
||||||
|
SerialCommParity.Space -> serialPort.setParity(SerialPort.SPACE_PARITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (serialComm.stopBits) {
|
||||||
|
"1" -> serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT)
|
||||||
|
"1.5" -> serialPort.setNumStopBits(SerialPort.ONE_POINT_FIVE_STOP_BITS)
|
||||||
|
"2" -> serialPort.setNumStopBits(SerialPort.TWO_STOP_BITS)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (serialComm.flowControl) {
|
||||||
|
SerialCommFlowControl.None -> serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED)
|
||||||
|
SerialCommFlowControl.RTS_CTS -> serialPort.setFlowControl(SerialPort.FLOW_CONTROL_RTS_ENABLED or SerialPort.FLOW_CONTROL_CTS_ENABLED)
|
||||||
|
SerialCommFlowControl.XON_XOFF -> serialPort.setFlowControl(SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED or SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serialPort.openPort()) {
|
||||||
|
throw IllegalStateException("Open serial port [${serialComm.port}] failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialPort
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package app.termora.actions
|
package app.termora.actions
|
||||||
|
|
||||||
import app.termora.LocalTerminalTab
|
import app.termora.*
|
||||||
import app.termora.OpenHostActionEvent
|
|
||||||
import app.termora.Protocol
|
|
||||||
import app.termora.SSHTerminalTab
|
|
||||||
|
|
||||||
class OpenHostAction : AnAction() {
|
class OpenHostAction : AnAction() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -18,9 +15,11 @@ class OpenHostAction : AnAction() {
|
|||||||
val terminalTabbedManager = evt.getData(DataProviders.TerminalTabbedManager) ?: return
|
val terminalTabbedManager = evt.getData(DataProviders.TerminalTabbedManager) ?: return
|
||||||
val windowScope = evt.getData(DataProviders.WindowScope) ?: return
|
val windowScope = evt.getData(DataProviders.WindowScope) ?: return
|
||||||
|
|
||||||
val tab = if (evt.host.protocol == Protocol.SSH)
|
val tab = when (evt.host.protocol) {
|
||||||
SSHTerminalTab(windowScope, evt.host)
|
Protocol.SSH -> SSHTerminalTab(windowScope, evt.host)
|
||||||
else LocalTerminalTab(windowScope, evt.host)
|
Protocol.Serial -> SerialTerminalTab(windowScope, evt.host)
|
||||||
|
else -> LocalTerminalTab(windowScope, evt.host)
|
||||||
|
}
|
||||||
|
|
||||||
terminalTabbedManager.addTerminalTab(tab)
|
terminalTabbedManager.addTerminalTab(tab)
|
||||||
tab.start()
|
tab.start()
|
||||||
|
|||||||
@@ -485,9 +485,11 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
|
|||||||
val m = args.first()
|
val m = args.first()
|
||||||
if (m == '6') {
|
if (m == '6') {
|
||||||
val position = terminal.getCursorModel().getPosition()
|
val position = terminal.getCursorModel().getPosition()
|
||||||
ptyConnector.write("${ControlCharacters.ESC}[${position.y};${position.x}R")
|
val bytes = "${ControlCharacters.ESC}[${position.y};${position.x}R".toByteArray(ptyConnector.getCharset())
|
||||||
|
ptyConnector.write(bytes)
|
||||||
} else if (m == '5') {
|
} else if (m == '5') {
|
||||||
ptyConnector.write("${ControlCharacters.ESC}[0n")
|
val bytes = "${ControlCharacters.ESC}[0n".toByteArray(ptyConnector.getCharset())
|
||||||
|
ptyConnector.write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora.terminal
|
package app.termora.terminal
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
interface PtyConnector {
|
interface PtyConnector {
|
||||||
@@ -15,15 +16,18 @@ interface PtyConnector {
|
|||||||
*/
|
*/
|
||||||
fun write(buffer: ByteArray, offset: Int, len: Int)
|
fun write(buffer: ByteArray, offset: Int, len: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入数组。
|
||||||
|
*
|
||||||
|
* 如果要写入 String 字符串,请通过 [getCharset] 编码。
|
||||||
|
*/
|
||||||
fun write(buffer: ByteArray) {
|
fun write(buffer: ByteArray) {
|
||||||
write(buffer, 0, buffer.size)
|
write(buffer, 0, buffer.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(buffer: String) {
|
/**
|
||||||
if (buffer.isEmpty()) return
|
* 写入单个 Int
|
||||||
write(buffer.toByteArray())
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
fun write(buffer: Int) {
|
fun write(buffer: Int) {
|
||||||
write(ByteBuffer.allocate(Integer.BYTES).putInt(buffer).flip().array())
|
write(ByteBuffer.allocate(Integer.BYTES).putInt(buffer).flip().array())
|
||||||
}
|
}
|
||||||
@@ -43,4 +47,8 @@ interface PtyConnector {
|
|||||||
*/
|
*/
|
||||||
fun close()
|
fun close()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
fun getCharset(): Charset = Charsets.UTF_8
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package app.termora.terminal
|
package app.termora.terminal
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
open class PtyConnectorDelegate(
|
open class PtyConnectorDelegate(
|
||||||
@Volatile var ptyConnector: PtyConnector? = null
|
@Volatile var ptyConnector: PtyConnector? = null
|
||||||
) : PtyConnector {
|
) : PtyConnector {
|
||||||
@@ -26,5 +28,7 @@ open class PtyConnectorDelegate(
|
|||||||
ptyConnector = null
|
ptyConnector = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCharset(): Charset {
|
||||||
|
return ptyConnector?.getCharset() ?: super.getCharset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,9 +20,6 @@ class PtyProcessConnector(private val process: PtyProcess, private val charset:
|
|||||||
output.flush()
|
output.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(buffer: String) {
|
|
||||||
write(buffer.toByteArray(charset))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resize(rows: Int, cols: Int) {
|
override fun resize(rows: Int, cols: Int) {
|
||||||
process.winSize = WinSize(cols, rows)
|
process.winSize = WinSize(cols, rows)
|
||||||
@@ -38,5 +35,7 @@ class PtyProcessConnector(private val process: PtyProcess, private val charset:
|
|||||||
process.destroyForcibly()
|
process.destroyForcibly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCharset(): Charset {
|
||||||
|
return charset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
|
|||||||
|
|
||||||
// 输入法提交
|
// 输入法提交
|
||||||
if (committedCharacterCount > 0) {
|
if (committedCharacterCount > 0) {
|
||||||
ptyConnector.write(sb.toString())
|
ptyConnector.write(sb.toString().toByteArray(ptyConnector.getCharset()))
|
||||||
} else {
|
} else {
|
||||||
val breakIterator = BreakIterator.getCharacterInstance()
|
val breakIterator = BreakIterator.getCharacterInstance()
|
||||||
val chars = mutableListOf<Char>()
|
val chars = mutableListOf<Char>()
|
||||||
@@ -404,9 +404,15 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (terminal.getTerminalModel().getData(DataKey.BracketedPasteMode, false)) {
|
if (terminal.getTerminalModel().getData(DataKey.BracketedPasteMode, false)) {
|
||||||
ptyConnector.write("${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~")
|
val bytes = ptyConnector.getCharset()
|
||||||
|
.encode("${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~")
|
||||||
|
.array()
|
||||||
|
ptyConnector.write(bytes)
|
||||||
} else {
|
} else {
|
||||||
ptyConnector.write(content)
|
val bytes = ptyConnector.getCharset()
|
||||||
|
.encode(content)
|
||||||
|
.array()
|
||||||
|
ptyConnector.write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.getScrollingModel().scrollToRow(
|
terminal.getScrollingModel().scrollToRow(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TerminalPanelKeyAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
terminal.getSelectionModel().clearSelection()
|
terminal.getSelectionModel().clearSelection()
|
||||||
ptyConnector.write("${e.keyChar}")
|
ptyConnector.write("${e.keyChar}".toByteArray(ptyConnector.getCharset()))
|
||||||
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ class TerminalPanelKeyAdapter(
|
|||||||
|
|
||||||
val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e))
|
val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e))
|
||||||
if (encode.isNotEmpty()) {
|
if (encode.isNotEmpty()) {
|
||||||
ptyConnector.write(encode)
|
ptyConnector.write(encode.toByteArray(ptyConnector.getCharset()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/TermoraDev/termora/issues/52
|
// https://github.com/TermoraDev/termora/issues/52
|
||||||
@@ -64,7 +64,7 @@ class TerminalPanelKeyAdapter(
|
|||||||
terminal.getSelectionModel().clearSelection()
|
terminal.getSelectionModel().clearSelection()
|
||||||
// 如果不为空表示已经发送过了,所以这里为空的时候再发送
|
// 如果不为空表示已经发送过了,所以这里为空的时候再发送
|
||||||
if (encode.isEmpty()) {
|
if (encode.isEmpty()) {
|
||||||
ptyConnector.write("${e.keyChar}")
|
ptyConnector.write("${e.keyChar}".toByteArray(ptyConnector.getCharset()))
|
||||||
}
|
}
|
||||||
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ class TerminalPanelMouseTrackingAdapter(
|
|||||||
val encode = terminal.getKeyEncoder()
|
val encode = terminal.getKeyEncoder()
|
||||||
.encode(TerminalKeyEvent(if (e.wheelRotation < 0) KeyEvent.VK_UP else KeyEvent.VK_DOWN))
|
.encode(TerminalKeyEvent(if (e.wheelRotation < 0) KeyEvent.VK_UP else KeyEvent.VK_DOWN))
|
||||||
if (encode.isBlank()) return
|
if (encode.isBlank()) return
|
||||||
|
val bytes = encode.toByteArray(ptyConnector.getCharset())
|
||||||
for (i in 0 until abs(unitsToScroll)) {
|
for (i in 0 until abs(unitsToScroll)) {
|
||||||
ptyConnector.write(encode)
|
ptyConnector.write(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,14 @@ 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
|
||||||
|
|
||||||
|
termora.new-host.serial=Serial
|
||||||
|
termora.new-host.serial.port=Port
|
||||||
|
termora.new-host.serial.baud-rate=Baud rate
|
||||||
|
termora.new-host.serial.data-bits=Data bits
|
||||||
|
termora.new-host.serial.parity=Parity
|
||||||
|
termora.new-host.serial.stop-bits=Stop bits
|
||||||
|
termora.new-host.serial.flow-control=Flow control
|
||||||
|
|
||||||
termora.new-host.tunneling=Tunneling
|
termora.new-host.tunneling=Tunneling
|
||||||
termora.new-host.tunneling.table.name=Name
|
termora.new-host.tunneling.table.name=Name
|
||||||
termora.new-host.tunneling.table.type=Type
|
termora.new-host.tunneling.table.type=Type
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ termora.new-host.terminal.startup-commands=启动命令
|
|||||||
termora.new-host.terminal.env=环境
|
termora.new-host.terminal.env=环境
|
||||||
|
|
||||||
|
|
||||||
|
termora.new-host.serial=串口
|
||||||
|
termora.new-host.serial.port=端口
|
||||||
|
termora.new-host.serial.baud-rate=波特率
|
||||||
|
termora.new-host.serial.data-bits=数据位
|
||||||
|
termora.new-host.serial.parity=校验位
|
||||||
|
termora.new-host.serial.stop-bits=停止位
|
||||||
|
termora.new-host.serial.flow-control=流控
|
||||||
|
|
||||||
|
|
||||||
termora.new-host.test-connection=测试连接
|
termora.new-host.test-connection=测试连接
|
||||||
termora.new-host.test-connection-successful=连接成功
|
termora.new-host.test-connection-successful=连接成功
|
||||||
|
|||||||
@@ -130,6 +130,14 @@ 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=環境
|
||||||
|
|
||||||
|
termora.new-host.serial=串口
|
||||||
|
termora.new-host.serial.port=端口
|
||||||
|
termora.new-host.serial.baud-rate=波特率
|
||||||
|
termora.new-host.serial.data-bits=資料位
|
||||||
|
termora.new-host.serial.parity=校驗位
|
||||||
|
termora.new-host.serial.stop-bits=停止位
|
||||||
|
termora.new-host.serial.flow-control=流控
|
||||||
|
|
||||||
termora.new-host.test-connection=測試連接
|
termora.new-host.test-connection=測試連接
|
||||||
termora.new-host.test-connection-successful=連線成功
|
termora.new-host.test-connection-successful=連線成功
|
||||||
|
|
||||||
|
|||||||
4
src/main/resources/icons/plugin.svg
Normal file
4
src/main/resources/icons/plugin.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 4H7C5.34315 4 4 5.34315 4 7V9C4 10.6569 5.34315 12 7 12H11V11V10V6V5V4ZM12 5V4C12 3.44772 11.5523 3 11 3H7C5.13616 3 3.57006 4.27477 3.12602 6H1C0.447715 6 0 6.44772 0 7V9C0 9.55228 0.447715 10 1 10H3.12602C3.57006 11.7252 5.13616 13 7 13H11C11.5523 13 12 12.5523 12 12V11H15.5C15.7761 11 16 10.7761 16 10.5C16 10.2239 15.7761 10 15.5 10H12V6H15.5C15.7761 6 16 5.77614 16 5.5C16 5.22386 15.7761 5 15.5 5H12ZM3 7V9H1V7L3 7Z" fill="#6C707E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 724 B |
4
src/main/resources/icons/plugin_dark.svg
Normal file
4
src/main/resources/icons/plugin_dark.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 4H7C5.34315 4 4 5.34315 4 7V9C4 10.6569 5.34315 12 7 12H11V11V10V6V5V4ZM12 5V4C12 3.44772 11.5523 3 11 3H7C5.13616 3 3.57006 4.27477 3.12602 6H1C0.447715 6 0 6.44772 0 7V9C0 9.55228 0.447715 10 1 10H3.12602C3.57006 11.7252 5.13616 13 7 13H11C11.5523 13 12 12.5523 12 12V11H15.5C15.7761 11 16 10.7761 16 10.5C16 10.2239 15.7761 10 15.5 10H12V6H15.5C15.7761 6 16 5.77614 16 5.5C16 5.22386 15.7761 5 15.5 5H12ZM3 7V9H1V7L3 7Z" fill="#CED0D6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 724 B |
Reference in New Issue
Block a user