Parcourir la source

Added checkbox in settings to start without a tray icon
references #1113, #1078, #1079, #1344

Sebastian Stenzel il y a 4 ans
Parent
commit
45c714a123

+ 7 - 2
main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java

@@ -32,6 +32,8 @@ import javafx.stage.Stage;
 import javafx.stage.Window;
 import java.awt.desktop.QuitResponse;
 import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
 
 @FxApplicationScoped
 public class FxApplication extends Application {
@@ -99,11 +101,14 @@ public class FxApplication extends Application {
 		});
 	}
 
-	public void showMainWindow() {
+	public CompletionStage<Stage> showMainWindow() {
+		CompletableFuture<Stage> future = new CompletableFuture<>();
 		Platform.runLater(() -> {
-			mainWindow.get().showMainWindow();
+			var win = mainWindow.get().showMainWindow();
 			LOG.debug("Showing MainWindow");
+			future.complete(win);
 		});
+		return future;
 	}
 
 	public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {

+ 7 - 7
main/ui/src/main/java/org/cryptomator/ui/launcher/AppLaunchEventHandler.java

@@ -34,15 +34,15 @@ class AppLaunchEventHandler {
 		this.vaultListManager = vaultListManager;
 	}
 
-	public void startHandlingLaunchEvents(boolean hasTrayIcon) {
-		executorService.submit(() -> handleLaunchEvents(hasTrayIcon));
+	public void startHandlingLaunchEvents() {
+		executorService.submit(this::handleLaunchEvents);
 	}
 
-	private void handleLaunchEvents(boolean hasTrayIcon) {
+	private void handleLaunchEvents() {
 		try {
 			while (!Thread.interrupted()) {
 				AppLaunchEvent event = launchEventQueue.take();
-				handleLaunchEvent(hasTrayIcon, event);
+				handleLaunchEvent(event);
 			}
 		} catch (InterruptedException e) {
 			LOG.warn("Interrupted launch event handler.");
@@ -50,10 +50,10 @@ class AppLaunchEventHandler {
 		}
 	}
 
-	private void handleLaunchEvent(boolean hasTrayIcon, AppLaunchEvent event) {
+	private void handleLaunchEvent(AppLaunchEvent event) {
 		switch (event.getType()) {
-			case REVEAL_APP -> fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
-			case OPEN_FILE -> fxApplicationStarter.get(hasTrayIcon).thenRun(() -> {
+			case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
+			case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
 				Platform.runLater(() -> {
 					event.getPathsToOpen().forEach(this::addVault);
 				});

+ 3 - 3
main/ui/src/main/java/org/cryptomator/ui/launcher/AppLifecycleListener.java

@@ -83,7 +83,7 @@ public class AppLifecycleListener {
 		if (allowQuitWithoutPrompt.get()) {
 			decoratedQuitResponse.performQuit();
 		} else {
-			fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
+			fxApplicationStarter.get().thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
 		}
 	}
 
@@ -113,11 +113,11 @@ public class AppLifecycleListener {
 	}
 
 	private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
+		fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
 	}
 
 	private void showAboutWindow(@SuppressWarnings("unused") AboutEvent aboutEvent) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
+		fxApplicationStarter.get().thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ABOUT));
 	}
 
 	private void forceUnmountRemainingVaults() {

+ 8 - 5
main/ui/src/main/java/org/cryptomator/ui/launcher/FxApplicationStarter.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.launcher;
 
+import org.cryptomator.common.settings.Settings;
 import org.cryptomator.ui.fxapp.FxApplication;
 import org.cryptomator.ui.fxapp.FxApplicationComponent;
 import org.slf4j.Logger;
@@ -8,6 +9,7 @@ import org.slf4j.LoggerFactory;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javafx.application.Platform;
+import java.awt.SystemTray;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ExecutorService;
@@ -22,23 +24,25 @@ public class FxApplicationStarter {
 	private final ExecutorService executor;
 	private final AtomicBoolean started;
 	private final CompletableFuture<FxApplication> future;
+	private final boolean hasTrayIcon;
 
 	@Inject
-	public FxApplicationStarter(FxApplicationComponent.Builder fxAppComponent, ExecutorService executor) {
+	public FxApplicationStarter(FxApplicationComponent.Builder fxAppComponent, ExecutorService executor, Settings settings) {
 		this.fxAppComponent = fxAppComponent;
 		this.executor = executor;
 		this.started = new AtomicBoolean();
 		this.future = new CompletableFuture<>();
+		this.hasTrayIcon = SystemTray.isSupported() && settings.showTrayIcon().get();
 	}
 
-	public CompletionStage<FxApplication> get(boolean hasTrayIcon) {
+	public CompletionStage<FxApplication> get() {
 		if (!started.getAndSet(true)) {
-			start(hasTrayIcon);
+			start();
 		}
 		return future;
 	}
 
-	private void start(boolean hasTrayIcon) {
+	private void start() {
 		executor.submit(() -> {
 			LOG.debug("Starting JavaFX runtime...");
 			Platform.startup(() -> {
@@ -50,5 +54,4 @@ public class FxApplicationStarter {
 			});
 		});
 	}
-
 }

+ 18 - 12
main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java

@@ -40,44 +40,50 @@ public class UiLauncher {
 	}
 
 	public void launch() {
-		final boolean hasTrayIcon;
-		if (SystemTray.isSupported()) {
+		boolean hidden = settings.startHidden().get();
+		if (SystemTray.isSupported() && settings.showTrayIcon().get()) {
 			trayComponent.build().addIconToSystemTray();
-			hasTrayIcon = true;
+			launch(true, hidden);
 		} else {
-			hasTrayIcon = false;
+			launch(false, hidden);
 		}
+	}
 
-		// show window on start?
-		if (hasTrayIcon && settings.startHidden().get()) {
+	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 {
-			showMainWindowAsync(hasTrayIcon);
+			LOG.debug("Showing application...");
+			showMainWindowAsync(false);
 		}
 
 		// register app reopen listener
-		Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));
+		Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(false));
 
 		// auto unlock
 		Collection<Vault> vaultsToAutoUnlock = vaults.filtered(this::shouldAttemptAutoUnlock);
 		if (!vaultsToAutoUnlock.isEmpty()) {
-			fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> {
+			fxApplicationStarter.get().thenAccept(app -> {
 				for (Vault vault : vaultsToAutoUnlock) {
 					app.startUnlockWorkflow(vault, Optional.empty());
 				}
 			});
 		}
 
-		launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
+		launchEventHandler.startHandlingLaunchEvents();
 	}
 
 	private boolean shouldAttemptAutoUnlock(Vault vault) {
 		return vault.isLocked() && vault.getVaultSettings().unlockAfterStartup().get();
 	}
 
-	private void showMainWindowAsync(boolean hasTrayIcon) {
-		fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
+	private void showMainWindowAsync(boolean minimize) {
+		fxApplicationStarter.get().thenCompose(FxApplication::showMainWindow).thenAccept(win -> win.setIconified(minimize));
 	}
 
 }

+ 0 - 1
main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowComponent.java

@@ -26,7 +26,6 @@ public interface MainWindowComponent {
 	default Stage showMainWindow() {
 		Stage stage = window();
 		stage.setScene(scene().get());
-		stage.setIconified(false);
 		stage.show();
 		stage.toFront();
 		stage.requestFocus();

+ 6 - 6
main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java

@@ -28,7 +28,7 @@ public class MainWindowTitleController implements FxController {
 	private final AppLifecycleListener appLifecycle;
 	private final Stage window;
 	private final FxApplication application;
-	private final boolean minimizeToSysTray;
+	private final boolean isTrayIconPresent;
 	private final UpdateChecker updateChecker;
 	private final BooleanBinding updateAvailable;
 	private final LicenseHolder licenseHolder;
@@ -39,11 +39,11 @@ public class MainWindowTitleController implements FxController {
 	private double yOffset;
 
 	@Inject
-	MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
+	MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean isTrayIconPresent, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
 		this.appLifecycle = appLifecycle;
 		this.window = window;
 		this.application = application;
-		this.minimizeToSysTray = minimizeToSysTray;
+		this.isTrayIconPresent = isTrayIconPresent;
 		this.updateChecker = updateChecker;
 		this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
 		this.licenseHolder = licenseHolder;
@@ -71,7 +71,7 @@ public class MainWindowTitleController implements FxController {
 
 	@FXML
 	public void close() {
-		if (minimizeToSysTray) {
+		if (isTrayIconPresent) {
 			window.close();
 		} else {
 			appLifecycle.quit();
@@ -112,8 +112,8 @@ public class MainWindowTitleController implements FxController {
 		return updateAvailable.get();
 	}
 
-	public boolean isMinimizeToSysTray() {
-		return minimizeToSysTray;
+	public boolean isTrayIconPresent() {
+		return isTrayIconPresent;
 	}
 
 	public BooleanBinding debugModeEnabledProperty() {

+ 6 - 4
main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java

@@ -27,6 +27,7 @@ import javafx.scene.control.Toggle;
 import javafx.scene.control.ToggleGroup;
 import javafx.stage.Stage;
 import javafx.util.StringConverter;
+import java.awt.SystemTray;
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.ResourceBundle;
@@ -41,7 +42,6 @@ public class GeneralPreferencesController implements FxController {
 
 	private final Stage window;
 	private final Settings settings;
-	private final boolean trayMenuSupported;
 	private final Optional<AutoStartProvider> autoStartProvider;
 	private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
 	private final LicenseHolder licenseHolder;
@@ -53,6 +53,7 @@ public class GeneralPreferencesController implements FxController {
 	private final ErrorComponent.Builder errorComponent;
 	public ChoiceBox<UiTheme> themeChoiceBox;
 	public ChoiceBox<KeychainBackend> keychainBackendChoiceBox;
+	public CheckBox showTrayIconCheckbox;
 	public CheckBox startHiddenCheckbox;
 	public CheckBox debugModeCheckbox;
 	public CheckBox autoStartCheckbox;
@@ -61,10 +62,9 @@ public class GeneralPreferencesController implements FxController {
 	public RadioButton nodeOrientationRtl;
 
 	@Inject
-	GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartProvider> autoStartProvider, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
+	GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, Set<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
 		this.window = window;
 		this.settings = settings;
-		this.trayMenuSupported = trayMenuSupported;
 		this.autoStartProvider = autoStartProvider;
 		this.keychainAccessProviders = keychainAccessProviders;
 		this.selectedTabProperty = selectedTabProperty;
@@ -85,6 +85,8 @@ public class GeneralPreferencesController implements FxController {
 		themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
 		themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
 
+		showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
+
 		startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
 
 		debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
@@ -106,7 +108,7 @@ public class GeneralPreferencesController implements FxController {
 	}
 
 	public boolean isTrayMenuSupported() {
-		return this.trayMenuSupported;
+		return SystemTray.isSupported();
 	}
 
 	public boolean isAutoStartSupported() {

+ 13 - 8
main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java

@@ -1,6 +1,7 @@
 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.preferences.SelectedPreferencesTab;
@@ -103,32 +104,36 @@ class TrayMenuController {
 		return actionEvent -> consumer.accept(vault);
 	}
 
+	private void quitApplication(EventObject actionEvent) {
+		appLifecycle.quit();
+	}
+
 	private void unlockVault(Vault vault) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.startUnlockWorkflow(vault, Optional.empty()));
+		showMainAppAndThen(app -> app.startUnlockWorkflow(vault, Optional.empty()));
 	}
 
 	private void lockVault(Vault vault) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.startLockWorkflow(vault, Optional.empty()));
+		showMainAppAndThen(app -> app.startLockWorkflow(vault, Optional.empty()));
 	}
 
 	private void lockAllVaults(ActionEvent actionEvent) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
+		showMainAppAndThen(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
 	}
 
 	private void revealVault(Vault vault) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().reveal(vault));
+		showMainAppAndThen(app -> app.getVaultService().reveal(vault));
 	}
 
 	void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
+		showMainAppAndThen(app -> app.showMainWindow());
 	}
 
 	private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
-		fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
+		showMainAppAndThen(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
 	}
 
-	private void quitApplication(EventObject actionEvent) {
-		appLifecycle.quit();
+	private void showMainAppAndThen(Consumer<FxApplication> action) {
+		fxApplicationStarter.get().thenAccept(action);
 	}
 
 }

+ 1 - 1
main/ui/src/main/resources/fxml/main_window_title.fxml

@@ -54,7 +54,7 @@
 				<Tooltip text="%main.preferencesBtn.tooltip"/>
 			</tooltip>
 		</Button>
-		<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${!controller.minimizeToSysTray}" managed="${!controller.minimizeToSysTray}">
+		<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${!controller.trayIconPresent}" managed="${!controller.trayIconPresent}">
 			<graphic>
 				<FontAwesome5IconView glyph="WINDOW_MINIMIZE" glyphSize="12"/>
 			</graphic>

+ 3 - 1
main/ui/src/main/resources/fxml/preferences_general.fxml

@@ -32,7 +32,9 @@
 			<RadioButton fx:id="nodeOrientationRtl" text="%preferences.general.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
 		</HBox>
 
-		<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
+		<CheckBox fx:id="showTrayIconCheckbox" text="%preferences.general.showTrayIcon" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
+
+		<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" />
 
 		<HBox spacing="6" alignment="CENTER_LEFT">
 			<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>

+ 1 - 0
main/ui/src/main/resources/i18n/strings.properties

@@ -150,6 +150,7 @@ preferences.general.theme.automatic=Automatic
 preferences.general.theme.light=Light
 preferences.general.theme.dark=Dark
 preferences.general.unlockThemes=Unlock dark mode
+preferences.general.showTrayIcon=Show tray icon (requires restart)
 preferences.general.startHidden=Hide window when starting Cryptomator
 preferences.general.debugLogging=Enable debug logging
 preferences.general.debugDirectory=Reveal log files