feat: support for restoring virtual windows

This commit is contained in:
hstyi
2025-04-07 11:45:54 +08:00
committed by hstyi
parent 4c8944d248
commit 54e0f621ce
10 changed files with 108 additions and 20 deletions

View File

@@ -52,6 +52,7 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
init {
terminalPanel.dropFiles = false
terminalPanel.dataProviderSupport.addData(DataProviders.TerminalTab, this)
}
override fun getJComponent(): JComponent {
@@ -222,6 +223,11 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
}
}
override fun willBeClose(): Boolean {
// 保存窗口状态
terminalPanel.storeVisualWindows(host.id)
return super.willBeClose()
}
private inner class MySessionListener : SessionListener, Disposable {
override fun sessionEvent(session: Session, event: Event) {

View File

@@ -14,11 +14,16 @@ import com.formdev.flatlaf.extras.components.FlatToolBar
import com.formdev.flatlaf.ui.FlatRoundBorder
import org.apache.commons.lang3.StringUtils
import java.awt.event.ActionListener
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener
import java.util.*
import javax.swing.JButton
import javax.swing.SwingUtilities
class FloatingToolbarPanel : FlatToolBar(), Disposable {
private val floatingToolbarEnable get() = Database.getDatabase().terminal.floatingToolbar
private var closed = false
private val anEvent get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
companion object {
@@ -72,6 +77,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
}
initActions()
initEvents()
}
override fun updateUI() {
@@ -123,12 +129,38 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
add(initCloseActionButton())
}
private fun initEvents() {
// 被添加到组件后
addPropertyChangeListener("ancestor", object : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent) {
removePropertyChangeListener("ancestor", this)
SwingUtilities.invokeLater { resumeVisualWindows() }
}
})
}
@Suppress("UNCHECKED_CAST")
private fun resumeVisualWindows() {
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
if (tab !is SSHTerminalTab) return
val terminalPanel = tab.getData(DataProviders.TerminalPanel) ?: return
terminalPanel.resumeVisualWindows(tab.host.id, object : DataProvider {
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
if (dataKey == DataProviders.TerminalTab) {
return tab as T
}
return super.getData(dataKey)
}
})
}
private fun initServerInfoActionButton(): JButton {
val btn = JButton(Icons.infoOutline)
btn.toolTipText = I18n.getString("termora.visual-window.system-information")
btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
if (tab !is SSHTerminalTab) {
@@ -156,7 +188,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.toolTipText = I18n.getString("termora.snippet.title")
btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val writer = tab.getData(DataProviders.TerminalWriter) ?: return
val dialog = SnippetTreeDialog(evt.window)
dialog.setLocationRelativeTo(btn)
@@ -174,7 +206,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.toolTipText = I18n.getString("termora.visual-window.nvidia-smi")
btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
if (tab !is SSHTerminalTab) {
@@ -233,7 +265,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
if (tab.canReconnect()) {
tab.reconnect()
}
@@ -242,8 +274,4 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
return btn
}
override fun dispose() {
}
}

View File

@@ -1,13 +1,14 @@
package app.termora.terminal.panel
import app.termora.Database
import app.termora.Disposable
import app.termora.Disposer
import app.termora.SSHTerminalTab
import app.termora.actions.DataProvider
import app.termora.actions.DataProviderSupport
import app.termora.actions.DataProviders
import app.termora.terminal.*
import app.termora.terminal.panel.vw.VisualWindow
import app.termora.terminal.panel.vw.VisualWindowManager
import app.termora.terminal.panel.vw.*
import com.formdev.flatlaf.util.SystemInfo
import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.StringUtils
@@ -44,15 +45,15 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
val SelectCopy = DataKey(Boolean::class)
}
private val properties get() = Database.getDatabase().properties
private val terminalBlink = TerminalBlink(terminal)
private val terminalFindPanel = TerminalFindPanel(this, terminal)
private val floatingToolbar = FloatingToolbarPanel()
private val terminalDisplay = TerminalDisplay(this, terminal, terminalBlink)
private val dataProviderSupport = DataProviderSupport()
private val layeredPane = TerminalLayeredPane()
private var visualWindows = emptyArray<VisualWindow>()
val scrollBar = TerminalScrollBar(this@TerminalPanel, terminalFindPanel, terminal)
val scrollBar = TerminalScrollBar(this, terminalFindPanel, terminal)
var enableFloatingToolbar = true
set(value) {
field = value
@@ -63,6 +64,8 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
}
}
val dataProviderSupport = DataProviderSupport()
/**
* 键盘事件
@@ -585,6 +588,37 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
requestFocusInWindow()
}
override fun resumeVisualWindows(id: String, dataProvider: DataProvider) {
val windows = properties.getString("VisualWindow.${id}.store") ?: return
for (name in windows.split(",")) {
if (name == "NVIDIA-SMI") {
addVisualWindow(
NvidiaSMIVisualWindow(
dataProvider.getData(DataProviders.TerminalTab) as SSHTerminalTab,
this
)
)
} else if (name == "SystemInformation") {
addVisualWindow(
SystemInformationVisualWindow(
dataProvider.getData(DataProviders.TerminalTab) as SSHTerminalTab,
this
)
)
}
}
}
override fun storeVisualWindows(id: String) {
val windows = mutableListOf<String>()
for (window in getVisualWindows()) {
if (window is Resumeable) {
windows.add(window.getWindowName())
}
}
properties.putString("VisualWindow.${id}.store", windows.joinToString(","))
}
override fun getDimension(): Dimension {
return Dimension(
terminalDisplay.size.width + padding.left + padding.right,

View File

@@ -47,6 +47,7 @@ class NvidiaSMIVisualWindow(tab: SSHTerminalTab, visualWindowManager: VisualWind
private val percentageBtn by lazy { JButton(if (isPercentage) Icons.text else Icons.percentage) }
init {
Disposer.register(tab, this)
initViews()
initEvents()
initVisualWindowPanel()

View File

@@ -0,0 +1,3 @@
package app.termora.terminal.panel.vw
interface Resumeable

View File

@@ -1,6 +1,5 @@
package app.termora.terminal.panel.vw
import app.termora.Disposer
import app.termora.SSHTerminalTab
import app.termora.actions.AnActionEvent
import app.termora.actions.DataProviders
@@ -11,11 +10,7 @@ abstract class SSHVisualWindow(
protected val tab: SSHTerminalTab,
id: String,
visualWindowManager: VisualWindowManager
) : VisualWindowPanel(id, visualWindowManager) {
init {
Disposer.register(tab, this)
}
) : VisualWindowPanel(id, visualWindowManager), Resumeable {
override fun toggleWindow() {
val evt = AnActionEvent(tab.getJComponent(), StringUtils.EMPTY, EventObject(this))

View File

@@ -25,6 +25,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private val systemInformationPanel by lazy { SystemInformationPanel() }
init {
Disposer.register(tab, this)
initViews()
initEvents()
initVisualWindowPanel()
@@ -137,7 +138,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private suspend fun refreshCPUAndMem(session: ClientSession) {
// top
var pair = SshClients.execChannel(session, "top -bn1")
val pair = SshClients.execChannel(session, "top -bn1")
if (pair.first != 0) {
return
}
@@ -236,7 +237,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private suspend fun refreshDisk(session: ClientSession) {
// df -h
var pair = SshClients.execChannel(session, "df -B1")
val pair = SshClients.execChannel(session, "df -B1")
if (pair.first != 0) {
return
}

View File

@@ -28,4 +28,9 @@ interface VisualWindow : Disposable {
* 切换独立模式
*/
fun toggleWindow()
/**
* 同一个类,返回的相同
*/
fun getWindowName(): String
}

View File

@@ -1,5 +1,6 @@
package app.termora.terminal.panel.vw
import app.termora.actions.DataProvider
import java.awt.Dimension
interface VisualWindowManager {
@@ -33,4 +34,14 @@ interface VisualWindowManager {
* 获取管理器的宽高
*/
fun getDimension(): Dimension
/**
* 恢复所有窗口
*/
fun resumeVisualWindows(id: String, dataProvider: DataProvider)
/**
* 存储所有窗口
*/
fun storeVisualWindows(id: String)
}

View File

@@ -374,4 +374,8 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
return null
}
}
override fun getWindowName(): String {
return id
}
}