Compare commits

...

37 Commits

Author SHA1 Message Date
hstyi
613a1ca78a release: 2.0.0-beta.14 2025-09-15 17:27:41 +08:00
dependabot[bot]
bf9e3ea2e2 chore(deps): bump com.qcloud:cos_api from 5.6.253 to 5.6.255
Bumps [com.qcloud:cos_api](https://github.com/tencentyun/cos-java-sdk-v5) from 5.6.253 to 5.6.255.
- [Release notes](https://github.com/tencentyun/cos-java-sdk-v5/releases)
- [Changelog](https://github.com/tencentyun/cos-java-sdk-v5/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tencentyun/cos-java-sdk-v5/commits)

---
updated-dependencies:
- dependency-name: com.qcloud:cos_api
  dependency-version: 5.6.255
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 17:18:58 +08:00
dependabot[bot]
a4390c4c6d chore(deps): bump org.commonmark:commonmark from 0.25.1 to 0.26.0
Bumps [org.commonmark:commonmark](https://github.com/commonmark/commonmark-java) from 0.25.1 to 0.26.0.
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0)

---
updated-dependencies:
- dependency-name: org.commonmark:commonmark
  dependency-version: 0.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 17:18:49 +08:00
hstyi
9cf317e245 fix: macros cannot sync 2025-09-15 07:01:18 +08:00
hstyi
d000d73122 fix: SFTP connection may time out 2025-09-12 10:22:39 +08:00
dependabot[bot]
88613ed2f6 chore(deps): bump kotlin from 2.2.10 to 2.2.20
Bumps `kotlin` from 2.2.10 to 2.2.20.

Updates `org.jetbrains.kotlin.jvm` from 2.2.10 to 2.2.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20)

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.10 to 2.2.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.jvm
  dependency-version: 2.2.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin.plugin.serialization
  dependency-version: 2.2.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-12 08:55:38 +08:00
hstyi
2fc381caa5 fix: virtual window auto hiding 2025-09-08 11:07:06 +08:00
hstyi
30e245f7a3 chore: add tooltip to some buttons 2025-09-08 10:59:18 +08:00
hstyi
35cf92e685 fix: exposed compile 2025-09-08 09:25:39 +08:00
hstyi
522ee44ca2 chore: upgrade exposed version 2025-09-06 10:54:31 +08:00
hstyi
5cf03e1f1f fix: transfer text error 2025-09-05 15:18:11 +08:00
dependabot[bot]
afca4ddf0e chore(deps): bump com.qcloud:cos_api from 5.6.251 to 5.6.253
Bumps [com.qcloud:cos_api](https://github.com/tencentyun/cos-java-sdk-v5) from 5.6.251 to 5.6.253.
- [Release notes](https://github.com/tencentyun/cos-java-sdk-v5/releases)
- [Changelog](https://github.com/tencentyun/cos-java-sdk-v5/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tencentyun/cos-java-sdk-v5/commits)

---
updated-dependencies:
- dependency-name: com.qcloud:cos_api
  dependency-version: 5.6.253
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 19:39:08 +08:00
dependabot[bot]
ca757f975a chore(deps): bump cn.hutool:hutool-all from 5.8.39 to 5.8.40
Bumps [cn.hutool:hutool-all](https://github.com/looly/hutool) from 5.8.39 to 5.8.40.
- [Release notes](https://github.com/looly/hutool/releases)
- [Changelog](https://github.com/chinabugotech/hutool/blob/v5-master/CHANGELOG.md)
- [Commits](https://github.com/looly/hutool/compare/5.8.39...5.8.40)

---
updated-dependencies:
- dependency-name: cn.hutool:hutool-all
  dependency-version: 5.8.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 06:11:18 +08:00
dependabot[bot]
79c304ae3d chore(deps): bump com.maxmind.geoip2:geoip2 from 4.3.1 to 4.4.0
Bumps [com.maxmind.geoip2:geoip2](https://github.com/maxmind/GeoIP2-java) from 4.3.1 to 4.4.0.
- [Release notes](https://github.com/maxmind/GeoIP2-java/releases)
- [Changelog](https://github.com/maxmind/GeoIP2-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/maxmind/GeoIP2-java/compare/v4.3.1...v4.4.0)

---
updated-dependencies:
- dependency-name: com.maxmind.geoip2:geoip2
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-29 16:48:43 +08:00
dependabot[bot]
1848c869e7 chore(deps): bump com.github.hstyi:geolite2
Bumps [com.github.hstyi:geolite2](https://github.com/hstyi/GeoLite2) from v1.0-202508110059 to v1.0-202508180058.
- [Release notes](https://github.com/hstyi/GeoLite2/releases)
- [Commits](https://github.com/hstyi/GeoLite2/commits)

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202508180058
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 11:10:58 +08:00
hsurich
029e570551 chore: improve SFTP file selection and deletion UX 2025-08-21 14:54:13 +08:00
dependabot[bot]
905c570e4c chore(deps): bump com.github.mwiede:jsch from 2.27.2 to 2.27.3
Bumps [com.github.mwiede:jsch](https://github.com/mwiede/jsch) from 2.27.2 to 2.27.3.
- [Release notes](https://github.com/mwiede/jsch/releases)
- [Changelog](https://github.com/mwiede/jsch/blob/master/ChangeLog)
- [Commits](https://github.com/mwiede/jsch/compare/jsch-2.27.2...jsch-2.27.3)

---
updated-dependencies:
- dependency-name: com.github.mwiede:jsch
  dependency-version: 2.27.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 12:05:00 +08:00
hstyi
a3069229b8 chore: ssh supports modifying the backspace behaviour 2025-08-20 21:31:37 +08:00
hstyi
1e930d61c9 fix: xterm Send Device Attributes 2025-08-20 17:05:19 +08:00
hstyi
0015c3a7fb chore: select host 2025-08-20 17:04:58 +08:00
hstyi
4bfb87e5c7 fix: connection timeout 2025-08-20 14:59:12 +08:00
hstyi
4fbb626c42 chore: find panel circle navigation 2025-08-20 10:52:07 +08:00
hstyi
35b175d944 fix: settings dialog scale 2025-08-20 09:08:58 +08:00
hstyi
5939297550 fix: some i18n errors 2025-08-18 15:40:25 +08:00
hstyi
e6e5867742 chore: Windows pack 2025-08-18 15:18:39 +08:00
hstyi
bd9b73ad6a release: 2.0.0-beta.13 2025-08-17 11:46:43 +08:00
hstyi
dbea769994 fix: sftp command may cause bad key types 2025-08-17 08:54:42 +08:00
hstyi
9cd83c4025 feat: host tree supports copy and paste 2025-08-15 16:35:43 +08:00
hstyi
d4cc080e7b chore: transfer text 2025-08-15 16:35:32 +08:00
hstyi
a324bc3d96 fix: keyword highlighting causes crash 2025-08-15 13:12:55 +08:00
dependabot[bot]
36929e9ea3 chore(deps): bump kotlin from 2.2.0 to 2.2.10
Bumps `kotlin` from 2.2.0 to 2.2.10.

Updates `org.jetbrains.kotlin.jvm` from 2.2.0 to 2.2.10
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.0...v2.2.10)

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.0 to 2.2.10
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.0...v2.2.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.jvm
  dependency-version: 2.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin.plugin.serialization
  dependency-version: 2.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 11:10:11 +08:00
hstyi
dd73b933d9 fix: some data cannot be pulled 2025-08-14 17:52:33 +08:00
hstyi
117a9ea692 fix: icons not displaying on some Linux systems 2025-08-13 09:36:59 +08:00
dependabot[bot]
2f932de295 chore(deps): bump com.huaweicloud:esdk-obs-java-bundle
Bumps [com.huaweicloud:esdk-obs-java-bundle](https://github.com/huaweicloud/huaweicloud-sdk-java-obs) from 3.25.5 to 3.25.7.
- [Release notes](https://github.com/huaweicloud/huaweicloud-sdk-java-obs/releases)
- [Commits](https://github.com/huaweicloud/huaweicloud-sdk-java-obs/compare/v3.25.5...v3.25.7)

---
updated-dependencies:
- dependency-name: com.huaweicloud:esdk-obs-java-bundle
  dependency-version: 3.25.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 09:36:18 +08:00
hstyi
679b24a74d chore: simplify host field input 2025-08-12 18:39:54 +08:00
hstyi
c6b33ea828 chore: improve settings action 2025-08-12 18:39:45 +08:00
dependabot[bot]
a4ea8f2491 chore(deps): bump com.github.hstyi:geolite2
Bumps [com.github.hstyi:geolite2](https://github.com/hstyi/GeoLite2) from v1.0-202508040102 to v1.0-202508110059.
- [Release notes](https://github.com/hstyi/GeoLite2/releases)
- [Commits](https://github.com/hstyi/GeoLite2/commits)

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202508110059
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 18:39:37 +08:00
43 changed files with 463 additions and 110 deletions

View File

@@ -1 +1 @@
2.0.0-beta.12
2.0.0-beta.14

View File

@@ -384,6 +384,7 @@ tasks.register<Exec>("jpackage") {
}
if (os.isLinux) {
options.add("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED")
if (isDeb) {
options.add("-Djpackage.app-layout=deb")
}
@@ -403,18 +404,6 @@ tasks.register<Exec>("jpackage") {
arguments.addAll(listOf("--copyright", "TermoraDev"))
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) {
arguments.addAll(listOf("--mac-package-name", project.name.uppercaseFirstChar()))
arguments.addAll(listOf("--mac-app-category", "developer-tools"))
@@ -681,17 +670,24 @@ fun packOnLinux(distributionDir: Directory, finalFilenameWithoutExtension: Strin
exec { commandLine("chmod", "+x", appimagetool.absolutePath) }
}
// Desktop file
val termoraName = project.name.uppercaseFirstChar()
// copy icon
FileUtils.copyFile(
File("${projectDir.absolutePath}/src/main/resources/icons/termora_256x256.png"),
distributionDir.file(termoraName + File.separator + termoraName + ".png").asFile
)
val desktopFile = distributionDir.file(termoraName + File.separator + termoraName + ".desktop").asFile
desktopFile.writeText(
"""[Desktop Entry]
Type=Application
Name=${termoraName}
Comment=Terminal emulator and SSH client
Icon=/lib/${termoraName}
Icon=${termoraName}
Categories=Development;
StartupWMClass=${termoraName}
Terminal=false
""".trimIndent()
)

View File

@@ -1,5 +1,5 @@
[versions]
kotlin = "2.2.0"
kotlin = "2.2.20"
slf4j = "2.0.17"
pty4j = "0.13.10"
tinylog = "2.7.0"
@@ -22,13 +22,13 @@ jna = "5.17.0"
jSystemThemeDetector = "3.9.1"
commons-io = "2.20.0"
jbr-api = "17.1.10.1"
hutool = "5.8.39"
jsch = "2.27.2"
hutool = "5.8.40"
jsch = "2.27.3"
okhttp = "5.1.0"
sshj = "0.39.0"
sshd-core = "2.15.0"
jgit = "7.2.0.202503040940-r"
commonmark = "0.25.1"
commonmark = "0.26.0"
jnafilechooser = "1.1.2"
xodus = "2.0.1"
bip39 = "1.0.9"
@@ -41,7 +41,7 @@ jSerialComm = "2.11.2"
ini4j = "0.5.5-2"
restart4j = "0.0.1"
eddsa = "0.3.0"
exposed = "1.0.0-beta-5"
exposed = "1.0.0-rc-1"
h2 = "2.3.232"
sqlite = "3.50.3.0"
jug = "5.1.0"
@@ -106,7 +106,7 @@ eddsa = { module = "net.i2p.crypto:eddsa", version.ref = "eddsa" }
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-crypt = { module = "org.jetbrains.exposed:exposed-crypt", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
exposed-migration = { module = "org.jetbrains.exposed:exposed-migration", version.ref = "exposed" }
exposed-migration = { module = "org.jetbrains.exposed:exposed-migration-core", version.ref = "exposed" }
h2 = { module = "com.h2database:h2", version.ref = "h2" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
jug = { module = "com.fasterxml.uuid:java-uuid-generator", version.ref = "jug" }

View File

@@ -8,7 +8,7 @@ project.version = "0.0.4"
dependencies {
testImplementation(kotlin("test"))
implementation("com.qcloud:cos_api:5.6.251")
implementation("com.qcloud:cos_api:5.6.255")
compileOnly(project(":"))
}

View File

@@ -7,9 +7,9 @@ project.version = "0.0.8"
dependencies {
testImplementation(kotlin("test"))
compileOnly(project(":"))
implementation("com.maxmind.geoip2:geoip2:4.3.1")
implementation("com.maxmind.geoip2:geoip2:4.4.0")
// https://github.com/hstyi/geolite2
implementation("com.github.hstyi:geolite2:v1.0-202508040102")
implementation("com.github.hstyi:geolite2:v1.0-202508180058")
}
apply(from = "$rootDir/plugins/common.gradle.kts")

View File

@@ -8,7 +8,7 @@ project.version = "0.0.2"
dependencies {
testImplementation(kotlin("test"))
implementation("com.huaweicloud:esdk-obs-java-bundle:3.25.5")
implementation("com.huaweicloud:esdk-obs-java-bundle:3.25.7")
compileOnly(project(":"))
}

View File

@@ -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.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.keys=My keys
termora.settings.sync.range.keyword-highlights=${termora.highlight}

View File

@@ -2,15 +2,10 @@ termora.plugins.sync.disabled-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.keys=我的密钥
termora.settings.sync.last-sync-time=最后同步时间
termora.settings.sync.done=同步数据成功
termora.settings.sync.import.file-too-large=文件太大
termora.settings.sync.import.successful=导入数据成功
termora.settings.sync.gist=片段
termora.settings.sync.token=令牌
termora.settings.sync.type=类型

View File

@@ -1,9 +1,6 @@
termora.plugins.sync.disabled-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.keys=我的密鑰
termora.settings.sync.last-sync-time=最後同步時間

View File

@@ -9,6 +9,7 @@ import org.apache.commons.lang3.SystemUtils
import org.apache.commons.lang3.math.NumberUtils
import org.slf4j.LoggerFactory
import org.tinylog.configuration.Configuration
import java.awt.Toolkit
import java.io.File
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@@ -35,10 +36,20 @@ class ApplicationInitializr {
// 检查是否单例
checkSingleton()
if (SystemUtils.IS_OS_MAC_OSX) {
if (SystemInfo.isMacOS) {
System.setProperty("apple.awt.application.name", Application.getName())
}
if (SystemInfo.isLinux) {
// https://stackoverflow.com/questions/10593075
runCatching {
val toolkit = Toolkit.getDefaultToolkit()
val awtAppClassNameField = toolkit.javaClass.getDeclaredField("awtAppClassName")
awtAppClassNameField.setAccessible(true)
awtAppClassNameField.set(toolkit, Application.getName())
}
}
// 启动
val runtime = measureTimeMillis { ApplicationRunner().run() }
val log = LoggerFactory.getLogger(javaClass)

View File

@@ -1,6 +1,7 @@
package app.termora
import app.termora.database.DatabaseManager
import com.formdev.flatlaf.util.UIScale
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Window
@@ -14,7 +15,10 @@ internal class SettingsDialog(owner: Window) : DialogWrapper(owner) {
private val properties get() = DatabaseManager.getInstance().properties
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
title = I18n.getString("termora.setting")
setLocationRelativeTo(null)

View File

@@ -2,6 +2,7 @@ package app.termora
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
import app.termora.plugin.internal.extension.DynamicExtensionHandler
import app.termora.tree.NewHostTree
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import com.formdev.flatlaf.extras.components.FlatToolBar
@@ -9,15 +10,14 @@ import com.formdev.flatlaf.util.SystemInfo
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Font
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.*
import javax.swing.*
import javax.swing.tree.TreePath
import kotlin.math.max
class TermoraFencePanel(
private val ws: WindowScope,
private val terminalTabbed: TerminalTabbed,
private val tabbed: FlatTabbedPane,
private val moveMouseAdapter: MouseAdapter,
@@ -98,6 +98,40 @@ class TermoraFencePanel(
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 {

View File

@@ -212,7 +212,7 @@ class TermoraFrame : JFrame(), DataProvider {
}
if (layout == TermoraLayout.Fence) {
val fencePanel = TermoraFencePanel(terminalTabbed, tabbedPane, moveMouseAdapter)
val fencePanel = TermoraFencePanel(windowScope, terminalTabbed, tabbedPane, moveMouseAdapter)
add(fencePanel, BorderLayout.CENTER)
dataProviderSupport.addData(DataProviders.Welcome.HostTree, fencePanel.getHostTree())
Disposer.register(windowScope, fencePanel)

View File

@@ -139,6 +139,7 @@ object AccountHttp {
}
} catch (e: Exception) {
if (cidr == "localhost" || cidr == "127.0.0.1") continue
if (log.isDebugEnabled) {
log.debug(e.message, e)
}

View File

@@ -126,7 +126,7 @@ class PullService private constructor() : SyncService(), Disposable, Application
while (true) {
val request = Request.Builder()
.get()
.url("${accountManager.getServer()}/v1/data/changes?since=${since}&after=${after}&limit=${limit}")
.url("${accountManager.getServer()}/v1/data/changes?since=${nextSince}&after=${after}&limit=${limit}")
.build()
val text = AccountHttp.execute(request = request)
val response = ohMyJson.decodeFromString<DataChangesResponse>(text)

View File

@@ -7,6 +7,7 @@ import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.lang3.ObjectUtils
import org.apache.commons.lang3.StringUtils
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update

View File

@@ -1,9 +1,6 @@
package app.termora.actions
import app.termora.ApplicationScope
import app.termora.I18n
import app.termora.Icons
import app.termora.SettingsDialog
import app.termora.*
import com.formdev.flatlaf.extras.FlatDesktop
import org.apache.commons.lang3.StringUtils
import java.awt.KeyboardFocusManager
@@ -32,13 +29,13 @@ class SettingsAction private constructor() : AnAction(
private val action get() = this
init {
FlatDesktop.setPreferencesHandler {
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner
// Doorman 的情况下不允许打开
if (owner != null && ApplicationScope.windowScopes().isNotEmpty()) {
actionPerformed(ActionEvent(owner, ActionEvent.ACTION_PERFORMED, StringUtils.EMPTY))
FlatDesktop.setPreferencesHandler(object : Runnable {
override fun run() {
val focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow ?: return
if (focusedWindow !is TermoraFrame) return
actionPerformed(ActionEvent(focusedWindow, ActionEvent.ACTION_PERFORMED, StringUtils.EMPTY))
}
}
})
}
override fun actionPerformed(evt: AnActionEvent) {

View File

@@ -16,8 +16,8 @@ import app.termora.snippet.SnippetManager
import app.termora.terminal.CursorStyle
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.StringUtils
import org.jetbrains.exposed.v1.core.SqlExpressionBuilder.eq
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.statements.StatementType
import org.jetbrains.exposed.v1.jdbc.*
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager

View File

@@ -101,6 +101,16 @@ internal class KeywordHighlightPaintListener private constructor() : TerminalPai
// -1 表示不使用高亮集
if (keywordHighlightSetId == "-1") return
try {
doFind(offset, count, terminal, keywordHighlightSetId)
} catch (e: Exception) {
if (log.isDebugEnabled) {
log.debug(e.message, e)
}
}
}
private fun doFind(offset: Int, count: Int, terminal: Terminal, keywordHighlightSetId: String) {
for (highlight in keywordHighlights) {
if (highlight.enabled.not()) continue
if (highlight.type != KeywordHighlightType.Highlight) continue
@@ -151,7 +161,6 @@ internal class KeywordHighlightPaintListener private constructor() : TerminalPai
}
}
}
override fun after(

View File

@@ -262,8 +262,8 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
OptionPane.openFileInFolder(
SwingUtilities.getWindowAncestor(this),
file, I18n.getString("termora.settings.sync.export-done-open-folder"),
I18n.getString("termora.settings.sync.export-done")
file, I18n.getString("termora.keymgr.export-done-open-folder"),
I18n.getString("termora.keymgr.export-done")
)
}

View File

@@ -32,7 +32,7 @@ class MacroManager private constructor() {
val accountId = AccountManager.getInstance().getAccountId()
database.save(
database.saveAndIncrementVersion(
Data(
id = macro.id,
ownerId = accountId,

View File

@@ -103,9 +103,20 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
commands.add("Compression=yes")
// HostKeyAlgorithms 让 SFTP 命令的顺序和 sshd 的一致 这样可以避免 known_hosts 文件不一致问题
val hostKeyAlgorithms = ClientBuilder.setUpDefaultSignatureFactories(true).joinToString(",") { it.name }
val hostKeyAlgorithms = ClientBuilder.setUpDefaultSignatureFactories(true).map { it.name }.toMutableList()
val localHostKeyAlgorithms = getLocalSSHHostKeyAlgorithms()
// 删除本地 ssh 不存在的算法
hostKeyAlgorithms.removeIf { localHostKeyAlgorithms.contains(it).not() }
// 把本地支持的再添加进去
for (algorithm in localHostKeyAlgorithms) {
if (hostKeyAlgorithms.contains(algorithm).not()) {
hostKeyAlgorithms.add(algorithm)
}
}
commands.add("-o")
commands.add("HostKeyAlgorithms=${hostKeyAlgorithms}")
commands.add("HostKeyAlgorithms=${hostKeyAlgorithms.joinToString(",")}")
// 不使用配置文件
commands.add("-F")
@@ -143,6 +154,15 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
return ptyConnector
}
private fun getLocalSSHHostKeyAlgorithms(): Set<String> {
val pb = ProcessBuilder("ssh", "-Q", "key")
val process = pb.start()
if (process.waitFor() != 0) {
return emptySet()
}
return String(process.inputStream.readAllBytes()).lines().filter { it.isNotBlank() }.toSet()
}
private fun setAuthentication(commands: MutableList<String>, host: Host) {
// 如果通过公钥连接
if (host.authentication.type == AuthenticationType.PublicKey) {

View File

@@ -8,6 +8,7 @@ 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.plugin.internal.telnet.TelnetHostOptionsPane.Backspace
import app.termora.tree.Filter
import app.termora.tree.HostTreeNode
import app.termora.tree.NewHostTreeDialog
@@ -24,6 +25,7 @@ import org.eclipse.jgit.internal.transport.sshd.agent.connector.WinPipeConnector
import java.awt.*
import java.awt.event.*
import javax.swing.*
import javax.swing.event.DocumentEvent
import javax.swing.table.DefaultTableCellRenderer
import javax.swing.table.DefaultTableModel
@@ -35,6 +37,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
private val terminalOption = BasicTerminalOption().apply {
showCharsetComboBox = true
showLoginScripts = true
showBackspaceComboBox = true
showEnvironmentTextArea = true
showStartupCommandTextField = true
showHeartbeatIntervalTextField = true
@@ -46,6 +49,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
private val jumpHostsOption = JumpHostsOption()
private val sftpOption = SFTPOption()
private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
private var setHostMode = false
init {
addOption(generalOption)
@@ -110,6 +114,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
x11Forwarding = tunnelingOption.x11ServerTextField.text,
loginScripts = terminalOption.loginScripts,
extras = mutableMapOf(
"backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name,
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
@@ -135,6 +140,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
}
fun setHost(host: Host) {
setHostMode = true
generalOption.portTextField.value = host.port
generalOption.nameTextField.text = host.name
generalOption.usernameTextField.text = host.username
@@ -166,6 +172,9 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
.getOrNull() ?: AltKeyModifier.EightBit
terminalOption.backspaceComboBox.selectedItem =
Backspace.valueOf(host.options.extras["backspace"] ?: Backspace.Delete.name)
val timeout = host.options.extras["timeout"] ?: "60"
terminalOption.timeoutTextField.value = timeout.toIntOrNull() ?: 60
@@ -298,6 +307,8 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
val remarkTextArea = FixedLengthTextArea(512)
val authenticationTypeComboBox = FlatComboBox<AuthenticationType>()
private var hostFocused = false
init {
initView()
initEvents()
@@ -405,6 +416,26 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
removeComponentListener(this)
}
})
hostTextField.addFocusListener(object : FocusAdapter() {
override fun focusGained(e: FocusEvent) {
hostTextField.removeFocusListener(this)
hostFocused = true
}
})
nameTextField.document.addDocumentListener(object : DocumentAdaptor() {
override fun changedUpdate(e: DocumentEvent) {
if (nameTextField.hasFocus().not()) return
if (hostFocused || setHostMode) {
nameTextField.document.removeDocumentListener(this)
return
}
hostTextField.text = nameTextField.text
}
})
}
private fun chooseKeyPair() {

View File

@@ -7,9 +7,8 @@ import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
import app.termora.database.DatabaseManager
import app.termora.keymap.KeyShortcut
import app.termora.keymap.KeymapManager
import app.termora.terminal.ControlCharacters
import app.termora.terminal.DataKey
import app.termora.terminal.PtyConnector
import app.termora.plugin.internal.telnet.TelnetHostOptionsPane
import app.termora.terminal.*
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
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.SshFutureListener
import org.slf4j.LoggerFactory
import java.awt.event.KeyEvent
import java.nio.charset.StandardCharsets
import javax.swing.Icon
import javax.swing.JComponent
@@ -110,7 +110,18 @@ class SSHTerminalTab(
// clear screen
terminal.clearScreen()
// 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(

View File

@@ -69,4 +69,7 @@ class SftpCommandTerminalTabbedContextMenuExtension private constructor() : Term
openHostAction.actionPerformed(OpenHostActionEvent(evt.source, host, evt))
}
override fun ordered(): Long {
return 1
}
}

View File

@@ -425,8 +425,11 @@ object SshClients {
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.ALLOW_DHG1_KEX_FALLBACK.set(sshClient, true)
CoreModuleProperties.IO_CONNECT_TIMEOUT.set(sshClient, timeout)
sshClient.setKeyPasswordProviderFactory { IdentityPasswordProvider(CredentialsProvider.getDefault()) }

View File

@@ -520,9 +520,13 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
val writer = terminalModel.getData(DataKey.TerminalWriter)
// VT102_RESPONSE
val bytes = "${ControlCharacters.ESC}[?6c".toByteArray(writer.getCharset())
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
if (args.startsWith('>')) {
val bytes = "${ControlCharacters.ESC}[>0;276;0c".toByteArray(writer.getCharset())
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
} else {
val bytes = "${ControlCharacters.ESC}[?1;2c".toByteArray(writer.getCharset())
writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
}
}

View File

@@ -172,7 +172,7 @@ class TerminalFindPanel(
}
} else {
if (index - 1 <= 0) {
index = 0
index = kinds.size - 1
} else {
index--
}

View File

@@ -30,6 +30,8 @@ import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import javax.swing.*
import javax.swing.event.PopupMenuEvent
import javax.swing.event.PopupMenuListener
import kotlin.io.path.absolutePathString
import kotlin.math.max
import kotlin.reflect.cast
@@ -58,12 +60,24 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private val connectFailedPanel = ConnectFailedPanel()
private val transferManager = TransferTableModel(coroutineScope)
private val disposable = Disposer.newDisposable()
private val focusedWindow get() = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow
private val owner get() = SwingUtilities.getWindowAncestor(this)
private val questionBtn = JButton(Icons.questionMark)
private val downloadBtn = JButton(Icons.download)
private val badgePresentation = Badge.getInstance(tab.windowScope)
.addBadge(downloadBtn).apply { visible = false }
private val support = DataProviderSupport()
private var isShowPopupMenu = false
override var isStickHover: Boolean
get() = super.isStickHover
set(value) {
if (isShowPopupMenu || owner != focusedWindow) {
super.isStickHover = true
} else {
super.isStickHover = value
}
}
init {
initViews()
@@ -135,6 +149,8 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
}
})
questionBtn.toolTipText = I18n.getString("termora.visual-window.transport.question")
// 立即连接
connect()
}
@@ -151,7 +167,7 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
val support = DefaultTransportSupport(fileSystem, fileSystem.defaultDir)
withContext(Dispatchers.Swing) {
val internalTransferManager = MyInternalTransferManager()
val transportPanel = TransportPanel(
val transportPanel = object : TransportPanel(
internalTransferManager, tab.host,
object : TransportSupportLoader {
override suspend fun getTransportSupport(): TransportSupport {
@@ -165,7 +181,27 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
override fun isLoaded(): Boolean {
return true
}
})
}) {
override fun customizeContextmenu(
rows: Array<Int>,
e: MouseEvent,
popupMenu: TransportPopupMenu
) {
popupMenu.addPopupMenuListener(object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
isShowPopupMenu = true
}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
isShowPopupMenu = false
}
override fun popupMenuCanceled(e: PopupMenuEvent?) {
isShowPopupMenu = false
}
})
}
}
internalTransferManager.setTransferPanel(transportPanel)
Disposer.register(transportPanel, object : Disposable {
override fun dispose() {
@@ -240,6 +276,10 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
super.dispose()
}
override fun reassemble() {
super.reassemble()
}
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
return support.getData(dataKey)
}

View File

@@ -36,6 +36,7 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
private var dialog: VisualWindowDialog? = null
private var oldBounds = Rectangle()
private var toggleWindowBtn = JButton(Icons.openInNewWindow)
private val closeBtn = JButton(Icons.close)
private var isAlwaysTop
get() = properties.getString("VisualWindow.${id}.dialog.isAlwaysTop", "false").toBoolean()
set(value) = properties.putString("VisualWindow.${id}.dialog.isAlwaysTop", value.toString())
@@ -47,8 +48,8 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
}
}
protected var isStickHover = false
private set(value) {
protected open var isStickHover = false
set(value) {
if (value == field) return
field = value
reassemble()
@@ -92,6 +93,8 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
oldBounds = bounds
alwaysTopBtn.isSelected = isAlwaysTop
alwaysTopBtn.isVisible = false
closeBtn.toolTipText = I18n.getString("termora.tabbed.contextmenu.close")
}
protected open fun toolbarButtons(): List<Pair<JButton, Position>> {
@@ -134,6 +137,7 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
addMouseListener(object : MouseAdapter() {})
toggleWindowBtn.addActionListener { toggleWindow() }
toggleWindowBtn.toolTipText = I18n.getString("termora.visual-window.toggle-window")
addPropertyChangeListener("isWindow") {
if (isWindow) {
@@ -165,6 +169,8 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
dialog?.isAlwaysOnTop = isAlwaysTop
}
}
closeBtn.addActionListener { if (beforeClose()) Disposer.dispose(visualWindow) }
}
private fun initToolBar() {
@@ -180,7 +186,7 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
buttons.filter { it.second == Position.Right }.forEach { toolbar.add(it.first) }
toolbar.add(toggleWindowBtn)
toolbar.add(JButton(Icons.close).apply { addActionListener { if (beforeClose()) Disposer.dispose(visualWindow) } })
toolbar.add(closeBtn)
toolbar.border = BorderFactory.createMatteBorder(0, 0, 1, 0, DynamicColor.BorderColor)
add(toolbar, BorderLayout.NORTH)
}

View File

@@ -66,6 +66,8 @@ class BookmarkButton : JButton(Icons.bookmarks) {
})
isBookmark = false
toolTipText = I18n.getString("termora.transport.bookmarks")
}
private fun showBookmarks(e: MouseEvent) {

View File

@@ -63,7 +63,7 @@ import kotlin.io.path.*
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
internal class TransportPanel(
internal open class TransportPanel(
private val internalTransferManager: InternalTransferManager,
val host: Host,
val loader: TransportSupportLoader,
@@ -131,10 +131,10 @@ internal class TransportPanel(
* 工作目录
*/
override var workdir: Path? = null
private set
protected set
override var loading = false
private set(value) {
protected set(value) {
val oldValue = field
field = value
if (oldValue != value) {
@@ -165,6 +165,14 @@ internal class TransportPanel(
toolbar.add(eyeBtn)
toolbar.add(refreshBtn)
prevBtn.toolTipText = I18n.getString("termora.transport.toolbar.prev")
homeBtn.toolTipText = I18n.getString("termora.transport.toolbar.home")
nextBtn.toolTipText = I18n.getString("termora.transport.toolbar.next")
parentBtn.toolTipText = I18n.getString("termora.transport.toolbar.parent")
eyeBtn.toolTipText = I18n.getString("termora.transport.toolbar.show-hide")
refreshBtn.toolTipText = I18n.getString("termora.transport.toolbar.refresh")
sorter.maxSortKeys = 1
table.setRowSorter(sorter)
table.setAutoCreateRowSorter(false)
@@ -411,7 +419,7 @@ internal class TransportPanel(
}
})
addPropertyChangeListener("workdir") { evt -> reload() }
addPropertyChangeListener("workdir") { _ -> reload() }
reload()
}
@@ -507,6 +515,29 @@ 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() {
override fun keyPressed(e: KeyEvent) {
@@ -530,12 +561,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)
if (SystemInfo.isMacOS.not()) {
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "Reload")
}
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_DELETE, 0), "Delete")
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "Delete")
}
private fun initTransferHandler() {
@@ -881,13 +925,18 @@ internal class TransportPanel(
}
}
private fun showContextmenu(rows: Array<Int>, e: MouseEvent) {
protected open fun showContextmenu(rows: Array<Int>, e: MouseEvent) {
val files = rows.map { model.getPath(it) to model.getAttributes(it) }
val popupMenu = TransportPopupMenu(owner, model, internalTransferManager, loader, files)
popupMenu.addActionListener(PopupMenuActionListener(files))
customizeContextmenu(rows, e, popupMenu)
popupMenu.show(table, e.x, e.y)
}
protected open fun customizeContextmenu(rows: Array<Int>, e: MouseEvent, popupMenu: TransportPopupMenu) {
}
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
return support.getData(dataKey)
}

View File

@@ -4,6 +4,7 @@ import app.termora.*
import app.termora.plugin.ExtensionManager
import app.termora.transfer.TransportPanel.Companion.isLocallyFileSystem
import com.formdev.flatlaf.extras.components.FlatPopupMenu
import com.formdev.flatlaf.util.SystemInfo
import kotlinx.coroutines.launch
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
@@ -41,7 +42,14 @@ internal class TransportPopupMenu(
private val copyPathMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.copy-path"))
private val copyMenu = JMenuItem(I18n.getString("termora.copy"))
private val pasteMenu = JMenuItem(I18n.getString("termora.paste"))
private val openInFinderMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.open-in-folder"))
private val openInFinderMenu = JMenuItem(
I18n.getString(
"termora.transport.table.contextmenu.open-in-folder",
if (SystemInfo.isMacOS) I18n.getString("termora.finder")
else if (SystemInfo.isWindows) I18n.getString("termora.explorer")
else I18n.getString("termora.folder")
)
)
private val renameMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.rename"))
private val deleteMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.delete"))

View File

@@ -8,6 +8,8 @@ import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import org.apache.sshd.client.SshClient
import org.apache.sshd.client.session.ClientSession
import org.apache.sshd.core.CoreModuleProperties
import org.apache.sshd.sftp.SftpModuleProperties
import org.apache.sshd.sftp.client.SftpClientFactory
internal class SFTPTransferProtocolProvider : TransferProtocolProvider {
@@ -32,6 +34,11 @@ internal class SFTPTransferProtocolProvider : TransferProtocolProvider {
client = if (owner == null) SshClients.openClient(requester.host)
else SshClients.openClient(requester.host, owner)
session = SshClients.openSession(requester.host, client)
CoreModuleProperties.IO_CONNECT_TIMEOUT.get(client).ifPresent { e ->
SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT.set(session, e)
}
val fileSystem = SftpClientFactory.instance().createSftpFileSystem(session)
val host = requester.host

View File

@@ -3,6 +3,8 @@ package app.termora.tree
import app.termora.*
import app.termora.Application.ohMyJson
import app.termora.account.AccountManager
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
import app.termora.actions.OpenHostAction
import app.termora.database.DatabaseChangedExtension
import app.termora.database.DatabaseManager
@@ -32,6 +34,10 @@ import org.jdesktop.swingx.action.ActionManager
import org.slf4j.LoggerFactory
import org.w3c.dom.Element
import org.w3c.dom.NodeList
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.Transferable
import java.awt.datatransfer.UnsupportedFlavorException
import java.awt.event.*
import java.io.*
import java.util.*
@@ -140,6 +146,41 @@ class NewHostTree : SimpleTree(), Disposable {
}
})
actionMap.put("copy", object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
toolkit.systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null)
val nodes = getSelectionSimpleTreeNodes(false).toMutableList()
nodes.removeIf { e -> e.getParents().any { nodes.contains(it) } }
if (nodes.isEmpty() || nodes.any { it is TeamTreeNode }) return
if (nodes.any { it.id == "0" || it.id.isBlank() }) return
toolkit.systemClipboard.setContents(NodesTransferable(nodes), null)
}
})
actionMap.put("paste", object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val lastNode = getLastSelectedPathNode() ?: return
val folder = if (lastNode.isFolder) lastNode.parent ?: simpleTreeModel.root
else lastNode.parent ?: return
if (toolkit.systemClipboard.isDataFlavorAvailable(NodesTransferable.FLAVOR).not()) return
val nodes = (toolkit.systemClipboard.getData(NodesTransferable.FLAVOR) as? List<*>)
?.filterIsInstance<HostTreeNode>() ?: return
for (node in nodes) {
val newNode = copyNode(node, folder.id)
// 复制的是文件夹,就在最后面
if (newNode.isFolder) {
simpleTreeModel.insertNodeInto(newNode, folder, folder.folderCount)
} else if (lastNode.isFolder) { // 用户选的节点是文件夹那就在最后一个child下面
simpleTreeModel.insertNodeInto(newNode, folder, folder.childCount)
} else { // 用户选的是主机并且复制的是主机
simpleTreeModel.insertNodeInto(newNode, folder, folder.getIndex(lastNode) + 1)
}
}
}
})
}
fun restoreExpansions() {
@@ -198,10 +239,12 @@ class NewHostTree : SimpleTree(), Disposable {
val sshMenu = importMenu.add(".ssh/config")
val mobaXtermMenu = importMenu.add("MobaXterm")
// 为了避免误导,如果是 SSH 右键时显示 SFTP
val sftpText = if (SSHProtocolProvider.PROTOCOL.equals(lastHost.protocol, true))
"SFTP" else I18n.getString("termora.transport.sftp")
val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.connect"))
val openWith = popupMenu.add(JMenu(I18n.getString("termora.welcome.contextmenu.connect-with"))) as JMenu
val openWithSFTP = openWith.add(I18n.getString("termora.transport.sftp"))
val openWithSFTP = openWith.add(sftpText)
val openWithSFTPCommand = openWith.add(I18n.getString("termora.tabbed.contextmenu.sftp-command"))
val openInNewWindow = popupMenu.add(I18n.getString("termora.welcome.contextmenu.open-in-new-window"))
popupMenu.addSeparator()
@@ -389,6 +432,20 @@ class NewHostTree : SimpleTree(), Disposable {
})
val mnemonics = mapOf(
refresh to KeyEvent.VK_R,
newMenu to KeyEvent.VK_W,
newFolder to KeyEvent.VK_F,
rename to KeyEvent.VK_M,
remove to KeyEvent.VK_D,
property to KeyEvent.VK_I,
)
for ((item, mnemonic) in mnemonics) {
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
item.setMnemonic(mnemonic)
}
popupMenu.show(this, evt.x, evt.y)
}
@@ -1075,5 +1132,23 @@ class NewHostTree : SimpleTree(), Disposable {
electerm,
}
private class NodesTransferable(val nodes: List<HostTreeNode>) : Transferable {
companion object {
val FLAVOR = DataFlavor("termora/host-tree", "Termora host tree transfers")
}
override fun getTransferDataFlavors(): Array<out DataFlavor> {
return arrayOf(FLAVOR)
}
override fun isDataFlavorSupported(flavor: DataFlavor?): Boolean {
return flavor == FLAVOR
}
override fun getTransferData(flavor: DataFlavor?): Any {
return if (flavor == FLAVOR) nodes else throw UnsupportedFlavorException(flavor)
}
}
}

View File

@@ -2,6 +2,7 @@ package app.termora.tree
import javax.swing.Icon
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.TreeNode
abstract class SimpleTreeNode<T>(data: T) : DefaultMutableTreeNode(data) {
@Suppress("UNCHECKED_CAST")
@@ -35,4 +36,15 @@ abstract class SimpleTreeNode<T>(data: T) : DefaultMutableTreeNode(data) {
return children
}
open fun getParents(): List<SimpleTreeNode<T>> {
val parents = mutableListOf<SimpleTreeNode<T>>()
var p = parent as TreeNode?
while (p != null) {
if (p is SimpleTreeNode<T>) {
parents.add(p)
}
p = p.parent
}
return parents
}
}

View File

@@ -239,6 +239,8 @@ termora.keymgr.table.name=Name
termora.keymgr.table.type=Type
termora.keymgr.table.length=Length
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.successful=${termora.terminal.copied}
@@ -248,6 +250,7 @@ termora.keymgr.ssh-copy-id.end=End of public key copying
# Tabbed
termora.tabbed.contextmenu.rename=Rename
termora.tabbed.contextmenu.select-host=Select Host
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.clone=Clone
@@ -308,6 +311,14 @@ termora.tools.multiple=Send command to the current window sessions
termora.transport.local=Local
termora.transport.file-already-exists=The file {0} already exists
termora.transport.toolbar.prev=Backward
termora.transport.toolbar.home=Home Folder
termora.transport.toolbar.next=Forward
termora.transport.toolbar.parent=Parent Folder
termora.transport.toolbar.show-hide=Show/Hide Folders
termora.transport.toolbar.refresh=Refresh Folder
termora.transport.bookmarks=Bookmarks Manager
termora.transport.bookmarks.up=Up
termora.transport.bookmarks.down=Down
@@ -324,7 +335,7 @@ termora.transport.table.owner=Owner
termora.transport.table.contextmenu.transfer=Transfer
termora.transport.table.contextmenu.edit=${termora.keymgr.edit}
termora.transport.table.contextmenu.copy-path=Copy Path
termora.transport.table.contextmenu.open-in-folder=Open in ${termora.finder}
termora.transport.table.contextmenu.open-in-folder=Open in {0}
termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename}
termora.transport.table.contextmenu.delete=${termora.remove}
termora.transport.table.contextmenu.rm-warning=Using the rm -rf command to delete a file is very dangerous
@@ -430,6 +441,8 @@ termora.visual-window.system-information.mem=Mem
termora.visual-window.system-information.swap=Swap
termora.visual-window.system-information.filesystem=Filesystem
termora.visual-window.system-information.used-total=Used / Total
termora.visual-window.toggle-window=Toggle window
termora.visual-window.transport.question=More Features
termora.visual-window.nvidia-smi=NVIDIA SMI

View File

@@ -69,27 +69,6 @@ termora.settings.terminal.floating-toolbar=Плавающая панель
termora.settings.terminal.auto-close-tab=Автозакрытие вкладки
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.author=Автор
termora.settings.about.source=Ссылка
@@ -204,6 +183,8 @@ termora.keymgr.table.name=Название
termora.keymgr.table.type=Тип
termora.keymgr.table.length=Длина
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.successful=${termora.terminal.copied}
@@ -212,6 +193,7 @@ termora.keymgr.ssh-copy-id.end=Копирования открытого клю
# Tabbed
termora.tabbed.contextmenu.rename=Переименовать
termora.tabbed.contextmenu.select-host=Выбрать хост
termora.tabbed.contextmenu.sftp-command=SFTP Команда
termora.tabbed.contextmenu.sftp-not-install=Программа SFTP не найдена, пожалуйста, установите и повторите попытку.
termora.tabbed.contextmenu.clone=Дублировать
@@ -269,6 +251,14 @@ termora.transport.bookmarks=Менеджер закладок
termora.transport.bookmarks.up=Вверх
termora.transport.bookmarks.down=Вниз
termora.transport.toolbar.prev=Назад
termora.transport.toolbar.home=Домашняя папка
termora.transport.toolbar.next=Вперёд
termora.transport.toolbar.parent=Родительская папка
termora.transport.toolbar.show-hide=Показать/Скрыть папки
termora.transport.toolbar.refresh=Обновить
termora.transport.table.filename=Имя файла
termora.transport.table.type=Тип
termora.transport.table.type.symbolic-link=Символьная Ссылка
@@ -376,6 +366,8 @@ termora.visual-window.system-information.mem=Память
termora.visual-window.system-information.swap=Подкачка
termora.visual-window.system-information.filesystem=Файловая система
termora.visual-window.system-information.used-total=Использовано / Всего
termora.visual-window.toggle-window=Переключить окно
termora.visual-window.transport.question=Больше возможностей
termora.visual-window.nvidia-smi=NVIDIA SMI

View File

@@ -232,6 +232,8 @@ termora.keymgr.table.name=名称
termora.keymgr.table.type=类型
termora.keymgr.table.length=长度
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.failed=复制失败
@@ -244,6 +246,7 @@ termora.tools.multiple=将命令发送到当前窗口会话
# Tabbed
termora.tabbed.contextmenu.rename=重命名
termora.tabbed.contextmenu.select-host=选中主机
termora.tabbed.contextmenu.sftp-command=SFTP 终端
termora.tabbed.contextmenu.sftp-not-install=没有找到 SFTP 程序,请安装后重试
termora.tabbed.contextmenu.clone=克隆
@@ -309,6 +312,15 @@ termora.transport.bookmarks=书签管理
termora.transport.bookmarks.up=上移
termora.transport.bookmarks.down=下移
termora.transport.toolbar.prev=返回
termora.transport.toolbar.home=默认目录
termora.transport.toolbar.next=前进
termora.transport.toolbar.parent=父目录
termora.transport.toolbar.show-hide=显示/隐藏目录
termora.transport.toolbar.refresh=刷新
termora.transport.table.filename=文件名
termora.transport.table.type=类型
termora.transport.table.size=大小
@@ -320,7 +332,7 @@ termora.transport.table.owner=所有者
# contextmenu
termora.transport.table.contextmenu.transfer=传输
termora.transport.table.contextmenu.copy-path=复制路径
termora.transport.table.contextmenu.open-in-folder=${termora.finder}中打开
termora.transport.table.contextmenu.open-in-folder={0}中打开
termora.transport.table.contextmenu.change-permissions=更改权限...
termora.transport.table.contextmenu.refresh=刷新
termora.transport.table.contextmenu.compress=压缩
@@ -426,6 +438,8 @@ termora.visual-window.system-information.mem=内存
termora.visual-window.system-information.swap=交换
termora.visual-window.system-information.filesystem=文件系统
termora.visual-window.system-information.used-total=使用 / 大小
termora.visual-window.toggle-window=切换窗口
termora.visual-window.transport.question=更多功能
termora.floating-toolbar.close-in-current-tab=在当前标签页关闭

View File

@@ -228,6 +228,8 @@ termora.keymgr.table.name=名稱
termora.keymgr.table.type=型別
termora.keymgr.table.length=長度
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.failed=複製失敗
@@ -239,6 +241,7 @@ termora.tools.multiple=將命令傳送到目前視窗會話
# Tabbed
termora.tabbed.contextmenu.rename=重新命名
termora.tabbed.contextmenu.select-host=選取主機
termora.tabbed.contextmenu.sftp-command=SFTP 終端
termora.tabbed.contextmenu.sftp-not-install=沒有找到 SFTP 程序,請安裝後重試
termora.tabbed.contextmenu.clone=克隆
@@ -304,6 +307,13 @@ termora.transport.bookmarks=書籤管理
termora.transport.bookmarks.up=上移
termora.transport.bookmarks.down=下移
termora.transport.toolbar.prev=返回
termora.transport.toolbar.home=預設目錄
termora.transport.toolbar.next=前進
termora.transport.toolbar.parent=父目錄
termora.transport.toolbar.show-hide=顯示/隱藏目錄
termora.transport.toolbar.refresh=重新整理
termora.transport.table.filename=檔名
termora.transport.table.type=類型
termora.transport.table.size=大小
@@ -315,7 +325,7 @@ termora.transport.table.owner=所有者
# contextmenu
termora.transport.table.contextmenu.transfer=傳輸
termora.transport.table.contextmenu.copy-path=複製路徑
termora.transport.table.contextmenu.open-in-folder=${termora.finder}中打開
termora.transport.table.contextmenu.open-in-folder={0}中打開
termora.transport.table.contextmenu.change-permissions=更改權限...
termora.transport.table.contextmenu.refresh=刷新
termora.transport.table.contextmenu.compress=壓縮
@@ -413,6 +423,8 @@ termora.visual-window.system-information.mem=內存
termora.visual-window.system-information.swap=交換
termora.visual-window.system-information.filesystem=檔案系統
termora.visual-window.system-information.used-total=使用 / 大小
termora.visual-window.toggle-window=切換視窗
termora.visual-window.transport.question=更多功能
termora.floating-toolbar.close-in-current-tab=在目前標籤頁關閉

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -34,6 +34,7 @@ DisableProgramGroupPage=yes
;PrivilegesRequired=lowest
OutputDir={#MyOutputDir}
OutputBaseFilename={#MyAppName}-{#MyAppVersion}
Compression=lzma2/max
SolidCompression=yes
WizardStyle=classic
;WizardStyle=modern

View 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