diff --git a/README.md b/README.md index 9b3ecdf..fb57a47 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ Linux-specific implemenations of the [integrations-api](https://github.com/crypt This project uses the following JVM properties: * `cryptomator.integrationsLinux.trayIconsDir` - specifies the directory from which svg images for the tray icon are loaded - +* `cryptomator.integrationsLinux.autoStartCmd` - specifies the command used for starting Cryptomator \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 9ec0149..dedd2b8 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,8 @@ +import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.cryptomator.integrations.revealpath.RevealPathService; import org.cryptomator.integrations.tray.TrayMenuController; +import org.cryptomator.linux.autostart.FreedesktopAutoStartService; import org.cryptomator.linux.keychain.KDEWalletKeychainAccess; import org.cryptomator.linux.keychain.SecretServiceKeychainAccess; import org.cryptomator.linux.revealpath.DBusSendRevealPathService; @@ -14,6 +16,7 @@ requires org.purejava.kwallet; requires de.swiesend.secretservice; + provides AutoStartProvider with FreedesktopAutoStartService; provides KeychainAccessProvider with SecretServiceKeychainAccess, KDEWalletKeychainAccess; provides RevealPathService with DBusSendRevealPathService; provides TrayMenuController with AppindicatorTrayMenuController; diff --git a/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java new file mode 100644 index 0000000..1c06d78 --- /dev/null +++ b/src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java @@ -0,0 +1,85 @@ +package org.cryptomator.linux.autostart; + +import org.cryptomator.integrations.autostart.AutoStartProvider; +import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Objects; + +/** + * Enables autostart for Linux desktop environments following the freedesktop standard. + *

+ * This service is based on version 0.5 of the freedesktop autostart-spec. + */ +@CheckAvailability +@OperatingSystem(OperatingSystem.Value.LINUX) +public class FreedesktopAutoStartService implements AutoStartProvider { + + private static final Logger LOG = LoggerFactory.getLogger(FreedesktopAutoStartService.class); + private static final String CMD_PROPERTY = "cryptomator.integrationsLinux.autoStartCmd"; + private static final String AUTOSTART_FILENAME = "Cryptomator.desktop"; + private static final String CONTENT_TEMPLATE = """ + [Desktop Entry] + Type=Application + Exec=%s + Hidden=false + NoDisplay=false + X-GNOME-Autostart-enabled=true + Name=Cryptomator + Comment=Created with %s + """; + + private final Path autostartFile; + private final String content; + private final boolean hasExecValue; + + public FreedesktopAutoStartService() { + var xdgConfigDirString = Objects.requireNonNullElse(System.getenv("XDG_CONFIG_HOME"), System.getProperty("user.home") + "/.config"); + this.autostartFile = Path.of(xdgConfigDirString, "autostart", AUTOSTART_FILENAME); + + var execValue = System.getProperty(CMD_PROPERTY); + if (execValue == null) { + LOG.debug("JVM property {} not set, using command path", CMD_PROPERTY); + execValue = ProcessHandle.current().info().command().orElse(""); + } + this.hasExecValue = execValue.isBlank(); + this.content = CONTENT_TEMPLATE.formatted(execValue, this.getClass().getName()); + } + + @Override + public synchronized void enable() throws ToggleAutoStartFailedException { + try { + Files.writeString(autostartFile, content, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new ToggleAutoStartFailedException("Failed to activate Cryptomator autostart for GNOME desktop environment.", e); + } + } + + @Override + public synchronized void disable() throws ToggleAutoStartFailedException { + try { + Files.deleteIfExists(autostartFile); + } catch (IOException e) { + throw new ToggleAutoStartFailedException("Failed to deactivate Cryptomator autostart for GNOME desktop environment.", e); + } + } + + @Override + public synchronized boolean isEnabled() { + return Files.exists(autostartFile); + } + + @CheckAvailability + public boolean isSupported() { + //TODO: might need to research which Desktop Environments support this + return hasExecValue && Files.exists(autostartFile.getParent()); + } + +} diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider b/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider new file mode 100644 index 0000000..7b57cfb --- /dev/null +++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.autostart.AutoStartProvider @@ -0,0 +1 @@ +org.cryptomator.linux.autostart.FreedesktopAutoStartService \ No newline at end of file diff --git a/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java new file mode 100644 index 0000000..d593628 --- /dev/null +++ b/src/test/java/org/cryptomator/linux/autostart/FreedesktopAutoStartIT.java @@ -0,0 +1,28 @@ +package org.cryptomator.linux.autostart; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class FreedesktopAutoStartIT { + + FreedesktopAutoStartService inTest = new FreedesktopAutoStartService(); + + @Test + @Order(1) + public void testAutostartEnable() { + Assertions.assertDoesNotThrow(() -> inTest.enable()); + Assertions.assertTrue(inTest.isEnabled()); + } + + + @Test + @Order(2) + public void testAutostartDisable() { + Assertions.assertDoesNotThrow(() -> inTest.disable()); + Assertions.assertFalse(inTest.isEnabled()); + } +}