mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-18 22:14:26 +00:00
Implemented #40, tested on macOS
This commit is contained in:
@@ -26,10 +26,10 @@ import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.ui.ExitUtil;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.AutoUnlocker;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
@@ -50,7 +50,9 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
@@ -83,11 +85,11 @@ public class MainController implements ViewController {
|
||||
private final Localization localization;
|
||||
private final ExecutorService executorService;
|
||||
private final BlockingQueue<Path> fileOpenRequests;
|
||||
private final Settings settings;
|
||||
private final VaultFactory vaultFactoy;
|
||||
private final ViewControllerLoader viewControllerLoader;
|
||||
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final VaultList vaults;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final BooleanBinding areAllVaultsLocked;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final BooleanExpression isSelectedVaultUnlocked = BooleanExpression.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
|
||||
private final BooleanExpression isSelectedVaultValid = BooleanExpression.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
|
||||
@@ -100,13 +102,12 @@ public class MainController implements ViewController {
|
||||
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
|
||||
Settings settings, VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults) {
|
||||
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.executorService = executorService;
|
||||
this.fileOpenRequests = fileOpenRequests;
|
||||
this.exitUtil = exitUtil;
|
||||
this.localization = localization;
|
||||
this.settings = settings;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
this.viewControllerLoader = viewControllerLoader;
|
||||
this.vaults = vaults;
|
||||
@@ -114,6 +115,10 @@ public class MainController implements ViewController {
|
||||
// derived bindings:
|
||||
this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass));
|
||||
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
|
||||
this.areAllVaultsLocked = new SimpleBooleanProperty(false).not(); // = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault::isUnlocked));
|
||||
|
||||
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
|
||||
autoUnlocker.unlockAllSilently();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -281,7 +286,7 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId(settings);
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path().set(vaultPath);
|
||||
return vaultFactoy.get(vaultSettings);
|
||||
});
|
||||
@@ -399,7 +404,6 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
public void didUnlock(Vault vault) {
|
||||
Platform.setImplicitExit(false);
|
||||
if (vault.equals(selectedVault.getValue())) {
|
||||
this.showUnlockedView(vault);
|
||||
}
|
||||
@@ -417,9 +421,6 @@ public class MainController implements ViewController {
|
||||
public void didLock(UnlockedController ctrl) {
|
||||
unlockedVaults.remove(ctrl.getVault());
|
||||
showUnlockView();
|
||||
if (!vaults.stream().anyMatch(Vault::isUnlocked)) {
|
||||
Platform.setImplicitExit(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void showChangePasswordView() {
|
||||
|
||||
@@ -17,6 +17,7 @@ import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
||||
@@ -28,6 +29,8 @@ import org.cryptomator.ui.model.WindowsDriveLetters;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.AsyncTaskService;
|
||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -72,6 +75,7 @@ public class UnlockController implements ViewController {
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private Vault vault;
|
||||
private Optional<UnlockListener> listener = Optional.empty();
|
||||
private Subscription vaultSubs = Subscription.EMPTY;
|
||||
|
||||
@Inject
|
||||
public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess) {
|
||||
@@ -124,6 +128,9 @@ public class UnlockController implements ViewController {
|
||||
@FXML
|
||||
private GridPane root;
|
||||
|
||||
@FXML
|
||||
private CheckBox unlockAfterStartup;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||
@@ -133,6 +140,7 @@ public class UnlockController implements ViewController {
|
||||
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
||||
mountName.textProperty().addListener(this::mountNameDidChange);
|
||||
savePassword.setDisable(!keychainAccess.isPresent());
|
||||
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||
} else {
|
||||
@@ -149,18 +157,17 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
|
||||
void setVault(Vault vault) {
|
||||
// TODO overheadhunter refactor
|
||||
if (this.vault != null) {
|
||||
this.vault.getVaultSettings().mountAfterUnlock().unbind();
|
||||
this.vault.getVaultSettings().revealAfterMount().unbind();
|
||||
}
|
||||
vaultSubs.unsubscribe();
|
||||
vaultSubs = Subscription.EMPTY;
|
||||
|
||||
// trigger "default" change to refresh key bindings:
|
||||
unlockButton.setDefaultButton(false);
|
||||
unlockButton.setDefaultButton(true);
|
||||
if (vault.equals(this.vault)) {
|
||||
if (Objects.equals(this.vault, Objects.requireNonNull(vault))) {
|
||||
return;
|
||||
}
|
||||
this.vault = Objects.requireNonNull(vault);
|
||||
assert vault != null;
|
||||
this.vault = vault;
|
||||
passwordField.swipe();
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
@@ -190,10 +197,18 @@ public class UnlockController implements ViewController {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
mountAfterUnlock.setSelected(this.vault.getVaultSettings().mountAfterUnlock().get());
|
||||
revealAfterMount.setSelected(this.vault.getVaultSettings().revealAfterMount().get());
|
||||
this.vault.getVaultSettings().mountAfterUnlock().bind(mountAfterUnlock.selectedProperty());
|
||||
this.vault.getVaultSettings().revealAfterMount().bind(revealAfterMount.selectedProperty());
|
||||
VaultSettings settings = vault.getVaultSettings();
|
||||
unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get());
|
||||
mountAfterUnlock.setSelected(settings.mountAfterUnlock().get());
|
||||
revealAfterMount.setSelected(settings.revealAfterMount().get());
|
||||
|
||||
// settings.unlockAfterStartup().bind(unlockAfterStartup.selectedProperty());
|
||||
// settings.mountAfterUnlock().bind(mountAfterUnlock.selectedProperty());
|
||||
// settings.revealAfterMount().bind(revealAfterMount.selectedProperty());
|
||||
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set));
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set));
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set));
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class AutoUnlocker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
|
||||
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final VaultList vaults;
|
||||
private final ExecutorService executor;
|
||||
|
||||
@Inject
|
||||
public AutoUnlocker(Optional<KeychainAccess> keychainAccess, VaultList vaults, ExecutorService executor) {
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.vaults = vaults;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void unlockAllSilently() {
|
||||
if (keychainAccess.isPresent()) {
|
||||
vaults.stream().filter(this::shouldUnlockAfterStartup).map(this::createUnlockTask).forEach(executor::submit);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUnlockAfterStartup(Vault vault) {
|
||||
return vault.getVaultSettings().unlockAfterStartup().get();
|
||||
}
|
||||
|
||||
private Runnable createUnlockTask(Vault vault) {
|
||||
return () -> unlockSilently(vault);
|
||||
}
|
||||
|
||||
private void unlockSilently(Vault vault) {
|
||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||
if (storedPw == null) {
|
||||
LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath());
|
||||
}
|
||||
try {
|
||||
vault.unlock(CharBuffer.wrap(storedPw));
|
||||
mountSilently(vault);
|
||||
} catch (CryptoException e) {
|
||||
LOG.error("Auto unlock failed.", e);
|
||||
} finally {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void mountSilently(Vault unlockedVault) {
|
||||
if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
unlockedVault.mount();
|
||||
revealSilently(unlockedVault);
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.error("Auto unlock succeded, but mounting the drive failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void revealSilently(Vault mountedVault) {
|
||||
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mountedVault.reveal();
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@@ -77,7 +78,7 @@ public class Vault {
|
||||
// Commands
|
||||
// ********************************************************************************/
|
||||
|
||||
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException {
|
||||
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
|
||||
return LazyInitializer.initializeLazily(cryptoFileSystem, () -> createCryptoFileSystem(passphrase), IOException.class);
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void mount() {
|
||||
public synchronized void mount() throws CommandFailedException {
|
||||
if (servlet == null) {
|
||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
||||
}
|
||||
@@ -136,14 +137,10 @@ public class Vault {
|
||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) //
|
||||
.build();
|
||||
|
||||
try {
|
||||
mount = servlet.mount(mountParams);
|
||||
Platform.runLater(() -> {
|
||||
mounted.set(true);
|
||||
});
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.error("Unable to mount filesystem", e);
|
||||
}
|
||||
mount = servlet.mount(mountParams);
|
||||
Platform.runLater(() -> {
|
||||
mounted.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void unmount() throws Exception {
|
||||
@@ -190,6 +187,10 @@ public class Vault {
|
||||
// Getter/Setter
|
||||
// *******************************************************************************/
|
||||
|
||||
public Observable[] observables() {
|
||||
return new Observable[] {unlockedProperty(), mountedProperty()};
|
||||
}
|
||||
|
||||
public VaultSettings getVaultSettings() {
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.ui.model.VaultComponent.Builder;
|
||||
|
||||
@Singleton
|
||||
public class VaultFactory {
|
||||
|
||||
private final Builder vaultComponentBuilder;
|
||||
private final VaultComponent.Builder vaultComponentBuilder;
|
||||
private final ConcurrentMap<VaultSettings, Vault> vaults = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -14,7 +14,9 @@ import javax.inject.Singleton;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.TransformationList;
|
||||
|
||||
@@ -59,15 +61,15 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sourceChanged(Change<? extends VaultSettings> c) {
|
||||
protected void sourceChanged(ListChangeListener.Change<? extends VaultSettings> c) {
|
||||
this.fireChange(new VaultListChange(c));
|
||||
}
|
||||
|
||||
private class VaultListChange extends Change<Vault> {
|
||||
private class VaultListChange extends ListChangeListener.Change<Vault> {
|
||||
|
||||
private final Change<? extends VaultSettings> delegate;
|
||||
private final ListChangeListener.Change<? extends VaultSettings> delegate;
|
||||
|
||||
public VaultListChange(Change<? extends VaultSettings> delegate) {
|
||||
public VaultListChange(ListChangeListener.Change<? extends VaultSettings> delegate) {
|
||||
super(VaultList.this);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
@@ -77,6 +79,11 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
|
||||
return delegate.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasUpdated() {
|
||||
return delegate.wasUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
@@ -94,27 +101,28 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
|
||||
|
||||
@Override
|
||||
public List<Vault> getRemoved() {
|
||||
List<Vault> removed = new ArrayList<>();
|
||||
for (VaultSettings s : delegate.getRemoved()) {
|
||||
removed.add(vaultFactory.get(s));
|
||||
}
|
||||
return removed;
|
||||
return Lists.transform(delegate.getRemoved(), vaultFactory::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasPermutated() {
|
||||
return delegate.wasPermutated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getPermutation() {
|
||||
if (delegate.wasPermutated()) {
|
||||
int len = getTo() - getFrom();
|
||||
int[] permutations = new int[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
permutations[i] = getPermutation(i);
|
||||
}
|
||||
return permutations;
|
||||
return IntStream.range(getFrom(), getTo()).map(delegate::getPermutation).toArray();
|
||||
} else {
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<ChoiceBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 4 -->
|
||||
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="debugModeLabel" text="%settings.debugMode.label" cacheShape="true" cache="true" />
|
||||
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
|
||||
|
||||
</children>
|
||||
|
||||
@@ -72,20 +72,24 @@
|
||||
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3.2 -->
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="mountAfterUnlock" cacheShape="true" cache="true" />
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="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" />
|
||||
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountAfterUnlock" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3.4 -->
|
||||
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="revealAfterMount" cacheShape="true" cache="true" />
|
||||
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
|
||||
<TextField GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3.5 -->
|
||||
<Label GridPane.rowIndex="5" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
||||
<ChoiceBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
<Label GridPane.rowIndex="5" GridPane.columnIndex="0" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="revealAfterMount" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3.6 -->
|
||||
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
||||
<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
</GridPane>
|
||||
|
||||
<!-- Row 4 -->
|
||||
|
||||
@@ -59,6 +59,7 @@ unlock.label.password=Password
|
||||
unlock.label.savePassword=Save Password
|
||||
unlock.label.mountAfterUnlock=Mount Drive
|
||||
unlock.label.mountName=Drive Name
|
||||
unlock.label.unlockAfterStartup=Unlock On Start
|
||||
unlock.label.revealAfterMount=Reveal Drive
|
||||
unlock.label.winDriveLetter=Drive Letter
|
||||
unlock.label.downloadsPageLink=All Cryptomator versions
|
||||
|
||||
Reference in New Issue
Block a user