From db603486b175ea9f34356508c98741f13a377c60 Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 14:45:49 +0300 Subject: [PATCH 1/6] refactor: Rename appName to appPackageName in AutoLaunchConfig for clarity --- .../kotlin/io/github/vinceglb/autolaunch/AutoLaunch.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..629e7ba 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 @@ -43,7 +43,7 @@ class AutoLaunch( } private val config = AutoLaunchConfig( - appName = appPackageName, + appPackageName = appPackageName, appPath = appPath ) From ffce7490a16d742b5f7ce2e47a6287bb42fe97d9 Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 14:47:33 +0300 Subject: [PATCH 2/6] refactor: Rename appName to appPackageName in AutoLaunchConfig for clarity --- .../io/github/vinceglb/autolaunch/PlatformAutoLaunch.kt | 2 +- .../github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt | 4 ++-- .../github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) 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/PlatformAutoLaunchMacOS.kt b/auto-launch/src/commonMain/kotlin/io/github/vinceglb/autolaunch/PlatformAutoLaunchMacOS.kt index f52ac59..567a8b3 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,7 +20,7 @@ internal class PlatformAutoLaunchMacOS(private val config: AutoLaunchConfig) : P | | | Label - | ${config.appName} + | ${config.appPackageName} | ProgramArguments | | ${config.appPath} 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..f434eb8 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 @@ -12,7 +12,7 @@ internal class PlatformAutoLaunchWindows( val value: String? = Advapi32Util.registryGetStringValue( WinReg.HKEY_CURRENT_USER, REGISTRY_KEY, - config.appName + config.appPackageName ) value == config.appPath } @@ -27,7 +27,7 @@ internal class PlatformAutoLaunchWindows( Advapi32Util.registrySetStringValue( WinReg.HKEY_CURRENT_USER, REGISTRY_KEY, - config.appName, + config.appPackageName, config.appPath ) } @@ -37,7 +37,7 @@ internal class PlatformAutoLaunchWindows( Advapi32Util.registryDeleteValue( WinReg.HKEY_CURRENT_USER, REGISTRY_KEY, - config.appName + config.appPackageName ) } } From 686cf13382ec130db30885d83b22c1f45b198ac0 Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 15:18:04 +0300 Subject: [PATCH 3/6] feat: Add Linux auto-launch implementation - Implemented auto-launch functionality for Linux systems. - Added methods to check if the application is installed by looking for the .desktop file in `/usr/share/applications`. - Added functionality to modify the .desktop file to include auto-start settings. - Enabled writing of the modified .desktop file to `~/.config/autostart` to activate auto-launch. - Provided enable and disable methods for managing auto-start. - Included comprehensive logging for each step to facilitate debugging. - Updated comments to explain the code logic and testing requirements. --- .../autolaunch/PlatformAutoLaunchLinux.kt | 102 +++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) 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..fad5cda 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,111 @@ 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) + } + } + + 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 From dea9d35a193618cf3b5161fca827a1ca83affe7d Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 16:22:23 +0300 Subject: [PATCH 4/6] feat: add method to detect if app is started via autostart Implemented a method to determine if the app is launched via autostart. Currently tested on Linux; Windows and macOS testing is still pending. Usage example: val isStartedViaAutostart = autoLaunch.isStartedViaAutostart() --- .../github/vinceglb/autolaunch/AutoLaunch.kt | 18 ++++++++++++++++++ .../autolaunch/PlatformAutoLaunchLinux.kt | 17 +++++++++++++++++ .../autolaunch/PlatformAutoLaunchMacOS.kt | 2 ++ .../autolaunch/PlatformAutoLaunchWindows.kt | 8 ++++---- sample/src/desktopMain/kotlin/Main.kt | 11 +++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) 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 629e7ba..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 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 fad5cda..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 @@ -31,6 +31,7 @@ internal class PlatformAutoLaunchLinux(private val config: AutoLaunchConfig) : P // 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", @@ -52,9 +53,24 @@ internal class PlatformAutoLaunchLinux(private val config: AutoLaunchConfig) : P } } + // 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") @@ -102,6 +118,7 @@ internal class PlatformAutoLaunchLinux(private val config: AutoLaunchConfig) : P println("Autostart desktop file not found at: ${autostartFile.path}") } } + } /* 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 567a8b3..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 @@ -24,6 +24,7 @@ internal class PlatformAutoLaunchMacOS(private val config: AutoLaunchConfig) : P | 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 f434eb8..9fd2ebb 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 @@ -14,7 +14,7 @@ internal class PlatformAutoLaunchWindows( REGISTRY_KEY, config.appPackageName ) - value == config.appPath + value == "${config.appPath} --autostart=true" } override suspend fun enable(): Unit = withContext(Dispatchers.IO) { @@ -23,12 +23,12 @@ internal class PlatformAutoLaunchWindows( 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.appPackageName, - config.appPath + "${config.appPath} --autostart=true" ) } @@ -49,4 +49,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 c119ffc..f94104d 100644 --- a/sample/src/desktopMain/kotlin/Main.kt +++ b/sample/src/desktopMain/kotlin/Main.kt @@ -35,6 +35,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() @@ -56,6 +58,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`.", From 3b01b389ff9f2b3931f5c8190c4c9fe8d5aad5ff Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 17:44:00 +0300 Subject: [PATCH 5/6] Fix Windows implementation and add `isStartedViaAutostart()` function - Corrected the Windows registry handling to avoid "file not found" errors. - Added a new function `isStartedViaAutostart()` to determine if the application was started with the autostart argument. - Verified the new implementation to ensure proper functionality. --- .../autolaunch/PlatformAutoLaunchWindows.kt | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) 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 9fd2ebb..2f3f9dc 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,23 +1,40 @@ 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.appPackageName - ) - value == "${config.appPath} --autostart=true" + try { + val value: String? = Advapi32Util.registryGetStringValue( + WinReg.HKEY_CURRENT_USER, + REGISTRY_KEY, + config.appPackageName + ) + value == "${config.appPath} --autostart=true" + } catch (e: Win32Exception) { + if (e.message?.contains("Le fichier spécifié est introuvable") == true) { + 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("Le fichier spécifié est introuvable: ${config.appPath}") + } + // Create the registry key if it doesn't exist if (!isRegistryKeyExists()) { Advapi32Util.registryCreateKey(WinReg.HKEY_CURRENT_USER, REGISTRY_KEY) From 3ff7d6887e970dbdb0381f1f5732d3ea52af3067 Mon Sep 17 00:00:00 2001 From: elyahou Date: Tue, 15 Oct 2024 17:48:53 +0300 Subject: [PATCH 6/6] Fix Windows implementation and add `isStartedViaAutostart()` function - Corrected the Windows registry handling to avoid "file not found" errors. - Added a new function `isStartedViaAutostart()` to determine if the application was started with the autostart argument. - Verified the new implementation to ensure proper functionality. --- .../github/vinceglb/autolaunch/PlatformAutoLaunchWindows.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2f3f9dc..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 @@ -20,7 +20,7 @@ internal class PlatformAutoLaunchWindows( ) value == "${config.appPath} --autostart=true" } catch (e: Win32Exception) { - if (e.message?.contains("Le fichier spécifié est introuvable") == true) { + if (e.errorCode == 2) { // ERROR_FILE_NOT_FOUND false } else { throw e @@ -32,7 +32,7 @@ internal class PlatformAutoLaunchWindows( // Check if the application path exists val appPath = File(config.appPath) if (!appPath.exists()) { - throw FileNotFoundException("Le fichier spécifié est introuvable: ${config.appPath}") + throw FileNotFoundException("File not found: ${config.appPath}") } // Create the registry key if it doesn't exist