Run on startup
This commit is contained in:
parent
44656c81d5
commit
4036febb70
3 changed files with 133 additions and 4 deletions
|
|
@ -19,6 +19,7 @@ import com.rukira.wowbackup.config.ConfigManager
|
|||
import com.rukira.wowbackup.logging.LoggingSetup
|
||||
import com.rukira.wowbackup.platform.OS
|
||||
import com.rukira.wowbackup.platform.Platform
|
||||
import com.rukira.wowbackup.platform.StartupManager
|
||||
import com.rukira.wowbackup.platform.WoWLocations
|
||||
import com.rukira.wowbackup.ui.Screen
|
||||
import com.rukira.wowbackup.ui.trayIconPainter
|
||||
|
|
@ -34,6 +35,9 @@ fun main() {
|
|||
ConfigManager.load()
|
||||
logger.info { "Config loaded. Configured: ${ConfigManager.isConfigured}" }
|
||||
|
||||
// Sync OS startup registration with persisted config
|
||||
StartupManager.setEnabled(ConfigManager.config.value.runAtStartup)
|
||||
|
||||
val detectedWoW = WoWLocations.findWoWInstall()
|
||||
if (detectedWoW != null) {
|
||||
logger.info { "Auto-detected WoW at: $detectedWoW" }
|
||||
|
|
@ -67,10 +71,6 @@ fun main() {
|
|||
isWindowVisible = true
|
||||
})
|
||||
Separator()
|
||||
Item("Backup Now", onClick = {
|
||||
BackupScheduler.triggerBackupNow()
|
||||
})
|
||||
Separator()
|
||||
Item("Quit", onClick = {
|
||||
BackupScheduler.stop()
|
||||
exitApplication()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
package com.rukira.wowbackup.platform
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import java.io.File
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
object StartupManager {
|
||||
|
||||
private const val APP_ID = "com.rukira.wowbackup"
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
val appPath = getAppPath()
|
||||
if (appPath == null) {
|
||||
logger.warn { "Cannot register startup: not running from a packaged app (dev mode?)" }
|
||||
return
|
||||
}
|
||||
|
||||
logger.info { "Setting launch-at-startup to $enabled (appPath=$appPath)" }
|
||||
|
||||
when (Platform.current) {
|
||||
OS.Mac -> if (enabled) enableMac(appPath) else disableMac()
|
||||
OS.Windows -> if (enabled) enableWindows(appPath) else disableWindows()
|
||||
else -> logger.warn { "Launch at startup is not supported on ${Platform.current}" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppPath(): String? = when (Platform.current) {
|
||||
OS.Mac -> {
|
||||
// java.home is e.g. /Applications/com.rukira.wowbackup.app/Contents/runtime/Contents/Home
|
||||
val javaHome = System.getProperty("java.home") ?: return null
|
||||
val marker = ".app/Contents"
|
||||
val idx = javaHome.indexOf(marker)
|
||||
if (idx < 0) null else javaHome.substring(0, idx + ".app".length)
|
||||
}
|
||||
OS.Windows -> {
|
||||
ProcessHandle.current().info().command().orElse(null)?.takeIf { it.endsWith(".exe", ignoreCase = true) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
// --- macOS: LaunchAgent plist ---
|
||||
|
||||
private val plistFile: File
|
||||
get() = File(System.getProperty("user.home"), "Library/LaunchAgents/$APP_ID.plist")
|
||||
|
||||
private fun enableMac(appPath: String) {
|
||||
val plist = """
|
||||
|<?xml version="1.0" encoding="UTF-8"?>
|
||||
|<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
| "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
|<plist version="1.0">
|
||||
|<dict>
|
||||
| <key>Label</key>
|
||||
| <string>$APP_ID</string>
|
||||
| <key>ProgramArguments</key>
|
||||
| <array>
|
||||
| <string>open</string>
|
||||
| <string>-a</string>
|
||||
| <string>$appPath</string>
|
||||
| </array>
|
||||
| <key>RunAtLoad</key>
|
||||
| <true/>
|
||||
|</dict>
|
||||
|</plist>
|
||||
""".trimMargin()
|
||||
|
||||
try {
|
||||
plistFile.parentFile.mkdirs()
|
||||
plistFile.writeText(plist)
|
||||
logger.info { "LaunchAgent plist written to ${plistFile.absolutePath}" }
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to write LaunchAgent plist" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableMac() {
|
||||
try {
|
||||
if (plistFile.exists()) {
|
||||
plistFile.delete()
|
||||
logger.info { "LaunchAgent plist removed" }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to remove LaunchAgent plist" }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Windows: Registry ---
|
||||
|
||||
private const val REG_KEY = """HKCU\Software\Microsoft\Windows\CurrentVersion\Run"""
|
||||
private const val REG_VALUE = "WoWBackup"
|
||||
|
||||
private fun enableWindows(appPath: String) {
|
||||
try {
|
||||
val process = ProcessBuilder(
|
||||
"reg", "add", REG_KEY, "/v", REG_VALUE, "/d", appPath, "/f"
|
||||
).redirectErrorStream(true).start()
|
||||
val exitCode = process.waitFor()
|
||||
if (exitCode == 0) {
|
||||
logger.info { "Registry startup entry added" }
|
||||
} else {
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
logger.error { "reg add failed (exit $exitCode): $output" }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to add registry startup entry" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableWindows() {
|
||||
try {
|
||||
val process = ProcessBuilder(
|
||||
"reg", "delete", REG_KEY, "/v", REG_VALUE, "/f"
|
||||
).redirectErrorStream(true).start()
|
||||
val exitCode = process.waitFor()
|
||||
if (exitCode == 0) {
|
||||
logger.info { "Registry startup entry removed" }
|
||||
} else {
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
// Exit code 1 with "not found" is expected when disabling and key doesn't exist
|
||||
logger.warn { "reg delete exited $exitCode: $output" }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to remove registry startup entry" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.rukira.wowbackup.config.AccentColor
|
|||
import com.rukira.wowbackup.config.AppConfig
|
||||
import com.rukira.wowbackup.config.ConfigManager
|
||||
import com.rukira.wowbackup.config.ThemeMode
|
||||
import com.rukira.wowbackup.platform.StartupManager
|
||||
import com.rukira.wowbackup.platform.WoWLocations
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -170,6 +171,7 @@ class ConfigViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
ConfigManager.save(_state.value.config)
|
||||
StartupManager.setEnabled(_state.value.config.runAtStartup)
|
||||
_state.update { it.copy(errors = emptyMap(), saveSuccess = true) }
|
||||
logger.info { "Configuration saved" }
|
||||
return true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue