ソースを参照

Re-layouted unlock UI and added textfield for custom mount flags (atm only supported for FUSE - see #802)

Sebastian Stenzel 6 年 前
コミット
b15d410378

+ 32 - 9
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -11,6 +11,7 @@ package org.cryptomator.ui.controllers;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import javafx.application.Application;
+import javafx.beans.Observable;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.fxml.FXML;
@@ -27,6 +28,7 @@ import javafx.scene.control.TextField;
 import javafx.scene.input.KeyEvent;
 import javafx.scene.layout.GridPane;
 import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
 import javafx.scene.text.Text;
 import javafx.stage.DirectoryChooser;
 import javafx.stage.Stage;
@@ -113,6 +115,12 @@ public class UnlockController implements ViewController {
 	@FXML
 	private TextField mountName;
 
+	@FXML
+	private CheckBox useCustomMountFlags;
+
+	@FXML
+	private TextField mountFlags;
+
 	@FXML
 	private CheckBox revealAfterMount;
 
@@ -134,17 +142,14 @@ public class UnlockController implements ViewController {
 	@FXML
 	private ProgressIndicator progressIndicator;
 
-	@FXML
-	private Text progressText;
-
 	@FXML
 	private Hyperlink downloadsPageLink;
 
 	@FXML
-	private GridPane advancedOptions;
+	private VBox advancedOptions;
 
 	@FXML
-	private GridPane root;
+	private VBox root;
 
 	@FXML
 	private CheckBox unlockAfterStartup;
@@ -158,6 +163,9 @@ public class UnlockController implements ViewController {
 		unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
 		mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
 		mountName.textProperty().addListener(this::mountNameDidChange);
+		useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange);
+		mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not());
+		mountFlags.textProperty().addListener(this::mountFlagsDidChange);
 		savePassword.setDisable(!keychainAccess.isPresent());
 		unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
 
@@ -173,7 +181,6 @@ public class UnlockController implements ViewController {
 		}
 	}
 
-
 	@Override
 	public Parent getRoot() {
 		return root;
@@ -199,7 +206,6 @@ public class UnlockController implements ViewController {
 		advancedOptions.setVisible(false);
 		advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
 		progressIndicator.setVisible(false);
-		progressText.setText(null);
 		state.successMessage().map(localization::getString).ifPresent(messageText::setText);
 		if (SystemUtils.IS_OS_WINDOWS) {
 			winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
@@ -212,6 +218,8 @@ public class UnlockController implements ViewController {
 		}
 		downloadsPageLink.setVisible(false);
 		mountName.setText(vault.getMountName());
+		useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags());
+		mountFlags.setText(vault.getMountFlags());
 		savePassword.setSelected(false);
 		// auto-fill pw from keychain:
 		if (keychainAccess.isPresent()) {
@@ -318,6 +326,23 @@ public class UnlockController implements ViewController {
 		} else {
 			vault.setMountName(newValue);
 		}
+		if (!useCustomMountFlags.isSelected()) {
+			mountFlags.setText(vault.getMountFlags()); // flags might depend on the volume name
+		}
+	}
+
+
+	private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused")Boolean oldValue, Boolean newValue) {
+		if (!newValue) {
+			vault.setMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS);
+			mountFlags.setText(vault.getMountFlags());
+		}
+	}
+
+	private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused")String oldValue, String newValue) {
+		if (useCustomMountFlags.isSelected()) {
+			vault.setMountFlags(newValue);
+		}
 	}
 
 	@FXML
