mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
chore: Windows MSIX
This commit is contained in:
@@ -6,6 +6,7 @@ enum class AppLayout {
|
||||
*/
|
||||
Zip,
|
||||
Exe,
|
||||
Appx,
|
||||
|
||||
/**
|
||||
* macOS
|
||||
|
||||
@@ -138,6 +138,8 @@ object Application {
|
||||
return AppLayout.Exe
|
||||
} else if ("zip" == layout) {
|
||||
return AppLayout.Zip
|
||||
} else if ("appx" == layout) {
|
||||
return AppLayout.Appx
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,8 @@ class ApplicationInitializr {
|
||||
|
||||
fun run() {
|
||||
|
||||
// 由于 macOS 签名和公证问题,依赖二进制依赖会单独在一个文件夹
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
setupNativeLibraries()
|
||||
}
|
||||
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
System.setProperty("apple.awt.application.name", Application.getName())
|
||||
}
|
||||
// 依赖二进制依赖会单独在一个文件夹
|
||||
setupNativeLibraries()
|
||||
|
||||
// 设置 tinylog
|
||||
setupTinylog()
|
||||
@@ -31,6 +25,11 @@ class ApplicationInitializr {
|
||||
// 检查是否单例
|
||||
checkSingleton()
|
||||
|
||||
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
System.setProperty("apple.awt.application.name", Application.getName())
|
||||
}
|
||||
|
||||
// 启动
|
||||
val runtime = measureTimeMillis { ApplicationRunner().run() }
|
||||
val log = LoggerFactory.getLogger(javaClass)
|
||||
@@ -42,23 +41,29 @@ class ApplicationInitializr {
|
||||
|
||||
|
||||
private fun setupNativeLibraries() {
|
||||
if (!SystemUtils.IS_OS_MAC_OSX) {
|
||||
return
|
||||
}
|
||||
|
||||
val appPath = Application.getAppPath()
|
||||
if (StringUtils.isBlank(appPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
val contents = File(appPath).parentFile?.parentFile ?: return
|
||||
var contents = File(appPath)
|
||||
if (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) {
|
||||
contents = contents.parentFile?.parentFile ?: return
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
contents = File(contents, "lib")
|
||||
}
|
||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||
contents = contents.parentFile ?: return
|
||||
}
|
||||
|
||||
val dylib = FileUtils.getFile(contents, "app", "dylib")
|
||||
if (!dylib.exists()) {
|
||||
if (dylib.exists().not()) {
|
||||
return
|
||||
}
|
||||
|
||||
val jna = FileUtils.getFile(dylib, "jna")
|
||||
if (jna.exists()) {
|
||||
System.setProperty("jna.nounpack", "true")
|
||||
System.setProperty("jna.boot.library.path", jna.absolutePath)
|
||||
}
|
||||
|
||||
@@ -72,7 +77,10 @@ class ApplicationInitializr {
|
||||
System.setProperty("jSerialComm.library.path", jSerialComm.absolutePath)
|
||||
}
|
||||
|
||||
val restart4j = FileUtils.getFile(dylib, "restart4j", "restarter")
|
||||
val restart4j = FileUtils.getFile(
|
||||
dylib, "restart4j",
|
||||
if (SystemUtils.IS_OS_WINDOWS) "restarter.exe" else "restarter"
|
||||
)
|
||||
if (restart4j.exists()) {
|
||||
System.setProperty("restarter.path", restart4j.absolutePath)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.sun.jna.platform.win32.WinDef.*
|
||||
import com.sun.jna.platform.win32.WinError
|
||||
import com.sun.jna.platform.win32.WinUser.*
|
||||
import com.sun.jna.platform.win32.Wtsapi32
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileLock
|
||||
import java.nio.file.Paths
|
||||
@@ -95,7 +94,6 @@ class ApplicationSingleton private constructor() : Disposable {
|
||||
private class Win32HelperWindow private constructor() : Runnable {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(Win32HelperWindow::class.java)
|
||||
private val WindowClass = "${Application.getName()}HelperWindowClass"
|
||||
private val WindowName =
|
||||
"${Application.getName()} hidden helper window, used only to catch the windows events"
|
||||
@@ -166,24 +164,15 @@ class ApplicationSingleton private constructor() : Disposable {
|
||||
override fun callback(hwnd: HWND, uMsg: Int, wParam: WPARAM, lParam: LPARAM): LRESULT {
|
||||
when (uMsg) {
|
||||
WM_CREATE -> {
|
||||
if (log.isDebugEnabled) {
|
||||
log.debug("win32 helper window created")
|
||||
}
|
||||
return LRESULT()
|
||||
}
|
||||
|
||||
TICK -> {
|
||||
if (log.isDebugEnabled) {
|
||||
log.debug("win32 helper window tick")
|
||||
}
|
||||
onTick()
|
||||
return LRESULT()
|
||||
}
|
||||
|
||||
WM_DESTROY -> {
|
||||
if (log.isDebugEnabled) {
|
||||
log.debug("win32 helper window destroyed")
|
||||
}
|
||||
User32.INSTANCE.PostQuitMessage(0)
|
||||
return LRESULT()
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.Component
|
||||
import java.awt.Window
|
||||
import java.awt.event.WindowEvent
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.SwingUtilities
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
@@ -121,11 +123,22 @@ class TermoraRestarter {
|
||||
|
||||
val instance = TermoraFrameManager.getInstance()
|
||||
for (window in instance.getWindows()) {
|
||||
disposeChildren(window)
|
||||
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSED))
|
||||
}
|
||||
Disposer.dispose(instance)
|
||||
}
|
||||
|
||||
private fun disposeChildren(window: Window) {
|
||||
for (win in Window.getWindows()) {
|
||||
if (win is JDialog) {
|
||||
if (win.owner == window) {
|
||||
disposeChildren(win)
|
||||
}
|
||||
win.dispatchEvent(WindowEvent(win, WindowEvent.WINDOW_CLOSED))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIsSupported(): Boolean {
|
||||
val appPath = Application.getAppPath()
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package app.termora.plugin.internal.update
|
||||
|
||||
import app.termora.Application
|
||||
import app.termora.*
|
||||
import app.termora.Application.httpClient
|
||||
import app.termora.ApplicationScope
|
||||
import app.termora.Disposable
|
||||
import app.termora.UpdaterManager
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.Request
|
||||
@@ -32,6 +29,7 @@ internal class Updater private constructor() : Disposable {
|
||||
private val updaterManager get() = UpdaterManager.getInstance()
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private var isRemindMeNextTime = false
|
||||
private val disabledUpdater get() = Application.getLayout() == AppLayout.Appx
|
||||
|
||||
/**
|
||||
* 安装包位置
|
||||
@@ -39,6 +37,14 @@ internal class Updater private constructor() : Disposable {
|
||||
private var pkg: LatestPkg? = null
|
||||
|
||||
fun scheduleUpdate() {
|
||||
|
||||
if (disabledUpdater) {
|
||||
if (coroutineScope.isActive) {
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
// 启动 3 分钟后才是检查
|
||||
if (Application.isUnknownVersion().not()) {
|
||||
@@ -66,6 +72,9 @@ internal class Updater private constructor() : Disposable {
|
||||
|
||||
private fun checkUpdate() {
|
||||
|
||||
// Windows 应用商店
|
||||
if (disabledUpdater) return
|
||||
|
||||
val latestVersion = updaterManager.fetchLatestVersion()
|
||||
if (latestVersion.isSelf) {
|
||||
return
|
||||
|
||||
40
src/main/resources/AppxManifest.xml
Normal file
40
src/main/resources/AppxManifest.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="TermoraDev.Termora"
|
||||
Publisher="CN=C804E131-4368-4BF7-9E7F-95C681AD0AAC"
|
||||
Version="@version@.0"
|
||||
ProcessorArchitecture="@architecture@"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Termora</DisplayName>
|
||||
<PublisherDisplayName>TermoraDev</PublisherDisplayName>
|
||||
<Logo>icons\termora.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-US"/>
|
||||
</Resources>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0"/>
|
||||
</Dependencies>
|
||||
|
||||
<Applications>
|
||||
<Application Id="Termora" Executable="Termora.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="Termora"
|
||||
Description="Termora is a cross-platform terminal emulator and SSH client, available on Windows, macOS, and Linux"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="icons\termora_150x150.png"
|
||||
Square44x44Logo="icons\termora_44x44.png"/>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
</Capabilities>
|
||||
</Package>
|
||||
BIN
src/main/resources/icons/termora_150x150.png
Normal file
BIN
src/main/resources/icons/termora_150x150.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/main/resources/icons/termora_44x44.png
Normal file
BIN
src/main/resources/icons/termora_44x44.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user