Merge branch 'release/1.5.0-beta3'

This commit is contained in:
Sebastian Stenzel
2020-02-27 10:02:37 +01:00
111 changed files with 1924 additions and 715 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: [overheadhunter, tobihagemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [cryptomator] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

12
.idea/compiler.xml generated
View File

@@ -7,20 +7,20 @@
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.25.2/dagger-compiler-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.25.2/dagger-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.26/dagger-compiler-2.26.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.26/dagger-2.26.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.25.2/dagger-producers-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.26/dagger-producers-2.26.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.25.2/dagger-spi-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.26/dagger-spi-2.26.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
@@ -32,9 +32,9 @@
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
</processorPath>
<module name="keychain" />
<module name="launcher" />
<module name="commons" />
<module name="ui" />
<module name="launcher" />
</profile>
</annotationProcessing>
</component>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
</parent>
<artifactId>buildkit</artifactId>
<packaging>pom</packaging>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>

View File

@@ -5,7 +5,6 @@
*******************************************************************************/
package org.cryptomator.common;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import javafx.beans.binding.Binding;
@@ -19,6 +18,8 @@ import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -27,12 +28,18 @@ import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Module(subcomponents = {VaultComponent.class})
public abstract class CommonsModule {
private static final int NUM_SCHEDULER_THREADS = 4;
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
private static final int NUM_SCHEDULER_THREADS = 2;
private static final int NUM_CORE_BG_THREADS = 6;
private static final long BG_THREAD_KEEPALIVE_SECONDS = 60l;
@Provides
@Singleton
@@ -69,18 +76,38 @@ public abstract class CommonsModule {
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
String name = String.format("App Scheduled Executor %02d", threadNumber.getAndIncrement());
Thread t = new Thread(r);
t.setName("Background Thread " + threadNumber.getAndIncrement());
t.setName(name);
t.setUncaughtExceptionHandler(CommonsModule::handleUncaughtExceptionInBackgroundThread);
t.setDaemon(true);
LOG.debug("Starting {}", t.getName());
return t;
});
shutdownHook.runOnShutdown(executorService::shutdown);
return executorService;
}
@Binds
@Provides
@Singleton
abstract ExecutorService bindExecutorService(ScheduledExecutorService executor);
static ExecutorService provideExecutorService(ShutdownHook shutdownHook) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ExecutorService executorService = new ThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
String name = String.format("App Background Thread %03d", threadNumber.getAndIncrement());
Thread t = new Thread(r);
t.setName(name);
t.setUncaughtExceptionHandler(CommonsModule::handleUncaughtExceptionInBackgroundThread);
t.setDaemon(true);
LOG.debug("Starting {}", t.getName());
return t;
});
shutdownHook.runOnShutdown(executorService::shutdown);
return executorService;
}
private static void handleUncaughtExceptionInBackgroundThread(Thread thread, Throwable throwable) {
LOG.error("Uncaught exception in " + thread.getName(), throwable);
}
@Provides
@Singleton

View File

