mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support system tray (#403)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ certs/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
.vs
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
|
||||
@@ -21,7 +21,13 @@ import org.apache.commons.lang3.LocaleUtils
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.json.JSONObject
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.MenuItem
|
||||
import java.awt.PopupMenu
|
||||
import java.awt.SystemTray
|
||||
import java.awt.TrayIcon
|
||||
import java.awt.event.ActionEvent
|
||||
import java.util.*
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.*
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -63,6 +69,9 @@ class ApplicationRunner {
|
||||
// 启动主窗口
|
||||
val startMainFrame = measureTimeMillis { startMainFrame() }
|
||||
|
||||
// 设置托盘
|
||||
val setupSystemTray = measureTimeMillis { SwingUtilities.invokeLater { setupSystemTray() } }
|
||||
|
||||
if (log.isDebugEnabled) {
|
||||
log.debug("printSystemInfo: {}ms", printSystemInfo)
|
||||
log.debug("openDatabase: {}ms", openDatabase)
|
||||
@@ -71,6 +80,7 @@ class ApplicationRunner {
|
||||
log.debug("setupLaf: {}ms", setupLaf)
|
||||
log.debug("openDoor: {}ms", openDoor)
|
||||
log.debug("startMainFrame: {}ms", startMainFrame)
|
||||
log.debug("setupSystemTray: {}ms", setupSystemTray)
|
||||
}
|
||||
}.let {
|
||||
if (log.isDebugEnabled) {
|
||||
@@ -106,6 +116,37 @@ class ApplicationRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSystemTray() {
|
||||
if (!SystemInfo.isWindows || !SystemTray.isSupported()) return
|
||||
|
||||
val tray = SystemTray.getSystemTray()
|
||||
val image = ImageIO.read(TermoraFrame::class.java.getResourceAsStream("/icons/termora_16x16.png"))
|
||||
val trayIcon = TrayIcon(image)
|
||||
val popupMenu = PopupMenu()
|
||||
trayIcon.popupMenu = popupMenu
|
||||
trayIcon.toolTip = Application.getName()
|
||||
|
||||
// PopupMenu 不支持中文
|
||||
val exitMenu = MenuItem("Exit")
|
||||
exitMenu.addActionListener { SwingUtilities.invokeLater { quitHandler() } }
|
||||
popupMenu.add(exitMenu)
|
||||
|
||||
// double click
|
||||
trayIcon.addActionListener(object : AbstractAction() {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
TermoraFrameManager.getInstance().tick()
|
||||
}
|
||||
})
|
||||
|
||||
tray.add(trayIcon)
|
||||
|
||||
Disposer.register(ApplicationScope.forApplicationScope(), object : Disposable {
|
||||
override fun dispose() {
|
||||
tray.remove(trayIcon)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun quitHandler() {
|
||||
for (frame in TermoraFrameManager.getInstance().getWindows()) {
|
||||
frame.dispose()
|
||||
|
||||
@@ -580,6 +580,11 @@ class Database private constructor(private val env: Environment) : Disposable {
|
||||
var darkTheme by StringPropertyDelegate("Dark")
|
||||
var lightTheme by StringPropertyDelegate("Light")
|
||||
|
||||
/**
|
||||
* 允许后台运行,也就是托盘
|
||||
*/
|
||||
var backgroundRunning by BooleanPropertyDelegate(false)
|
||||
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
|
||||
@@ -129,6 +129,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
val themeManager = ThemeManager.getInstance()
|
||||
val themeComboBox = FlatComboBox<String>()
|
||||
val languageComboBox = FlatComboBox<String>()
|
||||
val backgroundComBoBox = YesOrNoComboBox()
|
||||
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
|
||||
val preferredThemeBtn = JButton(Icons.settings)
|
||||
private val appearance get() = database.appearance
|
||||
@@ -142,6 +143,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
|
||||
followSystemCheckBox.isSelected = appearance.followSystem
|
||||
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
|
||||
backgroundComBoBox.selectedItem = appearance.backgroundRunning
|
||||
|
||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||
themeManager.themes.keys.forEach { themeComboBox.addItem(it) }
|
||||
@@ -178,6 +180,12 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
}
|
||||
|
||||
backgroundComBoBox.addItemListener {
|
||||
if (it.stateChange == ItemEvent.SELECTED) {
|
||||
appearance.backgroundRunning = backgroundComBoBox.selectedItem as Boolean
|
||||
}
|
||||
}
|
||||
|
||||
followSystemCheckBox.addActionListener {
|
||||
appearance.followSystem = followSystemCheckBox.isSelected
|
||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||
@@ -276,7 +284,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private fun getFormPanel(): JPanel {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
|
||||
"pref, $formMargin, pref, $formMargin"
|
||||
"pref, $formMargin, pref, $formMargin, pref"
|
||||
)
|
||||
val box = FlatToolBar()
|
||||
box.add(followSystemCheckBox)
|
||||
@@ -285,7 +293,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
return FormBuilder.create().layout(layout)
|
||||
val builder = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.settings.appearance.theme")}:").xy(1, rows)
|
||||
.add(themeComboBox).xy(3, rows)
|
||||
.add(box).xy(5, rows).apply { rows += step }
|
||||
@@ -296,7 +304,13 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
Application.browse(URI.create("https://github.com/TermoraDev/termora/tree/main/src/main/resources/i18n"))
|
||||
}
|
||||
})).xy(5, rows).apply { rows += step }
|
||||
.build()
|
||||
|
||||
if (SystemInfo.isWindows) {
|
||||
builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows)
|
||||
.add(backgroundComBoBox).xy(3, rows)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -408,8 +422,8 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
|
||||
private fun fireFontChanged() {
|
||||
TerminalPanelFactory.getInstance()
|
||||
.fireResize()
|
||||
TerminalPanelFactory.getInstance()
|
||||
.fireResize()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
@@ -470,7 +484,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
|
||||
shellComboBox.selectedItem = terminalSetting.localShell
|
||||
|
||||
val fonts = linkedSetOf<String>("JetBrains Mono", "Source Code Pro", "Monospaced")
|
||||
val fonts = linkedSetOf("JetBrains Mono", "Source Code Pro", "Monospaced")
|
||||
FontUtils.getAllFonts().forEach {
|
||||
if (!fonts.contains(it.family)) {
|
||||
fonts.addLast(it.family)
|
||||
|
||||
@@ -26,6 +26,7 @@ class TermoraFrameManager {
|
||||
|
||||
private val frames = mutableListOf<TermoraFrame>()
|
||||
private val properties get() = Database.getDatabase().properties
|
||||
private val isBackgroundRunning get() = Database.getDatabase().appearance.backgroundRunning
|
||||
|
||||
fun createWindow(): TermoraFrame {
|
||||
val frame = TermoraFrame().apply { registerCloseCallback(this) }
|
||||
@@ -83,13 +84,20 @@ class TermoraFrameManager {
|
||||
|
||||
override fun windowClosing(e: WindowEvent) {
|
||||
if (ApplicationScope.windowScopes().size == 1) {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
window,
|
||||
I18n.getString("termora.quit-confirm", Application.getName()),
|
||||
optionType = JOptionPane.YES_NO_OPTION,
|
||||
) == JOptionPane.YES_OPTION
|
||||
) {
|
||||
window.dispose()
|
||||
if (SystemInfo.isWindows && isBackgroundRunning) {
|
||||
// 最小化
|
||||
window.extendedState = window.extendedState or JFrame.ICONIFIED
|
||||
// 隐藏
|
||||
window.isVisible = false
|
||||
} else {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
window,
|
||||
I18n.getString("termora.quit-confirm", Application.getName()),
|
||||
optionType = JOptionPane.YES_NO_OPTION,
|
||||
) == JOptionPane.YES_OPTION
|
||||
) {
|
||||
window.dispose()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.dispose()
|
||||
@@ -106,6 +114,7 @@ class TermoraFrameManager {
|
||||
if (window.extendedState and JFrame.ICONIFIED == JFrame.ICONIFIED) {
|
||||
window.extendedState = window.extendedState and JFrame.ICONIFIED.inv()
|
||||
}
|
||||
window.isVisible = true
|
||||
}
|
||||
windows.last().toFront()
|
||||
} else {
|
||||
|
||||
@@ -50,6 +50,7 @@ termora.settings.appearance.theme=Theme
|
||||
termora.settings.appearance.language=Language
|
||||
termora.settings.appearance.i-want-to-translate=I want to translate
|
||||
termora.settings.appearance.follow-system=Sync with OS
|
||||
termora.settings.appearance.background-running=Backgrounding
|
||||
|
||||
termora.setting.security=Security
|
||||
termora.setting.security.enter-password=Enter password
|
||||
|
||||
@@ -48,6 +48,7 @@ termora.settings.appearance.theme=主题
|
||||
termora.settings.appearance.language=语言
|
||||
termora.settings.appearance.i-want-to-translate=我想要翻译
|
||||
termora.settings.appearance.follow-system=跟随系统
|
||||
termora.settings.appearance.background-running=后台运行
|
||||
|
||||
termora.setting.security=安全
|
||||
termora.setting.security.enter-password=请输入密码
|
||||
|
||||
@@ -49,6 +49,7 @@ termora.settings.appearance.theme=主题
|
||||
termora.settings.appearance.language=語言
|
||||
termora.settings.appearance.i-want-to-translate=我想要翻譯
|
||||
termora.settings.appearance.follow-system=跟隨系統
|
||||
termora.settings.appearance.background-running=後台運行
|
||||
|
||||
termora.setting.security=安全
|
||||
termora.setting.security.enter-password=請輸入密碼
|
||||
|
||||
Reference in New Issue
Block a user