diff --git a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/AutoLaunch.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/AutoLaunch.kt index 3f6b81b..8a6ba90 100644 --- a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/AutoLaunch.kt +++ b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/AutoLaunch.kt @@ -28,6 +28,24 @@ class AutoLaunch( */ suspend fun disable() = platformAutoLaunch.disable() + + /** + * Checks if the application was started with the '--autostart=true' argument. + * + * This method inspects the JVM input arguments to determine if the application + * was launched through the autostart mechanism, by verifying if the specific + * argument is present. + * + * @return true if the application was started with the '--autostart=true' argument, false otherwise. + */ + fun isStartedViaAutostart(): Boolean { + val inputArguments = System.getProperty("sun.java.command")?.split(" ") ?: emptyList() + println("Arguments fournis: $inputArguments") + return inputArguments.contains("--autostart=true") + } + + + companion object { /** * Get the app resolved executable path @@ -43,7 +61,7 @@ class AutoLaunch( } private val config = AutoLaunchConfig( - appName = appPackageName, + appPackageName = appPackageName, appPath = appPath ) diff --git a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunch.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunch.kt index 740a218..f2afda2 100644 --- a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunch.kt +++ b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunch.kt @@ -7,6 +7,6 @@ internal interface PlatformAutoLaunch { } internal data class AutoLaunchConfig( - val appName: String, + val appPackageName: String, val appPath: String, ) diff --git a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchLinux.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchLinux.kt index d8449c5..b66fb63 100644 --- a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchLinux.kt +++ b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchLinux.kt @@ -1,15 +1,128 @@ package io.github.vinceglb.autolaunch +import java.io.File + internal class PlatformAutoLaunchLinux(private val config: AutoLaunchConfig) : PlatformAutoLaunch { + + // Checks if the application is installed by looking for a corresponding .desktop file in /usr/share/applications + private fun isInstalled(): Boolean { + val appPackageName = config.appPackageName + val applicationsDirectory = File("/usr/share/applications") + val desktopFile = applicationsDirectory.listFiles()?.find { it.name.contains(appPackageName) && it.name.endsWith(".desktop") } + val isInstalled = desktopFile != null + println("Checking if app is installed: $isInstalled (package: $appPackageName)") + return isInstalled + } + + // Retrieves the content of the application's .desktop file from /usr/share/applications + private fun getDesktopFileContent(): String? { + val appPackageName = config.appPackageName + val applicationsDirectory = File("/usr/share/applications") + val desktopFile = applicationsDirectory.listFiles()?.find { it.name.contains(appPackageName) && it.name.endsWith(".desktop") } + return if (desktopFile != null && desktopFile.exists()) { + println("Reading desktop file content from: ${desktopFile.path}") + desktopFile.readText() + } else { + println("Desktop file not found for package: $appPackageName") + null + } + } + + // Modifies the content of the .desktop file to add necessary entries for autostart + private fun modifyDesktopFileContent(content: String): String { + val updatedContent = content.lines().toMutableList() + + // Ensure the file contains the necessary entries for autostart + val entries = listOf( + "X-GNOME-Autostart-enabled=true", + "StartupNotify=false", + "X-GNOME-Autostart-Delay=10", + "X-MATE-Autostart-Delay=10", + "X-KDE-autostart-after=panel" + ) + + entries.forEach { entry -> + val key = entry.substringBefore("=") + val existingIndex = updatedContent.indexOfFirst { it.startsWith(key) } + if (existingIndex != -1) { + println("Updating existing entry: $key") + updatedContent[existingIndex] = entry + } else { + println("Adding new entry: $entry") + updatedContent.add(entry) + } + } + + // Add --autostart=true to the Exec line + val execIndex = updatedContent.indexOfFirst { it.startsWith("Exec=") } + if (execIndex != -1) { + val execLine = updatedContent[execIndex] + if (!execLine.contains("--autostart=true")) { + println("Adding --autostart=true to the Exec line") + updatedContent[execIndex] = "$execLine --autostart=true" + } + } else { + // If Exec line does not exist, create a new one + println("Adding new Exec line with --autostart=true") + updatedContent.add("Exec=${config.appPath} --autostart=true") + } + + return updatedContent.joinToString("\n") + } + + + // Writes the modified .desktop file to the ~/.config/autostart directory + private fun writeAutostartDesktopFile(content: String) { + val autostartDirectory = File(System.getProperty("user.home"), ".config/autostart") + if (!autostartDirectory.exists()) { + println("Creating autostart directory at: ${autostartDirectory.path}") + autostartDirectory.mkdirs() + } + val autostartFile = File(autostartDirectory, "${config.appPackageName}.desktop") + println("Writing autostart desktop file to: ${autostartFile.path}") + autostartFile.writeText(content) + } + + // Checks if autostart is enabled by looking for the .desktop file in ~/.config/autostart override suspend fun isEnabled(): Boolean { - TODO("Not yet implemented") + val autostartFile = File(System.getProperty("user.home"), ".config/autostart/${config.appPackageName}.desktop") + val isEnabled = autostartFile.exists() + println("Checking if autostart is enabled: $isEnabled (path: ${autostartFile.path})") + return isEnabled } + // Enables autostart by copying and modifying the application's .desktop file override suspend fun enable() { - TODO("Not yet implemented") + println("Enabling autostart for app: ${config.appPackageName}") + if (isInstalled()) { + val desktopFileContent = getDesktopFileContent() + if (desktopFileContent != null) { + val modifiedContent = modifyDesktopFileContent(desktopFileContent) + writeAutostartDesktopFile(modifiedContent) + } else { + println("Failed to enable autostart: desktop file content is null") + } + } else { + println("Failed to enable autostart: app is not installed") + } } + // Disables autostart by deleting the .desktop file in ~/.config/autostart override suspend fun disable() { - TODO("Not yet implemented") + println("Disabling autostart for app: ${config.appPackageName}") + val autostartFile = File(System.getProperty("user.home"), ".config/autostart/${config.appPackageName}.desktop") + if (autostartFile.exists()) { + println("Deleting autostart desktop file at: ${autostartFile.path}") + autostartFile.delete() + } else { + println("Autostart desktop file not found at: ${autostartFile.path}") + } } + } + +/* +To test this functionality, it is necessary to create a .deb package and install it on the system. +Without installation via a .deb, no .desktop file will be automatically created in /usr/share/applications, +which makes it impossible to verify the presence of the application and enable autostart. +*/ \ No newline at end of file diff --git a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt index f52ac59..97d6fa3 100644 --- a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt +++ b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt @@ -6,7 +6,7 @@ import java.io.File internal class PlatformAutoLaunchMacOS(private val config: AutoLaunchConfig) : PlatformAutoLaunch { private val file = - File("${System.getProperty("user.home")}/Library/LaunchAgents/${config.appName}.plist") + File("${System.getProperty("user.home")}/Library/LaunchAgents/${config.appPackageName}.plist") override suspend fun isEnabled(): Boolean = withContext(Dispatchers.IO) { file.exists() @@ -20,10 +20,11 @@ internal class PlatformAutoLaunchMacOS(private val config: AutoLaunchConfig) : P | | | Label - | ${config.appName} + | ${config.appPackageName} | ProgramArguments | | ${config.appPath} + | --autostart=true | | RunAtLoad | @@ -36,4 +37,5 @@ internal class PlatformAutoLaunchMacOS(private val config: AutoLaunchConfig) : P override suspend fun disable(): Unit = withContext(Dispatchers.IO) { file.delete() } + } diff --git a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt index 73f8a97..d9b87ab 100644 --- a/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt +++ b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt @@ -1,34 +1,51 @@ package io.github.vinceglb.autolaunch import com.sun.jna.platform.win32.Advapi32Util +import com.sun.jna.platform.win32.Win32Exception import com.sun.jna.platform.win32.WinReg import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileNotFoundException internal class PlatformAutoLaunchWindows( private val config: AutoLaunchConfig ) : PlatformAutoLaunch { override suspend fun isEnabled(): Boolean = withContext(Dispatchers.IO) { - val value: String? = Advapi32Util.registryGetStringValue( - WinReg.HKEY_CURRENT_USER, - REGISTRY_KEY, - config.appName - ) - value == config.appPath + try { + val value: String? = Advapi32Util.registryGetStringValue( + WinReg.HKEY_CURRENT_USER, + REGISTRY_KEY, + config.appPackageName + ) + value == "${config.appPath} --autostart=true" + } catch (e: Win32Exception) { + if (e.errorCode == 2) { // ERROR_FILE_NOT_FOUND + false + } else { + throw e + } + } } override suspend fun enable(): Unit = withContext(Dispatchers.IO) { + // Check if the application path exists + val appPath = File(config.appPath) + if (!appPath.exists()) { + throw FileNotFoundException("File not found: ${config.appPath}") + } + // Create the registry key if it doesn't exist if (!isRegistryKeyExists()) { Advapi32Util.registryCreateKey(WinReg.HKEY_CURRENT_USER, REGISTRY_KEY) } - // Set the value + // Set the value with the autostart argument Advapi32Util.registrySetStringValue( WinReg.HKEY_CURRENT_USER, REGISTRY_KEY, - config.appName, - config.appPath + config.appPackageName, + "${config.appPath} --autostart=true" ) } @@ -37,7 +54,7 @@ internal class PlatformAutoLaunchWindows( Advapi32Util.registryDeleteValue( WinReg.HKEY_CURRENT_USER, REGISTRY_KEY, - config.appName + config.appPackageName ) } } @@ -49,4 +66,4 @@ internal class PlatformAutoLaunchWindows( private companion object { private const val REGISTRY_KEY = "Software\\Microsoft\\Windows\\CurrentVersion\\Run" } -} +} \ No newline at end of file diff --git a/sample/src/desktopMain/kotlin/Main.kt b/sample/src/desktopMain/kotlin/Main.kt index 431cefb..342f98c 100644 --- a/sample/src/desktopMain/kotlin/Main.kt +++ b/sample/src/desktopMain/kotlin/Main.kt @@ -36,6 +36,8 @@ fun App() { val scope = rememberCoroutineScope() val autoLaunch = remember { AutoLaunch(appPackageName = "com.autolaunch.sample") } var isEnabled by remember { mutableStateOf(false) } + val isStartedViaAutostart = autoLaunch.isStartedViaAutostart() + LaunchedEffect(Unit) { isEnabled = autoLaunch.isEnabled() @@ -57,6 +59,15 @@ fun App() { modifier = Modifier.padding(bottom = 16.dp) ) + + Text( + text = "The application was ${if (!isStartedViaAutostart) "not" else ""} started via autostart.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(bottom = 16.dp) + ) + + if (!AutoLaunch.isRunningFromDistributable) { Text( text = "You are in development.\nAutoLaunch works only when app is distributed.\nRun the app using `./gradlew runDistributable`.",