+ * The app starts with a set of predefined properties (defined in [APPDIR]/../Cryptomator.cfg).
+ * That file is difficult to edit and edits do not persist over updates.
+ * To allow overwriting these properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add the contained properties to the {@link System} properties.
+ * The predefined location are:
+ *
+ *
Linux - {@value LINUX_DIR }
+ *
macOS - {@value MAC_DIR }
+ *
Windows - {@value WIN_DIR }
+ *
+ *
+ *
+ * @see System#getProperties()
+ */
class AdminPropertiesSetter {
private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final String LINUX_DIR = "/etc/cryptmator";
+ private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
+ private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
+ private static final String PROP_FILENAME = "config.properties";
+
static {
final Path adminDir;
if (SystemUtils.IS_OS_WINDOWS) {
adminDir = Path.of(System.getenv().getOrDefault("ProgramData", "C:\\ProgramData"), "Cryptomator");
} else if (SystemUtils.IS_OS_MAC) {
- adminDir = Path.of("/Library/Application Support/Cryptomator");
+ adminDir = Path.of(MAC_DIR);
} else { //LINUX
- adminDir = Path.of("/etc/cryptmator");
+ adminDir = Path.of(LINUX_DIR);
}
- ADMIN_PROPERTIES_DIR = adminDir;
+ ADMIN_PROPERTIES_FILE = adminDir.resolve(PROP_FILENAME);
}
- private static final Path ADMIN_PROPERTIES_DIR;
- private static final Path ADMIN_PROPERTIES_FILE = ADMIN_PROPERTIES_DIR.resolve("config.properties");
+ private static final Path ADMIN_PROPERTIES_FILE;
+ /**
+ * Adjusts the system properties by loading administrative properties from a predefined file location.
+ *
+ * If the file exists and is a valid properties file, its properties will overwrite the existing system properties.
+ *
+ * WARNING: This method modifies the global system properties and should be used with caution. Overwriting some properties has no effect, because they are read only once from the original config during JVM startup.
+ *
+ * @return The adjusted system properties.
+ */
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
+
for (var key : adminProps.stringPropertyNames()) {
var value = adminProps.getProperty(key);
LOG.info("Overwriting {} with value {} from admin properties.", key, value);
@@ -47,6 +77,7 @@ class AdminPropertiesSetter {
adminProps.load(Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ));
} catch (NoSuchFileException _) {
//NO-OP
+ LOG.debug("No admin properties found at {}.", adminPropertiesPath);
} catch (IOException | IllegalArgumentException e) {
LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
From 45633837e0e6d5cba434fb7b6b7297d245e52f9f Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 13 Jan 2026 17:26:48 +0100
Subject: [PATCH 016/100] only allow overwriting a subset of JVM properties
---
.../launcher/AdminPropertiesSetter.java | 38 ++++++++++++++-----
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index d84c4f29b..9ba18fa8c 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -10,13 +10,12 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Properties;
+import java.util.Set;
/**
- * Class to change JVM system properties according to an external properties file
+ * Class to overwrite system properties with an external properties file
*
- * The app starts with a set of predefined properties (defined in [APPDIR]/../Cryptomator.cfg).
- * That file is difficult to edit and edits do not persist over updates.
- * To allow overwriting these properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add the contained properties to the {@link System} properties.
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
* The predefined location are:
*
*
Linux - {@value LINUX_DIR }
@@ -24,6 +23,15 @@ import java.util.Properties;
*
Windows - {@value WIN_DIR }
*
*
+ *
+ * Supported properties for override are:
+ *
+ *
cryptomator.logDir
+ *
cryptomator.pluginDir
+ *
cryptomator.p12Path
+ *
cryptomator.mountPointsDir
+ *
cryptomator.disableUpdateCheck
+ *
*
* @see System#getProperties()
*/
@@ -35,6 +43,13 @@ class AdminPropertiesSetter {
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
private static final String PROP_FILENAME = "config.properties";
+ private static final Set ALLOWED_OVERRIDES = Set.of( //
+ "cryptomator.logDir", //
+ "cryptomator.pluginDir", //
+ "cryptomator.p12Path", //
+ "cryptomator.mountPointsDir", //
+ "cryptomator.disableUpdateCheck");
+
static {
final Path adminDir;
@@ -53,9 +68,8 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid properties file, its properties will overwrite the existing system properties.
- *
- * WARNING: This method modifies the global system properties and should be used with caution. Overwriting some properties has no effect, because they are read only once from the original config during JVM startup.
+ * If the file exists and is a valid properties file, its content will overwrite existing system properties.
+ * Only some properties are supported, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
*/
@@ -64,9 +78,13 @@ class AdminPropertiesSetter {
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
for (var key : adminProps.stringPropertyNames()) {
- var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ if (ALLOWED_OVERRIDES.contains(key)) {
+ var value = adminProps.getProperty(key);
+ LOG.info("Overwriting {} with value {} from admin properties.", key, value);
+ systemProps.setProperty(key, value);
+ } else {
+ LOG.debug("Property {} in admin properties is not supported for override.", key);
+ }
}
return systemProps;
}
From b1c21501a6fef1fcfffc79d0d0161b03fc33d592 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 13 Jan 2026 18:40:53 +0100
Subject: [PATCH 017/100] Add dummy admin properties file in Windows installer
---
dist/common/config.properties | 13 +++++++++++++
dist/win/resources/main.wxs | 30 +++++++++++++++++++++++++++++-
2 files changed, 42 insertions(+), 1 deletion(-)
create mode 100644 dist/common/config.properties
diff --git a/dist/common/config.properties b/dist/common/config.properties
new file mode 100644
index 000000000..6ad5dfd90
--- /dev/null
+++ b/dist/common/config.properties
@@ -0,0 +1,13 @@
+# This is the Cryptomator administrative configuration file.
+# It is a simple key-value pair file.
+# Lines starting with '#' are comments and will be ignored.
+#
+# There are some shorthands for well known folders:
+# Substitution Key | Variable Value
+# @{appdir} | The application installation directory.
+# @{appdata} | %APPDATA% (Windows only).
+# @{localappdata} | %LOCALAPPDATA% (Windows only).
+# @{userhome} | The user's home directory.
+#
+# Example:
+# cryptomator.pluginDir=@{appdata}/Cryptomator/Plugins #Sets a plugin directory and enables plugin loading
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 6310c6985..01f399b77 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -87,7 +87,7 @@
-
+
@@ -99,6 +99,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -107,6 +133,8 @@
+
+
From 42b06aa5562516901eeb284781d642b7d4a8aef2 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 14 Jan 2026 14:12:28 +0100
Subject: [PATCH 018/100] Switch to JSON as config format
Java Properties require ISO 8859-1 character encoding, leading to manual edits of file.
---
.../launcher/AdminPropertiesSetter.java | 50 +++++++++++--------
.../org/cryptomator/launcher/BufferedLog.java | 28 +++++++++++
.../org/cryptomator/launcher/Cryptomator.java | 4 +-
3 files changed, 59 insertions(+), 23 deletions(-)
create mode 100644 src/main/java/org/cryptomator/launcher/BufferedLog.java
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 9ba18fa8c..061590e6b 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -1,21 +1,24 @@
package org.cryptomator.launcher;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.commons.lang3.SystemUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Class to overwrite system properties with an external properties file
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the properties file {@value PROP_FILENAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} loads the JSON file {@value CONFIG_NAME} from an OS-dependent location and add all supported properties to the {@link System} properties.
* The predefined location are:
*
*
Linux - {@value LINUX_DIR }
@@ -37,12 +40,12 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final ObjectMapper JSON = JsonMapper.builder().build();
private static final String LINUX_DIR = "/etc/cryptmator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
- private static final String PROP_FILENAME = "config.properties";
+ private static final String CONFIG_NAME = "config.json";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
@@ -60,7 +63,7 @@ class AdminPropertiesSetter {
} else { //LINUX
adminDir = Path.of(LINUX_DIR);
}
- ADMIN_PROPERTIES_FILE = adminDir.resolve(PROP_FILENAME);
+ ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
}
private static final Path ADMIN_PROPERTIES_FILE;
@@ -77,29 +80,34 @@ class AdminPropertiesSetter {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
- for (var key : adminProps.stringPropertyNames()) {
- if (ALLOWED_OVERRIDES.contains(key)) {
- var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ adminProps.forEach((key, value) -> {
+ if (ALLOWED_OVERRIDES.contains(key) && value instanceof String v) {
+ log("Overwriting {} with value {} from admin properties.", List.of(key, v));
+ systemProps.setProperty(key, v);
} else {
- LOG.debug("Property {} in admin properties is not supported for override.", key);
+ var reason = value instanceof String ? "Unsupported" : "Not a string";
+ log("Property {} in admin config ignored: {}.", List.of(key, reason));
}
- }
+ });
return systemProps;
}
- private static Properties loadAdminProperties(Path adminPropertiesPath) {
- var adminProps = new Properties();
- try {
- adminProps.load(Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ));
+ private static Map loadAdminProperties(Path adminPropertiesPath) {
+ try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
+ var map = JSON.readValue(in, new TypeReference
*
- * Supported properties for override are:
+ * The overridable properties are:
*
*
cryptomator.logDir
*
cryptomator.pluginDir
@@ -71,8 +71,8 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid properties file, its content will overwrite existing system properties.
- * Only some properties are supported, see {@link AdminPropertiesSetter}
+ * If the file exists and is a valid JSON file, its content will overwrite existing system properties.
+ * Only some properties can be overridden, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
*/
@@ -100,7 +100,7 @@ class AdminPropertiesSetter {
}
} catch (NoSuchFileException _) {
//NO-OP
- log("No admin properties found at {}.", List.of(adminPropertiesPath));
+ log("No admin properties found at {}.", List.of(adminPropertiesPath));
} catch (IOException | RuntimeException e) {
log("Failed to read administrative properties from {}. Returning empty properties.", List.of(adminPropertiesPath, e));
}
diff --git a/src/main/java/org/cryptomator/launcher/BufferedLog.java b/src/main/java/org/cryptomator/launcher/BufferedLog.java
index 2e223b13b..0facf7734 100644
--- a/src/main/java/org/cryptomator/launcher/BufferedLog.java
+++ b/src/main/java/org/cryptomator/launcher/BufferedLog.java
@@ -14,15 +14,11 @@ class BufferedLog {
record Entry(String className, String message, List messageInput) {}
- static void log(String className, String message, List messageInput) {
- add(new BufferedLog.Entry(className, message, messageInput));
+ synchronized static void log(String className, String message, List messageInput) {
+ logMessages.add(new BufferedLog.Entry(className, message, messageInput));
}
- synchronized static void add(Entry e) {
- logMessages.add(e);
- }
-
- synchronized static void flush(Logger log) {
+ synchronized static void flushTo(Logger log) {
logMessages.forEach(e -> {
var message = "PRE LOG INIT Event in %s: %s".formatted(e.className, e.message);
log.info(message, e.messageInput.toArray());
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index b61dba843..7bbcb8f05 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -65,7 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
- BufferedLog.flush(LOG);
+ BufferedLog.flushTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //
From a0c9caeb21d053d5e7001623c344feced452a6ba Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 14 Jan 2026 17:57:24 +0100
Subject: [PATCH 027/100] [skip ci] update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc6402c4c..3af691b80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility labels for GUI elements ([#4064](https://github.com/cryptomator/cryptomator/issues/4064), [#4066](https://github.com/cryptomator/cryptomator/pull/4066), [#4055](https://github.com/cryptomator/cryptomator/issues/4055))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
+* Admin configuration: Allow overwriting some app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
From 287ab4e792c45a39927cd5ab1a7a9d7aee830d85 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Thu, 15 Jan 2026 15:11:12 +0100
Subject: [PATCH 028/100] [skip ci] Update changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3af691b80..1c70c2ddd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility labels for GUI elements ([#4064](https://github.com/cryptomator/cryptomator/issues/4064), [#4066](https://github.com/cryptomator/cryptomator/pull/4066), [#4055](https://github.com/cryptomator/cryptomator/issues/4055))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
-* Admin configuration: Allow overwriting some app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
+* Admin configuration: Allow overwriting certain app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
From 088b177c0e7069091ecfe920cf8425676e9a6c04 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Thu, 15 Jan 2026 15:11:46 +0100
Subject: [PATCH 029/100] Ensure that null map is also logged
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 30dcd241d..36c7dd247 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -95,9 +95,10 @@ class AdminPropertiesSetter {
private static Map loadAdminProperties(Path adminPropertiesPath) {
try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
var map = JSON.readValue(in, new TypeReference
*
* The overridable properties are:
@@ -40,12 +37,12 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final ObjectMapper JSON = JsonMapper.builder().build();
+ private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
- private static final String WIN_DIR = "%PROGRAMDATA%\\Cryptomator";
- private static final String CONFIG_NAME = "config.json";
+ private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
+ private static final String CONFIG_NAME = "config.properties";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
@@ -57,7 +54,7 @@ class AdminPropertiesSetter {
static {
final Path adminDir;
if (SystemUtils.IS_OS_WINDOWS) {
- adminDir = Path.of(System.getenv().getOrDefault("ProgramData", "C:\\ProgramData"), "Cryptomator");
+ adminDir = Path.of(WIN_DIR);
} else if (SystemUtils.IS_OS_MAC) {
adminDir = Path.of(MAC_DIR);
} else { //LINUX
@@ -71,7 +68,7 @@ class AdminPropertiesSetter {
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
*
- * If the file exists and is a valid JSON file, its content will overwrite existing system properties.
+ * If the file exists and is a valid properties file, its content will overwrite existing system properties.
* Only some properties can be overridden, see {@link AdminPropertiesSetter}
*
* @return The adjusted system properties.
@@ -80,35 +77,29 @@ class AdminPropertiesSetter {
var systemProps = System.getProperties();
var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
- adminProps.forEach((key, value) -> {
- if (ALLOWED_OVERRIDES.contains(key) && value instanceof String v) {
- log("Overwriting {} with value {} from admin properties.", List.of(key, v));
- systemProps.setProperty(key, v);
+ for (var key : adminProps.stringPropertyNames()) {
+ if (ALLOWED_OVERRIDES.contains(key)) {
+ var value = adminProps.getProperty(key);
+ LOG.info("Overwriting {} with value {} from admin properties.", key, value);
+ systemProps.setProperty(key, value);
} else {
- var reason = value instanceof String ? "Unsupported" : "Not a string";
- log("Property {} in admin config ignored: {}.", List.of(key, reason));
+ LOG.debug("Property {} in admin properties is not supported for override.", key);
}
- });
+ }
return systemProps;
}
- static Map loadAdminProperties(Path adminPropertiesPath) {
- try (var in = Files.newInputStream(adminPropertiesPath, StandardOpenOption.READ)) {
- var map = JSON.readValue(in, new TypeReference>() {});
- if (map == null) {
- throw new NullPointerException("JSON parsing returned null");
- }
- return map;
+ private static Properties loadAdminProperties(Path adminPropertiesPath) {
+ var adminProps = new Properties();
+ try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
+ adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
- log("No admin properties found at {}.", List.of(adminPropertiesPath));
- } catch (IOException | RuntimeException e) {
- log("Failed to read administrative properties from {}. Returning empty properties.", List.of(adminPropertiesPath, e));
+ LOG.debug("No admin properties found at {}.", adminPropertiesPath);
+ } catch (IOException | IllegalArgumentException e) {
+ LOG.warn("Failed to read administrative properties from {}. Returning empty properties.", adminPropertiesPath, e);
}
- return Map.of();
+ return adminProps;
}
- static void log(String message, List messageInput) {
- BufferedLog.log(AdminPropertiesSetter.class.getName(), message, messageInput);
- }
}
From 046372f95bea9035d97598ff46e397252afd3c8e Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:33:26 +0100
Subject: [PATCH 032/100] refactored BufferedLog
* rename to EventualLogger
* adhere to slf4j API
* ensure single instance
---
.../launcher/AdminPropertiesSetter.java | 3 +-
.../org/cryptomator/launcher/BufferedLog.java | 28 -----
.../org/cryptomator/launcher/Cryptomator.java | 2 +-
.../cryptomator/launcher/EventualLogger.java | 115 ++++++++++++++++++
.../launcher/AdminPropertiesSetterTest.java | 81 ++++--------
5 files changed, 138 insertions(+), 91 deletions(-)
delete mode 100644 src/main/java/org/cryptomator/launcher/BufferedLog.java
create mode 100644 src/main/java/org/cryptomator/launcher/EventualLogger.java
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 69121cd6e..3f35529ec 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -2,7 +2,6 @@ package org.cryptomator.launcher;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -37,7 +36,7 @@ import java.util.Set;
*/
class AdminPropertiesSetter {
- private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class);
+ private static final Logger LOG = EventualLogger.getInstance();
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
diff --git a/src/main/java/org/cryptomator/launcher/BufferedLog.java b/src/main/java/org/cryptomator/launcher/BufferedLog.java
deleted file mode 100644
index 68eb75e26..000000000
--- a/src/main/java/org/cryptomator/launcher/BufferedLog.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.cryptomator.launcher;
-
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class BufferedLog {
-
- private final static List logMessages = new ArrayList<>();
-
- private BufferedLog() {
- }
-
- record Entry(String className, String message, List messageInput) {}
-
- synchronized static void log(String className, String message, List messageInput) {
- logMessages.add(new Entry(className, message, messageInput));
- }
-
- synchronized static void flushTo(Logger log) {
- logMessages.forEach(e -> {
- var message = "PRE LOG INIT Event in %s: %s".formatted(e.className, e.message);
- log.info(message, e.messageInput.toArray());
- });
- logMessages.clear();
- }
-}
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 7bbcb8f05..70e34a2bd 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -65,7 +65,7 @@ public class Cryptomator {
}
public static void main(String[] args) {
- BufferedLog.flushTo(LOG);
+ EventualLogger.getInstance().drainTo(LOG);
var printVersion = Optional.ofNullable(args) //
.stream() //Streams either one element (the args-array) or zero elements
.flatMap(Arrays::stream) //
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
new file mode 100644
index 000000000..0d3a0ffaa
--- /dev/null
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -0,0 +1,115 @@
+package org.cryptomator.launcher;
+
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+import org.slf4j.event.DefaultLoggingEvent;
+import org.slf4j.event.Level;
+import org.slf4j.event.LoggingEvent;
+import org.slf4j.helpers.AbstractLogger;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+class EventualLogger extends AbstractLogger {
+
+ static EventualLogger getInstance() {
+ return Wrapped.INSTANCE.get();
+ }
+
+
+ private final Queue bufferedEvents = new ArrayDeque<>();
+
+ private EventualLogger() {
+ }
+
+ synchronized void drainTo(Logger gutter) {
+ for (var event : bufferedEvents) {
+ gutter.atLevel(event.getLevel()).log(event.getMessage(), event.getArgumentArray());
+ }
+ bufferedEvents.clear();
+ }
+
+ @Override
+ protected synchronized void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
+ var event = new DefaultLoggingEvent(level, this);
+ if (marker != null) {
+ event.addMarker(marker);
+ }
+ event.setMessage(messagePattern);
+ for (var arg : arguments) {
+ event.addArgument(arg);
+ }
+ bufferedEvents.add(event);
+ }
+
+ //Unclear, unused and undocumented method of slf4j, see also https://github.com/qos-ch/slf4j/discussions/348
+ @Override
+ protected String getFullyQualifiedCallerName() {
+ return getClass().getCanonicalName();
+ }
+
+
+ @Override
+ public boolean isTraceEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isTraceEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isDebugEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isInfoEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled(Marker marker) {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled(Marker marker) {
+ return true;
+ }
+
+ private enum Wrapped {
+ INSTANCE;
+
+ EventualLogger actualInstance;
+
+ Wrapped() {
+ actualInstance = new EventualLogger();
+ }
+
+ public EventualLogger get() {
+ return actualInstance;
+ }
+ }
+}
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index 31b6be658..bb2ae97eb 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -1,98 +1,59 @@
package org.cryptomator.launcher;
-import com.fasterxml.jackson.databind.json.JsonMapper;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import org.mockito.Answers;
-import org.mockito.Mockito;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
-import java.util.Map;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
public class AdminPropertiesSetterTest {
- private static final Map NO_STRING_CONFIG = Map.of("list", List.of("a", "b", "c"), //
- "map", Map.of("a", 1, "b", 2));
-
- private static final Map CONFIG = Map.of("kack", "dudel", //
- "list", List.of("a", "b", "c"), //
- "map", Map.of("a", 1, "b", 2));
+ private static final String PROPS = """
+ fruit=banana
+ vegetable:kärrot
+ method=scan寧""";
@Test
- @DisplayName("Loading valid JSON")
- void loadValidJson(@TempDir Path path) throws IOException {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
- setupValidJson(configPath);
+ @DisplayName("UTF-8 is supported")
+ void loadUTF8Properties(@TempDir Path path) throws IOException {
+ var config = path.resolve("config.properties");
+ setupValidProperties(config);
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- Assertions.assertAll(CONFIG.entrySet().stream().map((e) -> //
- () -> MatcherAssert.assertThat(result, hasEntry(e.getKey(), e.getValue()))));
- }
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ Assertions.assertAll(List.of( //
+ () -> MatcherAssert.assertThat(properties, hasEntry("fruit", "banana")), //
+ () -> MatcherAssert.assertThat(properties, hasEntry("vegetable", "kärrot")), //
+ () -> MatcherAssert.assertThat(properties, hasEntry("method", "scan寧"))));
}
@Test
@DisplayName("Loading not existing file")
void loadNotExistingFile(@TempDir Path path) {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
-
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- MatcherAssert.assertThat(result, anEmptyMap());
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()));
- }
+ var config = path.resolve("config.properties");
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
}
@Test
- @DisplayName("Loading empty file")
+ @DisplayName("Loading invalid properties file")
void loadEmptyFile(@TempDir Path path) throws IOException {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod();
- var configPath = path.resolve("config.json");
- Files.createFile(configPath);
-
- var result = AdminPropertiesSetter.loadAdminProperties(configPath);
- MatcherAssert.assertThat(result, anEmptyMap());
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()));
- }
}
- void setupValidJson(Path p) throws IOException {
- var json = JsonMapper.builder().build();
+ void setupValidProperties(Path p) throws IOException {
try (var out = Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
- json.writerWithDefaultPrettyPrinter().writeValue(out, CONFIG);
+ var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ out.write(bytes);
}
}
- @Test
- @DisplayName("Keys with non-String values are ignored")
- void ignoreValues(@TempDir Path path) {
- try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS);
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(NO_STRING_CONFIG);
- adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
-
- AdminPropertiesSetter.adjustSystemProperties();
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()), Mockito.times(NO_STRING_CONFIG.size()));
- }
- }
-
-
}
From 7ee0606306e302b2c66feefc52f8303dd77ec079 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:08 +0100
Subject: [PATCH 033/100] adjust unit tests
---
.../launcher/AdminPropertiesSetter.java | 7 +++-
.../launcher/AdminPropertiesSetterTest.java | 34 ++++++++++++++-----
2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 3f35529ec..53863e4e3 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -37,6 +37,7 @@ import java.util.Set;
class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.getInstance();
+ private static final long MAX_CONFIG_SIZE_BYTES = 8192;
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
@@ -88,9 +89,13 @@ class AdminPropertiesSetter {
return systemProps;
}
- private static Properties loadAdminProperties(Path adminPropertiesPath) {
+ //visible for testing
+ static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
+ if(Files.size(adminPropertiesPath) >= MAX_CONFIG_SIZE_BYTES) {
+ throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
+ }
adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index bb2ae97eb..9e8b4962b 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -27,7 +28,10 @@ public class AdminPropertiesSetterTest {
@DisplayName("UTF-8 is supported")
void loadUTF8Properties(@TempDir Path path) throws IOException {
var config = path.resolve("config.properties");
- setupValidProperties(config);
+ try (var out = Files.newOutputStream(config, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+ var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ out.write(bytes);
+ }
var properties = AdminPropertiesSetter.loadAdminProperties(config);
Assertions.assertAll(List.of( //
@@ -37,7 +41,7 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("Loading not existing file")
+ @DisplayName("Loading not existing file returns empty properties")
void loadNotExistingFile(@TempDir Path path) {
var config = path.resolve("config.properties");
var properties = AdminPropertiesSetter.loadAdminProperties(config);
@@ -45,15 +49,29 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("Loading invalid properties file")
+ @DisplayName("Loading invalid file returns empty properties")
void loadEmptyFile(@TempDir Path path) throws IOException {
- }
-
- void setupValidProperties(Path p) throws IOException {
- try (var out = Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
- var bytes = PROPS.getBytes(StandardCharsets.UTF_8);
+ var config = path.resolve("config.properties");
+ try (var out = Files.newOutputStream(config, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+ var bytes = "method=\\u2u20".getBytes(StandardCharsets.UTF_8); //only one "u" is allowed in a Unicode escape sequence
out.write(bytes);
}
+
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
+ }
+
+ @Test
+ @DisplayName("Loading too big file returns empty properties")
+ void loadTooBigFile(@TempDir Path path) throws IOException {
+ var config = path.resolve("config.properties");
+ try (var channel = Files.newByteChannel(config, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ channel.position(10_000);
+ channel.write(ByteBuffer.wrap("test=test".getBytes()));
+ }
+
+ var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ MatcherAssert.assertThat(properties, anEmptyMap());
}
}
From b00c81c20a2bd88f3e0a399ee5652cc866c16df1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:24 +0100
Subject: [PATCH 034/100] use "cryptomator.config" for config file
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 53863e4e3..0aefb35ad 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -42,7 +42,7 @@ class AdminPropertiesSetter {
private static final String LINUX_DIR = "/etc/cryptomator";
private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
- private static final String CONFIG_NAME = "config.properties";
+ private static final String CONFIG_NAME = "cryptomator.config";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
From 35c2141fd6dfbba748ca9003f263a207159cd3e7 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:34:34 +0100
Subject: [PATCH 035/100] cleanup
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 0aefb35ad..590a68b57 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -57,7 +57,7 @@ class AdminPropertiesSetter {
adminDir = Path.of(WIN_DIR);
} else if (SystemUtils.IS_OS_MAC) {
adminDir = Path.of(MAC_DIR);
- } else { //LINUX
+ } else {
adminDir = Path.of(LINUX_DIR);
}
ADMIN_PROPERTIES_FILE = adminDir.resolve(CONFIG_NAME);
From 5cac6b81141fd45e3462f08b452d7b8d630add0f Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 15:55:04 +0100
Subject: [PATCH 036/100] also adjust distribution
---
dist/common/config.json | 3 ---
dist/common/cryptomator.config | 8 ++++++++
dist/win/resources/main.wxs | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
delete mode 100644 dist/common/config.json
create mode 100644 dist/common/cryptomator.config
diff --git a/dist/common/config.json b/dist/common/config.json
deleted file mode 100644
index d077d240e..000000000
--- a/dist/common/config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "ignored": "This is the Cryptomator administrative configuration file. For more info, read the docs at https:\\docs.cryptomator.org. This entry can be removed."
-}
\ No newline at end of file
diff --git a/dist/common/cryptomator.config b/dist/common/cryptomator.config
new file mode 100644
index 000000000..c0ab0f54f
--- /dev/null
+++ b/dist/common/cryptomator.config
@@ -0,0 +1,8 @@
+# This is the Cryptomator administrative configuration file.
+# It is a simple key-value pair file.
+# Lines starting with '#' are comments and will be ignored.
+# For more info, read the docs at https://docs.cryptomator.org.
+#
+# Example:
+# Sets the plugin directory and enables plugin loading
+# cryptomator.pluginDir=@{userhome}/Cryptomator/Plugins
\ No newline at end of file
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 143e5a1a2..0d6f0d8f1 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -116,7 +116,7 @@
-
+
From 29e76e7f93b99e08c258009380b883e04237d6c1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:07:17 +0100
Subject: [PATCH 037/100] keep throwables in EventualLogger
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 0d3a0ffaa..597b2f1e9 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -24,7 +24,9 @@ class EventualLogger extends AbstractLogger {
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
- gutter.atLevel(event.getLevel()).log(event.getMessage(), event.getArgumentArray());
+ gutter.atLevel(event.getLevel())
+ .setCause(event.getThrowable())
+ .log(event.getMessage(), event.getArgumentArray());
}
bufferedEvents.clear();
}
@@ -39,6 +41,7 @@ class EventualLogger extends AbstractLogger {
for (var arg : arguments) {
event.addArgument(arg);
}
+ event.setThrowable(throwable);
bufferedEvents.add(event);
}
From b651b9ac26d85f077bba1a8b31cad8ea214554c3 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:08:00 +0100
Subject: [PATCH 038/100] config file can be exactly the max size
---
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 590a68b57..5c1c2ed60 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -93,7 +93,7 @@ class AdminPropertiesSetter {
static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
- if(Files.size(adminPropertiesPath) >= MAX_CONFIG_SIZE_BYTES) {
+ if(Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
adminProps.load(reader);
From 300cac5441e38b3dc0a59217bc4740a9725746e7 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:36:56 +0100
Subject: [PATCH 039/100] don't forget log markers
---
.../java/org/cryptomator/launcher/EventualLogger.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 597b2f1e9..64c741725 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -24,9 +24,11 @@ class EventualLogger extends AbstractLogger {
synchronized void drainTo(Logger gutter) {
for (var event : bufferedEvents) {
- gutter.atLevel(event.getLevel())
- .setCause(event.getThrowable())
- .log(event.getMessage(), event.getArgumentArray());
+ var builder = gutter.atLevel(event.getLevel());
+ builder.setCause(event.getThrowable());
+ builder.setMessage(event.getMessage());
+ event.getArguments().forEach(builder::addArgument);
+ event.getMarkers().forEach(builder::addMarker);
}
bufferedEvents.clear();
}
From efbd107fb5b2bee1c8a26a9c50c2797a462ffe0e Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 21 Jan 2026 16:57:42 +0100
Subject: [PATCH 040/100] increase null safety
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index 64c741725..06f5e24c5 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -8,6 +8,8 @@ import org.slf4j.event.LoggingEvent;
import org.slf4j.helpers.AbstractLogger;
import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Objects;
import java.util.Queue;
class EventualLogger extends AbstractLogger {
@@ -28,7 +30,8 @@ class EventualLogger extends AbstractLogger {
builder.setCause(event.getThrowable());
builder.setMessage(event.getMessage());
event.getArguments().forEach(builder::addArgument);
- event.getMarkers().forEach(builder::addMarker);
+ Objects.requireNonNullElse(event.getMarkers(), List.of()).forEach(builder::addMarker);
+ builder.log();
}
bufferedEvents.clear();
}
@@ -40,7 +43,7 @@ class EventualLogger extends AbstractLogger {
event.addMarker(marker);
}
event.setMessage(messagePattern);
- for (var arg : arguments) {
+ for (var arg : Objects.requireNonNullElse(arguments, new Object[]{})) {
event.addArgument(arg);
}
event.setThrowable(throwable);
From aa898c634f7366c931e689a2861c2d30aacbc41e Mon Sep 17 00:00:00 2001
From: Jan-Peter Klein
Date: Fri, 23 Jan 2026 13:24:58 +0100
Subject: [PATCH 041/100] refactor recovery restore to sync logic with async
task wrapper for testability
---
.../RecoveryKeyCreationController.java | 65 ++++++++++---------
.../RecoveryKeyResetPasswordController.java | 60 +++++++++--------
2 files changed, 66 insertions(+), 59 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
index ac26fabd8..8b0e5f416 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
@@ -104,7 +104,7 @@ public class RecoveryKeyCreationController implements FxController {
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
cancelButton.setOnAction((_) -> back());
cancelButton.setText(resourceBundle.getString("generic.button.back"));
- nextButton.setOnAction((_) -> restoreWithPassword());
+ nextButton.setOnAction((_) -> restoreWithPasswordAsync());
}
}
@@ -137,8 +137,8 @@ public class RecoveryKeyCreationController implements FxController {
}
@FXML
- public void restoreWithPassword() {
- Task task = new RestoreWithPasswordTask();
+ public void restoreWithPasswordAsync() {
+ Task task = createTask(this::restoreWithPassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration with password for {}.", vault.getDisplayablePath());
@@ -175,11 +175,42 @@ public class RecoveryKeyCreationController implements FxController {
executor.submit(task);
}
+ void restoreWithPassword() throws IOException, CryptoException {
+ try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
+ Path recoveryPath = recoveryDirectory.getRecoveryPath();
+ Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
+
+ try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
+ var combo = MasterkeyService.detect(masterkey, vault.getPath())
+ .orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
+
+ CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
+ }
+
+ recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
+ }
+ }
+
@FXML
public void close() {
window.close();
}
+ @FunctionalInterface
+ private interface TaskAction {
+ void run() throws Exception;
+ }
+
+ private Task createTask(TaskAction action) {
+ return new Task() {
+ @Override
+ protected Void call() throws Exception {
+ action.run();
+ return null;
+ }
+ };
+ }
+
private class RecoveryKeyCreationTask extends Task {
private RecoveryKeyCreationTask() {
@@ -193,34 +224,6 @@ public class RecoveryKeyCreationController implements FxController {
}
- private class RestoreWithPasswordTask extends Task {
-
- private static final Logger LOG = LoggerFactory.getLogger(RestoreWithPasswordTask.class);
-
- private RestoreWithPasswordTask() {
- setOnFailed(_ -> LOG.error("Failed to restore vault configuration with password", getException()));
- }
-
- @Override
- protected Void call() throws IOException, CryptoException {
- try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
- Path recoveryPath = recoveryDirectory.getRecoveryPath();
- Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
-
- try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
- var combo = MasterkeyService.detect(masterkey, vault.getPath())
- .orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
-
- CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
- }
-
- recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
- }
- return null;
- }
-
- }
-
/* Getter/Setter */
public Vault getVault() {
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
index d904ea707..ccc7df0e3 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
@@ -117,15 +117,15 @@ public class RecoveryKeyResetPasswordController implements FxController {
@FXML
public void next() {
switch (recoverType.get()) {
- case RESTORE_ALL -> restorePassword();
+ case RESTORE_ALL -> restorePasswordAsync();
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
default -> resetPassword(); // Fallback
}
}
@FXML
- public void restorePassword() {
- Task task = new RestorePasswordTask();
+ public void restorePasswordAsync() {
+ Task task = createTask(this::restorePassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration for {}.", vault.getDisplayablePath());
@@ -156,6 +156,20 @@ public class RecoveryKeyResetPasswordController implements FxController {
executor.submit(task);
}
+ void restorePassword() throws IOException, CryptoException {
+ try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
+ Path recoveryPath = recoveryDirectory.getRecoveryPath();
+ MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
+
+ try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
+ CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
+ }
+
+ recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
+ recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
+ }
+ }
+
@FXML
public void resetPassword() {
Task task = new ResetPasswordTask();
@@ -182,6 +196,21 @@ public class RecoveryKeyResetPasswordController implements FxController {
executor.submit(task);
}
+ @FunctionalInterface
+ private interface TaskAction {
+ void run() throws Exception;
+ }
+
+ private Task createTask(TaskAction action) {
+ return new Task() {
+ @Override
+ protected Void call() throws Exception {
+ action.run();
+ return null;
+ }
+ };
+ }
+
private class ResetPasswordTask extends Task {
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
@@ -197,31 +226,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
}
}
- private class RestorePasswordTask extends Task {
-
- private static final Logger LOG = LoggerFactory.getLogger(RestorePasswordTask.class);
-
- public RestorePasswordTask() {
- setOnFailed(_ -> LOG.error("Failed to restore vault configuration", getException()));
- }
-
- @Override
- protected Void call() throws IOException, CryptoException {
- try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
- Path recoveryPath = recoveryDirectory.getRecoveryPath();
- MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
-
- try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
- CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
- }
-
- recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
- recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
- }
- return null;
- }
- }
-
/* Getter/Setter */
public ReadOnlyBooleanProperty passwordSufficientAndMatchingProperty() {
From 8b05ae0a542d5588adfc9faf4b82458d552224ea Mon Sep 17 00:00:00 2001
From: Jan-Peter Klein
Date: Fri, 23 Jan 2026 16:09:06 +0100
Subject: [PATCH 042/100] dedup createTask
---
.../RecoveryKeyCreationController.java | 17 +------------
.../RecoveryKeyResetPasswordController.java | 17 +------------
.../ui/recoverykey/RecoveryKeyTasks.java | 25 +++++++++++++++++++
3 files changed, 27 insertions(+), 32 deletions(-)
create mode 100644 src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyTasks.java
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
index 8b0e5f416..e67ca87c6 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java
@@ -138,7 +138,7 @@ public class RecoveryKeyCreationController implements FxController {
@FXML
public void restoreWithPasswordAsync() {
- Task task = createTask(this::restoreWithPassword);
+ Task task = RecoveryKeyTasks.createTask(this::restoreWithPassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration with password for {}.", vault.getDisplayablePath());
@@ -196,21 +196,6 @@ public class RecoveryKeyCreationController implements FxController {
window.close();
}
- @FunctionalInterface
- private interface TaskAction {
- void run() throws Exception;
- }
-
- private Task createTask(TaskAction action) {
- return new Task() {
- @Override
- protected Void call() throws Exception {
- action.run();
- return null;
- }
- };
- }
-
private class RecoveryKeyCreationTask extends Task {
private RecoveryKeyCreationTask() {
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
index ccc7df0e3..b57758b67 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java
@@ -125,7 +125,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
@FXML
public void restorePasswordAsync() {
- Task task = createTask(this::restorePassword);
+ Task task = RecoveryKeyTasks.createTask(this::restorePassword);
task.setOnScheduled(_ -> {
LOG.debug("Restoring vault configuration for {}.", vault.getDisplayablePath());
@@ -196,21 +196,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
executor.submit(task);
}
- @FunctionalInterface
- private interface TaskAction {
- void run() throws Exception;
- }
-
- private Task createTask(TaskAction action) {
- return new Task() {
- @Override
- protected Void call() throws Exception {
- action.run();
- return null;
- }
- };
- }
-
private class ResetPasswordTask extends Task {
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyTasks.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyTasks.java
new file mode 100644
index 000000000..ba6caf249
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyTasks.java
@@ -0,0 +1,25 @@
+package org.cryptomator.ui.recoverykey;
+
+import javafx.concurrent.Task;
+
+final class RecoveryKeyTasks {
+
+ private RecoveryKeyTasks() {
+ }
+
+ @FunctionalInterface
+ interface TaskAction {
+ void run() throws Exception;
+ }
+
+ static Task createTask(TaskAction action) {
+ return new Task() {
+ @Override
+ protected Void call() throws Exception {
+ action.run();
+ return null;
+ }
+ };
+ }
+
+}
From 2a5bce2c5c340e57b3e32c9fe9ee80ce72bb8ddb Mon Sep 17 00:00:00 2001
From: Jan-Peter Klein
Date: Tue, 27 Jan 2026 17:42:04 +0100
Subject: [PATCH 043/100] disable password tab when masterkey.file is missing
---
.../MasterkeyOptionsController.java | 27 ++++++++++++++-
.../fxml/vault_options_masterkey.fxml | 34 +++++++++++++++++--
src/main/resources/i18n/strings.properties | 2 ++
3 files changed, 59 insertions(+), 4 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
index 67ae2f42d..5303b3ae8 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
@@ -1,5 +1,6 @@
package org.cryptomator.ui.vaultoptions;
+import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
@@ -9,28 +10,38 @@ import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
+import javafx.application.Application;
+import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
+import java.nio.file.Files;
+
+import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {
+ private static final String DOCS_URL = "https://docs.cryptomator.org/desktop/"; //TODO: replace with correct docs link
+
private final Vault vault;
+ private final Lazy application;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
private final KeychainManager keychain;
private final ObservableValue passwordSaved;
+ private final BooleanProperty masterkeyFileAvailable;
@Inject
- MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) {
+ MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain, Lazy application) {
this.vault = vault;
this.window = window;
+ this.application = application;
this.changePasswordWindow = changePasswordWindow;
this.recoveryKeyWindow = recoveryKeyWindow;
this.forgetPasswordWindow = forgetPasswordWindow;
@@ -40,6 +51,7 @@ public class MasterkeyOptionsController implements FxController {
} else {
this.passwordSaved = new SimpleBooleanProperty(false);
}
+ this.masterkeyFileAvailable = new SimpleBooleanProperty(Files.exists(vault.getPath().resolve(MASTERKEY_FILENAME)));
}
@FXML
@@ -63,6 +75,11 @@ public class MasterkeyOptionsController implements FxController {
forgetPasswordWindow.vault(vault).owner(window).build().showForgetPassword();
}
+ @FXML
+ public void openDocs() {
+ application.get().getHostServices().showDocument(DOCS_URL);
+ }
+
public ObservableValue passwordSavedProperty() {
return passwordSaved;
}
@@ -70,4 +87,12 @@ public class MasterkeyOptionsController implements FxController {
public boolean isPasswordSaved() {
return passwordSaved.getValue();
}
+
+ public BooleanProperty masterkeyFileAvailableProperty() {
+ return masterkeyFileAvailable;
+ }
+
+ public boolean isMasterkeyFileAvailable() {
+ return masterkeyFileAvailable.get();
+ }
}
diff --git a/src/main/resources/fxml/vault_options_masterkey.fxml b/src/main/resources/fxml/vault_options_masterkey.fxml
index a06244178..924235642 100644
--- a/src/main/resources/fxml/vault_options_masterkey.fxml
+++ b/src/main/resources/fxml/vault_options_masterkey.fxml
@@ -3,9 +3,15 @@
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+ * To overwrite system properties, the method {@link #adjustSystemProperties()} reads the properties file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
*
* The overridable properties are:
*
@@ -38,10 +30,6 @@ class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
- private static final String LINUX_DIR = "/etc/cryptomator";
- private static final String MAC_DIR = "/Library/Application Support/Cryptomator";
- private static final String WIN_DIR = "C:\\ProgramData\\Cryptomator";
- private static final String CONFIG_NAME = "cryptomator.config";
private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfig";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
@@ -50,25 +38,6 @@ class AdminPropertiesSetter {
"cryptomator.mountPointsDir", //
"cryptomator.disableUpdateCheck");
- private static final Path ADMIN_PROPERTIES_FILE;
-
- static {
- final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
- if (systemPropertyDefinedAdminFile != null) {
- ADMIN_PROPERTIES_FILE = Path.of(systemPropertyDefinedAdminFile);
- } else {
- final Path defaultAdminDir;
- if (SystemUtils.IS_OS_WINDOWS) {
- defaultAdminDir = Path.of(WIN_DIR);
- } else if (SystemUtils.IS_OS_MAC) {
- defaultAdminDir = Path.of(MAC_DIR);
- } else {
- defaultAdminDir = Path.of(LINUX_DIR);
- }
- ADMIN_PROPERTIES_FILE = defaultAdminDir.resolve(CONFIG_NAME);
- }
- }
-
/**
* Adjusts the system properties by loading administrative properties from a predefined file location.
@@ -80,7 +49,13 @@ class AdminPropertiesSetter {
*/
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
- var adminProps = loadAdminProperties(ADMIN_PROPERTIES_FILE);
+
+ final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
+ if (systemPropertyDefinedAdminFile == null) {
+ LOG.debug("Path to admin properties file is not defined.");
+ return systemProps;
+ }
+ var adminProps = loadAdminProperties(Path.of(systemPropertyDefinedAdminFile));
for (var key : adminProps.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index f33bd4d80..e49942950 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -13,9 +13,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
+import java.util.Properties;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
public class AdminPropertiesSetterTest {
@@ -74,4 +78,19 @@ public class AdminPropertiesSetterTest {
MatcherAssert.assertThat(properties, anEmptyMap());
}
+ @Test
+ @DisplayName("If system property for config path is null, skip do not load anything")
+ void skipAdjustSystemPropertiesOnUndefinedProperty() {
+ Assertions.assertNull(System.getProperty("cryptomator.adminConfig"));
+
+ try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
+ adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
+ adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(new Properties());
+
+ AdminPropertiesSetter.adjustSystemProperties();
+
+ adminPropSetterMock.verify(() -> AdminPropertiesSetter.loadAdminProperties(any()), never());
+ }
+ }
+
}
From 53f368108ab1ee8c2ac0bbba67bc2a03d7f5bd6b Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 9 Feb 2026 16:54:04 +0100
Subject: [PATCH 058/100] Rename jvm property and actual config file
---
.github/workflows/appimage.yml | 2 +-
.github/workflows/mac-dmg-x64.yml | 2 +-
.github/workflows/mac-dmg.yml | 2 +-
.github/workflows/win-exe.yml | 2 +-
dist/linux/appimage/build.sh | 2 +-
dist/linux/debian/rules | 2 +-
dist/mac/dmg/build.sh | 2 +-
dist/win/build.ps1 | 2 +-
.../java/org/cryptomator/launcher/AdminPropertiesSetter.java | 2 +-
.../org/cryptomator/launcher/AdminPropertiesSetterTest.java | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 00eddfbe1..a1c57d91c 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -122,7 +122,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index fc995f565..d8047462e 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -128,7 +128,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 482aa61b6..9a06024a0 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -126,7 +126,7 @@ jobs:
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 9cf8eab91..23846ad5d 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -140,7 +140,7 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
- --java-options "-Dcryptomator.adminConfig=\"C:/ProgramData/Cryptomator/cryptomator.config\""
+ --java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index 847da6626..ee72b538b 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -88,7 +88,7 @@ ${JAVA_HOME}/bin/jpackage \
--app-version "${VERSION}.${REVISION_NO}" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index d4bd6c40b..0d2242f40 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -51,7 +51,7 @@ override_dh_auto_build:
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
--java-options "-Djava.net.useSystemProxies=true" \
- --java-options "-Dcryptomator.adminConfig=\"/etc/cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index 0e3be2add..438de7b9a 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -114,7 +114,7 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dapple.awt.enableTemplateImages=true" \
--java-options "-Dsun.java2d.metal=true" \
--java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \
- --java-options "-Dcryptomator.adminConfig=\"/Library/Application Support/Cryptomator/cryptomator.config\"" \
+ --java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index 35cc8e512..54ee8946e 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -155,7 +155,7 @@ $javaOptions = @(
"--java-options", "-Djava.net.useSystemProxies=true"
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
-"--java-options", "-Dcryptomator.adminConfig=`"C:/ProgramData/$AppName/cryptomator.config`""
+"--java-options", "-Dcryptomator.adminConfigPath=`"C:/ProgramData/$AppName/config.properties`""
"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 29113bec3..693ea402e 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -30,7 +30,7 @@ class AdminPropertiesSetter {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
- private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfig";
+ private static final String ADMIN_PROP_FILE_KEY = "cryptomator.adminConfigPath";
private static final Set ALLOWED_OVERRIDES = Set.of( //
"cryptomator.logDir", //
"cryptomator.pluginDir", //
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index e49942950..ef855dd5e 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -81,7 +81,7 @@ public class AdminPropertiesSetterTest {
@Test
@DisplayName("If system property for config path is null, skip do not load anything")
void skipAdjustSystemPropertiesOnUndefinedProperty() {
- Assertions.assertNull(System.getProperty("cryptomator.adminConfig"));
+ Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
From c6717bd4e1165380f4c6eb4af694f8998ce11319 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 9 Feb 2026 17:48:23 +0100
Subject: [PATCH 059/100] Rely on javafx to change color theme
---
src/main/java/module-info.java | 3 +
.../ui/fxapp/FxApplicationStyle.java | 6 ++
.../ui/fxapp/JfxUiAppearanceProvider.java | 75 +++++++++++++++++++
3 files changed, 84 insertions(+)
create mode 100644 src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 450cd4ca7..0ea231bdd 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -14,11 +14,13 @@ import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider;
import org.cryptomator.integrations.tray.TrayMenuController;
+import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.logging.LogbackConfiguratorFactory;
import org.cryptomator.networking.SSLContextProvider;
import org.cryptomator.networking.SSLContextWithMacKeychain;
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
import org.cryptomator.networking.SSLContextWithWindowsCertStore;
+import org.cryptomator.ui.fxapp.JfxUiAppearanceProvider;
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
open module org.cryptomator.desktop {
@@ -61,6 +63,7 @@ open module org.cryptomator.desktop {
uses SSLContextProvider;
uses org.cryptomator.event.NotificationHandler;
+ provides UiAppearanceProvider with JfxUiAppearanceProvider;
provides TrayMenuController with AwtTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;
provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore;
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index ee187fd65..76cb0671e 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
+import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@@ -36,6 +37,11 @@ public class FxApplicationStyle {
}
public void initialize() {
+ appearanceProvider.ifPresent(service -> {
+ if(service instanceof JfxUiAppearanceProvider fxService) {
+ fxService.setJavaFXPlatform(Platform.getPreferences());
+ }
+ });
settings.theme.addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme.get());
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
new file mode 100644
index 000000000..bf1545b4d
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -0,0 +1,75 @@
+package org.cryptomator.ui.fxapp;
+
+import org.cryptomator.integrations.common.DisplayName;
+import org.cryptomator.integrations.common.OperatingSystem;
+import org.cryptomator.integrations.common.Priority;
+import org.cryptomator.integrations.uiappearance.Theme;
+import org.cryptomator.integrations.uiappearance.UiAppearanceException;
+import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
+import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
+
+import javafx.application.ColorScheme;
+import javafx.application.Platform;
+import javafx.beans.value.ChangeListener;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+@DisplayName("JavaFX Color Scheme switcher")
+@OperatingSystem(OperatingSystem.Value.LINUX)
+@OperatingSystem(OperatingSystem.Value.WINDOWS)
+@OperatingSystem(OperatingSystem.Value.MAC)
+@Priority(1050)
+public class JfxUiAppearanceProvider implements UiAppearanceProvider {
+
+ final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
+ final AtomicReference fxPreferencesContainer = new AtomicReference<>();
+
+ public void setJavaFXPlatform(Platform.Preferences preferences) {
+ fxPreferencesContainer.set(preferences);
+ }
+
+ @Override
+ public Theme getSystemTheme() {
+ var pref = fxPreferencesContainer.get();
+ if (pref != null) {
+ return switch (pref.getColorScheme()) {
+ case DARK -> Theme.DARK;
+ case LIGHT -> Theme.LIGHT;
+ };
+ }
+ return Theme.LIGHT;
+ }
+
+ @Override
+ public void adjustToTheme(Theme theme) {
+ //no-op
+ }
+
+ @Override
+ public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
+ var pref = fxPreferencesContainer.get();
+ if (pref != null) {
+ var fxChangeListener = (ChangeListener) (_, _, newScheme) -> {
+ var newTheme = switch (newScheme) {
+ case DARK -> Theme.DARK;
+ case LIGHT -> Theme.LIGHT;
+ };
+ uiAppearanceListener.systemAppearanceChanged(newTheme);
+ };
+ uiAppearanceListeners.compute(uiAppearanceListener, (k, v) -> {
+ pref.colorSchemeProperty().addListener(fxChangeListener);
+ return fxChangeListener;
+ });
+ }
+ }
+
+ @Override
+ public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
+ var pref = fxPreferencesContainer.get();
+ var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
+ if (pref != null && fxChangeListener != null) {
+ pref.colorSchemeProperty().removeListener(fxChangeListener);
+ }
+ }
+
+}
From a4eadd481707f1718305e0c9cd3a3f4a9cc1e002 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 10 Feb 2026 17:58:29 +0100
Subject: [PATCH 060/100] remove theme change restrictions
---
.../java/org/cryptomator/common/settings/UiTheme.java | 8 --------
.../ui/preferences/InterfacePreferencesController.java | 2 +-
2 files changed, 1 insertion(+), 9 deletions(-)
diff --git a/src/main/java/org/cryptomator/common/settings/UiTheme.java b/src/main/java/org/cryptomator/common/settings/UiTheme.java
index 9c3153060..a1c510f5a 100644
--- a/src/main/java/org/cryptomator/common/settings/UiTheme.java
+++ b/src/main/java/org/cryptomator/common/settings/UiTheme.java
@@ -10,14 +10,6 @@ public enum UiTheme {
DARK("preferences.interface.theme.dark"), //
AUTOMATIC("preferences.interface.theme.automatic");
- public static UiTheme[] applicableValues() {
- if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
- return values();
- } else {
- return new UiTheme[]{LIGHT, DARK};
- }
- }
-
private final String displayName;
UiTheme(String displayName) {
diff --git a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
index 573bfc394..3eeb2f234 100644
--- a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
+++ b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
@@ -56,7 +56,7 @@ public class InterfacePreferencesController implements FxController {
@FXML
public void initialize() {
- themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
+ themeChoiceBox.getItems().addAll(UiTheme.values());
if (!themeChoiceBox.getItems().contains(settings.theme.get())) {
settings.theme.set(UiTheme.LIGHT);
}
From 46d1d605ad8a14e99006e56d9604182a2a759695 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 10 Feb 2026 18:01:36 +0100
Subject: [PATCH 061/100] refactor JfxUiAppearanceProvider class
* use delegate pattern for initialization
* add logging
---
.../ui/fxapp/FxApplicationStyle.java | 2 +-
.../ui/fxapp/JfxUiAppearanceProvider.java | 92 ++++++++++++++-----
2 files changed, 69 insertions(+), 25 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index 76cb0671e..d8b7f540d 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -39,7 +39,7 @@ public class FxApplicationStyle {
public void initialize() {
appearanceProvider.ifPresent(service -> {
if(service instanceof JfxUiAppearanceProvider fxService) {
- fxService.setJavaFXPlatform(Platform.getPreferences());
+ fxService.initialize(Platform.getPreferences());
}
});
settings.theme.addListener(this::appThemeChanged);
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index bf1545b4d..151353ff2 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -7,6 +7,8 @@ import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javafx.application.ColorScheme;
import javafx.application.Platform;
@@ -21,34 +23,39 @@ import java.util.concurrent.atomic.AtomicReference;
@Priority(1050)
public class JfxUiAppearanceProvider implements UiAppearanceProvider {
- final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
- final AtomicReference fxPreferencesContainer = new AtomicReference<>();
+ private static final Logger LOG = LoggerFactory.getLogger(JfxUiAppearanceProvider.class);
- public void setJavaFXPlatform(Platform.Preferences preferences) {
- fxPreferencesContainer.set(preferences);
+ private final AtomicReference realImpl = new AtomicReference<>(null);
+
+ public void initialize(Platform.Preferences preferences) {
+ realImpl.compareAndSet(null, new JfxUiAppearanceImpl(preferences));
+ LOG.debug("Initialized {} with JavaFX preferences", JfxUiAppearanceImpl.class);
}
- @Override
- public Theme getSystemTheme() {
- var pref = fxPreferencesContainer.get();
- if (pref != null) {
- return switch (pref.getColorScheme()) {
+ private static class JfxUiAppearanceImpl implements UiAppearanceProvider {
+
+ private final Platform.Preferences preferences;
+ private final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
+
+ private JfxUiAppearanceImpl(Platform.Preferences preferences) {
+ this.preferences = preferences;
+ }
+
+ @Override
+ public Theme getSystemTheme() {
+ return switch (preferences.getColorScheme()) {
case DARK -> Theme.DARK;
case LIGHT -> Theme.LIGHT;
};
}
- return Theme.LIGHT;
- }
- @Override
- public void adjustToTheme(Theme theme) {
- //no-op
- }
+ @Override
+ public void adjustToTheme(Theme theme) {
+ //no-op
+ }
- @Override
- public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var pref = fxPreferencesContainer.get();
- if (pref != null) {
+ @Override
+ public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
var fxChangeListener = (ChangeListener) (_, _, newScheme) -> {
var newTheme = switch (newScheme) {
case DARK -> Theme.DARK;
@@ -56,19 +63,56 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
};
uiAppearanceListener.systemAppearanceChanged(newTheme);
};
+ LOG.debug("Register listener for OS theme changes");
uiAppearanceListeners.compute(uiAppearanceListener, (k, v) -> {
- pref.colorSchemeProperty().addListener(fxChangeListener);
+ Platform.runLater(() -> preferences.colorSchemeProperty().addListener(fxChangeListener));
return fxChangeListener;
});
}
+
+ @Override
+ public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
+ var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
+ if (fxChangeListener != null) {
+ LOG.debug("Removing listener for OS theme changes");
+ Platform.runLater(() -> preferences.colorSchemeProperty().removeListener(fxChangeListener));
+ }
+ }
+ }
+
+
+ //just delegate methods
+ @Override
+ public Theme getSystemTheme() {
+ var impl = realImpl.get();
+ if (impl != null) {
+ return impl.getSystemTheme();
+ } else {
+ return Theme.LIGHT;
+ }
+ }
+
+ @Override
+ public void adjustToTheme(Theme theme) {
+ var impl = realImpl.get();
+ if (impl != null) {
+ impl.getSystemTheme();
+ }
+ }
+
+ @Override
+ public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
+ var impl = realImpl.get();
+ if (impl != null) {
+ impl.addListener(uiAppearanceListener);
+ }
}
@Override
public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var pref = fxPreferencesContainer.get();
- var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
- if (pref != null && fxChangeListener != null) {
- pref.colorSchemeProperty().removeListener(fxChangeListener);
+ var impl = realImpl.get();
+ if (impl != null) {
+ impl.removeListener(uiAppearanceListener);
}
}
From e3433cb312b5128d994132448ae6d2ba969f3236 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 10 Feb 2026 18:17:09 +0100
Subject: [PATCH 062/100] Refactor FxApplicationStyle class
---
.../ChooseExistingVaultController.java | 2 +-
.../ui/fxapp/FxApplicationStyle.java | 106 ++++++++----------
2 files changed, 49 insertions(+), 59 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
index be9ea15c7..7862c3b20 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java
@@ -59,7 +59,7 @@ public class ChooseExistingVaultController implements FxController {
this.vault = vault;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
- this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
+ this.screenshot = applicationStyle.appliedAppThemeProperty().map(this::selectScreenshot);
}
private Image selectScreenshot(Theme theme) {
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index d8b7f540d..cf524b449 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -38,86 +38,76 @@ public class FxApplicationStyle {
public void initialize() {
appearanceProvider.ifPresent(service -> {
- if(service instanceof JfxUiAppearanceProvider fxService) {
+ if (service instanceof JfxUiAppearanceProvider fxService) {
fxService.initialize(Platform.getPreferences());
}
});
+ applyTheme(settings.theme.get());
settings.theme.addListener(this::appThemeChanged);
- loadSelectedStyleSheet(settings.theme.get());
}
- private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
- if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
+ private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, UiTheme oldValue, UiTheme newValue) {
+ if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
+ appearanceProvider.ifPresent(service -> {
+ try {
+ service.removeListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.warn("Failed to disable automatic theme switching.");
+ }
+ });
+ }
+ applyTheme(newValue);
+ }
+
+ private void applyTheme(UiTheme theme) {
+ if (!licenseHolder.isValidLicense()) {
+ loadAndApplyLightTheme();
+ } else {
+ switch (theme) {
+ case AUTOMATIC -> registerAutomaticThemeChange();
+ case LIGHT -> loadAndApplyLightTheme();
+ case DARK -> loadAndApplyDarkTheme();
+ }
+ }
+ }
+
+ private void registerAutomaticThemeChange() {
+ appearanceProvider.ifPresent(provider -> {
try {
- appearanceProvider.get().removeListener(systemInterfaceThemeListener);
+ provider.addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
- LOG.error("Failed to disable automatic theme switching.");
+ LOG.error("Failed to enable automatic theme switching.");
}
- }
- loadSelectedStyleSheet(newValue);
+ systemInterfaceThemeChanged(provider.getSystemTheme());
+ });
}
- private void loadSelectedStyleSheet(UiTheme desiredTheme) {
- UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
- switch (theme) {
- case LIGHT -> applyLightTheme();
- case DARK -> applyDarkTheme();
- case AUTOMATIC -> {
- appearanceProvider.ifPresent(provider -> {
- try {
- provider.addListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.error("Failed to enable automatic theme switching.");
- }
- });
- applySystemTheme();
- }
+ private void systemInterfaceThemeChanged(Theme osTheme) {
+ switch (osTheme) {
+ case LIGHT -> loadAndApplyLightTheme();
+ case DARK -> loadAndApplyDarkTheme();
}
}
- private void systemInterfaceThemeChanged(Theme theme) {
- switch (theme) {
- case LIGHT -> applyLightTheme();
- case DARK -> applyDarkTheme();
- }
+ private void loadAndApplyLightTheme() {
+ loadAndApplyTheme(Theme.LIGHT, "/css/light_theme.css");
}
- private void applySystemTheme() {
- if (appearanceProvider.isPresent()) {
- systemInterfaceThemeChanged(appearanceProvider.get().getSystemTheme());
- } else {
- LOG.warn("No UiAppearanceProvider present, assuming LIGHT theme...");
- applyLightTheme();
- }
+ private void loadAndApplyDarkTheme() {
+ loadAndApplyTheme(Theme.DARK, "/css/dark_theme.css");
}
- private void applyLightTheme() {
- var stylesheet = Optional //
- .ofNullable(getClass().getResource("/css/light_theme.bss")) //
- .orElse(getClass().getResource("/css/light_theme.css"));
+ private void loadAndApplyTheme(Theme appTheme, String cssFile) {
+ var stylesheet = getClass().getResource(cssFile);
if (stylesheet == null) {
- LOG.warn("Failed to load light_theme stylesheet");
- } else {
- Application.setUserAgentStylesheet(stylesheet.toString());
- appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.LIGHT));
- appliedTheme.set(Theme.LIGHT);
+ throw new IllegalStateException("Cannot find resource %s".formatted(cssFile));
}
+ Application.setUserAgentStylesheet(stylesheet.toString());
+ appearanceProvider.ifPresent(provider -> provider.adjustToTheme(appTheme));
+ appliedTheme.set(appTheme);
}
- private void applyDarkTheme() {
- var stylesheet = Optional //
- .ofNullable(getClass().getResource("/css/dark_theme.bss")) //
- .orElse(getClass().getResource("/css/dark_theme.css"));
- if (stylesheet == null) {
- LOG.warn("Failed to load dark_theme stylesheet");
- } else {
- Application.setUserAgentStylesheet(stylesheet.toString());
- appearanceProvider.ifPresent(provider -> provider.adjustToTheme(Theme.DARK));
- appliedTheme.set(Theme.DARK);
- }
- }
-
- public ObjectProperty appliedThemeProperty() {
+ public ObjectProperty appliedAppThemeProperty() {
return appliedTheme;
}
}
From bdfd22c4831c712ff72d15335e5e9a446db5fde9 Mon Sep 17 00:00:00 2001
From: Ralph Plawetzki
Date: Wed, 11 Feb 2026 06:07:01 +0100
Subject: [PATCH 063/100] Partially reverts
https://github.com/cryptomator/cryptomator/commit/dd1af8cd783589e8c8b06e7c4881195e10a67815
Not needed anymore, see
https://github.com/cryptomator/integrations-linux/pull/125#issuecomment-3876060158
---
src/main/java/org/cryptomator/common/settings/Settings.java | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java
index a711e6536..efce23fef 100644
--- a/src/main/java/org/cryptomator/common/settings/Settings.java
+++ b/src/main/java/org/cryptomator/common/settings/Settings.java
@@ -152,11 +152,6 @@ public class Settings {
@SuppressWarnings("deprecation")
private void migrateLegacySettings(SettingsJson json) {
- // migrate renamed keychainAccess
- if(this.keychainProvider.getValueSafe().equals("org.cryptomator.linux.keychain.SecretServiceKeychainAccess")) {
- this.keychainProvider.setValue("org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess");
- }
-
// implicit migration of 1.6.x legacy setting "preferredVolumeImpl":
if (this.mountService.get() == null && json.preferredVolumeImpl != null) {
this.mountService.set(switch (json.preferredVolumeImpl) {
From b85780eae9f00964ee6fd88c6db5e0aa5ccda0d9 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 12:22:22 +0100
Subject: [PATCH 064/100] Disable JavaFX based UiAppearanceProvider for macOS
---
.../java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index 151353ff2..b9de27dc0 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference;
@DisplayName("JavaFX Color Scheme switcher")
@OperatingSystem(OperatingSystem.Value.LINUX)
@OperatingSystem(OperatingSystem.Value.WINDOWS)
-@OperatingSystem(OperatingSystem.Value.MAC)
@Priority(1050)
public class JfxUiAppearanceProvider implements UiAppearanceProvider {
From 5db05d8bc75a38f2bcf1b2ee0b1f547a1f0d6d1d Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 14:33:01 +0100
Subject: [PATCH 065/100] Apply suggestions from codereview
---
dist/win/resources/main.wxs | 13 +++----------
.../launcher/AdminPropertiesSetter.java | 14 ++++++++------
2 files changed, 11 insertions(+), 16 deletions(-)
diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs
index 9cd56f746..ac5e6a3d1 100644
--- a/dist/win/resources/main.wxs
+++ b/dist/win/resources/main.wxs
@@ -103,16 +103,9 @@
-
-
-
+
+
+
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 693ea402e..127d5b263 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -3,10 +3,13 @@ package org.cryptomator.launcher;
import org.slf4j.Logger;
import java.io.IOException;
+import java.io.Reader;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.Properties;
import java.util.Set;
@@ -72,13 +75,12 @@ class AdminPropertiesSetter {
//visible for testing
static Properties loadAdminProperties(Path adminPropertiesPath) {
var adminProps = new Properties();
- try {
- if (Files.size(adminPropertiesPath) > MAX_CONFIG_SIZE_BYTES) {
+ try (FileChannel ch = FileChannel.open(adminPropertiesPath, StandardOpenOption.READ); //
+ Reader reader = Channels.newReader(ch, StandardCharsets.UTF_8)) {
+ if (ch.size() > MAX_CONFIG_SIZE_BYTES) {
throw new IOException("Config file %s exceeds maximum size of %d".formatted(adminPropertiesPath, MAX_CONFIG_SIZE_BYTES));
}
- try (var reader = Files.newBufferedReader(adminPropertiesPath, StandardCharsets.UTF_8)) {
- adminProps.load(reader);
- }
+ adminProps.load(reader);
} catch (NoSuchFileException _) {
//NO-OP
LOG.debug("No admin properties found at {}.", adminPropertiesPath);
From cff47b1c7389ee96de106cd98ac0d365ae3d1c97 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 14:36:55 +0100
Subject: [PATCH 066/100] wrap system properties instead of direct modification
---
.../launcher/AdminPropertiesSetter.java | 16 +++++++++-------
.../launcher/AdminPropertiesSetterTest.java | 2 +-
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
index 127d5b263..17c52025e 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
@@ -53,23 +53,25 @@ class AdminPropertiesSetter {
static Properties adjustSystemProperties() {
var systemProps = System.getProperties();
- final String systemPropertyDefinedAdminFile = System.getProperty(ADMIN_PROP_FILE_KEY);
- if (systemPropertyDefinedAdminFile == null) {
+ final String adminCfgPath = System.getProperty(ADMIN_PROP_FILE_KEY);
+ if (adminCfgPath == null) {
LOG.debug("Path to admin properties file is not defined.");
return systemProps;
}
- var adminProps = loadAdminProperties(Path.of(systemPropertyDefinedAdminFile));
+ var adminProps = loadAdminProperties(Path.of(adminCfgPath));
+ var newSystemProps = new Properties(systemProps);
for (var key : adminProps.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
var value = adminProps.getProperty(key);
- LOG.info("Overwriting {} with value {} from admin properties.", key, value);
- systemProps.setProperty(key, value);
+ LOG.info("Overwriting {} with value {} from admin config.", key, value);
+ newSystemProps.setProperty(key, value);
} else {
- LOG.debug("Property {} in admin properties is not supported for override.", key);
+ LOG.debug("Property {} in admin config is not supported for override.", key);
}
}
- return systemProps;
+ System.setProperties(newSystemProps);
+ return newSystemProps;
}
//visible for testing
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
index ef855dd5e..ae1d940f3 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
@@ -79,7 +79,7 @@ public class AdminPropertiesSetterTest {
}
@Test
- @DisplayName("If system property for config path is null, skip do not load anything")
+ @DisplayName("If system property for config path is null, skip loading and replacing")
void skipAdjustSystemPropertiesOnUndefinedProperty() {
Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
From fb54d96997a92467cacd9b095125ce5130727561 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 14:49:56 +0100
Subject: [PATCH 067/100] fix wrong method delegation
---
.../java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index b9de27dc0..a7ac9163e 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -95,7 +95,7 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
public void adjustToTheme(Theme theme) {
var impl = realImpl.get();
if (impl != null) {
- impl.getSystemTheme();
+ impl.adjustToTheme(theme);
}
}
From 33851a85593427eeb0cdbf58b9757c77c7eea6d6 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:12:52 +0100
Subject: [PATCH 068/100] Refactor admin props
* rename class to AdminPropertiesFactory
* rename factory method to "create"
* remove side effects from methods
* returned properties default to system properties
---
...etter.java => AdminPropertiesFactory.java} | 38 ++++++++++---------
.../org/cryptomator/launcher/Cryptomator.java | 4 +-
...t.java => AdminPropertiesFactoryTest.java} | 25 ++++++------
3 files changed, 35 insertions(+), 32 deletions(-)
rename src/main/java/org/cryptomator/launcher/{AdminPropertiesSetter.java => AdminPropertiesFactory.java} (62%)
rename src/test/java/org/cryptomator/launcher/{AdminPropertiesSetterTest.java => AdminPropertiesFactoryTest.java} (74%)
diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
similarity index 62%
rename from src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
rename to src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
index 17c52025e..6a77f31cd 100644
--- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java
+++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesFactory.java
@@ -14,9 +14,11 @@ import java.util.Properties;
import java.util.Set;
/**
- * Class to overwrite system properties with an external properties file
+ * Factory to generate admin properties.
+ *
*
- * To overwrite system properties, the method {@link #adjustSystemProperties()} reads the properties file defined in the property {@value #ADMIN_PROP_FILE_KEY} and writes all supported properties to the {@link System} properties.
+ * Admin properties are {@link Properties} using system properties as defaults, but allow overwriting a specific set of properties with an external config file.
+ * Those properties are created by calling {@link #create()}. The method first reads system property {@value #ADMIN_PROP_FILE_KEY}. If it contains a path to a valid properties file, all overridable properties from the file are loaded into the returned admin properties.
*
* The overridable properties are:
*
@@ -27,9 +29,10 @@ import java.util.Set;
*
cryptomator.disableUpdateCheck
*
*
+ * @see Properties
* @see System#getProperties()
*/
-class AdminPropertiesSetter {
+class AdminPropertiesFactory {
private static final Logger LOG = EventualLogger.INSTANCE;
private static final long MAX_CONFIG_SIZE_BYTES = 8192;
@@ -43,39 +46,38 @@ class AdminPropertiesSetter {
/**
- * Adjusts the system properties by loading administrative properties from a predefined file location.
+ * Creates new {@link Properties} containing overridable properties from the admin config.
*
- * If the file exists and is a valid properties file, its content will overwrite existing system properties.
- * Only some properties can be overridden, see {@link AdminPropertiesSetter}
+ * The returned properties object uses as default the {@link System} properties.
+ * For a list of overridable properties, see {@link AdminPropertiesFactory}
*
- * @return The adjusted system properties.
+ * @return {@link Properties} containing overridable properties from the admin config and defaulting to system properties.
*/
- static Properties adjustSystemProperties() {
+ static Properties create() {
var systemProps = System.getProperties();
+ var adminProps = new Properties(systemProps);
final String adminCfgPath = System.getProperty(ADMIN_PROP_FILE_KEY);
if (adminCfgPath == null) {
- LOG.debug("Path to admin properties file is not defined.");
- return systemProps;
+ LOG.debug("Admin config property is not defined. Skipping.");
+ return adminProps;
}
- var adminProps = loadAdminProperties(Path.of(adminCfgPath));
+ var propsFromFile = loadPropertiesFromFile(Path.of(adminCfgPath));
- var newSystemProps = new Properties(systemProps);
- for (var key : adminProps.stringPropertyNames()) {
+ for (var key : propsFromFile.stringPropertyNames()) {
if (ALLOWED_OVERRIDES.contains(key)) {
- var value = adminProps.getProperty(key);
+ var value = propsFromFile.getProperty(key);
LOG.info("Overwriting {} with value {} from admin config.", key, value);
- newSystemProps.setProperty(key, value);
+ adminProps.setProperty(key, value);
} else {
LOG.debug("Property {} in admin config is not supported for override.", key);
}
}
- System.setProperties(newSystemProps);
- return newSystemProps;
+ return adminProps;
}
//visible for testing
- static Properties loadAdminProperties(Path adminPropertiesPath) {
+ static Properties loadPropertiesFromFile(Path adminPropertiesPath) {
var adminProps = new Properties();
try (FileChannel ch = FileChannel.open(adminPropertiesPath, StandardOpenOption.READ); //
Reader reader = Channels.newReader(ch, StandardCharsets.UTF_8)) {
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 855ef6e1d..4a6d0750d 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -35,8 +35,8 @@ public class Cryptomator {
private static final long STARTUP_TIME = System.currentTimeMillis();
static {
- var adjustedSystemProps = AdminPropertiesSetter.adjustSystemProperties();
- var lazyProcessedProps = new SubstitutingProperties(adjustedSystemProps, System.getenv());
+ var adminProps = AdminPropertiesFactory.create();
+ var lazyProcessedProps = new SubstitutingProperties(adminProps, System.getenv());
System.setProperties(lazyProcessedProps);
CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
LOG = LoggerFactory.getLogger(Cryptomator.class);
diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
similarity index 74%
rename from src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
rename to src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
index ae1d940f3..d75dc7fba 100644
--- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java
+++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesFactoryTest.java
@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
-public class AdminPropertiesSetterTest {
+public class AdminPropertiesFactoryTest {
private static final String PROPS = """
fruit=banana
@@ -37,7 +37,7 @@ public class AdminPropertiesSetterTest {
out.write(bytes);
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
Assertions.assertAll(List.of( //
() -> MatcherAssert.assertThat(properties, hasEntry("fruit", "banana")), //
() -> MatcherAssert.assertThat(properties, hasEntry("vegetable", "kärrot")), //
@@ -48,7 +48,7 @@ public class AdminPropertiesSetterTest {
@DisplayName("Loading not existing file returns empty properties")
void loadNotExistingFile(@TempDir Path path) {
var config = path.resolve("config.properties");
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@@ -61,7 +61,7 @@ public class AdminPropertiesSetterTest {
out.write(bytes);
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@@ -74,22 +74,23 @@ public class AdminPropertiesSetterTest {
channel.write(ByteBuffer.wrap("test=test".getBytes()));
}
- var properties = AdminPropertiesSetter.loadAdminProperties(config);
+ var properties = AdminPropertiesFactory.loadPropertiesFromFile(config);
MatcherAssert.assertThat(properties, anEmptyMap());
}
@Test
- @DisplayName("If system property for config path is null, skip loading and replacing")
- void skipAdjustSystemPropertiesOnUndefinedProperty() {
+ @DisplayName("If system properties do not contain config path, skip loading")
+ void skipLoadIfFilePathIsNotDefined() {
Assertions.assertNull(System.getProperty("cryptomator.adminConfigPath"));
- try (var adminPropSetterMock = mockStatic(AdminPropertiesSetter.class)) {
- adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod();
- adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(new Properties());
+ try (var adminPropSetterMock = mockStatic(AdminPropertiesFactory.class)) {
+ adminPropSetterMock.when(AdminPropertiesFactory::create).thenCallRealMethod();
+ adminPropSetterMock.when(() -> AdminPropertiesFactory.loadPropertiesFromFile(any())).thenReturn(new Properties());
- AdminPropertiesSetter.adjustSystemProperties();
+ var adminProps = AdminPropertiesFactory.create();
- adminPropSetterMock.verify(() -> AdminPropertiesSetter.loadAdminProperties(any()), never());
+ adminPropSetterMock.verify(() -> AdminPropertiesFactory.loadPropertiesFromFile(any()), never());
+ Assertions.assertEquals(System.getProperty("user.home"), adminProps.getProperty("user.home"));
}
}
From 34e5d19a0465ad7b28490dc95ed7cc5625444edf Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:16:11 +0100
Subject: [PATCH 069/100] prevent resource leak
---
.../java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index a7ac9163e..ca5bec7d4 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -63,7 +63,7 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
uiAppearanceListener.systemAppearanceChanged(newTheme);
};
LOG.debug("Register listener for OS theme changes");
- uiAppearanceListeners.compute(uiAppearanceListener, (k, v) -> {
+ uiAppearanceListeners.computeIfAbsent(uiAppearanceListener, k -> {
Platform.runLater(() -> preferences.colorSchemeProperty().addListener(fxChangeListener));
return fxChangeListener;
});
From 2a5ef5d99926088818505412c93854aed275f874 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:21:55 +0100
Subject: [PATCH 070/100] log inconsistent state
---
.../ui/fxapp/FxApplicationStyle.java | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index cf524b449..6a7ef934c 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -72,14 +72,15 @@ public class FxApplicationStyle {
}
private void registerAutomaticThemeChange() {
- appearanceProvider.ifPresent(provider -> {
- try {
- provider.addListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.error("Failed to enable automatic theme switching.");
- }
- systemInterfaceThemeChanged(provider.getSystemTheme());
- });
+ appearanceProvider.ifPresentOrElse(provider -> {
+ try {
+ provider.addListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.error("Failed to enable automatic theme switching.");
+ }
+ systemInterfaceThemeChanged(provider.getSystemTheme());
+ }, //
+ () -> LOG.warn("UI theme AUTOMATIC selected, but no supported UiAppearanceProvider present"));
}
private void systemInterfaceThemeChanged(Theme osTheme) {
From 99898b74fb971c16f5a54564fd7188dd10634d2b Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:55:19 +0100
Subject: [PATCH 071/100] minor cleanup
---
.../org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index ca5bec7d4..c121c38d5 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -27,8 +27,10 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
private final AtomicReference realImpl = new AtomicReference<>(null);
public void initialize(Platform.Preferences preferences) {
- realImpl.compareAndSet(null, new JfxUiAppearanceImpl(preferences));
- LOG.debug("Initialized {} with JavaFX preferences", JfxUiAppearanceImpl.class);
+ var isSet = realImpl.compareAndSet(null, new JfxUiAppearanceImpl(preferences));
+ if (isSet) {
+ LOG.debug("Initialized {} with JavaFX preferences", JfxUiAppearanceImpl.class);
+ }
}
private static class JfxUiAppearanceImpl implements UiAppearanceProvider {
From 1629eae5d368c47e5fbf08b27b0bfb56534e1764 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 11 Feb 2026 15:55:34 +0100
Subject: [PATCH 072/100] [skip ci] update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10f1135e8..2a3cabfa7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility: Adjust app to be used with a screen reader ([#547](https://github.com/cryptomator/cryptomator/issues/547))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
+* Automatic app color scheme selection according to OS ([#4134](https://github.com/cryptomator/cryptomator/pull/4134))
### Changed
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
From a77e90738ac6a401cf21e54798035135f9f72b3e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 16 Feb 2026 06:06:54 +0000
Subject: [PATCH 073/100] Bump the java-production-dependencies group across 1
directory with 11 updates
Bumps the java-production-dependencies group with 11 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [org.cryptomator:cryptolib](https://github.com/cryptomator/cryptolib) | `2.2.1` | `2.2.2` |
| [org.cryptomator:fuse-nio-adapter](https://github.com/cryptomator/fuse-nio-adapter) | `5.1.0` | `6.0.0` |
| [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) | `1.5.19` | `1.5.31` |
| [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.5.19` | `1.5.31` |
| org.apache.commons:commons-lang3 | `3.19.0` | `3.20.0` |
| [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.20.0` | `2.21.0` |
| com.fasterxml.jackson.datatype:jackson-datatype-jsr310 | `2.20.0` | `2.21.0` |
| [com.google.dagger:dagger](https://github.com/google/dagger) | `2.57.2` | `2.59.1` |
| [com.google.dagger:dagger-compiler](https://github.com/google/dagger) | `2.57.2` | `2.59.1` |
| [com.github.ben-manes.caffeine:caffeine](https://github.com/ben-manes/caffeine) | `3.2.2` | `3.2.3` |
| [org.cryptomator:integrations-linux](https://github.com/cryptomator/integrations-linux) | `1.7.0-beta3` | `1.7.0-beta4` |
Updates `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
- [Release notes](https://github.com/cryptomator/cryptolib/releases)
- [Changelog](https://github.com/cryptomator/cryptolib/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/cryptolib/compare/2.2.1...2.2.2)
Updates `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
- [Release notes](https://github.com/cryptomator/fuse-nio-adapter/releases)
- [Changelog](https://github.com/cryptomator/fuse-nio-adapter/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/fuse-nio-adapter/compare/5.1.0...6.0.0)
Updates `ch.qos.logback:logback-core` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)
Updates `ch.qos.logback:logback-classic` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)
Updates `ch.qos.logback:logback-classic` from 1.5.19 to 1.5.31
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.19...v_1.5.31)
Updates `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
- [Commits](https://github.com/FasterXML/jackson/commits)
Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
Updates `com.google.dagger:dagger` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)
Updates `com.google.dagger:dagger-compiler` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)
Updates `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
- [Release notes](https://github.com/ben-manes/caffeine/releases)
- [Commits](https://github.com/ben-manes/caffeine/compare/v3.2.2...v3.2.3)
Updates `com.google.dagger:dagger-compiler` from 2.57.2 to 2.59.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.2...dagger-2.59.1)
Updates `org.cryptomator:integrations-linux` from 1.7.0-beta3 to 1.7.0-beta4
- [Release notes](https://github.com/cryptomator/integrations-linux/releases)
- [Changelog](https://github.com/cryptomator/integrations-linux/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cryptomator/integrations-linux/compare/1.7.0-beta3...1.7.0-beta4)
---
updated-dependencies:
- dependency-name: org.cryptomator:cryptolib
dependency-version: 2.2.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:fuse-nio-adapter
dependency-version: 6.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-core
dependency-version: 1.5.31
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
dependency-version: 1.5.31
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
- dependency-name: ch.qos.logback:logback-classic
dependency-version: 1.5.31
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
- dependency-name: org.apache.commons:commons-lang3
dependency-version: 3.20.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.core:jackson-databind
dependency-version: 2.21.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
dependency-version: 2.21.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
dependency-version: 2.21.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger
dependency-version: 2.59.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger-compiler
dependency-version: 2.59.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: com.github.ben-manes.caffeine:caffeine
dependency-version: 3.2.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
- dependency-name: com.google.dagger:dagger-compiler
dependency-version: 2.59.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: java-production-dependencies
- dependency-name: org.cryptomator:integrations-linux
dependency-version: 1.7.0-beta4
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: java-production-dependencies
...
Signed-off-by: dependabot[bot]
---
pom.xml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/pom.xml b/pom.xml
index b91496be0..f5b50628f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,19 +37,19 @@
1.8.0-beta11.5.11.5.0-beta3
- 1.7.0-beta3
- 5.1.0
+ 1.7.0-beta4
+ 6.0.03.0.0
- 3.19.0
- 2.57.2
+ 3.20.0
+ 2.59.12.2
- 2.20.0
+ 2.21.0254.5.010.5
- 1.5.19
+ 1.5.312.0.170.8.11.9.0
@@ -94,7 +94,7 @@
org.cryptomatorcryptolib
- 2.2.1
+ 2.2.2org.cryptomator
@@ -228,7 +228,7 @@
com.github.ben-manes.caffeinecaffeine
- 3.2.2
+ 3.2.3
From 0b4d86768a292b45e5578ceb503a0ca767b948cc Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 10:13:21 +0100
Subject: [PATCH 074/100] Closes #4088
---
.../ui/notification/NotificationController.java | 9 ++-------
src/main/resources/i18n/strings.properties | 6 +++---
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationController.java b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
index fe96e3721..8c2bb1520 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationController.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
@@ -101,15 +101,10 @@ public class NotificationController implements FxController {
var device = userAndDevice.length == 1 ? userAndDevice[0] : userAndDevice[1];
var cleartextFileName = fiiue.cleartextPath().substring(fiiue.cleartextPath().lastIndexOf('/') + 1);
eventTimestamp.set(localizedTimeFormatter.format(fiiue.lastUpdated()));
- message.set("File is locked by another device");
- fileName.set(cleartextFileName);
- description.set("The file is opened by %s on device %s. Ask the user to close the file and sync again. Otherwise, you can ignore the lock and open it anyway.".formatted(user, device));
- actionText.set("Ignore Lock");
- /* TODO: Once feature is out of beta, activate translations
message.set(resourceBundle.getString("notification.inUse.message"));
- description.set(resourceBundle.getString("notification.inUse.description").formatted(fiiue.cleartextPath(), user, device));
+ fileName.set(cleartextFileName);
+ description.set(resourceBundle.getString("notification.inUse.description").formatted(user, device));
actionText.set(resourceBundle.getString("notification.inUse.action"));
- */
}
default -> {
message.set("NO CONTENT");
diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties
index 59403cc3d..e3595be48 100644
--- a/src/main/resources/i18n/strings.properties
+++ b/src/main/resources/i18n/strings.properties
@@ -723,6 +723,6 @@ eventView.entry.inUse.ignoreLock=Ignore Lock
# Notifications
## FileIsInUse Notification
-#notification.inUse.message=File is locked
-#notification.inUse.description=File %s is opened by user %s (%s). Ask the user to close the file. Otherwise, you can ignore the lock and open it anyway, but be aware of the data loss risk.
-#notification.inUse.action=Ignore Lock
\ No newline at end of file
+notification.inUse.message=File is locked by another device
+notification.inUse.description=The file is opened by %s on device %s. Ask the user to close the file and sync again. Otherwise, you can ignore the lock and open it anyway.
+notification.inUse.action=Ignore Lock
\ No newline at end of file
From cf0052b4f5627413ed073d44c4ef4f3df5fafa91 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 11:07:04 +0100
Subject: [PATCH 075/100] revert lambda processing
---
.../cryptomator/ui/fxapp/FxApplicationStyle.java | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index 6a7ef934c..ec8967340 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -47,14 +47,12 @@ public class FxApplicationStyle {
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, UiTheme oldValue, UiTheme newValue) {
- if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
- appearanceProvider.ifPresent(service -> {
- try {
- service.removeListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.warn("Failed to disable automatic theme switching.");
- }
- });
+ if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC && appearanceProvider.isPresent()) {
+ try {
+ appearanceProvider.get().removeListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.warn("Failed to disable automatic theme switching.");
+ }
}
applyTheme(newValue);
}
From 83ef9d06d9a60b919dddea372cdc6e4eabd8a231 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 11:55:03 +0100
Subject: [PATCH 076/100] simplify JfxUiAppearanceProvider
and move loading of appearance service into the fx app
---
.../launcher/CryptomatorModule.java | 6 --
.../ui/fxapp/FxApplicationModule.java | 10 +-
.../ui/fxapp/FxApplicationStyle.java | 5 -
.../ui/fxapp/JfxUiAppearanceProvider.java | 101 +++++-------------
4 files changed, 34 insertions(+), 88 deletions(-)
diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
index 42e908df2..5ff8d63ee 100644
--- a/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
+++ b/src/main/java/org/cryptomator/launcher/CryptomatorModule.java
@@ -4,7 +4,6 @@ import dagger.Module;
import dagger.Provides;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
-import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import javax.inject.Named;
@@ -30,11 +29,6 @@ class CryptomatorModule {
return new ArrayBlockingQueue<>(10);
}
- @Provides
- @Singleton
- static Optional provideAppearanceProvider() {
- return UiAppearanceProvider.get();
- }
@Provides
@Singleton
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
index 80a261bb8..6b19429b2 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
@@ -7,6 +7,7 @@ package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
+import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.decryptname.DecryptNameComponent;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent;
@@ -26,6 +27,7 @@ import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Optional;
@Module(subcomponents = {TrayMenuComponent.class, //
DecryptNameComponent.class, //
@@ -41,7 +43,7 @@ import java.io.InputStream;
ShareVaultComponent.class, //
EventViewComponent.class, //
RecoveryKeyComponent.class, //
- NotificationComponent.class })
+ NotificationComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -50,6 +52,12 @@ abstract class FxApplicationModule {
}
}
+ @Provides
+ @FxApplicationScoped
+ static Optional provideAppearanceProvider() {
+ return UiAppearanceProvider.get();
+ }
+
@Provides
@FxApplicationScoped
static TrayMenuComponent provideTrayMenuComponent(TrayMenuComponent.Builder builder) {
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index ec8967340..bd9ae4405 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -37,11 +37,6 @@ public class FxApplicationStyle {
}
public void initialize() {
- appearanceProvider.ifPresent(service -> {
- if (service instanceof JfxUiAppearanceProvider fxService) {
- fxService.initialize(Platform.getPreferences());
- }
- });
applyTheme(settings.theme.get());
settings.theme.addListener(this::appThemeChanged);
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index c121c38d5..ffe7093a2 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -14,7 +14,6 @@ import javafx.application.ColorScheme;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicReference;
@DisplayName("JavaFX Color Scheme switcher")
@OperatingSystem(OperatingSystem.Value.LINUX)
@@ -24,97 +23,47 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
private static final Logger LOG = LoggerFactory.getLogger(JfxUiAppearanceProvider.class);
- private final AtomicReference realImpl = new AtomicReference<>(null);
-
- public void initialize(Platform.Preferences preferences) {
- var isSet = realImpl.compareAndSet(null, new JfxUiAppearanceImpl(preferences));
- if (isSet) {
- LOG.debug("Initialized {} with JavaFX preferences", JfxUiAppearanceImpl.class);
- }
- }
-
- private static class JfxUiAppearanceImpl implements UiAppearanceProvider {
-
- private final Platform.Preferences preferences;
- private final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
-
- private JfxUiAppearanceImpl(Platform.Preferences preferences) {
- this.preferences = preferences;
- }
-
- @Override
- public Theme getSystemTheme() {
- return switch (preferences.getColorScheme()) {
- case DARK -> Theme.DARK;
- case LIGHT -> Theme.LIGHT;
- };
- }
-
- @Override
- public void adjustToTheme(Theme theme) {
- //no-op
- }
-
- @Override
- public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var fxChangeListener = (ChangeListener) (_, _, newScheme) -> {
- var newTheme = switch (newScheme) {
- case DARK -> Theme.DARK;
- case LIGHT -> Theme.LIGHT;
- };
- uiAppearanceListener.systemAppearanceChanged(newTheme);
- };
- LOG.debug("Register listener for OS theme changes");
- uiAppearanceListeners.computeIfAbsent(uiAppearanceListener, k -> {
- Platform.runLater(() -> preferences.colorSchemeProperty().addListener(fxChangeListener));
- return fxChangeListener;
- });
- }
-
- @Override
- public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
- if (fxChangeListener != null) {
- LOG.debug("Removing listener for OS theme changes");
- Platform.runLater(() -> preferences.colorSchemeProperty().removeListener(fxChangeListener));
- }
- }
- }
+ private final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
+ private final Platform.Preferences preferences = Platform.getPreferences(); //Note: this service impl MUST be loaded in the fx application thread
- //just delegate methods
@Override
public Theme getSystemTheme() {
- var impl = realImpl.get();
- if (impl != null) {
- return impl.getSystemTheme();
- } else {
- return Theme.LIGHT;
- }
+ return switch (preferences.getColorScheme()) {
+ case DARK -> Theme.DARK;
+ case LIGHT -> Theme.LIGHT;
+ };
}
@Override
public void adjustToTheme(Theme theme) {
- var impl = realImpl.get();
- if (impl != null) {
- impl.adjustToTheme(theme);
- }
+ //no-op
}
@Override
public void addListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var impl = realImpl.get();
- if (impl != null) {
- impl.addListener(uiAppearanceListener);
- }
+ var fxChangeListener = (ChangeListener) (_, _, newScheme) -> {
+ var newTheme = switch (newScheme) {
+ case DARK -> Theme.DARK;
+ case LIGHT -> Theme.LIGHT;
+ };
+ uiAppearanceListener.systemAppearanceChanged(newTheme);
+ };
+ LOG.debug("Register listener for OS theme changes");
+ uiAppearanceListeners.computeIfAbsent(uiAppearanceListener, k -> {
+ Platform.runLater(() -> preferences.colorSchemeProperty().addListener(fxChangeListener));
+ return fxChangeListener;
+ });
}
@Override
public void removeListener(UiAppearanceListener uiAppearanceListener) throws UiAppearanceException {
- var impl = realImpl.get();
- if (impl != null) {
- impl.removeListener(uiAppearanceListener);
+ var fxChangeListener = uiAppearanceListeners.remove(uiAppearanceListener);
+ if (fxChangeListener != null) {
+ LOG.debug("Removing listener for OS theme changes");
+ Platform.runLater(() -> preferences.colorSchemeProperty().removeListener(fxChangeListener));
}
}
-
}
+
+
From 9e6bd913cb8b964ebbd9d341c8fff5ceca5365fd Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 12:37:46 +0100
Subject: [PATCH 077/100] refactor FxApplicationStyle
use pattern (removeOldListeners, addNewListerner, applyTheme)
---
.../ui/fxapp/FxApplicationStyle.java | 65 ++++++++++++-------
1 file changed, 42 insertions(+), 23 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index bd9ae4405..ccda4a3f8 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -12,7 +12,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
-import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@@ -37,45 +36,65 @@ public class FxApplicationStyle {
}
public void initialize() {
- applyTheme(settings.theme.get());
+ var uiTheme = settings.theme.get();
+ if (uiTheme == UiTheme.AUTOMATIC) {
+ registerOsThemeListener();
+ }
+ applyTheme(uiTheme);
settings.theme.addListener(this::appThemeChanged);
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, UiTheme oldValue, UiTheme newValue) {
- if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC && appearanceProvider.isPresent()) {
- try {
- appearanceProvider.get().removeListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.warn("Failed to disable automatic theme switching.");
- }
+ if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
+ removeOsThemeListener();
}
+
+ if (newValue == UiTheme.AUTOMATIC) {
+ registerOsThemeListener();
+ }
+
applyTheme(newValue);
}
- private void applyTheme(UiTheme theme) {
+ private void removeOsThemeListener() {
+ if (appearanceProvider.isPresent()) {
+ try {
+ appearanceProvider.get().removeListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.warn("Failed to disable automatic theme switching.", e);
+ }
+ } else {
+ LOG.debug("Unable to remove listener os theme changes: No supported UiAppearanceProvider present");
+ }
+ }
+
+ private void registerOsThemeListener() {
+ if (appearanceProvider.isPresent()) {
+ try {
+ appearanceProvider.get().addListener(systemInterfaceThemeListener);
+ } catch (UiAppearanceException e) {
+ LOG.warn("Failed to enable automatic theme switching.", e);
+ }
+ } else {
+ LOG.warn("Unable to register for os theme changes: No supported UiAppearanceProvider present");
+ }
+ }
+
+ private void applyTheme(UiTheme uiTheme) {
if (!licenseHolder.isValidLicense()) {
loadAndApplyLightTheme();
} else {
- switch (theme) {
- case AUTOMATIC -> registerAutomaticThemeChange();
+ switch (uiTheme) {
+ case AUTOMATIC -> {
+ var osTheme = appearanceProvider.isPresent() ? appearanceProvider.get().getSystemTheme() : Theme.LIGHT;
+ systemInterfaceThemeChanged(osTheme);
+ }
case LIGHT -> loadAndApplyLightTheme();
case DARK -> loadAndApplyDarkTheme();
}
}
}
- private void registerAutomaticThemeChange() {
- appearanceProvider.ifPresentOrElse(provider -> {
- try {
- provider.addListener(systemInterfaceThemeListener);
- } catch (UiAppearanceException e) {
- LOG.error("Failed to enable automatic theme switching.");
- }
- systemInterfaceThemeChanged(provider.getSystemTheme());
- }, //
- () -> LOG.warn("UI theme AUTOMATIC selected, but no supported UiAppearanceProvider present"));
- }
-
private void systemInterfaceThemeChanged(Theme osTheme) {
switch (osTheme) {
case LIGHT -> loadAndApplyLightTheme();
From 158b454b0d2c62c71a09cac6fb528bdac7245dce Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 12:45:57 +0100
Subject: [PATCH 078/100] fix NPE in EventualLogger
---
src/main/java/org/cryptomator/launcher/EventualLogger.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java
index fac429cfe..c14e5070b 100644
--- a/src/main/java/org/cryptomator/launcher/EventualLogger.java
+++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java
@@ -26,7 +26,7 @@ class EventualLogger extends AbstractLogger {
var builder = gutter.atLevel(event.getLevel()) //
.setCause(event.getThrowable()) //
.setMessage(event.getMessage());
- event.getArguments().forEach(builder::addArgument);
+ Objects.requireNonNullElse(event.getArguments(), List.of()).forEach(builder::addArgument);
Objects.requireNonNullElse(event.getMarkers(), List.of()).forEach(builder::addMarker);
builder.log();
}
From f753ddc9bee6adcd72d270a3cf741b3c830b3f34 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 16:05:26 +0100
Subject: [PATCH 079/100] Apply suggestions from code review
Co-authored-by: Sebastian Stenzel
---
.../org/cryptomator/ui/fxapp/FxApplicationStyle.java | 12 ++++++------
.../ui/fxapp/JfxUiAppearanceProvider.java | 1 -
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
index ccda4a3f8..bfdb22196 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationStyle.java
@@ -45,12 +45,12 @@ public class FxApplicationStyle {
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue extends UiTheme> observable, UiTheme oldValue, UiTheme newValue) {
- if (oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
- removeOsThemeListener();
- }
-
- if (newValue == UiTheme.AUTOMATIC) {
+ if (oldValue == newValue) {
+ // no-op
+ } else if (newValue == UiTheme.AUTOMATIC) {
registerOsThemeListener();
+ } else if (oldValue == UiTheme.AUTOMATIC) {
+ removeOsThemeListener();
}
applyTheme(newValue);
@@ -86,7 +86,7 @@ public class FxApplicationStyle {
} else {
switch (uiTheme) {
case AUTOMATIC -> {
- var osTheme = appearanceProvider.isPresent() ? appearanceProvider.get().getSystemTheme() : Theme.LIGHT;
+ var osTheme = appearanceProvider.map(UiAppearanceProvider::getSystemTheme).orElse(Theme.LIGHT);
systemInterfaceThemeChanged(osTheme);
}
case LIGHT -> loadAndApplyLightTheme();
diff --git a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
index ffe7093a2..56562fc9c 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/JfxUiAppearanceProvider.java
@@ -26,7 +26,6 @@ public class JfxUiAppearanceProvider implements UiAppearanceProvider {
private final ConcurrentHashMap> uiAppearanceListeners = new ConcurrentHashMap<>();
private final Platform.Preferences preferences = Platform.getPreferences(); //Note: this service impl MUST be loaded in the fx application thread
-
@Override
public Theme getSystemTheme() {
return switch (preferences.getColorScheme()) {
From a79fd636340bef6fe97469df740b7ec997b8348d Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 16:15:05 +0100
Subject: [PATCH 080/100] [skip ci] update changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6163df43f..4b6e1ea74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Mark files in-use for Hub vaults ([#4078](https://github.com/cryptomator/cryptomator/pull/4078))
* Accessibility: Adjust app to be used with a screen reader ([#547](https://github.com/cryptomator/cryptomator/issues/547))
* Show Archived Vault Dialog on unlock when Hub returns 410 ([#4081](https://github.com/cryptomator/cryptomator/pull/4081))
-* Automatic app color scheme selection according to OS ([#4134](https://github.com/cryptomator/cryptomator/pull/4134))
+* Support automatic app theme selection according to OS theme on Linux ([#4027](https://github.com/cryptomator/cryptomator/issues/4027))
* Admin configuration: Allow overwriting certain app properties by external config file ([#4105](https://github.com/cryptomator/cryptomator/pull/4105))
### Changed
From e53598cfce2841564551fe6adf16bfc83d21393d Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Mon, 16 Feb 2026 17:06:00 +0100
Subject: [PATCH 081/100] update azure signing to stable version
---
.github/actions/win-sign-action/action.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/actions/win-sign-action/action.yml b/.github/actions/win-sign-action/action.yml
index ce4423883..b4c2eaa73 100644
--- a/.github/actions/win-sign-action/action.yml
+++ b/.github/actions/win-sign-action/action.yml
@@ -48,7 +48,7 @@ runs:
echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT"
shell: bash
- name: Sign DLLs with Azure Trusted Signing
- uses: azure/trusted-signing-action@fc390cf8ed0f14e248a542af1d838388a47c7a7c # v0.5.10
+ uses: azure/artifact-signing-action@87c2e83e6868da99d3380aa309851b32ed9a8346 # v1.1.0
with:
files-folder: ${{ inputs.base-dir }}
files-folder-filter: ${{ inputs.file-extensions }}
@@ -59,7 +59,7 @@ runs:
azure-tenant-id: ${{ steps.set-secrets.outputs.tenant-id }}
azure-client-id: ${{ steps.set-secrets.outputs.client-id }}
azure-client-secret: ${{ steps.set-secrets.outputs.client-secret }}
- trusted-signing-account-name: cryptomatorSigning
+ signing-account-name: cryptomatorSigning
certificate-profile-name: production
endpoint: https://weu.codesigning.azure.net/
timestamp-rfc3161: http://timestamp.acs.microsoft.com
From bc0bb38e4cf13c1be198f3100f7b749bcf08993b Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 12:45:46 +0100
Subject: [PATCH 082/100] remove dagger formatting due to compile error
---
pom.xml | 1 -
1 file changed, 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f5b50628f..490bf925f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -330,7 +330,6 @@
-Adagger.fastInit=enabled
- -Adagger.formatGeneratedSource=enabled
From 5c1f8e7576ee396ffc888c0cf71f7edff02b3363 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 12:46:19 +0100
Subject: [PATCH 083/100] update IDE file
---
.idea/compiler.xml | 44 ++++++++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 2d9504948..6ce105ba8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -12,17 +12,16 @@
-
+
-
-
+
+
-
+
-
-
-
+
+
@@ -37,8 +36,33 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -47,7 +71,7 @@
\ No newline at end of file
From 1b243bb725b2bdbfc49169ae20d95072f2079ba1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 15:12:25 +0100
Subject: [PATCH 084/100] suppress logging of expected exception
---
.../org/cryptomator/ui/fxapp/FxFSEventList.java | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java b/src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
index e9e574b95..f1dd20d53 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxFSEventList.java
@@ -3,6 +3,8 @@ package org.cryptomator.ui.fxapp;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
@@ -11,6 +13,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.Map;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -23,6 +26,8 @@ import java.util.concurrent.TimeUnit;
@FxApplicationScoped
public class FxFSEventList {
+ private static final Logger LOG = LoggerFactory.getLogger(FxFSEventList.class);
+
private final ObservableList> events;
private final FileSystemEventAggregator eventAggregator;
private final ScheduledExecutorService scheduler;
@@ -37,7 +42,13 @@ public class FxFSEventList {
}
public void schedulePollForUpdates() {
- scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
+ try {
+ scheduler.schedule(this::checkForEventUpdates, 1000, TimeUnit.MILLISECONDS);
+ } catch ( RejectedExecutionException e) {
+ if(!scheduler.isShutdown()) {
+ LOG.warn("Failed to poll for filesystem events", e);
+ }
+ }
}
/**
From 044dbcb11fb4f0f9cd39e354de28234b10ddf288 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 15:13:46 +0100
Subject: [PATCH 085/100] Update cryptofs to version 2.10.0-beta3
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 490bf925f..08c347f0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents
- 2.10.0-beta2
+ 2.10.0-beta31.8.0-beta11.5.11.5.0-beta3
From 74fc77ab8dccd331e48ea7dabd2ec5e01fdd2bcd Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 15:21:16 +0100
Subject: [PATCH 086/100] fix pom inconsistency
---
pom.xml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 08c347f0d..5b90b6b51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
2.10.0-beta3
+ 2.2.21.8.0-beta11.5.11.5.0-beta3
@@ -42,6 +43,7 @@
3.0.0
+ 3.2.33.20.02.59.12.2
@@ -94,7 +96,7 @@
org.cryptomatorcryptolib
- 2.2.2
+ ${cryptomator.cryptolib.version}org.cryptomator
@@ -228,7 +230,7 @@
com.github.ben-manes.caffeinecaffeine
- 3.2.3
+ ${caffeine.version}
From b86d8ca7902a9dda860034c5b1be17c6b29035e1 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Tue, 17 Feb 2026 15:30:20 +0100
Subject: [PATCH 087/100] [skip ci] update changelog
---
CHANGELOG.md | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b6e1ea74..336bff03c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,3 +24,18 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Built using JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/issues/4031))
* Modernized Template for GitHub Releases
* Disable user defined app start config on Windows ([#4132](https://github.com/cryptomator/cryptomator/issues/4132))
+* Updated dependencies
+ * `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
+ * `org.cryptomator:cryptofs` from 2.9.0 to 2.10.0-beta3
+ * `org.cryptomator:integrations-api` from 1.7.0 to 1.8.0-beta1
+ * `org.cryptomator:integrations-linux` from 1.6.1 to 1.7.0-beta4
+ * `org.cryptomator:integrations-mac` from 1.4.1 to 1.5.0-beta3
+ * `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
+ * `ch.qos.logback:*` from 1.5.19 to 1.5.31
+ * `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
+ * `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
+ * `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
+ * `com.google.dagger:*` from 2.57.2 to 2.59.1
+ * `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
+
+
From 171d5d5a4c49d75ad479b0dc76ea129c814a2de9 Mon Sep 17 00:00:00 2001
From: Jan-Peter Klein
Date: Wed, 18 Feb 2026 10:55:49 +0100
Subject: [PATCH 088/100] Fixes #4141
---
.../java/org/cryptomator/common/recovery/MasterkeyService.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java
index 7d487ec54..dd40e519f 100644
--- a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java
+++ b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java
@@ -61,6 +61,7 @@ public final class MasterkeyService {
Optional c9rFile = paths //
.filter(p -> p.toString().endsWith(".c9r")) //
.filter(p -> !p.endsWith("dir.c9r")) //
+ .filter(Files::isRegularFile) //
.findFirst();
if (c9rFile.isEmpty()) {
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
From e11d42bb07b76199f3885479db627b79145c43ab Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 11:07:18 +0100
Subject: [PATCH 089/100] update IDE file
---
.idea/compiler.xml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 6ce105ba8..16d2db425 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -12,7 +12,6 @@
-
@@ -71,7 +70,7 @@
\ No newline at end of file
From dba3de230b210bde3c7680926140b56edbd6f3aa Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 11:15:00 +0100
Subject: [PATCH 090/100] Update dependencies
* org.cryptomator:integrations-win from 1.5.1 to 1.6.0
* org.cryptomator:webdav-nio-adapter from 3.0.0 to 3.0.1
---
CHANGELOG.md | 2 ++
pom.xml | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 336bff03c..1264e196d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,8 @@ Changes to prior versions can be found on the [Github release page](https://gith
* `org.cryptomator:integrations-api` from 1.7.0 to 1.8.0-beta1
* `org.cryptomator:integrations-linux` from 1.6.1 to 1.7.0-beta4
* `org.cryptomator:integrations-mac` from 1.4.1 to 1.5.0-beta3
+ * `org.cryptomator:integrations-win` from 1.5.1 to 1.6.0
+ * `org.cryptomator:webdav-nio-adapter` from 3.0.0 to 3.0.1
* `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
* `ch.qos.logback:*` from 1.5.19 to 1.5.31
* `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
diff --git a/pom.xml b/pom.xml
index 5b90b6b51..8e5cf8618 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,11 +36,11 @@
2.10.0-beta32.2.21.8.0-beta1
- 1.5.1
+ 1.6.01.5.0-beta31.7.0-beta46.0.0
- 3.0.0
+ 3.0.13.2.3
From ac6f34fe1742d0f0568f704b1406b3191d19f26f Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 11:15:46 +0100
Subject: [PATCH 091/100] reorder changelog
---
CHANGELOG.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1264e196d..7f843e8ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,19 +25,19 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Modernized Template for GitHub Releases
* Disable user defined app start config on Windows ([#4132](https://github.com/cryptomator/cryptomator/issues/4132))
* Updated dependencies
- * `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
+ * `ch.qos.logback:*` from 1.5.19 to 1.5.31
+ * `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
+ * `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
+ * `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
+ * `com.google.dagger:*` from 2.57.2 to 2.59.1
+ * `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
* `org.cryptomator:cryptofs` from 2.9.0 to 2.10.0-beta3
+ * `org.cryptomator:cryptolib` from 2.2.1 to 2.2.2
+ * `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
* `org.cryptomator:integrations-api` from 1.7.0 to 1.8.0-beta1
* `org.cryptomator:integrations-linux` from 1.6.1 to 1.7.0-beta4
* `org.cryptomator:integrations-mac` from 1.4.1 to 1.5.0-beta3
* `org.cryptomator:integrations-win` from 1.5.1 to 1.6.0
* `org.cryptomator:webdav-nio-adapter` from 3.0.0 to 3.0.1
- * `org.cryptomator:fuse-nio-adapter` from 5.1.0 to 6.0.0
- * `ch.qos.logback:*` from 1.5.19 to 1.5.31
- * `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0
- * `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.21.0
- * `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.20.0 to 2.21.0
- * `com.google.dagger:*` from 2.57.2 to 2.59.1
- * `com.github.ben-manes.caffeine:caffeine` from 3.2.2 to 3.2.3
From 0ac63e7aba05fab1f389b37e914cbf57e89fe64c Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 11:26:42 +0100
Subject: [PATCH 092/100] Closes #4136
---
.github/workflows/appimage.yml | 1 -
.github/workflows/mac-dmg-x64.yml | 1 -
.github/workflows/mac-dmg.yml | 1 -
.github/workflows/win-exe.yml | 1 -
dist/linux/appimage/build.sh | 1 -
dist/linux/debian/rules | 1 -
dist/mac/dmg/build.sh | 1 -
dist/win/build.ps1 | 1 -
8 files changed, 8 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index a1c57d91c..70bcdf2c4 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -124,7 +124,6 @@ jobs:
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\""
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\""
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index d8047462e..ef3b5da9f 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -130,7 +130,6 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 9a06024a0..6a3e99d2a 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -128,7 +128,6 @@ jobs:
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 23846ad5d..12ef4ab5e 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -142,7 +142,6 @@ jobs:
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\""
- --java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\""
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index ee72b538b..7ceb98861 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -90,7 +90,6 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules
index 0d2242f40..fef465b65 100755
--- a/dist/linux/debian/rules
+++ b/dist/linux/debian/rules
@@ -53,7 +53,6 @@ override_dh_auto_build:
--java-options "-Djava.net.useSystemProxies=true" \
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/.local/share/Cryptomator/logs\"" \
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/.local/share/Cryptomator/plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:@{userhome}/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/.config/Cryptomator/ipc.socket\"" \
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index 438de7b9a..08620797a 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -117,7 +117,6 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\"" \
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/${APP_NAME}\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
- --java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/${APP_NAME}/Plugins\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/${APP_NAME}/settings.json\"" \
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/${APP_NAME}/ipc.socket\"" \
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/${APP_NAME}/key.p12\"" \
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index 54ee8946e..a2fe38a92 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -156,7 +156,6 @@ $javaOptions = @(
"--java-options", "-Dcryptomator.logDir=`"@{localappdata}/$AppName`""
"--java-options", "-XX:ErrorFile=`"C:/cryptomator/cryptomator_crash.log`""
"--java-options", "-Dcryptomator.adminConfigPath=`"C:/ProgramData/$AppName/config.properties`""
-"--java-options", "-Dcryptomator.pluginDir=`"@{appdata}/$AppName/Plugins`""
"--java-options", "-Dcryptomator.settingsPath=`"@{appdata}/$AppName/settings.json;@{userhome}/AppData/Roaming/$AppName/settings.json`""
"--java-options", "-Dcryptomator.ipcSocketPath=`"@{localappdata}/$AppName/ipc.socket`""
"--java-options", "-Dcryptomator.p12Path=`"@{appdata}/$AppName/key.p12;@{userhome}/AppData/Roaming/$AppName/key.p12`""
From 5ab12c1b1a9f3e2a6bbe128150d8a5ecabb02d96 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 11:49:13 +0100
Subject: [PATCH 093/100] Update to latest JDK 25
---
.github/workflows/appimage.yml | 2 +-
.github/workflows/debian.yml | 2 +-
.github/workflows/mac-dmg-x64.yml | 2 +-
.github/workflows/mac-dmg.yml | 2 +-
.github/workflows/win-exe.yml | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 70bcdf2c4..45add05ea 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -19,7 +19,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '25.0.1+8.0.LTS'
+ JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml
index 141e76e66..65a86c128 100644
--- a/.github/workflows/debian.yml
+++ b/.github/workflows/debian.yml
@@ -23,7 +23,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '25.0.1+8.0.LTS'
+ JAVA_VERSION: '25.0.2+10.0.LTS'
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index ef3b5da9f..c8d7ef6aa 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -24,7 +24,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '25.0.1+8.0.LTS'
+ JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 6a3e99d2a..5309f92fc 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -22,7 +22,7 @@ on:
env:
JAVA_DIST: 'temurin'
- JAVA_VERSION: '25.0.1+8.0.LTS'
+ JAVA_VERSION: '25.0.2+10.0.LTS'
jobs:
get-version:
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 12ef4ab5e..5b030cd6e 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -48,7 +48,7 @@ jobs:
- arch: x64
os: windows-latest
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
- java-version: '25.0.1+8'
+ java-version: '25.0.2+10'
java-package: 'jdk'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
From 70b7f39bccdf755af305c47eba6b37271f2e16a4 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 15:16:39 +0100
Subject: [PATCH 094/100] revert 5ab12c1b1a9f3e2a6bbe128150d8a5ecabb02d96 for
windows build
---
.github/workflows/win-exe.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 5b030cd6e..12ef4ab5e 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -48,7 +48,7 @@ jobs:
- arch: x64
os: windows-latest
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
- java-version: '25.0.2+10'
+ java-version: '25.0.1+8'
java-package: 'jdk'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
From 5409470750459a69a69c35da6b27a74b10696941 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 15:17:18 +0100
Subject: [PATCH 095/100] update wix
---
.github/workflows/win-exe.yml | 17 +++++++++++------
dist/win/build.ps1 | 8 ++++----
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index 12ef4ab5e..c88d9d2ed 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -27,6 +27,7 @@ env:
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
+ WIX_VERSION: '6.0.2'
defaults:
run:
@@ -62,9 +63,11 @@ jobs:
cache: 'maven'
- name: Install wix and extensions
run: |
- dotnet tool install --global wix --version 6.0.0
- wix.exe extension add WixToolset.UI.wixext/6.0.0 --global
- wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
+ dotnet tool install --global wix --version ${WIX_VERSION}
+ wix.exe extension add --global WixToolset.UI.wixext/${WIX_VERSION}
+ wix.exe extension add --global WixToolset.Util.wixext/${WIX_VERSION}
+ env:
+ WIX_VERSION: ${{ env.WIX_VERSION }}
- name: Download and extract JavaFX jmods from Gluon
if: matrix.arch == 'x64'
#In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip
@@ -302,9 +305,11 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install wix and extensions
run: |
- dotnet tool install --global wix --version 6.0.0
- wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global
- wix.exe extension add WixToolset.Util.wixext/6.0.0 --global
+ dotnet tool install --global wix --version ${WIX_VERSION}
+ wix.exe extension add --global WixToolset.BootstrapperApplications.wixext/${WIX_VERSION}
+ wix.exe extension add --global WixToolset.Util.wixext/${WIX_VERSION}
+ env:
+ WIX_VERSION: ${{ env.WIX_VERSION }}
- name: Download .msi
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index a2fe38a92..b6934bfe0 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -34,20 +34,20 @@ if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null)
}
if ((Get-Command 'wix' -ErrorAction SilentlyContinue) -eq $null)
{
- Write-Error 'Unable to find wix in your PATH (try: dotnet tool install --global wix --version 6.0.0)'
+ Write-Error 'Unable to find wix in your PATH (try: dotnet tool install --global wix --version 6.0.2)'
exit 1
}
$wixExtensions = & wix.exe extension list --global | Out-String
if ($wixExtensions -notmatch 'WixToolset.UI.wixext') {
- Write-Error 'Wix UI extension missing. Please install it with: wix.exe extension add WixToolset.UI.wixext/6.0.0 --global)'
+ Write-Error 'Wix UI extension missing. Please install it with: wix.exe extension add WixToolset.UI.wixext/6.0.2 --global)'
exit 1
}
if ($wixExtensions -notmatch 'WixToolset.Util.wixext') {
- Write-Error 'Wix Util extension missing. Please install it with: wix.exe extension add WixToolset.Util.wixext/6.0.0 --global)'
+ Write-Error 'Wix Util extension missing. Please install it with: wix.exe extension add WixToolset.Util.wixext/6.0.2 --global)'
exit 1
}
if ($wixExtensions -notmatch 'WixToolset.BootstrapperApplications.wixext') {
- Write-Error 'Wix Bootstrapper extension missing. Please install it with: wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global)'
+ Write-Error 'Wix Bootstrapper extension missing. Please install it with: wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.2 --global)'
exit 1
}
From f07523267d29a5332721299d94b96262028140f7 Mon Sep 17 00:00:00 2001
From: Armin Schrenk
Date: Wed, 18 Feb 2026 15:42:51 +0100
Subject: [PATCH 096/100] Bump javafx to version 25.0.2 (#4145)
---
.github/workflows/appimage.yml | 8 ++++----
.github/workflows/debian.yml | 8 ++++----
.github/workflows/mac-dmg-x64.yml | 4 ++--
.github/workflows/mac-dmg.yml | 4 ++--
.github/workflows/win-exe.yml | 4 ++--
dist/linux/appimage/build.sh | 6 +++---
dist/mac/dmg/build.sh | 6 +++---
dist/win/build.ps1 | 4 ++--
pom.xml | 2 +-
9 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml
index 45add05ea..601f3dc7f 100644
--- a/.github/workflows/appimage.yml
+++ b/.github/workflows/appimage.yml
@@ -37,12 +37,12 @@ jobs:
include:
- os: ubuntu-latest
appimage-suffix: x86_64
- openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
- openjfx-sha: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip'
+ openjfx-sha: 'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b'
- os: ubuntu-24.04-arm
appimage-suffix: aarch64
- openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
- openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip'
+ openjfx-sha: 'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml
index 65a86c128..178f46441 100644
--- a/.github/workflows/debian.yml
+++ b/.github/workflows/debian.yml
@@ -25,10 +25,10 @@ env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS'
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
- OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
- OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
- OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
- OPENJFX_JMODS_AARCH64_HASH: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
+ OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip'
+ OPENJFX_JMODS_AMD64_HASH: 'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b'
+ OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip'
+ OPENJFX_JMODS_AARCH64_HASH: 'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646'
jobs:
get-version:
diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml
index c8d7ef6aa..9afc867a6 100644
--- a/.github/workflows/mac-dmg-x64.yml
+++ b/.github/workflows/mac-dmg-x64.yml
@@ -44,8 +44,8 @@ jobs:
architecture: x64
output-suffix: x64
fuse-lib: macFUSE
- openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip'
- openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_osx-x64_bin-jmods.zip'
+ openjfx-sha: '0b4d8463f03901b7425d94628e4116b7078abb8dd540fbec415266fac20bda5c'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml
index 5309f92fc..06116d2a7 100644
--- a/.github/workflows/mac-dmg.yml
+++ b/.github/workflows/mac-dmg.yml
@@ -42,8 +42,8 @@ jobs:
architecture: aarch64
output-suffix: arm64
fuse-lib: FUSE-T
- openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip'
- openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba'
+ openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_osx-aarch64_bin-jmods.zip'
+ openjfx-sha: '4cd258001c75af7047005c5c891e2400ed11d24fbb09412324c0cbaf8b503c5a'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml
index c88d9d2ed..e41d3c618 100644
--- a/.github/workflows/win-exe.yml
+++ b/.github/workflows/win-exe.yml
@@ -22,8 +22,8 @@ on:
env:
- OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_windows-x64_bin-jmods.zip'
- OPENJFX_JMODS_AMD64_HASH: 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
+ OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_windows-x64_bin-jmods.zip'
+ OPENJFX_JMODS_AMD64_HASH: '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh
index 7ceb98861..71a840b1d 100755
--- a/dist/linux/appimage/build.sh
+++ b/dist/linux/appimage/build.sh
@@ -23,12 +23,12 @@ mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
cp ../../../LICENSE.txt ../../../target
cp ../../../target/cryptomator-*.jar ../../../target/mods
-JAVAFX_VERSION=25
+JAVAFX_VERSION=25.0.2
JAVAFX_ARCH="x64"
-JAVAFX_JMODS_SHA256='96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
+JAVAFX_JMODS_SHA256='e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b'
if [ "${CPU_ARCH}" = "aarch64" ]; then
JAVAFX_ARCH="aarch64"
- JAVAFX_JMODS_SHA256='951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
+ JAVAFX_JMODS_SHA256='c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646'
fi
# download javaFX jmods
diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh
index 08620797a..a9cdd6c46 100755
--- a/dist/mac/dmg/build.sh
+++ b/dist/mac/dmg/build.sh
@@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD`
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
FUSE_LIB="FUSE-T"
-JAVAFX_VERSION=25
+JAVAFX_VERSION=25.0.2
JAVAFX_ARCH="undefined"
JAVAFX_JMODS_SHA256="undefined"
if [ "$(machine)" = "arm64e" ]; then
JAVAFX_ARCH="aarch64"
- JAVAFX_JMODS_SHA256="13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba"
+ JAVAFX_JMODS_SHA256="4cd258001c75af7047005c5c891e2400ed11d24fbb09412324c0cbaf8b503c5a"
else
JAVAFX_ARCH="x64"
- JAVAFX_JMODS_SHA256="0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52"
+ JAVAFX_JMODS_SHA256="0b4d8463f03901b7425d94628e4116b7078abb8dd540fbec415266fac20bda5c"
fi
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
diff --git a/dist/win/build.ps1 b/dist/win/build.ps1
index b6934bfe0..82f5125b9 100644
--- a/dist/win/build.ps1
+++ b/dist/win/build.ps1
@@ -93,9 +93,9 @@ switch ($archName) {
$jmodPaths = "$Env:JAVA_HOME/jmods"
}
'x64' {
- $javaFxVersion='25'
+ $javaFxVersion='25.0.2'
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
- $javaFxJmodsSHA256 = 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7'
+ $javaFxJmodsSHA256 = '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3'
$javaFxJmods = '.\resources\jfxJmods.zip'
if( !(Test-Path -Path $javaFxJmods) ) {
diff --git a/pom.xml b/pom.xml
index 8e5cf8618..4a23c4eac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,7 +48,7 @@
2.59.12.22.21.0
- 25
+ 25.0.24.5.010.51.5.31
From 72d93e943b85d045f597e2bac533d162e99ca0ca Mon Sep 17 00:00:00 2001
From: mindmonk
Date: Wed, 18 Feb 2026 15:43:50 +0100
Subject: [PATCH 097/100] fix text wrapping in ResetPasswordDialog (#4123)
---
src/main/resources/fxml/recoverykey_validate.fxml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/resources/fxml/recoverykey_validate.fxml b/src/main/resources/fxml/recoverykey_validate.fxml
index e6a24ec49..0221f3df9 100644
--- a/src/main/resources/fxml/recoverykey_validate.fxml
+++ b/src/main/resources/fxml/recoverykey_validate.fxml
@@ -17,22 +17,22 @@
-