@@ -32,7 +32,6 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -46,16 +45,17 @@ public class SettingsProvider implements Supplier<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final long SAVE_DELAY_MS = 1000;
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final AtomicReference<Settings> settings = new AtomicReference<>();
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
private final Environment env;
private final ScheduledExecutorService scheduler;
private final Gson gson;
@Inject
public SettingsProvider(Environment env) {
public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
this.env = env;
this.scheduler = scheduler;
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
@@ -98,7 +98,7 @@ public class SettingsProvider implements Supplier<Settings> {
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
settingsPath.ifPresent(path -> {
Runnable saveCommand = () -> this.save(settings, path);
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
ScheduledFuture<?> scheduledTask = scheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
if (previouslyScheduledTask != null) {
previouslyScheduledTask.cancel(false);

View File

@@ -41,7 +41,6 @@ public final class WindowsDriveLetters {
public Set<String> getOccupiedDriveLetters() {
if (!SystemUtils.IS_OS_WINDOWS) {
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
return Set.of();
} else {
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
</parent>
<artifactId>keychain</artifactId>
<name>System Keychain Access</name>

View File

@@ -11,6 +11,7 @@ import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import org.cryptomator.common.JniModule;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.Set;
@@ -24,6 +25,7 @@ public class KeychainModule {
}
@Provides
@Singleton
public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>

View File

@@ -21,8 +21,10 @@ import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Singleton
@@ -40,7 +42,7 @@ class FileOpenRequestHandler {
}
private void openFiles(OpenFilesEvent evt) {
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
@@ -51,16 +53,18 @@ class FileOpenRequestHandler {
// visible for testing
void handleLaunchArgs(FileSystem fs, String[] args) {
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
try {
return fs.getPath(str);
} catch (InvalidPathException e) {
LOG.trace("Argument not a valid path: {}", str);
return null;
}
}).filter(Objects::nonNull);
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}).filter(Objects::nonNull).collect(Collectors.toList());
if (!pathsToOpen.isEmpty()) {
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
}

View File

@@ -8,6 +8,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Stream;
@@ -27,7 +28,7 @@ class IpcProtocolImpl implements IpcProtocol {
@Override
public void revealRunningApp() {
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Stream.empty()));
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Collections.emptyList()));
}
@Override

View File

@@ -55,10 +55,11 @@ public class LoggerConfiguration {
}
// configure upgrade logger:
Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
Logger upgrades = context.getLogger("org.cryptomator.cryptofs.migration");
upgrades.setLevel(Level.DEBUG);
upgrades.addAppender(stdout);
upgrades.addAppender(upgrade);
upgrades.addAppender(file);
upgrades.setAdditive(false);
// add shutdown hook

View File

@@ -15,16 +15,14 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileOpenRequestHandlerTest {
@@ -39,32 +37,30 @@ public class FileOpenRequestHandlerTest {
@Test
@DisplayName("./cryptomator.exe foo bar")
public void testOpenArgsWithCorrectPaths() throws IOException {
public void testOpenArgsWithCorrectPaths() {
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
Collection<Path> paths = evt.getPathsToOpen();
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
}
@Test
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
public void testOpenArgsWithIncorrectPaths() throws IOException {
public void testOpenArgsWithIncorrectPaths() {
FileSystem fs = Mockito.mock(FileSystem.class);
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
inTest.handleLaunchArgs(fs, new String[]{"foo"});
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
Assertions.assertTrue(paths.isEmpty());
Assertions.assertNull(evt);
}
@Test
@DisplayName("./cryptomator.exe foo (with full event queue)")
public void testOpenArgsWithFullQueue() throws IOException {
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Stream.empty()));
public void testOpenArgsWithFullQueue() {
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
inTest.handleLaunchArgs(new String[]{"foo"});

View File

@@ -3,13 +3,13 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
<organization>
<name>cryptomator.org</name>
<url>http://cryptomator.org</url>
<url>https://cryptomator.org</url>
</organization>
<developers>
@@ -24,33 +24,33 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>1.9.3</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
<cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
<cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<javafx.version>13.0.1</javafx.version>
<javafx.version>14-ea+8</javafx.version>
<commons-lang3.version>3.9</commons-lang3.version>
<jwt.version>3.8.3</jwt.version>
<easybind.version>1.0.3</easybind.version>
<guava.version>28.1-jre</guava.version>
<dagger.version>2.25.2</dagger.version>
<dagger.version>2.26</dagger.version>
<gson.version>2.8.6</gson.version>
<slf4j.version>1.7.29</slf4j.version>
<logback.version>1.2.3</logback.version>
<!-- test dependencies -->
<junit.jupiter.version>5.5.2</junit.jupiter.version>
<mockito.version>3.1.0</mockito.version>
<junit.jupiter.version>5.6.0</junit.jupiter.version>
<mockito.version>3.2.4</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
</properties>
<repositories>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.0-beta2</version>
<version>1.5.0-beta3</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>

View File

@@ -27,8 +27,8 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named;
import javax.inject.Provider;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -51,13 +51,13 @@ public abstract class AddVaultModule {
@Provides
@AddVaultWizardWindow
@AddVaultWizardScoped
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}
@@ -193,8 +193,8 @@ public abstract class AddVaultModule {
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyDisplayController.class)
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey) {
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get());
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey, ResourceBundle localization) {
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get(), localization);
}
@Binds

View File

@@ -10,7 +10,7 @@ public class ReadmeGenerator {
// specs: https://web.archive.org/web/20190708132914/http://www.kleinlercher.at/tools/Windows_Protocols/Word2007RTFSpec9.pdf
private static final String RTF_HEADER = "{\\rtf1\\fbidis\\ansi\\uc0\\fs32\n";
private static final String RTF_FOOTER = "}";
private static final String HELP_URL = "{\\field{\\*\\fldinst HYPERLINK \"http://www.google.com/\"}{\\fldrslt google.com}}";
private static final String HELP_URL = "{\\field{\\*\\fldinst HYPERLINK \"http://docs.cryptoamtor.org/\"}{\\fldrslt docs.cryptoamtor.org}}";
private final ResourceBundle resourceBundle;

View File

@@ -22,8 +22,8 @@ import org.cryptomator.ui.common.PasswordStrengthUtil;
import javax.inject.Named;
import javax.inject.Provider;
import java.nio.CharBuffer;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -46,13 +46,13 @@ abstract class ChangePasswordModule {
@Provides
@ChangePasswordWindow
@ChangePasswordScoped
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("changepassword.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -0,0 +1,36 @@
package org.cryptomator.ui.common;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.value.WritableValue;
import javafx.stage.Window;
import javafx.util.Duration;
public class Animations {
public static Timeline createShakeWindowAnimation(Window window) {
WritableValue<Double> writableWindowX = new WritableValue<>() {
@Override
public Double getValue() {
return window.getX();
}
@Override
public void setValue(Double value) {
window.setX(value);
}
};
return new Timeline( //
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
);
}
}

View File

@@ -12,12 +12,16 @@ public enum FxmlFile {
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
MAIN_WINDOW("/fxml/main_window.fxml"), //
MIGRATION_CAPABILITY_ERROR("/fxml/migration_capability_error.fxml"), //
MIGRATION_GENERIC_ERROR("/fxml/migration_generic_error.fxml"), //
MIGRATION_RUN("/fxml/migration_run.fxml"), //
MIGRATION_START("/fxml/migration_start.fxml"), //
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK("/fxml/unlock.fxml"),

View File

@@ -8,11 +8,11 @@ public class StackTraceController implements FxController {
private final String stackTrace;
public StackTraceController(Exception cause) {
public StackTraceController(Throwable cause) {
this.stackTrace = provideStackTrace(cause);
}
static String provideStackTrace(Exception cause) {
private static String provideStackTrace(Throwable cause) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cause.printStackTrace(new PrintStream(baos));
return baos.toString(StandardCharsets.UTF_8);

View File

@@ -73,21 +73,21 @@ public class Tasks {
return new TaskImpl<>(callable, successHandler, errorHandlers, finallyHandler);
}
public Task<T> runOnce(ExecutorService executorService) {
public Task<T> runOnce(ExecutorService executor) {
Task<T> task = build();
executorService.submit(task);
executor.submit(task);
return task;
}
public Task<T> scheduleOnce(ScheduledExecutorService executorService, long delay, TimeUnit unit) {
public Task<T> scheduleOnce(ScheduledExecutorService scheduler, long delay, TimeUnit unit) {
Task<T> task = build();
executorService.schedule(task, delay, unit);
scheduler.schedule(task, delay, unit);
return task;
}
public ScheduledService<T> schedulePeriodically(ExecutorService executorService, Duration initialDelay, Duration period) {
public ScheduledService<T> schedulePeriodically(ExecutorService executor, Duration initialDelay, Duration period) {
ScheduledService<T> service = new RestartingService<>(this::build);
service.setExecutor(executorService);
service.setExecutor(executor);
service.setDelay(initialDelay);
service.setPeriod(period);
Platform.runLater(service::start);

View File

@@ -4,15 +4,20 @@ import javafx.concurrent.Task;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
@@ -23,10 +28,12 @@ public class VaultService {
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
private final ExecutorService executorService;
private final Optional<KeychainAccess> keychain;
@Inject
public VaultService(ExecutorService executorService) {
public VaultService(ExecutorService executorService, Optional<KeychainAccess> keychain) {
this.executorService = executorService;
this.keychain = keychain;
}
public void reveal(Vault vault) {
@@ -45,6 +52,72 @@ public class VaultService {
return task;
}
/**
* Attempts to unlock all given vaults in a background thread using passwords stored in the system keychain.
*
* @param vaults The vaults to unlock
* @implNote No-op if no system keychain is present
*/
public void attemptAutoUnlock(Collection<Vault> vaults) {
if (!keychain.isPresent()) {
LOG.debug("No system keychain found. Unable to auto unlock without saved passwords.");
} else {
for (Vault vault : vaults) {
attemptAutoUnlock(vault, keychain.get());
}
}
}
/**
* Unlocks a vault in a background thread using a stored passphrase
*
* @param vault The vault to unlock
* @param keychainAccess The system keychain holding the passphrase for the vault
*/
public void attemptAutoUnlock(Vault vault, KeychainAccess keychainAccess) {
executorService.execute(createAutoUnlockTask(vault, keychainAccess));
}
/**
* Creates but doesn't start an auto-unlock task.
*
* @param vault The vault to unlock
* @param keychainAccess The system keychain holding the passphrase for the vault
* @return The task
*/
public Task<Vault> createAutoUnlockTask(Vault vault, KeychainAccess keychainAccess) {
Task<Vault> task = new AutoUnlockVaultTask(vault, keychainAccess);
task.setOnSucceeded(evt -> LOG.info("Auto-unlocked {}", vault.getDisplayableName()));
task.setOnFailed(evt -> LOG.error("Failed to auto-unlock " + vault.getDisplayableName(), evt.getSource().getException()));
return task;
}
/**
* Unlocks a vault in a background thread
*
* @param vault The vault to unlock
* @param passphrase The password to use - wipe this param asap
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
*/
public void unlock(Vault vault, CharSequence passphrase) {
executorService.execute(createUnlockTask(vault, passphrase));
}
/**
* Creates but doesn't start an unlock task.
*
* @param vault The vault to unlock
* @param passphrase The password to use - wipe this param asap
* @return The task
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
*/
public Task<Vault> createUnlockTask(Vault vault, CharSequence passphrase) {
Task<Vault> task = new UnlockVaultTask(vault, passphrase);
task.setOnSucceeded(evt -> LOG.info("Unlocked {}", vault.getDisplayableName()));
task.setOnFailed(evt -> LOG.error("Failed to unlock " + vault.getDisplayableName(), evt.getSource().getException()));
return task;
}
/**
* Locks a vault in a background thread.
*
@@ -60,6 +133,7 @@ public class VaultService {
*
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
* @return The task
*/
public Task<Vault> createLockTask(Vault vault, boolean forced) {
Task<Vault> task = new LockVaultTask(vault, forced);
@@ -145,6 +219,93 @@ public class VaultService {
}
}
private static class AutoUnlockVaultTask extends Task<Vault> {
private final Vault vault;
private final KeychainAccess keychain;
public AutoUnlockVaultTask(Vault vault, KeychainAccess keychain) {
this.vault = vault;
this.keychain = keychain;
}
@Override
protected Vault call() throws Exception {
char[] storedPw = null;
try {
storedPw = keychain.loadPassphrase(vault.getId());
if (storedPw == null) {
throw new InvalidPassphraseException();
}
vault.unlock(CharBuffer.wrap(storedPw));
} finally {
if (storedPw != null) {
Arrays.fill(storedPw, ' ');
}
}
return vault;
}
@Override
protected void scheduled() {
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
vault.setState(VaultState.UNLOCKED);
}
@Override
protected void failed() {
vault.setState(VaultState.LOCKED);
}
}
private static class UnlockVaultTask extends Task<Vault> {
private final Vault vault;
private final CharBuffer passphrase;
/**
* @param vault The vault to unlock
* @param passphrase The password to use - wipe this param asap
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
*/
public UnlockVaultTask(Vault vault, CharSequence passphrase) {
this.vault = vault;
this.passphrase = CharBuffer.allocate(passphrase.length());
for (int i = 0; i < passphrase.length(); i++) {
this.passphrase.put(i, passphrase.charAt(i));
}
}
@Override
protected Vault call() throws Exception {
try {
vault.unlock(passphrase);
} finally {
Arrays.fill(passphrase.array(), ' ');
}
return vault;
}
@Override
protected void scheduled() {
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
vault.setState(VaultState.UNLOCKED);
}
@Override
protected void failed() {
vault.setState(VaultState.LOCKED);
}
}
/**
* A task that locks a vault
*/
@@ -186,5 +347,4 @@ public class VaultService {
}
}

View File

@@ -5,12 +5,13 @@ package org.cryptomator.ui.controls;
*/
public enum FontAwesome5Icon {
ANCHOR("\uF13D"), //
ARROW_ALT_UP("\uF357"), //
ARROW_UP("\uF062"), //
CHECK("\uF00C"), //
COG("\uF013"), //
COGS("\uF085"), //
COPY("\uF0C5"), //
EXCLAMATION("\uF12A"),
CROWN("\uF521"), //
EXCLAMATION("\uF12A"), //
EXCLAMATION_CIRCLE("\uF06A"), //
EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), //
@@ -22,17 +23,17 @@ public enum FontAwesome5Icon {
HDD("\uF0A0"), //
KEY("\uF084"), //
LINK("\uF0C1"), //
LOCK_ALT("\uF30D"), //
LOCK_OPEN_ALT("\uF3C2"), //
LOCK("\uF023"), //
LOCK_OPEN("\uF3C1"), //
MAGIC("\uF0D0"), //
PLUS("\uF067"), //
PRINT("\uF02F"), //
QUESTION("\uF128"), //
SPARKLES("\uF890"), //
SPINNER("\uF110"), //
SYNC("\uF021"), //
TIMES("\uF00D"), //
USER_CROWN("\uF6A4"), //
WRENCH("\uF0AD"), //
WINDOW_MINIMIZE("\uF2D1"), //
;
private final String unicode;

View File

@@ -18,7 +18,7 @@ public class FontAwesome5IconView extends Text {
private static final FontAwesome5Icon DEFAULT_GLYPH = FontAwesome5Icon.ANCHOR;
private static final double DEFAULT_GLYPH_SIZE = 12.0;
private static final String FONT_PATH = "/css/fontawesome5-pro-solid.otf";
private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf";
private static final Font FONT;
private ObjectProperty<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);

View File

@@ -32,7 +32,7 @@ public class NiceSecurePasswordField extends StackPane {
iconContainer.getStyleClass().add(ICONS_STLYE_CLASS);
StackPane.setAlignment(iconContainer, Pos.CENTER_RIGHT);
capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_ALT_UP);
capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_UP);
capsLockedIcon.setGlyphSize(ICON_SIZE);
capsLockedIcon.visibleProperty().bind(passwordField.capsLockedProperty());
capsLockedIcon.managedProperty().bind(passwordField.capsLockedProperty());

View File

@@ -20,8 +20,8 @@ import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -37,13 +37,13 @@ abstract class ForgetPasswordModule {
@Provides
@ForgetPasswordWindow
@ForgetPasswordScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("forgetPasswordOwner") Stage owner) {
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("forgetPasswordOwner") Stage owner) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("forgetPassword.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -22,7 +22,9 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import javax.inject.Named;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, QuitComponent.class})
abstract class FxApplicationModule {
@@ -34,19 +36,29 @@ abstract class FxApplicationModule {
}
@Provides
@Named("windowIcon")
@Named("windowIcons")
@FxApplicationScoped
static Optional<Image> provideWindowIcon() {
static List<Image> provideWindowIcons() {
if (SystemUtils.IS_OS_MAC) {
return Optional.empty();
return Collections.emptyList();
}
try (InputStream in = FxApplicationModule.class.getResourceAsStream("/window_icon_32.png")) { // TODO: use some higher res depending on display?
return Optional.of(new Image(in));
try {
return List.of( //
createImageFromResource("/window_icon_32.png"), //
createImageFromResource("/window_icon_512.png") //
);
} catch (IOException e) {
return Optional.empty();
throw new UncheckedIOException("Failed to load embedded resource.", e);
}
}
private static Image createImageFromResource(String resourceName) throws IOException {
try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) {
return new Image(in);
}
}
@Binds
abstract Application bindApplication(FxApplication application);

View File

@@ -1,16 +1,17 @@
package org.cryptomator.ui.launcher;
import java.nio.file.Path;
import java.util.Collection;
import java.util.stream.Stream;
public class AppLaunchEvent {
private final Stream<Path> pathsToOpen;
private final EventType type;
private final Collection<Path> pathsToOpen;
public enum EventType {REVEAL_APP, OPEN_FILE}
public AppLaunchEvent(EventType type, Stream<Path> pathsToOpen) {
public AppLaunchEvent(EventType type, Collection<Path> pathsToOpen) {
this.type = type;
this.pathsToOpen = pathsToOpen;
}
@@ -19,7 +20,7 @@ public class AppLaunchEvent {
return type;
}
public Stream<Path> getPathsToOpen() {
public Collection<Path> getPathsToOpen() {
return pathsToOpen;
}
}

View File

@@ -0,0 +1,125 @@
package org.cryptomator.ui.launcher;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.desktop.QuitResponse;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Singleton
public class AppLifecycleListener {
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch;
private final ObservableList<Vault> vaults;
private final AtomicBoolean allowQuitWithoutPrompt;
@Inject
AppLifecycleListener(FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
this.fxApplicationStarter = fxApplicationStarter;
this.shutdownLatch = shutdownLatch;
this.vaults = vaults;
this.allowQuitWithoutPrompt = new AtomicBoolean(true);
vaults.addListener(this::vaultListChanged);
// register preferences shortcut
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
}
// register quit handler
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
}
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
}
/**
* Gracefully terminates the application.
*/
public void quit() {
handleQuitRequest(null, new QuitResponse() {
@Override
public void performQuit() {
System.exit(0);
}
@Override
public void cancelQuit() {
// no-op
}
});
}
private void handleQuitRequest(@SuppressWarnings("unused") EventObject e, QuitResponse response) {
QuitResponse decoratedQuitResponse = decorateQuitResponse(response);
if (allowQuitWithoutPrompt.get()) {
decoratedQuitResponse.performQuit();
} else {
fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(decoratedQuitResponse));
}
}
private QuitResponse decorateQuitResponse(QuitResponse originalQuitResponse) {
return new QuitResponse() {
@Override
public void performQuit() {
Platform.exit(); // will be no-op, if JavaFX never started.
shutdownLatch.countDown(); // main thread is waiting for this latch
EventQueue.invokeLater(originalQuitResponse::performQuit); // this will eventually call System.exit(0)
}
@Override
public void cancelQuit() {
originalQuitResponse.cancelQuit();
}
};
}
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
assert Platform.isFxApplicationThread();
boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
boolean suddenTerminationChanged = allowQuitWithoutPrompt.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
if (suddenTerminationChanged) {
LOG.debug("Allow quitting without prompt: {}", allVaultsAllowTermination);
}
}
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void forceUnmountRemainingVaults() {
for (Vault vault : vaults) {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
}
}
}
}
}

View File

@@ -1,6 +1,8 @@
package org.cryptomator.ui.launcher;
import javafx.collections.ObservableList;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
@@ -13,9 +15,8 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.awt.Desktop;
import java.awt.SystemTray;
import java.awt.desktop.AppReopenedEvent;
import java.awt.desktop.AppReopenedListener;
import java.awt.desktop.SystemEventListener;
import java.util.Collection;
import java.util.Optional;
@Singleton
@@ -24,14 +25,16 @@ public class UiLauncher {
private static final Logger LOG = LoggerFactory.getLogger(UiLauncher.class);
private final Settings settings;
private final ObservableList<Vault> vaults;
private final TrayMenuComponent.Builder trayComponent;
private final FxApplicationStarter fxApplicationStarter;
private final AppLaunchEventHandler launchEventHandler;
private final Optional<MacFunctions> macFunctions;
@Inject
public UiLauncher(Settings settings, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<MacFunctions> macFunctions) {
public UiLauncher(Settings settings, ObservableList<Vault> vaults, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional<MacFunctions> macFunctions) {
this.settings = settings;
this.vaults = vaults;
this.trayComponent = trayComponent;
this.fxApplicationStarter = fxApplicationStarter;
this.launchEventHandler = launchEventHandler;
@@ -48,7 +51,7 @@ public class UiLauncher {
}
// show window on start?
if (settings.startHidden().get()) {
if (hasTrayIcon && settings.startHidden().get()) {
LOG.debug("Hiding application...");
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToAgentApplication));
} else {
@@ -58,6 +61,12 @@ public class UiLauncher {
// register app reopen listener
Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon));
// auto unlock
Collection<Vault> vaultsWithAutoUnlockEnabled = vaults.filtered(v -> v.getVaultSettings().unlockAfterStartup().get());
if (!vaultsWithAutoUnlockEnabled.isEmpty()) {
fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> app.getVaultService().attemptAutoUnlock(vaultsWithAutoUnlockEnabled));
}
launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
}

View File

@@ -25,8 +25,8 @@ import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, WrongFileAlertComponent.class})
@@ -42,7 +42,7 @@ abstract class MainWindowModule {
@Provides
@MainWindow
@MainWindowScoped
static Stage provideStage(@Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage(StageStyle.UNDECORATED);
// TODO: min/max values chosen arbitrarily. We might wanna take a look at the user's resolution...
stage.setMinWidth(650);
@@ -50,7 +50,7 @@ abstract class MainWindowModule {
stage.setMaxWidth(1000);
stage.setMaxHeight(700);
stage.setTitle("Cryptomator");
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -5,12 +5,11 @@ import javafx.fxml.FXML;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.launcher.AppLifecycleListener;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,6 +23,7 @@ public class MainWindowTitleController implements FxController {
public HBox titleBar;
private final AppLifecycleListener appLifecycle;
private final Stage window;
private final FxApplication application;
private final boolean minimizeToSysTray;
@@ -35,7 +35,8 @@ public class MainWindowTitleController implements FxController {
private double yOffset;
@Inject
MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
MainWindowTitleController(AppLifecycleListener appLifecycle, @MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
this.appLifecycle = appLifecycle;
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
@@ -56,6 +57,10 @@ public class MainWindowTitleController implements FxController {
window.setX(event.getScreenX() - xOffset);
window.setY(event.getScreenY() - yOffset);
});
window.setOnCloseRequest(event -> {
close();
event.consume();
});
}
@FXML
@@ -63,10 +68,15 @@ public class MainWindowTitleController implements FxController {
if (minimizeToSysTray) {
window.close();
} else {
window.setIconified(true);
appLifecycle.quit();
}
}
@FXML
public void minimize() {
window.setIconified(true);
}
@FXML
public void showPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
@@ -91,5 +101,7 @@ public class MainWindowTitleController implements FxController {
return updateAvailable.get();
}
public boolean isMinimizeToSysTray() {
return minimizeToSysTray;
}
}

View File

@@ -33,11 +33,11 @@ public class VaultDetailController implements FxController {
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
switch (state) {
case LOCKED:
return FontAwesome5Icon.LOCK_ALT;
return FontAwesome5Icon.LOCK;
case PROCESSING:
return FontAwesome5Icon.SPINNER;
case UNLOCKED:
return FontAwesome5Icon.LOCK_OPEN_ALT;
return FontAwesome5Icon.LOCK_OPEN;
default:
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
}

View File

@@ -25,11 +25,11 @@ public class VaultListCellController implements FxController {
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
switch (state) {
case LOCKED:
return FontAwesome5Icon.LOCK_ALT;
return FontAwesome5Icon.LOCK;
case PROCESSING:
return FontAwesome5Icon.SPINNER;
case UNLOCKED:
return FontAwesome5Icon.LOCK_OPEN_ALT;
return FontAwesome5Icon.LOCK_OPEN;
default:
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
}

View File

@@ -0,0 +1,57 @@
package org.cryptomator.ui.migration;
import dagger.Lazy;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ResourceBundle;
@MigrationScoped
public class MigrationCapabilityErrorController implements FxController {
private final Stage window;
private final ResourceBundle localization;
private final Lazy<Scene> startScene;
private final StringBinding missingCapabilityDescription;
private final ReadOnlyObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
@Inject
MigrationCapabilityErrorController(@MigrationWindow Stage window, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, ResourceBundle localization, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene) {
this.window = window;
this.missingCapability = missingCapability;
this.localization = localization;
this.startScene = startScene;
this.missingCapabilityDescription = Bindings.createStringBinding(this::getMissingCapabilityDescription, missingCapability);
}
@FXML
public void back() {
window.setScene(startScene.get());
}
/* Getters */
public StringBinding missingCapabilityDescriptionProperty() {
return missingCapabilityDescription;
}
public String getMissingCapabilityDescription() {
FileSystemCapabilityChecker.Capability c = missingCapability.get();
if (c != null) {
return localization.getString("migration.error.missingFileSystemCapabilities.reason." + c.name());
} else {
return null;
}
}
}

View File

@@ -0,0 +1,29 @@
package org.cryptomator.ui.migration;
import dagger.Lazy;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
@MigrationScoped
public class MigrationGenericErrorController implements FxController {
private final Stage window;
private final Lazy<Scene> startScene;
@Inject
MigrationGenericErrorController(@MigrationWindow Stage window, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene) {
this.window = window;
this.startScene = startScene;
}
@FXML
public void back() {
window.setScene(startScene.get());
}
}

View File

@@ -4,22 +4,26 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StackTraceController;
import org.cryptomator.ui.mainwindow.MainWindow;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -35,16 +39,30 @@ abstract class MigrationModule {
@Provides
@MigrationWindow
@MigrationScoped
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("migration.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}
@Provides
@Named("genericErrorCause")
@MigrationScoped
static ObjectProperty<Throwable> provideGenericErrorCause() {
return new SimpleObjectProperty<>();
}
@Provides
@Named("capabilityErrorCause")
@MigrationScoped
static ObjectProperty<FileSystemCapabilityChecker.Capability> provideCapabilityErrorCause() {
return new SimpleObjectProperty<>();
}
@Provides
@FxmlScene(FxmlFile.MIGRATION_START)
@MigrationScoped
@@ -66,6 +84,21 @@ abstract class MigrationModule {
return fxmlLoaders.createScene("/fxml/migration_success.fxml");
}
@Provides
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
@MigrationScoped
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/migration_capability_error.fxml");
}
@Provides
@FxmlScene(FxmlFile.MIGRATION_GENERIC_ERROR)
@MigrationScoped
static Scene provideMigrationGenericErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/migration_generic_error.fxml");
}
// ------------------
@Binds
@@ -83,4 +116,21 @@ abstract class MigrationModule {
@FxControllerKey(MigrationSuccessController.class)
abstract FxController bindMigrationSuccessController(MigrationSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(MigrationCapabilityErrorController.class)
abstract FxController bindMigrationCapabilityErrorController(MigrationCapabilityErrorController controller);
@Binds
@IntoMap
@FxControllerKey(MigrationGenericErrorController.class)
abstract FxController bindMigrationGenericErrorController(MigrationGenericErrorController controller);
@Provides
@IntoMap
@FxControllerKey(StackTraceController.class)
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Throwable> errorCause) {
return new StackTraceController(errorCause.get());
}
}

View File

@@ -1,32 +1,28 @@
package org.cryptomator.ui.migration;
import dagger.Lazy;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.WritableValue;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -36,44 +32,54 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@MigrationScoped
public class MigrationRunController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MigrationRunController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private static final Duration MIGRATION_PROGRESS_UPDATE_INTERVAL = Duration.millis(25);
private static final long MIGRATION_PROGRESS_UPDATE_MILLIS = 50;
private final Stage window;
private final Vault vault;
private final ExecutorService executor;
private final ScheduledExecutorService scheduler;
private final Optional<KeychainAccess> keychainAccess;
private final ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
private final ObjectProperty<Throwable> errorCause;
private final Lazy<Scene> startScene;
private final Lazy<Scene> successScene;
private final ObjectBinding<ContentDisplay> migrateButtonContentDisplay;
private final Lazy<Scene> capabilityErrorScene;
private final Lazy<Scene> genericErrorScene;
private final BooleanProperty migrationButtonDisabled;
private final DoubleProperty migrationProgress;
private final ScheduledService<Double> migrationProgressObservationService;
private volatile double volatileMigrationProgress = -1.0;
public NiceSecurePasswordField passwordField;
@Inject
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene) {
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, Optional<KeychainAccess> keychainAccess, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @Named("genericErrorCause") ObjectProperty<Throwable> errorCause, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_GENERIC_ERROR) Lazy<Scene> genericErrorScene) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.scheduler = scheduler;
this.keychainAccess = keychainAccess;
this.missingCapability = missingCapability;
this.errorCause = errorCause;
this.startScene = startScene;
this.successScene = successScene;
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
this.capabilityErrorScene = capabilityErrorScene;
this.genericErrorScene = genericErrorScene;
this.migrationButtonDisabled = new SimpleBooleanProperty();
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
this.migrationProgressObservationService = new MigrationProgressObservationService();
migrationProgressObservationService.setExecutor(executor);
migrationProgressObservationService.setPeriod(MIGRATION_PROGRESS_UPDATE_INTERVAL);
}
public void initialize() {
@@ -93,36 +99,42 @@ public class MigrationRunController implements FxController {
LOG.info("Migrating vault {}", vault.getPath());
CharSequence password = passwordField.getCharacters();
vault.setState(VaultState.PROCESSING);
migrationProgressObservationService.start();
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
Platform.runLater(() -> {
migrationProgress.set(volatileMigrationProgress);
});
}, 0, MIGRATION_PROGRESS_UPDATE_MILLIS, TimeUnit.MILLISECONDS);
Tasks.create(() -> {
Migrators migrators = Migrators.get();
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged);
return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME);
}).onSuccess(needsAnotherMigration -> {
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
if (needsAnotherMigration) {
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayableName());
vault.setState(VaultState.NEEDS_MIGRATION);
} else {
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
vault.setState(VaultState.LOCKED);
passwordField.swipe();
window.setScene(successScene.get());
}
}).onError(InvalidPassphraseException.class, e -> {
shakeWindow();
Animations.createShakeWindowAnimation(window).play();
passwordField.selectAll();
passwordField.requestFocus();
vault.setState(VaultState.NEEDS_MIGRATION);
}).onError(NoApplicableMigratorException.class, e -> {
LOG.error("Can not migrate vault.", e);
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
LOG.error("Underlying file system not supported.", e);
vault.setState(VaultState.ERROR);
// TODO show specific error screen
missingCapability.set(e.getMissingCapability());
window.setScene(capabilityErrorScene.get());
}).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Migration failed for technical reasons.", e);
vault.setState(VaultState.ERROR);
// TODO show generic error screen
vault.setState(VaultState.NEEDS_MIGRATION);
errorCause.set(e);
window.setScene(genericErrorScene.get());
}).andFinally(() -> {
migrationProgressObservationService.cancel();
migrationProgressObservationService.reset();
progressSyncTask.cancel(true);
}).runOnce(executor);
}
@@ -161,54 +173,6 @@ public class MigrationRunController implements FxController {
}
}
// Sets migrationProgress to volatileMigrationProgress at its configured interval
private class MigrationProgressObservationService extends ScheduledService<Double> {
@Override
protected Task<Double> createTask() {
return new Task<>() {
@Override
protected Double call() {
return volatileMigrationProgress;
}
};
}
@Override
protected void succeeded() {
assert getValue() != null;
migrationProgress.set(getValue());
super.succeeded();
}
}
/* Animations */
private void shakeWindow() {
WritableValue<Double> writableWindowX = new WritableValue<>() {
@Override
public Double getValue() {
return window.getX();
}
@Override
public void setValue(Double value) {
window.setX(value);
}
};
Timeline timeline = new Timeline( //
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
);
timeline.play();
}
/* Getter/Setter */
public Vault getVault() {

View File

@@ -18,8 +18,8 @@ import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(includes = {AutoStartModule.class})
@@ -41,11 +41,11 @@ abstract class PreferencesModule {
@Provides
@PreferencesWindow
@PreferencesScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("preferences.title"));
stage.setResizable(false);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -20,8 +20,8 @@ import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -37,12 +37,12 @@ abstract class QuitModule {
@Provides
@QuitWindow
@QuitScoped
static Stage provideStage(@Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setMinWidth(300);
stage.setMinHeight(100);
stage.initModality(Modality.APPLICATION_MODAL);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -0,0 +1,69 @@
package org.cryptomator.ui.recoverykey;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class AutoCompleter {
private final List<String> dictionary;
public AutoCompleter(Collection<String> dictionary) {
this.dictionary = unmodifiableSortedRandomAccessList(dictionary);
}
private static <T extends Comparable<T>> List<T> unmodifiableSortedRandomAccessList(Collection<T> items) {
List<T> result = new ArrayList<>(items);
Collections.sort(result);
return Collections.unmodifiableList(result);
}
public Optional<String> autocomplete(String prefix) {
if (Strings.isNullOrEmpty(prefix)) {
return Optional.empty();
}
int potentialMatchIdx = findIndexOfLexicographicallyPreceeding(0, dictionary.size(), prefix);
if (potentialMatchIdx < dictionary.size()) {
String potentialMatch = dictionary.get(potentialMatchIdx);
return potentialMatch.startsWith(prefix) ? Optional.of(potentialMatch) : Optional.empty();
} else {
return Optional.empty();
}
}
/**
* Find the index of the first word in {@link #dictionary} that starts with a given prefix.
*
* This method performs an "unsuccessful" binary search (it doesn't return when encountering an exact match).
* Instead it continues searching in the left half (which includes the exact match) until only one element is left.
*
* If the dictionary doesn't contain a word "left" of the given prefix, this method returns an invalid index, though.
*
* @param begin Index of first element (inclusive)
* @param end Index of last element (exclusive)
* @param prefix
* @return index between [0, dictLen], i.e. index can exceed the upper bounds of {@link #dictionary}.
*/
private int findIndexOfLexicographicallyPreceeding(int begin, int end, String prefix) {
if (begin >= end) {
return begin; // this is usually where a binary search ends "unsuccessful"
}
int mid = (begin + end) / 2;
String word = dictionary.get(mid);
if (prefix.compareTo(word) <= 0) { // prefix preceeds or matches word
// proceed in left half
assert mid < end;
return findIndexOfLexicographicallyPreceeding(0, mid, prefix);
} else {
// proceed in right half
assert mid >= begin;
return findIndexOfLexicographicallyPreceeding(mid + 1, end, prefix);
}
}
}

View File

@@ -21,11 +21,21 @@ public interface RecoveryKeyComponent {
Stage window();
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
Lazy<Scene> scene();
Lazy<Scene> creationScene();
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
Lazy<Scene> recoverScene();
default void showRecoveryKeyCreationWindow() {
Stage stage = window();
stage.setScene(scene().get());
stage.setScene(creationScene().get());
stage.sizeToScene();
stage.show();
}
default void showRecoveryKeyRecoverWindow() {
Stage stage = window();
stage.setScene(recoverScene().get());
stage.sizeToScene();
stage.show();
}

View File

@@ -1,27 +1,21 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.WritableValue;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
@@ -48,19 +42,26 @@ public class RecoveryKeyCreationController implements FxController {
this.recoveryKeyFactory = recoveryKeyFactory;
this.recoveryKeyProperty = recoveryKey;
}
@FXML
public void createRecoveryKey() {
Tasks.create(() -> {
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
}).onSuccess(result -> {
recoveryKeyProperty.set(result);
Task<String> task = new RecoveryKeyCreationTask();
task.setOnScheduled(event -> {
LOG.debug("Creating recovery key for {}.", vault.getDisplayablePath());
});
task.setOnSucceeded(event -> {
String recoveryKey = task.getValue();
recoveryKeyProperty.set(recoveryKey);
window.setScene(successScene.get());
}).onError(IOException.class, e -> {
LOG.error("Creation of recovery key failed.", e);
}).onError(InvalidPassphraseException.class, e -> {
shakeWindow();
}).runOnce(executor);
});
task.setOnFailed(event -> {
if (task.getException() instanceof InvalidPassphraseException) {
Animations.createShakeWindowAnimation(window).play();
} else {
LOG.error("Creation of recovery key failed.", task.getException());
}
});
executor.submit(task);
}
@FXML
@@ -68,31 +69,13 @@ public class RecoveryKeyCreationController implements FxController {
window.close();
}
/* Animations */
private class RecoveryKeyCreationTask extends Task<String> {
private void shakeWindow() {
WritableValue<Double> writableWindowX = new WritableValue<>() {
@Override
public Double getValue() {
return window.getX();
}
@Override
protected String call() throws IOException {
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
}
@Override
public void setValue(Double value) {
window.setX(value);
}
};
Timeline timeline = new Timeline( //
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
);
timeline.play();
}
/* Getter/Setter */

View File

@@ -4,6 +4,7 @@ import javafx.fxml.FXML;
import javafx.print.PageLayout;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.control.Button;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.text.Font;
@@ -16,6 +17,8 @@ import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ResourceBundle;
public class RecoveryKeyDisplayController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
@@ -23,11 +26,14 @@ public class RecoveryKeyDisplayController implements FxController {
private final Stage window;
private final String vaultName;
private final String recoveryKey;
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
private final ResourceBundle localization;
public Button copyButton;
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey, ResourceBundle localization) {
this.window = window;
this.vaultName = vaultName;
this.recoveryKey = recoveryKey;
this.localization = localization;
}
@FXML
@@ -68,6 +74,8 @@ public class RecoveryKeyDisplayController implements FxController {
clipboardContent.putString(recoveryKey);
Clipboard.getSystemClipboard().setContent(clipboardContent);
LOG.info("Recovery key copied to clipboard.");
copyButton.setText(localization.getString("generic.button.copied"));
}
@FXML

View File

@@ -10,11 +10,13 @@ import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
@Singleton
public class RecoveryKeyFactory {
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private static final byte[] PEPPER = new byte[0];
private final WordEncoder wordEncoder;
@@ -22,6 +24,10 @@ public class RecoveryKeyFactory {
public RecoveryKeyFactory(WordEncoder wordEncoder) {
this.wordEncoder = wordEncoder;
}
public Collection<String> getDictionary() {
return wordEncoder.getWords();
}
/**
* @param vaultPath Path to the storage location of a vault
@@ -32,7 +38,7 @@ public class RecoveryKeyFactory {
* @apiNote This is a long-running operation and should be invoked in a background thread
*/
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException {
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, new byte[0], password);
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, PEPPER, password);
try {
return createRecoveryKey(rawKey);
} finally {
@@ -53,26 +59,53 @@ public class RecoveryKeyFactory {
}
}
/**
* Creates a completely new masterkey using a recovery key.
* @param vaultPath Path to the storage location of a vault
* @param recoveryKey A recovery key for this vault
* @param newPassword The new password used to encrypt the keys
* @throws IOException If the masterkey file could not be written
* @throws IllegalArgumentException If the recoveryKey is invalid
* @apiNote This is a long-running operation and should be invoked in a background thread
*/
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
try {
CryptoFileSystemProvider.restoreRawKey(vaultPath, MASTERKEY_FILENAME, rawKey, PEPPER, newPassword);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}
}
/**
* Checks whether a String is a syntactically correct recovery key with a valid checksum
* @param recoveryKey A word sequence which might be a recovery key
* @return <code>true</code> if this seems to be a legitimate recovery key
*/
public boolean validateRecoveryKey(String recoveryKey) {
final byte[] paddedKey;
try {
paddedKey = wordEncoder.decode(recoveryKey);
byte[] key = decodeRecoveryKey(recoveryKey);
Arrays.fill(key, (byte) 0x00);
return true;
} catch (IllegalArgumentException e) {
return false;
}
if (paddedKey.length != 66) {
return false;
}
private byte[] decodeRecoveryKey(String recoveryKey) throws IllegalArgumentException {
byte[] paddedKey = new byte[0];
try {
paddedKey = wordEncoder.decode(recoveryKey);
Preconditions.checkArgument(paddedKey.length == 66, "Recovery key doesn't consist of 66 bytes.");
byte[] rawKey = Arrays.copyOf(paddedKey, 64);
byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66);
byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes();
byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2);
Preconditions.checkArgument(Arrays.equals(expectedCrc16, actualCrc16), "Recovery key has invalid CRC.");
return rawKey;
} finally {
Arrays.fill(paddedKey, (byte) 0x00);
}
byte[] rawKey = Arrays.copyOf(paddedKey, 64);
byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66);
byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes();
byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2);
return Arrays.equals(expectedCrc16, actualCrc16);
}
}

View File

@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
@@ -17,11 +19,13 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -37,13 +41,13 @@ abstract class RecoveryKeyModule {
@Provides
@RecoveryKeyWindow
@RecoveryKeyScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("keyRecoveryOwner") Stage owner) {
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("keyRecoveryOwner") Stage owner) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("recoveryKey.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}
@@ -53,7 +57,15 @@ abstract class RecoveryKeyModule {
static StringProperty provideRecoveryKeyProperty() {
return new SimpleStringProperty();
}
@Provides
@RecoveryKeyScoped
@Named("newPassword")
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
return new SimpleObjectProperty<>("");
}
// ------------------
@Provides
@@ -70,6 +82,20 @@ abstract class RecoveryKeyModule {
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
}
@Provides
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
@RecoveryKeyScoped
static Scene provideRecoveryKeyRecoverScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/recoverykey_recover.fxml");
}
@Provides
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD)
@RecoveryKeyScoped
static Scene provideRecoveryKeyResetPasswordScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/recoverykey_reset_password.fxml");
}
// ------------------
@Binds
@@ -80,13 +106,30 @@ abstract class RecoveryKeyModule {
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyDisplayController.class)
static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get());
static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, ResourceBundle localization) {
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get(), localization);
}
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyRecoverController.class)
abstract FxController provideRecoveryKeyRecoverController(RecoveryKeyRecoverController controller);
@Binds
@IntoMap
@FxControllerKey(RecoveryKeySuccessController.class)
abstract FxController bindRecoveryKeySuccessController(RecoveryKeySuccessController controller);
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyResetPasswordController.class)
abstract FxController bindRecoveryKeyResetPasswordController(RecoveryKeyResetPasswordController controller);
@Provides
@IntoMap
@FxControllerKey(NewPasswordController.class)
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
return new NewPasswordController(resourceBundle, strengthRater, password);
}
}

View File

@@ -0,0 +1,116 @@
package org.cryptomator.ui.recoverykey;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import dagger.Lazy;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
import java.util.Optional;
@RecoveryKeyScoped
public class RecoveryKeyRecoverController implements FxController {
private final static CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
private final Stage window;
private final Vault vault;
private final StringProperty recoveryKey;
private final RecoveryKeyFactory recoveryKeyFactory;
private final BooleanBinding validRecoveryKey;
private final Lazy<Scene> resetPasswordScene;
private final AutoCompleter autoCompleter;
public TextArea textarea;
@Inject
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene) {
this.window = window;
this.vault = vault;
this.recoveryKey = recoveryKey;
this.recoveryKeyFactory = recoveryKeyFactory;
this.validRecoveryKey = Bindings.createBooleanBinding(this::isValidRecoveryKey, recoveryKey);
this.resetPasswordScene = resetPasswordScene;
this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary());
}
@FXML
public void initialize() {
recoveryKey.bind(textarea.textProperty());
}
private TextFormatter.Change filterTextChange(TextFormatter.Change change) {
if (Strings.isNullOrEmpty(change.getText())) {
// pass-through caret/selection changes that don't affect the text
return change;
}
if (!ALLOWED_CHARS.matchesAllOf(change.getText())) {
return null; // reject change
}
String text = change.getControlNewText();
int caretPos = change.getCaretPosition();
if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word?
int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0);
String currentWord = text.substring(beginOfWord, caretPos);
Optional<String> suggestion = autoCompleter.autocomplete(currentWord);
if (suggestion.isPresent()) {
String completion = suggestion.get().substring(currentWord.length());
change.setText(change.getText() + completion);
change.setAnchor(caretPos + completion.length());
}
}
return change;
}
@FXML
public void onKeyPressed(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) {
// apply autocompletion:
int pos = textarea.getAnchor();
textarea.insertText(pos, " ");
textarea.positionCaret(pos + 1);
}
}
@FXML
public void close() {
window.close();
}
@FXML
public void recover() {
window.setScene(resetPasswordScene.get());
}
/* Getter/Setter */
public Vault getVault() {
return vault;
}
public BooleanBinding validRecoveryKeyProperty() {
return validRecoveryKey;
}
public boolean isValidRecoveryKey() {
return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get());
}
public TextFormatter getRecoveryKeyTextFormatter() {
return new TextFormatter<>(this::filterTextChange);
}
}

