diff --git a/pom.xml b/pom.xml index be940d961..bee198a77 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,8 @@ 4.4.0 2.2 - + + 23.0.0 7.0.2 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 f13308be5..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; 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 6c4751bdf..a358126d4 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 (Exception 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..e6aab0309 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java @@ -2,20 +2,55 @@ 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 javax.inject.Named; import javax.inject.Singleton; import java.util.Optional; -import java.util.concurrent.CountDownLatch; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; -@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..01a8a6758 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -4,10 +4,10 @@ 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; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,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; @@ -34,7 +31,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 +40,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 +79,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..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; @@ -16,34 +15,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/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/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 1812d38bd..55f76d321 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -1,213 +1,70 @@ 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 extends Application { +public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); 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(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { 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); + applicationStyle.initialize(); + appWindows.initialize(); + applicationTerminator.initialize(); - hasVisibleWindows.addListener(this::hasVisibleStagesChanged); - - settings.theme().addListener(this::appThemeChanged); - 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) { - trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray); + // 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 { - trayIntegration.ifPresent(TrayIntegrationProvider::minimizedToTray); + hasTrayIcon = false; } - } - public void showPreferencesWindow(SelectedPreferencesTab selectedTab) { - Platform.runLater(() -> { - preferencesWindow.get().showPreferencesWindow(selectedTab); - LOG.debug("Showing Preferences"); - }); - } - - 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."))); + // show main window + appWindows.showMainWindow().thenAccept(stage -> { + if (settings.startHidden().get()) { + if (hasTrayIcon) { + stage.hide(); + } else { + stage.setIconified(true); + } } }); - } - 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 7d5fd55bf..2557aa9ee 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationComponent.java @@ -5,8 +5,12 @@ *******************************************************************************/ package org.cryptomator.ui.fxapp; +import dagger.BindsInstance; import dagger.Subcomponent; +import javafx.application.Application; +import javafx.stage.Stage; + @FxApplicationScoped @Subcomponent(modules = FxApplicationModule.class) public interface FxApplicationComponent { @@ -16,6 +20,12 @@ public interface FxApplicationComponent { @Subcomponent.Builder interface Builder { + @BindsInstance + Builder fxApplication(Application application); + + @BindsInstance + 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 74c201372..85e46dffa 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; @@ -15,67 +14,46 @@ 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; -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; 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 - @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); } } - @Binds - abstract Application bindApplication(FxApplication application); + @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..da2a4a800 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java @@ -0,0 +1,94 @@ +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(provider -> { + try { + provider.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(provider -> provider.adjustToTheme(Theme.LIGHT)); + } + + private void applyDarkTheme() { + Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString()); + 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 new file mode 100644 index 000000000..7c7b07c1e --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -0,0 +1,134 @@ +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 { + + 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; + 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? + vaultListChanged(vaults); + 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(); + } + } + } + + /** + * 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()) { + 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); + } + } + } + } + + /** + * 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() { + // 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..2fcf2718b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -0,0 +1,149 @@ +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.collections.ListChangeListener; +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; +import java.util.concurrent.ExecutorService; + +@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 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, ExecutorService executor) { + 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.executor = executor; + this.visibleWindows = Window.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()) { + visibleWindows.addListener(this::visibleWindowsChanged); + } + } + + 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); + } + } + + 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(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) // + .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.UNLOCKED, VaultState.Value.PROCESSING), "Vault not unlocked."); + LOG.debug("Start lock workflow for {}", vault.getDisplayName()); + return lockWorkflowFactory.create(vault, owner).lockWorkflow(); + }, Platform::runLater) // + .thenCompose(lockWorkflow -> CompletableFuture.runAsync(lockWorkflow, executor)) // + .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 1799a9700..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.cryptomator.ui.launcher; - -import dagger.Lazy; -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.Platform; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; - -@Singleton -public class FxApplicationStarter { - - private static final Logger LOG = LoggerFactory.getLogger(FxApplicationStarter.class); - - 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; - this.executor = executor; - this.started = new AtomicBoolean(); - this.future = new CompletableFuture<>(); - } - - public CompletionStage get() { - if (!started.getAndSet(true)) { - start(); - } - 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); - }); - }); - } -} 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 c30efa30e..000000000 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java +++ /dev/null @@ -1,67 +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 FxApplicationComponent provideFxApplicationComponent(FxApplicationComponent.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..c5657a488 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; @@ -19,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; @@ -43,12 +43,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 6f63db888..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,8 @@ 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; import org.cryptomator.ui.removevault.RemoveVaultComponent; @@ -34,6 +36,18 @@ 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 { + @Provides + @MainWindow + @MainWindowScoped + 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 static ObjectProperty provideSelectedVault() { @@ -47,22 +61,11 @@ 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") 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); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java index c8107415e..76eee0cb4 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java @@ -3,9 +3,9 @@ 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; -import org.cryptomator.ui.launcher.AppLifecycleListener; import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; @@ -25,9 +25,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 +40,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 +96,7 @@ public class MainWindowTitleController implements FxController { if (trayMenuInitialized) { window.close(); } else { - appLifecycle.quit(); + terminator.terminate(); } } @@ -107,17 +107,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/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index 87a419a94..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,9 +8,9 @@ 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; import javafx.beans.binding.Binding; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; @@ -22,7 +21,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 +32,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/mainwindow/VaultDetailLockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java index 5fee2e6d1..dab1f7a54 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java @@ -4,8 +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.health.HealthCheckComponent; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -14,25 +13,23 @@ 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 { 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 +42,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/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java index 191fc7a8f..4799d0325 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java @@ -2,9 +2,9 @@ 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; import javafx.fxml.FXML; import javafx.stage.Stage; @@ -12,13 +12,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 +30,7 @@ public class MigrationImpossibleController implements FxController { @FXML public void getMigrationHelp() { - fxApplication.getHostServices().showDocument(HELP_URI); + application.getHostServices().showDocument(HELP_URI); } /* Getter/Setters */ 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..b14286698 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; @@ -21,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}) @@ -37,12 +37,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"))); }