Ver código fonte

Merge branch 'develop' into feature/event-view

Armin Schrenk 3 meses atrás
pai
commit
7a50ba0b43
22 arquivos alterados com 161 adições e 62 exclusões
  1. 9 9
      .github/ISSUE_TEMPLATE/bug.yml
  2. 8 8
      .github/ISSUE_TEMPLATE/feature.yml
  3. 1 1
      .github/workflows/debian.yml
  4. 10 4
      dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml
  5. 4 1
      src/main/java/org/cryptomator/common/settings/VaultSettings.java
  6. 3 0
      src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java
  7. 8 3
      src/main/java/org/cryptomator/common/vaults/VaultListManager.java
  8. 3 1
      src/main/java/org/cryptomator/ui/addvaultwizard/ReadmeGenerator.java
  9. 2 0
      src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java
  10. 2 2
      src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
  11. 3 4
      src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
  12. 6 0
      src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java
  13. 29 0
      src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java
  14. 1 0
      src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java
  15. 42 10
      src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java
  16. 2 3
      src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java
  17. 4 3
      src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java
  18. 6 0
      src/main/resources/css/dark_theme.css
  19. 6 0
      src/main/resources/css/light_theme.css
  20. 8 8
      src/main/resources/fxml/simple_dialog.fxml
  21. 1 2
      src/main/resources/i18n/strings.properties
  22. 3 3
      src/test/java/org/cryptomator/ui/addvaultwizard/ReadMeGeneratorTest.java

+ 9 - 9
.github/ISSUE_TEMPLATE/bug.yml

@@ -1,7 +1,14 @@
 name: Bug Report
 description: Create a report to help us improve
-labels: ["type:bug"]
+type: "Bug"
 body:
+  - type: input
+    id: summary
+    attributes:
+      label: Summary
+      placeholder: Please summarize your problem.
+    validations:
+      required: true
   - type: checkboxes
     id: terms
     attributes:
@@ -11,13 +18,6 @@ body:
         required: true
       - label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
         required: true
-  - type: input
-    id: summary
-    attributes:
-      label: Summary
-      placeholder: Please summarize your problem.
-    validations:
-      required: true
   - type: textarea
     id: software-versions
     attributes:
@@ -97,4 +97,4 @@ body:
     id: further-info
     attributes:
       label: Anything else?
-      description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
+      description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?

+ 8 - 8
.github/ISSUE_TEMPLATE/feature.yml

@@ -1,7 +1,14 @@
 name: Feature Request
 description: Suggest an idea for this project
-labels: ["type:feature-request"]
+type: "Feature"
 body:
+  - type: input
+    id: summary
+    attributes:
+      label: Summary
+      placeholder: Please summarize your feature request.
+    validations:
+      required: true
   - type: checkboxes
     id: terms
     attributes:
@@ -11,13 +18,6 @@ body:
         required: true
       - label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
         required: true
-  - type: input
-    id: summary
-    attributes:
-      label: Summary
-      placeholder: Please summarize your feature request.
-    validations:
-      required: true
   - type: textarea
     id: motivation
     attributes:

+ 1 - 1
.github/workflows/debian.yml

@@ -28,7 +28,7 @@ env:
 jobs:
   build:
     name: Build Debian Package
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v4
       - id: versions

+ 10 - 4
dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml

@@ -6,6 +6,12 @@
 	<name>Cryptomator</name>
 	<summary>Encryption for your cloud made easy</summary>
 
+	<keywords>
+		<keyword>encryption</keyword>
+		<keyword>security</keyword>
+		<keyword>privacy</keyword>
+	</keywords>
+
 	<description>
 		<p>
 			Cryptomator provides easy-to-use, transparent, client-side encryption for your cloud.
@@ -43,16 +49,16 @@
 
 	<screenshots>
 		<screenshot type="default">
-			<caption>Encrypt your data, protect your privacy</caption>
-			<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
+			<caption>Encrypts your data, protects your privacy</caption>
+			<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
 		</screenshot>
 		<screenshot>
 			<caption>Dark theme available</caption>
 			<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
 		</screenshot>
 		<screenshot>
