mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
Compare commits
14 Commits
2.0.0-beta
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a165d715 | ||
|
|
ca757f975a | ||
|
|
79c304ae3d | ||
|
|
1848c869e7 | ||
|
|
029e570551 | ||
|
|
905c570e4c | ||
|
|
a3069229b8 | ||
|
|
1e930d61c9 | ||
|
|
0015c3a7fb | ||
|
|
4bfb87e5c7 | ||
|
|
4fbb626c42 | ||
|
|
35b175d944 | ||
|
|
5939297550 | ||
|
|
e6e5867742 |
@@ -404,18 +404,6 @@ tasks.register<Exec>("jpackage") {
|
|||||||
arguments.addAll(listOf("--copyright", "TermoraDev"))
|
arguments.addAll(listOf("--copyright", "TermoraDev"))
|
||||||
arguments.addAll(listOf("--app-content", "$buildDir/plugins"))
|
arguments.addAll(listOf("--app-content", "$buildDir/plugins"))
|
||||||
|
|
||||||
if (os.isWindows) {
|
|
||||||
arguments.addAll(
|
|
||||||
listOf(
|
|
||||||
"--description",
|
|
||||||
"${project.name.uppercaseFirstChar()}: A terminal emulator and SSH client"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
arguments.addAll(listOf("--description", "A terminal emulator and SSH client."))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (os.isMacOsX) {
|
if (os.isMacOsX) {
|
||||||
arguments.addAll(listOf("--mac-package-name", project.name.uppercaseFirstChar()))
|
arguments.addAll(listOf("--mac-package-name", project.name.uppercaseFirstChar()))
|
||||||
arguments.addAll(listOf("--mac-app-category", "developer-tools"))
|
arguments.addAll(listOf("--mac-app-category", "developer-tools"))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.2.10"
|
kotlin = "2.2.10"
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.17"
|
||||||
pty4j = "0.13.10"
|
pty4j = "0.13.11"
|
||||||
tinylog = "2.7.0"
|
tinylog = "2.7.0"
|
||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
flatlaf = "3.6.1"
|
flatlaf = "3.6.1"
|
||||||
@@ -22,8 +22,8 @@ jna = "5.17.0"
|
|||||||
jSystemThemeDetector = "3.9.1"
|
jSystemThemeDetector = "3.9.1"
|
||||||
commons-io = "2.20.0"
|
commons-io = "2.20.0"
|
||||||
jbr-api = "17.1.10.1"
|
jbr-api = "17.1.10.1"
|
||||||
hutool = "5.8.39"
|
hutool = "5.8.40"
|
||||||
jsch = "2.27.2"
|
jsch = "2.27.3"
|
||||||
okhttp = "5.1.0"
|
okhttp = "5.1.0"
|
||||||
sshj = "0.39.0"
|
sshj = "0.39.0"
|
||||||
sshd-core = "2.15.0"
|
sshd-core = "2.15.0"
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ project.version = "0.0.8"
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
compileOnly(project(":"))
|
compileOnly(project(":"))
|
||||||
implementation("com.maxmind.geoip2:geoip2:4.3.1")
|
implementation("com.maxmind.geoip2:geoip2:4.4.0")
|
||||||
// https://github.com/hstyi/geolite2
|
// https://github.com/hstyi/geolite2
|
||||||
implementation("com.github.hstyi:geolite2:v1.0-202508110059")
|
implementation("com.github.hstyi:geolite2:v1.0-202508180058")
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(from = "$rootDir/plugins/common.gradle.kts")
|
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||||
|
|||||||
@@ -2,13 +2,6 @@ termora.plugins.sync.disabled-sync=You are already logged in and cannot use this
|
|||||||
|
|
||||||
termora.settings.sync=Sync
|
termora.settings.sync=Sync
|
||||||
termora.settings.sync.done=Synchronized data successfully
|
termora.settings.sync.done=Synchronized data successfully
|
||||||
termora.settings.sync.export=${termora.keymgr.export}
|
|
||||||
termora.settings.sync.import=${termora.keymgr.import}
|
|
||||||
termora.settings.sync.import.file-too-large=The file is too large
|
|
||||||
termora.settings.sync.import.successful=Import data successfully
|
|
||||||
termora.settings.sync.export-done=The export was successful
|
|
||||||
termora.settings.sync.export-encrypt=Enter password to encrypt file (optional)
|
|
||||||
termora.settings.sync.export-done-open-folder=The export was successful. Do you want to open the folder?
|
|
||||||
termora.settings.sync.range=Range
|
termora.settings.sync.range=Range
|
||||||
termora.settings.sync.range.keys=My keys
|
termora.settings.sync.range.keys=My keys
|
||||||
termora.settings.sync.range.keyword-highlights=${termora.highlight}
|
termora.settings.sync.range.keyword-highlights=${termora.highlight}
|
||||||
|
|||||||
@@ -2,15 +2,10 @@ termora.plugins.sync.disabled-sync=你已登录,无法使用此功能
|
|||||||
|
|
||||||
|
|
||||||
termora.settings.sync=同步
|
termora.settings.sync=同步
|
||||||
termora.settings.sync.export-done=导出成功
|
|
||||||
termora.settings.sync.export-encrypt=输入密码加密文件 (可选)
|
|
||||||
termora.settings.sync.export-done-open-folder=导出成功,是否需要打开所在文件夹?
|
|
||||||
termora.settings.sync.range=范围
|
termora.settings.sync.range=范围
|
||||||
termora.settings.sync.range.keys=我的密钥
|
termora.settings.sync.range.keys=我的密钥
|
||||||
termora.settings.sync.last-sync-time=最后同步时间
|
termora.settings.sync.last-sync-time=最后同步时间
|
||||||
termora.settings.sync.done=同步数据成功
|
termora.settings.sync.done=同步数据成功
|
||||||
termora.settings.sync.import.file-too-large=文件太大
|
|
||||||
termora.settings.sync.import.successful=导入数据成功
|
|
||||||
termora.settings.sync.gist=片段
|
termora.settings.sync.gist=片段
|
||||||
termora.settings.sync.token=令牌
|
termora.settings.sync.token=令牌
|
||||||
termora.settings.sync.type=类型
|
termora.settings.sync.type=类型
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
termora.plugins.sync.disabled-sync=你已登錄,無法使用此功能
|
termora.plugins.sync.disabled-sync=你已登錄,無法使用此功能
|
||||||
|
|
||||||
termora.settings.sync=同步
|
termora.settings.sync=同步
|
||||||
termora.settings.sync.export-done=匯出成功
|
|
||||||
termora.settings.sync.export-encrypt=輸入密碼加密檔案 (可選)
|
|
||||||
termora.settings.sync.export-done-open-folder=匯出成功,是否需要打開所在資料夾?
|
|
||||||
termora.settings.sync.range=範圍
|
termora.settings.sync.range=範圍
|
||||||
termora.settings.sync.range.keys=我的密鑰
|
termora.settings.sync.range.keys=我的密鑰
|
||||||
termora.settings.sync.last-sync-time=最後同步時間
|
termora.settings.sync.last-sync-time=最後同步時間
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import app.termora.database.DatabaseManager
|
import app.termora.database.DatabaseManager
|
||||||
|
import com.formdev.flatlaf.util.UIScale
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Window
|
import java.awt.Window
|
||||||
@@ -14,7 +15,10 @@ internal class SettingsDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
private val properties get() = DatabaseManager.getInstance().properties
|
private val properties get() = DatabaseManager.getInstance().properties
|
||||||
|
|
||||||
init {
|
init {
|
||||||
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
|
size = Dimension(
|
||||||
|
UIScale.scale(UIManager.getInt("Dialog.width")),
|
||||||
|
UIScale.scale(UIManager.getInt("Dialog.height"))
|
||||||
|
)
|
||||||
isModal = true
|
isModal = true
|
||||||
title = I18n.getString("termora.setting")
|
title = I18n.getString("termora.setting")
|
||||||
setLocationRelativeTo(null)
|
setLocationRelativeTo(null)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora
|
|||||||
|
|
||||||
import app.termora.actions.AnAction
|
import app.termora.actions.AnAction
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
|
import app.termora.plugin.internal.extension.DynamicExtensionHandler
|
||||||
import app.termora.tree.NewHostTree
|
import app.termora.tree.NewHostTree
|
||||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
import com.formdev.flatlaf.extras.components.FlatToolBar
|
import com.formdev.flatlaf.extras.components.FlatToolBar
|
||||||
@@ -9,15 +10,14 @@ import com.formdev.flatlaf.util.SystemInfo
|
|||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import java.awt.event.ComponentAdapter
|
import java.awt.event.*
|
||||||
import java.awt.event.ComponentEvent
|
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import java.awt.event.MouseAdapter
|
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
import javax.swing.tree.TreePath
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
class TermoraFencePanel(
|
class TermoraFencePanel(
|
||||||
|
private val ws: WindowScope,
|
||||||
private val terminalTabbed: TerminalTabbed,
|
private val terminalTabbed: TerminalTabbed,
|
||||||
private val tabbed: FlatTabbedPane,
|
private val tabbed: FlatTabbedPane,
|
||||||
private val moveMouseAdapter: MouseAdapter,
|
private val moveMouseAdapter: MouseAdapter,
|
||||||
@@ -98,6 +98,40 @@ class TermoraFencePanel(
|
|||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DynamicExtensionHandler.getInstance()
|
||||||
|
.register(TerminalTabbedContextMenuExtension::class.java, object : TerminalTabbedContextMenuExtension {
|
||||||
|
override fun createJMenuItem(
|
||||||
|
windowScope: WindowScope,
|
||||||
|
tab: TerminalTab
|
||||||
|
): JMenuItem {
|
||||||
|
if (windowScope != ws) throw UnsupportedOperationException()
|
||||||
|
if (tab !is HostTerminalTab) throw UnsupportedOperationException()
|
||||||
|
if (tab.host.isTemporary) throw UnsupportedOperationException()
|
||||||
|
if (tab.host.id == "local") throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
val item = JMenuItem(I18n.getString("termora.tabbed.contextmenu.select-host"))
|
||||||
|
item.addActionListener(object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
val tree = getHostTree()
|
||||||
|
for (node in tree.simpleTreeModel.root.getAllChildren()) {
|
||||||
|
if (node.id == tab.host.id) {
|
||||||
|
tree.selectionPath = TreePath(tree.simpleTreeModel.getPathToRoot(node))
|
||||||
|
tree.requestFocusInWindow()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ordered(): Long {
|
||||||
|
return Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
}).let { Disposer.register(this, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class LeftTreePanel : JPanel(BorderLayout()), Disposable {
|
private inner class LeftTreePanel : JPanel(BorderLayout()), Disposable {
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (layout == TermoraLayout.Fence) {
|
if (layout == TermoraLayout.Fence) {
|
||||||
val fencePanel = TermoraFencePanel(terminalTabbed, tabbedPane, moveMouseAdapter)
|
val fencePanel = TermoraFencePanel(windowScope, terminalTabbed, tabbedPane, moveMouseAdapter)
|
||||||
add(fencePanel, BorderLayout.CENTER)
|
add(fencePanel, BorderLayout.CENTER)
|
||||||
dataProviderSupport.addData(DataProviders.Welcome.HostTree, fencePanel.getHostTree())
|
dataProviderSupport.addData(DataProviders.Welcome.HostTree, fencePanel.getHostTree())
|
||||||
Disposer.register(windowScope, fencePanel)
|
Disposer.register(windowScope, fencePanel)
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ object AccountHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
if (cidr == "localhost" || cidr == "127.0.0.1") continue
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug(e.message, e)
|
log.debug(e.message, e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,8 +262,8 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
|
|||||||
|
|
||||||
OptionPane.openFileInFolder(
|
OptionPane.openFileInFolder(
|
||||||
SwingUtilities.getWindowAncestor(this),
|
SwingUtilities.getWindowAncestor(this),
|
||||||
file, I18n.getString("termora.settings.sync.export-done-open-folder"),
|
file, I18n.getString("termora.keymgr.export-done-open-folder"),
|
||||||
I18n.getString("termora.settings.sync.export-done")
|
I18n.getString("termora.keymgr.export-done")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import app.termora.keymgr.KeyManagerDialog
|
|||||||
import app.termora.plugin.internal.AltKeyModifier
|
import app.termora.plugin.internal.AltKeyModifier
|
||||||
import app.termora.plugin.internal.BasicProxyOption
|
import app.termora.plugin.internal.BasicProxyOption
|
||||||
import app.termora.plugin.internal.BasicTerminalOption
|
import app.termora.plugin.internal.BasicTerminalOption
|
||||||
|
import app.termora.plugin.internal.telnet.TelnetHostOptionsPane.Backspace
|
||||||
import app.termora.tree.Filter
|
import app.termora.tree.Filter
|
||||||
import app.termora.tree.HostTreeNode
|
import app.termora.tree.HostTreeNode
|
||||||
import app.termora.tree.NewHostTreeDialog
|
import app.termora.tree.NewHostTreeDialog
|
||||||
@@ -36,6 +37,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
private val terminalOption = BasicTerminalOption().apply {
|
private val terminalOption = BasicTerminalOption().apply {
|
||||||
showCharsetComboBox = true
|
showCharsetComboBox = true
|
||||||
showLoginScripts = true
|
showLoginScripts = true
|
||||||
|
showBackspaceComboBox = true
|
||||||
showEnvironmentTextArea = true
|
showEnvironmentTextArea = true
|
||||||
showStartupCommandTextField = true
|
showStartupCommandTextField = true
|
||||||
showHeartbeatIntervalTextField = true
|
showHeartbeatIntervalTextField = true
|
||||||
@@ -112,6 +114,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
x11Forwarding = tunnelingOption.x11ServerTextField.text,
|
x11Forwarding = tunnelingOption.x11ServerTextField.text,
|
||||||
loginScripts = terminalOption.loginScripts,
|
loginScripts = terminalOption.loginScripts,
|
||||||
extras = mutableMapOf(
|
extras = mutableMapOf(
|
||||||
|
"backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name,
|
||||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||||
?: AltKeyModifier.EightBit.name),
|
?: AltKeyModifier.EightBit.name),
|
||||||
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
|
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
|
||||||
@@ -169,6 +172,9 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
|
|||||||
.getOrNull() ?: AltKeyModifier.EightBit
|
.getOrNull() ?: AltKeyModifier.EightBit
|
||||||
|
|
||||||
|
|
||||||
|
terminalOption.backspaceComboBox.selectedItem =
|
||||||
|
Backspace.valueOf(host.options.extras["backspace"] ?: Backspace.Delete.name)
|
||||||
|
|
||||||
val timeout = host.options.extras["timeout"] ?: "60"
|
val timeout = host.options.extras["timeout"] ?: "60"
|
||||||
terminalOption.timeoutTextField.value = timeout.toIntOrNull() ?: 60
|
terminalOption.timeoutTextField.value = timeout.toIntOrNull() ?: 60
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
|||||||
import app.termora.database.DatabaseManager
|
import app.termora.database.DatabaseManager
|
||||||
import app.termora.keymap.KeyShortcut
|
import app.termora.keymap.KeyShortcut
|
||||||
import app.termora.keymap.KeymapManager
|
import app.termora.keymap.KeymapManager
|
||||||
import app.termora.terminal.ControlCharacters
|
import app.termora.plugin.internal.telnet.TelnetHostOptionsPane
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.*
|
||||||
import app.termora.terminal.PtyConnector
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -20,6 +19,7 @@ import org.apache.sshd.client.session.ClientSession
|
|||||||
import org.apache.sshd.common.future.CloseFuture
|
import org.apache.sshd.common.future.CloseFuture
|
||||||
import org.apache.sshd.common.future.SshFutureListener
|
import org.apache.sshd.common.future.SshFutureListener
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
@@ -110,7 +110,18 @@ class SSHTerminalTab(
|
|||||||
// clear screen
|
// clear screen
|
||||||
terminal.clearScreen()
|
terminal.clearScreen()
|
||||||
// show cursor
|
// show cursor
|
||||||
terminalModel.setData(DataKey.Companion.ShowCursor, true)
|
terminalModel.setData(DataKey.ShowCursor, true)
|
||||||
|
|
||||||
|
val encoder = terminal.getKeyEncoder()
|
||||||
|
if (encoder is KeyEncoderImpl) {
|
||||||
|
val backspace = host.options.extras["backspace"]
|
||||||
|
if (backspace == TelnetHostOptionsPane.Backspace.Backspace.name) {
|
||||||
|
encoder.putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_BACK_SPACE), String(byteArrayOf(0x08)))
|
||||||
|
} else if (backspace == TelnetHostOptionsPane.Backspace.VT220.name) {
|
||||||
|
encoder.putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_BACK_SPACE), "${ControlCharacters.ESC}[3~")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptyConnectorFactory.decorate(
|
return ptyConnectorFactory.decorate(
|
||||||
|
|||||||
@@ -69,4 +69,7 @@ class SftpCommandTerminalTabbedContextMenuExtension private constructor() : Term
|
|||||||
openHostAction.actionPerformed(OpenHostActionEvent(evt.source, host, evt))
|
openHostAction.actionPerformed(OpenHostActionEvent(evt.source, host, evt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun ordered(): Long {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -425,8 +425,11 @@ object SshClients {
|
|||||||
|
|
||||||
|
|
||||||
val heartbeatInterval = max(host.options.heartbeatInterval, 3)
|
val heartbeatInterval = max(host.options.heartbeatInterval, 3)
|
||||||
|
val timeout = Duration.ofSeconds(host.options.extras["timeout"]?.toLongOrNull() ?: 60)
|
||||||
|
|
||||||
CoreModuleProperties.HEARTBEAT_INTERVAL.set(sshClient, Duration.ofSeconds(heartbeatInterval.toLong()))
|
CoreModuleProperties.HEARTBEAT_INTERVAL.set(sshClient, Duration.ofSeconds(heartbeatInterval.toLong()))
|
||||||
CoreModuleProperties.ALLOW_DHG1_KEX_FALLBACK.set(sshClient, true)
|
CoreModuleProperties.ALLOW_DHG1_KEX_FALLBACK.set(sshClient, true)
|
||||||
|
CoreModuleProperties.IO_CONNECT_TIMEOUT.set(sshClient, timeout)
|
||||||
|
|
||||||
sshClient.setKeyPasswordProviderFactory { IdentityPasswordProvider(CredentialsProvider.getDefault()) }
|
sshClient.setKeyPasswordProviderFactory { IdentityPasswordProvider(CredentialsProvider.getDefault()) }
|
||||||
|
|
||||||
|
|||||||
@@ -520,9 +520,13 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
|
|||||||
|
|
||||||
val writer = terminalModel.getData(DataKey.TerminalWriter)
|
val writer = terminalModel.getData(DataKey.TerminalWriter)
|
||||||
|
|
||||||
// VT102_RESPONSE
|
if (args.startsWith('>')) {
|
||||||
val bytes = "${ControlCharacters.ESC}[?6c".toByteArray(writer.getCharset())
|
val bytes = "${ControlCharacters.ESC}[>0;276;0c".toByteArray(writer.getCharset())
|
||||||
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
|
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
|
||||||
|
} else {
|
||||||
|
val bytes = "${ControlCharacters.ESC}[?1;2c".toByteArray(writer.getCharset())
|
||||||
|
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class TerminalFindPanel(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (index - 1 <= 0) {
|
if (index - 1 <= 0) {
|
||||||
index = 0
|
index = kinds.size - 1
|
||||||
} else {
|
} else {
|
||||||
index--
|
index--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -507,6 +507,28 @@ internal class TransportPanel(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
table.actionMap.put("Delete", object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
val rows = table.selectedRows.map { sorter.convertRowIndexToModel(it) }.toTypedArray()
|
||||||
|
val files = rows.map { model.getPath(it) to model.getAttributes(it) }
|
||||||
|
// 排除父目录
|
||||||
|
val validFiles = files.filter { !it.second.isParent }
|
||||||
|
if (validFiles.isNotEmpty()) {
|
||||||
|
// 显示删除确认对话框
|
||||||
|
if (OptionPane.showConfirmDialog(
|
||||||
|
owner,
|
||||||
|
I18n.getString("termora.keymgr.delete-warning"),
|
||||||
|
messageType = JOptionPane.WARNING_MESSAGE
|
||||||
|
) == JOptionPane.YES_OPTION
|
||||||
|
) {
|
||||||
|
// 直接执行删除操作
|
||||||
|
val future = internalTransferManager.addTransfer(validFiles, InternalTransferManager.TransferMode.Delete)
|
||||||
|
mountFuture(future)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 快速导航
|
// 快速导航
|
||||||
table.addKeyListener(object : KeyAdapter() {
|
table.addKeyListener(object : KeyAdapter() {
|
||||||
override fun keyPressed(e: KeyEvent) {
|
override fun keyPressed(e: KeyEvent) {
|
||||||
@@ -530,12 +552,25 @@ internal class TransportPanel(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 重写全选行为,排除".."父目录
|
||||||
|
table.actionMap.put("selectAll", object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
table.clearSelection()
|
||||||
|
val startRow = if (hasParent) 1 else 0 // 跳过".."行
|
||||||
|
if (startRow < table.rowCount) {
|
||||||
|
table.setRowSelectionInterval(startRow, table.rowCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
val inputMap = table.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
|
val inputMap = table.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
|
||||||
if (SystemInfo.isMacOS.not()) {
|
if (SystemInfo.isMacOS.not()) {
|
||||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "Reload")
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "Reload")
|
||||||
}
|
}
|
||||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "EnterSelectionFolder")
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "EnterSelectionFolder")
|
||||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, toolkit.menuShortcutKeyMaskEx), "Reload")
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, toolkit.menuShortcutKeyMaskEx), "Reload")
|
||||||
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "Delete")
|
||||||
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "Delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initTransferHandler() {
|
private fun initTransferHandler() {
|
||||||
|
|||||||
@@ -239,6 +239,8 @@ termora.keymgr.table.name=Name
|
|||||||
termora.keymgr.table.type=Type
|
termora.keymgr.table.type=Type
|
||||||
termora.keymgr.table.length=Length
|
termora.keymgr.table.length=Length
|
||||||
termora.keymgr.table.remark=Description
|
termora.keymgr.table.remark=Description
|
||||||
|
termora.keymgr.export-done=The export was successful
|
||||||
|
termora.keymgr.export-done-open-folder=The export was successful. Do you want to open the folder?
|
||||||
|
|
||||||
termora.keymgr.ssh-copy-id.number=Number of hosts [{0}] Number of public keys [{1}]
|
termora.keymgr.ssh-copy-id.number=Number of hosts [{0}] Number of public keys [{1}]
|
||||||
termora.keymgr.ssh-copy-id.successful=${termora.terminal.copied}
|
termora.keymgr.ssh-copy-id.successful=${termora.terminal.copied}
|
||||||
@@ -248,6 +250,7 @@ termora.keymgr.ssh-copy-id.end=End of public key copying
|
|||||||
|
|
||||||
# Tabbed
|
# Tabbed
|
||||||
termora.tabbed.contextmenu.rename=Rename
|
termora.tabbed.contextmenu.rename=Rename
|
||||||
|
termora.tabbed.contextmenu.select-host=Select Host
|
||||||
termora.tabbed.contextmenu.sftp-command=SFTP Command
|
termora.tabbed.contextmenu.sftp-command=SFTP Command
|
||||||
termora.tabbed.contextmenu.sftp-not-install=SFTP programme not found, please install and try again
|
termora.tabbed.contextmenu.sftp-not-install=SFTP programme not found, please install and try again
|
||||||
termora.tabbed.contextmenu.clone=Clone
|
termora.tabbed.contextmenu.clone=Clone
|
||||||
|
|||||||
@@ -69,27 +69,6 @@ termora.settings.terminal.floating-toolbar=Плавающая панель
|
|||||||
termora.settings.terminal.auto-close-tab=Автозакрытие вкладки
|
termora.settings.terminal.auto-close-tab=Автозакрытие вкладки
|
||||||
termora.settings.terminal.auto-close-tab-description=Автоматически закрывать вкладку при обычном отключении терминала
|
termora.settings.terminal.auto-close-tab-description=Автоматически закрывать вкладку при обычном отключении терминала
|
||||||
|
|
||||||
termora.settings.sync=Синхронизация
|
|
||||||
termora.settings.sync.done=Синхронизация успешна
|
|
||||||
termora.settings.sync.export=${termora.keymgr.export}
|
|
||||||
termora.settings.sync.import=${termora.keymgr.import}
|
|
||||||
termora.settings.sync.import.file-too-large=Файл слишком большой
|
|
||||||
termora.settings.sync.import.successful=Импортировано успешно
|
|
||||||
termora.settings.sync.export-done=Экспортировано успешно
|
|
||||||
termora.settings.sync.export-encrypt=Введите пароль для расшифровки файла (выборочно)
|
|
||||||
termora.settings.sync.export-done-open-folder=Экспорт прошел успешно. Открыть папку?
|
|
||||||
termora.settings.sync.range=Диапазон
|
|
||||||
termora.settings.sync.range.keys=Мои ключи
|
|
||||||
termora.settings.sync.range.keyword-highlights=${termora.highlight}
|
|
||||||
termora.settings.sync.last-sync-time=Последняя синхронизация
|
|
||||||
termora.settings.sync.gist=Gist
|
|
||||||
termora.settings.sync.token=Токен
|
|
||||||
termora.settings.sync.type=Сервис
|
|
||||||
termora.settings.sync.webdav.help=WebDAV адрес, https://yourhost/webdav/termora.json
|
|
||||||
termora.settings.sync.policy=Тип синхронизации
|
|
||||||
termora.settings.sync.policy.manual=Вручную
|
|
||||||
termora.settings.sync.policy.on-change=При изменениях
|
|
||||||
|
|
||||||
termora.settings.about=О программе
|
termora.settings.about=О программе
|
||||||
termora.settings.about.author=Автор
|
termora.settings.about.author=Автор
|
||||||
termora.settings.about.source=Ссылка
|
termora.settings.about.source=Ссылка
|
||||||
@@ -204,6 +183,8 @@ termora.keymgr.table.name=Название
|
|||||||
termora.keymgr.table.type=Тип
|
termora.keymgr.table.type=Тип
|
||||||
termora.keymgr.table.length=Длина
|
termora.keymgr.table.length=Длина
|
||||||
termora.keymgr.table.remark=Описание
|
termora.keymgr.table.remark=Описание
|
||||||
|
termora.keymgr.export-done=Экспорт выполнен успешно
|
||||||
|
termora.keymgr.export-done-open-folder=Экспорт выполнен успешно. Открыть папку?
|
||||||
|
|
||||||
termora.keymgr.ssh-copy-id.number=Кол-во хостов [{0}] Кол-во публичных ключей [{1}]
|
termora.keymgr.ssh-copy-id.number=Кол-во хостов [{0}] Кол-во публичных ключей [{1}]
|
||||||
termora.keymgr.ssh-copy-id.successful=${termora.terminal.copied}
|
termora.keymgr.ssh-copy-id.successful=${termora.terminal.copied}
|
||||||
@@ -212,6 +193,7 @@ termora.keymgr.ssh-copy-id.end=Копирования открытого клю
|
|||||||
|
|
||||||
# Tabbed
|
# Tabbed
|
||||||
termora.tabbed.contextmenu.rename=Переименовать
|
termora.tabbed.contextmenu.rename=Переименовать
|
||||||
|
termora.tabbed.contextmenu.select-host=Выбрать хост
|
||||||
termora.tabbed.contextmenu.sftp-command=SFTP Команда
|
termora.tabbed.contextmenu.sftp-command=SFTP Команда
|
||||||
termora.tabbed.contextmenu.sftp-not-install=Программа SFTP не найдена, пожалуйста, установите и повторите попытку.
|
termora.tabbed.contextmenu.sftp-not-install=Программа SFTP не найдена, пожалуйста, установите и повторите попытку.
|
||||||
termora.tabbed.contextmenu.clone=Дублировать
|
termora.tabbed.contextmenu.clone=Дублировать
|
||||||
|
|||||||
@@ -232,6 +232,8 @@ termora.keymgr.table.name=名称
|
|||||||
termora.keymgr.table.type=类型
|
termora.keymgr.table.type=类型
|
||||||
termora.keymgr.table.length=长度
|
termora.keymgr.table.length=长度
|
||||||
termora.keymgr.table.remark=备注
|
termora.keymgr.table.remark=备注
|
||||||
|
termora.keymgr.export-done=导出成功
|
||||||
|
termora.keymgr.export-done-open-folder=导出成功,是否需要打开所在文件夹?
|
||||||
|
|
||||||
termora.keymgr.ssh-copy-id.number=主机数量 [{0}] 公钥数量 [{1}]
|
termora.keymgr.ssh-copy-id.number=主机数量 [{0}] 公钥数量 [{1}]
|
||||||
termora.keymgr.ssh-copy-id.failed=复制失败
|
termora.keymgr.ssh-copy-id.failed=复制失败
|
||||||
@@ -244,6 +246,7 @@ termora.tools.multiple=将命令发送到当前窗口会话
|
|||||||
|
|
||||||
# Tabbed
|
# Tabbed
|
||||||
termora.tabbed.contextmenu.rename=重命名
|
termora.tabbed.contextmenu.rename=重命名
|
||||||
|
termora.tabbed.contextmenu.select-host=选中主机
|
||||||
termora.tabbed.contextmenu.sftp-command=SFTP 终端
|
termora.tabbed.contextmenu.sftp-command=SFTP 终端
|
||||||
termora.tabbed.contextmenu.sftp-not-install=没有找到 SFTP 程序,请安装后重试
|
termora.tabbed.contextmenu.sftp-not-install=没有找到 SFTP 程序,请安装后重试
|
||||||
termora.tabbed.contextmenu.clone=克隆
|
termora.tabbed.contextmenu.clone=克隆
|
||||||
|
|||||||
@@ -228,6 +228,8 @@ termora.keymgr.table.name=名稱
|
|||||||
termora.keymgr.table.type=型別
|
termora.keymgr.table.type=型別
|
||||||
termora.keymgr.table.length=長度
|
termora.keymgr.table.length=長度
|
||||||
termora.keymgr.table.remark=備註
|
termora.keymgr.table.remark=備註
|
||||||
|
termora.keymgr.export-done=匯出成功
|
||||||
|
termora.keymgr.export-done-open-folder=匯出成功,是否需要打開所在資料夾?
|
||||||
|
|
||||||
termora.keymgr.ssh-copy-id.number=主機數量 [{0}] 公鑰數量 [{1}]
|
termora.keymgr.ssh-copy-id.number=主機數量 [{0}] 公鑰數量 [{1}]
|
||||||
termora.keymgr.ssh-copy-id.failed=複製失敗
|
termora.keymgr.ssh-copy-id.failed=複製失敗
|
||||||
@@ -239,6 +241,7 @@ termora.tools.multiple=將命令傳送到目前視窗會話
|
|||||||
|
|
||||||
# Tabbed
|
# Tabbed
|
||||||
termora.tabbed.contextmenu.rename=重新命名
|
termora.tabbed.contextmenu.rename=重新命名
|
||||||
|
termora.tabbed.contextmenu.select-host=選取主機
|
||||||
termora.tabbed.contextmenu.sftp-command=SFTP 終端
|
termora.tabbed.contextmenu.sftp-command=SFTP 終端
|
||||||
termora.tabbed.contextmenu.sftp-not-install=沒有找到 SFTP 程序,請安裝後重試
|
termora.tabbed.contextmenu.sftp-not-install=沒有找到 SFTP 程序,請安裝後重試
|
||||||
termora.tabbed.contextmenu.clone=克隆
|
termora.tabbed.contextmenu.clone=克隆
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ DisableProgramGroupPage=yes
|
|||||||
;PrivilegesRequired=lowest
|
;PrivilegesRequired=lowest
|
||||||
OutputDir={#MyOutputDir}
|
OutputDir={#MyOutputDir}
|
||||||
OutputBaseFilename={#MyAppName}-{#MyAppVersion}
|
OutputBaseFilename={#MyAppName}-{#MyAppVersion}
|
||||||
|
Compression=lzma2/max
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=classic
|
WizardStyle=classic
|
||||||
;WizardStyle=modern
|
;WizardStyle=modern
|
||||||
|
|||||||
12
src/test/resources/issue-1055/Dockerfile
Normal file
12
src/test/resources/issue-1055/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM debian:bookworm
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
RUN sed -i 's|http://deb.debian.org/debian|http://mirrors.aliyun.com/debian|g' /etc/apt/sources.list.d/debian.sources \
|
||||||
|
&& sed -i 's|http://security.debian.org/debian-security|http://mirrors.aliyun.com/debian-security|g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates autoconf libevent-dev bison automake libtool pkg-config build-essential libncurses-dev
|
||||||
|
|
||||||
|
RUN git clone https://github.com/tmux/tmux.git && cd tmux && sh autogen.sh && ./configure && make && make install
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user