@@ -435,7 +460,6 @@ public class UnlockController implements ViewController {
 
 		CharSequence password = passwordField.getCharacters();
 		Tasks.create(() -> {
-			progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
 			vault.unlock(password);
 			if (keychainAccess.isPresent() && savePassword.isSelected()) {
 				keychainAccess.get().storePassphrase(vault.getId(), password);
@@ -476,7 +500,6 @@ public class UnlockController implements ViewController {
 		}).andFinally(() -> {
 			advancedOptions.setDisable(false);
 			progressIndicator.setVisible(false);
-			progressText.setText(null);
 		}).runOnce(executor);
 	}
 

+ 13 - 0
main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java

@@ -0,0 +1,13 @@
+package org.cryptomator.ui.model;
+
+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 DefaultMountFlags {
+}

+ 1 - 1
main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java

@@ -44,7 +44,7 @@ public class DokanyVolume implements Volume {
 	}
 
 	@Override
-	public void mount(CryptoFileSystem fs) throws VolumeException, IOException {
+	public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
 		Path mountPath = getMountPoint();
 		String mountName = vaultSettings.mountName().get();
 		try {

+ 7 - 12
main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.model;
 
+import com.google.common.base.Splitter;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.Environment;
 import org.cryptomator.common.settings.VaultSettings;
@@ -21,7 +22,6 @@ import java.nio.file.Files;
 import java.nio.file.NotDirectoryException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.List;
 import java.util.Optional;
 
 public class FuseVolume implements Volume {
@@ -44,7 +44,7 @@ public class FuseVolume implements Volume {
 	}
 
 	@Override
-	public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
+	public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
 		Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
 		if (optionalCustomMountPoint.isPresent()) {
 			Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
@@ -55,7 +55,7 @@ public class FuseVolume implements Volume {
 			this.mountPoint = prepareTemporaryMountPoint();
 			LOG.debug("Successfully created mount point: {}", mountPoint);
 		}
-		mount(fs.getPath("/"));
+		mount(fs.getPath("/"), mountFlags);
 	}
 
 	private void checkProvidedMountPoint(Path mountPoint) throws IOException {
@@ -96,11 +96,11 @@ public class FuseVolume implements Volume {
 		throw new VolumeException("Did not find feasible mount point.");
 	}
 
-	private void mount(Path root) throws VolumeException {
+	private void mount(Path root, String mountFlags) throws VolumeException {
 		try {
 			Mounter mounter = FuseMountFactory.getMounter();
 			EnvironmentVariables envVars = EnvironmentVariables.create() //
-					.withFlags(mountFlags(mounter))
+					.withFlags(splitFlags(mountFlags))
 					.withMountPoint(mountPoint) //
 					.build();
 			this.fuseMnt = mounter.mount(root, envVars);
@@ -109,13 +109,8 @@ public class FuseVolume implements Volume {
 		}
 	}
 
-	private String[] mountFlags(Mounter mounter) {
-		List<String> mountFlags = vaultSettings.mountFlags().get();
-		if (mountFlags.isEmpty()) {
-			return mounter.defaultMountFlags();
-		} else {
-			return mountFlags.toArray(String[]::new);
-		}
+	private String[] splitFlags(String str) {
+		return Splitter.on(' ').splitToList(str).toArray(String[]::new);
 	}
 
 	@Override

+ 13 - 0
main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java

@@ -0,0 +1,13 @@
+package org.cryptomator.ui.model;
+
+import javax.inject.Scope;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Scope
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@interface PerVault {
+
+}

+ 26 - 7
main/ui/src/main/java/org/cryptomator/ui/model/Vault.java

@@ -25,7 +25,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
 import org.cryptomator.cryptofs.CryptoFileSystemProvider;
 import org.cryptomator.cryptolib.api.CryptoException;
 import org.cryptomator.cryptolib.api.InvalidPassphraseException;
-import org.cryptomator.ui.model.VaultModule.PerVault;
 import org.fxmisc.easybind.EasyBind;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,6 +43,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 @PerVault
 public class Vault {
@@ -54,6 +54,7 @@ public class Vault {
 
 	private final VaultSettings vaultSettings;
 	private final Provider<Volume> volumeProvider;
+	private final Supplier<String> defaultMountFlags;
 	private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
 	private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
 
@@ -64,9 +65,10 @@ public class Vault {
 	}
 
 	@Inject
-	Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
+	Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags Supplier<String> defaultMountFlags) {
 		this.vaultSettings = vaultSettings;
 		this.volumeProvider = volumeProvider;
+		this.defaultMountFlags = defaultMountFlags;
 	}
 
 	// ******************************************************************************
@@ -110,7 +112,7 @@ public class Vault {
 			}
 			CryptoFileSystem fs = getCryptoFileSystem(passphrase);
 			volume = volumeProvider.get();
-			volume.mount(fs);
+			volume.mount(fs, getMountFlags());
 			Platform.runLater(() -> {
 				state.set(State.UNLOCKED);
 			});
@@ -241,10 +243,6 @@ public class Vault {
 		}
 	}
 
-	public String getMountName() {
-		return vaultSettings.mountName().get();
-	}
-
 	public String getCustomMountPath() {
 		return vaultSettings.individualMountPath().getValueSafe();
 	}
@@ -253,6 +251,10 @@ public class Vault {
 		vaultSettings.individualMountPath().set(mountPath);
 	}
 
+	public String getMountName() {
+		return vaultSettings.mountName().get();
+	}
+
 	public void setMountName(String mountName) throws IllegalArgumentException {
 		if (StringUtils.isBlank(mountName)) {
 			throw new IllegalArgumentException("mount name is empty");
@@ -261,6 +263,23 @@ public class Vault {
 		}
 	}
 
+	public boolean isHavingCustomMountFlags() {
+		return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
+	}
+
+	public String getMountFlags() {
+		String mountFlags = vaultSettings.mountFlags().get();
+		if (Strings.isNullOrEmpty(mountFlags)) {
+			return defaultMountFlags.get();
+		} else {
+			return mountFlags;
+		}
+	}
+
+	public void setMountFlags(String mountFlags) {
+		vaultSettings.mountFlags().set(mountFlags);
+	}
+
 	public Character getWinDriveLetter() {
 		if (vaultSettings.winDriveLetter().get() == null) {
 			return null;

+ 0 - 1
main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java

@@ -7,7 +7,6 @@ package org.cryptomator.ui.model;
 
 import dagger.BindsInstance;
 import org.cryptomator.common.settings.VaultSettings;
-import org.cryptomator.ui.model.VaultModule.PerVault;
 
 import dagger.Subcomponent;
 

+ 68 - 11
main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java

@@ -7,28 +7,24 @@ package org.cryptomator.ui.model;
 
 import dagger.Module;
 import dagger.Provides;
+import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VaultSettings;
 import org.cryptomator.common.settings.VolumeImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.inject.Scope;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
 
 @Module
 public class VaultModule {
 
 	private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
 
-	@Scope
-	@Documented
-	@Retention(RetentionPolicy.RUNTIME)
-	@interface PerVault {
-
-	}
-
 	@Provides
 	public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
 		VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
@@ -45,4 +41,65 @@ public class VaultModule {
 		}
 	}
 
+	@Provides
+	@PerVault
+	@DefaultMountFlags
+	public Supplier<String> provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
+		VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
+		switch (preferredImpl) {
+			case FUSE:
+				if (SystemUtils.IS_OS_MAC_OSX) {
+					return () -> getMacFuseDefaultMountFlags(settings, vaultSettings);
+				} else if (SystemUtils.IS_OS_LINUX) {
+					return () -> getLinuxFuseDefaultMountFlags(settings, vaultSettings);
+				}
+			case DOKANY:
+				return () -> getDokanyDefaultMountFlags(settings, vaultSettings);
+			default:
+				return () -> "--flags-supported-on-FUSE-or-DOKANY-only";
+		}
+	}
+
+	private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
+		assert SystemUtils.IS_OS_MAC_OSX;
+		// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
+		try {
+			Path userHome = Paths.get(System.getProperty("user.home"));
+			int uid = (int) Files.getAttribute(userHome, "unix:uid");
+			int gid = (int) Files.getAttribute(userHome, "unix:gid");
+			return "-ovolname=" + vaultSettings.mountName().get() // volume name
+					+ " -ouid=" + uid //
+					+ " -ogid=" + gid //
+					+ " -oatomic_o_trunc" //
+					+ " -oauto_xattr" //
+					+ " -oauto_cache" //
+					+ " -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC" // show files names in Unicode NFD encoding
+					+ " -onoappledouble" // vastly impacts performance for some reason...
+					+ " -odefault_permissions"; // let the kernel assume permissions based on file attributes etc
+		} catch (IOException e) {
+			LOG.error("Could not read uid/gid from USER_HOME", e);
+			return "";
+		}
+	}
+
+	private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
+		assert SystemUtils.IS_OS_LINUX;
+		try {
+			Path userHome = Paths.get(System.getProperty("user.home"));
+			int uid = (int) Files.getAttribute(userHome, "unix:uid");
+			int gid = (int) Files.getAttribute(userHome, "unix:gid");
+			return "-oauto_unmount" //
+					+ " -ouid=" + uid //
+					+ " -ogid=" + gid;
+		} catch (IOException e) {
+			LOG.error("Could not read uid/gid from USER_HOME", e);
+			return "";
+		}
+	}
+
+	private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
+		// TODO
+		return "--not-yet-supported";
+	}
+
 }

+ 1 - 1
main/ui/src/main/java/org/cryptomator/ui/model/Volume.java

@@ -22,7 +22,7 @@ public interface Volume {
 	 * @param fs
 	 * @throws IOException
 	 */
-	void mount(CryptoFileSystem fs) throws IOException, VolumeException;
+	void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException;
 
 	void reveal() throws VolumeException;
 

+ 1 - 2
main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java

@@ -11,7 +11,6 @@ import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
@@ -35,7 +34,7 @@ public class WebDavVolume implements Volume {
 	}
 
 	@Override
-	public void mount(CryptoFileSystem fs) throws VolumeException {
+	public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
 		if (server == null) {
 			server = serverProvider.get();
 		}

+ 61 - 82
main/ui/src/main/resources/fxml/unlock.fxml

@@ -17,102 +17,81 @@
 <?import javafx.scene.control.ProgressIndicator?>
 <?import javafx.scene.control.Separator?>
 <?import javafx.scene.control.TextField?>
-<?import javafx.scene.layout.*?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.VBox?>
 <?import javafx.scene.text.Text?>
 <?import javafx.scene.text.TextFlow?>
 <?import org.cryptomator.ui.controls.SecPasswordField?>
-<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
+<VBox fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" spacing="12" alignment="BOTTOM_CENTER" xmlns:fx="http://javafx.com/fxml" prefWidth="400">
+
 	<padding>
-		<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
+		<Insets top="24"/>
 	</padding>
 
-	<columnConstraints>
-		<ColumnConstraints percentWidth="38.2"/>
-		<ColumnConstraints percentWidth="61.8"/>
-	</columnConstraints>
-
-	<children>
-		<!-- Row 0 -->
-		<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
-		<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
-
-		<!-- Row 1 -->
-		<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
-			<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton" cacheShape="true" cache="true" />
-			<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true" cacheShape="true" cache="true" />
-		</HBox>
-
-		<!-- Row 3 -->
-		<Text fx:id="messageText" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
-
-		<!-- Row 3 -->
-		<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
-			<padding>
-				<Insets top="24.0" />
-			</padding>
+	<!-- Password Field -->
+	<HBox spacing="12" alignment="BASELINE_LEFT">
+		<Label text="%unlock.label.password" HBox.hgrow="NEVER"/>
+		<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" maxWidth="Infinity" HBox.hgrow="ALWAYS"/>
+	</HBox>
 
-			<columnConstraints>
-				<ColumnConstraints percentWidth="38.2"/>
-				<ColumnConstraints percentWidth="61.8"/>
-			</columnConstraints>
+	<!-- Unlock Button / Advanced Options Button -->
+	<HBox spacing="12.0" alignment="CENTER_RIGHT">
+		<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton"/>
+		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true"/>
+		<ProgressIndicator progress="-1" fx:id="progressIndicator"/>
+	</HBox>
 
-			<!-- Row 3.0 -->
-			<Separator GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true"/>
-			<HBox alignment="CENTER" prefWidth="400.0" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
-				<Label text="%unlock.label.advancedHeading" style="-fx-background-color: COLOR_BACKGROUND;" cacheShape="true" cache="true">
-					<padding>
-						<Insets left="6.0" right="6.0"/>
-					</padding>
-				</Label>
-			</HBox>
+	<!-- Advanced Options -->
+	<VBox fx:id="advancedOptions" spacing="6" VBox.vgrow="ALWAYS" visible="false">
 
-			<!-- Row 3.1 -->
-			<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
+		<Separator/>
 
-			<!-- Row 3.2 -->
-			<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
-
-			<!-- Row 3.3 -->
-			<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountName"  cacheShape="true" cache="true" />
-			<TextField GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
+		<!-- Mount Name -->
+		<HBox spacing="12" alignment="BASELINE_LEFT">
+			<Label text="%unlock.label.mountName"/>
+			<TextField fx:id="mountName" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
+		</HBox>
 
-			<!-- Row 3.4 -->
-			<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
+		<!-- Save Password -->
+		<CheckBox fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox"/>
 
-			<!-- Row 3.5 -->
-			<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode" cacheShape="true" cache="true" />
+		<!-- Auto Unlock -->
+		<CheckBox fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup"/>
 
-			<!-- Row 3.6 -->
-			<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
+		<!-- Reveal Drive -->
+		<CheckBox fx:id="revealAfterMount" text="%unlock.label.revealAfterMount"/>
 
-			<!-- Row 3.7 Alt1 -->
-			<Label GridPane.rowIndex="7" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
-			<ChoiceBox GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
+		<!-- Read-Only -->
+		<CheckBox fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode"/>
 
-			<!-- Row 3.7 Alt2 -->
-			<HBox fx:id="customMountPoint" GridPane.rowIndex="7" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="6" alignment="BASELINE_LEFT" cacheShape="true" cache="true">
-				<padding>
-					<Insets left="20.0" />
-				</padding>
-				<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS" cacheShape="true" cache="true" />
-				<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="&#xf434;" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true" cacheShape="true" cache="true" />
-			</HBox>
-		</GridPane>
+		<!-- Custom Mount Point -->
+		<CheckBox fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath"/>
+		<HBox fx:id="customMountPoint" spacing="6" alignment="BASELINE_LEFT">
+			<padding>
+				<Insets left="20.0"/>
+			</padding>
+			<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS"/>
+			<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="&#xf434;" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true"/>
+		</HBox>
 
-		<!-- Row 4 -->
-		<TextFlow GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
-			<GridPane.margin>
-				<Insets top="24.0"/>
-			</GridPane.margin>
-			<children>
-				<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
-			</children>
-		</TextFlow>
+		<!-- Mount Flags -->
+		<HBox spacing="12" alignment="BASELINE_LEFT">
+			<CheckBox fx:id="useCustomMountFlags" text="%unlock.label.useCustomMountFlags"/>
+			<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
+		</HBox>
 
-		<!-- Row 5 -->
-		<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
-			<ProgressIndicator progress="-1" fx:id="progressIndicator" cacheShape="true" cache="true" cacheHint="SPEED" />
-			<Text fx:id="progressText" cache="true" />
-		</VBox>
-	</children>
-</GridPane>
+		<!-- Windows Drive Letter -->
+		<HBox spacing="12">
+			<Label fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter"/>
+			<ChoiceBox fx:id="winDriveLetter" maxWidth="Infinity"/>
+		</HBox>
+	</VBox>
+
+	<!-- Status Text -->
+	<TextFlow>
+		<children>
+			<Text fx:id="messageText" visible="true"/>
+			<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink"/>
+		</children>
+	</TextFlow>
+</VBox>

+ 2 - 3
main/ui/src/main/resources/localization/en.txt

@@ -74,14 +74,14 @@ upgrade.version5toX.msg=This vault needs to be migrated to a newer format.\nPlea
 unlock.label.password=Password
 unlock.label.savePassword=Save Password
 unlock.label.mountName=Drive Name
+unlock.label.useCustomMountFlags=Custom Mount Flags
 unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
 unlock.label.revealAfterMount=Reveal Drive
 unlock.label.useReadOnlyMode=Read-Only
 unlock.label.winDriveLetter=Drive Letter
-unlock.label.useOwnMountPath=Use Custom Mount Point
+unlock.label.useOwnMountPath=Custom Mount Point
 unlock.label.chooseMountPath=Choose empty directory…
 unlock.label.downloadsPageLink=All Cryptomator versions
-unlock.label.advancedHeading=Advanced Options
 unlock.button.unlock=Unlock Vault
 unlock.button.advancedOptions.show=More Options
 unlock.button.advancedOptions.hide=Less Options
@@ -89,7 +89,6 @@ unlock.savePassword.delete.confirmation.title=Delete Saved Password
 unlock.savePassword.delete.confirmation.header=Do you really want to delete the saved password of this vault?
 unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
 unlock.choicebox.winDriveLetter.auto=Assign automatically
-unlock.pendingMessage.unlocking=Unlocking vault...
 unlock.errorMessage.wrongPassword=Wrong password
 unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
 unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.