-			<caption>Uses AES-GCM 256 - an industry standardized, quantum resistant encryption</caption>
-			<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
+			<caption>Easy to use - work on encrypted files as if they were not</caption>
+			<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
 		</screenshot>
 	</screenshots>
 

+ 4 - 1
src/main/java/org/cryptomator/common/settings/VaultSettings.java

@@ -58,6 +58,7 @@ public class VaultSettings {
 	public final StringExpression mountName;
 	public final StringProperty mountService;
 	public final IntegerProperty port;
+	public final StringProperty lastKnownKeyLoader;
 
 	VaultSettings(VaultSettingsJson json) {
 		this.id = json.id;
@@ -74,6 +75,7 @@ public class VaultSettings {
 		this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
 		this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
 		this.port = new SimpleIntegerProperty(this, "port", json.port);
+		this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
 		// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
 		this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
 			final String name;
@@ -99,7 +101,7 @@ public class VaultSettings {
 	}
 
 	Observable[] observables() {
-		return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
+		return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
 	}
 
 	public static VaultSettings withRandomId() {
@@ -130,6 +132,7 @@ public class VaultSettings {
 		json.mountPoint = mountPoint.map(Path::toString).getValue();
 		json.mountService = mountService.get();
 		json.port = port.get();
+		json.lastKnownKeyLoader = lastKnownKeyLoader.get();
 		return json;
 	}
 

+ 3 - 0
src/main/java/org/cryptomator/common/settings/VaultSettingsJson.java

@@ -48,6 +48,9 @@ class VaultSettingsJson {
 	@JsonProperty("mountService")
 	String mountService;
 
+	@JsonProperty("lastKnownKeyLoader")
+	String lastKnownKeyLoader;
+
 	@JsonProperty("port")
 	int port = VaultSettings.DEFAULT_PORT;
 

+ 8 - 3
src/main/java/org/cryptomator/common/vaults/VaultListManager.java

@@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.ResourceBundle;
 
@@ -49,9 +50,9 @@ public class VaultListManager {
 	@Inject
 	public VaultListManager(ObservableList<Vault> vaultList, //
 							AutoLocker autoLocker, //
-							List<MountService> mountServices,
-							VaultComponent.Factory vaultComponentFactory,
-							ResourceBundle resourceBundle,
+							List<MountService> mountServices, //
+							VaultComponent.Factory vaultComponentFactory, //
+							ResourceBundle resourceBundle, //
 							Settings settings) {
 		this.vaultList = vaultList;
 		this.autoLocker = autoLocker;
@@ -114,6 +115,10 @@ public class VaultListManager {
 	private Vault create(VaultSettings vaultSettings) {
 		var wrapper = new VaultConfigCache(vaultSettings);
 		try {
+			if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
+				var keyIdScheme = wrapper.get().getKeyId().getScheme();
+				vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
+			}
 			var vaultState = determineVaultState(vaultSettings.path.get());
 			if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
 				wrapper.reloadConfig();

+ 3 - 1
src/main/java/org/cryptomator/ui/addvaultwizard/ReadmeGenerator.java

@@ -76,8 +76,10 @@ public class ReadmeGenerator {
 		input.chars().forEachOrdered(c -> {
 			if (c < 128) {
 				sb.append((char) c);
+			} else if (c <= 0xFF) {
+				sb.append("\\'").append(String.format("%02X", c));
 			} else if (c < 0xFFFF) {
-				sb.append("\\u").append(c);
+				sb.append("\\uc1\\u").append(c);
 			}
 		});
 	}

+ 2 - 0
src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java

@@ -15,6 +15,7 @@ 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.masterkeyfile.MasterkeyFileLoadingStrategy;
 import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
 import org.jetbrains.annotations.VisibleForTesting;
 import org.slf4j.Logger;
@@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController {
 				.thenRunAsync(this::convertInternal, backgroundExecutorService) //
 				.whenCompleteAsync((result, exception) -> {
 					if (exception == null) {
+						vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
 						LOG.info("Conversion of vault {} succeeded.", vault.getPath());
 						window.setScene(successScene.get());
 					} else {

+ 2 - 2
src/main/java/org/cryptomator/ui/dialogs/Dialogs.java

@@ -38,7 +38,7 @@ public class Dialogs {
 				.setMessageKey("removeVault.message") //
 				.setDescriptionKey("removeVault.description") //
 				.setIcon(FontAwesome5Icon.QUESTION) //
-				.setOkButtonKey("removeVault.confirmBtn") //
+				.setOkButtonKey("generic.button.remove") //
 				.setCancelButtonKey("generic.button.cancel") //
 				.setOkAction(stage -> {
 					LOG.debug("Removing vault {}.", vault.getDisplayName());
@@ -54,7 +54,7 @@ public class Dialogs {
 				.setMessageKey("removeCert.message") //
 				.setDescriptionKey("removeCert.description") //
 				.setIcon(FontAwesome5Icon.QUESTION) //
-				.setOkButtonKey("removeCert.confirmBtn") //
+				.setOkButtonKey("generic.button.remove") //
 				.setCancelButtonKey("generic.button.cancel") //
 				.setOkAction(stage -> {
 					settings.licenseKey.set(null);

+ 3 - 4
src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java

@@ -31,8 +31,9 @@ public class SimpleDialog {
 		FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
 				new SimpleDialogController(resolveText(builder.messageKey, null), //
 						resolveText(builder.descriptionKey, null), //
-						builder.icon, resolveText(builder.okButtonKey, null), //
-						resolveText(builder.cancelButtonKey, null), //
+						builder.icon, //
+						resolveText(builder.okButtonKey, null), //
+						builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
 						() -> builder.okAction.accept(dialogStage), //
 						() -> builder.cancelAction.accept(dialogStage)), //
 				Scene::new, builder.resourceBundle);
@@ -67,7 +68,6 @@ public class SimpleDialog {
 		private String descriptionKey;
 		private String okButtonKey;
 		private String cancelButtonKey;
-
 		private FontAwesome5Icon icon;
 		private Consumer<Stage> okAction = Stage::close;
 		private Consumer<Stage> cancelAction = Stage::close;
@@ -128,7 +128,6 @@ public class SimpleDialog {
 			Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
 			Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
 			Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
-			Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set.");
 
 			try {
 				return new SimpleDialog(this);

+ 6 - 0
src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java

@@ -14,6 +14,7 @@ public class SimpleDialogController implements FxController {
 	private final String cancelButtonText;
 	private final Runnable okAction;
 	private final Runnable cancelAction;
+	private final boolean cancelButtonVisible;
 
 	public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
 		this.message = message;
@@ -23,6 +24,11 @@ public class SimpleDialogController implements FxController {
 		this.cancelButtonText = cancelButtonText;
 		this.okAction = okAction;
 		this.cancelAction = cancelAction;
+		this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
+	}
+
+	public boolean isCancelButtonVisible() {
+		return cancelButtonVisible;
 	}
 
 	public String getMessage() {

+ 29 - 0
src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java

@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading;
 import org.cryptomator.cryptolib.api.Masterkey;
 import org.cryptomator.cryptolib.api.MasterkeyLoader;
 import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
+import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
+import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,6 +30,33 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
 	@Override
 	Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
 
+	/**
+	 * Determines whether the provided key loader scheme corresponds to a Hub Vault.
+	 * <p>
+	 * This method compares the {@code keyLoader} parameter with the known Hub Vault schemes
+	 * {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}.
+	 *
+	 * @param keyLoader A string representing the key loader scheme to be checked.
+	 * @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise.
+	 */
+	static boolean isHubVault(String keyLoader) {
+		return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader);
+	}
+
+	/**
+	 * Determines whether the provided key loader scheme corresponds to a Masterkey File Vault.
+	 * <p>
+	 * This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme
+	 * {@link MasterkeyFileLoadingStrategy#SCHEME}.
+	 * </p>
+	 *
+	 * @param keyLoader A string representing the key loader scheme to be checked.
+	 * @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise.
+	 */
+	static boolean isMasterkeyFileVault(String keyLoader) {
+		return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader);
+	}
+
 	/**
 	 * Allows the loader to try and recover from an exception thrown during the last attempt.
 	 *

+ 1 - 0
src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java

@@ -165,6 +165,7 @@ public class ReceiveKeyController implements FxController {
 		var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
 		var request = HttpRequest.newBuilder(vaultKeyUri) //
 				.header("Authorization", "Bearer " + bearerToken) //
+				.header("Hub-Device-ID", deviceId) //
 				.GET() //
 				.timeout(REQ_TIMEOUT) //
 				.build();

+ 42 - 10
src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java

@@ -19,9 +19,11 @@ import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.fxml.FXML;
+import javafx.geometry.Rectangle2D;
 import javafx.scene.layout.StackPane;
 import javafx.stage.Screen;
 import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
 
 @MainWindowScoped
 public class MainWindowController implements FxController {
@@ -68,18 +70,15 @@ public class MainWindowController implements FxController {
 		int y = settings.windowYPosition.get();
 		int width = settings.windowWidth.get();
 		int height = settings.windowHeight.get();
-		if (windowPositionSaved(x, y, width, height) ) {
-			if(isWithinDisplayBounds(x, y, width, height)) { //use stored window position
-				window.setX(x);
-				window.setY(y);
-				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
-				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
-			} else if(isWithinDisplayBounds((int) window.getX(), (int) window.getY(), width, height)) { //just reset position of upper left corner, keep window size
-				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
-				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
-			} //else reset window completely
+		if (windowPositionSaved(x, y, width, height)) {
+			window.setX(x);
+			window.setY(y);
+			window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
+			window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
 		}
 
+		window.setOnShowing(this::checkDisplayBounds);
+
 		settings.windowXPosition.bind(window.xProperty());
 		settings.windowYPosition.bind(window.yProperty());
 		settings.windowWidth.bind(window.widthProperty());
@@ -90,6 +89,39 @@ public class MainWindowController implements FxController {
 		return x != 0 || y != 0 || width != 0 || height != 0;
 	}
 
+	private void checkDisplayBounds(WindowEvent windowEvent) {
+		int x = settings.windowXPosition.get();
+		int y = settings.windowYPosition.get();
+		int width = settings.windowWidth.get();
+		int height = settings.windowHeight.get();
+
+		// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
+		// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
+		// If the position is (-32000, -32000), restore to the last saved position
+		if (window.getX() == -32000 && window.getY() == -32000) {
+			window.setX(x);
+			window.setY(y);
+			window.setWidth(width);
+			window.setHeight(height);
+		}
+
+		Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
+		if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
+			LOG.debug("Resetting window position due to insufficient screen overlap");
+			var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
+			var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
+			//check if we can keep width and height
+			if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
+				//if so, keep window size
+				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
+				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
+			}
+			//reset position of upper left corner
+			window.setX(centeredX);
+			window.setY(centeredY);
+		}
+	}
+
 	private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
 		// define a rect which is inset on all sides from the window's rect:
 		final int shrinkedX = x + 20; // 20px left

+ 2 - 3
src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java

@@ -3,7 +3,7 @@ package org.cryptomator.ui.sharevault;
 import dagger.Lazy;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
+import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
 
 import javax.inject.Inject;
 import javafx.application.Application;
@@ -33,8 +33,7 @@ public class ShareVaultController implements FxController {
 		this.window = window;
 		this.application = application;
 		this.vault = vault;
-		var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
-		this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
+		this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get());
 	}
 
 	@FXML

+ 4 - 3
src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java

@@ -3,6 +3,7 @@ package org.cryptomator.ui.vaultoptions;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.common.vaults.VaultState;
 import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
 import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
 import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
 import org.slf4j.Logger;
@@ -42,11 +43,11 @@ public class VaultOptionsController implements FxController {
 		window.setOnShowing(this::windowWillAppear);
 		selectedTabProperty.addListener(observable -> this.selectChosenTab());
 		tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
-		var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
-		if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
+		var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get();
+		if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){
 			tabPane.getTabs().remove(keyTab);
 		}
-		if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
+		if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){
 			tabPane.getTabs().remove(hubTab);
 		}
 

+ 6 - 0
src/main/resources/css/dark_theme.css

@@ -359,6 +359,12 @@
 	-fx-background-color: PRIMARY;
 }
 
+.notification-debug:hover .notification-label,
+.notification-update:hover .notification-label,
+.notification-support:hover .notification-label {
+ 	-fx-underline:true;
+}
+
 /*******************************************************************************
  *                                                                             *
  * ScrollBar                                                                   *

+ 6 - 0
src/main/resources/css/light_theme.css

@@ -397,6 +397,12 @@
 	-fx-background-color: PRIMARY;
 }
 
+.notification-debug:hover .notification-label,
+.notification-update:hover .notification-label,
+.notification-support:hover .notification-label {
+	-fx-underline:true;
+}
+
 /*******************************************************************************
  *                                                                             *
  * ScrollBar                                                                   *

+ 8 - 8
src/main/resources/fxml/simple_dialog.fxml

@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.layout.HBox?>
-<?import javafx.scene.control.Button?>
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Label?>
 <?import javafx.scene.Group?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.Region?>
 <?import javafx.scene.layout.StackPane?>
-<?import javafx.scene.shape.Circle?>
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.layout.Region?>
-<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.shape.Circle?>
 <HBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
@@ -41,7 +41,7 @@
 			<Region VBox.vgrow="ALWAYS" minHeight="18"/>
 			<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
 				<buttons>
-					<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
+					<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
 					<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
 				</buttons>
 			</ButtonBar>

+ 1 - 2
src/main/resources/i18n/strings.properties

@@ -108,7 +108,6 @@ addvaultwizard.success.unlockNow=Unlock Now
 removeVault.title=Remove "%s"
 removeVault.message=Remove vault?
 removeVault.description=This will only make Cryptomator forget about this vault. You can add it again. No encrypted files will be deleted from your hard drive.
-removeVault.confirmBtn=Remove Vault
 
 # Change Password
 changepassword.title=Change Password
@@ -285,7 +284,7 @@ preferences.title=Preferences
 ## General
 preferences.general=General
 preferences.general.startHidden=Hide window when starting Cryptomator
-preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
+preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
 preferences.general.debugLogging=Enable debug logging
 preferences.general.debugDirectory=Reveal log files
 preferences.general.autoStart=Launch Cryptomator on system start

+ 3 - 3
src/test/java/org/cryptomator/ui/addvaultwizard/ReadMeGeneratorTest.java

@@ -15,8 +15,8 @@ public class ReadMeGeneratorTest {
 	@ParameterizedTest
 	@CsvSource({ //
 			"test,test", //
-			"t\u00E4st,t\\u228st", //
-			"t\uD83D\uDE09st,t\\u55357\\u56841st", //
+			"t\u00E4st,t\\'E4st", //
+			"t\uD83D\uDE09st,t\\uc1\\u55357\\uc1\\u56841st", //
 	})
 	public void testEscapeNonAsciiChars(String input, String expectedResult) {
 		ReadmeGenerator readmeGenerator = new ReadmeGenerator(null);
@@ -40,7 +40,7 @@ public class ReadMeGeneratorTest {
 		MatcherAssert.assertThat(result, CoreMatchers.startsWith("{\\rtf1\\fbidis\\ansi\\uc0\\fs32"));
 		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Dear User,}\\par"));
 		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 \\b please don't touch the \"d\" directory.}\\par "));
-		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\u55357\\u56841}\\par"));
+		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\uc1\\u55357\\uc1\\u56841}\\par"));
 		MatcherAssert.assertThat(result, CoreMatchers.endsWith("}"));
 	}