View File

@@ -0,0 +1,94 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
@RecoveryKeyScoped
public class RecoveryKeyResetPasswordController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyResetPasswordController.class);
private final Stage window;
private final Vault vault;
private final RecoveryKeyFactory recoveryKeyFactory;
private final ExecutorService executor;
private final StringProperty recoveryKey;
private final ObjectProperty<CharSequence> newPassword;
private final Lazy<Scene> recoverScene;
private final BooleanBinding invalidNewPassword;
@Inject
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @Named("newPassword")ObjectProperty<CharSequence> newPassword, @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
this.window = window;
this.vault = vault;
this.recoveryKeyFactory = recoveryKeyFactory;
this.executor = executor;
this.recoveryKey = recoveryKey;
this.newPassword = newPassword;
this.recoverScene = recoverScene;
this.invalidNewPassword = Bindings.createBooleanBinding(this::isInvalidNewPassword, newPassword);
}
@FXML
public void back() {
window.setScene(recoverScene.get());
}
@FXML
public void done() {
Task<Void> task = new ResetPasswordTask();
task.setOnScheduled(event -> {
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
});
task.setOnSucceeded(event -> {
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
// TODO show success screen
window.close();
});
task.setOnFailed(event -> {
// TODO show generic error screen
LOG.error("Creation of recovery key failed.", task.getException());
});
executor.submit(task);
}
private class ResetPasswordTask extends Task<Void> {
@Override
protected Void call() throws IOException, IllegalArgumentException {
recoveryKeyFactory.resetPasswordWithRecoveryKey(vault.getPath(), recoveryKey.get(), newPassword.get());
return null;
}
}
/* Getter/Setter */
public BooleanBinding invalidNewPasswordProperty() {
return invalidNewPassword;
}
public boolean isInvalidNewPassword() {
return newPassword.get() == null || newPassword.get().length() == 0;
}
}

View File

