From 12a21bbb71fb2169450bf70861da1ba38a65ff1b Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 17 Feb 2022 02:37:25 +0100 Subject: [PATCH 01/83] Added "--version" (short: "-v") --- .../org/cryptomator/launcher/Cryptomator.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 5502d804a..337d1af22 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -20,7 +20,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.CountDownLatch; @@ -54,6 +54,21 @@ public class Cryptomator { } public static void main(String[] args) { + var printVersion = Optional.ofNullable(args) // + .stream() //Streams either one element (the args-array) or zero elements + .flatMap(Arrays::stream) // + .anyMatch(arg -> "-v".equals(arg) || "--version".equals(arg)); + + if(printVersion) { + var appVer = Optional.ofNullable(System.getProperty("cryptomator.appVersion")); + var buildNumber = Optional.ofNullable(System.getProperty("cryptomator.buildNumber")); + + //Reduce noise for parsers by using System.out directly + System.out.printf("Cryptomator version %s (build %s)%n", appVer.orElse(""), buildNumber.orElse("")); + System.exit(0); + return; + } + int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); LOG.info("Exit {}", exitCode); System.exit(exitCode); // end remaining non-daemon threads. From a404740cebf43d7ee1f2b2c46a5497cd0b49cc8d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Mar 2022 17:35:48 +0100 Subject: [PATCH 02/83] Updated to integrations-api 1.1.0-beta2 --- pom.xml | 8 +- src/main/java/module-info.java | 11 +- .../cryptomator/common/PluginClassLoader.java | 1 + .../common/keychain/KeychainManager.java | 10 +- .../common/keychain/KeychainModule.java | 22 +--- .../ui/launcher/UiLauncherModule.java | 13 +- .../GeneralPreferencesController.java | 9 +- .../ui/traymenu/AwtTrayMenuController.java | 72 +++++++++++ .../ui/traymenu/TrayIconController.java | 51 -------- .../ui/traymenu/TrayImageFactory.java | 35 ------ .../ui/traymenu/TrayMenuComponent.java | 21 ++-- .../ui/traymenu/TrayMenuController.java | 119 +++++++++--------- .../ui/traymenu/TrayMenuModule.java | 18 +++ 13 files changed, 195 insertions(+), 195 deletions(-) create mode 100644 src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java delete mode 100644 src/main/java/org/cryptomator/ui/traymenu/TrayIconController.java delete mode 100644 src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java create mode 100644 src/main/java/org/cryptomator/ui/traymenu/TrayMenuModule.java diff --git a/pom.xml b/pom.xml index 8975b8693..2c18c2170 100644 --- a/pom.xml +++ b/pom.xml @@ -28,10 +28,10 @@ 2.3.1 - 1.1.0-beta1 - 1.0.0 - 1.0.0 - 1.0.1 + 1.1.0-beta2 + 1.1.0-beta1 + 1.1.0-beta1 + 1.1.0-beta1 1.3.3 1.3.3 1.2.6 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f13308be5..a8d85c27f 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,7 +1,5 @@ -import org.cryptomator.integrations.autostart.AutoStartProvider; -import org.cryptomator.integrations.keychain.KeychainAccessProvider; -import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; +import org.cryptomator.integrations.tray.TrayMenuController; +import org.cryptomator.ui.traymenu.AwtTrayMenuController; module org.cryptomator.desktop { requires org.cryptomator.cryptofs; @@ -29,10 +27,7 @@ module org.cryptomator.desktop { requires logback.classic; requires logback.core; - uses AutoStartProvider; - uses KeychainAccessProvider; - uses TrayIntegrationProvider; - uses UiAppearanceProvider; + provides TrayMenuController with AwtTrayMenuController; opens org.cryptomator.common.settings to com.google.gson; diff --git a/src/main/java/org/cryptomator/common/PluginClassLoader.java b/src/main/java/org/cryptomator/common/PluginClassLoader.java index 16932923b..28c73cd2c 100644 --- a/src/main/java/org/cryptomator/common/PluginClassLoader.java +++ b/src/main/java/org/cryptomator/common/PluginClassLoader.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +@Deprecated // to be moved to integrations-api 1.1.0 @Singleton public class PluginClassLoader extends URLClassLoader { diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java index d6adadfe4..e0269348a 100644 --- a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java +++ b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java @@ -44,9 +44,9 @@ public class KeychainManager implements KeychainAccessProvider { } @Override + @Deprecated public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException { - getKeychainOrFail().storePassphrase(key, passphrase); - setPassphraseStored(key, true); + storePassphrase(key, null, passphrase); } @Override @@ -69,11 +69,9 @@ public class KeychainManager implements KeychainAccessProvider { } @Override + @Deprecated public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException { - if (isPassphraseStored(key)) { - getKeychainOrFail().changePassphrase(key, passphrase); - setPassphraseStored(key, true); - } + changePassphrase(key, null, passphrase); } @Override diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainModule.java b/src/main/java/org/cryptomator/common/keychain/KeychainModule.java index 6356c4966..63749b445 100644 --- a/src/main/java/org/cryptomator/common/keychain/KeychainModule.java +++ b/src/main/java/org/cryptomator/common/keychain/KeychainModule.java @@ -2,42 +2,30 @@ package org.cryptomator.common.keychain; import dagger.Module; import dagger.Provides; -import org.cryptomator.common.PluginClassLoader; import org.cryptomator.common.settings.Settings; import org.cryptomator.integrations.keychain.KeychainAccessProvider; import javax.inject.Singleton; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectExpression; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.List; @Module public class KeychainModule { @Provides @Singleton - static Set> provideAvailableKeychainAccessProviderFactories(PluginClassLoader classLoader) { - return ServiceLoader.load(KeychainAccessProvider.class, classLoader).stream().collect(Collectors.toUnmodifiableSet()); + static List provideSupportedKeychainAccessProviders() { + return KeychainAccessProvider.get().toList(); } @Provides @Singleton - static Set provideSupportedKeychainAccessProviders(Set> availableFactories) { - return availableFactories.stream() // - .map(ServiceLoader.Provider::get) // - .filter(KeychainAccessProvider::isSupported) // - .collect(Collectors.toUnmodifiableSet()); - } - - @Provides - @Singleton - static ObjectExpression provideKeychainAccessProvider(Settings settings, Set providers) { + static ObjectExpression provideKeychainAccessProvider(Settings settings, List providers) { return Bindings.createObjectBinding(() -> { var selectedProviderClass = settings.keychainProvider().get(); var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny(); - var fallbackProvider = providers.stream().findAny().orElse(null); + var fallbackProvider = providers.stream().findFirst().orElse(null); return selectedProvider.orElse(fallbackProvider); }, settings.keychainProvider()); } diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java index c30efa30e..64b05bdab 100644 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java +++ b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java @@ -34,21 +34,20 @@ public abstract class UiLauncherModule { @Provides @Singleton - static Optional provideAppearanceProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst(); + static Optional provideAppearanceProvider() { + return UiAppearanceProvider.get(); } @Provides @Singleton - static Optional provideAutostartProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst(); + static Optional provideAutostartProvider() { + return AutoStartProvider.get(); } - @Provides @Singleton - static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst(); + static Optional provideTrayIntegrationProvider() { + return TrayIntegrationProvider.get(); } @Provides diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index fafea3f2f..424134a3e 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -27,6 +27,7 @@ import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.stage.Stage; import javafx.util.StringConverter; +import java.util.List; import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; @@ -46,7 +47,7 @@ public class GeneralPreferencesController implements FxController { private final ResourceBundle resourceBundle; private final Application application; private final Environment environment; - private final Set keychainAccessProviders; + private final List keychainAccessProviders; private final ErrorComponent.Builder errorComponent; public ChoiceBox themeChoiceBox; public ChoiceBox keychainBackendChoiceBox; @@ -61,7 +62,7 @@ public class GeneralPreferencesController implements FxController { @Inject - GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) { + GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, List keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) { this.window = window; this.settings = settings; this.trayMenuInitialized = trayMenu.isInitialized(); @@ -204,9 +205,9 @@ public class GeneralPreferencesController implements FxController { private static class KeychainProviderClassNameConverter extends StringConverter { - private final Set keychainAccessProviders; + private final List keychainAccessProviders; - public KeychainProviderClassNameConverter(Set keychainAccessProviders) { + public KeychainProviderClassNameConverter(List keychainAccessProviders) { this.keychainAccessProviders = keychainAccessProviders; } diff --git a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java new file mode 100644 index 000000000..7c1a0998b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java @@ -0,0 +1,72 @@ +package org.cryptomator.ui.traymenu; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.integrations.common.Priority; +import org.cryptomator.integrations.tray.ActionItem; +import org.cryptomator.integrations.tray.SeparatorItem; +import org.cryptomator.integrations.tray.SubMenuItem; +import org.cryptomator.integrations.tray.TrayMenuItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.AWTException; +import java.awt.Menu; +import java.awt.MenuItem; +import java.awt.PopupMenu; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@Priority(Priority.FALLBACK) +public class AwtTrayMenuController implements org.cryptomator.integrations.tray.TrayMenuController { + + private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class); + + private TrayIcon trayIcon; + private PopupMenu menu = new PopupMenu(); + + @Override + public void showTrayIcon(InputStream rawImageData, Runnable defaultAction, String tooltip) throws IOException { + var image = Toolkit.getDefaultToolkit().createImage(rawImageData.readAllBytes()); + trayIcon = new TrayIcon(image, tooltip, menu); + + trayIcon.setImageAutoSize(true); + if (SystemUtils.IS_OS_WINDOWS) { + trayIcon.addActionListener(evt -> defaultAction.run()); + } + + try { + SystemTray.getSystemTray().add(trayIcon); + LOG.debug("initialized tray icon"); + } catch (AWTException e) { + LOG.error("Error adding tray icon", e); + } + } + + @Override + public void updateTrayMenu(List items) { + menu.removeAll(); + addChildren(menu, items); + } + + private void addChildren(Menu menu, List items) { + for (var item : items) { + // TODO: use Pattern Matching for switch, once available + if (item instanceof ActionItem a) { + var menuItem = new MenuItem(a.title()); + menuItem.addActionListener(evt -> a.action().run()); + menu.add(menuItem); + } else if (item instanceof SeparatorItem) { + menu.addSeparator(); + } else if (item instanceof SubMenuItem s) { + var submenu = new Menu(s.title()); + addChildren(submenu, s.items()); + menu.add(submenu); + } + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayIconController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayIconController.java deleted file mode 100644 index 2c176df76..000000000 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayIconController.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cryptomator.ui.traymenu; - -import com.google.common.base.Preconditions; -import org.apache.commons.lang3.SystemUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.awt.AWTException; -import java.awt.SystemTray; -import java.awt.TrayIcon; - -@TrayMenuScoped -public class TrayIconController { - - private static final Logger LOG = LoggerFactory.getLogger(TrayIconController.class); - - private final TrayMenuController trayMenuController; - private final TrayIcon trayIcon; - private volatile boolean initialized; - - @Inject - TrayIconController(TrayImageFactory imageFactory, TrayMenuController trayMenuController) { - this.trayMenuController = trayMenuController; - this.trayIcon = new TrayIcon(imageFactory.loadImage(), "Cryptomator", trayMenuController.getMenu()); - } - - public synchronized void initializeTrayIcon() throws IllegalStateException { - Preconditions.checkState(!initialized); - - trayIcon.setImageAutoSize(true); - if (SystemUtils.IS_OS_WINDOWS) { - trayIcon.addActionListener(trayMenuController::showMainWindow); - } - - try { - SystemTray.getSystemTray().add(trayIcon); - LOG.debug("initialized tray icon"); - } catch (AWTException e) { - LOG.error("Error adding tray icon", e); - } - - trayMenuController.initTrayMenu(); - - this.initialized = true; - } - - public boolean isInitialized() { - return initialized; - } -} diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java b/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java deleted file mode 100644 index aa55ca766..000000000 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayImageFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cryptomator.ui.traymenu; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.integrations.uiappearance.Theme; -import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; - -import javax.inject.Inject; -import java.awt.Image; -import java.awt.Toolkit; -import java.util.Optional; - -@TrayMenuScoped -class TrayImageFactory { - - private final Optional appearanceProvider; - - @Inject - TrayImageFactory(Optional appearanceProvider) { - this.appearanceProvider = appearanceProvider; - } - - public Image loadImage() { - String resourceName = SystemUtils.IS_OS_MAC_OSX ? getMacResourceName() : getWinOrLinuxResourceName(); - return Toolkit.getDefaultToolkit().getImage(getClass().getResource(resourceName)); - } - - private String getMacResourceName() { - return "/img/tray_icon_mac.png"; - } - - private String getWinOrLinuxResourceName() { - return "/img/tray_icon.png"; - } - -} diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java index c4cbfd456..e50269007 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java @@ -5,28 +5,34 @@ *******************************************************************************/ package org.cryptomator.ui.traymenu; -import dagger.Lazy; +import com.google.common.base.Preconditions; import dagger.Subcomponent; +import org.cryptomator.integrations.tray.TrayMenuController; + import java.awt.SystemTray; +import java.util.Optional; @TrayMenuScoped -@Subcomponent +@Subcomponent(modules = {TrayMenuModule.class}) public interface TrayMenuComponent { - Lazy trayIconController(); + Optional trayMenuController(); + + org.cryptomator.ui.traymenu.TrayMenuController trayMenuController2(); // TODO tmp name /** * @return true if a tray icon can be installed */ default boolean isSupported() { - return SystemTray.isSupported(); + // TODO add isSupported to API and move SystemTray.isSupported() to impl + return trayMenuController().isPresent() && SystemTray.isSupported(); } /** * @return true if a tray icon has been installed */ default boolean isInitialized() { - return isSupported() && trayIconController().get().isInitialized(); + return isSupported() && trayMenuController2().isInitialized(); } /** @@ -35,8 +41,9 @@ public interface TrayMenuComponent { * @throws IllegalStateException If already added */ default void initializeTrayIcon() throws IllegalStateException { - assert isSupported(); - trayIconController().get().initializeTrayIcon(); + Preconditions.checkState(isSupported(), "system tray not supported"); + + trayMenuController2().initTrayMenu(); } @Subcomponent.Builder diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index 4ce3808c4..8189406d2 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -1,6 +1,12 @@ package org.cryptomator.ui.traymenu; +import com.google.common.base.Preconditions; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.integrations.tray.ActionItem; +import org.cryptomator.integrations.tray.SeparatorItem; +import org.cryptomator.integrations.tray.SubMenuItem; +import org.cryptomator.integrations.tray.TrayMenuItem; import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.launcher.AppLifecycleListener; import org.cryptomator.ui.launcher.FxApplicationStarter; @@ -10,44 +16,61 @@ import javax.inject.Inject; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; -import java.awt.Menu; -import java.awt.MenuItem; -import java.awt.PopupMenu; -import java.awt.event.ActionEvent; +import java.awt.Image; +import java.awt.Toolkit; import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; import java.util.EventObject; +import java.util.List; import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Consumer; @TrayMenuScoped -class TrayMenuController { +public class TrayMenuController { + + private static final String TRAY_ICON_MAC = "/img/tray_icon_mac.png"; + private static final String TRAY_ICON = "/img/tray_icon.png"; private final ResourceBundle resourceBundle; private final AppLifecycleListener appLifecycle; private final FxApplicationStarter fxApplicationStarter; private final ObservableList vaults; - private final PopupMenu menu; + private final org.cryptomator.integrations.tray.TrayMenuController trayMenu; + + private volatile boolean initialized; @Inject - TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults) { + TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults, Optional trayMenu) { this.resourceBundle = resourceBundle; this.appLifecycle = appLifecycle; this.fxApplicationStarter = fxApplicationStarter; this.vaults = vaults; - this.menu = new PopupMenu(); + this.trayMenu = trayMenu.orElse(null); } - public PopupMenu getMenu() { - return menu; - } + public synchronized void initTrayMenu() { + Preconditions.checkState(!initialized, "tray icon already initialized"); - public void initTrayMenu() { vaults.addListener(this::vaultListChanged); vaults.forEach(v -> { v.displayNameProperty().addListener(this::vaultListChanged); }); rebuildMenu(); + + try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) { + trayMenu.showTrayIcon(image, this::showMainWindow, "Cryptomator"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to load embedded resource", e); + } + + initialized = true; + } + + public boolean isInitialized() { + return initialized; } private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { @@ -56,58 +79,42 @@ class TrayMenuController { } private void rebuildMenu() { - menu.removeAll(); + List menu = new ArrayList<>(); - MenuItem showMainWindowItem = new MenuItem(resourceBundle.getString("traymenu.showMainWindow")); - showMainWindowItem.addActionListener(this::showMainWindow); - menu.add(showMainWindowItem); - - MenuItem showPreferencesItem = new MenuItem(resourceBundle.getString("traymenu.showPreferencesWindow")); - showPreferencesItem.addActionListener(this::showPreferencesWindow); - menu.add(showPreferencesItem); - - menu.addSeparator(); - for (Vault v : vaults) { - MenuItem submenu = buildSubmenu(v); - menu.add(submenu); + menu.add(new ActionItem(resourceBundle.getString("traymenu.showMainWindow"), this::showMainWindow)); + menu.add(new ActionItem(resourceBundle.getString("traymenu.showPreferencesWindow"), this::showPreferencesWindow)); + menu.add(new SeparatorItem()); + for (Vault vault : vaults) { + List submenu = buildSubmenu(vault); + menu.add(new SubMenuItem(vault.getDisplayName(), submenu)); } - menu.addSeparator(); + menu.add(new SeparatorItem()); + menu.add(new ActionItem(resourceBundle.getString("traymenu.lockAllVaults"), this::lockAllVaults)); + menu.add(new ActionItem(resourceBundle.getString("traymenu.quitApplication"), this::quitApplication)); +// lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty()); - MenuItem lockAllItem = new MenuItem(resourceBundle.getString("traymenu.lockAllVaults")); - lockAllItem.addActionListener(this::lockAllVaults); - lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty()); - menu.add(lockAllItem); - - MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication")); - quitApplicationItem.addActionListener(this::quitApplication); - menu.add(quitApplicationItem); + trayMenu.updateTrayMenu(menu); } - private Menu buildSubmenu(Vault vault) { - Menu submenu = new Menu(vault.getDisplayName()); - + private List buildSubmenu(Vault vault) { if (vault.isLocked()) { - MenuItem unlockItem = new MenuItem(resourceBundle.getString("traymenu.vault.unlock")); - unlockItem.addActionListener(createActionListenerForVault(vault, this::unlockVault)); - submenu.add(unlockItem); + return List.of( + new ActionItem(resourceBundle.getString("traymenu.vault.unlock"), () -> this.unlockVault(vault)) + ); } else if (vault.isUnlocked()) { - MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock")); - lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault)); - submenu.add(lockItem); + return List.of( + new ActionItem(resourceBundle.getString("traymenu.vault.lock"), () -> this.lockVault(vault)), + new ActionItem(resourceBundle.getString("traymenu.vault.reveal"), () -> this.revealVault(vault)) - MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal")); - revealItem.addActionListener(createActionListenerForVault(vault, this::revealVault)); - submenu.add(revealItem); + ); + } else { + return List.of(); } - - return submenu; } - private ActionListener createActionListenerForVault(Vault vault, Consumer consumer) { - return actionEvent -> consumer.accept(vault); - } + /* action listeners: */ - private void quitApplication(EventObject actionEvent) { + private void quitApplication() { appLifecycle.quit(); } @@ -119,7 +126,7 @@ class TrayMenuController { showMainAppAndThen(app -> app.startLockWorkflow(vault, Optional.empty())); } - private void lockAllVaults(ActionEvent actionEvent) { + private void lockAllVaults() { showMainAppAndThen(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false)); } @@ -127,11 +134,11 @@ class TrayMenuController { showMainAppAndThen(app -> app.getVaultService().reveal(vault)); } - void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) { - showMainAppAndThen(app -> app.showMainWindow()); + void showMainWindow() { + showMainAppAndThen(FxApplication::showMainWindow); } - private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { + private void showPreferencesWindow() { showMainAppAndThen(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY)); } diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuModule.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuModule.java new file mode 100644 index 000000000..3110be883 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuModule.java @@ -0,0 +1,18 @@ +package org.cryptomator.ui.traymenu; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.integrations.tray.TrayMenuController; + +import java.util.Optional; + +@Module +public class TrayMenuModule { + + @Provides + @TrayMenuScoped + static Optional provideSupportedKeychainAccessProviders() { + return TrayMenuController.get(); + } + +} From ad6d5bfae93aa9516060df750417ce55627bee3c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Mar 2022 17:42:56 +0100 Subject: [PATCH 03/83] resolved class name conflict --- .../ui/traymenu/AwtTrayMenuController.java | 4 +++- .../{TrayMenuController.java => TrayMenuBuilder.java} | 11 ++++------- .../cryptomator/ui/traymenu/TrayMenuComponent.java | 7 +++---- 3 files changed, 10 insertions(+), 12 deletions(-) rename src/main/java/org/cryptomator/ui/traymenu/{TrayMenuController.java => TrayMenuBuilder.java} (90%) diff --git a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java index 7c1a0998b..034847913 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java @@ -5,6 +5,7 @@ import org.cryptomator.integrations.common.Priority; import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SubMenuItem; +import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.tray.TrayMenuItem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +22,7 @@ import java.io.InputStream; import java.util.List; @Priority(Priority.FALLBACK) -public class AwtTrayMenuController implements org.cryptomator.integrations.tray.TrayMenuController { +public class AwtTrayMenuController implements TrayMenuController { private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class); @@ -58,6 +59,7 @@ public class AwtTrayMenuController implements org.cryptomator.integrations.tray. if (item instanceof ActionItem a) { var menuItem = new MenuItem(a.title()); menuItem.addActionListener(evt -> a.action().run()); + // TODO menuItem.setEnabled(a.enabled()); menu.add(menuItem); } else if (item instanceof SeparatorItem) { menu.addSeparator(); diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java similarity index 90% rename from src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java rename to src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java index 8189406d2..5a83c77c2 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java @@ -6,6 +6,7 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SubMenuItem; +import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.integrations.tray.TrayMenuItem; import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.launcher.AppLifecycleListener; @@ -16,20 +17,16 @@ import javax.inject.Inject; import javafx.application.Platform; import javafx.beans.Observable; import javafx.collections.ObservableList; -import java.awt.Image; -import java.awt.Toolkit; -import java.awt.event.ActionListener; import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; -import java.util.EventObject; import java.util.List; import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Consumer; @TrayMenuScoped -public class TrayMenuController { +public class TrayMenuBuilder { private static final String TRAY_ICON_MAC = "/img/tray_icon_mac.png"; private static final String TRAY_ICON = "/img/tray_icon.png"; @@ -38,12 +35,12 @@ public class TrayMenuController { private final AppLifecycleListener appLifecycle; private final FxApplicationStarter fxApplicationStarter; private final ObservableList vaults; - private final org.cryptomator.integrations.tray.TrayMenuController trayMenu; + private final TrayMenuController trayMenu; private volatile boolean initialized; @Inject - TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults, Optional trayMenu) { + TrayMenuBuilder(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults, Optional trayMenu) { this.resourceBundle = resourceBundle; this.appLifecycle = appLifecycle; this.fxApplicationStarter = fxApplicationStarter; diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java index e50269007..61861e12c 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java @@ -18,7 +18,7 @@ public interface TrayMenuComponent { Optional trayMenuController(); - org.cryptomator.ui.traymenu.TrayMenuController trayMenuController2(); // TODO tmp name + TrayMenuBuilder trayMenuBuilder(); /** * @return true if a tray icon can be installed @@ -32,7 +32,7 @@ public interface TrayMenuComponent { * @return true if a tray icon has been installed */ default boolean isInitialized() { - return isSupported() && trayMenuController2().isInitialized(); + return isSupported() && trayMenuBuilder().isInitialized(); } /** @@ -42,8 +42,7 @@ public interface TrayMenuComponent { */ default void initializeTrayIcon() throws IllegalStateException { Preconditions.checkState(isSupported(), "system tray not supported"); - - trayMenuController2().initTrayMenu(); + trayMenuBuilder().initTrayMenu(); } @Subcomponent.Builder From fab70ef8c97a82b01c4acf059505f6aa31b033dc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 17:25:04 +0100 Subject: [PATCH 04/83] run Cryptomator binaries instead of java from AppImage launcher .sh --- .github/workflows/appimage.yml | 2 ++ dist/linux/appimage/build.sh | 2 ++ .../resources/AppDir/bin/cryptomator.sh | 25 ++++--------------- dist/linux/launcher-gtk2.properties | 12 +++++++++ 4 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 dist/linux/launcher-gtk2.properties diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 694f6ee45..ac9ee4eac 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -60,6 +60,7 @@ jobs: --compress=1 - name: Run jpackage run: > + envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties ${JAVA_HOME}/bin/jpackage --verbose --type app-image @@ -83,6 +84,7 @@ jobs: --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.showTrayIcon=false" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\"" + --add-launcher Cryptomator-gtk2=launcher-gtk2.properties \ --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 6dd670df2..3cd080de5 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -26,6 +26,7 @@ ${JAVA_HOME}/bin/jlink \ --compress=1 # create app dir +envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties ${JAVA_HOME}/bin/jpackage \ --verbose \ --type app-image \ @@ -48,6 +49,7 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.showTrayIcon=false" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ + --add-launcher cryptomator-gtk2=launcher-gtk2.properties \ --resource-dir ../resources # transform AppDir diff --git a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh index 82c3e01ca..80b919c72 100755 --- a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh +++ b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh @@ -15,26 +15,11 @@ elif command -v pacman &> /dev/null; then # don't forget arch GTK3_PRESENT=`pacman -Qi gtk3 &> /dev/null; echo $?` fi -if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then - GTK_FLAG="-Djdk.gtk.version=2" -fi - # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 export LD_PRELOAD=lib/app/libjffi.so -# start Cryptomator -./lib/runtime/bin/java \ - -p "lib/app/mods" \ - -cp "lib/app/*" \ - -Dfile.encoding="utf-8" \ - -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \ - -Dcryptomator.pluginDir="~/.local/share/Cryptomator/plugins" \ - -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \ - -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json" \ - -Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" \ - -Dcryptomator.buildNumber="appimage-${REVISION_NO}" \ - -Dcryptomator.appVersion="${SEMVER_STR}" \ - $GTK_FLAG \ - -Xss5m \ - -Xmx256m \ - -m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator +if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then + ./bin/Cryptomator-gtk2 +else + ./bin/Cryptomator +fi \ No newline at end of file diff --git a/dist/linux/launcher-gtk2.properties b/dist/linux/launcher-gtk2.properties new file mode 100644 index 000000000..bf4727fa9 --- /dev/null +++ b/dist/linux/launcher-gtk2.properties @@ -0,0 +1,12 @@ +java-options=-Xss5m \ + -Xmx256m \ + -Dfile.encoding=\"utf-8\" \ + -Dcryptomator.appVersion=\"${SEMVER_STR}\" \ + -Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \ + -Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\" \ + -Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\" \ + -Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\" \ + -Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\" \ + -Dcryptomator.showTrayIcon=false \ + -Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\" \ + -Djdk.gtk.version=2 \ No newline at end of file From b565f1d0c0b75025b1f0389249f90b689b2e6ceb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 17:26:28 +0100 Subject: [PATCH 05/83] remove unused java binaries from jlinked runtime image --- .github/workflows/appimage.yml | 1 + dist/linux/appimage/build.sh | 1 + dist/linux/debian/rules | 1 + dist/mac/dmg/build.sh | 1 + dist/win/build.ps1 | 1 + 5 files changed, 5 insertions(+) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index ac9ee4eac..10dc04f16 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -54,6 +54,7 @@ jobs: --output runtime --module-path "${JAVA_HOME}/jmods" --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands --no-header-files --no-man-pages --strip-debug diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 3cd080de5..de0685379 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -20,6 +20,7 @@ ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index e4f824394..eda04ae65 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -19,6 +19,7 @@ override_dh_auto_build: jlink \ --output runtime \ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index c90411acb..df9664eb5 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -38,6 +38,7 @@ ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ + --strip-native-commands \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 0b0e953bc..5e4cc484a 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -42,6 +42,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { --output runtime ` --module-path "$Env:JAVA_HOME/jmods" ` --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` + --strip-native-commands ` --no-header-files ` --no-man-pages ` --strip-debug ` From 8dce21ea403dbb7cbed3acacdc86f2b6cc377338 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 17:31:49 +0100 Subject: [PATCH 06/83] run envsubst as an independent step --- .github/workflows/appimage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 10dc04f16..e05199967 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -59,9 +59,10 @@ jobs: --no-man-pages --strip-debug --compress=1 + - name: Prepare additional launcher + run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties - name: Run jpackage run: > - envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties ${JAVA_HOME}/bin/jpackage --verbose --type app-image From 550546c4b7b669e9d34080fc3414a3f4e19bd8b4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 17:42:28 +0100 Subject: [PATCH 07/83] not required in yml multiline strings --- .github/workflows/appimage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index e05199967..d5fc543f8 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -86,7 +86,7 @@ jobs: --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" --java-options "-Dcryptomator.showTrayIcon=false" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\"" - --add-launcher Cryptomator-gtk2=launcher-gtk2.properties \ + --add-launcher Cryptomator-gtk2=launcher-gtk2.properties --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | From 504a384225b35448ae0b5f25bc3b9f76e63cee1f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 17:58:21 +0100 Subject: [PATCH 08/83] fix envsubst --- .github/workflows/appimage.yml | 7 +++---- dist/linux/appimage/resources/AppDir/bin/cryptomator.sh | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index d5fc543f8..01a98b832 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -61,6 +61,9 @@ jobs: --compress=1 - name: Prepare additional launcher run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties + env: + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + REVISION_NUM: ${{ steps.versions.outputs.revNum }} - name: Run jpackage run: > ${JAVA_HOME}/bin/jpackage @@ -92,7 +95,6 @@ jobs: run: | mv appdir/Cryptomator Cryptomator.AppDir cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ - envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg @@ -104,9 +106,6 @@ jobs: ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - env: - REVISION_NO: ${{ steps.versions.outputs.revNum }} - SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 run: | JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` diff --git a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh index 80b919c72..39579a122 100755 --- a/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh +++ b/dist/linux/appimage/resources/AppDir/bin/cryptomator.sh @@ -19,7 +19,7 @@ fi export LD_PRELOAD=lib/app/libjffi.so if [ "$GTK2_PRESENT" -eq 0 ] && [ "$GTK3_PRESENT" -ne 0 ]; then - ./bin/Cryptomator-gtk2 + bin/Cryptomator-gtk2 else - ./bin/Cryptomator + bin/Cryptomator fi \ No newline at end of file From 73bbcdcca12c444cf51f018a5440f6dedcc4ce3e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 28 Mar 2022 17:52:39 +0200 Subject: [PATCH 09/83] start javafx via `Application.launch(...)` again --- src/main/java/module-info.java | 1 + .../cryptomator/ui/fxapp/FxApplication.java | 7 +-- .../ui/fxapp/FxApplicationComponent.java | 11 ++++ .../ui/fxapp/FxApplicationModule.java | 7 --- .../ui/launcher/FxApplicationStarter.java | 51 +++++++++++++------ .../ui/launcher/UiLauncherModule.java | 6 --- .../ui/mainwindow/MainWindowModule.java | 11 ---- .../ui/mainwindow/VaultDetailController.java | 5 +- .../MigrationImpossibleController.java | 9 ++-- 9 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f13308be5..7441a629d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -47,6 +47,7 @@ module org.cryptomator.desktop { opens org.cryptomator.ui.health to javafx.fxml; opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml; opens org.cryptomator.ui.lock to javafx.fxml; + opens org.cryptomator.ui.launcher to javafx.graphics; opens org.cryptomator.ui.mainwindow to javafx.fxml; opens org.cryptomator.ui.migration to javafx.fxml; opens org.cryptomator.ui.preferences to javafx.fxml; diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 1812d38bd..d2b11147a 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -41,7 +41,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @FxApplicationScoped -public class FxApplication extends Application { +public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); @@ -87,11 +87,6 @@ public class FxApplication extends Application { loadSelectedStyleSheet(settings.theme().get()); } - @Override - public void start(Stage stage) { - throw new UnsupportedOperationException("Use start() instead."); - } - private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) { LOG.debug("has visible stages: {}", newValue); if (newValue) { diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java index 7d5fd55bf..de4134ddb 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java @@ -5,7 +5,12 @@ *******************************************************************************/ package org.cryptomator.ui.fxapp; +import dagger.BindsInstance; import dagger.Subcomponent; +import org.cryptomator.ui.mainwindow.MainWindow; + +import javafx.application.Application; +import javafx.stage.Stage; @FxApplicationScoped @Subcomponent(modules = FxApplicationModule.class) @@ -16,6 +21,12 @@ public interface FxApplicationComponent { @Subcomponent.Builder interface Builder { + @BindsInstance + Builder fxApplication(Application application); + + @BindsInstance + Builder mainWindow(@MainWindow Stage mainWindow); + FxApplicationComponent build(); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 74c201372..737ddb7cd 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -5,7 +5,6 @@ *******************************************************************************/ package org.cryptomator.ui.fxapp; -import dagger.Binds; import dagger.Module; import dagger.Provides; import org.apache.commons.lang3.SystemUtils; @@ -18,10 +17,7 @@ import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.unlock.UnlockComponent; import javax.inject.Named; -import javafx.application.Application; -import javafx.collections.ObservableSet; import javafx.scene.image.Image; -import javafx.stage.Stage; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -62,9 +58,6 @@ abstract class FxApplicationModule { } } - @Binds - abstract Application bindApplication(FxApplication application); - @Provides static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) { return builder.build(); diff --git a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java b/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java index 1799a9700..32f41185b 100644 --- a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java +++ b/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java @@ -1,6 +1,6 @@ package org.cryptomator.ui.launcher; -import dagger.Lazy; +import com.google.common.base.Preconditions; import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.FxApplicationComponent; import org.slf4j.Logger; @@ -8,47 +8,68 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; -import javafx.application.Platform; +import javafx.application.Application; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import java.lang.ref.WeakReference; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; @Singleton public class FxApplicationStarter { private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class); + private static final AtomicReference FX_APP_COMP_BUILDER = new AtomicReference<>(); + private static final CompletableFuture FUTURE = new CompletableFuture<>(); - private final Lazy fxAppComponent; private final ExecutorService executor; private final AtomicBoolean started; - private final CompletableFuture future; @Inject - public FxApplicationStarter(Lazy fxAppComponent, ExecutorService executor) { - this.fxAppComponent = fxAppComponent; + public FxApplicationStarter(FxApplicationComponent.Builder fxAppCompBuilder, ExecutorService executor) { + FX_APP_COMP_BUILDER.set(fxAppCompBuilder); this.executor = executor; this.started = new AtomicBoolean(); - this.future = new CompletableFuture<>(); } public CompletionStage get() { if (!started.getAndSet(true)) { start(); } - return future; + return FUTURE; } private void start() { executor.submit(() -> { LOG.debug("Starting JavaFX runtime..."); - Platform.startup(() -> { - assert Platform.isFxApplicationThread(); - LOG.info("JavaFX Runtime started."); - FxApplication app = fxAppComponent.get().application(); - app.start(); - future.complete(app); - }); + Application.launch(CryptomatorGui.class); }); } + + public static class CryptomatorGui extends Application { + + @Override + public void start(Stage primaryStage) throws Exception { + var builder = Objects.requireNonNull(FX_APP_COMP_BUILDER.get()); // TODO add message? + + // set defaults for primary stage: + // TODO: invoke StageFactory stuff... + primaryStage.setTitle("Cryptomator"); + primaryStage.initStyle(StageStyle.UNDECORATED); + primaryStage.setMinWidth(650); + primaryStage.setMinHeight(440); + + // build subcomponent + var comp = builder.mainWindow(primaryStage).fxApplication(this).build(); + + // call delegate + var app = comp.application(); + app.start(); + FUTURE.complete(app); + } + } } diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java index c30efa30e..fa4631af4 100644 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java +++ b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java @@ -26,12 +26,6 @@ public abstract class UiLauncherModule { return builder.build(); } - @Provides - @Singleton - static FxApplicationComponent provideFxApplicationComponent(FxApplicationComponent.Builder builder) { - return builder.build(); - } - @Provides @Singleton static Optional provideAppearanceProvider(PluginClassLoader classLoader) { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index 6f63db888..f9372ba08 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -47,17 +47,6 @@ abstract class MainWindowModule { return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); } - @Provides - @MainWindow - @MainWindowScoped - static Stage provideStage(StageFactory factory) { - Stage stage = factory.create(StageStyle.UNDECORATED); - stage.setMinWidth(650); - stage.setMinHeight(440); - stage.setTitle("Cryptomator"); - return stage; - } - @Provides @MainWindowScoped @Named("errorWindow") diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index 87a419a94..cf585de39 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -12,6 +12,7 @@ import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.fxapp.FxApplication; import javax.inject.Inject; +import javafx.application.Application; import javafx.beans.binding.Binding; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; @@ -22,7 +23,7 @@ import javafx.fxml.FXML; public class VaultDetailController implements FxController { private final ReadOnlyObjectProperty vault; - private final FxApplication application; + private final Application application; private final Binding glyph; private final BooleanBinding anyVaultSelected; @@ -33,7 +34,7 @@ public class VaultDetailController implements FxController { @Inject - VaultDetailController(ObjectProperty vault, FxApplication application) { + VaultDetailController(ObjectProperty vault, Application application) { this.vault = vault; this.application = application; this.glyph = EasyBind.select(vault) // diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java index 191fc7a8f..e861d6438 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java @@ -5,6 +5,7 @@ import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplication; import javax.inject.Inject; +import javafx.application.Application; import javafx.fxml.FXML; import javafx.stage.Stage; @@ -12,13 +13,13 @@ public class MigrationImpossibleController implements FxController { private static final String HELP_URI = "https://docs.cryptomator.org/en/1.5/help/manual-migration/"; - private final FxApplication fxApplication; + private final Application application; private final Stage window; private final Vault vault; @Inject - MigrationImpossibleController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault) { - this.fxApplication = fxApplication; + MigrationImpossibleController(Application application, @MigrationWindow Stage window, @MigrationWindow Vault vault) { + this.application = application; this.window = window; this.vault = vault; } @@ -30,7 +31,7 @@ public class MigrationImpossibleController implements FxController { @FXML public void getMigrationHelp() { - fxApplication.getHostServices().showDocument(HELP_URI); + application.getHostServices().showDocument(HELP_URI); } /* Getter/Setters */ From e9073604193b18b59ae3122c489a7fba8e1e0452 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 29 Mar 2022 20:39:46 +0200 Subject: [PATCH 10/83] =?UTF-8?q?refactored=20launcher,=20deleted=20UiLaun?= =?UTF-8?q?cherModule=20component=20graph=20is=20now:=20Main=20=E2=86=92?= =?UTF-8?q?=20FxApplicationComponent=20=E2=86=92=20Tray,=20MainWindow,=20e?= =?UTF-8?q?tc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 + src/main/java/module-info.java | 5 +- .../common/vaults/DefaultMountFlags.java | 2 +- .../cryptomator/launcher/AppLaunchEvent.java | 13 ++ .../org/cryptomator/launcher/Cryptomator.java | 43 ++-- .../launcher/CryptomatorComponent.java | 6 +- .../launcher/CryptomatorModule.java | 45 +++- .../launcher/FileOpenRequestHandler.java | 3 - .../launcher/IpcMessageHandler.java | 1 - .../ui/addvaultwizard/AddVaultModule.java | 6 +- .../AddVaultSuccessController.java | 11 +- .../ChooseExistingVaultController.java | 9 +- .../CreateNewVaultPasswordController.java | 14 +- .../ChangePasswordController.java | 13 +- .../cryptomator/ui/common/ErrorComponent.java | 27 +-- .../cryptomator/ui/common/VaultService.java | 5 +- .../AppLaunchEventHandler.java | 31 +-- .../cryptomator/ui/fxapp/AutoUnlocker.java | 26 +++ .../ui/fxapp/ExitingQuitResponse.java | 20 ++ .../cryptomator/ui/fxapp/FxApplication.java | 214 +++--------------- .../ui/fxapp/FxApplicationComponent.java | 3 +- .../ui/fxapp/FxApplicationModule.java | 12 +- .../ui/fxapp/FxApplicationStyle.java | 98 ++++++++ .../ui/fxapp/FxApplicationTerminator.java | 123 ++++++++++ .../ui/fxapp/FxApplicationWindows.java | 146 ++++++++++++ .../cryptomator/ui/fxapp/PrimaryStage.java | 14 ++ .../ui/health/CheckListController.java | 13 +- .../ui/health/StartController.java | 12 +- .../ui/launcher/AppLaunchEvent.java | 28 --- .../ui/launcher/AppLifecycleListener.java | 146 ------------ .../ui/launcher/FxApplicationStarter.java | 75 ------ .../cryptomator/ui/launcher/UiLauncher.java | 90 -------- .../ui/launcher/UiLauncherModule.java | 61 ----- .../cryptomator/ui/lock/LockComponent.java | 16 +- .../org/cryptomator/ui/lock/LockModule.java | 7 +- .../org/cryptomator/ui/lock/LockWorkflow.java | 10 +- .../cryptomator/ui/mainwindow/MainWindow.java | 2 +- .../ui/mainwindow/MainWindowModule.java | 6 + .../mainwindow/MainWindowTitleController.java | 21 +- .../VaultDetailLockedController.java | 9 +- .../VaultDetailUnknownErrorController.java | 12 +- .../VaultDetailUnlockedController.java | 11 +- .../VaultListContextMenuController.java | 20 +- .../ui/migration/MigrationModule.java | 6 +- .../ui/migration/MigrationRunController.java | 12 +- .../migration/MigrationSuccessController.java | 13 +- .../GeneralPreferencesController.java | 11 +- .../RecoveryKeyCreationController.java | 10 +- .../RecoveryKeyResetPasswordController.java | 10 +- .../ui/removevault/RemoveVaultModule.java | 8 +- .../ui/traymenu/TrayMenuController.java | 37 ++- .../ui/unlock/UnlockComponent.java | 15 +- .../cryptomator/ui/unlock/UnlockModule.java | 7 +- .../cryptomator/ui/unlock/UnlockWorkflow.java | 10 +- .../ui/vaultoptions/VaultOptionsModule.java | 6 +- .../wrongfilealert/WrongFileAlertModule.java | 8 +- .../launcher/FileOpenRequestHandlerTest.java | 3 +- 57 files changed, 764 insertions(+), 829 deletions(-) create mode 100644 src/main/java/org/cryptomator/launcher/AppLaunchEvent.java rename src/main/java/org/cryptomator/ui/{launcher => fxapp}/AppLaunchEventHandler.java (72%) create mode 100644 src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java create mode 100644 src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java create mode 100644 src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java create mode 100644 src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java create mode 100644 src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java create mode 100644 src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java delete mode 100644 src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java delete mode 100644 src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java delete mode 100644 src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java delete mode 100644 src/main/java/org/cryptomator/ui/launcher/UiLauncher.java delete mode 100644 src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java diff --git a/pom.xml b/pom.xml index 880c07f73..629938792 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ 2.2 + 23.0.0 7.0.0 0.8.7 @@ -224,6 +225,13 @@ 1.2 test + + + org.jetbrains + annotations + ${jetbrains.annotations.version} + provided + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 7441a629d..90125d7cc 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -4,6 +4,8 @@ import org.cryptomator.integrations.tray.TrayIntegrationProvider; import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; module org.cryptomator.desktop { + requires static org.jetbrains.annotations; + requires org.cryptomator.cryptofs; requires org.cryptomator.frontend.dokany; requires org.cryptomator.frontend.fuse; @@ -36,6 +38,8 @@ module org.cryptomator.desktop { opens org.cryptomator.common.settings to com.google.gson; + opens org.cryptomator.launcher to javafx.graphics; + opens org.cryptomator.common to javafx.fxml; opens org.cryptomator.common.vaults to javafx.fxml; opens org.cryptomator.ui.addvaultwizard to javafx.fxml; @@ -47,7 +51,6 @@ module org.cryptomator.desktop { opens org.cryptomator.ui.health to javafx.fxml; opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml; opens org.cryptomator.ui.lock to javafx.fxml; - opens org.cryptomator.ui.launcher to javafx.graphics; opens org.cryptomator.ui.mainwindow to javafx.fxml; opens org.cryptomator.ui.migration to javafx.fxml; opens org.cryptomator.ui.preferences to javafx.fxml; diff --git a/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java b/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java index 68b61688b..4f3a8ff15 100644 --- a/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java +++ b/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java @@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Documented @Retention(RUNTIME) -public @interface DefaultMountFlags { +@interface DefaultMountFlags { } diff --git a/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java b/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java new file mode 100644 index 000000000..7fde984e8 --- /dev/null +++ b/src/main/java/org/cryptomator/launcher/AppLaunchEvent.java @@ -0,0 +1,13 @@ +package org.cryptomator.launcher; + +import java.nio.file.Path; +import java.util.Collection; + +public record AppLaunchEvent(AppLaunchEvent.EventType type, Collection pathsToOpen) { + + public enum EventType { + REVEAL_APP, + OPEN_FILE + } + +} diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 5502d804a..bece40eb9 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -13,17 +13,15 @@ import org.cryptomator.common.ShutdownHook; import org.cryptomator.ipc.IpcCommunicator; import org.cryptomator.logging.DebugMode; import org.cryptomator.logging.LoggerConfiguration; -import org.cryptomator.ui.launcher.UiLauncher; +import org.cryptomator.ui.fxapp.FxApplicationComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; -import java.io.IOException; +import javafx.application.Application; +import javafx.stage.Stage; import java.util.List; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; @Singleton @@ -38,19 +36,15 @@ public class Cryptomator { private final DebugMode debugMode; private final Environment env; private final Lazy ipcMessageHandler; - private final CountDownLatch shutdownLatch; private final ShutdownHook shutdownHook; - private final Lazy uiLauncher; @Inject - Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy ipcMessageHandler, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy uiLauncher) { + Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { this.logConfig = logConfig; this.debugMode = debugMode; this.env = env; this.ipcMessageHandler = ipcMessageHandler; - this.shutdownLatch = shutdownLatch; this.shutdownHook = shutdownHook; - this.uiLauncher = uiLauncher; } public static void main(String[] args) { @@ -96,21 +90,38 @@ public class Cryptomator { } /** - * Launches the JavaFX application and waits until shutdown is requested. + * Launches the JavaFX application, blocking the main thread until shuts down. * * @return Nonzero exit code in case of an error. - * @implNote This method blocks until {@link #shutdownLatch} reached zero. */ private int runGuiApplication() { try { - uiLauncher.get().launch(); - shutdownLatch.await(); + Application.launch(MainApp.class); LOG.info("UI shut down"); return 0; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + } catch (Throwable e) { + LOG.error("Terminating due to error", e); return 1; } } + public static class MainApp extends Application { + + @Override + public void start(Stage primaryStage) { + LOG.info("JavaFX application started."); + FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() // + .fxApplication(this) // + .primaryStage(primaryStage) // + .build(); + component.application().start(); + } + + @Override + public void stop() { + LOG.info("JavaFX application stopped."); + } + + } + } diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java index 70bf9e772..b43c0eca0 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java @@ -3,14 +3,16 @@ package org.cryptomator.launcher; import dagger.Component; import org.cryptomator.common.CommonsModule; import org.cryptomator.logging.LoggerModule; -import org.cryptomator.ui.launcher.UiLauncherModule; +import org.cryptomator.ui.fxapp.FxApplicationComponent; import javax.inject.Singleton; @Singleton -@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class, UiLauncherModule.class}) +@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class}) public interface CryptomatorComponent { Cryptomator application(); + FxApplicationComponent.Builder fxAppComponentBuilder(); + } diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java index 906971492..414ba208e 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java @@ -2,20 +2,57 @@ package org.cryptomator.launcher; import dagger.Module; import dagger.Provides; +import org.cryptomator.common.PluginClassLoader; +import org.cryptomator.integrations.autostart.AutoStartProvider; +import org.cryptomator.integrations.tray.TrayIntegrationProvider; +import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; +import org.cryptomator.ui.fxapp.FxApplicationComponent; +import org.cryptomator.ui.traymenu.TrayMenuComponent; import javax.inject.Named; import javax.inject.Singleton; import java.util.Optional; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; -@Module +@Module(subcomponents = {FxApplicationComponent.class}) class CryptomatorModule { @Provides @Singleton - @Named("shutdownLatch") - static CountDownLatch provideShutdownLatch() { - return new CountDownLatch(1); + static ResourceBundle provideLocalization() { + return ResourceBundle.getBundle("i18n.strings"); } + @Provides + @Singleton + @Named("launchEventQueue") + static BlockingQueue provideFileOpenRequests() { + return new ArrayBlockingQueue<>(10); + } + + // TODO: still needed after integrations-api 1.1.0? + + @Provides + @Singleton + static Optional provideAppearanceProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst(); + } + + @Provides + @Singleton + static Optional provideAutostartProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst(); + } + + @Provides + @Singleton + static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst(); + } + + } diff --git a/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java index b4e37e1f9..eb2418c69 100644 --- a/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java +++ b/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java @@ -6,7 +6,6 @@ *******************************************************************************/ package org.cryptomator.launcher; -import org.cryptomator.ui.launcher.AppLaunchEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,12 +19,10 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.BlockingQueue; -import java.util.stream.Collectors; @Singleton class FileOpenRequestHandler { diff --git a/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java b/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java index 5c28d05a4..05565f97d 100644 --- a/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java +++ b/src/main/java/org/cryptomator/launcher/IpcMessageHandler.java @@ -1,7 +1,6 @@ package org.cryptomator.launcher; import org.cryptomator.ipc.IpcMessageListener; -import org.cryptomator.ui.launcher.AppLaunchEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java index 8a5a776ea..c6acbadf6 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java @@ -14,7 +14,7 @@ import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.NewPasswordController; import org.cryptomator.ui.common.PasswordStrengthUtil; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController; import javax.inject.Named; @@ -43,12 +43,12 @@ public abstract class AddVaultModule { @Provides @AddVaultWizardWindow @AddVaultWizardScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) { + static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("addvaultwizard.title")); stage.setResizable(false); stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(owner); + stage.initOwner(primaryStage); return stage; } diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java index 99d01577f..e0306b4e7 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultSuccessController.java @@ -2,25 +2,24 @@ package org.cryptomator.ui.addvaultwizard; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import javax.inject.Inject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; import javafx.stage.Stage; -import java.util.Optional; @AddVaultWizardScoped public class AddVaultSuccessController implements FxController { - private final FxApplication fxApplication; + private final FxApplicationWindows appWindows; private final Stage window; private final ReadOnlyObjectProperty vault; @Inject - AddVaultSuccessController(FxApplication fxApplication, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty vault) { - this.fxApplication = fxApplication; + AddVaultSuccessController(FxApplicationWindows appWindows, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty vault) { + this.appWindows = appWindows; this.window = window; this.vault = vault; } @@ -28,7 +27,7 @@ public class AddVaultSuccessController implements FxController { @FXML public void unlockAndClose() { close(); - fxApplication.startUnlockWorkflow(vault.get(), Optional.of(window)); + appWindows.startUnlockWorkflow(vault.get(), window); } @FXML diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 4fceaa929..deaa9fdde 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -8,6 +8,7 @@ import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +35,7 @@ public class ChooseExistingVaultController implements FxController { private final Stage window; private final Lazy welcomeScene; private final Lazy successScene; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; private final ObjectProperty vaultPath; private final ObjectProperty vault; private final VaultListManager vaultListManager; @@ -43,11 +44,11 @@ public class ChooseExistingVaultController implements FxController { private Image screenshot; @Inject - ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ErrorComponent.Builder errorComponent, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) { + ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, FxApplicationWindows appWindows, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) { this.window = window; this.welcomeScene = welcomeScene; this.successScene = successScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; this.vaultPath = vaultPath; this.vault = vault; this.vaultListManager = vaultListManager; @@ -82,7 +83,7 @@ public class ChooseExistingVaultController implements FxController { window.setScene(successScene.get()); } catch (IOException e) { LOG.error("Failed to open existing vault.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); } } } diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java index 578b90969..51a8a1147 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java @@ -10,12 +10,12 @@ import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.NewPasswordController; import org.cryptomator.ui.common.Tasks; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.slf4j.Logger; @@ -60,7 +60,7 @@ public class CreateNewVaultPasswordController implements FxController { private final Lazy chooseLocationScene; private final Lazy recoveryKeyScene; private final Lazy successScene; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; private final ExecutorService executor; private final RecoveryKeyFactory recoveryKeyFactory; private final StringProperty vaultNameProperty; @@ -83,12 +83,12 @@ public class CreateNewVaultPasswordController implements FxController { public NewPasswordController newPasswordSceneController; @Inject - CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) { + CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, FxApplicationWindows appWindows, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) { this.window = window; this.chooseLocationScene = chooseLocationScene; this.recoveryKeyScene = recoveryKeyScene; this.successScene = successScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; this.executor = executor; this.recoveryKeyFactory = recoveryKeyFactory; this.vaultNameProperty = vaultName; @@ -127,7 +127,7 @@ public class CreateNewVaultPasswordController implements FxController { Files.createDirectory(pathToVault); } catch (IOException e) { LOG.error("Failed to create vault directory.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); return; } @@ -152,7 +152,7 @@ public class CreateNewVaultPasswordController implements FxController { window.setScene(recoveryKeyScene.get()); }).onError(IOException.class, e -> { LOG.error("Failed to initialize vault.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); }).andFinally(() -> { processing.set(false); }).runOnce(executor); @@ -168,7 +168,7 @@ public class CreateNewVaultPasswordController implements FxController { window.setScene(successScene.get()); }).onError(IOException.class, e -> { LOG.error("Failed to initialize vault.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); }).andFinally(() -> { processing.set(false); }).runOnce(executor); diff --git a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java index c715f0466..200a70328 100644 --- a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java +++ b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java @@ -8,10 +8,10 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.NewPasswordController; import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; -import java.security.SecureRandom; import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; @@ -38,9 +37,8 @@ public class ChangePasswordController implements FxController { private final Stage window; private final Vault vault; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; private final KeychainManager keychain; - private final SecureRandom csprng; private final MasterkeyFileAccess masterkeyFileAccess; public NiceSecurePasswordField oldPasswordField; @@ -49,12 +47,11 @@ public class ChangePasswordController implements FxController { public NewPasswordController newPasswordController; @Inject - public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ErrorComponent.Builder errorComponent, KeychainManager keychain, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) { + public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, FxApplicationWindows appWindows, KeychainManager keychain, MasterkeyFileAccess masterkeyFileAccess) { this.window = window; this.vault = vault; - this.errorComponent = errorComponent; + this.appWindows = appWindows; this.keychain = keychain; - this.csprng = csprng; this.masterkeyFileAccess = masterkeyFileAccess; } @@ -95,7 +92,7 @@ public class ChangePasswordController implements FxController { oldPasswordField.requestFocus(); } catch (IOException | CryptoException e) { LOG.error("Password change failed. Unable to perform operation.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); } } diff --git a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java index 92276f5bd..2f449ec4b 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java +++ b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java @@ -16,34 +16,17 @@ public interface ErrorComponent { @FxmlScene(FxmlFile.ERROR) Scene scene(); - default void showErrorScene() { - if (Platform.isFxApplicationThread()) { - show(); - } else { - Platform.runLater(this::show); - } - } - - private void show() { + default Stage show() { Stage stage = window(); stage.setScene(scene()); stage.show(); + return stage; } - @Subcomponent.Builder - interface Builder { - - @BindsInstance - Builder cause(Throwable cause); - - @BindsInstance - Builder window(Stage window); - - @BindsInstance - Builder returnToScene(@Nullable Scene previousScene); - - ErrorComponent build(); + @Subcomponent.Factory + interface Factory { + ErrorComponent create(@BindsInstance Throwable cause, @BindsInstance Stage window, @BindsInstance @Nullable Scene previousScene); } } diff --git a/src/main/java/org/cryptomator/ui/common/VaultService.java b/src/main/java/org/cryptomator/ui/common/VaultService.java index b81ddec49..a6486f35f 100644 --- a/src/main/java/org/cryptomator/ui/common/VaultService.java +++ b/src/main/java/org/cryptomator/ui/common/VaultService.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.concurrent.Task; +import javafx.stage.Stage; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -53,7 +54,9 @@ public class VaultService { * * @param vault The vault to lock * @param forced Whether to attempt a forced lock + * @deprecated use {@link org.cryptomator.ui.fxapp.FxApplicationWindows#startLockWorkflow(Vault, Stage)} */ + @Deprecated public void lock(Vault vault, boolean forced) { executorService.execute(createLockTask(vault, forced)); } @@ -90,7 +93,7 @@ public class VaultService { * @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask */ public Task> createLockAllTask(Collection vaults, boolean forced) { - List> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList()); + List> lockTasks = vaults.stream().>map(v -> new LockVaultTask(v, forced)).toList(); lockTasks.forEach(executorService::execute); Task> task = new WaitForTasksTask(lockTasks); String vaultNames = vaults.stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")); diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java similarity index 72% rename from src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java rename to src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java index 52ba838c0..39e40600e 100644 --- a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java @@ -1,14 +1,14 @@ -package org.cryptomator.ui.launcher; +package org.cryptomator.ui.fxapp; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.launcher.AppLaunchEvent; +import org.cryptomator.ui.common.VaultService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Singleton; import javafx.application.Platform; import java.io.IOException; import java.nio.file.Path; @@ -17,22 +17,25 @@ import java.util.concurrent.ExecutorService; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT; -@Singleton +// TODO: use message bus +@FxApplicationScoped class AppLaunchEventHandler { private static final Logger LOG = LoggerFactory.getLogger(AppLaunchEventHandler.class); private final BlockingQueue launchEventQueue; private final ExecutorService executorService; - private final FxApplicationStarter fxApplicationStarter; + private final FxApplicationWindows appWindows; private final VaultListManager vaultListManager; + private final VaultService vaultService; @Inject - public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationStarter fxApplicationStarter, VaultListManager vaultListManager) { + public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) { this.launchEventQueue = launchEventQueue; this.executorService = executorService; - this.fxApplicationStarter = fxApplicationStarter; + this.appWindows = appWindows; this.vaultListManager = vaultListManager; + this.vaultService = vaultService; } public void startHandlingLaunchEvents() { @@ -52,14 +55,12 @@ class AppLaunchEventHandler { } private void handleLaunchEvent(AppLaunchEvent event) { - switch (event.getType()) { - case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow); - case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> { - Platform.runLater(() -> { - event.getPathsToOpen().forEach(this::addOrRevealVault); - }); + switch (event.type()) { + case REVEAL_APP -> appWindows.showMainWindow(); + case OPEN_FILE -> Platform.runLater(() -> { + event.pathsToOpen().forEach(this::addOrRevealVault); }); - default -> LOG.warn("Unsupported event type: {}", event.getType()); + default -> LOG.warn("Unsupported event type: {}", event.type()); } } @@ -75,7 +76,7 @@ class AppLaunchEventHandler { } if (v.isUnlocked()) { - fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v)); + vaultService.reveal(v); } LOG.debug("Added vault {}", potentialVaultPath); } catch (IOException e) { diff --git a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java new file mode 100644 index 000000000..9d6a73fa0 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java @@ -0,0 +1,26 @@ +package org.cryptomator.ui.fxapp; + +import org.cryptomator.common.vaults.Vault; + +import javax.inject.Inject; +import javafx.collections.ObservableList; + +@FxApplicationScoped +public class AutoUnlocker { + + private final ObservableList vaults; + private final FxApplicationWindows appWindows; + + @Inject + public AutoUnlocker(ObservableList vaults, FxApplicationWindows appWindows) { + this.vaults = vaults; + this.appWindows = appWindows; + } + + public void unlock() { + vaults.stream().filter(Vault::isLocked).filter(v -> v.getVaultSettings().unlockAfterStartup().get()).forEach(v -> { + appWindows.startUnlockWorkflow(v, null); + }); + } + +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java b/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java new file mode 100644 index 000000000..ae2bcd438 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/ExitingQuitResponse.java @@ -0,0 +1,20 @@ +package org.cryptomator.ui.fxapp; + +import javafx.application.Platform; +import java.awt.desktop.QuitResponse; + +record ExitingQuitResponse(QuitResponse delegate) implements QuitResponse { + + @Override + public void performQuit() { + Platform.exit(); + // TODO wait a moment for javafx to terminate? + delegate.performQuit(); + } + + @Override + public void cancelQuit() { + delegate.cancelQuit(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index d2b11147a..057466320 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -1,208 +1,68 @@ package org.cryptomator.ui.fxapp; import dagger.Lazy; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.value.ObservableValue; -import javafx.collections.ObservableList; -import javafx.stage.Stage; -import javafx.stage.Window; -import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.UiTheme; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.common.vaults.VaultState; -import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.integrations.uiappearance.Theme; -import org.cryptomator.integrations.uiappearance.UiAppearanceException; -import org.cryptomator.integrations.uiappearance.UiAppearanceListener; -import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; -import org.cryptomator.ui.common.ErrorComponent; -import org.cryptomator.ui.common.VaultService; -import org.cryptomator.ui.lock.LockComponent; -import org.cryptomator.ui.mainwindow.MainWindowComponent; -import org.cryptomator.ui.preferences.PreferencesComponent; -import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.cryptomator.ui.quit.QuitComponent; -import org.cryptomator.ui.unlock.UnlockComponent; +import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Provider; -import java.awt.desktop.QuitResponse; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import javafx.application.Platform; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import java.awt.SystemTray; @FxApplicationScoped public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); + private final Stage primaryStage; private final Settings settings; - private final Lazy mainWindow; - private final Lazy preferencesWindow; - private final Lazy quitWindow; - private final Provider unlockWorkflowBuilderProvider; - private final Provider lockWorkflowBuilderProvider; - private final ErrorComponent.Builder errorWindowBuilder; - private final Optional trayIntegration; - private final Optional appearanceProvider; - private final VaultService vaultService; - private final LicenseHolder licenseHolder; - private final ObservableList visibleWindows; - private final BooleanBinding hasVisibleWindows; - private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged; + private final AppLaunchEventHandler launchEventHandler; + private final Lazy trayMenu; + private final FxApplicationWindows appWindows; + private final FxApplicationStyle applicationStyle; + private final FxApplicationTerminator applicationTerminator; + private final AutoUnlocker autoUnlocker; @Inject - FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWorkflowBuilderProvider, Provider lockWorkflowBuilderProvider, Lazy quitWindow, ErrorComponent.Builder errorWindowBuilder, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) { + FxApplication(@PrimaryStage Stage primaryStage, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { + this.primaryStage = primaryStage; this.settings = settings; - this.mainWindow = mainWindow; - this.preferencesWindow = preferencesWindow; - this.unlockWorkflowBuilderProvider = unlockWorkflowBuilderProvider; - this.lockWorkflowBuilderProvider = lockWorkflowBuilderProvider; - this.quitWindow = quitWindow; - this.errorWindowBuilder = errorWindowBuilder; - this.trayIntegration = trayIntegration; - this.appearanceProvider = appearanceProvider; - this.vaultService = vaultService; - this.licenseHolder = licenseHolder; - this.visibleWindows = Stage.getWindows().filtered(Window::isShowing); - this.hasVisibleWindows = Bindings.isNotEmpty(visibleWindows); + this.launchEventHandler = launchEventHandler; + this.trayMenu = trayMenu; + this.appWindows = appWindows; + this.applicationStyle = applicationStyle; + this.applicationTerminator = applicationTerminator; + this.autoUnlocker = autoUnlocker; } public void start() { LOG.trace("FxApplication.start()"); - Platform.setImplicitExit(false); + primaryStage.setTitle("Cryptomator"); + primaryStage.initStyle(StageStyle.UNDECORATED); + primaryStage.setMinWidth(650); + primaryStage.setMinHeight(440); - hasVisibleWindows.addListener(this::hasVisibleStagesChanged); + applicationStyle.initialize(); + appWindows.initialize(); + applicationTerminator.initialize(); - settings.theme().addListener(this::appThemeChanged); - loadSelectedStyleSheet(settings.theme().get()); - } - - private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) { - LOG.debug("has visible stages: {}", newValue); - if (newValue) { - trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray); - } else { - trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray); + // init system tray + if (SystemTray.isSupported() && settings.showTrayIcon().get()) { + trayMenu.get().initializeTrayIcon(); + Platform.setImplicitExit(false); // don't quit when closing all windows } - } - public void showPreferencesWindow(SelectedPreferencesTab selectedTab) { - Platform.runLater(() -> { - preferencesWindow.get().showPreferencesWindow(selectedTab); - LOG.debug("Showing Preferences"); + // show main window + appWindows.showMainWindow().thenAccept(stage -> { + boolean hide = settings.startHidden().get(); + stage.setIconified(hide); }); - } - public CompletionStage showMainWindow() { - CompletableFuture future = new CompletableFuture<>(); - Platform.runLater(() -> { - var win = mainWindow.get().showMainWindow(); - LOG.debug("Showing MainWindow"); - future.complete(win); - }); - return future; - } - - public void startUnlockWorkflow(Vault vault, Optional owner) { - Platform.runLater(() -> { - if (vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING)) { - unlockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow(); - LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); - } else { - showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to unlock vault in non-locked state."))); - } - }); - } - - public void startLockWorkflow(Vault vault, Optional owner) { - Platform.runLater(() -> { - if (vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING)) { - lockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow(); - LOG.debug("Start lock workflow for {}", vault.getDisplayName()); - } else { - showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to lock vault in non-unlocked state."))); - } - }); - } - - public void showQuitWindow(QuitResponse response) { - Platform.runLater(() -> { - quitWindow.get().showQuitWindow(response); - LOG.debug("Showing QuitWindow"); - }); - } - - public VaultService getVaultService() { - return vaultService; - } - - private void appThemeChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { - if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) { - try { - appearanceProvider.get().removeListener(systemInterfaceThemeListener); - } catch (UiAppearanceException e) { - LOG.error("Failed to disable automatic theme switching."); - } - } - loadSelectedStyleSheet(newValue); - } - - private void loadSelectedStyleSheet(UiTheme desiredTheme) { - UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT; - switch (theme) { - case LIGHT -> applyLightTheme(); - case DARK -> applyDarkTheme(); - case AUTOMATIC -> { - appearanceProvider.ifPresent(appearanceProvider -> { - try { - appearanceProvider.addListener(systemInterfaceThemeListener); - } catch (UiAppearanceException e) { - LOG.error("Failed to enable automatic theme switching."); - } - }); - applySystemTheme(); - } - } - } - - private void systemInterfaceThemeChanged(Theme theme) { - switch (theme) { - case LIGHT -> applyLightTheme(); - case DARK -> applyDarkTheme(); - } - } - - private void applySystemTheme() { - if (appearanceProvider.isPresent()) { - systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme()); - } else { - LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme..."); - applyLightTheme(); - } - } - - private void applyLightTheme() { - Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString()); - appearanceProvider.ifPresent(appearanceProvider -> { - appearanceProvider.adjustToTheme(Theme.LIGHT); - }); - } - - private void applyDarkTheme() { - Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString()); - appearanceProvider.ifPresent(appearanceProvider -> { - appearanceProvider.adjustToTheme(Theme.DARK); - }); + launchEventHandler.startHandlingLaunchEvents(); + autoUnlocker.unlock(); } } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java index de4134ddb..2557aa9ee 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java @@ -7,7 +7,6 @@ package org.cryptomator.ui.fxapp; import dagger.BindsInstance; import dagger.Subcomponent; -import org.cryptomator.ui.mainwindow.MainWindow; import javafx.application.Application; import javafx.stage.Stage; @@ -25,7 +24,7 @@ public interface FxApplicationComponent { Builder fxApplication(Application application); @BindsInstance - Builder mainWindow(@MainWindow Stage mainWindow); + Builder primaryStage(@PrimaryStage Stage primaryStage); FxApplicationComponent build(); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 737ddb7cd..68c97426a 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -14,6 +14,7 @@ import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.quit.QuitComponent; +import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.unlock.UnlockComponent; import javax.inject.Named; @@ -24,7 +25,7 @@ import java.io.UncheckedIOException; import java.util.Collections; import java.util.List; -@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class}) +@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class}) abstract class FxApplicationModule { @Provides @@ -59,16 +60,25 @@ abstract class FxApplicationModule { } @Provides + @FxApplicationScoped + static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) { + return builder.build(); + } + + @Provides + @FxApplicationScoped static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) { return builder.build(); } @Provides + @FxApplicationScoped static PreferencesComponent providePreferencesComponent(PreferencesComponent.Builder builder) { return builder.build(); } @Provides + @FxApplicationScoped static QuitComponent provideQuitComponent(QuitComponent.Builder builder) { return builder.build(); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java new file mode 100644 index 000000000..902576715 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java @@ -0,0 +1,98 @@ +package org.cryptomator.ui.fxapp; + +import org.cryptomator.common.LicenseHolder; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.UiTheme; +import org.cryptomator.integrations.uiappearance.Theme; +import org.cryptomator.integrations.uiappearance.UiAppearanceException; +import org.cryptomator.integrations.uiappearance.UiAppearanceListener; +import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.application.Application; +import javafx.beans.value.ObservableValue; +import java.util.Optional; + +@FxApplicationScoped +public class FxApplicationStyle { + + private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStyle.class); + + private final Settings settings; + private final Optional appearanceProvider; + private final LicenseHolder licenseHolder; + private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged; + + @Inject + public FxApplicationStyle(Settings settings, Optional appearanceProvider, LicenseHolder licenseHolder){ + this.settings = settings; + this.appearanceProvider = appearanceProvider; + this.licenseHolder = licenseHolder; + } + + public void initialize() { + settings.theme().addListener(this::appThemeChanged); + loadSelectedStyleSheet(settings.theme().get()); + } + + private void appThemeChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { + if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) { + try { + appearanceProvider.get().removeListener(systemInterfaceThemeListener); + } catch (UiAppearanceException e) { + LOG.error("Failed to disable automatic theme switching."); + } + } + loadSelectedStyleSheet(newValue); + } + + private void loadSelectedStyleSheet(UiTheme desiredTheme) { + UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT; + switch (theme) { + case LIGHT -> applyLightTheme(); + case DARK -> applyDarkTheme(); + case AUTOMATIC -> { + appearanceProvider.ifPresent(appearanceProvider -> { + try { + appearanceProvider.addListener(systemInterfaceThemeListener); + } catch (UiAppearanceException e) { + LOG.error("Failed to enable automatic theme switching."); + } + }); + applySystemTheme(); + } + } + } + + private void systemInterfaceThemeChanged(Theme theme) { + switch (theme) { + case LIGHT -> applyLightTheme(); + case DARK -> applyDarkTheme(); + } + } + + private void applySystemTheme() { + if (appearanceProvider.isPresent()) { + systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme()); + } else { + LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme..."); + applyLightTheme(); + } + } + + private void applyLightTheme() { + Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString()); + appearanceProvider.ifPresent(appearanceProvider -> { + appearanceProvider.adjustToTheme(Theme.LIGHT); + }); + } + + private void applyDarkTheme() { + Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString()); + appearanceProvider.ifPresent(appearanceProvider -> { + appearanceProvider.adjustToTheme(Theme.DARK); + }); + } +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java new file mode 100644 index 000000000..7e471bd11 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -0,0 +1,123 @@ +package org.cryptomator.ui.fxapp; + +import com.google.common.base.Preconditions; +import org.cryptomator.common.ShutdownHook; +import org.cryptomator.common.vaults.LockNotCompletedException; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.common.vaults.Volume; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.beans.Observable; +import javafx.collections.ObservableList; +import java.awt.Desktop; +import java.awt.desktop.QuitResponse; +import java.awt.desktop.QuitStrategy; +import java.util.EnumSet; +import java.util.EventObject; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.cryptomator.common.vaults.VaultState.Value.*; + +@FxApplicationScoped +public class FxApplicationTerminator { + + public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR); + + private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class); + + private final ObservableList vaults; + private final ShutdownHook shutdownHook; + private final FxApplicationWindows appWindows; + private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean(); + + @Inject + public FxApplicationTerminator(ObservableList vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows){ + this.vaults = vaults; + this.shutdownHook = shutdownHook; + this.appWindows = appWindows; + } + + public void initialize() { + Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported"); + Desktop desktop = Desktop.getDesktop(); + + // register quit handler + if (desktop.isSupported(Desktop.Action.APP_QUIT_HANDLER)) { + desktop.setQuitHandler(this::handleQuitRequest); + } + + // set quit strategy (cmd+q would call `System.exit(0)` otherwise) + if (desktop.isSupported(Desktop.Action.APP_QUIT_STRATEGY)) { + desktop.setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS); + } + + // allow sudden termination? + vaults.addListener(this::vaultListChanged); + + shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults); + } + + /** + * Gracefully terminates the application. + */ + public void terminate() { + handleQuitRequest(null, new NoopQuitResponse()); + } + + private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { + boolean allowSuddenTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains); + boolean stateChanged = allowQuitWithoutPrompt.compareAndSet(!allowSuddenTermination, allowSuddenTermination); + Desktop desktop = Desktop.getDesktop(); + if (stateChanged && desktop.isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { + if (allowSuddenTermination) { + LOG.debug("Enabling sudden termination"); + desktop.enableSuddenTermination(); + } else { + LOG.debug("Disabling sudden termination"); + desktop.disableSuddenTermination(); + } + } + } + + private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) { + var exitingResponse = new ExitingQuitResponse(response); + if (allowQuitWithoutPrompt.get()) { + exitingResponse.performQuit(); + } else { + appWindows.showQuitWindow(exitingResponse); + } + } + + private void forceUnmountRemainingVaults() { + for (Vault vault : vaults) { + if (vault.isUnlocked()) { + try { + vault.lock(true); + } catch (Volume.VolumeException e) { + LOG.error("Failed to unmount vault " + vault.getPath(), e); + } catch (LockNotCompletedException e) { + LOG.error("Failed to lock vault " + vault.getPath(), e); + } + } + } + } + + private class NoopQuitResponse implements QuitResponse { + + @Override + public void performQuit() { + // no-op + } + + @Override + public void cancelQuit() { + // no-op + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java new file mode 100644 index 000000000..fae7b1b9b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -0,0 +1,146 @@ +package org.cryptomator.ui.fxapp; + +import com.google.common.base.Preconditions; +import dagger.Lazy; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.integrations.tray.TrayIntegrationProvider; +import org.cryptomator.ui.common.ErrorComponent; +import org.cryptomator.ui.lock.LockComponent; +import org.cryptomator.ui.mainwindow.MainWindowComponent; +import org.cryptomator.ui.preferences.PreferencesComponent; +import org.cryptomator.ui.preferences.SelectedPreferencesTab; +import org.cryptomator.ui.quit.QuitComponent; +import org.cryptomator.ui.unlock.UnlockComponent; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableValue; +import javafx.collections.transformation.FilteredList; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.stage.Window; +import java.awt.Desktop; +import java.awt.desktop.AppReopenedListener; +import java.awt.desktop.QuitResponse; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +@FxApplicationScoped +public class FxApplicationWindows { + + private static final Logger LOG = LoggerFactory.getLogger(FxApplicationWindows.class); + + private final Stage primaryStage; + private final Optional trayIntegration; + private final Lazy mainWindow; + private final Lazy preferencesWindow; + private final Lazy quitWindow; + private final UnlockComponent.Factory unlockWorkflowFactory; + private final LockComponent.Factory lockWorkflowFactory; + private final ErrorComponent.Factory errorWindowFactory; + private final FilteredList visibleWindows; + + @Inject + public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional trayIntegration, Lazy mainWindow, Lazy preferencesWindow, Lazy quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory) { + this.primaryStage = primaryStage; + this.trayIntegration = trayIntegration; + this.mainWindow = mainWindow; + this.preferencesWindow = preferencesWindow; + this.quitWindow = quitWindow; + this.unlockWorkflowFactory = unlockWorkflowFactory; + this.lockWorkflowFactory = lockWorkflowFactory; + this.errorWindowFactory = errorWindowFactory; + this.visibleWindows = Stage.getWindows().filtered(Window::isShowing); + } + + public void initialize() { + Preconditions.checkState(Desktop.isDesktopSupported(), "java.awt.Desktop not supported"); + Desktop desktop = Desktop.getDesktop(); + + // register preferences shortcut + if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) { + desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY)); + } + + // register preferences shortcut + if (desktop.isSupported(Desktop.Action.APP_ABOUT)) { + desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT)); + } + + // register app reopen listener + if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) { + desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow()); + } + + // observe visible windows + if (trayIntegration.isPresent()) { + Bindings.isNotEmpty(visibleWindows).addListener(this::visibleWindowsChanged); + } + } + + private void visibleWindowsChanged(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) { + LOG.debug("has visible stages: {}", newValue); + if (newValue) { + trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray); + } else { + trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray); + } + } + + public CompletionStage showMainWindow() { + return CompletableFuture.supplyAsync(mainWindow.get()::showMainWindow, Platform::runLater); + } + + public CompletionStage showPreferencesWindow(SelectedPreferencesTab selectedTab) { + return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater); + } + + public CompletionStage showQuitWindow(QuitResponse response) { + return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater); + } + + public CompletionStage startUnlockWorkflow(Vault vault, @Nullable Stage owner) { + return CompletableFuture.supplyAsync(() -> { + Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked."); + LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); + return unlockWorkflowFactory.create(vault, owner).unlockWorkflow(); + }, Platform::runLater) // + .thenCompose(CompletableFuture::runAsync) // run unlock in forkjoin pool TODO: use executorservice + .exceptionally(e -> { + showErrorWindow(e, owner == null ? primaryStage : owner, null); + return null; + }); + } + + public CompletionStage startLockWorkflow(Vault vault, @Nullable Stage owner) { + return CompletableFuture.supplyAsync(() -> { + Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not unlocked."); + LOG.debug("Start lock workflow for {}", vault.getDisplayName()); + return lockWorkflowFactory.create(vault, owner).lockWorkflow(); + }, Platform::runLater) // + .thenCompose(CompletableFuture::runAsync) // run lock in forkjoin pool TODO: use executorservice + .exceptionally(e -> { + showErrorWindow(e, owner == null ? primaryStage : owner, null); + return null; + }); + } + + /** + * Displays the generic error scene in the given window. + * + * @param cause The exception to show + * @param window What window to display the scene in + * @param previousScene To what scene to return to when pressing "back". Back button will be hidden, if null + * @return A + */ + public CompletionStage showErrorWindow(Throwable cause, Stage window, @Nullable Scene previousScene) { + return CompletableFuture.supplyAsync(() -> errorWindowFactory.create(cause, window, previousScene).show(), Platform::runLater); + } + +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java b/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java new file mode 100644 index 000000000..e20b43525 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/PrimaryStage.java @@ -0,0 +1,14 @@ +package org.cryptomator.ui.fxapp; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface PrimaryStage { + +} diff --git a/src/main/java/org/cryptomator/ui/health/CheckListController.java b/src/main/java/org/cryptomator/ui/health/CheckListController.java index 75ecdef52..22ec37b48 100644 --- a/src/main/java/org/cryptomator/ui/health/CheckListController.java +++ b/src/main/java/org/cryptomator/ui/health/CheckListController.java @@ -1,9 +1,8 @@ package org.cryptomator.ui.health; import com.google.common.base.Preconditions; -import dagger.Lazy; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,9 +14,7 @@ import javafx.beans.property.ObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; -import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.CheckBox; import javafx.scene.control.ListView; import javafx.scene.control.SelectionMode; import javafx.stage.Stage; @@ -37,7 +34,7 @@ public class CheckListController implements FxController { private final ObjectProperty selectedCheck; private final BooleanBinding mainRunStarted; //TODO: rerunning not considered for now private final BooleanBinding somethingsRunning; - private final Lazy errorComponentBuilder; + private final FxApplicationWindows appWindows; private final IntegerBinding chosenTaskCount; private final BooleanBinding anyCheckSelected; private final CheckListCellFactory listCellFactory; @@ -46,7 +43,7 @@ public class CheckListController implements FxController { public ListView checksListView; @Inject - public CheckListController(@HealthCheckWindow Stage window, List checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty selectedCheck, Lazy errorComponentBuilder, CheckListCellFactory listCellFactory) { + public CheckListController(@HealthCheckWindow Stage window, List checks, CheckExecutor checkExecutor, ReportWriter reportWriteTask, ObjectProperty selectedCheck, FxApplicationWindows appWindows, CheckListCellFactory listCellFactory) { this.window = window; this.checks = FXCollections.observableList(checks, Check::observables); this.checkExecutor = checkExecutor; @@ -54,7 +51,7 @@ public class CheckListController implements FxController { this.chosenChecks = this.checks.filtered(Check::isChosenForExecution); this.reportWriter = reportWriteTask; this.selectedCheck = selectedCheck; - this.errorComponentBuilder = errorComponentBuilder; + this.appWindows = appWindows; this.chosenTaskCount = Bindings.size(this.chosenChecks); this.mainRunStarted = Bindings.isEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.RUNNABLE)); this.somethingsRunning = Bindings.isNotEmpty(this.checks.filtered(c -> c.getState() == Check.CheckState.SCHEDULED || c.getState() == Check.CheckState.RUNNING)); @@ -104,7 +101,7 @@ public class CheckListController implements FxController { reportWriter.writeReport(chosenChecks); } catch (IOException e) { LOG.error("Failed to write health check report.", e); - errorComponentBuilder.get().cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); } } diff --git a/src/main/java/org/cryptomator/ui/health/StartController.java b/src/main/java/org/cryptomator/ui/health/StartController.java index 44c3f3a8f..fa41a7fdc 100644 --- a/src/main/java/org/cryptomator/ui/health/StartController.java +++ b/src/main/java/org/cryptomator/ui/health/StartController.java @@ -7,10 +7,10 @@ import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfigLoadException; import org.cryptomator.cryptofs.VaultKeyInvalidException; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.unlock.UnlockCancelledException; import org.slf4j.Logger; @@ -40,10 +40,10 @@ public class StartController implements FxController { private final AtomicReference masterkeyRef; private final AtomicReference vaultConfigRef; private final Lazy checkScene; - private final Lazy errorComponent; + private final FxApplicationWindows appWindows; @Inject - public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy checkScene, Lazy errorComponent, @Named("unlockWindow") Stage unlockWindow) { + public StartController(@HealthCheckWindow Stage window, @HealthCheckWindow Vault vault, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy checkScene, FxApplicationWindows appWindows, @Named("unlockWindow") Stage unlockWindow) { this.window = window; this.unlockWindow = unlockWindow; this.vaultConfig = vault.getVaultConfigCache(); @@ -52,7 +52,7 @@ public class StartController implements FxController { this.masterkeyRef = masterkeyRef; this.vaultConfigRef = vaultConfigRef; this.checkScene = checkScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; } @FXML @@ -106,10 +106,10 @@ public class StartController implements FxController { // ok } else if (e instanceof VaultKeyInvalidException) { LOG.error("Invalid key"); //TODO: specific error screen - errorComponent.get().window(window).cause(e).build().showErrorScene(); + appWindows.showErrorWindow(e, window, null); } else { LOG.error("Failed to load key.", e); - errorComponent.get().window(window).cause(e).build().showErrorScene(); + appWindows.showErrorWindow(e, window, null); } } diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java b/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java deleted file mode 100644 index 710c6d435..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/AppLaunchEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.cryptomator.ui.launcher; - -import java.nio.file.Path; -import java.util.Collection; - -public class AppLaunchEvent { - - private final EventType type; - private final Collection pathsToOpen; - - public enum EventType { - REVEAL_APP, - OPEN_FILE - } - - public AppLaunchEvent(EventType type, Collection pathsToOpen) { - this.type = type; - this.pathsToOpen = pathsToOpen; - } - - public EventType getType() { - return type; - } - - public Collection getPathsToOpen() { - return pathsToOpen; - } -} diff --git a/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java b/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java deleted file mode 100644 index 66b75840b..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.cryptomator.ui.launcher; - -import org.cryptomator.common.ShutdownHook; -import org.cryptomator.common.vaults.LockNotCompletedException; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultState; -import org.cryptomator.common.vaults.Volume; -import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import javafx.application.Platform; -import javafx.beans.Observable; -import javafx.collections.ObservableList; -import java.awt.Desktop; -import java.awt.EventQueue; -import java.awt.desktop.AboutEvent; -import java.awt.desktop.QuitResponse; -import java.awt.desktop.QuitStrategy; -import java.util.EnumSet; -import java.util.EventObject; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.cryptomator.common.vaults.VaultState.Value.*; - -@Singleton -public class AppLifecycleListener { - - private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class); - public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR); - - private final FxApplicationStarter fxApplicationStarter; - private final CountDownLatch shutdownLatch; - private final ObservableList vaults; - private final AtomicBoolean allowQuitWithoutPrompt; - - @Inject - AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList vaults) { - this.fxApplicationStarter = fxApplicationStarter; - this.shutdownLatch = shutdownLatch; - this.vaults = vaults; - this.allowQuitWithoutPrompt = new AtomicBoolean(true); - vaults.addListener(this::vaultListChanged); - - // register preferences shortcut - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) { - Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow); - } - - // register preferences shortcut - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_ABOUT)) { - Desktop.getDesktop().setAboutHandler(this::showAboutWindow); - } - - // register quit handler - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) { - Desktop.getDesktop().setQuitHandler(this::handleQuitRequest); - } - - // set quit strategy (cmd+q would call `System.exit(0)` otherwise) - if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_STRATEGY)) { - Desktop.getDesktop().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS); - } - - shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults); - } - - /** - * Gracefully terminates the application. - */ - public void quit() { - handleQuitRequest(null, new QuitResponse() { - @Override - public void performQuit() { - // no-op - } - - @Override - public void cancelQuit() { - // no-op - } - }); - } - - private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) { - QuitResponse decoratedQuitResponse = decorateQuitResponse(response); - if (allowQuitWithoutPrompt.get()) { - decoratedQuitResponse.performQuit(); - } else { - fxApplicationStarter.get().thenAccept(app -> app.showQuitWindow(decoratedQuitResponse)); - } - } - - private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) { - return new QuitResponse() { - @Override - public void performQuit() { - Platform.exit(); // will be no-op, if JavaFX never started. - shutdownLatch.countDown(); // main thread is waiting for this latch - originalQuitResponse.performQuit(); - } - - @Override - public void cancelQuit() { - originalQuitResponse.cancelQuit(); - } - }; - } - - private void vaultListChanged(@SuppressWarnings("unused") Observable observable) { - assert Platform.isFxApplicationThread(); - boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains); - boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination); - if (suddenTerminationChanged) { - LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination); - } - } - - private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { - fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY)); - } - - private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) { - fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT)); - } - - private void forceUnmountRemainingVaults() { - for (Vault vault : vaults) { - if (vault.isUnlocked()) { - try { - vault.lock(true); - } catch (Volume.VolumeException e) { - LOG.error("Failed to unmount vault " + vault.getPath(), e); - } catch (LockNotCompletedException e) { - LOG.error("Failed to lock vault " + vault.getPath(), e); - } - } - } - } - -} diff --git a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java b/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java deleted file mode 100644 index 32f41185b..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.cryptomator.ui.launcher; - -import com.google.common.base.Preconditions; -import org.cryptomator.ui.fxapp.FxApplication; -import org.cryptomator.ui.fxapp.FxApplicationComponent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javafx.application.Application; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import java.lang.ref.WeakReference; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -@Singleton -public class FxApplicationStarter { - - private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class); - private static final AtomicReference FX_APP_COMP_BUILDER = new AtomicReference<>(); - private static final CompletableFuture FUTURE = new CompletableFuture<>(); - - private final ExecutorService executor; - private final AtomicBoolean started; - - @Inject - public FxApplicationStarter(FxApplicationComponent.Builder fxAppCompBuilder, ExecutorService executor) { - FX_APP_COMP_BUILDER.set(fxAppCompBuilder); - this.executor = executor; - this.started = new AtomicBoolean(); - } - - public CompletionStage get() { - if (!started.getAndSet(true)) { - start(); - } - return FUTURE; - } - - private void start() { - executor.submit(() -> { - LOG.debug("Starting JavaFX runtime..."); - Application.launch(CryptomatorGui.class); - }); - } - - public static class CryptomatorGui extends Application { - - @Override - public void start(Stage primaryStage) throws Exception { - var builder = Objects.requireNonNull(FX_APP_COMP_BUILDER.get()); // TODO add message? - - // set defaults for primary stage: - // TODO: invoke StageFactory stuff... - primaryStage.setTitle("Cryptomator"); - primaryStage.initStyle(StageStyle.UNDECORATED); - primaryStage.setMinWidth(650); - primaryStage.setMinHeight(440); - - // build subcomponent - var comp = builder.mainWindow(primaryStage).fxApplication(this).build(); - - // call delegate - var app = comp.application(); - app.start(); - FUTURE.complete(app); - } - } -} diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java deleted file mode 100644 index 08461a56d..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.cryptomator.ui.launcher; - -import dagger.Lazy; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.ui.fxapp.FxApplication; -import org.cryptomator.ui.traymenu.TrayMenuComponent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javafx.collections.ObservableList; -import java.awt.Desktop; -import java.awt.SystemTray; -import java.awt.desktop.AppReopenedListener; -import java.util.Collection; -import java.util.Optional; - -@Singleton -public class UiLauncher { - - private static final Logger LOG = LoggerFactory.getLogger(UiLauncher.class); - - private final Settings settings; - private final ObservableList vaults; - private final Lazy trayMenu; - private final FxApplicationStarter fxApplicationStarter; - private final AppLaunchEventHandler launchEventHandler; - private final Optional trayIntegration; - - @Inject - public UiLauncher(Settings settings, ObservableList vaults, Lazy trayMenu, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional trayIntegration) { - this.settings = settings; - this.vaults = vaults; - this.trayMenu = trayMenu; - this.fxApplicationStarter = fxApplicationStarter; - this.launchEventHandler = launchEventHandler; - this.trayIntegration = trayIntegration; - } - - public void launch() { - boolean hidden = settings.startHidden().get(); - if (SystemTray.isSupported() && settings.showTrayIcon().get()) { - trayMenu.get().initializeTrayIcon(); - launch(true, hidden); - } else { - launch(false, hidden); - } - } - - private void launch(boolean withTrayIcon, boolean hidden) { - // start hidden, minimized or normal? - if (withTrayIcon && hidden) { - LOG.debug("Hiding application..."); - trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray); - } else if (!withTrayIcon && hidden) { - LOG.debug("Minimizing application..."); - showMainWindowAsync(true); - } else { - LOG.debug("Showing application..."); - showMainWindowAsync(false); - } - - // register app reopen listener - Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(false)); - - // auto unlock - Collection vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock); - if (!vaultsToAutoUnlock.isEmpty()) { - fxApplicationStarter.get().thenAccept(app -> { - for (Vault vault : vaultsToAutoUnlock) { - app.startUnlockWorkflow(vault, Optional.empty()); - } - }); - } - - launchEventHandler.startHandlingLaunchEvents(); - } - - private boolean shouldAttemptAutoUnlock(Vault vault) { - return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get(); - } - - private void showMainWindowAsync(boolean minimize) { - fxApplicationStarter.get().thenCompose(FxApplication::showMainWindow).thenAccept(win -> win.setIconified(minimize)); - } - -} diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java deleted file mode 100644 index fa4631af4..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.cryptomator.ui.launcher; - -import dagger.Module; -import dagger.Provides; -import org.cryptomator.common.PluginClassLoader; -import org.cryptomator.integrations.autostart.AutoStartProvider; -import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; -import org.cryptomator.ui.fxapp.FxApplicationComponent; -import org.cryptomator.ui.traymenu.TrayMenuComponent; - -import javax.inject.Named; -import javax.inject.Singleton; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.ServiceLoader; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; - -@Module(subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class}) -public abstract class UiLauncherModule { - - @Provides - @Singleton - static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) { - return builder.build(); - } - - @Provides - @Singleton - static Optional provideAppearanceProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst(); - } - - @Provides - @Singleton - static Optional provideAutostartProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst(); - } - - - @Provides - @Singleton - static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) { - return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst(); - } - - @Provides - @Singleton - static ResourceBundle provideLocalization() { - return ResourceBundle.getBundle("i18n.strings"); - } - - @Provides - @Singleton - @Named("launchEventQueue") - static BlockingQueue provideFileOpenRequests() { - return new ArrayBlockingQueue<>(10); - } - -} diff --git a/src/main/java/org/cryptomator/ui/lock/LockComponent.java b/src/main/java/org/cryptomator/ui/lock/LockComponent.java index 9796c88c7..eda81f7f6 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockComponent.java +++ b/src/main/java/org/cryptomator/ui/lock/LockComponent.java @@ -2,11 +2,11 @@ package org.cryptomator.ui.lock; import dagger.BindsInstance; import dagger.Subcomponent; +import org.cryptomator.common.Nullable; import org.cryptomator.common.vaults.Vault; import javax.inject.Named; import javafx.stage.Stage; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -25,15 +25,9 @@ public interface LockComponent { return workflow; } - @Subcomponent.Builder - interface Builder { - - @BindsInstance - LockComponent.Builder vault(@LockWindow Vault vault); - - @BindsInstance - LockComponent.Builder owner(@Named("lockWindowOwner") Optional owner); - - LockComponent build(); + @Subcomponent.Factory + interface Factory { + LockComponent create(@BindsInstance @LockWindow Vault vault, @BindsInstance @Named("lockWindowOwner") @Nullable Stage owner); } + } diff --git a/src/main/java/org/cryptomator/ui/lock/LockModule.java b/src/main/java/org/cryptomator/ui/lock/LockModule.java index ddee13dff..6a82f2bab 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockModule.java +++ b/src/main/java/org/cryptomator/ui/lock/LockModule.java @@ -12,6 +12,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; +import org.jetbrains.annotations.Nullable; import javax.inject.Named; import javax.inject.Provider; @@ -43,12 +44,12 @@ abstract class LockModule { @Provides @LockWindow @LockScoped - static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Named("lockWindowOwner") Optional owner) { + static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Nullable @Named("lockWindowOwner") Stage owner) { Stage stage = factory.create(); stage.setTitle(vault.getDisplayName()); stage.setResizable(false); - if (owner.isPresent()) { - stage.initOwner(owner.get()); + if (owner != null) { + stage.initOwner(owner); stage.initModality(Modality.WINDOW_MODAL); } else { stage.initModality(Modality.APPLICATION_MODAL); diff --git a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java index 1e05ceb73..2d4961dde 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java @@ -5,9 +5,9 @@ import org.cryptomator.common.vaults.LockNotCompletedException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.common.vaults.Volume; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,16 +40,16 @@ public class LockWorkflow extends Task { private final AtomicReference> forceRetryDecision; private final Lazy lockForcedScene; private final Lazy lockFailedScene; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; @Inject - public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) { + public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, FxApplicationWindows appWindows) { this.lockWindow = lockWindow; this.vault = vault; this.forceRetryDecision = forceRetryDecision; this.lockForcedScene = lockForcedScene; this.lockFailedScene = lockFailedScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; } @Override @@ -109,7 +109,7 @@ public class LockWorkflow extends Task { lockWindow.setScene(lockFailedScene.get()); lockWindow.show(); } else { - errorComponent.cause(throwable).window(lockWindow).build().showErrorScene(); + appWindows.showErrorWindow(throwable, lockWindow, null); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java index 51143e1f6..22f3616ea 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindow.java @@ -9,6 +9,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Documented @Retention(RUNTIME) -public @interface MainWindow { +@interface MainWindow { } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index f9372ba08..fb65b4707 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -13,6 +13,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.migration.MigrationComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent; @@ -34,6 +35,11 @@ import java.util.ResourceBundle; @Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) abstract class MainWindowModule { + @Binds + @MainWindow + @MainWindowScoped + abstract Stage bindMainWindow(@PrimaryStage Stage primaryStage); + @Provides @MainWindowScoped static ObjectProperty provideSelectedVault() { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java index c8107415e..c13e4417c 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java @@ -4,8 +4,9 @@ import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.fxapp.FxApplicationTerminator; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.UpdateChecker; -import org.cryptomator.ui.launcher.AppLifecycleListener; import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; @@ -25,9 +26,9 @@ public class MainWindowTitleController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class); - private final AppLifecycleListener appLifecycle; private final Stage window; - private final FxApplication application; + private final FxApplicationTerminator terminator; + private final FxApplicationWindows appWindows; private final boolean trayMenuInitialized; private final UpdateChecker updateChecker; private final BooleanBinding updateAvailable; @@ -40,10 +41,10 @@ public class MainWindowTitleController implements FxController { private double yOffset; @Inject - MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) { - this.appLifecycle = appLifecycle; + MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) { this.window = window; - this.application = application; + this.terminator = terminator; + this.appWindows = appWindows; this.trayMenuInitialized = trayMenu.isInitialized(); this.updateChecker = updateChecker; this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); @@ -96,7 +97,7 @@ public class MainWindowTitleController implements FxController { if (trayMenuInitialized) { window.close(); } else { - appLifecycle.quit(); + terminator.terminate(); } } @@ -107,17 +108,17 @@ public class MainWindowTitleController implements FxController { @FXML public void showPreferences() { - application.showPreferencesWindow(SelectedPreferencesTab.ANY); + appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY); } @FXML public void showGeneralPreferences() { - application.showPreferencesWindow(SelectedPreferencesTab.GENERAL); + appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL); } @FXML public void showDonationKeyPreferences() { - application.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE); + appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE); } /* Getter/Setter */ diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java index 5fee2e6d1..af550b73e 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java @@ -5,6 +5,7 @@ import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -23,16 +24,16 @@ import java.util.Optional; public class VaultDetailLockedController implements FxController { private final ReadOnlyObjectProperty vault; - private final FxApplication application; + private final FxApplicationWindows appWindows; private final VaultOptionsComponent.Builder vaultOptionsWindow; private final KeychainManager keychain; private final Stage mainWindow; private final BooleanExpression passwordSaved; @Inject - VaultDetailLockedController(ObjectProperty vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) { + VaultDetailLockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) { this.vault = vault; - this.application = application; + this.appWindows = appWindows; this.vaultOptionsWindow = vaultOptionsWindow; this.keychain = keychain; this.mainWindow = mainWindow; @@ -45,7 +46,7 @@ public class VaultDetailLockedController implements FxController { @FXML public void unlock() { - application.startUnlockWorkflow(vault.get(), Optional.of(mainWindow)); + appWindows.startUnlockWorkflow(vault.get(), mainWindow); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java index 22365da7c..6e40d54b3 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java @@ -1,15 +1,13 @@ package org.cryptomator.ui.mainwindow; -import com.tobiasdiez.easybind.EasyBind; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.removevault.RemoveVaultComponent; import javax.inject.Inject; import javax.inject.Named; -import javafx.beans.binding.Binding; import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.stage.Stage; @@ -18,21 +16,21 @@ import javafx.stage.Stage; public class VaultDetailUnknownErrorController implements FxController { private final ObjectProperty vault; - private final ErrorComponent.Builder errorComponentBuilder; + private final FxApplicationWindows appWindows; private final Stage errorWindow; private final RemoveVaultComponent.Builder removeVault; @Inject - public VaultDetailUnknownErrorController(ObjectProperty vault, ErrorComponent.Builder errorComponentBuilder, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) { + public VaultDetailUnknownErrorController(ObjectProperty vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) { this.vault = vault; - this.errorComponentBuilder = errorComponentBuilder; + this.appWindows = appWindows; this.errorWindow = errorWindow; this.removeVault = removeVault; } @FXML public void showError() { - errorComponentBuilder.window(errorWindow).cause(vault.get().getLastKnownException()).build().showErrorScene(); + appWindows.showErrorWindow(vault.get().getLastKnownException(), errorWindow, null); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java index 0af909bbc..63d88a6a4 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java @@ -6,7 +6,7 @@ import com.google.common.cache.LoadingCache; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.VaultService; -import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.stats.VaultStatisticsComponent; import javax.inject.Inject; @@ -14,22 +14,21 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; import javafx.stage.Stage; -import java.util.Optional; @MainWindowScoped public class VaultDetailUnlockedController implements FxController { private final ReadOnlyObjectProperty vault; - private final FxApplication application; + private final FxApplicationWindows appWindows; private final VaultService vaultService; private final Stage mainWindow; private final LoadingCache vaultStats; private final VaultStatisticsComponent.Builder vaultStatsBuilder; @Inject - public VaultDetailUnlockedController(ObjectProperty vault, FxApplication application, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) { + public VaultDetailUnlockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultService vaultService, VaultStatisticsComponent.Builder vaultStatsBuilder, @MainWindow Stage mainWindow) { this.vault = vault; - this.application = application; + this.appWindows = appWindows; this.vaultService = vaultService; this.mainWindow = mainWindow; this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats)); @@ -47,7 +46,7 @@ public class VaultDetailUnlockedController implements FxController { @FXML public void lock() { - application.startLockWorkflow(vault.get(), Optional.of(mainWindow)); + appWindows.startLockWorkflow(vault.get(), mainWindow); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index 145618fa8..c9d788b90 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -7,7 +7,8 @@ import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; +import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -18,7 +19,6 @@ import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.stage.Stage; import java.util.EnumSet; -import java.util.Optional; import static org.cryptomator.common.vaults.VaultState.Value.*; @@ -27,7 +27,8 @@ public class VaultListContextMenuController implements FxController { private final ObservableOptionalValue selectedVault; private final Stage mainWindow; - private final FxApplication application; + private final FxApplicationWindows appWindows; + private final VaultService vaultService; private final KeychainManager keychain; private final RemoveVaultComponent.Builder removeVault; private final VaultOptionsComponent.Builder vaultOptionsWindow; @@ -38,10 +39,11 @@ public class VaultListContextMenuController implements FxController { private final Binding selectedVaultLockable; @Inject - VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplication application, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) { + VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) { this.selectedVault = EasyBind.wrapNullable(selectedVault); this.mainWindow = mainWindow; - this.application = application; + this.appWindows = appWindows; + this.vaultService = vaultService; this.keychain = keychain; this.removeVault = removeVault; this.vaultOptionsWindow = vaultOptionsWindow; @@ -74,22 +76,20 @@ public class VaultListContextMenuController implements FxController { @FXML public void didClickUnlockVault() { selectedVault.ifValuePresent(v -> { - application.startUnlockWorkflow(v, Optional.of(mainWindow)); + appWindows.startUnlockWorkflow(v, mainWindow); }); } @FXML public void didClickLockVault() { selectedVault.ifValuePresent(v -> { - application.startLockWorkflow(v, Optional.of(mainWindow)); + appWindows.startLockWorkflow(v, mainWindow); }); } @FXML public void didClickRevealVault() { - selectedVault.ifValuePresent(v -> { - application.getVaultService().reveal(v); - }); + selectedVault.ifValuePresent(vaultService::reveal); } // Getter and Setter diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationModule.java b/src/main/java/org/cryptomator/ui/migration/MigrationModule.java index 44f6960b1..76f6ac161 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationModule.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationModule.java @@ -6,13 +6,13 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker; import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.PrimaryStage; import javax.inject.Named; import javax.inject.Provider; @@ -37,7 +37,7 @@ abstract class MigrationModule { @Provides @MigrationWindow @MigrationScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) { + static Stage provideStage(StageFactory factory, @PrimaryStage Stage owner, ResourceBundle resourceBundle) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("migration.title")); stage.setResizable(false); diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java b/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java index 503814b25..c68456523 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java @@ -12,12 +12,12 @@ import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,7 +58,7 @@ public class MigrationRunController implements FxController { private final ScheduledExecutorService scheduler; private final KeychainManager keychain; private final ObjectProperty missingCapability; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; private final Lazy startScene; private final Lazy successScene; private final Lazy impossibleScene; @@ -73,14 +73,14 @@ public class MigrationRunController implements FxController { public NiceSecurePasswordField passwordField; @Inject - public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy impossibleScene, ErrorComponent.Builder errorComponent) { + public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy impossibleScene, FxApplicationWindows appWindows) { this.window = window; this.vault = vault; this.executor = executor; this.scheduler = scheduler; this.keychain = keychain; this.missingCapability = missingCapability; - this.errorComponent = errorComponent; + this.appWindows = appWindows; this.startScene = startScene; this.successScene = successScene; this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty()); @@ -146,12 +146,12 @@ public class MigrationRunController implements FxController { }).onError(FileNameTooLongException.class, e -> { LOG.error("Migration failed because the underlying file system does not support long filenames.", e); vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION); - errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, startScene.get()); window.setScene(impossibleScene.get()); }).onError(Exception.class, e -> { // including RuntimeExceptions LOG.error("Migration failed for technical reasons.", e); vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION); - errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, startScene.get()); }).andFinally(() -> { passwordField.setDisable(false); progressSyncTask.cancel(true); diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java b/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java index 1a5de5647..fab680eac 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationSuccessController.java @@ -2,25 +2,24 @@ package org.cryptomator.ui.migration; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.fxapp.PrimaryStage; import javax.inject.Inject; import javafx.fxml.FXML; import javafx.stage.Stage; -import java.util.Optional; @MigrationScoped public class MigrationSuccessController implements FxController { - private final FxApplication fxApplication; + private final FxApplicationWindows appWindows; private final Stage window; private final Vault vault; private final Stage mainWindow; @Inject - MigrationSuccessController(FxApplication fxApplication, @MigrationWindow Stage window, @MigrationWindow Vault vault, @MainWindow Stage mainWindow) { - this.fxApplication = fxApplication; + MigrationSuccessController(FxApplicationWindows appWindows, @MigrationWindow Stage window, @MigrationWindow Vault vault, @PrimaryStage Stage mainWindow) { + this.appWindows = appWindows; this.window = window; this.vault = vault; this.mainWindow = mainWindow; @@ -29,7 +28,7 @@ public class MigrationSuccessController implements FxController { @FXML public void unlockAndClose() { close(); - fxApplication.startUnlockWorkflow(vault, Optional.of(mainWindow)); + appWindows.startUnlockWorkflow(vault, mainWindow); } @FXML diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index fafea3f2f..7430cf207 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -7,8 +7,8 @@ import org.cryptomator.common.settings.UiTheme; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +47,7 @@ public class GeneralPreferencesController implements FxController { private final Application application; private final Environment environment; private final Set keychainAccessProviders; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; public ChoiceBox themeChoiceBox; public ChoiceBox keychainBackendChoiceBox; public CheckBox showMinimizeButtonCheckbox; @@ -59,9 +59,8 @@ public class GeneralPreferencesController implements FxController { public RadioButton nodeOrientationLtr; public RadioButton nodeOrientationRtl; - @Inject - GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) { + GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) { this.window = window; this.settings = settings; this.trayMenuInitialized = trayMenu.isInitialized(); @@ -73,7 +72,7 @@ public class GeneralPreferencesController implements FxController { this.resourceBundle = resourceBundle; this.application = application; this.environment = environment; - this.errorComponent = errorComponent; + this.appWindows = appWindows; } @FXML @@ -142,7 +141,7 @@ public class GeneralPreferencesController implements FxController { } catch (ToggleAutoStartFailedException e) { autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state LOG.error("Failed to toggle autostart.", e); - errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(e, window, window.getScene()); } }); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java index 104794604..87dcbd0e8 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java @@ -5,11 +5,11 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,18 +33,18 @@ public class RecoveryKeyCreationController implements FxController { private final ExecutorService executor; private final RecoveryKeyFactory recoveryKeyFactory; private final StringProperty recoveryKeyProperty; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; public NiceSecurePasswordField passwordField; @Inject - public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, ErrorComponent.Builder errorComponent) { + public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows) { this.window = window; this.successScene = successScene; this.vault = vault; this.executor = executor; this.recoveryKeyFactory = recoveryKeyFactory; this.recoveryKeyProperty = recoveryKey; - this.errorComponent = errorComponent; + this.appWindows = appWindows; } @FXML @@ -63,7 +63,7 @@ public class RecoveryKeyCreationController implements FxController { Animations.createShakeWindowAnimation(window).play(); } else { LOG.error("Creation of recovery key failed.", task.getException()); - errorComponent.cause(task.getException()).window(window).returnToScene(window.getScene()).build().showErrorScene(); + appWindows.showErrorWindow(task.getException(), window, window.getScene()); } }); executor.submit(task); diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 705991287..ca3c4e041 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -2,11 +2,11 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.NewPasswordController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,19 +31,19 @@ public class RecoveryKeyResetPasswordController implements FxController { private final ExecutorService executor; private final StringProperty recoveryKey; private final Lazy recoverScene; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; public NewPasswordController newPasswordController; @Inject - public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverScene, ErrorComponent.Builder errorComponent) { + public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverScene, FxApplicationWindows appWindows) { this.window = window; this.vault = vault; this.recoveryKeyFactory = recoveryKeyFactory; this.executor = executor; this.recoveryKey = recoveryKey; this.recoverScene = recoverScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; } @FXML @@ -64,7 +64,7 @@ public class RecoveryKeyResetPasswordController implements FxController { }); task.setOnFailed(event -> { LOG.error("Resetting password failed.", task.getException()); - errorComponent.cause(task.getException()).window(window).returnToScene(recoverScene.get()).build().showErrorScene(); + appWindows.showErrorWindow(task.getException(), window, recoverScene.get()); }); executor.submit(task); } diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java index 5915766ed..4288e9c7c 100644 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java +++ b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java @@ -5,13 +5,13 @@ import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.PrimaryStage; import javax.inject.Provider; import javafx.scene.Scene; @@ -33,12 +33,12 @@ abstract class RemoveVaultModule { @Provides @RemoveVaultWindow @RemoveVaultScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) { + static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("removeVault.title")); stage.setResizable(false); stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(owner); + stage.initOwner(primaryStage); return stage; } diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index dd08d5dc0..32f6cbc52 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -1,9 +1,9 @@ package org.cryptomator.ui.traymenu; import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.fxapp.FxApplication; -import org.cryptomator.ui.launcher.AppLifecycleListener; -import org.cryptomator.ui.launcher.FxApplicationStarter; +import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.fxapp.FxApplicationTerminator; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.preferences.SelectedPreferencesTab; import javax.inject.Inject; @@ -16,7 +16,6 @@ import java.awt.PopupMenu; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.EventObject; -import java.util.Optional; import java.util.ResourceBundle; import java.util.function.Consumer; @@ -24,16 +23,18 @@ import java.util.function.Consumer; class TrayMenuController { private final ResourceBundle resourceBundle; - private final AppLifecycleListener appLifecycle; - private final FxApplicationStarter fxApplicationStarter; + private final VaultService vaultService; + private final FxApplicationWindows appWindows; + private final FxApplicationTerminator appTerminator; private final ObservableList vaults; private final PopupMenu menu; @Inject - TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList vaults) { + TrayMenuController(ResourceBundle resourceBundle, VaultService vaultService, FxApplicationWindows appWindows, FxApplicationTerminator appTerminator, ObservableList vaults) { this.resourceBundle = resourceBundle; - this.appLifecycle = appLifecycle; - this.fxApplicationStarter = fxApplicationStarter; + this.vaultService = vaultService; + this.appWindows = appWindows; + this.appTerminator = appTerminator; this.vaults = vaults; this.menu = new PopupMenu(); } @@ -110,35 +111,31 @@ class TrayMenuController { } private void quitApplication(EventObject actionEvent) { - appLifecycle.quit(); + appTerminator.terminate(); } private void unlockVault(Vault vault) { - showMainAppAndThen(app -> app.startUnlockWorkflow(vault, Optional.empty())); + appWindows.startUnlockWorkflow(vault, null); } private void lockVault(Vault vault) { - showMainAppAndThen(app -> app.startLockWorkflow(vault, Optional.empty())); + appWindows.startLockWorkflow(vault, null); } private void lockAllVaults(ActionEvent actionEvent) { - showMainAppAndThen(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false)); + vaultService.lockAll(vaults.filtered(Vault::isUnlocked), false); } private void revealVault(Vault vault) { - showMainAppAndThen(app -> app.getVaultService().reveal(vault)); + vaultService.reveal(vault); } void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) { - showMainAppAndThen(app -> app.showMainWindow()); + appWindows.showMainWindow(); } private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { - showMainAppAndThen(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY)); - } - - private void showMainAppAndThen(Consumer action) { - fxApplicationStarter.get().thenAccept(action); + appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY); } } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java b/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java index 9c0338c5b..67e905200 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockComponent.java @@ -7,11 +7,11 @@ package org.cryptomator.ui.unlock; import dagger.BindsInstance; import dagger.Subcomponent; +import org.cryptomator.common.Nullable; import org.cryptomator.common.vaults.Vault; import javax.inject.Named; import javafx.stage.Stage; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -29,16 +29,9 @@ public interface UnlockComponent { return workflow; } - @Subcomponent.Builder - interface Builder { - - @BindsInstance - Builder vault(@UnlockWindow Vault vault); - - @BindsInstance - Builder owner(@Named("unlockWindowOwner") Optional owner); - - UnlockComponent build(); + @Subcomponent.Factory + interface Factory { + UnlockComponent create(@BindsInstance @UnlockWindow Vault vault, @BindsInstance @Named("unlockWindowOwner") @Nullable Stage owner); } } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index 3c1267e65..348d34832 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -14,6 +14,7 @@ import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; +import org.jetbrains.annotations.Nullable; import javax.inject.Named; import javax.inject.Provider; @@ -37,12 +38,12 @@ abstract class UnlockModule { @Provides @UnlockWindow @UnlockScoped - static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault, @Named("unlockWindowOwner") Optional owner) { + static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault, @Nullable @Named("unlockWindowOwner") Stage owner) { Stage stage = factory.create(); stage.setTitle(vault.getDisplayName()); stage.setResizable(false); - if (owner.isPresent()) { - stage.initOwner(owner.get()); + if (owner != null) { + stage.initOwner(owner); stage.initModality(Modality.WINDOW_MODAL); } else { stage.initModality(Modality.APPLICATION_MODAL); diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 6964c3c86..45b9bf000 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -9,10 +9,10 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.common.vaults.Volume.VolumeException; import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,17 +42,17 @@ public class UnlockWorkflow extends Task { private final VaultService vaultService; private final Lazy successScene; private final Lazy invalidMountPointScene; - private final ErrorComponent.Builder errorComponent; + private final FxApplicationWindows appWindows; private final KeyLoadingStrategy keyLoadingStrategy; @Inject - UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy invalidMountPointScene, ErrorComponent.Builder errorComponent, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) { + UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) { this.window = window; this.vault = vault; this.vaultService = vaultService; this.successScene = successScene; this.invalidMountPointScene = invalidMountPointScene; - this.errorComponent = errorComponent; + this.appWindows = appWindows; this.keyLoadingStrategy = keyLoadingStrategy; } @@ -118,7 +118,7 @@ public class UnlockWorkflow extends Task { private void handleGenericError(Throwable e) { LOG.error("Unlock failed for technical reasons.", e); - errorComponent.cause(e).window(window).build().showErrorScene(); + appWindows.showErrorWindow(e, window, null); } @Override diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java index cb6c109b9..e6966da64 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java @@ -13,7 +13,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import javax.inject.Provider; @@ -44,14 +44,14 @@ abstract class VaultOptionsModule { @Provides @VaultOptionsWindow @VaultOptionsScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage owner, @VaultOptionsWindow Vault vault) { + static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @VaultOptionsWindow Vault vault) { Stage stage = factory.create(); stage.setTitle(vault.getDisplayName()); stage.setResizable(true); stage.setMinWidth(400); stage.setMinHeight(300); stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(owner); + stage.initOwner(primaryStage); return stage; } diff --git a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java index 7600146e5..19dd486fb 100644 --- a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java +++ b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java @@ -5,13 +5,13 @@ import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.fxapp.PrimaryStage; import javax.inject.Provider; import javafx.scene.Scene; @@ -33,11 +33,11 @@ abstract class WrongFileAlertModule { @Provides @WrongFileAlertWindow @WrongFileAlertScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage mainWindow, ResourceBundle resourceBundle) { + static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("wrongFileAlert.title")); stage.setResizable(false); - stage.initOwner(mainWindow); + stage.initOwner(primaryStage); stage.initModality(Modality.WINDOW_MODAL); return stage; } diff --git a/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java b/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java index e249e6412..bb9cabbf3 100644 --- a/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java +++ b/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java @@ -5,7 +5,6 @@ *******************************************************************************/ package org.cryptomator.launcher; -import org.cryptomator.ui.launcher.AppLaunchEvent; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; @@ -43,7 +42,7 @@ public class FileOpenRequestHandlerTest { AppLaunchEvent evt = queue.poll(); Assertions.assertNotNull(evt); - Collection paths = evt.getPathsToOpen(); + Collection paths = evt.pathsToOpen(); MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar"))); } From e2184ec0096c91ba44d59ec114e6860b243d6dee Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 08:20:49 +0200 Subject: [PATCH 11/83] upload .msi and .exe to AV allowlisting servers --- .github/workflows/win-exe.yml | 44 ++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index ec5b60ed4..58b9d2bb6 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -279,4 +279,46 @@ jobs: token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | Cryptomator-*.exe - Cryptomator-*.asc \ No newline at end of file + Cryptomator-*.asc + + allowlist: + name: Anti Virus Allowlisting + # if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + needs: [build-msi, build-exe] + steps: + - name: Download .msi + uses: actions/download-artifact@v2 + with: + name: msi + path: msi + - name: Download .exe + uses: actions/download-artifact@v2 + with: + name: exe + path: exe + - name: Collect files + run: | + mkdir files + cp msi/*.msi files + cp exe/*.exe files + - name: Upload to Kaspersky + uses: SamKirkland/FTP-Deploy-Action@4.3.0 + with: + protocol: ftps + server: allowlist.kaspersky-labs.com + port: 990 + username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }} + password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }} + local-dir: files/ + dry-run: true + - name: Upload to Avast + uses: SamKirkland/FTP-Deploy-Action@4.3.0 + with: + protocol: ftp + server: whitelisting.avast.com + port: 21 + username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }} + password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }} + local-dir: files/ + dry-run: true \ No newline at end of file From 28db04e6219d1a17972eda71c7a6d8fbae084be5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 08:37:00 +0200 Subject: [PATCH 12/83] arm av allowlisting [ci skip] --- .github/workflows/win-exe.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 58b9d2bb6..d2b568860 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -283,7 +283,7 @@ jobs: allowlist: name: Anti Virus Allowlisting - # if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest needs: [build-msi, build-exe] steps: @@ -311,7 +311,6 @@ jobs: username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }} password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }} local-dir: files/ - dry-run: true - name: Upload to Avast uses: SamKirkland/FTP-Deploy-Action@4.3.0 with: @@ -321,4 +320,3 @@ jobs: username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }} password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }} local-dir: files/ - dry-run: true \ No newline at end of file From d4754448290e6956b650da11bcf95c93cbe57ea7 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 09:59:16 +0200 Subject: [PATCH 13/83] fixed some code smells --- .../java/org/cryptomator/launcher/Cryptomator.java | 2 +- .../org/cryptomator/launcher/CryptomatorModule.java | 2 -- .../ChooseExistingVaultController.java | 4 ---- .../org/cryptomator/ui/common/ErrorComponent.java | 1 - .../org/cryptomator/ui/fxapp/FxApplicationStyle.java | 12 ++++-------- .../ui/fxapp/FxApplicationTerminator.java | 3 +-- .../cryptomator/ui/fxapp/FxApplicationWindows.java | 11 +++++++---- .../java/org/cryptomator/ui/lock/LockModule.java | 1 - .../ui/mainwindow/MainWindowTitleController.java | 1 - .../ui/mainwindow/VaultDetailController.java | 2 -- .../ui/mainwindow/VaultDetailLockedController.java | 4 ---- .../ui/migration/MigrationImpossibleController.java | 1 - .../java/org/cryptomator/ui/unlock/UnlockModule.java | 1 - 13 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 1a9d0b79b..a358126d4 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -99,7 +99,7 @@ public class Cryptomator { Application.launch(MainApp.class); LOG.info("UI shut down"); return 0; - } catch (Throwable e) { + } catch (Exception e) { LOG.error("Terminating due to error", e); return 1; } diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java index 414ba208e..e6aab0309 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java @@ -7,7 +7,6 @@ import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.tray.TrayIntegrationProvider; import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; import org.cryptomator.ui.fxapp.FxApplicationComponent; -import org.cryptomator.ui.traymenu.TrayMenuComponent; import javax.inject.Named; import javax.inject.Singleton; @@ -16,7 +15,6 @@ import java.util.ResourceBundle; import java.util.ServiceLoader; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CountDownLatch; @Module(subcomponents = {FxApplicationComponent.class}) class CryptomatorModule { diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index deaa9fdde..01a8a6758 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -4,7 +4,6 @@ import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -21,9 +20,6 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.ResourceBundle; diff --git a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java index 2f449ec4b..8cb430584 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java +++ b/src/main/java/org/cryptomator/ui/common/ErrorComponent.java @@ -4,7 +4,6 @@ import dagger.BindsInstance; import dagger.Subcomponent; import org.cryptomator.common.Nullable; -import javafx.application.Platform; import javafx.scene.Scene; import javafx.stage.Stage; diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java index 902576715..da2a4a800 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java @@ -54,9 +54,9 @@ public class FxApplicationStyle { case LIGHT -> applyLightTheme(); case DARK -> applyDarkTheme(); case AUTOMATIC -> { - appearanceProvider.ifPresent(appearanceProvider -> { + appearanceProvider.ifPresent(provider -> { try { - appearanceProvider.addListener(systemInterfaceThemeListener); + provider.addListener(systemInterfaceThemeListener); } catch (UiAppearanceException e) { LOG.error("Failed to enable automatic theme switching."); } @@ -84,15 +84,11 @@ public class FxApplicationStyle { private void applyLightTheme() { Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString()); - appearanceProvider.ifPresent(appearanceProvider -> { - appearanceProvider.adjustToTheme(Theme.LIGHT); - }); + appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT)); } private void applyDarkTheme() { Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString()); - appearanceProvider.ifPresent(appearanceProvider -> { - appearanceProvider.adjustToTheme(Theme.DARK); - }); + appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK)); } } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java index 7e471bd11..fac4bb75d 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -26,8 +26,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*; @FxApplicationScoped public class FxApplicationTerminator { - public static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR); - + private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR); private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class); private final ObservableList vaults; diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index fae7b1b9b..7236ea56b 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -30,6 +30,7 @@ import java.awt.desktop.QuitResponse; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; @FxApplicationScoped public class FxApplicationWindows { @@ -44,10 +45,11 @@ public class FxApplicationWindows { private final UnlockComponent.Factory unlockWorkflowFactory; private final LockComponent.Factory lockWorkflowFactory; private final ErrorComponent.Factory errorWindowFactory; + private final ExecutorService executor; private final FilteredList visibleWindows; @Inject - public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional trayIntegration, Lazy mainWindow, Lazy preferencesWindow, Lazy quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory) { + public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional trayIntegration, Lazy mainWindow, Lazy preferencesWindow, Lazy quitWindow, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor) { this.primaryStage = primaryStage; this.trayIntegration = trayIntegration; this.mainWindow = mainWindow; @@ -56,7 +58,8 @@ public class FxApplicationWindows { this.unlockWorkflowFactory = unlockWorkflowFactory; this.lockWorkflowFactory = lockWorkflowFactory; this.errorWindowFactory = errorWindowFactory; - this.visibleWindows = Stage.getWindows().filtered(Window::isShowing); + this.executor = executor; + this.visibleWindows = Window.getWindows().filtered(Window::isShowing); } public void initialize() { @@ -111,7 +114,7 @@ public class FxApplicationWindows { LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); return unlockWorkflowFactory.create(vault, owner).unlockWorkflow(); }, Platform::runLater) // - .thenCompose(CompletableFuture::runAsync) // run unlock in forkjoin pool TODO: use executorservice + .thenCompose(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) // .exceptionally(e -> { showErrorWindow(e, owner == null ? primaryStage : owner, null); return null; @@ -124,7 +127,7 @@ public class FxApplicationWindows { LOG.debug("Start lock workflow for {}", vault.getDisplayName()); return lockWorkflowFactory.create(vault, owner).lockWorkflow(); }, Platform::runLater) // - .thenCompose(CompletableFuture::runAsync) // run lock in forkjoin pool TODO: use executorservice + .thenCompose(lockWorkflow -> CompletableFuture.runAsync(lockWorkflow, executor)) // .exceptionally(e -> { showErrorWindow(e, owner == null ? primaryStage : owner, null); return null; diff --git a/src/main/java/org/cryptomator/ui/lock/LockModule.java b/src/main/java/org/cryptomator/ui/lock/LockModule.java index 6a82f2bab..c5657a488 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockModule.java +++ b/src/main/java/org/cryptomator/ui/lock/LockModule.java @@ -20,7 +20,6 @@ import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; import java.util.Map; -import java.util.Optional; import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java index c13e4417c..76eee0cb4 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java @@ -3,7 +3,6 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.FxApplicationTerminator; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.UpdateChecker; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index cf585de39..b38710023 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -1,7 +1,6 @@ package org.cryptomator.ui.mainwindow; import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.Animations; @@ -9,7 +8,6 @@ import org.cryptomator.ui.common.AutoAnimator; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.controls.FontAwesome5Icon; import org.cryptomator.ui.controls.FontAwesome5IconView; -import org.cryptomator.ui.fxapp.FxApplication; import javax.inject.Inject; import javafx.application.Application; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java index af550b73e..dab1f7a54 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java @@ -4,9 +4,7 @@ import com.tobiasdiez.easybind.EasyBind; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -15,10 +13,8 @@ import javafx.beans.binding.BooleanExpression; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.stage.Stage; -import java.util.Optional; @MainWindowScoped public class VaultDetailLockedController implements FxController { diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java index e861d6438..4799d0325 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java @@ -2,7 +2,6 @@ package org.cryptomator.ui.migration; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplication; import javax.inject.Inject; import javafx.application.Application; diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index 348d34832..b14286698 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -22,7 +22,6 @@ import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; import java.util.Map; -import java.util.Optional; import java.util.ResourceBundle; @Module(subcomponents = {KeyLoadingComponent.class}) From 7df449524a5e646741b7d55d0ee129d53af92345 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 10:03:02 +0200 Subject: [PATCH 14/83] fixed lock workflow --- .../java/org/cryptomator/ui/fxapp/FxApplicationWindows.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 7236ea56b..df74986fc 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -123,7 +123,7 @@ public class FxApplicationWindows { public CompletionStage startLockWorkflow(Vault vault, @Nullable Stage owner) { return CompletableFuture.supplyAsync(() -> { - Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not unlocked."); + Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING), "Vault not unlocked."); LOG.debug("Start lock workflow for {}", vault.getDisplayName()); return lockWorkflowFactory.create(vault, owner).lockWorkflow(); }, Platform::runLater) // From a558135fec91e8ff8f5c9f17af994f6c19cb763f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 11:03:15 +0200 Subject: [PATCH 15/83] Rename .exe Installer Bundle (#2145) --- .github/workflows/win-exe.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index d2b568860..5b1ba1aaf 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -225,11 +225,11 @@ jobs: run: > "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj -ext WixBalExtension - -out installer/unsigned/Cryptomator.exe + -out installer/unsigned/Cryptomator-Installer.exe - name: Detach burn engine in preparation to sign run: > "${WIX}/bin/insignia.exe" - -ib installer/unsigned/Cryptomator.exe + -ib installer/unsigned/Cryptomator-Installer.exe -o tmp/engine.exe - name: Codesign burn engine uses: skymatic/code-sign-action@v1 @@ -243,8 +243,8 @@ jobs: - name: Reattach signed burn engine to installer run : > "${WIX}/bin/insignia.exe" - -ab tmp/engine.exe installer/unsigned/Cryptomator.exe - -o installer/Cryptomator.exe + -ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe + -o installer/Cryptomator-Installer.exe - name: Codesign EXE uses: skymatic/code-sign-action@v1 with: @@ -255,7 +255,7 @@ jobs: timestampUrl: 'http://timestamp.digicert.com' folder: installer - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe + run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import From a4c50da222bc6404972098f4cf6901dbba7ed5ff Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 31 Mar 2022 11:12:50 +0200 Subject: [PATCH 16/83] replicate naming scheme of win exe installer in CI job to local build --- dist/win/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 0b0e953bc..e3ba36efd 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -140,4 +140,4 @@ Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Crypt -dAboutUrl="$aboutUrl" ` -dHelpUrl="$helpUrl" ` -dUpdateUrl="$updateUrl" -& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\CryptomatorBundle.exe \ No newline at end of file +& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\Cryptomator-Installer.exe \ No newline at end of file From c7d1b9dbd670203e7644c734a7be5b3c3ce6e663 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 31 Mar 2022 16:32:17 +0200 Subject: [PATCH 17/83] don't prompt user if quitting directly after app start --- .../java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java index fac4bb75d..db7ca4d6d 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -56,6 +56,7 @@ public class FxApplicationTerminator { } // allow sudden termination? + vaultListChanged(vaults); vaults.addListener(this::vaultListChanged); shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults); From 0bb6e64d83289cf1e2d21cd49d8963a17344def6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 1 Apr 2022 14:01:58 +0200 Subject: [PATCH 18/83] when "starting hidden", only minimize when there is no tray icon --- .../java/org/cryptomator/ui/fxapp/FxApplication.java | 10 +++++++++- .../cryptomator/ui/fxapp/FxApplicationWindows.java | 12 ++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 057466320..57f46a3c9 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -50,15 +50,23 @@ public class FxApplication { applicationTerminator.initialize(); // init system tray + final boolean hasTrayIcon; if (SystemTray.isSupported() && settings.showTrayIcon().get()) { trayMenu.get().initializeTrayIcon(); Platform.setImplicitExit(false); // don't quit when closing all windows + hasTrayIcon = true; + } else { + hasTrayIcon = false; } // show main window appWindows.showMainWindow().thenAccept(stage -> { boolean hide = settings.startHidden().get(); - stage.setIconified(hide); + if (hasTrayIcon) { + stage.hide(); + } else { + stage.setIconified(hide); + } }); launchEventHandler.startHandlingLaunchEvents(); diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index df74986fc..2fcf2718b 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -18,8 +18,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; import javafx.collections.transformation.FilteredList; import javafx.scene.Scene; import javafx.stage.Stage; @@ -83,13 +82,14 @@ public class FxApplicationWindows { // observe visible windows if (trayIntegration.isPresent()) { - Bindings.isNotEmpty(visibleWindows).addListener(this::visibleWindowsChanged); + visibleWindows.addListener(this::visibleWindowsChanged); } } - private void visibleWindowsChanged(@SuppressWarnings("unused") ObservableValue observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) { - LOG.debug("has visible stages: {}", newValue); - if (newValue) { + private void visibleWindowsChanged(ListChangeListener.Change change) { + int visibleWindows = change.getList().size(); + LOG.debug("visible windows: {}", visibleWindows); + if (visibleWindows > 0) { trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray); } else { trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray); From c1504e29c14b7f70ada20a9414a2c82e3ac5d48a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 1 Apr 2022 14:48:14 +0200 Subject: [PATCH 19/83] added documentation [ci skip] --- .../ui/fxapp/FxApplicationTerminator.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java index db7ca4d6d..7c7b07c1e 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -35,7 +35,7 @@ public class FxApplicationTerminator { private final AtomicBoolean allowQuitWithoutPrompt = new AtomicBoolean(); @Inject - public FxApplicationTerminator(ObservableList vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows){ + public FxApplicationTerminator(ObservableList vaults, ShutdownHook shutdownHook, FxApplicationWindows appWindows) { this.vaults = vaults; this.shutdownHook = shutdownHook; this.appWindows = appWindows; @@ -84,6 +84,12 @@ public class FxApplicationTerminator { } } + /** + * Asks the app to quit. If confirmed, the JavaFX application will exit before giving a {@code response}. + * + * @param e ignored + * @param response a quit response that will be {@link ExitingQuitResponse decorated in order to exit the JavaFX application}. + */ private void handleQuitRequest(@SuppressWarnings("unused") @Nullable EventObject e, QuitResponse response) { var exitingResponse = new ExitingQuitResponse(response); if (allowQuitWithoutPrompt.get()) { @@ -107,7 +113,12 @@ public class FxApplicationTerminator { } } - private class NoopQuitResponse implements QuitResponse { + /** + * A dummy QuitResponse that ignores the response. + * + * To be used with {@link #handleQuitRequest(EventObject, QuitResponse)} if the invoking method is not interested in the response. + */ + private static class NoopQuitResponse implements QuitResponse { @Override public void performQuit() { From 887e0332da5bd273323ea8a3e33a14cdd48cba9f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 1 Apr 2022 14:50:27 +0200 Subject: [PATCH 20/83] changed misleading comment [ci skip] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 01cd0ce81..bee198a77 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ 4.4.0 2.2 - + 23.0.0 7.0.2 0.8.7 From 2d830cdb31fe9e94931f8465359169e0b1fef252 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 1 Apr 2022 15:36:20 +0200 Subject: [PATCH 21/83] easier-to-understand if/else for combinations of "startHidden" and "hasTrayIcon" fixes bug when startHidden was false and hasTrayIcon was true --- .../java/org/cryptomator/ui/fxapp/FxApplication.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 57f46a3c9..ac52d4a9c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -61,11 +61,12 @@ public class FxApplication { // show main window appWindows.showMainWindow().thenAccept(stage -> { - boolean hide = settings.startHidden().get(); - if (hasTrayIcon) { - stage.hide(); - } else { - stage.setIconified(hide); + if (settings.startHidden().get()) { + if (hasTrayIcon) { + stage.hide(); + } else { + stage.setIconified(true); + } } }); From a143ecdcf8840bd79fa1286210c4caf67afc2c66 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 1 Apr 2022 17:42:29 +0200 Subject: [PATCH 22/83] Refactored use of StageFactory --- .../cryptomator/ui/common/StageFactory.java | 13 ++++---- .../ui/common/StageInitializer.java | 32 +++++++++++++++++++ .../cryptomator/ui/fxapp/FxApplication.java | 9 +----- .../ui/fxapp/FxApplicationModule.java | 25 --------------- .../ui/mainwindow/MainWindowModule.java | 14 ++++++-- 5 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/cryptomator/ui/common/StageInitializer.java diff --git a/src/main/java/org/cryptomator/ui/common/StageFactory.java b/src/main/java/org/cryptomator/ui/common/StageFactory.java index 9a0dcb1c5..3a8c20cb3 100644 --- a/src/main/java/org/cryptomator/ui/common/StageFactory.java +++ b/src/main/java/org/cryptomator/ui/common/StageFactory.java @@ -1,23 +1,24 @@ package org.cryptomator.ui.common; +import org.cryptomator.ui.fxapp.FxApplicationScoped; + +import javax.inject.Inject; import javafx.stage.Stage; import javafx.stage.StageStyle; import java.util.function.Consumer; +@FxApplicationScoped public class StageFactory { private final Consumer initializer; - public StageFactory(Consumer initializer) { + @Inject + public StageFactory(StageInitializer initializer) { this.initializer = initializer; } public Stage create() { - return create(StageStyle.DECORATED); - } - - public Stage create(StageStyle stageStyle) { - Stage stage = new Stage(stageStyle); + Stage stage = new Stage(StageStyle.DECORATED); initializer.accept(stage); return stage; } diff --git a/src/main/java/org/cryptomator/ui/common/StageInitializer.java b/src/main/java/org/cryptomator/ui/common/StageInitializer.java new file mode 100644 index 000000000..1534deb52 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/common/StageInitializer.java @@ -0,0 +1,32 @@ +package org.cryptomator.ui.common; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.fxapp.FxApplicationScoped; + +import javax.inject.Inject; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import java.util.List; +import java.util.function.Consumer; + +/** + * Performs common setup for all stages + */ +@FxApplicationScoped +public class StageInitializer implements Consumer { + + private final List windowIcons; + + @Inject + public StageInitializer() { + this.windowIcons = SystemUtils.IS_OS_MAC ? List.of() : List.of( // + new Image(StageInitializer.class.getResource("/img/window_icon_32.png").toString()), // + new Image(StageInitializer.class.getResource("/img/window_icon_512.png").toString()) // + ); + } + + @Override + public void accept(Stage stage) { + stage.getIcons().setAll(windowIcons); + } +} diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index ac52d4a9c..55f76d321 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -17,7 +17,6 @@ public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); - private final Stage primaryStage; private final Settings settings; private final AppLaunchEventHandler launchEventHandler; private final Lazy trayMenu; @@ -27,8 +26,7 @@ public class FxApplication { private final AutoUnlocker autoUnlocker; @Inject - FxApplication(@PrimaryStage Stage primaryStage, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { - this.primaryStage = primaryStage; + FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { this.settings = settings; this.launchEventHandler = launchEventHandler; this.trayMenu = trayMenu; @@ -40,11 +38,6 @@ public class FxApplication { public void start() { LOG.trace("FxApplication.start()"); - primaryStage.setTitle("Cryptomator"); - primaryStage.initStyle(StageStyle.UNDECORATED); - primaryStage.setMinWidth(650); - primaryStage.setMinHeight(440); - applicationStyle.initialize(); appWindows.initialize(); applicationTerminator.initialize(); diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 68c97426a..85e46dffa 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -28,31 +28,6 @@ import java.util.List; @Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class}) abstract class FxApplicationModule { - @Provides - @Named("windowIcons") - @FxApplicationScoped - static List provideWindowIcons() { - if (SystemUtils.IS_OS_MAC) { - return Collections.emptyList(); - } - try { - return List.of( // - createImageFromResource("/img/window_icon_32.png"), // - createImageFromResource("/img/window_icon_512.png") // - ); - } catch (IOException e) { - throw new UncheckedIOException("Failed to load embedded resource.", e); - } - } - - @Provides - @FxApplicationScoped - static StageFactory provideStageFactory(@Named("windowIcons") List windowIcons) { - return new StageFactory(stage -> { - stage.getIcons().addAll(windowIcons); - }); - } - private static Image createImageFromResource(String resourceName) throws IOException { try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) { return new Image(in); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index fb65b4707..94acba3cc 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -13,6 +13,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.common.StageInitializer; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.migration.MigrationComponent; @@ -35,10 +36,17 @@ import java.util.ResourceBundle; @Module(subcomponents = {AddVaultWizardComponent.class, HealthCheckComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) abstract class MainWindowModule { - @Binds + @Provides @MainWindow @MainWindowScoped - abstract Stage bindMainWindow(@PrimaryStage Stage primaryStage); + static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) { + initializer.accept(stage); + stage.setTitle("Cryptomator"); + stage.initStyle(StageStyle.UNDECORATED); + stage.setMinWidth(650); + stage.setMinHeight(440); + return stage; + } @Provides @MainWindowScoped @@ -57,7 +65,7 @@ abstract class MainWindowModule { @MainWindowScoped @Named("errorWindow") static Stage provideErrorStage(@MainWindow Stage window, StageFactory factory, ResourceBundle resourceBundle) { - Stage stage = factory.create(StageStyle.DECORATED); + Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("main.vaultDetail.error.windowTitle")); stage.initModality(Modality.APPLICATION_MODAL); stage.initOwner(window); From ec2524f6ff9b20a269d3c2d63f49cd34c8b4f4ae Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 10:06:15 +0200 Subject: [PATCH 23/83] use correct StartupWMClass (fixes #1955) [ci skip] --- dist/linux/common/org.cryptomator.Cryptomator.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/linux/common/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop index 1872b9f38..396c3acb7 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -7,5 +7,5 @@ Terminal=false Type=Application Categories=Utility;Security;FileTools; StartupNotify=true -StartupWMClass=org.cryptomator.launcher.Cryptomator +StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault; From 6af016f1feb85ce63d21ebeed4d9be398b301ca5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 11:51:34 +0200 Subject: [PATCH 24/83] log errors occuring in async "show window" tasks --- .../cryptomator/ui/fxapp/FxApplicationWindows.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 2fcf2718b..ba28f9bc4 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -97,15 +97,15 @@ public class FxApplicationWindows { } public CompletionStage showMainWindow() { - return CompletableFuture.supplyAsync(mainWindow.get()::showMainWindow, Platform::runLater); + return CompletableFuture.supplyAsync(mainWindow.get()::showMainWindow, Platform::runLater).whenComplete(this::reportErrors); } public CompletionStage showPreferencesWindow(SelectedPreferencesTab selectedTab) { - return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater); + return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors); } public CompletionStage showQuitWindow(QuitResponse response) { - return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater); + return CompletableFuture.supplyAsync(() -> quitWindow.get().showQuitWindow(response), Platform::runLater).whenComplete(this::reportErrors); } public CompletionStage startUnlockWorkflow(Vault vault, @Nullable Stage owner) { @@ -143,7 +143,13 @@ public class FxApplicationWindows { * @return A */ public CompletionStage showErrorWindow(Throwable cause, Stage window, @Nullable Scene previousScene) { - return CompletableFuture.supplyAsync(() -> errorWindowFactory.create(cause, window, previousScene).show(), Platform::runLater); + return CompletableFuture.supplyAsync(() -> errorWindowFactory.create(cause, window, previousScene).show(), Platform::runLater).whenComplete(this::reportErrors); + } + + private void reportErrors(@Nullable Stage stage, @Nullable Throwable error) { + if (error != null) { + LOG.error("Failed to display stage", error); + } } } From c130441700c9a42a1fd96e5b9ddc4eeeed2404b6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 12:15:09 +0200 Subject: [PATCH 25/83] add setting for user-chosen "language" --- .../java/org/cryptomator/common/settings/Settings.java | 7 +++++++ .../cryptomator/common/settings/SettingsJsonAdapter.java | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java index 82f2fb794..8cc23bbfc 100644 --- a/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/src/main/java/org/cryptomator/common/settings/Settings.java @@ -44,6 +44,7 @@ public class Settings { public static final String DEFAULT_LICENSE_KEY = ""; public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false; public static final String DEFAULT_DISPLAY_CONFIGURATION = ""; + public static final String DEFAULT_LANGUAGE = null; private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); @@ -66,6 +67,7 @@ public class Settings { private final IntegerProperty windowWidth = new SimpleIntegerProperty(); private final IntegerProperty windowHeight = new SimpleIntegerProperty(); private final ObjectProperty displayConfiguration = new SimpleObjectProperty<>(DEFAULT_DISPLAY_CONFIGURATION); + private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE); private Consumer saveCmd; @@ -96,6 +98,7 @@ public class Settings { windowWidth.addListener(this::somethingChanged); windowHeight.addListener(this::somethingChanged); displayConfiguration.addListener(this::somethingChanged); + language.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { @@ -191,4 +194,8 @@ public class Settings { public ObjectProperty displayConfigurationProperty() { return displayConfiguration; } + + public StringProperty languageProperty() { + return language; + } } diff --git a/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java index 6d8d880e6..d10066f8f 100644 --- a/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java +++ b/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java @@ -57,6 +57,7 @@ public class SettingsJsonAdapter extends TypeAdapter { out.name("windowWidth").value((value.windowWidthProperty().get())); out.name("windowHeight").value((value.windowHeightProperty().get())); out.name("displayConfiguration").value((value.displayConfigurationProperty().get())); + out.name("language").value((value.languageProperty().get())); out.endObject(); } @@ -97,6 +98,7 @@ public class SettingsJsonAdapter extends TypeAdapter { case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt()); case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt()); case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString()); + case "language" -> settings.languageProperty().set(in.nextString()); default -> { LOG.warn("Unsupported vault setting found in JSON: " + name); From d9aa6ae91a3c7fa81cb618af8d8e260579fdeb8d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 12:16:57 +0200 Subject: [PATCH 26/83] display "language" chooser in general preferences --- .../launcher/SupportedLanguages.java | 12 +++++++ .../GeneralPreferencesController.java | 35 +++++++++++++++++++ .../resources/fxml/preferences_general.fxml | 5 +++ src/main/resources/i18n/strings.properties | 2 ++ 4 files changed, 54 insertions(+) create mode 100644 src/main/java/org/cryptomator/launcher/SupportedLanguages.java diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java new file mode 100644 index 000000000..7f891ed1f --- /dev/null +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -0,0 +1,12 @@ +package org.cryptomator.launcher; + +import java.util.List; + +public class SupportedLanguages { + + // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_": + public static List LANGUAGAE_TAGS = List.of("ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // + "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", // + "sk", "sr", "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); + +} diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index 7430cf207..3ebdad4a4 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.preferences; +import com.google.common.base.Strings; import org.cryptomator.common.Environment; import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; @@ -7,6 +8,7 @@ import org.cryptomator.common.settings.UiTheme; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; +import org.cryptomator.launcher.SupportedLanguages; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.traymenu.TrayMenuComponent; @@ -27,6 +29,7 @@ import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.stage.Stage; import javafx.util.StringConverter; +import java.util.Locale; import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; @@ -53,6 +56,7 @@ public class GeneralPreferencesController implements FxController { public CheckBox showMinimizeButtonCheckbox; public CheckBox showTrayIconCheckbox; public CheckBox startHiddenCheckbox; + public ChoiceBox preferredLanguageChoiceBox; public CheckBox debugModeCheckbox; public CheckBox autoStartCheckbox; public ToggleGroup nodeOrientation; @@ -90,6 +94,11 @@ public class GeneralPreferencesController implements FxController { startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); + preferredLanguageChoiceBox.getItems().add(null); + preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS); + preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); + preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); + debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled())); @@ -183,6 +192,32 @@ public class GeneralPreferencesController implements FxController { } + private static class LanguageTagConverter extends StringConverter { + + private final ResourceBundle resourceBundle; + + LanguageTagConverter(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + @Override + public String toString(String tag) { + if (tag == null) { + return resourceBundle.getString("preferences.general.language.auto"); + } else { + var locale = Locale.forLanguageTag(tag); + var lang = locale.getDisplayLanguage(locale); + var region = locale.getDisplayCountry(locale); + return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")"); + } + } + + @Override + public String fromString(String displayLanguage) { + throw new UnsupportedOperationException(); + } + } + private class KeychainProviderDisplayNameConverter extends StringConverter { @Override diff --git a/src/main/resources/fxml/preferences_general.fxml b/src/main/resources/fxml/preferences_general.fxml index ea087b9e3..1e3be74c6 100644 --- a/src/main/resources/fxml/preferences_general.fxml +++ b/src/main/resources/fxml/preferences_general.fxml @@ -38,6 +38,11 @@ + + + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 6648496b9..4c2ab688c 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -199,6 +199,8 @@ preferences.general.unlockThemes=Unlock dark mode preferences.general.showMinimizeButton=Show minimize button preferences.general.showTrayIcon=Show tray icon (requires restart) preferences.general.startHidden=Hide window when starting Cryptomator +preferences.general.language=Language (requires restart) +preferences.general.language.auto=System Default preferences.general.debugLogging=Enable debug logging preferences.general.debugDirectory=Reveal log files preferences.general.autoStart=Launch Cryptomator on system start From 71d346eddd8aae80ce84ba5c6aa804947087b8c1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 12:17:10 +0200 Subject: [PATCH 27/83] apply chosen language at application start --- .../org/cryptomator/launcher/Cryptomator.java | 5 +++- .../launcher/SupportedLanguages.java | 29 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index a358126d4..90f391ec6 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -34,14 +34,16 @@ public class Cryptomator { private final LoggerConfiguration logConfig; private final DebugMode debugMode; + private final SupportedLanguages supportedLanguages; private final Environment env; private final Lazy ipcMessageHandler; private final ShutdownHook shutdownHook; @Inject - Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { + Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { this.logConfig = logConfig; this.debugMode = debugMode; + this.supportedLanguages = supportedLanguages; this.env = env; this.ipcMessageHandler = ipcMessageHandler; this.shutdownHook = shutdownHook; @@ -63,6 +65,7 @@ public class Cryptomator { logConfig.init(); LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); + supportedLanguages.applyPreferred(); /* * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java index 7f891ed1f..e02e876d6 100644 --- a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -1,12 +1,39 @@ package org.cryptomator.launcher; -import java.util.List; +import org.cryptomator.common.settings.Settings; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.Locale; + +@Singleton public class SupportedLanguages { + private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class); // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_": public static List LANGUAGAE_TAGS = List.of("ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", // "sk", "sr", "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); + @Nullable + private final String preferredLanguage; + + @Inject + public SupportedLanguages(Settings settings) { + this.preferredLanguage = settings.languageProperty().get(); + } + + public void applyPreferred() { + if (preferredLanguage == null) { + LOG.debug("Using system locale"); + return; + } + var preferredLocale = Locale.forLanguageTag(preferredLanguage); + LOG.debug("Applying preferred locale {}", preferredLocale.getDisplayName(Locale.ENGLISH)); + Locale.setDefault(preferredLocale); + } } From 77d81acb1ed80391d49f8b6c5503e23bd481ccd6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 2 Apr 2022 15:54:37 +0200 Subject: [PATCH 28/83] added "English" as first option --- src/main/java/org/cryptomator/launcher/SupportedLanguages.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java index e02e876d6..8057c14b7 100644 --- a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -15,7 +15,7 @@ public class SupportedLanguages { private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class); // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_": - public static List LANGUAGAE_TAGS = List.of("ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // + public static List LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", // "sk", "sr", "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); From 5dc8fd258283b24146e78dddb676d618fbc3c935 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 3 Apr 2022 12:25:15 +0200 Subject: [PATCH 29/83] made constant final --- .../java/org/cryptomator/launcher/SupportedLanguages.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java index 8057c14b7..d473dcdf8 100644 --- a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -15,9 +15,9 @@ public class SupportedLanguages { private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class); // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_": - public static List LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // - "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", // - "sk", "sr", "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); + public static final List LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // + "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", // + "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); @Nullable private final String preferredLanguage; From d7da78fc07bf46fd6a07cee4eefa14ba8a9cd7b4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 3 Apr 2022 15:26:04 +0200 Subject: [PATCH 30/83] adjust to latest API --- pom.xml | 2 +- src/main/java/module-info.java | 1 + .../cryptomator/ui/fxapp/FxApplication.java | 2 +- .../ui/traymenu/AwtTrayMenuController.java | 19 +++++++++++----- .../ui/traymenu/TrayMenuBuilder.java | 22 +++++++++++++------ .../ui/traymenu/TrayMenuComponent.java | 9 ++++---- 6 files changed, 36 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index 51203f0e5..98994551d 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 2.4.0 - 1.1.0-beta2 + 1.1.0-beta3 1.1.0-beta1 1.1.0-beta1 1.1.0-beta1 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e8ee1c871..dd3d7680b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -29,6 +29,7 @@ module org.cryptomator.desktop { requires logback.classic; requires logback.core; + exports org.cryptomator.ui.traymenu to org.cryptomator.integrations.api; provides TrayMenuController with AwtTrayMenuController; opens org.cryptomator.common.settings to com.google.gson; diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 55f76d321..f3109e7bf 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -44,7 +44,7 @@ public class FxApplication { // init system tray final boolean hasTrayIcon; - if (SystemTray.isSupported() && settings.showTrayIcon().get()) { + if (settings.showTrayIcon().get() && trayMenu.get().isSupported()) { trayMenu.get().initializeTrayIcon(); Platform.setImplicitExit(false); // don't quit when closing all windows hasTrayIcon = true; diff --git a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java index 034847913..6f791321f 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java @@ -1,11 +1,13 @@ package org.cryptomator.ui.traymenu; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.integrations.common.CheckAvailability; import org.cryptomator.integrations.common.Priority; import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SubMenuItem; import org.cryptomator.integrations.tray.TrayMenuController; +import org.cryptomator.integrations.tray.TrayMenuException; import org.cryptomator.integrations.tray.TrayMenuItem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,18 +23,23 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +@CheckAvailability @Priority(Priority.FALLBACK) public class AwtTrayMenuController implements TrayMenuController { private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class); - private TrayIcon trayIcon; - private PopupMenu menu = new PopupMenu(); + private final PopupMenu menu = new PopupMenu(); + + @CheckAvailability + public static boolean isAvailable() { + return SystemTray.isSupported(); + } @Override - public void showTrayIcon(InputStream rawImageData, Runnable defaultAction, String tooltip) throws IOException { - var image = Toolkit.getDefaultToolkit().createImage(rawImageData.readAllBytes()); - trayIcon = new TrayIcon(image, tooltip, menu); + public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) { + var image = Toolkit.getDefaultToolkit().createImage(rawImageData); + var trayIcon = new TrayIcon(image, tooltip, menu); trayIcon.setImageAutoSize(true); if (SystemUtils.IS_OS_WINDOWS) { @@ -59,7 +66,7 @@ public class AwtTrayMenuController implements TrayMenuController { if (item instanceof ActionItem a) { var menuItem = new MenuItem(a.title()); menuItem.addActionListener(evt -> a.action().run()); - // TODO menuItem.setEnabled(a.enabled()); + menuItem.setEnabled(a.enabled()); menu.add(menuItem); } else if (item instanceof SeparatorItem) { menu.addSeparator(); diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java index b8e5d8c2b..ea8599b51 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java @@ -7,11 +7,14 @@ import org.cryptomator.integrations.tray.ActionItem; import org.cryptomator.integrations.tray.SeparatorItem; import org.cryptomator.integrations.tray.SubMenuItem; import org.cryptomator.integrations.tray.TrayMenuController; +import org.cryptomator.integrations.tray.TrayMenuException; import org.cryptomator.integrations.tray.TrayMenuItem; import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.fxapp.FxApplicationTerminator; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.preferences.SelectedPreferencesTab; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.application.Platform; @@ -27,6 +30,7 @@ import java.util.ResourceBundle; @TrayMenuScoped public class TrayMenuBuilder { + private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class); private static final String TRAY_ICON_MAC = "/img/tray_icon_mac.png"; private static final String TRAY_ICON = "/img/tray_icon.png"; @@ -56,15 +60,16 @@ public class TrayMenuBuilder { vaults.forEach(v -> { v.displayNameProperty().addListener(this::vaultListChanged); }); - rebuildMenu(); try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) { - trayMenu.showTrayIcon(image, this::showMainWindow, "Cryptomator"); + trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator"); + rebuildMenu(); + initialized = true; } catch (IOException e) { throw new UncheckedIOException("Failed to load embedded resource", e); + } catch (TrayMenuException e) { + LOG.error("Adding tray icon failed", e); } - - initialized = true; } public boolean isInitialized() { @@ -88,11 +93,14 @@ public class TrayMenuBuilder { menu.add(new SubMenuItem(label, submenu)); } menu.add(new SeparatorItem()); - menu.add(new ActionItem(resourceBundle.getString("traymenu.lockAllVaults"), this::lockAllVaults)); + menu.add(new ActionItem(resourceBundle.getString("traymenu.lockAllVaults"), this::lockAllVaults, vaults.stream().anyMatch(Vault::isUnlocked))); menu.add(new ActionItem(resourceBundle.getString("traymenu.quitApplication"), this::quitApplication)); -// lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty()); - trayMenu.updateTrayMenu(menu); + try { + trayMenu.updateTrayMenu(menu); + } catch (TrayMenuException e) { + LOG.error("Updating tray menu failed", e); + } } private List buildSubmenu(Vault vault) { diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java index 61861e12c..ba84171b3 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuComponent.java @@ -24,8 +24,7 @@ public interface TrayMenuComponent { * @return true if a tray icon can be installed */ default boolean isSupported() { - // TODO add isSupported to API and move SystemTray.isSupported() to impl - return trayMenuController().isPresent() && SystemTray.isSupported(); + return trayMenuController().isPresent(); } /** @@ -38,11 +37,13 @@ public interface TrayMenuComponent { /** * Installs a tray icon to the system tray. * - * @throws IllegalStateException If already added + * @throws IllegalStateException If not {@link #isSupported() supported} */ default void initializeTrayIcon() throws IllegalStateException { Preconditions.checkState(isSupported(), "system tray not supported"); - trayMenuBuilder().initTrayMenu(); + if (!trayMenuBuilder().isInitialized()) { + trayMenuBuilder().initTrayMenu(); + } } @Subcomponent.Builder From ec909ce723f4e9f895b49e506a15d7b94c8d9727 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 4 Apr 2022 15:10:24 +0200 Subject: [PATCH 31/83] rethrow AWTException as TrayMenuException --- .../org/cryptomator/ui/traymenu/AwtTrayMenuController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java index 6f791321f..b3a4b3f6e 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java @@ -37,7 +37,7 @@ public class AwtTrayMenuController implements TrayMenuController { } @Override - public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) { + public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException { var image = Toolkit.getDefaultToolkit().createImage(rawImageData); var trayIcon = new TrayIcon(image, tooltip, menu); @@ -50,7 +50,7 @@ public class AwtTrayMenuController implements TrayMenuController { SystemTray.getSystemTray().add(trayIcon); LOG.debug("initialized tray icon"); } catch (AWTException e) { - LOG.error("Error adding tray icon", e); + throw new TrayMenuException("Failed to add icon to system tray.", e); } } From ceb3cbc43f9be9921343e8987f755c0ef6e946d4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 4 Apr 2022 15:10:35 +0200 Subject: [PATCH 32/83] remove unused imports --- .../java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java index b3a4b3f6e..79f1dd628 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java @@ -19,8 +19,6 @@ import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; -import java.io.IOException; -import java.io.InputStream; import java.util.List; @CheckAvailability From d10c8fcf1783f646b49042643967d564cebe5d64 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 4 Apr 2022 15:13:27 +0200 Subject: [PATCH 33/83] amend settings deserialization test --- .../cryptomator/common/settings/SettingsJsonAdapterTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java b/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java index b2ecf4b89..a8bc4551a 100644 --- a/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java +++ b/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java @@ -29,6 +29,7 @@ public class SettingsJsonAdapterTest { ], "checkForUpdatesEnabled": true, "port": 8080, + "language": "de-DE", "numTrayNotifications": 42, "preferredVolumeImpl": "FUSE" } @@ -39,6 +40,7 @@ public class SettingsJsonAdapterTest { Assertions.assertTrue(settings.checkForUpdates().get()); Assertions.assertEquals(2, settings.getDirectories().size()); Assertions.assertEquals(8080, settings.port().get()); + Assertions.assertEquals("de-DE", settings.languageProperty().get()); Assertions.assertEquals(42, settings.numTrayNotifications().get()); Assertions.assertEquals(WebDavUrlScheme.DAV, settings.preferredGvfsScheme().get()); Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get()); From 508b9f5c6431065f6571abc0ce3496a9f92af035 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 4 Apr 2022 15:57:01 +0200 Subject: [PATCH 34/83] test if bundle for supported locales exist and are not empty --- .../launcher/SupportedLanguagesTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/org/cryptomator/launcher/SupportedLanguagesTest.java diff --git a/src/test/java/org/cryptomator/launcher/SupportedLanguagesTest.java b/src/test/java/org/cryptomator/launcher/SupportedLanguagesTest.java new file mode 100644 index 000000000..5a9f1bc07 --- /dev/null +++ b/src/test/java/org/cryptomator/launcher/SupportedLanguagesTest.java @@ -0,0 +1,31 @@ +package org.cryptomator.launcher; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.stream.Stream; + +public class SupportedLanguagesTest { + + @DisplayName("test if resource bundle is localized") + @ParameterizedTest(name = "{0}") + @MethodSource("languageTags") + public void testResourceBundleExists(String tag) { + var locale = Locale.forLanguageTag(tag); + Assertions.assertNotEquals("und", locale.toLanguageTag(), "Undefined language tag"); + + var bundle = Assertions.assertDoesNotThrow(() -> ResourceBundle.getBundle("/i18n/strings", locale)); + + Assertions.assertEquals(locale, bundle.getLocale()); + Assertions.assertFalse(bundle.keySet().isEmpty()); + } + + public static Stream languageTags() { + return SupportedLanguages.LANGUAGAE_TAGS.stream() // + .filter(tag -> !"en".equals(tag)); // english uses the default bundle + } +} \ No newline at end of file From 7c772e2767b5bab51365f5dbae1e1eac570d27f0 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 5 Apr 2022 07:46:17 +0200 Subject: [PATCH 35/83] removed `user.language=en` and `jdk.gtk.version=2` flags from run configurations [ci skip] --- .idea/runConfigurations/Cryptomator_Linux.xml | 2 +- .idea/runConfigurations/Cryptomator_Linux_Dev.xml | 2 +- .idea/runConfigurations/Cryptomator_Windows.xml | 2 +- .idea/runConfigurations/Cryptomator_Windows_Dev.xml | 2 +- .idea/runConfigurations/Cryptomator_macOS.xml | 2 +- .idea/runConfigurations/Cryptomator_macOS_Dev.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.idea/runConfigurations/Cryptomator_Linux.xml b/.idea/runConfigurations/Cryptomator_Linux.xml index 97bcd58df..81c2da004 100644 --- a/.idea/runConfigurations/Cryptomator_Linux.xml +++ b/.idea/runConfigurations/Cryptomator_Linux.xml @@ -2,7 +2,7 @@