mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22:58 +08:00
Compare commits
11 Commits
2.0.0-beta
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f329ef60df | ||
|
|
8acfdb8bca | ||
|
|
a7aec52f2a | ||
|
|
7f1317a9a7 | ||
|
|
a8a1fea91b | ||
|
|
675ad4608a | ||
|
|
72ba3757e2 | ||
|
|
c58e84d2ae | ||
|
|
18a7a5059b | ||
|
|
f0102b6f13 | ||
|
|
0cf8eb3c17 |
7
.github/workflows/linux.yml
vendored
7
.github/workflows/linux.yml
vendored
@@ -3,7 +3,8 @@ name: Linux
|
||||
on: [ push, pull_request ]
|
||||
|
||||
env:
|
||||
DOCKER_NAME: hstyi/jbr:21.0.7b1038.58
|
||||
JBR_MAJOR: 21.0.7
|
||||
JBR_PATCH: b1038.58
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -25,6 +26,10 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-gradlexyz-
|
||||
|
||||
- name: Set dynamic DOCKER_NAME
|
||||
run: |
|
||||
echo "DOCKER_NAME=hstyi/jbr:${{ env.JBR_MAJOR }}${{ env.JBR_PATCH }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Create docker-run.sh helper script
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
4
.github/workflows/osx.yml
vendored
4
.github/workflows/osx.yml
vendored
@@ -8,6 +8,8 @@ env:
|
||||
# 只有发布版本时才需要公证
|
||||
TERMORA_MAC_NOTARY: "${{ startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' }}"
|
||||
TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE: ${{ secrets.TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE }}
|
||||
JBR_MAJOR: 21.0.7
|
||||
JBR_PATCH: b1038.58
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -60,7 +62,7 @@ jobs:
|
||||
else
|
||||
ARCH="x64"
|
||||
fi
|
||||
wget -q -O $RUNNER_TEMP/java_package.tar.gz https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.7-osx-$ARCH-b1034.51.tar.gz
|
||||
wget -q -O $RUNNER_TEMP/java_package.tar.gz https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${{ env.JBR_MAJOR }}-osx-$ARCH-${{ env.JBR_PATCH }}.tar.gz
|
||||
|
||||
# install jdk
|
||||
- name: Installing Java
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
name: Windows x86-64
|
||||
name: Windows
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
env:
|
||||
JBR_MAJOR: 21.0.7
|
||||
JBR_PATCH: b1038.58
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-11-arm, windows-latest ]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set architecture
|
||||
id: set-arch
|
||||
run: |
|
||||
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
|
||||
echo "ARCH=aarch64" >> $env:GITHUB_ENV
|
||||
} else {
|
||||
echo "ARCH=x64" >> $env:GITHUB_ENV
|
||||
}
|
||||
|
||||
- name: Install zip
|
||||
run: |
|
||||
$system32 = [System.Environment]::GetEnvironmentVariable("WINDIR") + "\System32"
|
||||
@@ -21,9 +37,13 @@ jobs:
|
||||
|
||||
- name: Installing Java
|
||||
run: |
|
||||
curl -s --output ${{ runner.temp }}\java_package.zip -L https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.7-windows-x64-b1034.51.zip
|
||||
unzip -q ${{ runner.temp }}\java_package.zip -d ${{ runner.temp }}\jbr
|
||||
echo "JAVA_HOME=${{ runner.temp }}\jbr\jbrsdk-21.0.7-windows-x64-b1034.51" >> $env:GITHUB_ENV
|
||||
$zipPath = "${{ runner.temp }}\java_package.zip"
|
||||
$extractDir = "${{ runner.temp }}\jbr"
|
||||
$url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${{ env.JBR_MAJOR }}-windows-${{ env.ARCH }}-${{ env.JBR_PATCH }}.zip"
|
||||
curl -s --output $zipPath -L $url
|
||||
unzip -q $zipPath -d $extractDir
|
||||
$jbrDir = Get-ChildItem $extractDir | Select-Object -First 1
|
||||
echo "JAVA_HOME=$($jbrDir.FullName)" >> $env:GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
@@ -49,7 +69,7 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: termora-windows-x86-64
|
||||
name: termora-windows-${{ runner.arch }}
|
||||
path: |
|
||||
build/distributions/*.zip
|
||||
build/distributions/*.exe
|
||||
@@ -90,8 +90,6 @@ Termora is developed using [**Kotlin/JVM**](https://kotlinlang.org/) and partial
|
||||
We recommend using the [JetBrainsRuntime](https://github.com/JetBrains/JetBrainsRuntime) JDK for development.
|
||||
|
||||
- Run locally: `./gradlew :run`
|
||||
- Build for current OS: `./gradlew :dist`
|
||||
|
||||
|
||||
|
||||
## 📄 License
|
||||
|
||||
@@ -88,8 +88,6 @@ Termora 使用 [**Kotlin/JVM**](https://kotlinlang.org/) 开发,支持(正
|
||||
建议使用 [JetBrainsRuntime](https://github.com/JetBrains/JetBrainsRuntime) JDK 运行环境。
|
||||
|
||||
- 本地运行:`./gradlew :run`
|
||||
- 构建当前系统安装包:`./gradlew :dist`
|
||||
|
||||
|
||||
|
||||
## 📄 授权协议
|
||||
|
||||
@@ -480,10 +480,6 @@ tasks.register<Exec>("jpackage") {
|
||||
}
|
||||
|
||||
if (os.isWindows) {
|
||||
arguments.add("--win-dir-chooser")
|
||||
arguments.add("--win-shortcut")
|
||||
arguments.add("--win-shortcut-prompt")
|
||||
arguments.addAll(listOf("--win-upgrade-uuid", "E1D93CAD-5BF8-442E-93BA-6E90DE601E4C"))
|
||||
arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.ico"))
|
||||
}
|
||||
|
||||
@@ -496,7 +492,7 @@ tasks.register<Exec>("jpackage") {
|
||||
if (os.isMacOsX) {
|
||||
arguments.add("dmg")
|
||||
} else if (os.isWindows) {
|
||||
arguments.add("msi")
|
||||
arguments.add("app-image")
|
||||
} else if (os.isLinux) {
|
||||
arguments.add(if (isDeb) "deb" else "app-image")
|
||||
if (isDeb) {
|
||||
@@ -568,7 +564,7 @@ tasks.register("check-license") {
|
||||
* 创建 zip、msi
|
||||
*/
|
||||
fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: String, projectName: String) {
|
||||
val dir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
|
||||
val dir = layout.buildDirectory.dir("distributions").get().asFile
|
||||
val cfg = FileUtils.getFile(dir, projectName, "app", "${projectName}.cfg")
|
||||
val configText = cfg.readText()
|
||||
|
||||
@@ -593,21 +589,12 @@ fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: Str
|
||||
"/DMyAppVersion=${appVersion}",
|
||||
"/DMyOutputDir=${distributionDir.asFile.absolutePath}",
|
||||
"/DMySetupIconFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora.ico")}",
|
||||
"/DMySourceDir=${layout.buildDirectory.dir("jpackage/images/win-msi.image/${projectName}").get().asFile}",
|
||||
"/DMySourceDir=${FileUtils.getFile(dir, projectName).absolutePath}",
|
||||
"/F${finalFilenameWithoutExtension}",
|
||||
FileUtils.getFile(projectDir, "src", "main", "resources", "termora.iss")
|
||||
)
|
||||
}
|
||||
|
||||
// msi
|
||||
exec {
|
||||
commandLine(
|
||||
"cmd", "/c", "move",
|
||||
"${projectName}-${appVersion}.msi",
|
||||
"${finalFilenameWithoutExtension}.msi"
|
||||
)
|
||||
workingDir = distributionDir.asFile
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
project.version = "0.0.1"
|
||||
project.version = "0.0.2"
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.awt.Component
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.awt.event.ItemEvent
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
@@ -246,6 +247,12 @@ class FTPHostOptionsPane : OptionsPane() {
|
||||
removeComponentListener(this)
|
||||
}
|
||||
})
|
||||
|
||||
authenticationTypeComboBox.addItemListener {
|
||||
if (it.stateChange == ItemEvent.SELECTED) {
|
||||
passwordTextField.isEnabled = authenticationTypeComboBox.selectedItem == AuthenticationType.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
|
||||
|
||||
project.version = "0.0.1"
|
||||
project.version = "0.0.2"
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package app.termora.plugins.serial
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.plugin.internal.BasicGeneralOption
|
||||
import app.termora.plugin.internal.BasicTerminalOption
|
||||
import com.fazecast.jSerialComm.SerialPort
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
@@ -15,12 +17,15 @@ import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
class SerialHostOptionsPane : OptionsPane() {
|
||||
private val generalOption = BasicGeneralOption()
|
||||
private val terminalOption = TerminalOption()
|
||||
private val terminalOption = BasicTerminalOption().apply {
|
||||
showCharsetComboBox = true
|
||||
showStartupCommandTextField = true
|
||||
init()
|
||||
}
|
||||
private val serialCommOption = SerialCommOption()
|
||||
|
||||
init {
|
||||
@@ -48,6 +53,10 @@ class SerialHostOptionsPane : OptionsPane() {
|
||||
encoding = terminalOption.charsetComboBox.selectedItem as String,
|
||||
startupCommand = terminalOption.startupCommandTextField.text,
|
||||
serialComm = serialComm,
|
||||
extras = mutableMapOf(
|
||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||
?: AltKeyModifier.EightBit.name),
|
||||
)
|
||||
)
|
||||
|
||||
return Host(
|
||||
@@ -128,67 +137,6 @@ class SerialHostOptionsPane : OptionsPane() {
|
||||
}
|
||||
|
||||
|
||||
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected inner class SerialCommOption : JPanel(BorderLayout()), Option {
|
||||
val serialPortComboBox = OutlineComboBox<String>()
|
||||
val baudRateComboBox = OutlineComboBox<Int>()
|
||||
|
||||
@@ -24,12 +24,13 @@ import java.awt.*
|
||||
import java.awt.desktop.AppReopenedEvent
|
||||
import java.awt.desktop.AppReopenedListener
|
||||
import java.awt.desktop.SystemEventListener
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.WindowEvent
|
||||
import java.awt.event.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.*
|
||||
import javax.swing.event.PopupMenuEvent
|
||||
import javax.swing.event.PopupMenuListener
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class ApplicationRunner {
|
||||
@@ -112,16 +113,63 @@ class ApplicationRunner {
|
||||
if (!SystemInfo.isWindows || !SystemTray.isSupported()) return
|
||||
|
||||
val tray = SystemTray.getSystemTray()
|
||||
val image = ImageIO.read(TermoraFrame::class.java.getResourceAsStream("/icons/termora_16x16.png"))
|
||||
val image = ImageIO.read(TermoraFrame::class.java.getResourceAsStream("/icons/termora_32x32.png"))
|
||||
val trayIcon = TrayIcon(image)
|
||||
val popupMenu = PopupMenu()
|
||||
trayIcon.popupMenu = popupMenu
|
||||
val dialog = JDialog()
|
||||
val trayPopup = JPopupMenu()
|
||||
|
||||
dialog.isUndecorated = true
|
||||
dialog.isModal = false
|
||||
dialog.size = Dimension(0, 0)
|
||||
|
||||
trayIcon.isImageAutoSize = true
|
||||
trayIcon.toolTip = Application.getName()
|
||||
|
||||
// PopupMenu 不支持中文
|
||||
val exitMenu = MenuItem("Exit")
|
||||
exitMenu.addActionListener { SwingUtilities.invokeLater { quitHandler() } }
|
||||
popupMenu.add(exitMenu)
|
||||
trayPopup.add(I18n.getString("termora.exit")).addActionListener { quitHandler() }
|
||||
trayPopup.addPopupMenuListener(object : PopupMenuListener {
|
||||
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
|
||||
|
||||
}
|
||||
|
||||
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
|
||||
SwingUtilities.invokeLater {
|
||||
if (dialog.isVisible) {
|
||||
dialog.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun popupMenuCanceled(e: PopupMenuEvent?) {
|
||||
popupMenuWillBecomeInvisible(e)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
trayIcon.addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseReleased(e: MouseEvent) {
|
||||
maybeShowPopup(e)
|
||||
}
|
||||
|
||||
override fun mousePressed(e: MouseEvent) {
|
||||
maybeShowPopup(e)
|
||||
}
|
||||
|
||||
private fun maybeShowPopup(e: MouseEvent) {
|
||||
if (e.isPopupTrigger) {
|
||||
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||
trayPopup.setLocation(mouseLocation.x, mouseLocation.y)
|
||||
trayPopup.setInvoker(dialog)
|
||||
dialog.isVisible = true
|
||||
trayPopup.isVisible = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
dialog.addWindowFocusListener(object : WindowAdapter() {
|
||||
override fun windowLostFocus(e: WindowEvent) {
|
||||
dialog.isVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
// double click
|
||||
trayIcon.addActionListener(object : AbstractAction() {
|
||||
|
||||
21
src/main/kotlin/app/termora/FramePlugin.kt
Normal file
21
src/main/kotlin/app/termora/FramePlugin.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.DatabasePropertiesChangedExtension
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.InternalPlugin
|
||||
|
||||
internal class FramePlugin : InternalPlugin() {
|
||||
init {
|
||||
support.addExtension(DatabasePropertiesChangedExtension::class.java) { KeymapRefresher.getInstance() }
|
||||
support.addExtension(DatabaseChangedExtension::class.java) { KeymapRefresher.getInstance() }
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "Frame"
|
||||
}
|
||||
|
||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||
return support.getExtensions(clazz)
|
||||
}
|
||||
}
|
||||
65
src/main/kotlin/app/termora/KeymapRefresher.kt
Normal file
65
src/main/kotlin/app/termora/KeymapRefresher.kt
Normal file
@@ -0,0 +1,65 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.DatabasePropertiesChangedExtension
|
||||
import app.termora.keymap.KeymapManager
|
||||
|
||||
internal class KeymapRefresher private constructor() : DatabasePropertiesChangedExtension, DatabaseChangedExtension {
|
||||
companion object {
|
||||
fun getInstance(): KeymapRefresher {
|
||||
return ApplicationScope.forApplicationScope()
|
||||
.getOrCreate(KeymapRefresher::class) { KeymapRefresher() }
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners = mutableListOf<() -> Unit>()
|
||||
private var currentKeymap: String? = null
|
||||
private val keymapManager get() = KeymapManager.getInstance()
|
||||
private val activeKeymapName get() = keymapManager.getActiveKeymap().name
|
||||
|
||||
override fun onDataChanged(
|
||||
id: String,
|
||||
type: String,
|
||||
action: DatabaseChangedExtension.Action,
|
||||
source: DatabaseChangedExtension.Source
|
||||
) {
|
||||
if (type != "Keymap") return
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPropertyChanged(name: String, key: String, value: String) {
|
||||
if (name != "Setting.Properties") return
|
||||
if (key != "Keymap.Active") return
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
synchronized(this) {
|
||||
if (currentKeymap == activeKeymapName) {
|
||||
return
|
||||
}
|
||||
|
||||
currentKeymap = activeKeymapName
|
||||
|
||||
for (function in listeners) {
|
||||
function.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addRefreshListener(listener: () -> Unit): Disposable {
|
||||
synchronized(this) {
|
||||
listeners.add(listener)
|
||||
return object : Disposable {
|
||||
override fun dispose() {
|
||||
removeRefreshListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeRefreshListener(listener: () -> Unit) {
|
||||
synchronized(this) { listeners.remove(listener) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.actions.DataProviders
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.terminal.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.swing.Swing
|
||||
@@ -46,6 +47,9 @@ abstract class PtyHostTerminalTab(
|
||||
// 开启 reader
|
||||
startPtyConnectorReader()
|
||||
|
||||
// 修饰
|
||||
terminalKeyModifiers()
|
||||
|
||||
// 启动命令
|
||||
if (host.options.startupCommand.isNotBlank()) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
@@ -155,6 +159,15 @@ abstract class PtyHostTerminalTab(
|
||||
ptyConnector.write(bytes)
|
||||
}
|
||||
|
||||
open fun terminalKeyModifiers() {
|
||||
val altModifier = host.options.extras["altModifier"]
|
||||
if (altModifier == AltKeyModifier.CharactersPrecededByESC.name) {
|
||||
terminalModel.setData(DataKey.AltModifier, AltKeyModifier.CharactersPrecededByESC)
|
||||
} else {
|
||||
terminalModel.setData(DataKey.AltModifier, AltKeyModifier.EightBit)
|
||||
}
|
||||
}
|
||||
|
||||
override fun canReconnect(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.UIManager
|
||||
|
||||
class SettingsDialog(owner: Window) : DialogWrapper(owner) {
|
||||
internal class SettingsDialog(owner: Window) : DialogWrapper(owner) {
|
||||
private val optionsPane = SettingsOptionsPane()
|
||||
private val properties get() = DatabaseManager.getInstance().properties
|
||||
|
||||
|
||||
@@ -839,7 +839,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private fun p(): JPanel {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, 20dlu, pref, 4dlu, pref, 4dlu, pref, 4dlu, pref"
|
||||
"pref, 20dlu, pref, 4dlu, pref, 4dlu, pref, 4dlu, pref, 4dlu, pref"
|
||||
)
|
||||
|
||||
|
||||
@@ -848,7 +848,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
|
||||
val branch = if (Application.isUnknownVersion()) "main" else Application.getVersion()
|
||||
|
||||
return FormBuilder.create().padding("$FORM_MARGIN, $FORM_MARGIN, $FORM_MARGIN, $FORM_MARGIN")
|
||||
val builder = FormBuilder.create().padding("$FORM_MARGIN, $FORM_MARGIN, $FORM_MARGIN, $FORM_MARGIN")
|
||||
.layout(layout).debug(false)
|
||||
.add(I18n.getString("termora.settings.about.termora", Application.getVersion()))
|
||||
.xyw(1, rows, 3, "center, fill").apply { rows += step }
|
||||
@@ -870,8 +870,14 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
"Open-source software"
|
||||
)
|
||||
).xy(3, rows).apply { rows += step }
|
||||
.build()
|
||||
|
||||
if (I18n.isChinaMainland()) {
|
||||
builder.add("交流群:").xy(1, rows)
|
||||
.add(createHyperlink("https://www.termora.cn/muted/discussion-group", "Discussion Group"))
|
||||
.xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.actions.TerminalFocusModeAction
|
||||
import app.termora.database.DatabaseManager
|
||||
import app.termora.terminal.*
|
||||
import app.termora.terminal.panel.TerminalPanel
|
||||
import app.termora.tlog.TerminalLoggerDataListener
|
||||
import java.awt.Color
|
||||
import javax.swing.UIManager
|
||||
import kotlin.reflect.cast
|
||||
|
||||
class TerminalFactory private constructor() : Disposable {
|
||||
private val terminals = mutableListOf<Terminal>()
|
||||
@@ -75,6 +77,8 @@ class TerminalFactory private constructor() : Disposable {
|
||||
override fun <T : Any> getData(key: DataKey<T>, defaultValue: T): T {
|
||||
if (key == TerminalPanel.SelectCopy) {
|
||||
return config.selectCopy as T
|
||||
} else if (key == TerminalPanel.FocusMode) {
|
||||
return key.clazz.cast(TerminalFocusModeAction.getInstance().isSelected)
|
||||
}
|
||||
return super.getData(key, defaultValue)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package app.termora
|
||||
|
||||
|
||||
import app.termora.actions.*
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.DatabasePropertiesChangedExtension
|
||||
import app.termora.findeverywhere.FindEverywhereProvider
|
||||
import app.termora.findeverywhere.FindEverywhereProviderExtension
|
||||
import app.termora.findeverywhere.FindEverywhereResult
|
||||
@@ -73,12 +71,8 @@ class TermoraFrame : JFrame(), DataProvider {
|
||||
}
|
||||
|
||||
// 快捷键变动时重新监听
|
||||
val refresher = KeymapRefresher()
|
||||
dynamicExtensionHandler.register(DatabasePropertiesChangedExtension::class.java, refresher)
|
||||
KeymapRefresher.getInstance().addRefreshListener { initKeymap() }
|
||||
.let { Disposer.register(windowScope, it) }
|
||||
dynamicExtensionHandler.register(DatabaseChangedExtension::class.java, refresher)
|
||||
.let { Disposer.register(windowScope, it) }
|
||||
|
||||
|
||||
// FindEverywhere
|
||||
dynamicExtensionHandler
|
||||
@@ -418,29 +412,6 @@ class TermoraFrame : JFrame(), DataProvider {
|
||||
return object : MouseAdapter() {}
|
||||
}
|
||||
|
||||
private inner class KeymapRefresher : DatabasePropertiesChangedExtension, DatabaseChangedExtension {
|
||||
|
||||
override fun onDataChanged(
|
||||
id: String,
|
||||
type: String,
|
||||
action: DatabaseChangedExtension.Action,
|
||||
source: DatabaseChangedExtension.Source
|
||||
) {
|
||||
if (type != "Keymap") return
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPropertyChanged(name: String, key: String, value: String) {
|
||||
if (name != "Setting.Properties") return
|
||||
if (key != "Keymap.Active") return
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
initKeymap()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class RedirectAnActionEvent(
|
||||
source: Any,
|
||||
|
||||
@@ -53,6 +53,7 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() {
|
||||
addAction(TerminalClearScreenAction.CLEAR_SCREEN, TerminalClearScreenAction())
|
||||
addAction(OpenLocalTerminalAction.LOCAL_TERMINAL, OpenLocalTerminalAction())
|
||||
addAction(TerminalSelectAllAction.SELECT_ALL, TerminalSelectAllAction())
|
||||
addAction(TerminalFocusModeAction.FocusMode, TerminalFocusModeAction.getInstance())
|
||||
|
||||
addAction(TerminalZoomInAction.ZOOM_IN, TerminalZoomInAction())
|
||||
addAction(TerminalZoomOutAction.ZOOM_OUT, TerminalZoomOutAction())
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package app.termora.actions
|
||||
|
||||
import app.termora.ApplicationScope
|
||||
import app.termora.EnableManager
|
||||
import app.termora.I18n
|
||||
import app.termora.Icons
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class TerminalFocusModeAction private constructor() : AnAction(
|
||||
I18n.getString("termora.actions.focus-mode"),
|
||||
Icons.eye
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val FocusMode = "TerminalFocusMode"
|
||||
private val log = LoggerFactory.getLogger(TerminalFocusModeAction::class.java)
|
||||
fun getInstance(): TerminalFocusModeAction {
|
||||
return ApplicationScope.forApplicationScope()
|
||||
.getOrCreate(TerminalFocusModeAction::class) { TerminalFocusModeAction() }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
putValue(SHORT_DESCRIPTION, I18n.getString("termora.actions.focus-mode"))
|
||||
putValue(ACTION_COMMAND_KEY, FocusMode)
|
||||
setStateAction()
|
||||
isSelected = enableManager.getFlag("Terminal.FocusMode", false)
|
||||
}
|
||||
|
||||
private val enableManager get() = EnableManager.getInstance()
|
||||
|
||||
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
enableManager.setFlag("Terminal.FocusMode", isSelected)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import app.termora.I18n
|
||||
import app.termora.Scope
|
||||
import app.termora.WindowScope
|
||||
import app.termora.actions.MultipleAction
|
||||
import app.termora.actions.TerminalFocusModeAction
|
||||
|
||||
import org.jdesktop.swingx.action.ActionManager
|
||||
|
||||
@@ -13,6 +14,7 @@ class QuickActionsFindEverywhereProvider(private val windowScope: WindowScope) :
|
||||
Actions.KEY_MANAGER,
|
||||
Actions.KEYWORD_HIGHLIGHT,
|
||||
MultipleAction.MULTIPLE,
|
||||
TerminalFocusModeAction.FocusMode,
|
||||
)
|
||||
|
||||
override fun find(pattern: String, scope: Scope): List<FindEverywhereResult> {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package app.termora.highlight
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.Application.ohMyJson
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.terminal.TerminalColor
|
||||
import com.formdev.flatlaf.extras.components.FlatTable
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.Component
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.swing.*
|
||||
import javax.swing.border.EmptyBorder
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
@@ -29,7 +34,8 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
|
||||
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
|
||||
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
|
||||
private val deleteBtn = JButton(I18n.getString("termora.remove"))
|
||||
|
||||
private val importBtn = JButton(I18n.getString("termora.keymgr.import"))
|
||||
private val exportBtn = JButton(I18n.getString("termora.keymgr.export"))
|
||||
|
||||
init {
|
||||
initView()
|
||||
@@ -213,6 +219,29 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
|
||||
deleteBtn.isEnabled = editBtn.isEnabled
|
||||
}
|
||||
|
||||
exportBtn.addActionListener {
|
||||
val fileChooser = FileChooser()
|
||||
fileChooser.fileSelectionMode = JFileChooser.FILES_ONLY
|
||||
fileChooser.win32Filters.add(Pair("All files", listOf("*")))
|
||||
fileChooser.showSaveDialog(owner, "highlights.json").thenAccept { file ->
|
||||
file?.outputStream()?.use {
|
||||
val highlights = keywordHighlightManager.getKeywordHighlights(accountOwner.id)
|
||||
.map { e -> e.copy(id = randomUUID()) }
|
||||
IOUtils.write(ohMyJson.encodeToString(highlights), it, StandardCharsets.UTF_8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
importBtn.addActionListener {
|
||||
val chooser = FileChooser()
|
||||
chooser.osxAllowedFileTypes = listOf("json")
|
||||
chooser.allowsMultiSelection = false
|
||||
chooser.win32Filters.add(Pair("JSON files", listOf("json")))
|
||||
chooser.fileSelectionMode = JFileChooser.FILES_ONLY
|
||||
chooser.showOpenDialog(owner)
|
||||
.thenAccept { if (it.isNotEmpty()) SwingUtilities.invokeLater { importKeywordHighlights(it.first()) } }
|
||||
}
|
||||
|
||||
Disposer.register(this, object : Disposable {
|
||||
override fun dispose() {
|
||||
terminal.close()
|
||||
@@ -220,6 +249,23 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
|
||||
})
|
||||
}
|
||||
|
||||
private fun importKeywordHighlights(file: File) {
|
||||
try {
|
||||
val highlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(file.readText())
|
||||
.map { it.copy(id = randomUUID()) }
|
||||
for (highlight in highlights) {
|
||||
keywordHighlightManager.addKeywordHighlight(highlight, accountOwner)
|
||||
model.fireTableRowsInserted(model.rowCount - 1, model.rowCount)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
OptionPane.showMessageDialog(
|
||||
owner,
|
||||
message = e.message ?: ExceptionUtils.getRootCauseMessage(e),
|
||||
messageType = JOptionPane.ERROR_MESSAGE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCenterPanel(): JComponent {
|
||||
|
||||
val panel = JPanel(BorderLayout())
|
||||
@@ -232,13 +278,15 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
|
||||
val formMargin = "4dlu"
|
||||
val layout = FormLayout(
|
||||
"default:grow",
|
||||
"pref, $formMargin, pref, $formMargin, pref"
|
||||
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||
)
|
||||
panel.add(
|
||||
FormBuilder.create().layout(layout).padding(EmptyBorder(0, 12, 0, 0))
|
||||
.add(addBtn).xy(1, rows).apply { rows += step }
|
||||
.add(editBtn).xy(1, rows).apply { rows += step }
|
||||
.add(deleteBtn).xy(1, rows).apply { rows += step }
|
||||
.add(importBtn).xy(1, rows).apply { rows += step }
|
||||
.add(exportBtn).xy(1, rows).apply { rows += step }
|
||||
.build(),
|
||||
BorderLayout.EAST)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package app.termora.plugin
|
||||
|
||||
import app.termora.Application
|
||||
import app.termora.ApplicationScope
|
||||
import app.termora.FramePlugin
|
||||
import app.termora.account.AccountPlugin
|
||||
import app.termora.plugin.internal.badge.BadgePlugin
|
||||
import app.termora.plugin.internal.extension.DynamicExtensionPlugin
|
||||
@@ -111,6 +112,8 @@ internal class PluginManager private constructor() {
|
||||
plugins.add(PluginDescriptor(BadgePlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// update plugin
|
||||
plugins.add(PluginDescriptor(UpdatePlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
// frame plugin
|
||||
plugins.add(PluginDescriptor(FramePlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
|
||||
// ssh plugin
|
||||
plugins.add(PluginDescriptor(SSHInternalPlugin(), origin = PluginOrigin.Internal, version = version))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package app.termora.plugin.internal
|
||||
|
||||
enum class AltKeyModifier {
|
||||
EightBit,
|
||||
CharactersPrecededByESC,
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package app.termora.plugin.internal
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.OptionsPane.Companion.FORM_MARGIN
|
||||
import app.termora.OptionsPane.Option
|
||||
import app.termora.plugin.internal.telnet.TelnetHostOptionsPane.Backspace
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
class BasicTerminalOption() : JPanel(BorderLayout()), Option {
|
||||
|
||||
var showCharsetComboBox: Boolean = false
|
||||
var showStartupCommandTextField: Boolean = false
|
||||
var showHeartbeatIntervalTextField: Boolean = false
|
||||
var showEnvironmentTextArea: Boolean = false
|
||||
var showLoginScripts: Boolean = false
|
||||
var showBackspaceComboBox: Boolean = false
|
||||
var showCharacterAtATimeTextField: Boolean = false
|
||||
var showAltModifierComboBox: Boolean = true
|
||||
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val heartbeatIntervalTextField = IntSpinner(30, minimum = 3, maximum = Int.MAX_VALUE)
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
val loginScripts = mutableListOf<LoginScript>()
|
||||
val backspaceComboBox = JComboBox<Backspace>()
|
||||
val altModifierComboBox = JComboBox<AltKeyModifier>()
|
||||
val characterAtATimeTextField = YesOrNoComboBox()
|
||||
|
||||
|
||||
private val loginScriptPanel = LoginScriptPanel(loginScripts)
|
||||
private val tabbed = FlatTabbedPane()
|
||||
|
||||
fun init() {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
if (showLoginScripts) {
|
||||
tabbed.styleMap = mapOf(
|
||||
"focusColor" to DynamicColor("TabbedPane.background"),
|
||||
"hoverColor" to DynamicColor("TabbedPane.background"),
|
||||
)
|
||||
tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4
|
||||
putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel)
|
||||
add(tabbed, BorderLayout.CENTER)
|
||||
} else {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
if (showAltModifierComboBox) {
|
||||
altModifierComboBox.addItem(AltKeyModifier.EightBit)
|
||||
altModifierComboBox.addItem(AltKeyModifier.CharactersPrecededByESC)
|
||||
|
||||
altModifierComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<*>?,
|
||||
value: Any?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): Component? {
|
||||
var text = value?.toString() ?: value
|
||||
if (value == AltKeyModifier.CharactersPrecededByESC) {
|
||||
text = I18n.getString("termora.new-host.terminal.alt-modifier.by-esc")
|
||||
} else if (value == AltKeyModifier.EightBit) {
|
||||
text = I18n.getString("termora.new-host.terminal.alt-modifier.eight-bit")
|
||||
}
|
||||
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showBackspaceComboBox) {
|
||||
backspaceComboBox.addItem(Backspace.Delete)
|
||||
backspaceComboBox.addItem(Backspace.Backspace)
|
||||
backspaceComboBox.addItem(Backspace.VT220)
|
||||
}
|
||||
|
||||
if (showCharacterAtATimeTextField) {
|
||||
characterAtATimeTextField.selectedItem = false
|
||||
}
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val builder = FormBuilder.create().layout(layout)
|
||||
if (showLoginScripts) {
|
||||
builder.border(BorderFactory.createEmptyBorder(6, 8, 6, 8))
|
||||
}
|
||||
|
||||
if (showCharsetComboBox) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
if (showAltModifierComboBox) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.alt-modifier")}:").xy(1, rows)
|
||||
.add(altModifierComboBox).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
if (showBackspaceComboBox) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows)
|
||||
.add(backspaceComboBox).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
if (showCharacterAtATimeTextField) {
|
||||
builder
|
||||
.add("${I18n.getString("termora.new-host.terminal.character-mode")}:").xy(1, rows)
|
||||
.add(characterAtATimeTextField).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
if (showHeartbeatIntervalTextField) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows)
|
||||
.add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
if (showStartupCommandTextField) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
}
|
||||
|
||||
|
||||
if (showEnvironmentTextArea) {
|
||||
builder.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
}
|
||||
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,25 @@
|
||||
package app.termora.plugin.internal.local
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.Host
|
||||
import app.termora.Options
|
||||
import app.termora.OptionsPane
|
||||
import app.termora.SerialComm
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.plugin.internal.BasicGeneralOption
|
||||
import app.termora.plugin.internal.BasicTerminalOption
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.Window
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
internal open class LocalHostOptionsPane : OptionsPane() {
|
||||
protected val generalOption = BasicGeneralOption()
|
||||
protected val terminalOption = TerminalOption()
|
||||
private val terminalOption = BasicTerminalOption().apply {
|
||||
showCharsetComboBox = true
|
||||
showEnvironmentTextArea = true
|
||||
showStartupCommandTextField = true
|
||||
init()
|
||||
}
|
||||
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
|
||||
init {
|
||||
@@ -35,6 +40,10 @@ internal open class LocalHostOptionsPane : OptionsPane() {
|
||||
env = terminalOption.environmentTextArea.text,
|
||||
startupCommand = terminalOption.startupCommandTextField.text,
|
||||
serialComm = serialComm,
|
||||
extras = mutableMapOf(
|
||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||
?: AltKeyModifier.EightBit.name),
|
||||
)
|
||||
)
|
||||
|
||||
return Host(
|
||||
@@ -77,83 +86,4 @@ internal open class LocalHostOptionsPane : OptionsPane() {
|
||||
textField.requestFocusInWindow()
|
||||
}
|
||||
|
||||
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import java.awt.KeyboardFocusManager
|
||||
import java.awt.Window
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.awt.event.ItemEvent
|
||||
import javax.swing.*
|
||||
|
||||
internal open class RDPHostOptionsPane : OptionsPane() {
|
||||
@@ -223,6 +224,12 @@ internal open class RDPHostOptionsPane : OptionsPane() {
|
||||
removeComponentListener(this)
|
||||
}
|
||||
})
|
||||
|
||||
authenticationTypeComboBox.addItemListener {
|
||||
if (it.stateChange == ItemEvent.SELECTED) {
|
||||
passwordTextField.isEnabled = authenticationTypeComboBox.selectedItem == AuthenticationType.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ import app.termora.*
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.keymgr.KeyManager
|
||||
import app.termora.keymgr.KeyManagerDialog
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.plugin.internal.BasicProxyOption
|
||||
import app.termora.plugin.internal.BasicTerminalOption
|
||||
import app.termora.tree.Filter
|
||||
import app.termora.tree.HostTreeNode
|
||||
import app.termora.tree.NewHostTreeDialog
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.extras.components.FlatComboBox
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
@@ -21,20 +22,26 @@ import org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixDomainSocket
|
||||
import org.eclipse.jgit.internal.transport.sshd.agent.connector.WinPipeConnector
|
||||
import java.awt.*
|
||||
import java.awt.event.*
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
import javax.swing.table.DefaultTableModel
|
||||
|
||||
@Suppress("CascadeIf")
|
||||
open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
|
||||
protected val tunnelingOption = TunnelingOption()
|
||||
protected val generalOption = GeneralOption()
|
||||
protected val proxyOption = BasicProxyOption()
|
||||
protected val terminalOption = TerminalOption()
|
||||
protected val jumpHostsOption = JumpHostsOption()
|
||||
protected val sftpOption = SFTPOption()
|
||||
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
|
||||
private val tunnelingOption = TunnelingOption()
|
||||
private val generalOption = GeneralOption()
|
||||
private val proxyOption = BasicProxyOption()
|
||||
private val terminalOption = BasicTerminalOption().apply {
|
||||
showCharsetComboBox = true
|
||||
showLoginScripts = true
|
||||
showEnvironmentTextArea = true
|
||||
showStartupCommandTextField = true
|
||||
showHeartbeatIntervalTextField = true
|
||||
init()
|
||||
}
|
||||
private val jumpHostsOption = JumpHostsOption()
|
||||
private val sftpOption = SFTPOption()
|
||||
private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
|
||||
init {
|
||||
addOption(generalOption)
|
||||
@@ -47,7 +54,7 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP
|
||||
}
|
||||
|
||||
|
||||
open fun getHost(): Host {
|
||||
fun getHost(): Host {
|
||||
val name = generalOption.nameTextField.text
|
||||
val protocol = SSHProtocolProvider.PROTOCOL
|
||||
val host = generalOption.hostTextField.text
|
||||
@@ -98,6 +105,10 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP
|
||||
enableX11Forwarding = tunnelingOption.x11ForwardingCheckBox.isSelected,
|
||||
x11Forwarding = tunnelingOption.x11ServerTextField.text,
|
||||
loginScripts = terminalOption.loginScripts,
|
||||
extras = mutableMapOf(
|
||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||
?: AltKeyModifier.EightBit.name),
|
||||
)
|
||||
)
|
||||
|
||||
return Host(
|
||||
@@ -486,102 +497,6 @@ open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsP
|
||||
}
|
||||
|
||||
|
||||
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val heartbeatIntervalTextField = IntSpinner(30, minimum = 3, maximum = Int.MAX_VALUE)
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
val loginScripts = mutableListOf<LoginScript>()
|
||||
|
||||
private val loginScriptPanel = LoginScriptPanel(loginScripts)
|
||||
private val tabbed = FlatTabbedPane()
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
|
||||
tabbed.styleMap = mapOf(
|
||||
"focusColor" to DynamicColor("TabbedPane.background"),
|
||||
"hoverColor" to DynamicColor("TabbedPane.background"),
|
||||
)
|
||||
tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4
|
||||
putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel)
|
||||
add(tabbed, BorderLayout.CENTER)
|
||||
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.border(BorderFactory.createEmptyBorder(6, 8, 6, 8))
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.heartbeat-interval")}:").xy(1, rows)
|
||||
.add(heartbeatIntervalTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected inner class SFTPOption : JPanel(BorderLayout()), Option {
|
||||
val defaultDirectoryField = OutlineTextField(255)
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.plugin.internal.BasicProxyOption
|
||||
import app.termora.plugin.internal.BasicTerminalOption
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
@@ -12,7 +13,6 @@ import java.awt.BorderLayout
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
|
||||
@@ -20,7 +20,16 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
||||
|
||||
// telnet 不支持代理密码
|
||||
private val proxyOption = BasicProxyOption(authenticationTypes = listOf())
|
||||
private val terminalOption = TerminalOption()
|
||||
private val terminalOption = BasicTerminalOption().apply {
|
||||
showCharsetComboBox = true
|
||||
showBackspaceComboBox = true
|
||||
showStartupCommandTextField = true
|
||||
showCharacterAtATimeTextField = true
|
||||
showEnvironmentTextArea = true
|
||||
showLoginScripts = true
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
addOption(generalOption)
|
||||
@@ -58,7 +67,9 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
||||
serialComm = serialComm,
|
||||
extras = mutableMapOf(
|
||||
"backspace" to (terminalOption.backspaceComboBox.selectedItem as Backspace).name,
|
||||
"character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false")
|
||||
"character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false"),
|
||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||
?: AltKeyModifier.EightBit.name),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -226,108 +237,6 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
|
||||
}
|
||||
|
||||
|
||||
private inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val backspaceComboBox = JComboBox<Backspace>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val characterAtATimeTextField = YesOrNoComboBox()
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
val loginScripts = mutableListOf<LoginScript>()
|
||||
|
||||
private val loginScriptPanel = LoginScriptPanel(loginScripts)
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
backspaceComboBox.addItem(Backspace.Delete)
|
||||
backspaceComboBox.addItem(Backspace.Backspace)
|
||||
backspaceComboBox.addItem(Backspace.VT220)
|
||||
|
||||
characterAtATimeTextField.selectedItem = false
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
val tabbed = FlatTabbedPane()
|
||||
tabbed.styleMap = mapOf(
|
||||
"focusColor" to DynamicColor("TabbedPane.background"),
|
||||
"hoverColor" to DynamicColor("TabbedPane.background"),
|
||||
)
|
||||
tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 4
|
||||
putClientProperty("ContentPanelBorder", BorderFactory.createEmptyBorder())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.general"), getCenterComponent())
|
||||
tabbed.addTab(I18n.getString("termora.new-host.terminal.login-scripts"), loginScriptPanel)
|
||||
add(tabbed, BorderLayout.CENTER)
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.border(BorderFactory.createEmptyBorder(6, 8, 6, 8))
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.backspace")}:").xy(1, rows)
|
||||
.add(backspaceComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.character-mode")}:").xy(1, rows)
|
||||
.add(characterAtATimeTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
enum class Backspace {
|
||||
/**
|
||||
* 0x08
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package app.termora.plugin.internal.wsl
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.plugin.internal.BasicTerminalOption
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
@@ -12,12 +14,17 @@ import java.awt.KeyboardFocusManager
|
||||
import java.awt.Window
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import java.nio.charset.Charset
|
||||
import javax.swing.*
|
||||
|
||||
internal open class WSLHostOptionsPane : OptionsPane() {
|
||||
protected val generalOption = GeneralOption()
|
||||
protected val terminalOption = TerminalOption()
|
||||
protected val terminalOption = BasicTerminalOption().apply {
|
||||
showCharsetComboBox = true
|
||||
showStartupCommandTextField = true
|
||||
showEnvironmentTextArea = true
|
||||
init()
|
||||
}
|
||||
|
||||
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
|
||||
init {
|
||||
@@ -36,7 +43,11 @@ internal open class WSLHostOptionsPane : OptionsPane() {
|
||||
encoding = terminalOption.charsetComboBox.selectedItem as String,
|
||||
env = terminalOption.environmentTextArea.text,
|
||||
startupCommand = terminalOption.startupCommandTextField.text,
|
||||
extras = mutableMapOf("wsl-guid" to wsl.guid, "wsl-flavor" to wsl.flavor)
|
||||
extras = mutableMapOf(
|
||||
"wsl-guid" to wsl.guid, "wsl-flavor" to wsl.flavor,
|
||||
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
|
||||
?: AltKeyModifier.EightBit.name),
|
||||
)
|
||||
)
|
||||
|
||||
return Host(
|
||||
@@ -216,85 +227,5 @@ internal open class WSLHostOptionsPane : OptionsPane() {
|
||||
|
||||
}
|
||||
|
||||
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
|
||||
val charsetComboBox = JComboBox<String>()
|
||||
val startupCommandTextField = OutlineTextField()
|
||||
val environmentTextArea = FixedLengthTextArea(2048)
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
|
||||
startupCommandTextField.placeholderText = "--cd ~"
|
||||
|
||||
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
environmentTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
environmentTextArea.rows = 8
|
||||
environmentTextArea.lineWrap = true
|
||||
environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
for (e in Charset.availableCharsets()) {
|
||||
charsetComboBox.addItem(e.key)
|
||||
}
|
||||
|
||||
charsetComboBox.selectedItem = "UTF-8"
|
||||
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.terminal")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
|
||||
.add(charsetComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
|
||||
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows)
|
||||
.add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.terminal
|
||||
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@@ -192,6 +193,11 @@ class DataKey<T : Any>(val clazz: KClass<T>) {
|
||||
* TerminalWriter
|
||||
*/
|
||||
val TerminalWriter = DataKey(app.termora.terminal.panel.TerminalWriter::class)
|
||||
|
||||
/**
|
||||
* [app.termora.plugin.internal.AltKeyModifier]
|
||||
*/
|
||||
val AltModifier = DataKey(AltKeyModifier::class)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.swing.Swing
|
||||
import java.awt.*
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.UIManager
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@@ -263,9 +264,8 @@ class TerminalDisplay(
|
||||
var j = 1
|
||||
while (j <= cols) {
|
||||
val position = Position(row + 1, j)
|
||||
val caret = showCursor && j == cursorPosition.x + inputMethodData.offset
|
||||
&& i == cursorPosition.y + (maxVerticalScrollOffset - verticalScrollOffset)
|
||||
|
||||
val isCursorLine = i == cursorPosition.y + (maxVerticalScrollOffset - verticalScrollOffset)
|
||||
val caret = showCursor && j == cursorPosition.x + inputMethodData.offset && isCursorLine
|
||||
val (text, style, length) = if (characters.hasNext()) characters.next() else triple
|
||||
var textStyle = style
|
||||
val hasSelection = selectionModel.hasSelection(y = i + verticalScrollOffset, x = j)
|
||||
@@ -307,6 +307,16 @@ class TerminalDisplay(
|
||||
length * averageCharWidth
|
||||
)
|
||||
|
||||
// Focus Mode
|
||||
if (terminalModel.getData(TerminalPanel.FocusMode, false)) {
|
||||
if (terminalModel.isAlternateScreenBuffer().not()) {
|
||||
if (isCursorLine.not()) {
|
||||
background = colorPalette.getColor(TerminalColor.Basic.BACKGROUND)
|
||||
foreground = UIManager.getColor("textInactiveText").rgb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有颜色反转并且与渲染的背景色一致,那么无需渲染背景
|
||||
if (textStyle.inverse || background != colorPalette.getColor(TerminalColor.Basic.BACKGROUND)) {
|
||||
g.color = Color(background)
|
||||
|
||||
@@ -44,6 +44,7 @@ class TerminalPanel(val tab: TerminalTab?, val terminal: Terminal, private val w
|
||||
val Finding = DataKey(Boolean::class)
|
||||
val Focused = DataKey(Boolean::class)
|
||||
val SelectCopy = DataKey(Boolean::class)
|
||||
val FocusMode = DataKey(Boolean::class)
|
||||
}
|
||||
|
||||
private val properties get() = DatabaseManager.getInstance().properties
|
||||
|
||||
@@ -2,7 +2,9 @@ package app.termora.terminal.panel
|
||||
|
||||
import app.termora.keymap.KeyShortcut
|
||||
import app.termora.keymap.KeymapManager
|
||||
import app.termora.plugin.internal.AltKeyModifier
|
||||
import app.termora.terminal.ControlCharacters
|
||||
import app.termora.terminal.DataKey
|
||||
import app.termora.terminal.Terminal
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -89,8 +91,10 @@ class TerminalPanelKeyAdapter(
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/TermoraDev/termora/issues/865
|
||||
val modifier = terminal.getTerminalModel().getData(DataKey.AltModifier, AltKeyModifier.EightBit)
|
||||
// https://github.com/TermoraDev/termora/issues/331
|
||||
if (isAltPressedOnly(e) && Character.isDefined(e.keyChar)) {
|
||||
if (isAltPressedOnly(e) && Character.isDefined(e.keyChar) && modifier == AltKeyModifier.CharactersPrecededByESC) {
|
||||
val c = String(charArrayOf(ASCII_ESC, simpleMapKeyCodeToChar(e)))
|
||||
writer.write(TerminalWriter.WriteRequest.fromBytes(c.toByteArray(writer.getCharset())))
|
||||
// scroll to bottom
|
||||
|
||||
@@ -467,6 +467,15 @@ internal class TransportPanel(
|
||||
}
|
||||
})
|
||||
|
||||
table.actionMap.put("copy", 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) }
|
||||
if (files.any { it.second.isParent }) return
|
||||
toolkit.systemClipboard.setContents(TransferTransferable(panel, files), null)
|
||||
}
|
||||
})
|
||||
|
||||
table.actionMap.put("Reload", object : AbstractAction() {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
reload()
|
||||
@@ -514,7 +523,6 @@ internal class TransportPanel(
|
||||
data class TransferData(
|
||||
// true 就是本地拖拽上传
|
||||
val locally: Boolean,
|
||||
val row: Int,
|
||||
val insertRow: Boolean,
|
||||
val workdir: Path,
|
||||
val files: List<Pair<Path, Attributes>>
|
||||
@@ -540,18 +548,22 @@ internal class TransportPanel(
|
||||
|
||||
private fun getTransferData(support: TransferSupport, load: Boolean): TransferData? {
|
||||
val workdir = workdir ?: return null
|
||||
val dropLocation = support.dropLocation as? JTable.DropLocation ?: return null
|
||||
val row = if (dropLocation.isInsertRow) 0 else sorter.convertRowIndexToModel(dropLocation.row)
|
||||
if (dropLocation.isInsertRow.not() && dropLocation.column != TransportTableModel.COLUMN_NAME) return null
|
||||
if (dropLocation.isInsertRow.not() && model.getAttributes(row).isDirectory.not()) return null
|
||||
if (hasParent && dropLocation.row == 0) return null
|
||||
val paths = mutableListOf<Pair<Path, Attributes>>()
|
||||
var locally = false
|
||||
|
||||
if (support.isDataFlavorSupported(TransferTransferable.FLAVOR)) {
|
||||
val transferTransferable = support.transferable.getTransferData(TransferTransferable.FLAVOR)
|
||||
as? TransferTransferable ?: return null
|
||||
if (support.isDrop) {
|
||||
if (transferTransferable.component == panel) return null
|
||||
} else {
|
||||
// 如果在一个目录,那么是不允许粘贴的
|
||||
for (pair in transferTransferable.files) {
|
||||
if (pair.first.parent?.pathString == workdir.pathString) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
paths.addAll(transferTransferable.files)
|
||||
} else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
if (loader.isLoaded() && loader.getSyncTransportSupport().getFileSystem().isLocallyFileSystem())
|
||||
@@ -569,15 +581,29 @@ internal class TransportPanel(
|
||||
return null
|
||||
}
|
||||
|
||||
if (support.isDrop) {
|
||||
val dropLocation = support.dropLocation as? JTable.DropLocation ?: return null
|
||||
val row = if (dropLocation.isInsertRow) 0 else sorter.convertRowIndexToModel(dropLocation.row)
|
||||
if (dropLocation.isInsertRow.not() && dropLocation.column != TransportTableModel.COLUMN_NAME) return null
|
||||
if (dropLocation.isInsertRow.not() && model.getAttributes(row).isDirectory.not()) return null
|
||||
if (hasParent && dropLocation.row == 0) return null
|
||||
return TransferData(
|
||||
locally = locally,
|
||||
row = row,
|
||||
insertRow = dropLocation.isInsertRow,
|
||||
workdir = if (dropLocation.isInsertRow) workdir else model.getPath(row),
|
||||
files = paths
|
||||
)
|
||||
}
|
||||
|
||||
return TransferData(
|
||||
locally = locally,
|
||||
insertRow = false,
|
||||
workdir = workdir,
|
||||
files = paths
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override fun getSourceActions(c: JComponent?): Int {
|
||||
return COPY
|
||||
}
|
||||
@@ -899,7 +925,7 @@ internal class TransportPanel(
|
||||
}
|
||||
}
|
||||
|
||||
private class TransferTransferable(val component: TransportPanel, val files: List<Pair<Path, Attributes>>) :
|
||||
class TransferTransferable(val component: TransportPanel, val files: List<Pair<Path, Attributes>>) :
|
||||
Transferable {
|
||||
companion object {
|
||||
val FLAVOR = DataFlavor("termora/transfers", "Termora transfers")
|
||||
@@ -1041,7 +1067,6 @@ internal class TransportPanel(
|
||||
}
|
||||
|
||||
private inner class PopupMenuActionListener(private val files: List<Pair<Path, Attributes>>) : ActionListener {
|
||||
@Suppress("CascadeIf")
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
val actionCommand = TransportPopupMenu.ActionCommand.valueOf(e.actionCommand)
|
||||
if (actionCommand == TransportPopupMenu.ActionCommand.Transfer) {
|
||||
@@ -1089,6 +1114,12 @@ internal class TransportPanel(
|
||||
Files.setPosixFilePermissions(path, c.permissions)
|
||||
}
|
||||
}
|
||||
} else if (actionCommand == TransportPopupMenu.ActionCommand.Copy) {
|
||||
val transferable = TransferTransferable(panel, files)
|
||||
toolkit.systemClipboard.setContents(transferable, null)
|
||||
} else if (actionCommand == TransportPopupMenu.ActionCommand.Paste) {
|
||||
val transferable = toolkit.systemClipboard.getContents(null) ?: return
|
||||
table.transferHandler.importData(TransferHandler.TransferSupport(table, transferable))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import javax.swing.JMenu
|
||||
import javax.swing.JMenuItem
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.event.EventListenerList
|
||||
import javax.swing.event.PopupMenuEvent
|
||||
import javax.swing.event.PopupMenuListener
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.name
|
||||
|
||||
@@ -39,6 +41,8 @@ internal class TransportPopupMenu(
|
||||
private val transferMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.transfer"))
|
||||
private val editMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.edit"))
|
||||
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 renameMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.rename"))
|
||||
private val deleteMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.delete"))
|
||||
@@ -82,6 +86,9 @@ internal class TransportPopupMenu(
|
||||
add(transferMenu)
|
||||
add(editMenu)
|
||||
addSeparator()
|
||||
add(copyMenu)
|
||||
add(pasteMenu)
|
||||
addSeparator()
|
||||
add(copyPathMenu)
|
||||
if (fileSystem?.isLocallyFileSystem() == true) {
|
||||
add(openInFinderMenu)
|
||||
@@ -133,6 +140,7 @@ internal class TransportPopupMenu(
|
||||
renameMenu.isEnabled = hasParent.not() && files.size == 1
|
||||
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||
changePermissionsMenu.isVisible = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
|
||||
copyMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||
|
||||
for ((item, mnemonic) in mnemonics) {
|
||||
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
|
||||
@@ -166,6 +174,22 @@ internal class TransportPopupMenu(
|
||||
sb.deleteCharAt(sb.length - 1)
|
||||
toolkit.systemClipboard.setContents(StringSelection(sb.toString()), null)
|
||||
}
|
||||
copyMenu.addActionListener { fireActionPerformed(it, ActionCommand.Copy) }
|
||||
pasteMenu.addActionListener { fireActionPerformed(it, ActionCommand.Paste) }
|
||||
|
||||
addPopupMenuListener(object : PopupMenuListener {
|
||||
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
|
||||
pasteMenu.isEnabled = toolkit.systemClipboard
|
||||
.isDataFlavorAvailable(TransportPanel.TransferTransferable.FLAVOR)
|
||||
}
|
||||
|
||||
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
|
||||
}
|
||||
|
||||
override fun popupMenuCanceled(e: PopupMenuEvent?) {
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
fun fireActionPerformed(evt: ActionEvent, command: ActionCommand) {
|
||||
@@ -241,6 +265,8 @@ internal class TransportPopupMenu(
|
||||
ChangePermissions,
|
||||
Rmrf,
|
||||
Reconnect,
|
||||
Paste,
|
||||
Copy,
|
||||
}
|
||||
|
||||
data class ChangePermission(val permissions: Set<PosixFilePermission>, val includeSubFolder: Boolean)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
termora.title=Termora
|
||||
termora.confirm=OK
|
||||
termora.exit=退出
|
||||
termora.cancel=Cancel
|
||||
termora.copy=Copy
|
||||
termora.paste=Paste
|
||||
termora.apply=Apply
|
||||
termora.save=Save
|
||||
termora.remove=Delete
|
||||
@@ -185,6 +187,9 @@ termora.new-host.terminal.backspace=Backspace
|
||||
termora.new-host.terminal.character-mode=Character-at-a-time
|
||||
termora.new-host.terminal.heartbeat-interval=Heartbeat Interval
|
||||
termora.new-host.terminal.startup-commands=Startup Command
|
||||
termora.new-host.terminal.alt-modifier=Alt modifier
|
||||
termora.new-host.terminal.alt-modifier.eight-bit=8-bit characters
|
||||
termora.new-host.terminal.alt-modifier.by-esc=Characters preceded by ESC
|
||||
termora.new-host.terminal.env=Environment
|
||||
termora.new-host.terminal.login-scripts=Login Scripts
|
||||
termora.new-host.terminal.expect=Expect
|
||||
@@ -391,6 +396,7 @@ termora.toolbar.customize-toolbar=Customize Toolbar...
|
||||
|
||||
# Actions
|
||||
termora.actions.copy-from-terminal=Copy from Terminal
|
||||
termora.actions.focus-mode=Focus Mode
|
||||
termora.actions.paste-to-terminal=Paste to Terminal
|
||||
termora.actions.select-all-in-terminal=Select All in Terminal
|
||||
termora.actions.open-terminal-find=Open Terminal Find
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
termora.title=Termora
|
||||
termora.confirm=Ок
|
||||
termora.exit=покидать
|
||||
termora.cancel=Отмена
|
||||
termora.copy=Копировать
|
||||
termora.paste=Вставить
|
||||
termora.apply=Применить
|
||||
termora.save=Сохранить
|
||||
termora.remove=Удалить
|
||||
@@ -341,6 +343,7 @@ termora.toolbar.customize-toolbar=Настроить Панель Инструм
|
||||
|
||||
# Actions
|
||||
termora.actions.copy-from-terminal=Копировать из Терминала
|
||||
termora.actions.focus-mode=Режим фокусировки
|
||||
termora.actions.paste-to-terminal=Вставить в Терминала
|
||||
termora.actions.select-all-in-terminal=Выделить Все в Терминале
|
||||
termora.actions.open-terminal-find=Открыть Поиск Терминала
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
termora.confirm=确认
|
||||
termora.exit=退出
|
||||
termora.cancel=取消
|
||||
termora.copy=复制
|
||||
termora.paste=粘贴
|
||||
termora.apply=应用
|
||||
termora.save=保存
|
||||
termora.remove=删除
|
||||
@@ -177,6 +179,9 @@ termora.new-host.terminal.backspace=退格键
|
||||
termora.new-host.terminal.character-mode=单字符模式
|
||||
termora.new-host.terminal.heartbeat-interval=心跳间隔
|
||||
termora.new-host.terminal.startup-commands=启动命令
|
||||
termora.new-host.terminal.alt-modifier=Alt 键修饰
|
||||
termora.new-host.terminal.alt-modifier.eight-bit=8 位字符
|
||||
termora.new-host.terminal.alt-modifier.by-esc=ESC 键作为前缀
|
||||
termora.new-host.terminal.env=环境
|
||||
termora.new-host.terminal.login-scripts=登录脚本
|
||||
termora.new-host.terminal.expect=预期
|
||||
@@ -395,6 +400,7 @@ termora.protocol.not-supported=不支持 {0} 协议,你可能需要安装插
|
||||
|
||||
# Actions
|
||||
termora.actions.copy-from-terminal=从终端复制
|
||||
termora.actions.focus-mode=专注模式
|
||||
termora.actions.paste-to-terminal=粘贴到终端
|
||||
termora.actions.select-all-in-terminal=在终端中全选
|
||||
termora.actions.open-terminal-find=打开终端查找
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
termora.confirm=確定
|
||||
termora.exit=Exit
|
||||
termora.cancel=取消
|
||||
termora.copy=複製
|
||||
termora.paste=貼上
|
||||
termora.apply=应用
|
||||
termora.save=儲存
|
||||
termora.remove=刪除
|
||||
@@ -174,6 +177,9 @@ termora.new-host.terminal.encoding=編碼
|
||||
termora.new-host.terminal.backspace=退格鍵
|
||||
termora.new-host.terminal.character-mode=單字元模式
|
||||
termora.new-host.terminal.startup-commands=啟動命令
|
||||
termora.new-host.terminal.alt-modifier=Alt 鍵修飾
|
||||
termora.new-host.terminal.alt-modifier.eight-bit=8 位元字符
|
||||
termora.new-host.terminal.alt-modifier.by-esc=ESC 鍵作為前綴
|
||||
termora.new-host.terminal.heartbeat-interval=心跳間隔
|
||||
termora.new-host.terminal.env=環境
|
||||
termora.new-host.terminal.login-scripts=登入腳本
|
||||
@@ -382,6 +388,7 @@ termora.protocol.not-supported=不支援 {0} 協議,你可能需要安裝插
|
||||
|
||||
# Actions
|
||||
termora.actions.copy-from-terminal=從終端複製
|
||||
termora.actions.focus-mode=專注模式
|
||||
termora.actions.paste-to-terminal=貼上到終端
|
||||
termora.actions.select-all-in-terminal=在終端中全選
|
||||
termora.actions.open-terminal-find=開啟終端搜尋
|
||||
|
||||
Reference in New Issue
Block a user