@@ -2,6 +2,7 @@ package org.cryptomator.ui.recoverykey;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -11,6 +12,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -31,6 +33,10 @@ class WordEncoder {
this(DEFAULT_WORD_FILE);
}
public List<String> getWords() {
return words;
}
public WordEncoder(String wordFile) {
try (InputStream in = getClass().getResourceAsStream(wordFile); //
Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII.newDecoder()); //
@@ -78,7 +84,7 @@ class WordEncoder {
* @throws IllegalArgumentException If the encoded string doesn't consist of a multiple of two words or one of the words is unknown to this encoder.
*/
public byte[] decode(String encoded) {
List<String> splitted = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(encoded);
List<String> splitted = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(Strings.nullToEmpty(encoded));
Preconditions.checkArgument(splitted.size() % 2 == 0, "%s needs to be a multiple of two words", encoded);
byte[] result = new byte[splitted.size() / 2 * 3];
for (int i = 0; i < splitted.size(); i+=2) {

View File

@@ -21,8 +21,8 @@ import org.cryptomator.ui.mainwindow.MainWindow;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -38,13 +38,13 @@ abstract class RemoveVaultModule {
@Provides
@RemoveVaultWindow
@RemoveVaultScoped
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("removeVault.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

View File

@@ -3,57 +3,37 @@ package org.cryptomator.ui.traymenu;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.launcher.AppLifecycleListener;
import org.cryptomator.ui.launcher.FxApplicationStarter;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.awt.Desktop;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.desktop.QuitResponse;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@TrayMenuScoped
class TrayMenuController {
private static final Logger LOG = LoggerFactory.getLogger(TrayMenuController.class);
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
private final ResourceBundle resourceBundle;
private final AppLifecycleListener appLifecycle;
private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch;
private final ShutdownHook shutdownHook;
private final ObservableList<Vault> vaults;
private final PopupMenu menu;
private final AtomicBoolean allowSuddenTermination;
@Inject
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
TrayMenuController(ResourceBundle resourceBundle, AppLifecycleListener appLifecycle, FxApplicationStarter fxApplicationStarter, ObservableList<Vault> vaults) {
this.resourceBundle = resourceBundle;
this.appLifecycle = appLifecycle;
this.fxApplicationStarter = fxApplicationStarter;
this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
this.vaults = vaults;
this.menu = new PopupMenu();
this.allowSuddenTermination = new AtomicBoolean(true);
}
public PopupMenu getMenu() {
@@ -62,40 +42,12 @@ class TrayMenuController {
public void initTrayMenu() {
vaults.addListener(this::vaultListChanged);
rebuildMenu();
// register preferences shortcut
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) {
Desktop.getDesktop().setPreferencesHandler(this::showPreferencesWindow);
}
// register quit handler
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
}
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
// allow sudden termination
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
Desktop.getDesktop().enableSuddenTermination();
}
}
private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
assert Platform.isFxApplicationThread();
rebuildMenu();
boolean allVaultsAllowTermination = vaults.stream().map(Vault::getState).allMatch(STATES_ALLOWING_TERMINATION::contains);
boolean suddenTerminationChanged = allowSuddenTermination.compareAndSet(!allVaultsAllowTermination, allVaultsAllowTermination);
if (suddenTerminationChanged && Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
if (allVaultsAllowTermination) {
Desktop.getDesktop().enableSuddenTermination();
LOG.debug("sudden termination enabled");
} else {
Desktop.getDesktop().disableSuddenTermination();
LOG.debug("sudden termination disabled");
}
}
}
private void rebuildMenu() {
@@ -174,37 +126,8 @@ class TrayMenuController {
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void handleQuitRequest(EventObject e, QuitResponse response) {
if (allowSuddenTermination.get()) {
response.performQuit(); // really?
} else {
fxApplicationStarter.get(true).thenAccept(app -> app.showQuitWindow(response));
}
}
private void quitApplication(EventObject actionEvent) {
handleQuitRequest(actionEvent, new QuitResponse() {
@Override
public void performQuit() {
shutdownLatch.countDown();
}
@Override
public void cancelQuit() {
// no-op
}
});
appLifecycle.quit();
}
private void forceUnmountRemainingVaults() {
for (Vault vault : vaults) {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
}
}
}
}
}

View File

@@ -1,32 +1,28 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.WritableValue;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.slf4j.Logger;
@@ -50,22 +46,24 @@ public class UnlockController implements FxController {
private final ExecutorService executor;
private final ObjectBinding<ContentDisplay> unlockButtonState;
private final Optional<KeychainAccess> keychainAccess;
private final VaultService vaultService;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final Lazy<Scene> genericErrorScene;
private final ObjectProperty<Exception> genericErrorCause;
private final ObjectProperty<Throwable> genericErrorCause;
private final ForgetPasswordComponent.Builder forgetPassword;
private final BooleanProperty unlockButtonDisabled;
public NiceSecurePasswordField passwordField;
public CheckBox savePassword;
@Inject
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Exception> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Throwable> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
this.keychainAccess = keychainAccess;
this.vaultService = vaultService;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.genericErrorScene = genericErrorScene;
@@ -93,36 +91,36 @@ public class UnlockController implements FxController {
public void unlock() {
LOG.trace("UnlockController.unlock()");
CharSequence password = passwordField.getCharacters();
vault.setState(VaultState.PROCESSING);
Tasks.create(() -> {
vault.unlock(password);
Task<Vault> task = vaultService.createUnlockTask(vault, password);
task.setOnSucceeded(event -> {
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
try {
keychainAccess.get().storePassphrase(vault.getId(), password);
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}
}).onSuccess(() -> {
vault.setState(VaultState.UNLOCKED);
passwordField.swipe();
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayableName());
window.setScene(successScene.get());
}).onError(InvalidPassphraseException.class, e -> {
shakeWindow();
passwordField.selectAll();
passwordField.requestFocus();
}).onError(NotDirectoryException.class, e -> {
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
window.setScene(invalidMountPointScene.get());
}).onError(DirectoryNotEmptyException.class, e -> {
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
window.setScene(invalidMountPointScene.get());
}).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Unlock failed for technical reasons.", e);
genericErrorCause.set(e);
window.setScene(genericErrorScene.get());
}).andFinally(() -> {
if (!vault.isUnlocked()) {
vault.setState(VaultState.LOCKED);
});
task.setOnFailed(event -> {
if (task.getException() instanceof InvalidPassphraseException) {
Animations.createShakeWindowAnimation(window).play();
passwordField.selectAll();
passwordField.requestFocus();
} else if (task.getException() instanceof NotDirectoryException
|| task.getException() instanceof DirectoryNotEmptyException) {
LOG.error("Unlock failed. Mount point not an empty directory: {}", task.getException().getMessage());
window.setScene(invalidMountPointScene.get());
} else {
LOG.error("Unlock failed for technical reasons.", task.getException());
genericErrorCause.set(task.getException());
window.setScene(genericErrorScene.get());
}
}).runOnce(executor);
});
executor.execute(task);
}
/* Save Password */
@@ -167,33 +165,6 @@ public class UnlockController implements FxController {
}
}
/* Animations */
private void shakeWindow() {
WritableValue<Double> writableWindowX = new WritableValue<>() {
@Override
public Double getValue() {
return window.getX();
}
@Override
public void setValue(Double value) {
window.setX(value);
}
};
Timeline timeline = new Timeline( //
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
);
timeline.play();
}
/* Getter/Setter */
public Vault getVault() {

View File

@@ -21,8 +21,8 @@ import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(subcomponents = {ForgetPasswordComponent.class})
@@ -38,19 +38,19 @@ abstract class UnlockModule {
@Provides
@UnlockWindow
@UnlockScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("unlock.title"));
stage.setResizable(false);
stage.initModality(Modality.APPLICATION_MODAL);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}
@Provides
@Named("genericErrorCause")
@UnlockScoped
static ObjectProperty<Exception> provideGenericErrorCause() {
static ObjectProperty<Throwable> provideGenericErrorCause() {
return new SimpleObjectProperty<>();
}
@@ -109,7 +109,7 @@ abstract class UnlockModule {
@Provides
@IntoMap
@FxControllerKey(StackTraceController.class)
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Exception> errorCause) {
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Throwable> errorCause) {
return new StackTraceController(errorCause.get());
}

View File

@@ -1,11 +1,9 @@
package org.cryptomator.ui.vaultoptions;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.control.CheckBox;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
@@ -13,26 +11,15 @@ import javax.inject.Inject;
public class GeneralVaultOptionsController implements FxController {
private final Vault vault;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
public CheckBox unlockOnStartupCheckbox;
@Inject
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault) {
this.vault = vault;
this.window = window;
this.changePasswordWindow = changePasswordWindow;
this.recoveryKeyWindow = recoveryKeyWindow;
}
@FXML
public void changePassword() {
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
public void initialize() {
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
}
@FXML
public void showRecoveryKey() {
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
}
}

View File

@@ -0,0 +1,42 @@
package org.cryptomator.ui.vaultoptions;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {
private final Vault vault;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
@Inject
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
this.vault = vault;
this.window = window;
this.changePasswordWindow = changePasswordWindow;
this.recoveryKeyWindow = recoveryKeyWindow;
}
@FXML
public void changePassword() {
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
}
@FXML
public void showRecoveryKey() {
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
}
@FXML
public void showRecoverVaultDialogue() {
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyRecoverWindow();
}
}

View File

@@ -21,8 +21,8 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
@@ -38,13 +38,15 @@ abstract class VaultOptionsModule {
@Provides
@VaultOptionsWindow
@VaultOptionsScoped
static Stage provideStage(@MainWindow Stage owner, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(@MainWindow Stage owner, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(vault.getDisplayableName());
stage.setResizable(false);
stage.setResizable(true);
stage.setMinWidth(400);
stage.setMinHeight(300);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}
@@ -72,4 +74,9 @@ abstract class VaultOptionsModule {
@FxControllerKey(MountOptionsController.class)
abstract FxController bindMountOptionsController(MountOptionsController controller);
@Binds
@IntoMap
@FxControllerKey(MasterkeyOptionsController.class)
abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller);
}

View File

@@ -17,8 +17,8 @@ import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
@@ -34,12 +34,12 @@ abstract class WrongFileAlertModule {
@Provides
@WrongFileAlertWindow
@WrongFileAlertScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
Stage stage = new Stage();
stage.setTitle(resourceBundle.getString("wrongFileAlert.title"));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
windowIcon.ifPresent(stage.getIcons()::add);
stage.getIcons().addAll(windowIcons);
return stage;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -17,7 +17,7 @@
}
@font-face {
src: url('dosis-bold.ttf');
src: url('quicksand-bold.ttf');
}
/*******************************************************************************
@@ -27,39 +27,37 @@
******************************************************************************/
.root {
GREEN_0: #373B30;
GREEN_1: #384D14;
GREEN_2: #476611;
GREEN_3: #598016;
GREEN_4: #699917;
GREEN_5: #79B01A;
GREEN_6: #91C734;
GREEN_7: #B9E070;
GREEN_8: #D7E7BA;
GREEN_9: #F3F5F0;
PRIMARY_D2: #2D4D2E;
PRIMARY_D1: #407F41;
PRIMARY: #49B04A;
PRIMARY_L1: #66CC68;
PRIMARY_L2: #EBF5EB;
GRAY_0: #222222;
GRAY_1: #3B3B3B;
GRAY_2: #515151;
GRAY_3: #626262;
GRAY_4: #7E7E7E;
GRAY_5: #9E9E9E;
GRAY_6: #B1B1B1;
GRAY_7: #CFCFCF;
GRAY_8: #E1E1E1;
GRAY_9: #F7F7F7;
SECONDARY: #008A7B;
GRAY_0: #1F2122;
GRAY_1: #35393B;
GRAY_2: #494E51;
GRAY_3: #585E62;
GRAY_4: #71797E;
GRAY_5: #8E989E;
GRAY_6: #9FAAB1;
GRAY_7: #BEC9CF;
GRAY_8: #D3DCE1;
GRAY_9: #EDF3F7;
RED_5: #E74C3C;
ORANGE_5: #E67E22;
YELLOW_5: #F1C40F;
PRIMARY_BG: GREEN_3;
SECONDARY_BG: GRAY_3;
MAIN_BG: GRAY_1;
TEXT_FILL: GRAY_9;
TEXT_FILL_PRIMARY: GREEN_5;
TEXT_FILL_SECONDARY: GRAY_5;
TEXT_FILL_WHITE: white;
TEXT_FILL_HIGHLIGHTED: PRIMARY;
TEXT_FILL_MUTED: GRAY_5;
TITLE_BG: linear-gradient(to bottom, GRAY_2, GRAY_1);
TITLE_TEXT_FILL: PRIMARY;
CONTROL_BORDER_NORMAL: GRAY_3;
CONTROL_BORDER_FOCUSED: GRAY_5;
CONTROL_BORDER_DISABLED: GRAY_2;
@@ -67,27 +65,20 @@
CONTROL_BG_HOVER: GRAY_1;
CONTROL_BG_ARMED: GRAY_2;
CONTROL_BG_DISABLED: GRAY_1;
CONTROL_PRIMARY_BORDER_NORMAL: GREEN_5;
CONTROL_PRIMARY_BORDER_FOCUSED: GREEN_7;
CONTROL_PRIMARY_BORDER_DISABLED: GREEN_3;
CONTROL_PRIMARY_BG_NORMAL: GREEN_3;
CONTROL_PRIMARY_BG_ARMED: GREEN_4;
CONTROL_PRIMARY_BG_DISABLED: GREEN_2;
CONTROL_PRIMARY_LIGHT_BG_NORMAL: GREEN_0;
CONTROL_WHITE_BG_ARMED: GRAY_8;
CONTROL_BG_SELECTED: GRAY_1;
CONTROL_PRIMARY_BORDER_NORMAL: PRIMARY;
CONTROL_PRIMARY_BORDER_ARMED: PRIMARY_L1;
CONTROL_PRIMARY_BORDER_FOCUSED: SECONDARY;
CONTROL_PRIMARY_BG_NORMAL: PRIMARY;
CONTROL_PRIMARY_BG_ARMED: PRIMARY_L1;
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
SCROLL_BAR_THUMB_HOVER: GRAY_4;
INDICATOR_BG: RED_5;
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
PROGRESS_INDICATOR_BEGIN: GRAY_7;
PROGRESS_INDICATOR_END: GRAY_5;
PROGRESS_BAR_BG: GRAY_2;
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_3;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2: YELLOW_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3: GREEN_6;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4: GREEN_5;
-fx-background-color: MAIN_BG;
-fx-text-fill: TEXT_FILL;
@@ -105,7 +96,7 @@
}
.label-secondary {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.label-large {
@@ -136,11 +127,11 @@
}
.glyph-icon-primary {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.glyph-icon-secondary {
-fx-fill: SECONDARY_BG;
.glyph-icon-muted {
-fx-fill: TEXT_FILL_MUTED;
}
.glyph-icon-white {
@@ -158,15 +149,16 @@
******************************************************************************/
.main-window .title {
-fx-background-color: PRIMARY_BG;
-fx-background-color: CONTROL_BORDER_NORMAL, TITLE_BG;
-fx-background-insets: 0, 0 0 1px 0;
}
.main-window .title .label {
-fx-font-family: 'Dosis';
-fx-font-size: 21px;
-fx-font-family: 'Quicksand';
-fx-font-size: 16px;
-fx-font-style: normal;
-fx-font-weight: 700;
-fx-text-fill: white;
-fx-text-fill: TITLE_TEXT_FILL;
}
.main-window .title .button {
@@ -181,24 +173,23 @@
}
.main-window .title .button:armed .glyph-icon {
-fx-fill: CONTROL_WHITE_BG_ARMED;
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px;
-fx-background-radius: 6px, 5px, 4px;
-fx-translate-x: -1px;
-fx-translate-y: 1px;
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 0);
}
.main-window .drag-n-drop-indicator {
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
-fx-background-color: SECONDARY;
-fx-padding: 3px;
}
@@ -225,24 +216,24 @@
}
.tab-pane .tab:selected {
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
}
.tab-pane .tab .tab-label {
-fx-text-fill: SECONDARY_BG;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-alignment: CENTER;
}
.tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG;
-fx-fill: TEXT_FILL_MUTED;
}
.tab-pane .tab:selected .glyph-icon {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.tab-pane .tab:selected .tab-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
}
/*******************************************************************************
@@ -271,16 +262,16 @@
}
.list-view:focused .list-cell:selected {
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
.list-cell:selected {
-fx-background-color: CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: CONTROL_BG_SELECTED;
}
.list-cell .glyph-icon {
-fx-fill: SECONDARY_BG;
-fx-fill: TEXT_FILL_MUTED;
}
.list-cell .header-label {
@@ -289,16 +280,16 @@
}
.list-cell .detail-label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
}
.list-cell:selected .glyph-icon {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.list-cell:selected .header-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
}
.list-cell.drop-above {
@@ -379,13 +370,13 @@
}
.badge-primary {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-background-color: PRIMARY_BG;
-fx-text-fill: white;
-fx-background-color: PRIMARY;
}
.badge-secondary {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-background-color: SECONDARY_BG;
-fx-text-fill: white;
-fx-background-color: SECONDARY;
}
/*******************************************************************************
@@ -395,27 +386,27 @@
******************************************************************************/
.password-strength-indicator .segment {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
.password-strength-indicator.strength-0 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0;
-fx-background-color: RED_5;
}
.password-strength-indicator.strength-1 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1;
-fx-background-color: ORANGE_5;
}
.password-strength-indicator.strength-2 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2;
-fx-background-color: YELLOW_5;
}
.password-strength-indicator.strength-3 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3;
-fx-background-color: PRIMARY;
}
.password-strength-indicator.strength-4 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4;
-fx-background-color: PRIMARY_D1;
}
/*******************************************************************************
@@ -442,8 +433,8 @@
.text-input {
-fx-cursor: text;
-fx-text-fill: TEXT_FILL;
-fx-highlight-fill: PRIMARY_BG;
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
-fx-highlight-fill: PRIMARY;
-fx-prompt-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
@@ -455,7 +446,7 @@
}
.text-input:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
@@ -487,7 +478,7 @@
}
.text-input:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
@@ -499,8 +490,8 @@
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
-fx-cursor: text;
-fx-text-fill: TEXT_FILL;
-fx-highlight-fill: PRIMARY_BG;
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
-fx-highlight-fill: PRIMARY;
-fx-prompt-text-fill: TEXT_FILL_MUTED;
-fx-background-color: null;
}
@@ -529,7 +520,7 @@
}
.button:default {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-text-fill: white;
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_NORMAL;
}
@@ -538,25 +529,25 @@
}
.button:default:armed {
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_ARMED;
-fx-background-color: CONTROL_PRIMARY_BORDER_ARMED, CONTROL_PRIMARY_BG_ARMED;
}
.button:disabled,
.button:default:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
.button:disabled .glyph-icon {
-fx-fill: TEXT_FILL_SECONDARY;
-fx-fill: TEXT_FILL_MUTED;
}
.button:default .glyph-icon {
-fx-fill: TEXT_FILL_WHITE;
-fx-fill: white;
}
.button:default .label {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-text-fill: white;
}
.button-large {
@@ -577,7 +568,7 @@
}
.hyperlink.hyperlink-secondary {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.hyperlink-hover-icon {
@@ -601,7 +592,7 @@
}
.check-box:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.check-box > .box {
@@ -694,7 +685,7 @@
}
.choice-box:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.choice-box > .open-button {
@@ -709,7 +700,7 @@
}
.choice-box:disabled > .open-button > .arrow {
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
-fx-background-color: transparent, TEXT_FILL_MUTED;
}
.choice-box .context-menu {
@@ -758,7 +749,7 @@
}
.menu-item:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.radio-menu-item:checked > .left-container > .radio {

View File

@@ -17,7 +17,7 @@
}
@font-face {
src: url('dosis-bold.ttf');
src: url('quicksand-bold.ttf');
}
/*******************************************************************************
@@ -27,16 +27,13 @@
******************************************************************************/
.root {
GREEN_0: #373B30;
GREEN_1: #384D14;
GREEN_2: #476611;
GREEN_3: #598016;
GREEN_4: #699917;
GREEN_5: #79B01A;
GREEN_6: #91C734;
GREEN_7: #B9E070;
GREEN_8: #D7E7BA;
GREEN_9: #F3F5F0;
PRIMARY_D2: #2D4D2E;
PRIMARY_D1: #407F41;
PRIMARY: #49B04A;
PRIMARY_L1: #66CC68;
PRIMARY_L2: #EBF5EB;
SECONDARY: #008A7B;
GRAY_0: #222222;
GRAY_1: #3B3B3B;
@@ -53,13 +50,14 @@
ORANGE_5: #E67E22;
YELLOW_5: #F1C40F;
PRIMARY_BG: GREEN_5;
SECONDARY_BG: GRAY_5;
MAIN_BG: GRAY_9;
TEXT_FILL: GRAY_0;
TEXT_FILL_PRIMARY: GREEN_4;
TEXT_FILL_SECONDARY: GRAY_4;
TEXT_FILL_WHITE: white;
TEXT_FILL_HIGHLIGHTED: PRIMARY;
TEXT_FILL_MUTED: GRAY_5;
TITLE_BG: PRIMARY;
TITLE_TEXT_FILL: white;
CONTROL_BORDER_NORMAL: GRAY_7;
CONTROL_BORDER_FOCUSED: GRAY_5;
CONTROL_BORDER_DISABLED: GRAY_8;
@@ -67,27 +65,20 @@
CONTROL_BG_HOVER: GRAY_9;
CONTROL_BG_ARMED: GRAY_8;
CONTROL_BG_DISABLED: GRAY_9;
CONTROL_PRIMARY_BORDER_NORMAL: GREEN_3;
CONTROL_PRIMARY_BORDER_FOCUSED: GREEN_1;
CONTROL_PRIMARY_BORDER_DISABLED: GREEN_5;
CONTROL_PRIMARY_BG_NORMAL: GREEN_5;
CONTROL_PRIMARY_BG_ARMED: GREEN_4;
CONTROL_PRIMARY_BG_DISABLED: GREEN_6;
CONTROL_PRIMARY_LIGHT_BG_NORMAL: GREEN_9;
CONTROL_WHITE_BG_ARMED: GRAY_8;
CONTROL_BG_SELECTED: PRIMARY_L2;
CONTROL_PRIMARY_BORDER_NORMAL: PRIMARY_D1;
CONTROL_PRIMARY_BORDER_ARMED: PRIMARY_D2;
CONTROL_PRIMARY_BORDER_FOCUSED: SECONDARY;
CONTROL_PRIMARY_BG_NORMAL: PRIMARY;
CONTROL_PRIMARY_BG_ARMED: PRIMARY_D1;
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
SCROLL_BAR_THUMB_HOVER: GRAY_6;
INDICATOR_BG: RED_5;
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
PROGRESS_INDICATOR_BEGIN: GRAY_2;
PROGRESS_INDICATOR_END: GRAY_4;
PROGRESS_BAR_BG: GRAY_8;
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2: YELLOW_5;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3: GREEN_6;
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4: GREEN_5;
-fx-background-color: MAIN_BG;
-fx-text-fill: TEXT_FILL;
@@ -105,7 +96,7 @@
}
.label-secondary {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.label-large {
@@ -136,11 +127,11 @@
}
.glyph-icon-primary {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.glyph-icon-secondary {
-fx-fill: SECONDARY_BG;
.glyph-icon-muted {
-fx-fill: TEXT_FILL_MUTED;
}
.glyph-icon-white {
@@ -158,15 +149,15 @@
******************************************************************************/
.main-window .title {
-fx-background-color: PRIMARY_BG;
-fx-background-color: TITLE_BG;
}
.main-window .title .label {
-fx-font-family: 'Dosis';
-fx-font-size: 21px;
-fx-font-family: 'Quicksand';
-fx-font-size: 16px;
-fx-font-style: normal;
-fx-font-weight: 700;
-fx-text-fill: white;
-fx-text-fill: TITLE_TEXT_FILL;
}
.main-window .title .button {
@@ -181,24 +172,23 @@
}
.main-window .title .button:armed .glyph-icon {
-fx-fill: CONTROL_WHITE_BG_ARMED;
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px;
-fx-background-radius: 6px, 5px, 4px;
-fx-translate-x: -1px;
-fx-translate-y: 1px;
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 0);
}
.main-window .drag-n-drop-indicator {
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
-fx-border-color: SECONDARY;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
-fx-background-color: SECONDARY;
-fx-padding: 3px;
}
@@ -225,24 +215,24 @@
}
.tab-pane .tab:selected {
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
}
.tab-pane .tab .tab-label {
-fx-text-fill: SECONDARY_BG;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-alignment: CENTER;
}
.tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG;
-fx-fill: TEXT_FILL_MUTED;
}
.tab-pane .tab:selected .glyph-icon {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.tab-pane .tab:selected .tab-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
}
/*******************************************************************************
@@ -271,16 +261,16 @@
}
.list-view:focused .list-cell:selected {
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
.list-cell:selected {
-fx-background-color: CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-color: CONTROL_BG_SELECTED;
}
.list-cell .glyph-icon {
-fx-fill: SECONDARY_BG;
-fx-fill: TEXT_FILL_MUTED;
}
.list-cell .header-label {
@@ -289,16 +279,16 @@
}
.list-cell .detail-label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
}
.list-cell:selected .glyph-icon {
-fx-fill: PRIMARY_BG;
-fx-fill: PRIMARY;
}
.list-cell:selected .header-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
-fx-text-fill: TEXT_FILL_HIGHLIGHTED;
}
.list-cell.drop-above {
@@ -379,13 +369,13 @@
}
.badge-primary {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-background-color: PRIMARY_BG;
-fx-text-fill: white;
-fx-background-color: PRIMARY;
}
.badge-secondary {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-background-color: SECONDARY_BG;
-fx-text-fill: white;
-fx-background-color: SECONDARY;
}
/*******************************************************************************
@@ -395,27 +385,27 @@
******************************************************************************/
.password-strength-indicator .segment {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG;
-fx-background-color: CONTROL_BORDER_NORMAL;
}
.password-strength-indicator.strength-0 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0;
-fx-background-color: RED_5;
}
.password-strength-indicator.strength-1 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1;
-fx-background-color: ORANGE_5;
}
.password-strength-indicator.strength-2 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_2;
-fx-background-color: YELLOW_5;
}
.password-strength-indicator.strength-3 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_3;
-fx-background-color: PRIMARY;
}
.password-strength-indicator.strength-4 .segment.active {
-fx-background-color: PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_4;
-fx-background-color: PRIMARY_D1;
}
/*******************************************************************************
@@ -442,8 +432,8 @@
.text-input {
-fx-cursor: text;
-fx-text-fill: TEXT_FILL;
-fx-highlight-fill: PRIMARY_BG;
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
-fx-highlight-fill: PRIMARY;
-fx-prompt-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px;
-fx-background-radius: 4px;
@@ -455,7 +445,7 @@
}
.text-input:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
@@ -487,7 +477,7 @@
}
.text-input:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
@@ -499,8 +489,8 @@
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
-fx-cursor: text;
-fx-text-fill: TEXT_FILL;
-fx-highlight-fill: PRIMARY_BG;
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
-fx-highlight-fill: PRIMARY;
-fx-prompt-text-fill: TEXT_FILL_MUTED;
-fx-background-color: null;
}
@@ -529,7 +519,7 @@
}
.button:default {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-text-fill: white;
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_NORMAL;
}
@@ -538,25 +528,25 @@
}
.button:default:armed {
-fx-background-color: CONTROL_PRIMARY_BORDER_NORMAL, CONTROL_PRIMARY_BG_ARMED;
-fx-background-color: CONTROL_PRIMARY_BORDER_ARMED, CONTROL_PRIMARY_BG_ARMED;
}
.button:disabled,
.button:default:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
.button:disabled .glyph-icon {
-fx-fill: TEXT_FILL_SECONDARY;
-fx-fill: TEXT_FILL_MUTED;
}
.button:default .glyph-icon {
-fx-fill: TEXT_FILL_WHITE;
-fx-fill: white;
}
.button:default .label {
-fx-text-fill: TEXT_FILL_WHITE;
-fx-text-fill: white;
}
.button-large {
@@ -577,7 +567,7 @@
}
.hyperlink.hyperlink-secondary {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.hyperlink-hover-icon {
@@ -601,7 +591,7 @@
}
.check-box:disabled {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.check-box > .box {
@@ -694,7 +684,7 @@
}
.choice-box:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.choice-box > .open-button {
@@ -709,7 +699,7 @@
}
.choice-box:disabled > .open-button > .arrow {
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
-fx-background-color: transparent, TEXT_FILL_MUTED;
}
.choice-box .context-menu {
@@ -758,7 +748,7 @@
}
.menu-item:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
-fx-text-fill: TEXT_FILL_MUTED;
}
.radio-menu-item:checked > .left-container > .radio {

Binary file not shown.

View File

@@ -21,7 +21,7 @@
<Region VBox.vgrow="ALWAYS"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="/bot_welcome.png"/>
<Image url="/bot.png"/>
</ImageView>
<Region VBox.vgrow="ALWAYS"/>
@@ -29,7 +29,7 @@
<VBox alignment="CENTER" spacing="9">
<Button styleClass="button-large" text="%addvaultwizard.welcome.newButton" onAction="#createNewVault" prefWidth="Infinity">
<graphic>
<FontAwesome5IconView glyph="SPARKLES" glyphSize="15"/>
<FontAwesome5IconView glyph="MAGIC" glyphSize="15"/>
</graphic>
</Button>
<Button styleClass="button-large" text="%addvaultwizard.welcome.existingButton" onAction="#chooseExistingVault" prefWidth="Infinity">

View File

@@ -4,14 +4,10 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.changepassword.ChangePasswordController"

View File

@@ -1,11 +1,11 @@
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.Region?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<HBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:id="titleBar"
@@ -19,13 +19,13 @@
<Insets bottom="6" left="12" right="12" top="6"/>
</padding>
<children>
<Label text="Cryptomator"/>
<Label text="CRYPTOMATOR"/>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
@@ -36,13 +36,21 @@
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.preferencesBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${!controller.minimizeToSysTray}" managed="${!controller.minimizeToSysTray}">
<graphic>
<FontAwesome5IconView glyph="WINDOW_MINIMIZE" glyphSize="12"/>
</graphic>
<tooltip>
<Tooltip text="%main.minimizeBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.migration.MigrationCapabilityErrorController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<Label text="%migration.error.missingFileSystemCapabilities.title" wrapText="true"/>
<Label text="%migration.error.missingFileSystemCapabilities.description" wrapText="true"/>
<Label text="${controller.missingCapabilityDescription}" wrapText="true"/>
</VBox>
</HBox>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.migration.MigrationGenericErrorController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/stacktrace.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -22,7 +22,7 @@
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="USER_CROWN" glyphSize="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
</StackPane>
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
</HBox>

View File

@@ -26,7 +26,7 @@
<FontAwesome5IconView glyph="PRINT"/>
</graphic>
</Button>
<Button text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
<Button fx:id="copyButton" text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
<graphic>
<FontAwesome5IconView glyph="COPY"/>
</graphic>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyRecoverController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
<Label text="%recoveryKey.recover.validKey" graphicTextGap="6" contentDisplay="LEFT" visible="${controller.validRecoveryKey}">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="C+X">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close" />
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validRecoveryKey}" />
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+I">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back" />
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#done" disable="${controller.invalidNewPassword}"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -39,7 +39,7 @@
</HBox>
<Hyperlink styleClass="hyperlink-secondary,hyperlink-hover-icon" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" onAction="#revealStorageLocation">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-secondary" glyph="EYE"/>
<FontAwesome5IconView styleClass="glyph-icon-muted" glyph="EYE"/>
</graphic>
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>

View File

@@ -5,8 +5,7 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
@@ -25,12 +24,11 @@
</ContextMenu>
</contextMenu>
</ListView>
<AnchorPane visible="${controller.emptyVaultList}">
<HBox AnchorPane.leftAnchor="10" AnchorPane.rightAnchor="10" AnchorPane.bottomAnchor="100" alignment="BOTTOM_LEFT">
<Label textAlignment="CENTER" text="%main.vaultlist.emptyList.onboardingInstruction" wrapText="true"/>
</HBox>
<Arc styleClass="onboarding-overlay-arc" AnchorPane.bottomAnchor="5" type="OPEN" centerX="-10" centerY="0" radiusY="100" radiusX="60" startAngle="0" length="-60" strokeWidth="1"/>
</AnchorPane>
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
<Region VBox.vgrow="ALWAYS"/>
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" wrapText="true"/>
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
</VBox>
</StackPane>
<Button styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" onAction="#didClickAddVault" alignment="BASELINE_CENTER" maxWidth="Infinity">
<graphic>

View File

@@ -7,7 +7,7 @@
xmlns:fx="http://javafx.com/fxml"
fx:id="tabPane"
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
minWidth="400"
prefWidth="400"
tabMinWidth="60"
tabClosingPolicy="UNAVAILABLE"
tabDragPolicy="FIXED">
@@ -28,5 +28,13 @@
<fx:include source="/fxml/vault_options_mount.fxml"/>
</content>
</Tab>
<Tab fx:id="keyTab" text="%vaultOptions.masterkey">
<graphic>
<FontAwesome5IconView glyph="KEY"/>
</graphic>
<content>
<fx:include source="/fxml/vault_options_masterkey.fxml"/>
</content>
</Tab>
</tabs>
</TabPane>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
@@ -11,7 +11,6 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Button text="%vaultOptions.general.changePasswordBtn" onAction="#changePassword"/>
<Button text="%vaultOptions.general.showRecoveryKeyBtn" onAction="#showRecoveryKey"/>
<CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/>
</children>
</VBox>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.vaultoptions.MasterkeyOptionsController"
spacing="6"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Button text="%vaultOptions.masterkey.changePasswordBtn" onAction="#changePassword" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="KEY"/>
</graphic>
</Button>
<Region VBox.vgrow="ALWAYS"/>
<Label maxWidth="-Infinity" text="%vaultOptions.masterkey.recoveryKeyExpanation" wrapText="true"/>
<HBox spacing="6" alignment="CENTER">
<Button text="%vaultOptions.masterkey.showRecoveryKeyBtn" onAction="#showRecoveryKey" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="EYE"/>
</graphic>
</Button>
<Button text="%vaultOptions.masterkey.recoverPasswordBtn" onAction="#showRecoverVaultDialogue" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="SYNC"/>
</graphic>
</Button>
</HBox>
<Region VBox.vgrow="ALWAYS"/>
</children>
</VBox>

View File

@@ -8,6 +8,7 @@ generic.button.back=Back
generic.button.cancel=Cancel
generic.button.change=Change
generic.button.copy=Copy
generic.button.copied=Copied!
generic.button.done=Done
generic.button.next=Next
generic.button.print=Print
@@ -49,12 +50,12 @@ addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your da
addvaultwizard.new.generateRecoveryKeyChoice.yes=Yes please, better safe than sorry
addvaultwizard.new.generateRecoveryKeyChoice.no=No thanks, I will not lose my password
### Information
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
addvault.new.readme.storageLocation.fileName=HELP.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT DIRECTORY ⚠️
addvault.new.readme.storageLocation.2=This is your vault's storage location. {\\b DO NOT} alter any files within this directory.
addvault.new.readme.storageLocation.3=If you want to encrypt files using Cryptomator, unlock the vault and use the provided drive.
addvault.new.readme.storageLocation.4=If you need help, try %s.
addvault.new.readme.accessLocation.fileName=WELCOME TO YOUR VAULT.rtf
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ENCRYPTED VOLUME 🔐️
addvault.new.readme.accessLocation.2=This is your vault's access location. Any files added to this volume will be encrypted by Cryptomator. To access this volume at a later point in time, simply unlock it again from within the Cryptomator application.
addvault.new.readme.accessLocation.3=Feel free to remove this file.
@@ -104,6 +105,11 @@ migration.run.startMigrationBtn=Migrate Vault
## Sucess
migration.success.nextStepsInstructions=Migrated "%s" successfully.\nYou can now unlock your vault.
migration.success.unlockNow=Unlock Now
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Unsupported File System
migration.error.missingFileSystemCapabilities.description=Migration was not started, because your vault is located on an inadequate file system.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=The file system does not support long file names.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=The file system does not support long paths.
# Preferences
preferences.title=Preferences
@@ -135,6 +141,7 @@ preferences.donationKey.getDonationKey=Get a donation key
# Main Window
main.closeBtn.tooltip=Close
main.minimizeBtn.tooltip=Minimize
main.preferencesBtn.tooltip=Preferences
main.donationKeyMissing.tooltip=Please consider donating
## Drag 'n' Drop
@@ -171,8 +178,7 @@ wrongFileAlert.information=You have tried to add a file or folder that does not
# Vault Options
## General
vaultOptions.general=General
vaultOptions.general.changePasswordBtn=Change Password
vaultOptions.general.showRecoveryKeyBtn=Display Recovery Key
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
## Mount
vaultOptions.mount=Mounting
vaultOptions.mount.readonly=Read-Only
@@ -185,12 +191,20 @@ vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
vaultOptions.mount.mountPoint.custom=Custom path
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Change Password
vaultOptions.masterkey.recoveryKeyExpanation=A recovery key is your only means to restore access to a vault if you lose your password.
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
# Recovery Key
recoveryKey.title=Recovery Key
recoveryKey.enterPassword.prompt=Enter your password to show the recovery key for "%s":
recoveryKey.display.message=The following recovery key can be used to restore access to "%s":
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
recoveryKey.recover.prompt=Enter your recovery key for "%s":
recoveryKey.recover.validKey=This is a valid recovery key
# New Password
newPassword.promptText=Enter a new password

View File

@@ -43,8 +43,10 @@ addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جر
## Existing
addvaultwizard.existing.chooseBtn=اختر…
## Success
addvaultwizard.success.unlockNow=افتح الان
# Remove Vault
removeVault.title=احذف الحافظة
removeVault.confirmBtn=احذف الحافظة
# Change Password
@@ -69,6 +71,7 @@ migration.start.confirm=نعم, محفظتي متزامنة بالكامل
migration.run.startMigrationBtn=ترقية الحافظة
## Sucess
migration.success.unlockNow=افتح الان
## Missing file system capabilities
# Preferences
preferences.title=تفضيلات
@@ -86,6 +89,7 @@ preferences.updates.checkNowBtn=تحقق الان
main.preferencesBtn.tooltip=تفضيلات
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.remove=احذف الحافظة
main.vaultlist.addVaultBtn=أضِف مخزنًا
## Vault Detail
### Locked
@@ -104,9 +108,13 @@ main.vaultDetail.migrateButton=ترقية الحافظة
vaultOptions.general=عام
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
## Master Key
# Recovery Key
# New Password
newPassword.reenterPassword=تأكيد كلمة المرور الجديدة
newPassword.passwordsMatch=كلمات المرور متطابقة!
newPassword.passwordsDoNotMatch=كلمات المرور غير متطابقة
# Quit

View File

@@ -43,12 +43,9 @@ addvaultwizard.new.createVaultBtn=Vytvořit trezor
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ano prosím, jistota je jistota
addvaultwizard.new.generateRecoveryKeyChoice.no=Ne, díky, neztratím své heslo
### Information
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
addvault.new.readme.storageLocation.2=Toto je umístění vašeho trezoru. NEMĚŇTE žádné soubory uvnitř adresáře.
addvault.new.readme.storageLocation.3=Pokud chcete zašifrovat soubory pomocí Cryptomatoru, odemkněte trezor a použijte přidělený disk.
addvault.new.readme.storageLocation.4=Pokud potřebujete pomoc, zkuste %s.
addvault.new.readme.accessLocation.fileName=Vítej ve svém novém trezoru.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ZAŠIFROVANÁ JEDNOTKA 🔐️
addvault.new.readme.accessLocation.2=Toto je přístup k vašemu trezoru. Všechny soubory přidané do této jednotky budou šifrovány pomocí Cryptomatoru. Pro přístup k této jednotce kdykoliv v budoucnu ji jednoduše opět odemkněte pomocí Cryptomatoru.
addvault.new.readme.accessLocation.3=Tento soubor můžete odstranit.
@@ -96,6 +93,7 @@ migration.run.startMigrationBtn=Migrovat trezor
## Sucess
migration.success.nextStepsInstructions=Migrace "%s" byla úspěšná.\nNyní můžete svůj trezor odemknout.
migration.success.unlockNow=Odemknout nyní
## Missing file system capabilities
# Preferences
preferences.title=Nastavení
@@ -158,8 +156,6 @@ wrongFileAlert.information=Zkoušíte přidat soubor či adresář, který zřej
# Vault Options
## General
vaultOptions.general=Obecné
vaultOptions.general.changePasswordBtn=Změnit heslo
vaultOptions.general.showRecoveryKeyBtn=Zobrazit klíč k obnově
## Mount
vaultOptions.mount=Připojení
vaultOptions.mount.readonly=Pouze pro čtení
@@ -172,6 +168,9 @@ vaultOptions.mount.mountPoint.driveLetter=Použít přiřazené písmeno
vaultOptions.mount.mountPoint.custom=Vlastní cesta
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
## Master Key
vaultOptions.masterkey.changePasswordBtn=Změnit heslo
vaultOptions.masterkey.showRecoveryKeyBtn=Zobrazit klíč k obnově
# Recovery Key
recoveryKey.title=Klíč k obnově
@@ -180,6 +179,9 @@ recoveryKey.display.message=Následující obnovovací klíč může být použi
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
# New Password
newPassword.reenterPassword=Potvrď nové heslo
newPassword.passwordsMatch=Hesla se shodují!
newPassword.passwordsDoNotMatch=Hesla se neshodují
passwordStrength.messageLabel.0=velmi slabé
passwordStrength.messageLabel.1=slabé
passwordStrength.messageLabel.2=dostatečné

View File

@@ -7,6 +7,7 @@ generic.button.back=Zurück
generic.button.cancel=Abbrechen
generic.button.change=Ändern
generic.button.copy=Kopieren
generic.button.copied=Kopiert!
generic.button.done=Fertig
generic.button.next=Weiter
generic.button.print=Drucken
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Ohne dieses Passwort kannst du auf
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
### Information
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ TRESORDATEIEN ⚠️
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors. Ändere {\\b KEINE} Dateien in diesem Verzeichnis.
addvault.new.readme.storageLocation.3=Wenn du Dateien mit Cryptomator verschlüsseln möchtest, entsperre den Tresor und verwende das zur Verfügung gestellte Laufwerk.
addvault.new.readme.storageLocation.4=Falls du Hilfe brauchst, versuche %s.
addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZU DEINEM TRESOR.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️
addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
addvault.new.readme.accessLocation.3=Diese Datei kannst du löschen.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Tresor migrieren
## Sucess
migration.success.nextStepsInstructions=„%s“ erfolgreich migriert.\nDu kannst deinen Tresor jetzt entsperren.
migration.success.unlockNow=Jetzt entsperren
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Nicht unterstütztes Dateisystem
migration.error.missingFileSystemCapabilities.description=Die Migration wurde nicht gestartet, da sich dein Tresor auf einem ungeeigneten Dateisystem befindet.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Das Dateisystem unterstützt keine langen Dateinamen.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Das Dateisystem unterstützt keine langen Pfadnamen.
# Preferences
preferences.title=Einstellungen
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Einen Spendenschlüssel erhalten
# Main Window
main.closeBtn.tooltip=Schließen
main.minimizeBtn.tooltip=Minimieren
main.preferencesBtn.tooltip=Einstellungen
main.donationKeyMissing.tooltip=Bitte denke über eine Spende nach
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuz
# Vault Options
## General
vaultOptions.general=Allgemein
vaultOptions.general.changePasswordBtn=Passwort ändern
vaultOptions.general.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
vaultOptions.general.unlockAfterStartup=Tresor beim Starten von Cryptomator entsperren
## Mount
vaultOptions.mount=Laufwerk
vaultOptions.mount.readonly=Schreibgeschützt
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Zugewiesenen Laufwerksbuchstaben verwe
vaultOptions.mount.mountPoint.custom=Eigener Pfad
vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen 
vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
## Master Key
vaultOptions.masterkey=Passwort
vaultOptions.masterkey.changePasswordBtn=Password ändern
vaultOptions.masterkey.recoveryKeyExpanation=Bei Verlust deines Passworts ist ein Wiederherstellungsschlüssel deine einzige Möglichkeit, den Zugriff auf einen Tresor wiederherzustellen.
vaultOptions.masterkey.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
vaultOptions.masterkey.recoverPasswordBtn=Passwort wiederherstellen
# Recovery Key
recoveryKey.title=Wiederherstellungsschlüssel
recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
recoveryKey.display.message=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
recoveryKey.recover.prompt=Gib deinen Wiederherstellungsschlüssel für „%s“ ein:
recoveryKey.recover.validKey=Dies ist ein gültiger Wiederherstellungsschlüssel
# New Password
newPassword.promptText=Gib ein neues Passwort ein

View File

@@ -30,6 +30,7 @@
## Start
## Run
## Sucess
## Missing file system capabilities
# Preferences
## General
@@ -50,6 +51,7 @@
# Vault Options
## General
## Mount
## Master Key
# Recovery Key

View File

@@ -7,6 +7,7 @@ generic.button.back=Volver
generic.button.cancel=Cancelar
generic.button.change=Modificar
generic.button.copy=Copiar
generic.button.copied=¡Copiado!
generic.button.done=Hecho
generic.button.next=Continuar
generic.button.print=Imprimir
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=No podrá acceder a sus datos sin s
addvaultwizard.new.generateRecoveryKeyChoice.yes=Sí, por favor
addvaultwizard.new.generateRecoveryKeyChoice.no=No, gracias
### Information
addvault.new.readme.storageLocation.fileName=QUÉ ES ESTE DIRECTORIO.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARCHIVOS DE LA BÓVEDA ⚠️
addvault.new.readme.storageLocation.2=Esta es la ubicación del almacenamiento de su bóveda. {\\b NO} altere ningún archivo dentro de este directorio.
addvault.new.readme.storageLocation.3=Si se desea cifrar archivos usando Cryptomator, desbloquear la bóveda y utilizar la unidad proporcionada.
addvault.new.readme.storageLocation.4=Si se necesita ayuda, probar %s.
addvault.new.readme.accessLocation.fileName=BIENVENIDO A SU BÓVEDA.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUMEN CIFRADO 🔐️
addvault.new.readme.accessLocation.2=Esta es el lugar de acceso de la bóveda. Cualquier archivo añadido a este volumen será cifrado por Cryptomator. Para acceder a este volumen en otro momento, es necesario desbloquearlo otra vez desde la aplicación de Cryptomator.
addvault.new.readme.accessLocation.3=No dude en eliminar este archivo.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrar bóveda
## Sucess
migration.success.nextStepsInstructions="%s" se migró con éxito.\nYa se puede desbloquear la bóveda.
migration.success.unlockNow=Desbloquear ahora
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Sistema de archivos no soportado
migration.error.missingFileSystemCapabilities.description=La migración no se inició porque la bóveda se encuentra en un sistema de archivos inadecuado.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=El sistema de archivos no soporta nombres largos.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=El sistema de archivos no soporta rutas largas.
# Preferences
preferences.title=Preferencias
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtener clave de donación
# Main Window
main.closeBtn.tooltip=Cerrar
main.minimizeBtn.tooltip=Minimizar
main.preferencesBtn.tooltip=Preferencias
main.donationKeyMissing.tooltip=Por favor, considera donar
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Se ha intentado añadir un archivo o carpeta que no p
# Vault Options
## General
vaultOptions.general=General
vaultOptions.general.changePasswordBtn=Cambiar contraseña
vaultOptions.general.showRecoveryKeyBtn=Mostrar la clave de recuperación
vaultOptions.general.unlockAfterStartup=Desbloquear bóveda al iniciar Cryptomator
## Mount
vaultOptions.mount=Montaje
vaultOptions.mount.readonly=Sólo lectura
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidad asignada
vaultOptions.mount.mountPoint.custom=Ruta personalizada
vaultOptions.mount.mountPoint.directoryPickerButton=Elegir…
vaultOptions.mount.mountPoint.directoryPickerTitle=Elegir un directorio vacío
## Master Key
vaultOptions.masterkey=Contraseña
vaultOptions.masterkey.changePasswordBtn=Cambiar contraseña
vaultOptions.masterkey.recoveryKeyExpanation=Una clave de recuperación es el único medio para restaurar el acceso a una bóveda si pierde su contraseña.
vaultOptions.masterkey.showRecoveryKeyBtn=Mostrar clave de recuperación
vaultOptions.masterkey.recoverPasswordBtn=Recuperar contraseña
# Recovery Key
recoveryKey.title=Clave de recuperación
recoveryKey.enterPassword.prompt=Ingresar la contraseña para mostrar la clave de recuperación para "%s":
recoveryKey.display.message=La siguiente clave de recuperación puede usarse para restaurar el acceso a "%s":
recoveryKey.display.StorageHints=Manténgala en algún lugar seguro, p.ej.:\n • Almacenarla en un administrador de contraseñas\n • Guardarla en una llave USB\n • Imprimirla en un papel
recoveryKey.recover.prompt=Ingresar la clave de recuperación para "%s":
recoveryKey.recover.validKey=Esta es una clave de recuperación válida
# New Password
newPassword.promptText=Ingrese una contraseña nueva

View File

@@ -7,6 +7,7 @@ generic.button.back=Précédent
generic.button.cancel=Annuler
generic.button.change=Modifier
generic.button.copy=Copier
generic.button.copied=Copié !
generic.button.done=Terminé
generic.button.next=Suivant
generic.button.print=Imprimer
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Sans votre mot de passe, il sera d'
addvaultwizard.new.generateRecoveryKeyChoice.yes=Oui, mieux vaut prévenir que guérir
addvaultwizard.new.generateRecoveryKeyChoice.no=Non merci, je n'oublierai pas mon mot de passe
### Information
addvault.new.readme.storageLocation.fileName=QUEL EST CE RÉPERTOIRE.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FICHIERS DU COFFRE ⚠️
addvault.new.readme.storageLocation.2=Ceci est l'emplacement de stockage de votre coffre. {\\b NE PAS} modifier les fichiers dans ce répertoire.
addvault.new.readme.storageLocation.3=Si vous voulez chiffrer des fichiers en utilisant Cryptomator, déverrouillez le coffre et utilisez le lecteur fourni.
addvault.new.readme.storageLocation.4=Si vous avez besoin d'aide, essayez %s.
addvault.new.readme.accessLocation.fileName=BIENVENUE DANS VOTRE COFFRE.rft
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CHIFFRÉ 🔐️
addvault.new.readme.accessLocation.2=Ceci est l'emplacement d'accès à votre coffre. Tous les fichiers ajoutés à ce volume seront chiffrés par Cryptomator. Pour accéder à ce volume plus tard, il suffit de le déverrouiller de nouveau à partir de l'application Cryptomator.
addvault.new.readme.accessLocation.3=Vous pouvez supprimer ce fichier.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrer le coffre
## Sucess
migration.success.nextStepsInstructions=“%s” migré.\nVous pouvez à présent déverrouiller ce coffre.
migration.success.unlockNow=Déverouiller
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Système de fichiers non pris en charge
migration.error.missingFileSystemCapabilities.description=La migration n'a pas débuté car votre coffre se trouve dans un système de fichiers inapproprié.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Ce système de fichiers ne prend pas en charge les noms de fichiers longs.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Ce système de fichiers ne prend pas en charge les chemins d'accès longs.
# Preferences
preferences.title=Préférences
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtenir une clé de don
# Main Window
main.closeBtn.tooltip=Fermer
main.minimizeBtn.tooltip=Réduire
main.preferencesBtn.tooltip=Préférences
main.donationKeyMissing.tooltip=Merci d'envisager un don
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Vous avez essayé dajouter un fichier ou un dossie
# Vault Options
## General
vaultOptions.general=Général
vaultOptions.general.changePasswordBtn=Modifier le mot de passe
vaultOptions.general.showRecoveryKeyBtn=Afficher la clé de récupération
vaultOptions.general.unlockAfterStartup=Déverrouiller le coffre au démarrage
## Mount
vaultOptions.mount=Montage
vaultOptions.mount.readonly=Lecture seule
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Utiliser la lettre du lecteur assigné
vaultOptions.mount.mountPoint.custom=Chemin personnalisé
vaultOptions.mount.mountPoint.directoryPickerButton=Choisir...
vaultOptions.mount.mountPoint.directoryPickerTitle=Choisir un répertoire vide
## Master Key
vaultOptions.masterkey=Mot de passe
vaultOptions.masterkey.changePasswordBtn=Modifier le mot de passe
vaultOptions.masterkey.recoveryKeyExpanation=Une clé de récupération est votre seul moyen de rétablir l'accès à un coffre-fort, si vous perdez votre mot de passe.
vaultOptions.masterkey.showRecoveryKeyBtn=Afficher la clé de récupération
vaultOptions.masterkey.recoverPasswordBtn=Récupération du mot de passe
# Recovery Key
recoveryKey.title=Clé de récupération
recoveryKey.enterPassword.prompt=Entrez votre mot de passe pour afficher la clé de récupération pour "%s":
recoveryKey.display.message=La clé de récupération suivante peut être utilisée pour restaurer l'accès à "%s " :
recoveryKey.display.StorageHints=Gardez-le quelque part de façon très sûr, par ex. :\n • Stockez le en utilisant un gestionnaire de mot de passe\n • Enregistrez-le sur une clé USB\n • Imprimez-le sur papier
recoveryKey.recover.prompt=Entrez votre clé de récupération pour "%s":
recoveryKey.recover.validKey=Cette clé de récupération est valide
# New Password
newPassword.promptText=Entrer un nouveau mot de passe

View File

@@ -30,6 +30,7 @@
## Start
## Run
## Sucess
## Missing file system capabilities
# Preferences
## General
@@ -50,6 +51,7 @@
# Vault Options
## General
## Mount
## Master Key
# Recovery Key

View File

@@ -7,6 +7,7 @@ generic.button.back=Indietro
generic.button.cancel=Annulla
generic.button.change=Modifica
generic.button.copy=Copia
generic.button.copied=Copiato!
generic.button.done=Fatto
generic.button.next=Avanti
generic.button.print=Stampa
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Non sarai in grado di accedere ai t
addvaultwizard.new.generateRecoveryKeyChoice.yes=Si, per favore, è meglio essere al sicuro
addvaultwizard.new.generateRecoveryKeyChoice.no=No grazie, non perderò la mia password
### Information
addvault.new.readme.storageLocation.fileName=CHE COSA È QUESTA DIRECTORY.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ FILE CASSAFORTE ⚠️
addvault.new.readme.storageLocation.2=Questa è la posizione di archiviazione della tua cassaforte. {\\b NON} modifica qualsiasi file all'interno di questa cartella.
addvault.new.readme.storageLocation.3=Se vuoi crittografare i file usando Cryptomator, sblocca la cassaforte e usa l'unità fornita.
addvault.new.readme.storageLocation.4=Se hai bisogno di aiuto, prova %s.
addvault.new.readme.accessLocation.fileName=BENVENUTO NELLA TUA CASSAFORTE.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CRIPTATO 🔐️
addvault.new.readme.accessLocation.2=Questa è la posizione di accesso del tuo caveau. Tutti i file aggiunti a questo volume verranno crittografati da Cryptomator. Per accedere a questo volume in un secondo momento, è sufficiente sbloccarlo nuovamente dall'applicazione Cryptomator.
addvault.new.readme.accessLocation.3=Sei libero di rimuovere questo file.
@@ -103,6 +101,7 @@ migration.run.startMigrationBtn=Migra Cassaforte
## Sucess
migration.success.nextStepsInstructions=Migrato "%s" con successo.\nOra puoi sbloccare la tua cassaforte.
migration.success.unlockNow=Sblocca adesso
## Missing file system capabilities
# Preferences
preferences.title=Impostazioni
@@ -133,6 +132,7 @@ preferences.donationKey.getDonationKey=Ottieni una chiave di donazione
# Main Window
main.closeBtn.tooltip=Chiudi
main.minimizeBtn.tooltip=Minimizza
main.preferencesBtn.tooltip=Impostazioni
main.donationKeyMissing.tooltip=Per favore considera una donazione
## Drag 'n' Drop
@@ -169,8 +169,7 @@ wrongFileAlert.information=Hai provato ad aggiungere un file o una cartella che
# Vault Options
## General
vaultOptions.general=Generali
vaultOptions.general.changePasswordBtn=Modifica password
vaultOptions.general.showRecoveryKeyBtn=Visualizza la chiave di recupero
vaultOptions.general.unlockAfterStartup=Sblocca vault all'avvio di Cryptomator
## Mount
vaultOptions.mount=Montaggio
vaultOptions.mount.readonly=Sola lettura
@@ -183,12 +182,18 @@ vaultOptions.mount.mountPoint.driveLetter=Usa la lettera del drive assegnata
vaultOptions.mount.mountPoint.custom=Percorso personalizzato
vaultOptions.mount.mountPoint.directoryPickerButton=Scegli…
vaultOptions.mount.mountPoint.directoryPickerTitle=Scegli una directory vuota
## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Modifica password
vaultOptions.masterkey.recoverPasswordBtn=Recupera password
# Recovery Key
recoveryKey.title=Chiave di recupero
recoveryKey.enterPassword.prompt=Inserisci la password per visualizzare la chiave di recupero per "%s":
recoveryKey.display.message=La seguente chiave di recupero può essere utilizzata per ripristinare l'accesso a %s":
recoveryKey.display.StorageHints=Mantienilo da qualche parte molto sicuro, ad es.\n • Archivialo usando un gestore di password\n • Salvarlo su un'unità flash USB\n • Stamparlo su carta
recoveryKey.recover.prompt=Inserisci la tua chiave di recupero per "%s":
recoveryKey.recover.validKey=Questa è una chiave di recupero valida
# New Password
newPassword.promptText=Inserisci una nuova password

View File

@@ -7,6 +7,7 @@ generic.button.back=戻る
generic.button.cancel=キャンセル
generic.button.change=変更
generic.button.copy=コピー
generic.button.copied=コピー完了!
generic.button.done=完了
generic.button.next=次へ
generic.button.print=印刷
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=データにアクセスするに
addvaultwizard.new.generateRecoveryKeyChoice.yes=はい、後悔するより手段を用意します
addvaultwizard.new.generateRecoveryKeyChoice.no=いいえ、パスワードをなくしません
### Information
addvault.new.readme.storageLocation.fileName=このディレクトリについて.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ 金庫ファイル ⚠️
addvault.new.readme.storageLocation.2=ここは金庫のストレージ先です。このディレクトリーのいずれに対しても変更を、{\\b 加えないでください} 。
addvault.new.readme.storageLocation.3=Cryptomator を利用してファイルを暗号化するには、金庫を施錠して表示されるドライブを使用してください。
addvault.new.readme.storageLocation.4=ヘルプが必要であれば %s をお試しください。
addvault.new.readme.accessLocation.fileName=金庫へようこそ.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 暗号ボリューム 🔐️
addvault.new.readme.accessLocation.2=ここは、金庫のアクセス先です。Cryptomator によりこのボリュームに追加したファイルが暗号化されます。これからは Cryptomator アプリケーションから解錠してこのボリュームにアクセスしてください。
addvault.new.readme.accessLocation.3=このファイルはいつでも削除できます。
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=金庫を移行
## Sucess
migration.success.nextStepsInstructions="%s" の移行が成功しました。\n金庫を解錠できるようになりました。
migration.success.unlockNow=今すぐ解錠
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=サポートされないファイルタイプ
migration.error.missingFileSystemCapabilities.description=金庫が不適切なファイルシステムにあるため、移行が開始されませんでした。
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=ファイルシステムが長いファイル名をサポートしていません。
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=ファイルシステムが長いパスをサポートしていません。
# Preferences
preferences.title=設定
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Donation Key を入手する
# Main Window
main.closeBtn.tooltip=閉じる
main.minimizeBtn.tooltip=最小化
main.preferencesBtn.tooltip=設定
main.donationKeyMissing.tooltip=寄付をお願いします
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Cryptomator の金庫ではないファイルかフ
# Vault Options
## General
vaultOptions.general=基本設定
vaultOptions.general.changePasswordBtn=パスワードの変更
vaultOptions.general.showRecoveryKeyBtn=回復キーを表示
vaultOptions.general.unlockAfterStartup=Cryptomatorの起動時に金庫を解錠する
## Mount
vaultOptions.mount=マウント
vaultOptions.mount.readonly=読み取り専用
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=割り当てられるドライブ文
vaultOptions.mount.mountPoint.custom=カスタムパス
vaultOptions.mount.mountPoint.directoryPickerButton=選択...
vaultOptions.mount.mountPoint.directoryPickerTitle=空のディレクトリを選択してください
## Master Key
vaultOptions.masterkey=パスワード
vaultOptions.masterkey.changePasswordBtn=パスワードの変更
vaultOptions.masterkey.recoveryKeyExpanation=回復キーはパスワードを忘れてしまった場合でも、金庫へのアクセスを回復する唯一の手段です。
vaultOptions.masterkey.showRecoveryKeyBtn=回復キーを表示
vaultOptions.masterkey.recoverPasswordBtn=パスワードの回復
# Recovery Key
recoveryKey.title=回復キー
recoveryKey.enterPassword.prompt="%s" の回復キーを表示するためのパスワードを入力してください:
recoveryKey.display.message="%s" へのアクセス権限を復元するリカバリーキー:
recoveryKey.display.StorageHints=とても安全な場所に保存してください、例えば:\n • パスワード管理ソフトに保存\n • USB フラッシュドライブに保存\n • 紙に印刷
recoveryKey.recover.prompt="%s" の回復キーを入力してください:
recoveryKey.recover.validKey=有効な回復キー
# New Password
newPassword.promptText=新しいパスワードを入力してください

View File

@@ -48,12 +48,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터
addvaultwizard.new.generateRecoveryKeyChoice.yes=네. 미안한 것 보단 안전함이 더 낫습니다.
addvaultwizard.new.generateRecoveryKeyChoice.no=아니요, 나는 비밀번호를 잊지 않을겁니다.
### Information
addvault.new.readme.storageLocation.fileName=이 디렉터리에 대해.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT 파일 ⚠️
addvault.new.readme.storageLocation.2=이곳은 Vault 파일 저장경로입니다. {\\b 절대로} 이 디렉터리의 파일을 임의로 교체하거나 수정하지 마십시요.
addvault.new.readme.storageLocation.3=Cryptomator를 사용하여 파일을 암호화하고 싶으시면, Vault를 잠금해제하고 준비된 드라이브에 사용하십시요.
addvault.new.readme.storageLocation.4=도움이 필요하시면, %s를 시도하여 보십시요.
addvault.new.readme.accessLocation.fileName=Vault에 오신것을 환영합니다.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ 암호화된 볼륨 🔐️
addvault.new.readme.accessLocation.2=이곳은 Vault의 접근장소입니다. 이 볼륨에 추가된 파일은 Cryptomator로 암호화 될 것입니다. 나중에 이 볼륨에 접근하기 위해서는, 단순히 Cryptomator 애플리케이션 내에서 잠금해제하여 주십시요.
addvault.new.readme.accessLocation.3=이 파일은 지우셔도 무방합니다.
@@ -103,6 +100,11 @@ migration.run.startMigrationBtn=Vault 마이그레이션
## Sucess
migration.success.nextStepsInstructions="%s" 의 마이그레이션이 성공적으로 완료되었습니다. 이제 Vault를 잠금해제할 수 있습니다.
migration.success.unlockNow=지금 잠금해제
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=지원하지 않는 파일 시스템
migration.error.missingFileSystemCapabilities.description=Vault가 부적절한 파일시스템에 있기 때문에 마이그레이션이 시작되지 않았습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=너무 긴 파일 이름을 파일시스템에서 지원하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=너무 긴 경로를 파일시스템에서 지원하지 않습니다.
# Preferences
preferences.title=환경설정
@@ -134,6 +136,7 @@ preferences.donationKey.getDonationKey=도네이션(기부) 키 얻기
# Main Window
main.closeBtn.tooltip=닫기
main.minimizeBtn.tooltip=최소화
main.preferencesBtn.tooltip=환경설정
main.donationKeyMissing.tooltip=기부를 부탁드립니다.
## Drag 'n' Drop
@@ -170,8 +173,7 @@ wrongFileAlert.information=Cryptomator vault에 없는 파일이나 폴더를
# Vault Options
## General
vaultOptions.general=일반
vaultOptions.general.changePasswordBtn=비밀번호 변경
vaultOptions.general.showRecoveryKeyBtn=복구 키 출력
vaultOptions.general.unlockAfterStartup=Cryptomator를 시작할 때 Vault 잠금 해제
## Mount
vaultOptions.mount=드라이브 구성
vaultOptions.mount.readonly=읽기 전용
@@ -184,12 +186,18 @@ vaultOptions.mount.mountPoint.driveLetter=드라이브 문자를 지정하여
vaultOptions.mount.mountPoint.custom=사용자 지정 경로
vaultOptions.mount.mountPoint.directoryPickerButton=선택
vaultOptions.mount.mountPoint.directoryPickerTitle=빈 디렉터리를 선택
## Master Key
vaultOptions.masterkey.changePasswordBtn=비밀번호 변경
vaultOptions.masterkey.recoveryKeyExpanation=복구 키는 비밀번호를 잊어버렸을 때, Vault의 접근을 복원 할 수 있는 유일한 방법입니다.
vaultOptions.masterkey.showRecoveryKeyBtn=복구 키 표시
# Recovery Key
recoveryKey.title=복구 키
recoveryKey.enterPassword.prompt="%s"의 복구 키를 표시하려면 비밀번호를 입력하여 주십시요.
recoveryKey.display.message="%s" 데이터 접근을 복원하는데 사용 할 수 있는 복구 키 입니다:
recoveryKey.display.StorageHints=매우 안전한곳에 보관하십시요. 예시:\n • 비밀번호 관리자를 사용하여 저장\n • USB 플래시 드라이브에 저장\n • 종이에 출력
recoveryKey.recover.prompt="%s"의 복구키를 입력하십시요:
recoveryKey.recover.validKey=올바른 복구 키 입니다.
# New Password
newPassword.promptText=새 비밀번호를 입력하십시요.

View File

@@ -7,6 +7,7 @@ generic.button.back=Terug
generic.button.cancel=Annuleren
generic.button.change=Wijzig
generic.button.copy=Kopieer
generic.button.copied=Gekopieerd!
generic.button.done=Klaar
generic.button.next=Volgende
generic.button.print=Afdrukken
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Zonder wachtwoord hebt u geen toega
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja graag, voorkomen is beter dan genezen
addvaultwizard.new.generateRecoveryKeyChoice.no=Nee bedankt, ik verlies mijn wachtwoord niet
### Information
addvault.new.readme.storageLocation.fileName=WAT IS DEZE MAP.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KLUIS BESTANDEN ⚠️
addvault.new.readme.storageLocation.2=Dit is de opslaglocatie van uw kluis. Wijzig {\\b GEEN} bestanden binnen deze map.
addvault.new.readme.storageLocation.3=Als u bestanden wilt versleutelen met Cryptomator, ontgrendel dan de kluis en gebruik de opgegeven schijf.
addvault.new.readme.storageLocation.4=Als je hulp nodig hebt, probeer %s.
addvault.new.readme.accessLocation.fileName=WELKOM IN UW KLUIS.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐 VERSLEUTELD VOLUME 🔐
addvault.new.readme.accessLocation.2=Dit is de toegangslocatie van uw kluis. Alle bestanden die aan dit volume worden toegevoegd zullen worden versleuteld door Cryptomator. Om dit volume op een later tijdstip te kunnen gebruiken, ontgrendelt u het gewoon opnieuw vanuit de Cryptomator applicatie.
addvault.new.readme.accessLocation.3=Voel je vrij om dit bestand te verwijderen.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Kluis migreren
## Sucess
migration.success.nextStepsInstructions="%s" is succesvol gemigreerd.\nU kunt nu uw kluis ontgrendelen.
migration.success.unlockNow=Nu Ontgrendelen
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Niet ondersteund bestandssysteem
migration.error.missingFileSystemCapabilities.description=Migratie is niet gestart, omdat uw kluis zich op een niet-adequaat bestandssysteem bevindt.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Het bestandssysteem ondersteunt geen lange bestandsnamen.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Het bestandssysteem ondersteunt geen lange paden.
# Preferences
preferences.title=Voorkeuren
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Verkrijg een donatiesleutel
# Main Window
main.closeBtn.tooltip=Sluiten
main.minimizeBtn.tooltip=Minimaliseer
main.preferencesBtn.tooltip=Voorkeuren
main.donationKeyMissing.tooltip=Overweeg alstublieft om een donatie te doen
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=U hebt geprobeerd om een bestand of een map toe te vo
# Vault Options
## General
vaultOptions.general=Algemeen
vaultOptions.general.changePasswordBtn=Wijzig wachtwoord
vaultOptions.general.showRecoveryKeyBtn=Toon herstelsleutel
vaultOptions.general.unlockAfterStartup=Ontgrendel kluis bij het starten van Cryptomator
## Mount
vaultOptions.mount=Aankoppelen
vaultOptions.mount.readonly=Alleen-Lezen
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Gebruik de toegewezen schijfletter
vaultOptions.mount.mountPoint.custom=Aangepast pad
vaultOptions.mount.mountPoint.directoryPickerButton=Kies…
vaultOptions.mount.mountPoint.directoryPickerTitle=Kies een lege map
## Master Key
vaultOptions.masterkey=Wachtwoord
vaultOptions.masterkey.changePasswordBtn=Wijzig wachtwoord
vaultOptions.masterkey.recoveryKeyExpanation=Een herstelsleutel is je enige manier om de toegang tot een kluis te herstellen als je je wachtwoord kwijtraakt.
vaultOptions.masterkey.showRecoveryKeyBtn=Toon herstelsleutel
vaultOptions.masterkey.recoverPasswordBtn=Wachtwoord herstellen
# Recovery Key
recoveryKey.title=Herstelsleutel
recoveryKey.enterPassword.prompt=Voer uw wachtwoord in om de herstelsleutel voor "%s" te tonen:
recoveryKey.display.message=De volgende herstelsleutel kan worden gebruikt om "%s" te herstellen:
recoveryKey.display.StorageHints=Bewaar het op een veilige plek, bv:\n • Bewaar het in een wachtwoordmanager\n • Sla het op op een USB-stick\n • Print het op papier
recoveryKey.recover.prompt=Voer uw herstelsleutel in voor "%s":
recoveryKey.recover.validKey=Dit is een geldige herstelsleutel
# New Password
newPassword.promptText=Voer een nieuw wachtwoord in

View File

@@ -30,6 +30,7 @@
## Start
## Run
## Sucess
## Missing file system capabilities
# Preferences
## General
@@ -50,6 +51,7 @@
# Vault Options
## General
## Mount
## Master Key
# Recovery Key

View File

@@ -7,6 +7,7 @@ generic.button.back=Voltar
generic.button.cancel=Cancelar
generic.button.change=Alterar
generic.button.copy=Copiar
generic.button.copied=Copiado!
generic.button.done=Pronto
generic.button.next=Próximo
generic.button.print=Imprimir
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Você não será capaz de acessar s
addvaultwizard.new.generateRecoveryKeyChoice.yes=Sim, por favor, melhor seguro do que arrependido
addvaultwizard.new.generateRecoveryKeyChoice.no=Não, obrigado, eu não vou perder minha senha
### Information
addvault.new.readme.storageLocation.fileName=O QUE É ESTE DIRETÓRIO.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ARQUIVOS DO COFRE ⚠️
addvault.new.readme.storageLocation.2=Esta é a localização do seu cofre. {\\b NÃO} altere arquivos neste diretório.
addvault.new.readme.storageLocation.3=Se você quiser criptografar seus arquivos usando o Cryptomator, desbloqueie o cofre e use o local de armazenamento provido.
addvault.new.readme.storageLocation.4=Se precisar de ajuda, tente %s.
addvault.new.readme.accessLocation.fileName=BEM VINDO AO SEU COFRE.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VOLUME CRIPTOGRAFADO 🔐️
addvault.new.readme.accessLocation.2=Este é o local de acesso do seu cofre. Qualquer arquivo que você adicionar a este volume será criptografado pelo Cryptomator. Para acessar este volume posteriormente, simplesmente desbloqueie novamente o cofre na aplicação do Cryptomator.
addvault.new.readme.accessLocation.3=Sinta-se livre para apagar este arquivo.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrar Cofre
## Sucess
migration.success.nextStepsInstructions="%s" migrado com sucesso.\nVocê agora pode desbloquear este cofre.
migration.success.unlockNow=Desbloquear Agora
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Sistema de arquivos não suportado
migration.error.missingFileSystemCapabilities.description=A migração não foi iniciada, porque o seu cofre está localizado em um sistema de arquivos inadequado.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=O sistema de arquivos não suporta arquivos com nome longo.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=O sistema de arquivos não suporta caminhos longos.
# Preferences
preferences.title=Preferências
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Obtenha uma chave de doação
# Main Window
main.closeBtn.tooltip=Fechar
main.minimizeBtn.tooltip=Minimizar
main.preferencesBtn.tooltip=Preferências
main.donationKeyMissing.tooltip=Por favor, considere uma doação
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Você tentou adicionar um arquivo ou diretório que n
# Vault Options
## General
vaultOptions.general=Geral
vaultOptions.general.changePasswordBtn=Alterar Senha
vaultOptions.general.showRecoveryKeyBtn=Exibir chave de recuperação
vaultOptions.general.unlockAfterStartup=Desbloqueie o cofre ao iniciar o Cryptomator
## Mount
vaultOptions.mount=Montagem
vaultOptions.mount.readonly=Somente Leitura
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Usar letra de unidade atribuída
vaultOptions.mount.mountPoint.custom=Caminho personalizado
vaultOptions.mount.mountPoint.directoryPickerButton=Escolher…
vaultOptions.mount.mountPoint.directoryPickerTitle=Escolha um diretório vazio
## Master Key
vaultOptions.masterkey=Senha
vaultOptions.masterkey.changePasswordBtn=Alterar Senha
vaultOptions.masterkey.recoveryKeyExpanation=Se você perder a sua senha, a única forma de restaurar acesso a um cofre é através de uma chave de recuperação.
vaultOptions.masterkey.showRecoveryKeyBtn=Exibir chave de recuperação
vaultOptions.masterkey.recoverPasswordBtn=Recuperar Senha
# Recovery Key
recoveryKey.title=Chave de recuperação
recoveryKey.enterPassword.prompt=Digite sua senha para mostrar a chave de recuperação de "%s":
recoveryKey.display.message=A seguinte chave de recuperação pode ser usada para restaurar o acesso a "%s":
recoveryKey.display.StorageHints=Mantenha-a em um lugar bem seguro, por exemplo:\n • Guarde num gerenciador de senhas\n • Grave num flash drive USB\n • Imprima, ou escreva, num papel
recoveryKey.recover.prompt=Digite sua chave de recuperação para "%s":
recoveryKey.recover.validKey=Esta é uma chave de recuperação válida
# New Password
newPassword.promptText=Digite a nova senha

View File

@@ -7,6 +7,7 @@ generic.button.back=Назад
generic.button.cancel=Отмена
generic.button.change=Изменить
generic.button.copy=Копировать
generic.button.copied=Скопировано!
generic.button.done=Готово
generic.button.next=Далее
generic.button.print=Распечатать
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получ
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, пожалуй, лучше так, чем потом сожалеть
addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
### Information
addvault.new.readme.storageLocation.fileName=ЧТО ЗА КАТАЛОГ.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ ФАЙЛЫ ХРАНИЛИЩ ⚠️
addvault.new.readme.storageLocation.2=Это место, где находится ваше хранилище. {\\b НЕ} изменяйте никакие файлы в этом каталоге.
addvault.new.readme.storageLocation.3=Если вы хотите зашифровать файлы с помощью Cryptomator, разблокируйте хранилище и используйте предоставленный диск.
addvault.new.readme.storageLocation.4=Нужна помощь? Попробуйте %s.
addvault.new.readme.accessLocation.fileName=ДОБРО ПОЖАЛОВАТЬ В ХРАНИЛИЩЕ.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ЗАШИФРОВАННЫЙ ТОМ 🔐️
addvault.new.readme.accessLocation.2=Это место доступа в хранилище. Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Чтобы получить доступ к этому тому позже, просто разблокируйте его из приложения Cryptomator.
addvault.new.readme.accessLocation.3=Этот файл можно удалить.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Перенести хранилище
## Sucess
migration.success.nextStepsInstructions=Перенос "%s" успешно выполнен.\nТеперь можно разблокировать хранилище.
migration.success.unlockNow=Разблокировать
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Не поддерживаемая файловая система
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как ваше хранилище расположено в неадекватной файловой системе.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловая система не поддерживает длинные имена файлов.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловая система не поддерживает длинные пути.
# Preferences
preferences.title=Настройки
@@ -134,6 +137,7 @@ preferences.donationKey.getDonationKey=Получить ключ пожертв
# Main Window
main.closeBtn.tooltip=Закрыть
main.minimizeBtn.tooltip=Минимизировать
main.preferencesBtn.tooltip=Настройки
main.donationKeyMissing.tooltip=Пожалуйста, обдумайте возможность поддержать приложение
## Drag 'n' Drop
@@ -170,8 +174,7 @@ wrongFileAlert.information=Вы пытались добавить файл ил
# Vault Options
## General
vaultOptions.general=Общие
vaultOptions.general.changePasswordBtn=Изменить пароль
vaultOptions.general.showRecoveryKeyBtn=Показать ключ восстановления
vaultOptions.general.unlockAfterStartup=Разблокировать хранилище при запуске Cryptomator
## Mount
vaultOptions.mount=Монтирование
vaultOptions.mount.readonly=Только чтение
@@ -184,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Использовать назнач
vaultOptions.mount.mountPoint.custom=Пользовательский путь
vaultOptions.mount.mountPoint.directoryPickerButton=Выбрать…
vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустой каталог
## Master Key
vaultOptions.masterkey=Пароль
vaultOptions.masterkey.changePasswordBtn=Изменить пароль
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу в случае утери пароля.
vaultOptions.masterkey.showRecoveryKeyBtn=Показать ключ восстановления
vaultOptions.masterkey.recoverPasswordBtn=Восстановить пароль
# Recovery Key
recoveryKey.title=Ключ восстановления
recoveryKey.enterPassword.prompt=Введите пароль, чтобы показать ключ для "%s":
recoveryKey.display.message=Следующий ключ восстановления можно использовать для восстановления доступа к "%s":
recoveryKey.display.StorageHints=Храните его в надежном месте, например:\n • в менеджере паролей\n • на флэш-накопителе USB\n • распечатайте на бумаге
recoveryKey.recover.prompt=Введите ваш ключ восстановления для "%s":
recoveryKey.recover.validKey=Это действительный ключ восстановления
# New Password
newPassword.promptText=Введите новый пароль

View File

@@ -7,6 +7,7 @@ generic.button.back=Bakåt
generic.button.cancel=Avbryt
generic.button.change=Ändra
generic.button.copy=Kopiera
generic.button.copied=Kopierad!
generic.button.done=Klar
generic.button.next=Nästa
generic.button.print=Skriv ut
@@ -30,7 +31,7 @@ addvaultwizard.welcome.newButton=Skapa nytt valv
addvaultwizard.welcome.existingButton=Öppna befintligt valv
## New
### Name
addvaultwizard.new.nameInstruction=Välj namn för valvet
addvaultwizard.new.nameInstruction=Ange namn för valvet
addvaultwizard.new.namePrompt=Valvets namn
### Location
addvaultwizard.new.locationInstruction=Var ska Cryptomator lagra de krypterade filerna för ditt valv?
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Du kommer inte ha tillgång till di
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja tack. För säkerhets skull
addvaultwizard.new.generateRecoveryKeyChoice.no=Nej tack. Här slarvas inga lösenord bort
### Information
addvault.new.readme.storageLocation.fileName=VAD ÄR DETTA FÖR KATALOG.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VALV-FILER ⚠️
addvault.new.readme.storageLocation.2=Detta är ditt valvs lagringsplats. {\\b ÄNDRA INTE} någon fil i denna katalog.
addvault.new.readme.storageLocation.3=Om du vill kryptera filer med Cryptomator, lås upp valvet och använd angiven enhet.
addvault.new.readme.storageLocation.4=Om du behöver hjälp, prova %s.
addvault.new.readme.accessLocation.fileName=VÄLKOMMEN TILL DITT VALV.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ KRYPTERAD VOLYM 🔐️
addvault.new.readme.accessLocation.2=Detta är ditt valvs åtkomstplats. Filer som läggs till i denna volym kommer att krypteras av Cryptomator. För att komma åt denna volym senare, lås bara upp den igen från Cryptomator-programmet.
addvault.new.readme.accessLocation.3=Du kan ta bort denna fil.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Migrera valv
## Sucess
migration.success.nextStepsInstructions="%s" migrerades fullständigt.\nDu kan nu låsa upp ditt valv.
migration.success.unlockNow=Lås upp nu
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Filsystemet stöds ej
migration.error.missingFileSystemCapabilities.description=Migreringen startades inte, eftersom ditt valv ligger på ett ogiltigt filsystem.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Filsystemet har inte stöd för långa filnamn.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Filsystemet har inte stöd för långa filnamn.
# Preferences
preferences.title=Inställningar
@@ -112,6 +115,9 @@ preferences.general.theme=Utseende
preferences.general.startHidden=Dölj fönster när Cryptomator startar
preferences.general.debugLogging=Aktivera debug loggning
preferences.general.autoStart=Starta Cryptomator vid systemstart
preferences.general.interfaceOrientation=Gränssnittsjustering
preferences.general.interfaceOrientation.ltr=Vänster till höger
preferences.general.interfaceOrientation.rtl=Höger till vänster
## Volume
preferences.volume=Virtuell enhet
preferences.volume.type=Volym-typ
@@ -131,6 +137,7 @@ preferences.donationKey.getDonationKey=Skaffa en donationsnyckel
# Main Window
main.closeBtn.tooltip=Stäng
main.minimizeBtn.tooltip=Minimera
main.preferencesBtn.tooltip=Inställningar
main.donationKeyMissing.tooltip=Överväg gärna en donation
## Drag 'n' Drop
@@ -167,8 +174,7 @@ wrongFileAlert.information=Du försöker lägga till en fil eller katalog som in
# Vault Options
## General
vaultOptions.general=Allmänt
vaultOptions.general.changePasswordBtn=Ändra lösenord
vaultOptions.general.showRecoveryKeyBtn=Visa Återsällningsnyckel
vaultOptions.general.unlockAfterStartup=Lås upp valvet när Cryptomator startas
## Mount
vaultOptions.mount=Montering
vaultOptions.mount.readonly=Skrivskyddad
@@ -181,12 +187,20 @@ vaultOptions.mount.mountPoint.driveLetter=Använd tilldelad enhetsbokstav
vaultOptions.mount.mountPoint.custom=Anpassad sökväg
vaultOptions.mount.mountPoint.directoryPickerButton=Välj…
vaultOptions.mount.mountPoint.directoryPickerTitle=Välj en tom katalog
## Master Key
vaultOptions.masterkey=Lösenord
vaultOptions.masterkey.changePasswordBtn=Ändra lösenord
vaultOptions.masterkey.recoveryKeyExpanation=En återställningsnyckel är ditt enda sätt att återställa åtkomst till ett valv, om du förlorar ditt lösenord.
vaultOptions.masterkey.showRecoveryKeyBtn=Visa återsällningsnyckel
vaultOptions.masterkey.recoverPasswordBtn=Återställ lösenord
# Recovery Key
recoveryKey.title=Återställningsnyckel
recoveryKey.enterPassword.prompt=Ange ditt lösenord för att visa återställningsnyckeln för "%s":
recoveryKey.display.message=Använd denna återställningsnyckel för att återställa åtkomst till "%s":
recoveryKey.display.StorageHints=Spara den på en säker plats, t.ex:\n • I en lösenordshanterare\n • På ett USB-minne (förvara säkert) \n • Skriv ut på ett papper (förvara säkert)
recoveryKey.recover.prompt=Ange din återställningsnyckel för "%s":
recoveryKey.recover.validKey=Detta är en giltig återställningsnyckel
# New Password
newPassword.promptText=Ange ett nytt lösenord

View File

@@ -7,6 +7,7 @@ generic.button.back=Geri
generic.button.cancel=İptal
generic.button.change=Değiştir
generic.button.copy=Kopyala
generic.button.copied=Kopyalandı!
generic.button.done=Bitti
generic.button.next=İleri
generic.button.print=Yazdır
@@ -48,12 +49,9 @@ addvaultwizard.new.generateRecoveryKeyChoice=Şifreniz olmadan verilerinize eri
addvaultwizard.new.generateRecoveryKeyChoice.yes=Evet lütfen, kafamı taşlara vurmaktan iyidir
addvaultwizard.new.generateRecoveryKeyChoice.no=Hayır teşekkürler, şifremi kaybetmeyeceğim
### Information
addvault.new.readme.storageLocation.fileName=BU DİZİN NEDİR.rtf
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ KASA DOSYALARI ⚠️
addvault.new.readme.storageLocation.2=Bu kasanızın depolama yeri. Bu dizindeki herhangi bir dosyayı asla {\\b DEĞİŞTİRMEYİN}.
addvault.new.readme.storageLocation.3=Dosyaları Cryptomator kullanarak şifrelemek istiyorsanız kasanın kilidini açın ve verilen sürücüyü kullanın.
addvault.new.readme.storageLocation.4=Eğer yardıma ihtiyacınız varsa, %s 'ı deneyin.
addvault.new.readme.accessLocation.fileName=KASANIZA HOŞGELDİNİZ.rtf
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ ŞİFRELENMİŞ BİRİM 🔐️
addvault.new.readme.accessLocation.2=Bu kasanızın erişim yeri. Bu birime eklenen dosyalar Cryptomator tarafından şifrelenir. Bu birime daha sonra bir daha erişmek için Cryptomator uygulamasından tekrar kilidini kaldırmanız yeterlidir.
addvault.new.readme.accessLocation.3=Bu dosyayı silmeye çekinmeyin.
@@ -103,6 +101,11 @@ migration.run.startMigrationBtn=Kasayı Taşı
## Sucess
migration.success.nextStepsInstructions="%s" başarıyla taşındı.\nKasanızın kilidini şimdi kaldırabilirsiniz.
migration.success.unlockNow=Kilidi Şimdi Aç
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Desteklenmeyen Dosya Sistemi
migration.error.missingFileSystemCapabilities.description=Kasanız desteklenmeyen bir dosya sisteminde bulunduğundan taşıma başlatılmadı.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Dosya sistemi, uzun belge adlarını desteklemiyor.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Dosya sistemi, uzun yolları desteklemiyor.
# Preferences
preferences.title=Seçenekler
@@ -111,7 +114,7 @@ preferences.general=Genel
preferences.general.theme=Görünüş ve Davranış
preferences.general.startHidden=Cryptomator'ı başlatırken pencereyi gizle
preferences.general.debugLogging=Hata ayıklama günlüğünü etkinleştir
preferences.general.autoStart=Cryptomator'u sistrm başlangıcında çalıştır
preferences.general.autoStart=Cryptomator'u sistem başlangıcında çalıştır
preferences.general.interfaceOrientation=Arayüz Yönü
preferences.general.interfaceOrientation.ltr=Sola Yaslı
preferences.general.interfaceOrientation.rtl=Sağa Yaslı
@@ -129,11 +132,12 @@ preferences.updates.updateAvailable=%s sürümüne güncelleme mevcut.
## Donation Key
preferences.donationKey=Bağış
preferences.donationKey.registeredFor=%s için kaydedildi
preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bış anahtarı dediğimiz şey, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
preferences.donationKey.noDonationKey=Geçerli bir bağış anahtarı bulunamadı. Bu anahtar, lisans anahtarı gibidir ama ücretsiz uygulama kullanan muhteşem insanlar içindir ;-)
preferences.donationKey.getDonationKey=Bir bağış anahtarı al
# Main Window
main.closeBtn.tooltip=Kapat
main.minimizeBtn.tooltip=Simge Durumuna Küçült
main.preferencesBtn.tooltip=Seçenekler
main.donationKeyMissing.tooltip=Bağış yapmayı düşünmez miydiniz?
## Drag 'n' Drop
@@ -170,10 +174,9 @@ wrongFileAlert.information=Cryptomator kasası gibi görünmeyen bir dosya veya
# Vault Options
## General
vaultOptions.general=Genel
vaultOptions.general.changePasswordBtn=Şifreyi Değiştir
vaultOptions.general.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
vaultOptions.general.unlockAfterStartup=Cryptomator başlatıldığında kasayı
## Mount
vaultOptions.mount=Bağlanılıyor
vaultOptions.mount=Bağlantı
vaultOptions.mount.readonly=Salt-Okunur
vaultOptions.mount.driveName=Sürücü Adı
vaultOptions.mount.customMountFlags=Özel Bağlantı Parametreleri
@@ -184,12 +187,19 @@ vaultOptions.mount.mountPoint.driveLetter=Bir sürücü harfi kullan
vaultOptions.mount.mountPoint.custom=Özel Dosya Yolu
vaultOptions.mount.mountPoint.directoryPickerButton=Seç…
vaultOptions.mount.mountPoint.directoryPickerTitle=Boş bir dizin seçin
## Master Key
vaultOptions.masterkey=Şifre
vaultOptions.masterkey.changePasswordBtn=Şifreyi Değiştir
vaultOptions.masterkey.showRecoveryKeyBtn=Kurtarma Anahtarını Göster
vaultOptions.masterkey.recoverPasswordBtn=Parolanızı Kurtarın
# Recovery Key
recoveryKey.title=Kurtarma Anahtarı
recoveryKey.enterPassword.prompt="%s" için kurtarma anahtarını göstermek üzere şifrenizi girin:
recoveryKey.display.message=Aşağıdaki kurtarma anahtarı "%s" kasasına erişimi kazanmak için kullanılabilir:
recoveryKey.display.StorageHints=Bunu çok güvenli bir yerde saklayın, örneğin:\n • Şifre yöneticisi kullanarak depolayın\n • USB flash belleğe kaydedin\n • Bir sayfaya yazdırın
recoveryKey.recover.prompt="%s" için kurtarma anahtarınızı girin:
recoveryKey.recover.validKey=Bu geçerli bir kurtarma anahtarı
# New Password
newPassword.promptText=Yeni bir şifre girin

Some files were not shown because too many files have changed in this diff Show More