mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
Compare commits
20 Commits
2.0.0-beta
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aee34415a7 | ||
|
|
e4e70cc72c | ||
|
|
49779fe8f2 | ||
|
|
969ddc3662 | ||
|
|
de9b418c75 | ||
|
|
f8588745cd | ||
|
|
7c0cbab187 | ||
|
|
176fa64de0 | ||
|
|
495ab69195 | ||
|
|
93c28242fb | ||
|
|
57662f717b | ||
|
|
3669bd1f88 | ||
|
|
00e695b7d5 | ||
|
|
02c92e6019 | ||
|
|
8ba74f0846 | ||
|
|
79ed6d3858 | ||
|
|
8a66606275 | ||
|
|
3ebdf73fbf | ||
|
|
d249e5da5a | ||
|
|
7243e933e6 |
11
.github/workflows/osx.yml
vendored
11
.github/workflows/osx.yml
vendored
@@ -81,6 +81,10 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-gradle-
|
||||
|
||||
- name: Install create-dmg
|
||||
shell: bash
|
||||
run: brew install create-dmg
|
||||
|
||||
- name: Compile
|
||||
shell: bash
|
||||
run: ./gradlew :check-license && ./gradlew classes -x test
|
||||
@@ -93,13 +97,6 @@ jobs:
|
||||
shell: bash
|
||||
run: ./gradlew :jpackage && ./gradlew :dist
|
||||
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: termora-osx-zip-${{ runner.arch }}
|
||||
path: |
|
||||
build/distributions/*.zip
|
||||
|
||||
- name: Upload dmg artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -82,6 +82,7 @@ Termora is developed using [**Kotlin/JVM**](https://kotlinlang.org/) and partial
|
||||
- 🧾 [Latest Release](https://github.com/TermoraDev/termora/releases/latest)
|
||||
- 🍺 **Homebrew**: `brew install --cask termora`
|
||||
- 🔨 **WinGet**: `winget install termora`
|
||||
- <img src="https://apps.microsoft.com/assets/icons/logo-16x16.png" alt="microsoft logo"/> <b>Microsoft Store</b>: <a href="https://apps.microsoft.com/store/detail/9NRZBHG43SB9?cid=DevShareMCLPCS">Visit Termora in the Microsoft Store</a>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ Termora 使用 [**Kotlin/JVM**](https://kotlinlang.org/) 开发,支持(正
|
||||
- 🧾 [Latest release](https://github.com/TermoraDev/termora/releases/latest)
|
||||
- 🍺 **Homebrew**:`brew install --cask termora`
|
||||
- 🪟 **WinGet**:`winget install termora`
|
||||
- <img src="https://apps.microsoft.com/assets/icons/logo-16x16.png" alt="microsoft logo"/> <b>Microsoft Store</b>: <a href="https://apps.microsoft.com/store/detail/9NRZBHG43SB9?cid=DevShareMCLPCS">Termora</a>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ val appVersion = project.version.toString().split("-")[0]
|
||||
val makeAppx = if (os.isWindows) StringUtils.defaultString(System.getenv("MAKEAPPX_PATH")) else StringUtils.EMPTY
|
||||
val isDeb = os.isLinux && System.getenv("TERMORA_TYPE") == "deb"
|
||||
val isAppx = os.isWindows && makeAppx.isNotBlank() && System.getenv("TERMORA_TYPE") == "appx"
|
||||
val isBeta = project.version.toString().contains("beta", ignoreCase = true)
|
||||
|
||||
// macOS 签名信息
|
||||
val macOSSignUsername = System.getenv("TERMORA_MAC_SIGN_USER_NAME") ?: StringUtils.EMPTY
|
||||
@@ -173,10 +174,12 @@ publishing {
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
val betaVersion = project.version.toString().substringAfterLast('.')
|
||||
filesMatching("**/AppxManifest.xml") {
|
||||
filter<ReplaceTokens>(
|
||||
"tokens" to mapOf(
|
||||
"version" to appVersion,
|
||||
"betaVersion" to if (isBeta) betaVersion else "0",
|
||||
"architecture" to if (arch.isArm64) "arm64" else "x64",
|
||||
"projectDir" to project.projectDir.absolutePath,
|
||||
)
|
||||
@@ -442,7 +445,7 @@ tasks.register<Exec>("jpackage") {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
if (os.isMacOsX && macOSSign) {
|
||||
if (macOSSign) {
|
||||
arguments.add("--mac-sign")
|
||||
arguments.add("--mac-signing-key-user-name")
|
||||
arguments.add(macOSSignUsername)
|
||||
|
||||
@@ -9,7 +9,7 @@ kotlinx-serialization-json = "1.9.0"
|
||||
commons-codec = "1.19.0"
|
||||
commons-lang3 = "3.18.0"
|
||||
commons-csv = "1.14.1"
|
||||
commons-net = "3.11.1"
|
||||
commons-net = "3.12.0"
|
||||
commons-text = "1.14.0"
|
||||
commons-compress = "1.28.0"
|
||||
commons-vfs2 = "2.10.0"
|
||||
@@ -28,7 +28,7 @@ okhttp = "5.1.0"
|
||||
sshj = "0.39.0"
|
||||
sshd-core = "2.15.0"
|
||||
jgit = "7.2.0.202503040940-r"
|
||||
commonmark = "0.25.0"
|
||||
commonmark = "0.25.1"
|
||||
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-4"
|
||||
exposed = "1.0.0-beta-5"
|
||||
h2 = "2.3.232"
|
||||
sqlite = "3.50.3.0"
|
||||
jug = "5.1.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
|
||||
|
||||
project.version = "0.0.6"
|
||||
project.version = "0.0.7"
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.DialogWrapper
|
||||
import app.termora.Disposable
|
||||
import app.termora.Disposer
|
||||
import app.termora.OptionPane
|
||||
import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.UIManager
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.name
|
||||
|
||||
|
||||
class EditorDialog(file: Path, owner: Window, private val myDisposable: Disposable) : DialogWrapper(null) {
|
||||
|
||||
private val filename = file.name
|
||||
private val filepath = File(file.absolutePathString())
|
||||
private val editorPanel = EditorPanel(this, filepath)
|
||||
private val disposed = AtomicBoolean()
|
||||
|
||||
init {
|
||||
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
|
||||
isModal = false
|
||||
controlsVisible = true
|
||||
isResizable = true
|
||||
title = filename
|
||||
iconImages = owner.iconImages
|
||||
escapeDispose = false
|
||||
defaultCloseOperation = DO_NOTHING_ON_CLOSE
|
||||
|
||||
initEvents()
|
||||
|
||||
setLocationRelativeTo(owner)
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
addWindowListener(object : WindowAdapter() {
|
||||
override fun windowClosing(e: WindowEvent?) {
|
||||
if (disposed.compareAndSet(false, true)) {
|
||||
doCancelAction()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Disposer.register(myDisposable, object : Disposable {
|
||||
override fun dispose() {
|
||||
if (disposed.compareAndSet(false, true)) {
|
||||
doCancelAction()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Disposer.register(disposable, object : Disposable {
|
||||
override fun dispose() {
|
||||
if (disposed.compareAndSet(false, true)) {
|
||||
Disposer.dispose(myDisposable)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun doCancelAction() {
|
||||
if (editorPanel.changes()) {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
this,
|
||||
"文件尚未保存,你确定要退出吗?",
|
||||
optionType = JOptionPane.OK_CANCEL_OPTION,
|
||||
) != JOptionPane.OK_OPTION
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
super.doCancelAction()
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
return editorPanel
|
||||
}
|
||||
|
||||
override fun createSouthPanel(): JComponent? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.Disposable
|
||||
import app.termora.Disposer
|
||||
import app.termora.EnableManager
|
||||
import app.termora.OptionPane
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.UIManager
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.name
|
||||
import kotlin.math.max
|
||||
|
||||
class EditorFrame(private val file: Path, private val owner: Window, private val disposable: Disposable) : JFrame() {
|
||||
private val enableManager get() = EnableManager.getInstance()
|
||||
private val disposed = AtomicBoolean()
|
||||
private val filepath = File(file.absolutePathString())
|
||||
private val frame get() = this
|
||||
private val editorPanel = EditorPanel(this, filepath)
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvent()
|
||||
}
|
||||
|
||||
private fun initEvent() {
|
||||
|
||||
Disposer.register(disposable, object : Disposable {
|
||||
override fun dispose() {
|
||||
if (disposed.compareAndSet(false, true)) frame.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
addWindowListener(object : WindowAdapter() {
|
||||
override fun windowClosed(e: WindowEvent) {
|
||||
if (disposed.compareAndSet(false, true)) Disposer.dispose(disposable)
|
||||
enableManager.setFlag("Plugins.editor.dialog.width", width)
|
||||
enableManager.setFlag("Plugins.editor.dialog.height", height)
|
||||
enableManager.setFlag("Plugins.editor.dialog.extendedState", extendedState)
|
||||
}
|
||||
|
||||
override fun windowClosing(e: WindowEvent?) {
|
||||
if (editorPanel.changes()) {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
frame,
|
||||
EditorI18n.getString("termora.plugins.editor.not-save"),
|
||||
optionType = JOptionPane.OK_CANCEL_OPTION,
|
||||
) == JOptionPane.OK_OPTION
|
||||
) {
|
||||
frame.dispose()
|
||||
}
|
||||
} else {
|
||||
frame.dispose()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
|
||||
val state = enableManager.getFlag("Plugins.editor.dialog.extendedState", 0)
|
||||
|
||||
if ((state and MAXIMIZED_BOTH) == MAXIMIZED_BOTH) {
|
||||
frame.setLocationRelativeTo(null)
|
||||
frame.extendedState = state
|
||||
} else {
|
||||
val mySize = size
|
||||
mySize.width = max(enableManager.getFlag("Plugins.editor.dialog.width", mySize.width), mySize.width)
|
||||
mySize.height = max(enableManager.getFlag("Plugins.editor.dialog.height", mySize.height), mySize.height)
|
||||
size = mySize
|
||||
setLocationRelativeTo(owner)
|
||||
}
|
||||
|
||||
title = file.name
|
||||
iconImages = owner.iconImages
|
||||
defaultCloseOperation = DO_NOTHING_ON_CLOSE
|
||||
|
||||
rootPane.contentPane.layout = BorderLayout()
|
||||
rootPane.contentPane.add(editorPanel, BorderLayout.CENTER)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.NamedI18n
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object EditorI18n : NamedI18n("i18n/messages") {
|
||||
private val log = LoggerFactory.getLogger(EditorI18n::class.java)
|
||||
|
||||
override fun getLogger(): Logger {
|
||||
return log
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import javax.swing.event.DocumentEvent
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class EditorPanel(private val window: JDialog, private val file: File) : JPanel(BorderLayout()) {
|
||||
class EditorPanel(private val window: JFrame, private val file: File) : JPanel(BorderLayout()) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EditorPanel::class.java)
|
||||
|
||||
@@ -14,7 +14,7 @@ class MyTransportEditFileExtension private constructor() : TransportEditFileExte
|
||||
|
||||
override fun edit(owner: Window, path: Path): Disposable {
|
||||
val disposable = Disposer.newDisposable()
|
||||
SwingUtilities.invokeLater { EditorDialog(path, owner, disposable).isVisible = true }
|
||||
SwingUtilities.invokeLater { EditorFrame(path, owner, disposable).isVisible = true }
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
termora.plugins.editor.not-save=The file has not been saved. Are you sure you want to exit?
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
termora.plugins.editor.not-save=Файл не сохранён. Вы уверены, что хотите выйти?
|
||||
@@ -0,0 +1 @@
|
||||
termora.plugins.editor.not-save=文件尚未保存,你确定要退出吗?
|
||||
@@ -0,0 +1 @@
|
||||
termora.plugins.editor.not-save=檔案尚未儲存,你確定要退出嗎?
|
||||
@@ -9,7 +9,7 @@ dependencies {
|
||||
compileOnly(project(":"))
|
||||
implementation("com.maxmind.geoip2:geoip2:4.3.1")
|
||||
// https://github.com/hstyi/geolite2
|
||||
implementation("com.github.hstyi:geolite2:v1.0-202507280101")
|
||||
implementation("com.github.hstyi:geolite2:v1.0-202508040102")
|
||||
}
|
||||
|
||||
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
|
||||
|
||||
project.version = "0.0.4"
|
||||
project.version = "0.0.5"
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package app.termora.plugins.serial
|
||||
|
||||
import app.termora.Host
|
||||
import app.termora.Icons
|
||||
import app.termora.PtyHostTerminalTab
|
||||
import app.termora.WindowScope
|
||||
import app.termora.*
|
||||
import app.termora.terminal.PtyConnector
|
||||
import org.apache.commons.io.Charsets
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -11,6 +8,8 @@ import javax.swing.Icon
|
||||
|
||||
class SerialTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
PtyHostTerminalTab(windowScope, host) {
|
||||
|
||||
|
||||
override suspend fun openPtyConnector(): PtyConnector {
|
||||
val serialPort = Serials.openPort(host)
|
||||
return SerialPortPtyConnector(
|
||||
@@ -19,6 +18,10 @@ class SerialTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
)
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return SerialTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
return Icons.plugin
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ enum class AppLayout {
|
||||
* macOS
|
||||
*/
|
||||
App,
|
||||
AppStore,
|
||||
|
||||
/**
|
||||
* Linux
|
||||
|
||||
@@ -3,14 +3,16 @@ package app.termora
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.DataProvider
|
||||
import app.termora.actions.DataProviders
|
||||
import app.termora.terminal.*
|
||||
import app.termora.terminal.ControlCharacters
|
||||
import app.termora.terminal.DataKey
|
||||
import app.termora.terminal.DataListener
|
||||
import app.termora.terminal.Terminal
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.swing.Swing
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.beans.PropertyChangeEvent
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
|
||||
@@ -27,13 +29,8 @@ abstract class HostTerminalTab(
|
||||
protected val terminalTabbedManager
|
||||
get() = AnActionEvent(getJComponent(), StringUtils.EMPTY, EventObject(getJComponent()))
|
||||
.getData(DataProviders.TerminalTabbedManager)
|
||||
protected val coroutineScope by lazy { CoroutineScope(SupervisorJob() + Dispatchers.Swing) }
|
||||
protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Swing)
|
||||
protected val terminalModel get() = terminal.getTerminalModel()
|
||||
protected var unread = false
|
||||
set(value) {
|
||||
field = value
|
||||
firePropertyChange(PropertyChangeEvent(this, "icon", null, null))
|
||||
}
|
||||
|
||||
|
||||
/* visualTerminal */
|
||||
@@ -45,15 +42,6 @@ abstract class HostTerminalTab(
|
||||
terminal.getTerminalModel().setData(Host, host)
|
||||
terminal.getTerminalModel().addDataListener(object : DataListener {
|
||||
override fun onChanged(key: DataKey<*>, data: Any) {
|
||||
if (key == VisualTerminal.Written) {
|
||||
if (hasFocus || unread) {
|
||||
return
|
||||
}
|
||||
// 如果当前选中的不是这个 Tab,那么设置成未读
|
||||
if (terminalTabbedManager?.getSelectedTerminalTab() != this@HostTerminalTab) {
|
||||
unread = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -75,8 +63,6 @@ abstract class HostTerminalTab(
|
||||
|
||||
override fun onGrabFocus() {
|
||||
super.onGrabFocus()
|
||||
if (!unread) return
|
||||
unread = false
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
@@ -533,7 +533,7 @@ class AuraLaf : FlatPropertiesLaf("Aura", Properties().apply {
|
||||
TerminalColor.Bright.WHITE -> 0xffffff
|
||||
|
||||
TerminalColor.Basic.SELECTION_BACKGROUND,
|
||||
TerminalColor.Cursor.BACKGROUND -> 0xedecee
|
||||
TerminalColor.Cursor.BACKGROUND -> 0xacacac
|
||||
|
||||
else -> Int.MAX_VALUE
|
||||
}
|
||||
|
||||
@@ -2,19 +2,21 @@ package app.termora
|
||||
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.DataProviders
|
||||
import app.termora.actions.SwitchTabAction
|
||||
import app.termora.database.DatabaseManager
|
||||
import app.termora.keymap.KeyShortcut
|
||||
import app.termora.keymap.KeymapManager
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
import com.formdev.flatlaf.ui.FlatTabbedPaneUI
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.awt.*
|
||||
import java.awt.event.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.*
|
||||
import javax.swing.ImageIcon
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.*
|
||||
import kotlin.math.abs
|
||||
|
||||
class MyTabbedPane : FlatTabbedPane() {
|
||||
internal class MyTabbedPane : FlatTabbedPane(), Disposable {
|
||||
|
||||
private val dragMouseAdaptor = DragMouseAdaptor()
|
||||
private val terminalTabbedManager
|
||||
@@ -23,6 +25,30 @@ class MyTabbedPane : FlatTabbedPane() {
|
||||
private val owner
|
||||
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||
.getData(DataProviders.TermoraFrame) as TermoraFrame
|
||||
private val keymap get() = KeymapManager.getInstance().getActiveKeymap()
|
||||
private val tabOrder get() = DatabaseManager.getInstance().appearance.tabOrder
|
||||
private val isScreen get() = TermoraLayout.Layout == TermoraLayout.Screen
|
||||
private var isSwitchTabMode = false
|
||||
set(value) {
|
||||
if (tabOrder == TabOrder.Always.name) {
|
||||
if (field.not()) {
|
||||
field = true
|
||||
repaint()
|
||||
}
|
||||
return
|
||||
} else if (tabOrder == TabOrder.Hide.name) {
|
||||
if (field) {
|
||||
field = false
|
||||
repaint()
|
||||
}
|
||||
return
|
||||
} else if (tabOrder == TabOrder.AsNeed.name) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isFocusable = false
|
||||
@@ -38,6 +64,16 @@ class MyTabbedPane : FlatTabbedPane() {
|
||||
private fun initEvents() {
|
||||
addMouseListener(dragMouseAdaptor)
|
||||
addMouseMotionListener(dragMouseAdaptor)
|
||||
|
||||
val awtEventListener = MyAWTEventListener()
|
||||
toolkit.addAWTEventListener(awtEventListener, AWTEvent.KEY_EVENT_MASK or AWTEvent.WINDOW_EVENT_MASK)
|
||||
|
||||
Disposer.register(this, object : Disposable {
|
||||
override fun dispose() {
|
||||
toolkit.removeAWTEventListener(awtEventListener)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun processMouseEvent(e: MouseEvent) {
|
||||
@@ -70,6 +106,32 @@ class MyTabbedPane : FlatTabbedPane() {
|
||||
firePropertyChange("selectedIndex", oldIndex, index)
|
||||
}
|
||||
|
||||
override fun updateUI() {
|
||||
super.updateUI()
|
||||
setUI(MyMyTabbedPaneUI())
|
||||
}
|
||||
|
||||
private inner class MyAWTEventListener : AWTEventListener {
|
||||
override fun eventDispatched(event: AWTEvent) {
|
||||
if (event is KeyEvent) {
|
||||
if (isSwitchTabMode) isSwitchTabMode = false
|
||||
val shortcuts = keymap.getShortcut(SwitchTabAction.SWITCH_TAB)
|
||||
if (shortcuts.isEmpty()) return
|
||||
val shortcut = shortcuts.first() as KeyShortcut
|
||||
val modifiers = KeyStroke.getKeyStroke(event.keyCode, event.modifiersEx).modifiers
|
||||
if (shortcut.keyStroke.modifiers != modifiers) return
|
||||
if (SwingUtilities.getWindowAncestor(event.component) != owner) return
|
||||
if (isSwitchTabMode.not()) isSwitchTabMode = true
|
||||
} else if (event is WindowEvent) {
|
||||
if (event.id == WindowEvent.WINDOW_LOST_FOCUS || event.id == WindowEvent.WINDOW_DEACTIVATED) {
|
||||
if (isSwitchTabMode) isSwitchTabMode = false
|
||||
} else if (event.id == WindowEvent.WINDOW_GAINED_FOCUS || event.id == WindowEvent.WINDOW_ACTIVATED) {
|
||||
// 触发一次刷新
|
||||
isSwitchTabMode = isSwitchTabMode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DragMouseAdaptor : MouseAdapter(), KeyEventDispatcher {
|
||||
private var mousePressedPoint = Point()
|
||||
@@ -267,5 +329,81 @@ class MyTabbedPane : FlatTabbedPane() {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyMyTabbedPaneUI : FlatTabbedPaneUI() {
|
||||
override fun paintIcon(
|
||||
g: Graphics,
|
||||
tabPlacement: Int,
|
||||
tabIndex: Int,
|
||||
icon: Icon,
|
||||
iconRect: Rectangle?,
|
||||
isSelected: Boolean
|
||||
) {
|
||||
super.paintIcon(g, tabPlacement, tabIndex, MyIcon(icon, tabIndex, isSelected), iconRect, isSelected)
|
||||
}
|
||||
|
||||
|
||||
override fun createMoreTabsButton(): JButton {
|
||||
return MyMoreTabsButton()
|
||||
}
|
||||
|
||||
private inner class MyMoreTabsButton : FlatMoreTabsButton() {
|
||||
override fun createTabMenuItem(tabIndex: Int): JMenuItem? {
|
||||
val item = super.createTabMenuItem(tabIndex)
|
||||
if (tabIndex == 0 && isScreen) {
|
||||
item.text = Application.getName()
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun getIconAt(index: Int): Icon? {
|
||||
if (isSwitchTabMode) {
|
||||
return MyIcon(super.getIconAt(index), index, selectedIndex == index)
|
||||
}
|
||||
return super.getIconAt(index)
|
||||
}
|
||||
|
||||
private inner class MyIcon(private val icon: Icon, private val tabIndex: Int, private val isSelected: Boolean) :
|
||||
Icon {
|
||||
override fun paintIcon(c: Component, g: Graphics, x: Int, y: Int) {
|
||||
if (isScreen && tabIndex == 0) {
|
||||
icon.paintIcon(c, g, x, y)
|
||||
return
|
||||
}
|
||||
|
||||
if (isSwitchTabMode.not()) {
|
||||
icon.paintIcon(c, g, x, y)
|
||||
return
|
||||
}
|
||||
|
||||
if (g !is Graphics2D) return
|
||||
|
||||
g.save()
|
||||
setupAntialiasing(g)
|
||||
|
||||
val fm = g.getFontMetrics(g.font)
|
||||
val text = "${tabIndex + 1}"
|
||||
val textWidth = fm.stringWidth(text)
|
||||
val textHeight = fm.ascent
|
||||
|
||||
val centerX = x + (icon.iconWidth - textWidth) / 2
|
||||
val centerY = y + (icon.iconHeight + textHeight) / 2 - 1
|
||||
|
||||
g.color = c.getForeground()
|
||||
g.drawString(text, centerX, centerY)
|
||||
|
||||
g.restore()
|
||||
}
|
||||
|
||||
override fun getIconWidth(): Int {
|
||||
return icon.iconWidth
|
||||
}
|
||||
|
||||
override fun getIconHeight(): Int {
|
||||
return icon.iconHeight
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -173,9 +173,20 @@ abstract class PtyHostTerminalTab(
|
||||
}
|
||||
|
||||
override fun reconnect() {
|
||||
stop()
|
||||
start()
|
||||
val manager = terminalTabbedManager ?: return
|
||||
val index = manager.indexOfTerminalTab(this)
|
||||
if (index < 0) return
|
||||
|
||||
val tab = createReconnectTerminalTab()
|
||||
manager.addTerminalTab(index, tab, true)
|
||||
manager.closeTerminalTab(this, true)
|
||||
|
||||
if (tab is HostTerminalTab) {
|
||||
tab.start()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun createReconnectTerminalTab(): TerminalTab
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return terminalPanel
|
||||
|
||||
@@ -114,6 +114,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
val languageComboBox = FlatComboBox<String>()
|
||||
val backgroundComBoBox = YesOrNoComboBox()
|
||||
val confirmTabCloseComBoBox = YesOrNoComboBox()
|
||||
val tabOrderComboBox = FlatComboBox<TabOrder>()
|
||||
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
|
||||
val preferredThemeBtn = JButton(Icons.settings)
|
||||
val opacitySpinner = NumberSpinner(100, 0, 100)
|
||||
@@ -128,6 +129,12 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
|
||||
private fun initView() {
|
||||
|
||||
tabOrderComboBox.addItem(TabOrder.Hide)
|
||||
tabOrderComboBox.addItem(TabOrder.AsNeed)
|
||||
tabOrderComboBox.addItem(TabOrder.Always)
|
||||
tabOrderComboBox.selectedItem = runCatching { TabOrder.valueOf(appearance.tabOrder) }
|
||||
.getOrNull() ?: TabOrder.Hide
|
||||
|
||||
layoutComboBox.addItem(TermoraLayout.Screen)
|
||||
layoutComboBox.addItem(TermoraLayout.Fence)
|
||||
layoutComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
@@ -226,6 +233,14 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
})
|
||||
|
||||
tabOrderComboBox.addItemListener(object : ItemListener {
|
||||
override fun itemStateChanged(e: ItemEvent) {
|
||||
if (e.stateChange == ItemEvent.SELECTED) {
|
||||
appearance.tabOrder = tabOrderComboBox.selectedItem?.toString() ?: return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
opacitySpinner.addChangeListener {
|
||||
val opacity = opacitySpinner.value
|
||||
if (opacity is Double) {
|
||||
@@ -349,7 +364,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private fun getFormPanel(): JPanel {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, default, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
val box = FlatToolBar()
|
||||
box.add(followSystemCheckBox)
|
||||
@@ -380,6 +395,9 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows)
|
||||
.add(backgroundComBoBox).xy(3, rows).apply { rows += step }
|
||||
|
||||
builder.add("${I18n.getString("termora.settings.appearance.tab-order")}:").xy(1, rows)
|
||||
.add(tabOrderComboBox).xy(3, rows).apply { rows += step }
|
||||
|
||||
val confirmTabCloseBox = Box.createHorizontalBox()
|
||||
confirmTabCloseBox.add(JLabel("${I18n.getString("termora.settings.appearance.confirm-tab-close")}:"))
|
||||
confirmTabCloseBox.add(Box.createHorizontalStrut(8))
|
||||
@@ -404,6 +422,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private val fontSizeTextField = IntSpinner(0, 9, 99)
|
||||
private val terminalSetting get() = DatabaseManager.getInstance().terminal
|
||||
private val selectCopyComboBox = YesOrNoComboBox()
|
||||
private val rightClickComboBox = OutlineComboBox<String>()
|
||||
private val autoCloseTabComboBox = YesOrNoComboBox()
|
||||
private val floatingToolbarComboBox = YesOrNoComboBox()
|
||||
private val hyperlinkComboBox = YesOrNoComboBox()
|
||||
@@ -417,6 +436,12 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
}
|
||||
|
||||
rightClickComboBox.addItemListener {
|
||||
if (it.stateChange == ItemEvent.SELECTED) {
|
||||
terminalSetting.rightClick = rightClickComboBox.selectedItem as String
|
||||
}
|
||||
}
|
||||
|
||||
fallbackFontComboBox.addItemListener {
|
||||
if (it.stateChange == ItemEvent.SELECTED) {
|
||||
terminalSetting.fallbackFont = fallbackFontComboBox.selectedItem as String
|
||||
@@ -518,6 +543,10 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
fontSizeTextField.value = terminalSetting.fontSize
|
||||
maxRowsTextField.value = terminalSetting.maxRows
|
||||
|
||||
rightClickComboBox.addItem("Copy")
|
||||
rightClickComboBox.addItem("CopyAndPaste")
|
||||
|
||||
rightClickComboBox.selectedItem = terminalSetting.rightClick
|
||||
|
||||
cursorStyleComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
@@ -532,6 +561,24 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
}
|
||||
|
||||
rightClickComboBox.renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<*>?,
|
||||
value: Any?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): Component {
|
||||
var text = value?.toString()
|
||||
if (value == "Copy") {
|
||||
text = I18n.getString("termora.settings.terminal.right-click.copy")
|
||||
} else if (value == "CopyAndPaste") {
|
||||
text = I18n.getString("termora.settings.terminal.right-click.copy-and-paste")
|
||||
}
|
||||
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||
}
|
||||
}
|
||||
|
||||
cursorStyleComboBox.addItem(CursorStyle.Block)
|
||||
cursorStyleComboBox.addItem(CursorStyle.Bar)
|
||||
cursorStyleComboBox.addItem(CursorStyle.Underline)
|
||||
@@ -595,7 +642,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, left:pref, $FORM_MARGIN, pref, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
val beepBtn = JButton(Icons.run)
|
||||
@@ -624,6 +671,8 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
.add(hyperlinkComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.settings.terminal.select-copy")}:").xy(1, rows)
|
||||
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.settings.terminal.right-click")}:").xy(1, rows)
|
||||
.add(rightClickComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
|
||||
.add(cursorStyleComboBox).xy(3, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.settings.terminal.cursor-blink")}:").xy(1, rows)
|
||||
|
||||
7
src/main/kotlin/app/termora/TabOrder.kt
Normal file
7
src/main/kotlin/app/termora/TabOrder.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package app.termora
|
||||
|
||||
internal enum class TabOrder {
|
||||
Hide,
|
||||
AsNeed,
|
||||
Always,
|
||||
}
|
||||
@@ -337,13 +337,7 @@ class TerminalTabbed(
|
||||
val c = tab.getJComponent()
|
||||
val title = (c.getClientProperty(titleProperty) ?: tab.getTitle()).toString()
|
||||
|
||||
tabbedPane.insertTab(
|
||||
title,
|
||||
tab.getIcon(),
|
||||
c,
|
||||
StringUtils.EMPTY,
|
||||
index
|
||||
)
|
||||
tabbedPane.insertTab(title, tab.getIcon(), c, StringUtils.EMPTY, index)
|
||||
|
||||
// 设置标题
|
||||
c.putClientProperty(titleProperty, title)
|
||||
@@ -367,6 +361,10 @@ class TerminalTabbed(
|
||||
}
|
||||
}
|
||||
|
||||
override fun indexOfTerminalTab(tab: TerminalTab):Int {
|
||||
return tabbedPane.indexOfComponent(tab.getJComponent())
|
||||
}
|
||||
|
||||
private inner class SwitchFindEverywhereResult(
|
||||
private val title: String,
|
||||
private val icon: Icon?,
|
||||
|
||||
@@ -8,4 +8,5 @@ interface TerminalTabbedManager {
|
||||
fun setSelectedTerminalTab(tab: TerminalTab)
|
||||
fun closeTerminalTab(tab: TerminalTab, disposable: Boolean = true)
|
||||
fun refreshTerminalTabs()
|
||||
fun indexOfTerminalTab(tab: TerminalTab): Int
|
||||
}
|
||||
@@ -164,6 +164,8 @@ class TermoraFrame : JFrame(), DataProvider {
|
||||
|
||||
}).let { Disposer.register(windowScope, it) }
|
||||
|
||||
Disposer.register(windowScope, tabbedPane)
|
||||
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
@@ -224,7 +224,7 @@ class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProv
|
||||
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.title")
|
||||
return StringUtils.EMPTY
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
|
||||
@@ -8,7 +8,7 @@ object DataProviders {
|
||||
val Terminal = DataKey(app.termora.terminal.Terminal::class)
|
||||
val TerminalWriter get() = DataKey.TerminalWriter
|
||||
|
||||
val TabbedPane = DataKey(app.termora.MyTabbedPane::class)
|
||||
internal val TabbedPane = DataKey(app.termora.MyTabbedPane::class)
|
||||
val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class)
|
||||
val TerminalTab = DataKey(app.termora.TerminalTab::class)
|
||||
val TerminalTabbedManager = DataKey(app.termora.TerminalTabbedManager::class)
|
||||
|
||||
@@ -666,6 +666,11 @@ class DatabaseManager private constructor() : Disposable {
|
||||
*/
|
||||
var selectCopy by BooleanPropertyDelegate(false)
|
||||
|
||||
/**
|
||||
* 右键点击:Copy、CopyAndPaste
|
||||
*/
|
||||
var rightClick by StringPropertyDelegate("Copy")
|
||||
|
||||
/**
|
||||
* 光标样式
|
||||
*/
|
||||
@@ -716,6 +721,11 @@ class DatabaseManager private constructor() : Disposable {
|
||||
*/
|
||||
var layout by StringPropertyDelegate(TermoraLayout.Screen.name)
|
||||
|
||||
/**
|
||||
* 标签序号
|
||||
*/
|
||||
var tabOrder by StringPropertyDelegate(TabOrder.Hide.name)
|
||||
|
||||
/**
|
||||
* 跟随系统
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@ class NewKeywordHighlightDialog(
|
||||
FlatClientProperties.BUTTON_TYPE_TOOLBAR_BUTTON
|
||||
)
|
||||
|
||||
matchCaseBtn.toolTipText = "Match case"
|
||||
matchCaseBtn.toolTipText = I18n.getString("termora.match-case")
|
||||
|
||||
|
||||
val box = FlatToolBar()
|
||||
|
||||
@@ -31,7 +31,7 @@ class LocalTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
return if (unread) Icons.terminalUnread else Icons.terminal
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun willBeClose(): Boolean {
|
||||
@@ -62,6 +62,9 @@ class LocalTerminalTab(windowScope: WindowScope, host: Host) :
|
||||
) == JOptionPane.OK_OPTION
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return LocalTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
private fun getPtyProcessConnector(): PtyProcessConnector? {
|
||||
var p = getPtyConnector() as PtyConnector?
|
||||
|
||||
@@ -185,6 +185,10 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
|
||||
return Icons.fileFormat
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return SFTPPtyTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
override fun sendStartupCommand(ptyConnector: PtyConnector, bytes: ByteArray) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ class SSHTerminalTab(
|
||||
return mutex.isLocked.not()
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return SSHTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
override suspend fun openPtyConnector(): PtyConnector {
|
||||
if (mutex.tryLock()) {
|
||||
try {
|
||||
@@ -79,15 +83,14 @@ class SSHTerminalTab(
|
||||
}
|
||||
|
||||
val loading = coroutineScope.launch(Dispatchers.Swing) {
|
||||
val braille = "⡿⣟⣯⣷⣾⣽⣻⢿".reversed().toCharArray()
|
||||
// val braille = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏".toCharArray()
|
||||
var c = 0
|
||||
while (isActive) {
|
||||
if (++c > 6) c = 1
|
||||
terminal.write("${ControlCharacters.ESC}[1;32m")
|
||||
terminal.write(".".repeat(c))
|
||||
terminal.write(" ".repeat(6 - c))
|
||||
terminal.write("${ControlCharacters.ESC}[0m")
|
||||
delay(350.milliseconds)
|
||||
terminal.write("${ControlCharacters.BS}".repeat(6))
|
||||
if (++c >= braille.size) c = 0
|
||||
terminal.write("${braille[c]}")
|
||||
delay(100.milliseconds)
|
||||
terminal.write("${ControlCharacters.BS}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,17 +214,6 @@ class SSHTerminalTab(
|
||||
return super.getData(dataKey)
|
||||
}
|
||||
|
||||
override fun reconnect() {
|
||||
stop()
|
||||
|
||||
// 重新连接时就等于重新打开了一个标签,handler 重置
|
||||
handler.client = null
|
||||
handler.session = null
|
||||
handler.client = null
|
||||
|
||||
start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
if (mutex.tryLock()) {
|
||||
try {
|
||||
@@ -234,7 +226,7 @@ class SSHTerminalTab(
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
return if (unread) Icons.terminalUnread else Icons.terminal
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun beforeClose() {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package app.termora.plugin.internal.telnet
|
||||
|
||||
import app.termora.Host
|
||||
import app.termora.ProxyType
|
||||
import app.termora.PtyHostTerminalTab
|
||||
import app.termora.WindowScope
|
||||
import app.termora.*
|
||||
import app.termora.terminal.ControlCharacters
|
||||
import app.termora.terminal.KeyEncoderImpl
|
||||
import app.termora.terminal.PtyConnector
|
||||
@@ -71,5 +68,9 @@ class TelnetTerminalTab(
|
||||
return ptyConnectorFactory.decorate(TelnetStreamPtyConnector(telnet, telnet.charset, characterMode))
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return TelnetTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package app.termora.plugin.internal.updater
|
||||
|
||||
import app.termora.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.swing.Swing
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.semver4j.Semver
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.KeyboardFocusManager
|
||||
@@ -15,12 +18,12 @@ internal class MyApplicationRunnerExtension private constructor() : ApplicationR
|
||||
private val log = LoggerFactory.getLogger(MyApplicationRunnerExtension::class.java)
|
||||
}
|
||||
|
||||
private val disabledUpdater get() = Application.getLayout() == AppLayout.Appx
|
||||
private val disabledUpdater get() = Application.getLayout() == AppLayout.Appx || Application.getLayout() == AppLayout.AppStore
|
||||
private val updaterManager get() = UpdaterManager.getInstance()
|
||||
|
||||
|
||||
override fun ready() {
|
||||
swingCoroutineScope.launch {
|
||||
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
delay(3.seconds)
|
||||
scheduleUpdate()
|
||||
@@ -31,7 +34,7 @@ internal class MyApplicationRunnerExtension private constructor() : ApplicationR
|
||||
}
|
||||
|
||||
|
||||
private fun scheduleUpdate() {
|
||||
private suspend fun scheduleUpdate() {
|
||||
if (disabledUpdater) return
|
||||
|
||||
val latestVersion = updaterManager.fetchLatestVersion()
|
||||
@@ -45,13 +48,15 @@ internal class MyApplicationRunnerExtension private constructor() : ApplicationR
|
||||
return
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Swing) {
|
||||
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusedWindow
|
||||
?: TermoraFrameManager.getInstance().getWindows().firstOrNull()
|
||||
if (owner == null) return
|
||||
|
||||
if (owner != null) {
|
||||
val dialog = UpdaterDialog(owner, latestVersion)
|
||||
dialog.isModal = true
|
||||
dialog.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package app.termora.plugin.internal.wsl
|
||||
|
||||
import app.termora.Host
|
||||
import app.termora.PtyConnectorFactory
|
||||
import app.termora.PtyHostTerminalTab
|
||||
import app.termora.WindowScope
|
||||
import app.termora.*
|
||||
import app.termora.terminal.PtyConnector
|
||||
import org.apache.commons.io.Charsets
|
||||
import org.apache.commons.io.FileUtils
|
||||
@@ -51,6 +48,10 @@ class WSLHostTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
|
||||
return ptyConnector
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
return WSLHostTerminalTab(windowScope, host)
|
||||
}
|
||||
|
||||
|
||||
override fun sendStartupCommand(ptyConnector: PtyConnector, bytes: ByteArray) {
|
||||
// Nothing
|
||||
|
||||
@@ -332,15 +332,16 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
|
||||
var top = sr.getOrElse(0) { 1 }
|
||||
var bottom = sr.getOrElse(1) { terminalModel.getRows() }
|
||||
|
||||
if (bottom <= top || top < 1) {
|
||||
if (bottom <= top) {
|
||||
if (log.isWarnEnabled) {
|
||||
log.warn("Set Scrolling Region Error. top: $top , bottom: $bottom")
|
||||
}
|
||||
|
||||
top = 1
|
||||
bottom = terminalModel.getRows()
|
||||
}
|
||||
|
||||
top = max(1, top)
|
||||
bottom = min(terminalModel.getRows(), bottom)
|
||||
|
||||
|
||||
// 设置滚动区域
|
||||
terminal.getTerminalModel().setData(
|
||||
DataKey.ScrollingRegion,
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.termora.terminal.panel
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.TerminalCopyAction
|
||||
import app.termora.actions.TerminalPasteAction
|
||||
import app.termora.database.DatabaseManager
|
||||
import app.termora.terminal.*
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.jdesktop.swingx.action.ActionManager
|
||||
@@ -27,6 +28,7 @@ class TerminalPanelMouseSelectionAdapter(private val terminalPanel: TerminalPane
|
||||
private val isSelectCopy get() = terminalModel.getData(TerminalPanel.SelectCopy, false)
|
||||
private val selectionModel get() = terminal.getSelectionModel()
|
||||
private val wordBreakIterator = BreakIterator.getWordInstance()
|
||||
private val rightClickMode get() = DatabaseManager.getInstance().terminal.rightClick
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(TerminalPanelMouseSelectionAdapter::class.java)
|
||||
@@ -50,7 +52,7 @@ class TerminalPanelMouseSelectionAdapter(private val terminalPanel: TerminalPane
|
||||
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
// 如果有选中并且开启了选中复制,那么右键直接是粘贴
|
||||
if (selectionModel.hasSelection() && !isSelectCopy) {
|
||||
if (selectionModel.hasSelection() && isSelectCopy.not()) {
|
||||
triggerCopyAction(
|
||||
KeyEvent(
|
||||
e.component,
|
||||
@@ -61,6 +63,20 @@ class TerminalPanelMouseSelectionAdapter(private val terminalPanel: TerminalPane
|
||||
'C'
|
||||
)
|
||||
)
|
||||
|
||||
if (rightClickMode == "CopyAndPaste") {
|
||||
triggerPasteAction(
|
||||
KeyEvent(
|
||||
e.component,
|
||||
KeyEvent.KEY_PRESSED,
|
||||
e.`when`,
|
||||
e.modifiersEx,
|
||||
KeyEvent.VK_V,
|
||||
'V'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
// paste
|
||||
triggerPasteAction(
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package app.termora.terminal.panel.vw
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.actions.AnAction
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.DataProviders
|
||||
import app.termora.actions.*
|
||||
import app.termora.plugin.internal.badge.Badge
|
||||
import app.termora.plugin.internal.ssh.SSHTerminalTab
|
||||
import app.termora.plugin.internal.ssh.SSHTerminalTab.Companion.SSHSession
|
||||
@@ -39,7 +37,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
||||
internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: VisualWindowManager) :
|
||||
SSHVisualWindow(tab, "Transfer", visualWindowManager) {
|
||||
SSHVisualWindow(tab, "Transfer", visualWindowManager), DataProvider {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(TransferVisualWindow::class.java)
|
||||
@@ -65,7 +63,7 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
|
||||
private val downloadBtn = JButton(Icons.download)
|
||||
private val badgePresentation = Badge.getInstance(tab.windowScope)
|
||||
.addBadge(downloadBtn).apply { visible = false }
|
||||
|
||||
private val support = DataProviderSupport()
|
||||
|
||||
init {
|
||||
initViews()
|
||||
@@ -82,6 +80,8 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
|
||||
|
||||
|
||||
add(panel, BorderLayout.CENTER)
|
||||
|
||||
support.addData(TransportViewer.MyTransferManager, transferManager)
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
@@ -240,6 +240,10 @@ internal class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||
return support.getData(dataKey)
|
||||
}
|
||||
|
||||
override fun toolbarButtons(): List<Pair<JButton, Position>> {
|
||||
return listOf(downloadBtn to Position.Left, questionBtn to Position.Right)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package app.termora.tlog
|
||||
|
||||
import app.termora.Host
|
||||
import app.termora.Icons
|
||||
import app.termora.PtyHostTerminalTab
|
||||
import app.termora.WindowScope
|
||||
import app.termora.*
|
||||
import app.termora.terminal.PtyConnector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -71,6 +68,10 @@ class LogViewerTerminalTab(
|
||||
return false
|
||||
}
|
||||
|
||||
override fun createReconnectTerminalTab(): TerminalTab {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun canClone(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package app.termora.transfer
|
||||
|
||||
import app.termora.Application
|
||||
import app.termora.ApplicationScope
|
||||
import app.termora.I18n
|
||||
import app.termora.OptionPane
|
||||
import app.termora.*
|
||||
import app.termora.plugin.ExtensionManager
|
||||
import app.termora.transfer.TransportPanel.Companion.isLocallyFileSystem
|
||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.apache.sshd.sftp.client.fs.SftpFileSystem
|
||||
@@ -149,7 +147,12 @@ internal class TransportPopupMenu(
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
transferMenu.addActionListener { fireActionPerformed(it, ActionCommand.Transfer) }
|
||||
transferMenu.addActionListener {
|
||||
swingCoroutineScope.launch {
|
||||
fireActionPerformed(it, ActionCommand.Transfer)
|
||||
}
|
||||
}
|
||||
|
||||
deleteMenu.addActionListener {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
owner,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<Identity Name="TermoraDev.Termora"
|
||||
Publisher="CN=C804E131-4368-4BF7-9E7F-95C681AD0AAC"
|
||||
Version="@version@.0"
|
||||
Version="@version@.@betaVersion@"
|
||||
ProcessorArchitecture="@architecture@"/>
|
||||
|
||||
<Properties>
|
||||
|
||||
@@ -45,6 +45,7 @@ termora.settings.appearance.i-want-to-translate=I want to translate
|
||||
termora.settings.appearance.follow-system=Sync with OS
|
||||
termora.settings.appearance.opacity=Opacity
|
||||
termora.settings.appearance.background-running=Backgrounding
|
||||
termora.settings.appearance.tab-order=Tab Order
|
||||
termora.settings.appearance.confirm-tab-close=Confirm tab close
|
||||
|
||||
termora.settings.terminal=Terminal
|
||||
@@ -56,6 +57,9 @@ termora.settings.terminal.debug=Debug mode
|
||||
termora.settings.terminal.beep=Beep
|
||||
termora.settings.terminal.hyperlink=Hyperlink
|
||||
termora.settings.terminal.select-copy=Select copy
|
||||
termora.settings.terminal.right-click=Right click
|
||||
termora.settings.terminal.right-click.copy-and-paste=Copy and Paste
|
||||
termora.settings.terminal.right-click.copy=${termora.copy}
|
||||
termora.settings.terminal.cursor-style=Cursor type
|
||||
termora.settings.terminal.cursor-blink=Cursor blink
|
||||
termora.settings.terminal.local-shell=Local shell
|
||||
|
||||
@@ -40,6 +40,7 @@ termora.settings.appearance.follow-system=Как в системе
|
||||
termora.settings.appearance.opacity=Прозрачность
|
||||
termora.settings.appearance.background-image=Фоновое изображение
|
||||
termora.settings.appearance.background-running=Фон
|
||||
termora.settings.appearance.tab-order=Порядок вкладок
|
||||
termora.settings.appearance.confirm-tab-close=Подтверждение закрытия вкладки
|
||||
|
||||
termora.setting.security=Безопасность
|
||||
@@ -59,6 +60,8 @@ termora.settings.terminal.debug=Режим отладки
|
||||
termora.settings.terminal.beep=Сигнал
|
||||
termora.settings.terminal.hyperlink=Ссылки
|
||||
termora.settings.terminal.select-copy=Копировать выделенное
|
||||
termora.settings.terminal.right-click=правой кнопкой мыши
|
||||
termora.settings.terminal.right-click.copy-and-paste=Копировать и вставить
|
||||
termora.settings.terminal.cursor-style=Вид курсора
|
||||
termora.settings.terminal.cursor-blink=Мигать курсором
|
||||
termora.settings.terminal.local-shell=Локальный терминал
|
||||
|
||||
@@ -49,6 +49,7 @@ termora.settings.appearance.i-want-to-translate=我想要翻译
|
||||
termora.settings.appearance.follow-system=跟随系统
|
||||
termora.settings.appearance.opacity=透明度
|
||||
termora.settings.appearance.background-running=后台运行
|
||||
termora.settings.appearance.tab-order=标签序号
|
||||
termora.settings.appearance.confirm-tab-close=标签关闭前确认
|
||||
|
||||
# Find everywhere
|
||||
@@ -70,6 +71,8 @@ termora.settings.terminal.debug=调试模式
|
||||
termora.settings.terminal.beep=蜂鸣声
|
||||
termora.settings.terminal.hyperlink=超链接
|
||||
termora.settings.terminal.select-copy=选中复制
|
||||
termora.settings.terminal.right-click=右键点击
|
||||
termora.settings.terminal.right-click.copy-and-paste=复制 & 粘贴
|
||||
termora.settings.terminal.cursor-style=光标样式
|
||||
termora.settings.terminal.cursor-blink=光标闪烁
|
||||
termora.settings.terminal.local-shell=本地终端
|
||||
|
||||
@@ -48,6 +48,7 @@ termora.settings.appearance.i-want-to-translate=我想要翻譯
|
||||
termora.settings.appearance.follow-system=跟隨系統
|
||||
termora.settings.appearance.opacity=透明度
|
||||
termora.settings.appearance.background-running=後台運行
|
||||
termora.settings.appearance.tab-order=標籤序號
|
||||
termora.settings.appearance.confirm-tab-close=關閉分頁確認
|
||||
|
||||
termora.settings.keymap=鍵盤
|
||||
@@ -79,8 +80,10 @@ termora.settings.terminal.size=大小
|
||||
termora.settings.terminal.max-rows=最大行數
|
||||
termora.settings.terminal.debug=偵錯模式
|
||||
termora.settings.terminal.beep=超連結
|
||||
termora.settings.terminal.hyperlink=Hyperlink
|
||||
termora.settings.terminal.hyperlink=超連結
|
||||
termora.settings.terminal.select-copy=選取複製
|
||||
termora.settings.terminal.right-click=右鍵點擊
|
||||
termora.settings.terminal.right-click.copy-and-paste=複製 & 貼上
|
||||
termora.settings.terminal.cursor-style=遊標風格
|
||||
termora.settings.terminal.cursor-blink=遊標閃爍
|
||||
termora.settings.terminal.local-shell=本地端
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" fill="#EBECF0" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 701 B |
@@ -1,4 +1,4 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" fill="#43454A" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 701 B |
Reference in New Issue
Block a user