mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: macOS supports running in the background (#487)
This commit is contained in:
@@ -28,8 +28,12 @@ import java.awt.MenuItem
|
|||||||
import java.awt.PopupMenu
|
import java.awt.PopupMenu
|
||||||
import java.awt.SystemTray
|
import java.awt.SystemTray
|
||||||
import java.awt.TrayIcon
|
import java.awt.TrayIcon
|
||||||
|
import java.awt.desktop.AppReopenedEvent
|
||||||
|
import java.awt.desktop.AppReopenedListener
|
||||||
|
import java.awt.desktop.SystemEventListener
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
@@ -123,7 +127,20 @@ class ApplicationRunner {
|
|||||||
TermoraFrameManager.getInstance().createWindow().isVisible = true
|
TermoraFrameManager.getInstance().createWindow().isVisible = true
|
||||||
|
|
||||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
SwingUtilities.invokeLater { FlatDesktop.setQuitHandler { quitHandler() } }
|
SwingUtilities.invokeLater {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置 Dock
|
||||||
|
setupMacOSDock()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command + Q
|
||||||
|
FlatDesktop.setQuitHandler { quitHandler() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,9 +176,13 @@ class ApplicationRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun quitHandler() {
|
private fun quitHandler() {
|
||||||
for (frame in TermoraFrameManager.getInstance().getWindows()) {
|
val windows = TermoraFrameManager.getInstance().getWindows()
|
||||||
|
|
||||||
|
for (frame in windows) {
|
||||||
frame.dispose()
|
frame.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Disposer.dispose(TermoraFrameManager.getInstance())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSettings() {
|
private fun loadSettings() {
|
||||||
@@ -243,7 +264,35 @@ class ApplicationRunner {
|
|||||||
|
|
||||||
UIManager.put("List.selectionArc", UIManager.getInt("Component.arc"))
|
UIManager.put("List.selectionArc", UIManager.getInt("Component.arc"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMacOSDock() {
|
||||||
|
val countDownLatch = CountDownLatch(1)
|
||||||
|
val cls = Class.forName("com.apple.eawt.Application")
|
||||||
|
val app = cls.getMethod("getApplication").invoke(null)
|
||||||
|
val addAppEventListener = cls.getMethod("addAppEventListener", SystemEventListener::class.java)
|
||||||
|
|
||||||
|
addAppEventListener.invoke(app, object : AppReopenedListener {
|
||||||
|
override fun appReopened(e: AppReopenedEvent) {
|
||||||
|
val manager = TermoraFrameManager.getInstance()
|
||||||
|
if (manager.getWindows().isEmpty()) {
|
||||||
|
manager.createWindow().isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当应用程序销毁时,驻守线程也可以退出了
|
||||||
|
Disposer.register(ApplicationScope.forApplicationScope(), object : Disposable {
|
||||||
|
override fun dispose() {
|
||||||
|
countDownLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 驻守线程,不然当所有窗口都关闭时,程序会自动退出
|
||||||
|
// wait application exit
|
||||||
|
Thread.ofPlatform().daemon(false)
|
||||||
|
.priority(Thread.MIN_PRIORITY)
|
||||||
|
.start { countDownLatch.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun printSystemInfo() {
|
private fun printSystemInfo() {
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class ApplicationScope private constructor() : Scope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun windowScopes(): List<WindowScope> {
|
fun windowScopes(): List<WindowScope> {
|
||||||
|
if (scopes.isEmpty()) return emptyList()
|
||||||
return scopes.values.toList()
|
return scopes.values.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
|
|
||||||
backgroundComBoBox.isEnabled = SystemInfo.isWindows
|
backgroundComBoBox.isEnabled = SystemInfo.isWindows || SystemInfo.isMacOS
|
||||||
backgroundImageTextField.isEditable = false
|
backgroundImageTextField.isEditable = false
|
||||||
backgroundImageTextField.trailingComponent = backgroundButton
|
backgroundImageTextField.trailingComponent = backgroundButton
|
||||||
backgroundImageTextField.text = FilenameUtils.getName(appearance.backgroundImage)
|
backgroundImageTextField.text = FilenameUtils.getName(appearance.backgroundImage)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.awt.Frame
|
|||||||
import java.awt.Window
|
import java.awt.Window
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.swing.JFrame
|
import javax.swing.JFrame
|
||||||
import javax.swing.JOptionPane
|
import javax.swing.JOptionPane
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
@@ -24,7 +25,7 @@ import kotlin.math.max
|
|||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
class TermoraFrameManager {
|
class TermoraFrameManager : Disposable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(TermoraFrameManager::class.java)
|
private val log = LoggerFactory.getLogger(TermoraFrameManager::class.java)
|
||||||
@@ -37,6 +38,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 isDisposed = AtomicBoolean(false)
|
||||||
private val isBackgroundRunning get() = Database.getDatabase().appearance.backgroundRunning
|
private val isBackgroundRunning get() = Database.getDatabase().appearance.backgroundRunning
|
||||||
|
|
||||||
fun createWindow(): TermoraFrame {
|
fun createWindow(): TermoraFrame {
|
||||||
@@ -80,6 +82,7 @@ class TermoraFrameManager {
|
|||||||
|
|
||||||
|
|
||||||
private fun registerCloseCallback(window: TermoraFrame) {
|
private fun registerCloseCallback(window: TermoraFrame) {
|
||||||
|
val manager = this
|
||||||
window.addWindowListener(object : WindowAdapter() {
|
window.addWindowListener(object : WindowAdapter() {
|
||||||
override fun windowClosed(e: WindowEvent) {
|
override fun windowClosed(e: WindowEvent) {
|
||||||
|
|
||||||
@@ -95,31 +98,49 @@ class TermoraFrameManager {
|
|||||||
Disposer.dispose(windowScope)
|
Disposer.dispose(windowScope)
|
||||||
|
|
||||||
val windowScopes = ApplicationScope.windowScopes()
|
val windowScopes = ApplicationScope.windowScopes()
|
||||||
|
if (windowScopes.isNotEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 如果已经没有 Window 域了,那么就可以退出程序了
|
// 如果已经没有 Window 域了,那么就可以退出程序了
|
||||||
if (windowScopes.isEmpty()) {
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
this@TermoraFrameManager.dispose()
|
Disposer.dispose(manager)
|
||||||
|
} else if (SystemInfo.isMacOS) {
|
||||||
|
// 如果 macOS 开启了后台运行,那么尽管所有窗口都没了,也不会退出
|
||||||
|
if (isBackgroundRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Disposer.dispose(manager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun windowClosing(e: WindowEvent) {
|
override fun windowClosing(e: WindowEvent) {
|
||||||
if (ApplicationScope.windowScopes().size == 1) {
|
if (ApplicationScope.windowScopes().size != 1) {
|
||||||
|
window.dispose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 Windows 开启了后台运行,那么最小化
|
||||||
if (SystemInfo.isWindows && isBackgroundRunning) {
|
if (SystemInfo.isWindows && isBackgroundRunning) {
|
||||||
// 最小化
|
// 最小化
|
||||||
window.extendedState = window.extendedState or JFrame.ICONIFIED
|
window.extendedState = window.extendedState or JFrame.ICONIFIED
|
||||||
// 隐藏
|
// 隐藏
|
||||||
window.isVisible = false
|
window.isVisible = false
|
||||||
} else {
|
return
|
||||||
if (OptionPane.showConfirmDialog(
|
}
|
||||||
|
|
||||||
|
// 如果 macOS 已经开启了后台运行,那么直接销毁,因为会有一个进程驻守
|
||||||
|
if (SystemInfo.isMacOS && isBackgroundRunning) {
|
||||||
|
window.dispose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val option = OptionPane.showConfirmDialog(
|
||||||
window,
|
window,
|
||||||
I18n.getString("termora.quit-confirm", Application.getName()),
|
I18n.getString("termora.quit-confirm", Application.getName()),
|
||||||
optionType = JOptionPane.YES_NO_OPTION,
|
optionType = JOptionPane.YES_NO_OPTION,
|
||||||
) == JOptionPane.YES_OPTION
|
)
|
||||||
) {
|
if (option == JOptionPane.YES_OPTION) {
|
||||||
window.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.dispose()
|
window.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +163,8 @@ class TermoraFrameManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dispose() {
|
override fun dispose() {
|
||||||
|
if (isDisposed.compareAndSet(false, true)) {
|
||||||
Disposer.dispose(ApplicationScope.forApplicationScope())
|
Disposer.dispose(ApplicationScope.forApplicationScope())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -152,6 +174,7 @@ class TermoraFrameManager {
|
|||||||
log.error(e.message, e)
|
log.error(e.message, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user