mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12: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
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
|
.vs
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ import org.apache.commons.lang3.LocaleUtils
|
|||||||
import org.apache.commons.lang3.SystemUtils
|
import org.apache.commons.lang3.SystemUtils
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.slf4j.LoggerFactory
|
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 java.util.*
|
||||||
|
import javax.imageio.ImageIO
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
@@ -63,6 +69,9 @@ class ApplicationRunner {
|
|||||||
// 启动主窗口
|
// 启动主窗口
|
||||||
val startMainFrame = measureTimeMillis { startMainFrame() }
|
val startMainFrame = measureTimeMillis { startMainFrame() }
|
||||||
|
|
||||||
|
// 设置托盘
|
||||||
|
val setupSystemTray = measureTimeMillis { SwingUtilities.invokeLater { setupSystemTray() } }
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("printSystemInfo: {}ms", printSystemInfo)
|
log.debug("printSystemInfo: {}ms", printSystemInfo)
|
||||||
log.debug("openDatabase: {}ms", openDatabase)
|
log.debug("openDatabase: {}ms", openDatabase)
|
||||||
@@ -71,6 +80,7 @@ class ApplicationRunner {
|
|||||||
log.debug("setupLaf: {}ms", setupLaf)
|
log.debug("setupLaf: {}ms", setupLaf)
|
||||||
log.debug("openDoor: {}ms", openDoor)
|
log.debug("openDoor: {}ms", openDoor)
|
||||||
log.debug("startMainFrame: {}ms", startMainFrame)
|
log.debug("startMainFrame: {}ms", startMainFrame)
|
||||||
|
log.debug("setupSystemTray: {}ms", setupSystemTray)
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (log.isDebugEnabled) {
|
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() {
|
private fun quitHandler() {
|
||||||
for (frame in TermoraFrameManager.getInstance().getWindows()) {
|
for (frame in TermoraFrameManager.getInstance().getWindows()) {
|
||||||
frame.dispose()
|
frame.dispose()
|
||||||
|
|||||||
@@ -580,6 +580,11 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
var darkTheme by StringPropertyDelegate("Dark")
|
var darkTheme by StringPropertyDelegate("Dark")
|
||||||
var lightTheme by StringPropertyDelegate("Light")
|
var lightTheme by StringPropertyDelegate("Light")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许后台运行,也就是托盘
|
||||||
|
*/
|
||||||
|
var backgroundRunning by BooleanPropertyDelegate(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 语言
|
* 语言
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
val themeManager = ThemeManager.getInstance()
|
val themeManager = ThemeManager.getInstance()
|
||||||
val themeComboBox = FlatComboBox<String>()
|
val themeComboBox = FlatComboBox<String>()
|
||||||
val languageComboBox = FlatComboBox<String>()
|
val languageComboBox = FlatComboBox<String>()
|
||||||
|
val backgroundComBoBox = YesOrNoComboBox()
|
||||||
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
|
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
|
||||||
val preferredThemeBtn = JButton(Icons.settings)
|
val preferredThemeBtn = JButton(Icons.settings)
|
||||||
private val appearance get() = database.appearance
|
private val appearance get() = database.appearance
|
||||||
@@ -142,6 +143,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
followSystemCheckBox.isSelected = appearance.followSystem
|
followSystemCheckBox.isSelected = appearance.followSystem
|
||||||
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
|
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
|
||||||
|
backgroundComBoBox.selectedItem = appearance.backgroundRunning
|
||||||
|
|
||||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||||
themeManager.themes.keys.forEach { themeComboBox.addItem(it) }
|
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 {
|
followSystemCheckBox.addActionListener {
|
||||||
appearance.followSystem = followSystemCheckBox.isSelected
|
appearance.followSystem = followSystemCheckBox.isSelected
|
||||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||||
@@ -276,7 +284,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private fun getFormPanel(): JPanel {
|
private fun getFormPanel(): JPanel {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
|
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
|
||||||
"pref, $formMargin, pref, $formMargin"
|
"pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
val box = FlatToolBar()
|
val box = FlatToolBar()
|
||||||
box.add(followSystemCheckBox)
|
box.add(followSystemCheckBox)
|
||||||
@@ -285,7 +293,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
var rows = 1
|
var rows = 1
|
||||||
val step = 2
|
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("${I18n.getString("termora.settings.appearance.theme")}:").xy(1, rows)
|
||||||
.add(themeComboBox).xy(3, rows)
|
.add(themeComboBox).xy(3, rows)
|
||||||
.add(box).xy(5, rows).apply { rows += step }
|
.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"))
|
Application.browse(URI.create("https://github.com/TermoraDev/termora/tree/main/src/main/resources/i18n"))
|
||||||
}
|
}
|
||||||
})).xy(5, rows).apply { rows += step }
|
})).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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -470,7 +484,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
shellComboBox.selectedItem = terminalSetting.localShell
|
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 {
|
FontUtils.getAllFonts().forEach {
|
||||||
if (!fonts.contains(it.family)) {
|
if (!fonts.contains(it.family)) {
|
||||||
fonts.addLast(it.family)
|
fonts.addLast(it.family)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class TermoraFrameManager {
|
|||||||
|
|
||||||
private val frames = mutableListOf<TermoraFrame>()
|
private val frames = mutableListOf<TermoraFrame>()
|
||||||
private val properties get() = Database.getDatabase().properties
|
private val properties get() = Database.getDatabase().properties
|
||||||
|
private val isBackgroundRunning get() = Database.getDatabase().appearance.backgroundRunning
|
||||||
|
|
||||||
fun createWindow(): TermoraFrame {
|
fun createWindow(): TermoraFrame {
|
||||||
val frame = TermoraFrame().apply { registerCloseCallback(this) }
|
val frame = TermoraFrame().apply { registerCloseCallback(this) }
|
||||||
@@ -83,6 +84,12 @@ class TermoraFrameManager {
|
|||||||
|
|
||||||
override fun windowClosing(e: WindowEvent) {
|
override fun windowClosing(e: WindowEvent) {
|
||||||
if (ApplicationScope.windowScopes().size == 1) {
|
if (ApplicationScope.windowScopes().size == 1) {
|
||||||
|
if (SystemInfo.isWindows && isBackgroundRunning) {
|
||||||
|
// 最小化
|
||||||
|
window.extendedState = window.extendedState or JFrame.ICONIFIED
|
||||||
|
// 隐藏
|
||||||
|
window.isVisible = false
|
||||||
|
} else {
|
||||||
if (OptionPane.showConfirmDialog(
|
if (OptionPane.showConfirmDialog(
|
||||||
window,
|
window,
|
||||||
I18n.getString("termora.quit-confirm", Application.getName()),
|
I18n.getString("termora.quit-confirm", Application.getName()),
|
||||||
@@ -91,6 +98,7 @@ class TermoraFrameManager {
|
|||||||
) {
|
) {
|
||||||
window.dispose()
|
window.dispose()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
window.dispose()
|
window.dispose()
|
||||||
}
|
}
|
||||||
@@ -106,6 +114,7 @@ class TermoraFrameManager {
|
|||||||
if (window.extendedState and JFrame.ICONIFIED == JFrame.ICONIFIED) {
|
if (window.extendedState and JFrame.ICONIFIED == JFrame.ICONIFIED) {
|
||||||
window.extendedState = window.extendedState and JFrame.ICONIFIED.inv()
|
window.extendedState = window.extendedState and JFrame.ICONIFIED.inv()
|
||||||
}
|
}
|
||||||
|
window.isVisible = true
|
||||||
}
|
}
|
||||||
windows.last().toFront()
|
windows.last().toFront()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ termora.settings.appearance.theme=Theme
|
|||||||
termora.settings.appearance.language=Language
|
termora.settings.appearance.language=Language
|
||||||
termora.settings.appearance.i-want-to-translate=I want to translate
|
termora.settings.appearance.i-want-to-translate=I want to translate
|
||||||
termora.settings.appearance.follow-system=Sync with OS
|
termora.settings.appearance.follow-system=Sync with OS
|
||||||
|
termora.settings.appearance.background-running=Backgrounding
|
||||||
|
|
||||||
termora.setting.security=Security
|
termora.setting.security=Security
|
||||||
termora.setting.security.enter-password=Enter password
|
termora.setting.security.enter-password=Enter password
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ termora.settings.appearance.theme=主题
|
|||||||
termora.settings.appearance.language=语言
|
termora.settings.appearance.language=语言
|
||||||
termora.settings.appearance.i-want-to-translate=我想要翻译
|
termora.settings.appearance.i-want-to-translate=我想要翻译
|
||||||
termora.settings.appearance.follow-system=跟随系统
|
termora.settings.appearance.follow-system=跟随系统
|
||||||
|
termora.settings.appearance.background-running=后台运行
|
||||||
|
|
||||||
termora.setting.security=安全
|
termora.setting.security=安全
|
||||||
termora.setting.security.enter-password=请输入密码
|
termora.setting.security.enter-password=请输入密码
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ termora.settings.appearance.theme=主题
|
|||||||
termora.settings.appearance.language=語言
|
termora.settings.appearance.language=語言
|
||||||
termora.settings.appearance.i-want-to-translate=我想要翻譯
|
termora.settings.appearance.i-want-to-translate=我想要翻譯
|
||||||
termora.settings.appearance.follow-system=跟隨系統
|
termora.settings.appearance.follow-system=跟隨系統
|
||||||
|
termora.settings.appearance.background-running=後台運行
|
||||||
|
|
||||||
termora.setting.security=安全
|
termora.setting.security=安全
|
||||||
termora.setting.security.enter-password=請輸入密碼
|
termora.setting.security.enter-password=請輸入密碼
|
||||||
|
|||||||
Reference in New Issue
Block a user