diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4547fb8df..a4670277a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Did you find a bug? - Ensure you're running the latest version of Cryptomator. -- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively. +- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/android/issues) respectively. - Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq). - If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new/choose). diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5e792ef0a..be3ba6a19 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,10 @@ updates: ignore: - dependency-name: "org.cryptomator:integrations-api" versions: ["2.0.0-alpha1"] + - dependency-name: "jakarta.inject:jakarta.inject-api" + versions: ["2.0.1.MR"] + - dependency-name: "org.openjfx:*" + update-types: ["version-update:semver-major"] groups: java-test-dependencies: patterns: diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 1b0b34435..b4e73e306 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -10,8 +10,8 @@ on: required: false env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' jobs: get-version: @@ -29,12 +29,12 @@ jobs: include: - os: ubuntu-latest appimage-suffix: x86_64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-x64_bin-jmods.zip' - openjfx-sha: 'd44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' - - os: [self-hosted, Linux, ARM64] + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip' + openjfx-sha: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' + - os: ubuntu-24.04-arm appimage-suffix: aarch64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-aarch64_bin-jmods.zip' - openjfx-sha: '3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip' + openjfx-sha: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' steps: - uses: actions/checkout@v4 - name: Setup Java @@ -98,7 +98,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" @@ -116,7 +116,7 @@ jobs: --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\"" - --add-launcher Cryptomator-gtk2=launcher-gtk2.properties + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | @@ -133,13 +133,13 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon - ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop + ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop + ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - name: Download AppImageKit run: | - curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage + curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage chmod +x appimagetool.AppImage ./appimagetool.AppImage --appimage-extract - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 @@ -153,7 +153,7 @@ jobs: run: > ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.appimage-suffix }}.AppImage -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.appimage-suffix }}.AppImage.zsync' - --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" + --sign --sign-key=615D449FE6E6A235 - name: Create detached GPG signatures run: | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage @@ -176,4 +176,4 @@ jobs: files: | cryptomator-*.AppImage cryptomator-*.zsync - cryptomator-*.asc \ No newline at end of file + cryptomator-*.asc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e86b61ea..545513129 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,8 +6,8 @@ on: types: [labeled] env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 defaults: run: @@ -53,10 +53,25 @@ jobs: generate_release_notes: true body: |- :construction: Work in Progress + ### What's New 🎉 - ⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳ + ### Bugfixes 🐛 - As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a). + ### Other Changes 📎 --- - \ No newline at end of file + + TODO FULL CHANGELOG + + 📜 List of closed issues is available [here](TODO) + + --- + ⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳ + + + + As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a). diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml index c73546994..64456baae 100644 --- a/.github/workflows/check-jdk-updates.yml +++ b/.github/workflows/check-jdk-updates.yml @@ -1,56 +1,75 @@ -name: Checks JDK version for minor updates +name: Check JDK for non-major updates on: schedule: - cron: '0 0 1 * *' # run once a month at the first day of month + workflow_dispatch: env: - JDK_VERSION: '22.0.1+8' - JDK_VENDOR: zulu + JDK_VERSION: '23.0.1+11' + JDK_VENDOR: temurin + RUNTIME_VERSION_HELPER: > + public class Test { + public static void main(String[] args) { + System.out.println(Runtime.version()); + } + } jobs: - jdk-current: - name: Check out current version - runs-on: ubuntu-latest - outputs: - jdk-date: ${{ steps.get-data.outputs.jdk-date}} - steps: - - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JDK_VERSION }} - distribution: ${{ env.JDK_VENDOR }} - check-latest: false - - name: Read JAVA_VERSION_DATE and store in env variable - id: get-data - run: | - date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" - jdk-latest: + check-version: name: Checkout latest jdk version runs-on: ubuntu-latest - outputs: - jdk-date: ${{ steps.get-data.outputs.jdk-date}} - jdk-version: ${{ steps.get-data.outputs.jdk-version}} + env: + JDK_MAJOR_VERSION: 'toBeFilled' steps: - - uses: actions/setup-java@v4 + - name: Determine current major version + run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV" + shell: pwsh + - name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }} + uses: actions/setup-java@v4 with: - java-version: 21 + java-version: ${{ env.JDK_MAJOR_VERSION}} distribution: ${{ env.JDK_VENDOR }} check-latest: true - - name: Read JAVA_VERSION_DATE and store in env variable - id: get-data + - name: Determine if update is available + id: determine + shell: pwsh run: | - date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-date=${date}" >> "$GITHUB_OUTPUT" - version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"') - echo "jdk-version=${version}" >> "$GITHUB_OUTPUT" - notify: - name: Notifies for jdk update - runs-on: ubuntu-latest - needs: [jdk-current, jdk-latest] - if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }} - steps: - - name: Slack Notification + $latestVersion = 0,0,0,0 #INTERIM, UPDATE, PATCH and BUILD + $currentVersion = 0,0,0,0 + + # Get the latest JDK runtime version + "${env:RUNTIME_VERSION_HELPER}" | Set-Content -Path "GetRuntimeVersion.java" + $latestVersionString = & java GetRuntimeVersion.java + $runtimeVersionAndBuild = $latestVersionString.Split('+') + if($runtimeVersionAndBuild.Length -eq 2) { + $latestVersion[3]=$runtimeVersionAndBuild[1]; + } + $tmp=$runtimeVersionAndBuild[0].Split('.') + for($i=0;$i -lt $latestVersion.Length; $i++) { + $latestVersion[$i]=$tmp[$i+1]; + } + + # Get the current JDK version + $runtimeVersionAndBuild = '${{ env.JDK_VERSION}}'.Split('+') + if($runtimeVersionAndBuild.Length -eq 2) { + $currentVersion[3]=$runtimeVersionAndBuild[1]; + } + $tmp=$runtimeVersionAndBuild[0].Split('.') + for($i=0;$i -lt $currentVersion.Length; $i++) { + $currentVersion[$i]=$tmp[$i+1]; + } + + # compare + for($i=0; $i -lt $currentVersion.Length ; $i++) { + if($latestVersion[$i] -gt $currentVersion[$i]){ + echo 'UPDATE_AVAILABLE=true' >> "$env:GITHUB_OUTPUT" + echo "LATEST_JDK_VERSION='${latestVersionString}'" >> "$env:GITHUB_OUTPUT" + return 0; + } + } + - name: Notify + if: steps.determine.outputs.UPDATE_AVAILABLE == 'true' uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -59,6 +78,6 @@ jobs: SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "JDK update available" - SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions." + SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions." SLACK_FOOTER: false - MSG_MINIMAL: true + MSG_MINIMAL: true \ No newline at end of file diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 7ab8cc7ea..16c5bc530 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -16,14 +16,14 @@ on: type: boolean env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' - COFFEELIBS_JDK: 22 - COFFEELIBS_JDK_VERSION: '22.0.2+9-0ppa1' - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: 'd44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' - OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-aarch64_bin-jmods.zip' - OPENJFX_JMODS_AARCH64_HASH: '3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' + COFFEELIBS_JDK: 23 + COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1' + OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-x64_bin-jmods.zip' + OPENJFX_JMODS_AMD64_HASH: '2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' + OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_linux-aarch64_bin-jmods.zip' + OPENJFX_JMODS_AARCH64_HASH: '09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' jobs: build: diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 40dfe97fa..00e30c984 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -11,7 +11,7 @@ jobs: with: runner-os: 'ubuntu-latest' java-distribution: 'temurin' - java-version: 22 + java-version: 23 check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux' secrets: nvd-api-key: ${{ secrets.NVD_API_KEY }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 7dc22462f..5a5ab09b0 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -22,8 +22,8 @@ on: value: ${{ jobs.determine-version.outputs.type }} env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 jobs: determine-version: diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml new file mode 100644 index 000000000..c7ef8b8b6 --- /dev/null +++ b/.github/workflows/mac-dmg-x64.yml @@ -0,0 +1,264 @@ +name: Build macOS .dmg for x64 + +####################################### +# STOP! DO NOT EDIT THIS FILE! +# +# It is a copy of mac-dmg.yml with tiny adjustements (mainly lines 42 to 47) +# It was made necessary, since Github does not offer free macos intel runners for macos 15 and above. +# This workflow can only be triggered by a release. +# +####################################### + +on: + release: + types: [published] + +env: + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' + +jobs: + get-version: + uses: ./.github/workflows/get-version.yml + with: + version: ${{ inputs.version }} + + build-arm: + name: Build Cryptomator.app for ${{ matrix.output-suffix }} + runs-on: ${{ matrix.os }} + needs: [get-version] + strategy: + fail-fast: false + matrix: + include: + - os: macos-15-large + architecture: x64 + output-suffix: x64 + fuse-lib: macFUSE + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-x64_bin-jmods.zip' + openjfx-sha: '8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e' + steps: + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: ${{ env.JAVA_DIST }} + java-version: ${{ env.JAVA_VERSION }} + architecture: ${{ matrix.architecture }} + check-latest: true + cache: 'maven' + - name: Download OpenJFX jmods + id: download-jmods + run: | + curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip + echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check + mkdir -p openjfx-jmods/ + unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods + - name: Ensure major jfx version in pom and in jmods is the same + run: | + JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) + JMOD_VERSION=${JMOD_VERSION#*@} + JMOD_VERSION=${JMOD_VERSION%%.*} + POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) + POM_JFX_VERSION=${POM_JFX_VERSION#*@} + POM_JFX_VERSION=${POM_JFX_VERSION%%.*} + + if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then + >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" + exit 1 + fi + - name: Set version + run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} + - name: Run maven + run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + #Remark: no compression is applied for improved build compression later (here dmg) + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods:openjfx-jmods" + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --strip-native-commands + --no-header-files + --no-man-pages + --strip-debug + --compress zip-0 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" + --app-version "${{ needs.get-version.outputs.semVerNum }}" + --java-options "--enable-preview" + --java-options "--enable-native-access=org.cryptomator.jfuse.mac" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Djava.net.useSystemProxies=true" + --java-options "-Dapple.awt.enableTemplateImages=true" + --java-options "-Dsun.java2d.metal=true" + --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" + --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\"" + --java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\"" + --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\"" + --java-options "-Dcryptomator.showTrayIcon=true" + --java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\"" + --mac-package-identifier org.cryptomator + --resource-dir dist/mac/resources + - name: Patch Cryptomator.app + run: | + mv appdir/Cryptomator.app Cryptomator.app + mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ + sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist + sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist + echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile + env: + VERSION_NO: ${{ needs.get-version.outputs.semVerNum }} + REVISION_NO: ${{ needs.get-version.outputs.revNum }} + PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }} + - name: Generate license for dmg + run: > + mvn -B -Djavafx.platform=mac license:add-third-party + -Dlicense.thirdPartyFilename=license.rtf + -Dlicense.outputDirectory=dist/mac/dmg/resources + -Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl + -Dlicense.includedScopes=compile + -Dlicense.excludedGroups=^org\.cryptomator + -Dlicense.failOnMissing=true + -Dlicense.licenseMergesUrl=file://${{ github.workspace }}/license/merges + - name: Install codesign certificate + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH + + # create temporary keychain + security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + security set-keychain-settings -lut 900 $KEYCHAIN_PATH + security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + env: + CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }} + CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }} + CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }} + - name: Codesign + run: | + echo "Codesigning jdk files..." + find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + find Cryptomator.app/Contents/runtime/Contents/Home/lib/ \( -name 'jspawnhelper' -o -name 'pauseengine' -o -name 'simengine' \) -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \; + echo "Codesigning jar contents..." + find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do + if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then + JAR_FILENAME=$(basename ${JAR_PATH}) + OUTPUT_PATH=${JAR_PATH%.*} + echo "Codesigning libs in ${JAR_FILENAME}..." + unzip -q ${JAR_PATH} -d ${OUTPUT_PATH} + find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + rm ${JAR_PATH} + pushd ${OUTPUT_PATH} > /dev/null + zip -qr ../${JAR_FILENAME} * + popd > /dev/null + rm -r ${OUTPUT_PATH} + fi + done + echo "Codesigning Cryptomator.app..." + sed -i '' "s|###APP_IDENTIFIER_PREFIX###|${TEAM_IDENTIFIER}.|g" dist/mac/Cryptomator.entitlements + sed -i '' "s|###TEAM_IDENTIFIER###|${TEAM_IDENTIFIER}|g" dist/mac/Cryptomator.entitlements + codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app + env: + CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} + TEAM_IDENTIFIER: ${{ secrets.MACOS_TEAM_IDENTIFIER }} + - name: Prepare .dmg contents + run: | + mkdir dmg + mv Cryptomator.app dmg + cp dist/mac/dmg/resources/${{ matrix.fuse-lib }}.webloc dmg + ls -l dmg + - name: Install create-dmg + run: | + brew install create-dmg + create-dmg --help + - name: Create .dmg + run: > + create-dmg + --volname Cryptomator + --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns" + --background "dist/mac/dmg/resources/Cryptomator-${{ matrix.fuse-lib }}-background.tiff" + --window-pos 400 100 + --window-size 640 694 + --icon-size 128 + --icon "Cryptomator.app" 128 245 + --hide-extension "Cryptomator.app" + --icon "${{ matrix.fuse-lib }}.webloc" 320 501 + --hide-extension "${{ matrix.fuse-lib }}.webloc" + --app-drop-link 512 245 + --eula "dist/mac/dmg/resources/license.rtf" + --icon ".background" 128 758 + --icon ".VolumeIcon.icns" 512 758 + Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg + env: + VERSION_NO: ${{ needs.get-version.outputs.semVerNum }} + - name: Notarize .dmg + if: startsWith(github.ref, 'refs/tags/') || inputs.notarize + uses: cocoalibs/xcode-notarization-action@v1 + with: + app-path: 'Cryptomator-*.dmg' + apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} + password: ${{ secrets.MACOS_NOTARIZATION_PW }} + team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + xcode-path: '/Applications/Xcode_16.app' + - name: Add possible alpha/beta tags to installer name + run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Clean up codesign certificate + if: ${{ always() }} + run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db + continue-on-error: true + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dmg-${{ matrix.output-suffix }} + path: | + Cryptomator-*.dmg + Cryptomator-*.asc + if-no-files-found: error + - name: Publish dmg on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' + uses: softprops/action-gh-release@v2 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + Cryptomator-*.dmg + Cryptomator-*.asc diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index fdfda1f71..d724e6ed2 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -1,4 +1,4 @@ -name: Build macOS .dmg +name: Build macOS .dmg for arm64 on: release: @@ -15,8 +15,8 @@ on: type: boolean env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' + JAVA_DIST: 'temurin' + JAVA_VERSION: '23.0.1+11' jobs: get-version: @@ -32,20 +32,12 @@ jobs: fail-fast: false matrix: include: - - os: macos-12 - architecture: x64 - output-suffix: x64 - xcode-path: '/Applications/Xcode_13.2.1.app' - fuse-lib: macFUSE - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-x64_bin-jmods.zip' - openjfx-sha: '115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4' - - os: [self-hosted, macOS, ARM64] + - os: macos-15 architecture: aarch64 output-suffix: arm64 - xcode-path: '/Applications/Xcode_13.2.1.app' fuse-lib: FUSE-T - openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-aarch64_bin-jmods.zip' - openjfx-sha: '813c6748f7c99cb7a579d48b48a087b4682b1fad1fc1a4fe5f9b21cf872b15a7' + openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_osx-aarch64_bin-jmods.zip' + openjfx-sha: 'a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6' steps: - uses: actions/checkout@v4 - name: Setup Java @@ -109,7 +101,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.mac" @@ -240,7 +232,7 @@ jobs: apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} password: ${{ secrets.MACOS_NOTARIZATION_PW }} team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - xcode-path: ${{ matrix.xcode-path }} + xcode-path: '/Applications/Xcode_16.app' - name: Add possible alpha/beta tags to installer name run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg - name: Create detached GPG signature with key 615D449FE6E6A235 diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index e6512a52e..5b0cb5111 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -4,8 +4,8 @@ on: pull_request: env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 defaults: run: diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 0454dc08d..448d9a5a4 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -11,8 +11,8 @@ defaults: shell: bash env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 22 + JAVA_DIST: 'temurin' + JAVA_VERSION: 23 jobs: check-preconditions: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 2209de339..14b9dd5aa 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -16,9 +16,9 @@ on: env: JAVA_DIST: 'zulu' - JAVA_VERSION: '22.0.2+9' - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_windows-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6' + JAVA_VERSION: '23.0.1+11' + OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip' + OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' @@ -89,7 +89,7 @@ jobs: --verbose --output runtime --module-path "jfxjmods;${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -110,7 +110,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" @@ -218,7 +218,7 @@ jobs: --dest installer --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2024 Skymatic GmbH" + --copyright "(C) 2016 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}" --win-menu --win-dir-chooser @@ -304,7 +304,7 @@ jobs: -out dist/win/bundle/ -dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" -dBundleVendor="Skymatic GmbH" - -dBundleCopyright="(C) 2016 - 2024 Skymatic GmbH" + -dBundleCopyright="(C) 2016 - 2025 Skymatic GmbH" -dAboutUrl="https://cryptomator.org" -dHelpUrl="https://cryptomator.org/contact" -dUpdateUrl="https://cryptomator.org/downloads/" diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 245eff5d4..6d5a9c57d 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -18,7 +18,7 @@ jobs: env: GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }} - name: Submit package - uses: vedantmgoyal2009/winget-releaser@v2 + uses: vedantmgoyal2009/winget-releaser@main with: identifier: Cryptomator.Cryptomator version: ${{ inputs.tag }} diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 18834c0fc..655ed6260 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,22 +14,23 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index fcc66a657..5b252fc35 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg)](https://snyk.io/test/github/cryptomator/cryptomator) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptomator&metric=alert_status)](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator) -[![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator) +[![Mastodon](https://img.shields.io/mastodon/follow/176112?domain=mastodon.online&style=flat)](https://mastodon.online/@cryptomator) [![Crowdin](https://badges.crowdin.net/cryptomator/localized.svg)](https://translate.cryptomator.org/) [![Latest Release](https://img.shields.io/github/release/cryptomator/cryptomator.svg)](https://github.com/cryptomator/cryptomator/releases/latest) [![Community](https://img.shields.io/badge/help-Community-orange.svg)](https://community.cryptomator.org) @@ -17,20 +17,14 @@ Cryptomator is provided free of charge as an open-source project despite the hig ### Gold Sponsors - - - - - - -
gee-whiz
+Become our Gold Sponsor and showcase your brand to a targeted audience! Please contact us if you are interested. ### Silver Sponsors - + @@ -38,9 +32,9 @@ Cryptomator is provided free of charge as an open-source project despite the hig ### Special Shoutout -Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource). +Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/company/opensource). -MacStadium +MacStadium --- @@ -60,7 +54,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator - File names get encrypted - Folder structure gets obfuscated - Use as many vaults in your Dropbox as you want, each having individual passwords -- Four thousand commits for the security of your data!! :tada: +- More than Five thousand commits for the security of your data!! :tada: ### Privacy @@ -84,7 +78,7 @@ For more information on the security details visit [cryptomator.org](https://doc ### Dependencies -* JDK 22 (e.g. temurin, zulu) +* JDK 23 (e.g. temurin, zulu) * Maven 3 ### Run Maven diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index e815fd5b7..63cdc09f9 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -25,10 +25,10 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods JAVAFX_VERSION=22.0.2 JAVAFX_ARCH="x64" -JAVAFX_JMODS_SHA256='d44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0' +JAVAFX_JMODS_SHA256='2164bca470bf70a5e2764645e2078ba7f787b274e5be3d7df30d87c5bb62bba6' if [ "${CPU_ARCH}" = "aarch64" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256='3d5457136690c4f5bb9522d38b45218e045bdac13c24aa4c808c7c8d17d039c7' + JAVAFX_JMODS_SHA256='09c92fa9fa0b82adefd88640a14ebb2a49e5f3f733a57d1542f5590d060ffe1b' fi # download javaFX jmods @@ -76,7 +76,7 @@ ${JAVA_HOME}/bin/jpackage \ --vendor "Skymatic GmbH" \ --java-options "--enable-preview" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ - --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --copyright "(C) 2016 - 2025 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --app-version "${VERSION}.${REVISION_NO}" \ @@ -91,6 +91,7 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --resource-dir ../resources # transform AppDir @@ -108,13 +109,13 @@ cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/ap cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg -ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon -ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop +ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop +ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun # load AppImageTool -curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage +curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage chmod +x /tmp/appimagetool.AppImage # create AppImage diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index b15ba0356..eb01b6d1e 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -74,6 +74,9 @@ + + https://github.com/cryptomator/cryptomator/releases/1.15.0 + https://github.com/cryptomator/cryptomator/releases/1.14.2 diff --git a/dist/linux/debian/control b/dist/linux/debian/control index 2a9bbae69..96d29acd4 100644 --- a/dist/linux/debian/control +++ b/dist/linux/debian/control @@ -2,7 +2,7 @@ Source: cryptomator Maintainer: Cryptobot Section: utils Priority: optional -Build-Depends: debhelper (>=10), coffeelibs-jdk-22 (>= 22.0.1+8-0ppa1), libgtk-3-0, libxxf86vm1, libgl1 +Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.1+11-0ppa1), libgtk-3-0, libxxf86vm1, libgl1 Standards-Version: 4.5.0 Homepage: https://cryptomator.org Vcs-Git: https://github.com/cryptomator/cryptomator.git diff --git a/dist/linux/debian/copyright b/dist/linux/debian/copyright index 322e549d0..f4155bf5a 100644 --- a/dist/linux/debian/copyright +++ b/dist/linux/debian/copyright @@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator Source: https://cryptomator.org Files: * -Copyright: 2016-2024 Skymatic GmbH +Copyright: 2016-2025 Skymatic GmbH License: GPL-3+ Files: debian/org.cryptomator.Cryptomator.appdata.xml -Copyright: 2016-2024 Skymatic GmbH +Copyright: 2016-2025 Skymatic GmbH License: FSFAP License: GPL-3+ diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index 81d14f66a..a899dba68 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -4,7 +4,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -JAVA_HOME = /usr/lib/jvm/java-22-coffeelibs +JAVA_HOME = /usr/lib/jvm/java-23-coffeelibs DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) ifeq ($(DEB_BUILD_ARCH),amd64) JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods @@ -45,7 +45,7 @@ override_dh_auto_build: --vendor "Skymatic GmbH" \ --java-options "--enable-preview" \ --java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ - --copyright "(C) 2016 - 2024 Skymatic GmbH" \ + --copyright "(C) 2016 - 2025 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dfile.encoding=\"utf-8\"" \ @@ -62,6 +62,7 @@ override_dh_auto_build: --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \ --java-options "-Dcryptomator.integrationsLinux.autoStartCmd=\"cryptomator\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ --verbose diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index bceabbc3f..dbbd818d9 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -24,7 +24,7 @@ rm -rf runtime dmg *.app *.dmg # set variables APP_NAME="Cryptomator" VENDOR="Skymatic GmbH" -COPYRIGHT_YEARS="2016 - 2024" +COPYRIGHT_YEARS="2016 - 2025" PACKAGE_IDENTIFIER="org.cryptomator" MAIN_JAR_GLOB="cryptomator-*.jar" MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" @@ -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=22.0.2 +JAVAFX_VERSION=23.0.1 JAVAFX_ARCH="undefined" JAVAFX_JMODS_SHA256="undefined" if [ "$(machine)" = "arm64e" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256="813c6748f7c99cb7a579d48b48a087b4682b1fad1fc1a4fe5f9b21cf872b15a7" + JAVAFX_JMODS_SHA256="a800724a1f3e6757ecfa0bd5bf7ed64d2e6a7a3f5b3522650a70b8cfc7782fb6" else JAVAFX_ARCH="x64" - JAVAFX_JMODS_SHA256="115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4" + JAVAFX_JMODS_SHA256="8857965975c464a0e5d57709292ce357d0ebb39f6168c41d5ca38301e42c3c8e" fi JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip" diff --git a/dist/mac/dmg/resources/licenseTemplate.ftl b/dist/mac/dmg/resources/licenseTemplate.ftl index 45a523f51..606c1f9b9 100644 --- a/dist/mac/dmg/resources/licenseTemplate.ftl +++ b/dist/mac/dmg/resources/licenseTemplate.ftl @@ -17,7 +17,7 @@ \f1\b0 \ \ -\f0\b \'a9 2016 \'96 2024 Skymatic GmbH +\f0\b \'a9 2016 \'96 2025 Skymatic GmbH \f1\b0 \ \ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\ diff --git a/dist/win/build.bat b/dist/win/build.bat index 6dcb344c0..3532a69fd 100644 --- a/dist/win/build.bat +++ b/dist/win/build.bat @@ -11,7 +11,7 @@ SET HELP_URL="https://cryptomator.org/contact/" SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" SET LOOPBACK_ALIAS="cryptomator-vault" -powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^ +pwsh -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^ -AppName %APPNAME%^ -MainJarGlob "%MAIN_JAR_GLOB%"^ -ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 61da2106e..a733f4557 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -51,23 +51,23 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { } ## download jfx jmods -$javaFxVersion='22.0.2' +$javaFxVersion='23.0.1' $javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" -$javaFxJmodsSHA256 = 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6' +$javaFxJmodsSHA256 = 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3' $javaFxJmods = '.\resources\jfxJmods.zip' if( !(Test-Path -Path $javaFxJmods) ) { Write-Output "Downloading ${javaFxJmodsUrl}..." Invoke-WebRequest $javaFxJmodsUrl -OutFile $javaFxJmods # redirects are followed by default } -$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash +$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash.ToLower() if( $jmodsChecksumActual -ne $javaFxJmodsSHA256 ) { Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $javaFxJmodsSHA256 , actual: $jmodsChecksumActual" exit 1; } Expand-Archive -Path $javaFxJmods -Force -DestinationPath ".\resources\" -Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" +Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" -ErrorAction Ignore Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop ## create custom runtime @@ -75,7 +75,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja --verbose ` --output runtime ` --module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods" ` - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --strip-native-commands ` --no-header-files ` --no-man-pages ` diff --git a/dist/win/bundle/resources/licenseTemplate.ftl b/dist/win/bundle/resources/licenseTemplate.ftl index 6940f9fe6..f0232dc73 100644 --- a/dist/win/bundle/resources/licenseTemplate.ftl +++ b/dist/win/bundle/resources/licenseTemplate.ftl @@ -10,7 +10,7 @@ \vieww12000\viewh15840\viewkind0 \pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par \par -\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par +\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par \par diff --git a/dist/win/contrib/dokan1.dll b/dist/win/contrib/dokan1.dll deleted file mode 100755 index 194945aed..000000000 Binary files a/dist/win/contrib/dokan1.dll and /dev/null differ diff --git a/dist/win/resources/licenseTemplate.ftl b/dist/win/resources/licenseTemplate.ftl index 011fda3cb..de5590ba1 100644 --- a/dist/win/resources/licenseTemplate.ftl +++ b/dist/win/resources/licenseTemplate.ftl @@ -10,7 +10,7 @@ \vieww12000\viewh15840\viewkind0 \pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par \par -\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par +\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par \par diff --git a/pom.xml b/pom.xml index 91e7e79d9..bb9bce0bf 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator cryptomator - 1.14.2 + 1.15.0 Cryptomator Desktop App @@ -26,51 +26,53 @@ UTF-8 - 22 + 23 org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents - 2.7.1 - 1.4.0 + 2.8.0 + 1.5.0 1.3.0 1.2.4 - 1.5.1 + 1.5.2 5.0.2 2.0.7 - 3.16.0 - 2.51.1 + 3.17.0 + 2.55 2.2 - 33.3.0-jre - 2.17.2 - 22.0.2 + 2.18.2 + 23.0.1 4.4.0 9.37.3 - 1.5.7 + 1.5.16 2.0.16 0.8.0 1.9.0 - 5.11.0 - 5.12.0 + 5.11.4 + 5.15.2 3.0 - 24.1.0 - 10.0.3 + 26.0.1 + 12.0.1 0.8.12 - 2.4.0 - 1.3.0 + 2.5.0 + 1.4.0 3.13.0 3.3.1 - 3.7.1 - 3.4.0 + 3.8.1 + 3.5.2 3.4.2 + + + @@ -191,40 +193,23 @@ - - com.google.guava - guava - ${guava.version} - - - - com.google.guava - listenablefuture - - - com.google.code.findbugs - jsr305 - - - org.checkerframework - checker-qual - - - com.google.errorprone - error_prone_annotations - - - com.google.j2objc - j2objc-annotations - - - com.google.dagger dagger ${dagger.version} + + jakarta.inject + jakarta.inject-api + 2.0.1 + + + + com.github.ben-manes.caffeine + caffeine + 3.2.0 + org.junit.jupiter @@ -326,7 +311,6 @@ -Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled - --enable-preview @@ -353,11 +337,11 @@ - --enable-preview plain true + @{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar} @@ -367,6 +351,13 @@ org.apache.maven.plugins maven-dependency-plugin + + jar-paths-to-properties + validate + + properties + + copy-mods @@ -439,6 +430,9 @@ prepare-agent + + surefire.jacoco.args + report diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 3d4194bb3..4dd4242b3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,8 +1,10 @@ import ch.qos.logback.classic.spi.Configurator; +import org.cryptomator.networking.SSLContextWithPKCS12TrustStore; import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider; -import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider; +import org.cryptomator.common.locationpresets.GoogleDriveMacLocationPresetsProvider; +import org.cryptomator.common.locationpresets.GoogleDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider; @@ -12,6 +14,9 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider; +import org.cryptomator.networking.SSLContextWithMacKeychain; +import org.cryptomator.networking.SSLContextProvider; +import org.cryptomator.networking.SSLContextWithWindowsCertStore; import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.logging.LogbackConfiguratorFactory; import org.cryptomator.ui.traymenu.AwtTrayMenuController; @@ -42,21 +47,25 @@ open module org.cryptomator.desktop { requires com.nulabinc.zxcvbn; requires com.tobiasdiez.easybind; requires dagger; - requires java.compiler; requires io.github.coffeelibs.tinyoauth2client; requires org.slf4j; requires org.apache.commons.lang3; - /* TODO: filename-based modules: */ - requires static javax.inject; /* ugly dagger/guava crap */ + /* dagger bs */ + requires jakarta.inject; + requires static javax.inject; + requires java.compiler; + requires com.github.benmanes.caffeine; uses org.cryptomator.common.locationpresets.LocationPresetsProvider; + uses SSLContextProvider; provides TrayMenuController with AwtTrayMenuController; provides Configurator with LogbackConfiguratorFactory; + provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore; provides LocationPresetsProvider with // DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, // - GoogleDriveLocationPresetsProvider, // + GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, // ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, // LeitzcloudLocationPresetsProvider, // MegaLocationPresetsProvider, // diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java index b2af2725f..ac03e5ed6 100644 --- a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java +++ b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java @@ -1,8 +1,7 @@ package org.cryptomator.common.keychain; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; @@ -24,9 +23,9 @@ public class KeychainManager implements KeychainAccessProvider { @Inject KeychainManager(ObjectExpression selectedKeychain) { this.keychain = selectedKeychain; - this.passphraseStoredProperties = CacheBuilder.newBuilder() // + this.passphraseStoredProperties = Caffeine.newBuilder() // .weakValues() // - .build(CacheLoader.from(this::createStoredPassphraseProperty)); + .build(this::createStoredPassphraseProperty); keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll()); } @@ -124,7 +123,7 @@ public class KeychainManager implements KeychainAccessProvider { * @see #isPassphraseStored(String) */ public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) { - return passphraseStoredProperties.getUnchecked(key); + return passphraseStoredProperties.get(key); } private BooleanProperty createStoredPassphraseProperty(String key) { diff --git a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java new file mode 100644 index 000000000..a1d4d0c0e --- /dev/null +++ b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java @@ -0,0 +1,144 @@ +package org.cryptomator.common.locationpresets; + +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; + +@OperatingSystem(MAC) +@CheckAvailability +public final class GoogleDriveMacLocationPresetsProvider implements LocationPresetsProvider { + + private static final Path ROOT_LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage/").toAbsolutePath(); + private static final Predicate PATTERN = Pattern.compile("^GoogleDrive-[^/]+$").asMatchPredicate(); + + private static final List FALLBACK_LOCATIONS = List.of( // + LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), // + LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), // + LocationPresetsProvider.resolveLocation("~/GoogleDrive"), // + LocationPresetsProvider.resolveLocation("~/Google Drive") // + ); + + @Override + public Stream getLocations() { + List cloudStorageDirLocations = getCloudStorageDirLocations(); + return cloudStorageDirLocations.isEmpty() ? getFallbackLocation().stream() : cloudStorageDirLocations.stream(); + + } + + @CheckAvailability + public static boolean isPresent() { + return isRootLocationPresent() || FALLBACK_LOCATIONS.stream().anyMatch(Files::isDirectory); + } + + /** + * Checks if a root location directory is present that matches the specified pattern. + *

+ * This method scans the {@code ROOT_LOCATION} directory for subdirectories and tests each one against a pre-defined pattern ({@code PATTERN}). + * + * @return {@code true} if a matching root location is present, otherwise {@code false}. + */ + public static boolean isRootLocationPresent() { + try (var dirStream = Files.list(ROOT_LOCATION)) { + return dirStream.anyMatch(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString())); + } catch (IOException | UncheckedIOException e) { + return false; + } + } + + /** + * Returns Google Drive preset String. + * + * @param accountPath The path to the Google Drive account directory (e.g. {@code ~/Library/CloudStorage/GoogleDrive-username}) + * @return {@code String}. For example: "Google Drive - username" + */ + private String getDriveLocationString(Path accountPath) { + String accountName = accountPath.getFileName().toString().replace("GoogleDrive-", ""); + return "Google Drive - " + accountName; + } + + /** + * Retrieves a list of cloud storage directory locations based on the {@code ROOT_LOCATION}. + *

+ * This method lists all directories in the {@code ROOT_LOCATION}, filters them based on whether their names match + * a predefined pattern ({@code PATTERN}), and then extracts presets using {@code getPresetsFromAccountPath(Path)}. + *

+ * + * @return a list of {@code LocationPreset} objects representing valid cloud storage directory locations. + */ + private List getCloudStorageDirLocations() { + try (var dirStream = Files.list(ROOT_LOCATION)) { + return dirStream.filter(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString())) + .flatMap(this::getPresetsFromAccountPath) + .toList(); + } catch (IOException | UncheckedIOException e) { + return List.of(); + } + } + + /** + * Retrieves a stream of {@code LocationPreset} objects from a given Google Drive account path. + *

+ * This method lists all directories within the provided {@code accountPath} and filters them + * to identify folders whose names match any of the translations defined in {@code MY_DRIVE_TRANSLATIONS}. + * + * @param accountPath the root path of the Google Drive account to scan. + * @return a stream of {@code LocationPreset} objects representing matching directories. + */ + private Stream getPresetsFromAccountPath(Path accountPath) { + try (var driveStream = Files.list(accountPath)) { + return driveStream + .filter(preset -> MY_DRIVE_TRANSLATIONS + .contains(preset.getFileName().toString())) + .map(drivePath -> new LocationPreset( + getDriveLocationString(accountPath), + drivePath + )).toList().stream(); + } catch (IOException e) { + return Stream.empty(); + } + } + + /** + * Returns a list containing a fallback location preset for Google Drive. + *

+ * This method iterates through the predefined fallback locations, checks if any of them is a directory, + * and creates a {@code LocationPreset} object for the first matching directory found. + * + * @return a list containing a single fallback location preset if a valid directory is found, otherwise an empty list. + * @deprecated This method is intended for legacy support and may be removed in future releases. + */ + @Deprecated + private List getFallbackLocation() { + return FALLBACK_LOCATIONS.stream() + .filter(Files::isDirectory) + .map(location -> new LocationPreset("Google Drive", location)) + .findFirst() + .stream() + .toList(); + } + + /** + * Set of translations for "My Drive" in various languages. + *

+ * This constant is used to identify different language-specific labels for "My Drive" in Google Drive. + *

+ * The translations were originally extracted from the Chromium project’s Chrome OS translation files. + *

+ * Source: `ui/chromeos/translations` directory in the Chromium repository. + */ + private static final Set MY_DRIVE_TRANSLATIONS = Set.of("My Drive", "የእኔ Drive", "ملفاتي", "মোৰ ড্ৰাইভ", "Diskim", "Мой Дыск", "Моят диск", "আমার ড্রাইভ", "Moj disk", "La meva unitat", "Můj disk", "Mit drev", "Meine Ablage", "Το Drive μου", "Mi unidad", "Minu ketas", "Nire unitatea", "Aking Drive", "Oma Drive", "Mon disque", "Mon Drive", "A miña unidade", "મારી ડ્રાઇવ", "मेरी ड्राइव", "Saját meghajtó", "Իմ դրայվը", "Drive Saya", "Drifið mitt", "I miei file", "האחסון שלי", "マイドライブ", "ჩემი Drive", "Менің Drive дискім", "ដ្រាយរបស់ខ្ញុំ", "ನನ್ನ ಡ್ರೈವ್", "내 드라이브", "Менин Drive'ым", "Mano Diskas", "Mans disks", "Мојот Drive", "എന്റെ ഡ്രൈവ്", "Миний Драйв", "माझा ड्राइव्ह", "मेरो ड्राइभ", "Mijn Drive", "Min disk", "ମୋ ଡ୍ରାଇଭ୍", "Mój dysk", "Meu Drive", "O meu disco", "Contul meu Drive", "Мой диск", "මගේ Drive", "Môj disk", "Disku im", "Мој диск", "Min enhet", "Hifadhi Yangu", "எனது இயக்ககம்", "నా డ్రైవ్‌", "ไดรฟ์ของฉัน", "Drive'ım", "Мій диск", "میری ڈرائیو", "Drive của tôi", "我的云端硬盘", "我的雲端硬碟", "IDrayivu yami"); +} diff --git a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java similarity index 85% rename from src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java rename to src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java index 970bea042..fea1632c4 100644 --- a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java +++ b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java @@ -9,13 +9,11 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Stream; -import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS; @OperatingSystem(WINDOWS) -@OperatingSystem(MAC) @CheckAvailability -public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider { +public final class GoogleDriveWindowsLocationPresetsProvider implements LocationPresetsProvider { private static final List LOCATIONS = Arrays.asList( // LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), // @@ -37,5 +35,4 @@ public final class GoogleDriveLocationPresetsProvider implements LocationPresets .findFirst() // .stream(); } - } diff --git a/src/main/java/org/cryptomator/common/mount/Mounter.java b/src/main/java/org/cryptomator/common/mount/Mounter.java index 6ca067305..89f8fb782 100644 --- a/src/main/java/org/cryptomator/common/mount/Mounter.java +++ b/src/main/java/org/cryptomator/common/mount/Mounter.java @@ -160,7 +160,7 @@ public class Mounter { var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue()); if (isConflictingMountService(mountService)) { - var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}"; + var msg = mountService.getClass() + " unavailable due to conflict with either of " + CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName()); throw new ConflictingMountServiceException(msg); } diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java index 24dbed679..907d99885 100644 --- a/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/src/main/java/org/cryptomator/common/settings/Settings.java @@ -32,7 +32,6 @@ public class Settings { private static final Logger LOG = LoggerFactory.getLogger(Settings.class); - static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false; static final boolean DEFAULT_CHECK_FOR_UPDATES = false; static final boolean DEFAULT_START_HIDDEN = false; static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false; @@ -50,11 +49,9 @@ public class Settings { SystemUtils.IS_OS_LINUX ? "org.cryptomator.linux.quickaccess.NautilusBookmarks" : null; static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name(); - static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false; public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z"); + public final ObservableList directories; - public final BooleanProperty askedForUpdateCheck; - public final BooleanProperty checkForUpdates; public final BooleanProperty startHidden; public final BooleanProperty autoCloseVaults; public final BooleanProperty useKeychain; @@ -67,14 +64,16 @@ public class Settings { public final StringProperty quickAccessService; public final ObjectProperty userInterfaceOrientation; public final StringProperty licenseKey; - public final BooleanProperty showMinimizeButton; public final BooleanProperty showTrayIcon; + public final BooleanProperty compactMode; public final IntegerProperty windowXPosition; public final IntegerProperty windowYPosition; public final IntegerProperty windowWidth; public final IntegerProperty windowHeight; public final StringProperty language; public final StringProperty mountService; + public final BooleanProperty checkForUpdates; + public final ObjectProperty lastUpdateCheckReminder; public final ObjectProperty lastSuccessfulUpdateCheck; private Consumer saveCmd; @@ -92,8 +91,6 @@ public class Settings { */ Settings(SettingsJson json) { this.directories = FXCollections.observableArrayList(VaultSettings::observables); - this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck); - this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled); this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden); this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults); this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain); @@ -105,8 +102,8 @@ public class Settings { this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider); this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT)); this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey); - this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton); this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon); + this.compactMode = new SimpleBooleanProperty(this, "compactMode", json.compactMode); this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition); this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition); this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth); @@ -114,6 +111,8 @@ public class Settings { this.language = new SimpleStringProperty(this, "language", json.language); this.mountService = new SimpleStringProperty(this, "mountService", json.mountService); this.quickAccessService = new SimpleStringProperty(this, "quickAccessService", json.quickAccessService); + this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled); + this.lastUpdateCheckReminder = new SimpleObjectProperty<>(this, "lastUpdateCheckReminder", json.lastReminderForUpdateCheck); this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck); this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList()); @@ -121,8 +120,6 @@ public class Settings { migrateLegacySettings(json); directories.addListener(this::somethingChanged); - askedForUpdateCheck.addListener(this::somethingChanged); - checkForUpdates.addListener(this::somethingChanged); startHidden.addListener(this::somethingChanged); autoCloseVaults.addListener(this::somethingChanged); useKeychain.addListener(this::somethingChanged); @@ -134,8 +131,8 @@ public class Settings { keychainProvider.addListener(this::somethingChanged); userInterfaceOrientation.addListener(this::somethingChanged); licenseKey.addListener(this::somethingChanged); - showMinimizeButton.addListener(this::somethingChanged); showTrayIcon.addListener(this::somethingChanged); + compactMode.addListener(this::somethingChanged); windowXPosition.addListener(this::somethingChanged); windowYPosition.addListener(this::somethingChanged); windowWidth.addListener(this::somethingChanged); @@ -143,6 +140,8 @@ public class Settings { language.addListener(this::somethingChanged); mountService.addListener(this::somethingChanged); quickAccessService.addListener(this::somethingChanged); + checkForUpdates.addListener(this::somethingChanged); + lastUpdateCheckReminder.addListener(this::somethingChanged); lastSuccessfulUpdateCheck.addListener(this::somethingChanged); } @@ -177,8 +176,6 @@ public class Settings { SettingsJson serialized() { var json = new SettingsJson(); json.directories = directories.stream().map(VaultSettings::serialized).toList(); - json.askedForUpdateCheck = askedForUpdateCheck.get(); - json.checkForUpdatesEnabled = checkForUpdates.get(); json.startHidden = startHidden.get(); json.autoCloseVaults = autoCloseVaults.get(); json.useKeychain = useKeychain.get(); @@ -190,8 +187,8 @@ public class Settings { json.keychainProvider = keychainProvider.get(); json.uiOrientation = userInterfaceOrientation.get().name(); json.licenseKey = licenseKey.get(); - json.showMinimizeButton = showMinimizeButton.get(); json.showTrayIcon = showTrayIcon.get(); + json.compactMode = compactMode.get(); json.windowXPosition = windowXPosition.get(); json.windowYPosition = windowYPosition.get(); json.windowWidth = windowWidth.get(); @@ -199,6 +196,8 @@ public class Settings { json.language = language.get(); json.mountService = mountService.get(); json.quickAccessService = quickAccessService.get(); + json.checkForUpdatesEnabled = checkForUpdates.get(); + json.lastReminderForUpdateCheck = lastUpdateCheckReminder.get(); json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get(); return json; } diff --git a/src/main/java/org/cryptomator/common/settings/SettingsJson.java b/src/main/java/org/cryptomator/common/settings/SettingsJson.java index 7996dcb39..15f5f2790 100644 --- a/src/main/java/org/cryptomator/common/settings/SettingsJson.java +++ b/src/main/java/org/cryptomator/common/settings/SettingsJson.java @@ -18,15 +18,9 @@ class SettingsJson { @JsonProperty("writtenByVersion") String writtenByVersion; - @JsonProperty("askedForUpdateCheck") - boolean askedForUpdateCheck = Settings.DEFAULT_ASKED_FOR_UPDATE_CHECK; - @JsonProperty("autoCloseVaults") boolean autoCloseVaults = Settings.DEFAULT_AUTO_CLOSE_VAULTS; - @JsonProperty("checkForUpdatesEnabled") - boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES; - @JsonProperty("debugMode") boolean debugMode = Settings.DEFAULT_DEBUG_MODE; @@ -51,12 +45,12 @@ class SettingsJson { @JsonProperty("port") int port = Settings.DEFAULT_PORT; - @JsonProperty("showMinimizeButton") - boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON; - @JsonProperty("showTrayIcon") boolean showTrayIcon; + @JsonProperty("compactMode") + boolean compactMode; + @JsonProperty("startHidden") boolean startHidden = Settings.DEFAULT_START_HIDDEN; @@ -82,6 +76,13 @@ class SettingsJson { @JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233 String preferredVolumeImpl; + @JsonProperty("checkForUpdatesEnabled") + boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES; + + @JsonProperty("lastReminderForUpdateCheck") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + Instant lastReminderForUpdateCheck = Settings.DEFAULT_TIMESTAMP; + @JsonProperty("lastSuccessfulUpdateCheck") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") Instant lastSuccessfulUpdateCheck = Settings.DEFAULT_TIMESTAMP; diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index e65734e68..f857d6ba1 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -44,6 +44,7 @@ import javafx.beans.property.SimpleBooleanProperty; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.ReadOnlyFileSystemException; import java.util.EnumSet; import java.util.Objects; import java.util.Set; @@ -111,15 +112,22 @@ public class Vault { private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException { Set flags = EnumSet.noneOf(FileSystemFlags.class); - if (vaultSettings.usesReadOnlyMode.get()) { + var createReadOnly = vaultSettings.usesReadOnlyMode.get(); + try { + FileSystemCapabilityChecker.assertWriteAccess(getPath()); + } catch (FileSystemCapabilityChecker.MissingCapabilityException e) { + if (!createReadOnly) { + throw new ReadOnlyFileSystemException(); + } + } + if (createReadOnly) { flags.add(FileSystemFlags.READONLY); } else if (vaultSettings.maxCleartextFilenameLength.get() == -1) { LOG.debug("Determining cleartext filename length limitations..."); - var checker = new FileSystemCapabilityChecker(); int shorteningThreshold = configCache.get().allegedShorteningThreshold(); - int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath()); + int ciphertextLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(getPath()); if (ciphertextLimit < shorteningThreshold) { - int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath()); + int cleartextLimit = FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength(getPath()); vaultSettings.maxCleartextFilenameLength.set(cleartextLimit); } else { vaultSettings.maxCleartextFilenameLength.setValue(UNLIMITED_FILENAME_LENGTH); diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index a8d38c312..3e0a613ca 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -9,8 +9,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; -import org.cryptomator.common.SubstitutingProperties; import org.cryptomator.common.ShutdownHook; +import org.cryptomator.common.SubstitutingProperties; +import org.cryptomator.networking.SSLContextProvider; import org.cryptomator.ipc.IpcCommunicator; import org.cryptomator.logging.DebugMode; import org.cryptomator.ui.fxapp.FxApplicationComponent; @@ -19,8 +20,10 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; +import javax.net.ssl.SSLContext; import javafx.application.Application; import javafx.stage.Stage; +import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -48,14 +51,16 @@ public class Cryptomator { private final Environment env; private final Lazy ipcMessageHandler; private final ShutdownHook shutdownHook; + private final SecureRandom csprng; @Inject - Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { + Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook, SecureRandom csprng) { this.debugMode = debugMode; this.supportedLanguages = supportedLanguages; this.env = env; this.ipcMessageHandler = ipcMessageHandler; this.shutdownHook = shutdownHook; + this.csprng = csprng; } public static void main(String[] args) { @@ -89,7 +94,7 @@ public class Cryptomator { LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); supportedLanguages.applyPreferred(); - + changeDefaultSSLContext(); /* * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. * If no external process could be reached, the args will be handled by the loopback IPC endpoint. @@ -115,6 +120,17 @@ public class Cryptomator { } } + private void changeDefaultSSLContext() { + SSLContextProvider.loadAll().findFirst().ifPresent(p -> { + try { + var context = p.getContext(csprng); + SSLContext.setDefault(context); + } catch (SSLContextProvider.SSLContextBuildException e) { + LOG.warn("Failed to change default SSL context with provider {}", p.getClass().getName(), e); + } + }); + } + /** * Launches the JavaFX application, blocking the main thread until shuts down. * diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java index 99477177b..53136e59b 100644 --- a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -20,7 +20,7 @@ public class SupportedLanguages { // "en" is not part of this list - it is always inserted at the top. public static final List LANGUAGE_TAGS = List.of("ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fr", "gl", "he", // "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "nb", "nl", "nn", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", "sr-Latn", "sv", "sw", // - "ta", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW"); + "ta", "th", "tr", "ug", "uk", "vi", "zh", "zh-HK", "zh-TW"); public static final String ENGLISH = "en"; private final List sortedLanguageTags; diff --git a/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java new file mode 100644 index 000000000..87e1b82ef --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java @@ -0,0 +1,33 @@ +package org.cryptomator.networking; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; + +abstract class SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + abstract KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException; + + @Override + public SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException { + try { + KeyStore truststore = getTruststore(); + truststore.load(null, null); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(truststore); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), csprng); + return context; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | KeyManagementException | IOException e) { + throw new SSLContextBuildException(e); + } + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextProvider.java b/src/main/java/org/cryptomator/networking/SSLContextProvider.java new file mode 100644 index 000000000..3563c546c --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextProvider.java @@ -0,0 +1,24 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.IntegrationsLoader; + +import javax.net.ssl.SSLContext; +import java.security.SecureRandom; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +public interface SSLContextProvider { + + SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException; + + class SSLContextBuildException extends Exception { + + SSLContextBuildException(Throwable t) { + super(t); + } + } + + static Stream loadAll() { + return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java new file mode 100644 index 000000000..0078fdfd3 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * SSLContextProvider for macOS using the macOS Keychain as truststore + */ +@OperatingSystem(OperatingSystem.Value.MAC) +public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + return KeyStore.getInstance("KeychainStore-ROOT"); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java new file mode 100644 index 000000000..f530435f8 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java @@ -0,0 +1,42 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Optional; + +/** + * SSLContextProvider for Linux using a PKCS#12 file as trust store + */ +@OperatingSystem(OperatingSystem.Value.LINUX) +@CheckAvailability +public class SSLContextWithPKCS12TrustStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + private static final String CERT_FILE_LOCATION_PROPERTY = "cryptomator.networking.truststore.p12Path"; + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + var pkcs12FilePath = Path.of(System.getProperty(CERT_FILE_LOCATION_PROPERTY)); + try { + return KeyStore.getInstance(pkcs12FilePath.toFile(), new char[]{}); + } catch (IllegalArgumentException e) { + throw new NoSuchFileException(pkcs12FilePath.toString()); + } + } + + @CheckAvailability + public static boolean isSupported() { + var pkcs12Path = System.getProperty(CERT_FILE_LOCATION_PROPERTY); + return Optional.ofNullable(pkcs12Path) // + .map(Path::of) // + .map(Files::exists).orElse(false); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java new file mode 100644 index 000000000..0f1ea04b5 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.security.KeyStore; +import java.security.KeyStoreException; + +/** + * SSLContextProvider for Windows using the Windows certificate store as trust store + *

+ * In order to work, the jdk.crypto.mscapi jmod is needed + */ +@OperatingSystem(OperatingSystem.Value.WINDOWS) +public class SSLContextWithWindowsCertStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException { + return KeyStore.getInstance("WINDOWS-ROOT"); + } + +} diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index f222d92c4..c8b7bee22 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -40,7 +40,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.Optional; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; @@ -50,7 +49,7 @@ public class CreateNewVaultLocationController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); - private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp"; + private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator"; private final Stage window; private final Lazy chooseNameScene; @@ -126,16 +125,19 @@ public class CreateNewVaultLocationController implements FxController { private boolean isActuallyWritable(Path p) { - Path tmpFile = p.resolve(TEMP_FILE_FORMAT); - try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { + Path tmpDir = null; + try { + tmpDir = Files.createTempDirectory(p, TEMP_FILE_PREFIX ); return true; } catch (IOException e) { return false; } finally { - try { - Files.deleteIfExists(tmpFile); - } catch (IOException e) { - LOG.warn("Unable to delete temporary file {}. Needs to be deleted manually.", tmpFile); + if (tmpDir != null) { + try { + Files.deleteIfExists(tmpDir); + } catch (IOException e) { + LOG.warn("Unable to delete temporary directory {}. Needs to be deleted manually.", tmpDir); + } } } } diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 938f93687..6bf8ac7db 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -12,7 +12,6 @@ public enum FxmlFile { CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), // CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), // CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), // - DOKANY_SUPPORT_END("/fxml/dokany_support_end.fxml"), // ERROR("/fxml/error.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // HEALTH_START("/fxml/health_start.fxml"), // @@ -45,8 +44,8 @@ public enum FxmlFile { RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), // RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), // RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), // - REMOVE_VAULT("/fxml/remove_vault.fxml"), // SHARE_VAULT("/fxml/share_vault.fxml"), // + SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), // UPDATE_REMINDER("/fxml/update_reminder.fxml"), // UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"), UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index e454835cf..485e89304 100644 --- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -17,6 +17,7 @@ public enum FontAwesome5Icon { COGS("\uF085"), // COPY("\uF0C5"), // CROWN("\uF521"), // + DONATE("\uF4B9"), // EDIT("\uF044"), // EXCHANGE_ALT("\uF362"), // EXCLAMATION("\uF12A"), // @@ -49,6 +50,7 @@ public enum FontAwesome5Icon { SEARCH("\uF002"), // SHARE("\uF064"), // SPINNER("\uF110"), // + SPONSORS("\uF2B5"), // STETHOSCOPE("\uF0f1"), // SYNC("\uF021"), // TIMES("\uF00D"), // diff --git a/src/main/java/org/cryptomator/ui/controls/NotificationBar.java b/src/main/java/org/cryptomator/ui/controls/NotificationBar.java new file mode 100644 index 000000000..64a08f220 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/controls/NotificationBar.java @@ -0,0 +1,96 @@ +package org.cryptomator.ui.controls; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +public class NotificationBar extends HBox { + + @FXML + private Label notificationLabel; + + private final BooleanProperty dismissable = new SimpleBooleanProperty(); + private final BooleanProperty notify = new SimpleBooleanProperty(); + + + public NotificationBar() { + setAlignment(Pos.CENTER); + setStyle("-fx-alignment: center;"); + + Region spacer = new Region(); + spacer.setMinWidth(40); + + Region leftRegion = new Region(); + HBox.setHgrow(leftRegion, javafx.scene.layout.Priority.ALWAYS); + + Region rightRegion = new Region(); + HBox.setHgrow(rightRegion, javafx.scene.layout.Priority.ALWAYS); + + VBox vbox = new VBox(); + vbox.setAlignment(Pos.CENTER); + HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS); + + notificationLabel = new Label(); + notificationLabel.getStyleClass().add("notification-label"); + notificationLabel.setStyle("-fx-alignment: center;"); + vbox.getChildren().add(notificationLabel); + + Button closeButton = new Button("X"); + closeButton.setMinWidth(40); + closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;"); + closeButton.visibleProperty().bind(dismissable); + + closeButton.setOnAction(_ -> { + visibleProperty().unbind(); + managedProperty().unbind(); + visibleProperty().set(false); + managedProperty().set(false); + }); + closeButton.visibleProperty().bind(dismissable); + + getChildren().addAll(spacer, leftRegion, vbox, rightRegion, closeButton); + + visibleProperty().bind(notifyProperty()); + managedProperty().bind(notifyProperty()); + } + + public String getText() { + return notificationLabel.getText(); + } + + public void setText(String text) { + notificationLabel.setText(text); + } + + public void setStyleClass(String styleClass) { + getStyleClass().clear(); + getStyleClass().add(styleClass); + } + + public boolean isDismissable() { + return dismissable.get(); + } + + public void setDismissable(boolean value) { + dismissable.set(value); + } + + public boolean getNotify() { + return notify.get(); + } + + public void setNotify(boolean value) { + notify.set(value); + } + + public BooleanProperty notifyProperty() { + return notify; + } + +} diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java new file mode 100644 index 000000000..5107fe740 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -0,0 +1,90 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.controls.FontAwesome5Icon; +import org.cryptomator.ui.fxapp.FxApplicationScoped; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.collections.ObservableList; +import javafx.stage.Stage; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +@FxApplicationScoped +public class Dialogs { + + private final ResourceBundle resourceBundle; + private final StageFactory stageFactory; + + @Inject + public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) { + this.resourceBundle = resourceBundle; + this.stageFactory = stageFactory; + } + + private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class); + + private SimpleDialog.Builder createDialogBuilder() { + return new SimpleDialog.Builder(resourceBundle, stageFactory); + } + + public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList vaults) { + return createDialogBuilder().setOwner(window) // + .setTitleKey("removeVault.title", vault.getDisplayName()) // + .setMessageKey("removeVault.message") // + .setDescriptionKey("removeVault.description") // + .setIcon(FontAwesome5Icon.QUESTION) // + .setOkButtonKey("removeVault.confirmBtn") // + .setCancelButtonKey("generic.button.cancel") // + .setOkAction(stage -> { + LOG.debug("Removing vault {}.", vault.getDisplayName()); + vaults.remove(vault); + stage.close(); + }); + } + + public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("removeCert.title") // + .setMessageKey("removeCert.message") // + .setDescriptionKey("removeCert.description") // + .setIcon(FontAwesome5Icon.QUESTION) // + .setOkButtonKey("removeCert.confirmBtn") // + .setCancelButtonKey("generic.button.cancel") // + .setOkAction(stage -> { + settings.licenseKey.set(null); + stage.close(); + }); + } + + public SimpleDialog.Builder prepareDokanySupportEndDialog(Stage window, Consumer cancelAction) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("dokanySupportEnd.title") // + .setMessageKey("dokanySupportEnd.message") // + .setDescriptionKey("dokanySupportEnd.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("generic.button.close") // + .setCancelButtonKey("dokanySupportEnd.preferencesBtn") // + .setOkAction(Stage::close) // + .setCancelAction(cancelAction); + } + + public SimpleDialog.Builder prepareRetryIfReadonlyDialog(Stage window, Consumer okAction) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("retryIfReadonly.title") // + .setMessageKey("retryIfReadonly.message") // + .setDescriptionKey("retryIfReadonly.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("retryIfReadonly.retry") // + .setCancelButtonKey("generic.button.close") // + .setOkAction(okAction) // + .setCancelAction(Stage::close); + } +} diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java new file mode 100644 index 000000000..84d9e4f75 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java @@ -0,0 +1,140 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; +import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.controls.FontAwesome5Icon; + +import javafx.scene.Scene; +import javafx.stage.Modality; +import javafx.stage.Stage; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.IllegalFormatException; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +public class SimpleDialog { + + private final ResourceBundle resourceBundle; + private final Stage dialogStage; + + SimpleDialog(Builder builder) throws IOException { + this.resourceBundle = builder.resourceBundle; + dialogStage = builder.stageFactory.create(); + dialogStage.initOwner(builder.owner); + dialogStage.initModality(Modality.WINDOW_MODAL); + dialogStage.setTitle(resolveText(builder.titleKey, builder.titleArgs)); + dialogStage.setResizable(false); + + FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( // + new SimpleDialogController(resolveText(builder.messageKey, null), // + resolveText(builder.descriptionKey, null), // + builder.icon, resolveText(builder.okButtonKey, null), // + resolveText(builder.cancelButtonKey, null), // + () -> builder.okAction.accept(dialogStage), // + () -> builder.cancelAction.accept(dialogStage)), // + Scene::new, builder.resourceBundle); + + dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot())); + } + + public void showAndWait() { + dialogStage.showAndWait(); + } + + private String resolveText(String key, String[] args) { + if (key == null || key.isEmpty() || !resourceBundle.containsKey(key)) { + throw new IllegalArgumentException(String.format("Invalid key: '%s'. Key not found in ResourceBundle.", key)); + } + String text = resourceBundle.getString(key); + try { + return args != null && args.length > 0 ? String.format(text, (Object[]) args) : text; + } catch (IllegalFormatException e) { + throw new IllegalArgumentException("Formatting error: Check if arguments match placeholders in the text.", e); + } + } + + public static class Builder { + + private Stage owner; + private final ResourceBundle resourceBundle; + private final StageFactory stageFactory; + private String titleKey; + private String[] titleArgs; + private String messageKey; + private String descriptionKey; + private String okButtonKey; + private String cancelButtonKey; + + private FontAwesome5Icon icon; + private Consumer okAction = Stage::close; + private Consumer cancelAction = Stage::close; + + public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) { + this.resourceBundle = resourceBundle; + this.stageFactory = stageFactory; + } + + public Builder setOwner(Stage owner) { + this.owner = owner; + return this; + } + + public Builder setTitleKey(String titleKey, String... args) { + this.titleKey = titleKey; + this.titleArgs = args; + return this; + } + + public Builder setMessageKey(String messageKey) { + this.messageKey = messageKey; + return this; + } + + public Builder setDescriptionKey(String descriptionKey) { + this.descriptionKey = descriptionKey; + return this; + } + + public Builder setIcon(FontAwesome5Icon icon) { + this.icon = icon; + return this; + } + + public Builder setOkButtonKey(String okButtonKey) { + this.okButtonKey = okButtonKey; + return this; + } + + public Builder setCancelButtonKey(String cancelButtonKey) { + this.cancelButtonKey = cancelButtonKey; + return this; + } + + public Builder setOkAction(Consumer okAction) { + this.okAction = okAction; + return this; + } + + public Builder setCancelAction(Consumer cancelAction) { + this.cancelAction = cancelAction; + return this; + } + + public SimpleDialog build() { + Objects.requireNonNull(titleKey, "SimpleDialog titleKey must be set."); + Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set."); + Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set."); + Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set."); + Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set."); + + try { + return new SimpleDialog(this); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create SimpleDialog.", e); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java new file mode 100644 index 000000000..0eee1b308 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java @@ -0,0 +1,61 @@ +package org.cryptomator.ui.dialogs; + +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.controls.FontAwesome5Icon; + +import javafx.fxml.FXML; + +public class SimpleDialogController implements FxController { + + private final String message; + private final String description; + private final FontAwesome5Icon icon; + private final String okButtonText; + private final String cancelButtonText; + private final Runnable okAction; + private final Runnable cancelAction; + + public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) { + this.message = message; + this.description = description; + this.icon = icon; + this.okButtonText = okButtonText; + this.cancelButtonText = cancelButtonText; + this.okAction = okAction; + this.cancelAction = cancelAction; + } + + public String getMessage() { + return message; + } + + public String getDescription() { + return description; + } + + public FontAwesome5Icon getIcon() { + return icon; + } + + public String getOkButtonText() { + return okButtonText; + } + + public String getCancelButtonText() { + return cancelButtonText; + } + + @FXML + private void handleOk() { + if (okAction != null) { + okAction.run(); + } + } + + @FXML + private void handleCancel() { + if (cancelAction != null) { + cancelAction.run(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java deleted file mode 100644 index 48a5ab36c..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javafx.scene.Scene; -import javafx.stage.Stage; - -@DokanySupportEndScoped -@Subcomponent(modules = {DokanySupportEndModule.class}) -public interface DokanySupportEndComponent { - - @DokanySupportEndWindow - Stage window(); - - @FxmlScene(FxmlFile.DOKANY_SUPPORT_END) - Lazy dokanySupportEndScene(); - - - default void showDokanySupportEndWindow() { - Stage stage = window(); - stage.setScene(dokanySupportEndScene().get()); - stage.sizeToScene(); - stage.show(); - } - - @Subcomponent.Factory - interface Factory { - - DokanySupportEndComponent create(); - } -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java deleted file mode 100644 index a1d626402..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndController.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.preferences.SelectedPreferencesTab; - -import javax.inject.Inject; -import javafx.fxml.FXML; -import javafx.stage.Stage; - - -@DokanySupportEndScoped -public class DokanySupportEndController implements FxController { - - private final Stage window; - private final FxApplicationWindows applicationWindows; - - @Inject - DokanySupportEndController(@DokanySupportEndWindow Stage window, FxApplicationWindows applicationWindows) { - this.window = window; - this.applicationWindows = applicationWindows; - } - - @FXML - public void close() { - window.close(); - } - - public void openVolumePreferences() { - applicationWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME); - window.close(); - } - -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java deleted file mode 100644 index 64689eb26..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndModule.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.StageFactory; - -import javax.inject.Provider; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class DokanySupportEndModule { - - @Provides - @DokanySupportEndWindow - @DokanySupportEndScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @DokanySupportEndWindow - @DokanySupportEndScoped - static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setTitle(resourceBundle.getString("dokanySupportEnd.title")); - stage.setMinWidth(500); - stage.setMinHeight(100); - stage.initModality(Modality.APPLICATION_MODAL); - return stage; - } - - @Provides - @FxmlScene(FxmlFile.DOKANY_SUPPORT_END) - @DokanySupportEndScoped - static Scene provideDokanySupportEndScene(@DokanySupportEndWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.DOKANY_SUPPORT_END); - } - - - @Binds - @IntoMap - @FxControllerKey(DokanySupportEndController.class) - abstract FxController bindDokanySupportEndController(DokanySupportEndController controller); - -} diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java deleted file mode 100644 index 967e6f86f..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Scope -@Documented -@Retention(RetentionPolicy.RUNTIME) -@interface DokanySupportEndScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java b/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java deleted file mode 100644 index bb9b1617c..000000000 --- a/src/main/java/org/cryptomator/ui/dokanysupportend/DokanySupportEndWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.dokanysupportend; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Qualifier -@Documented -@Retention(RUNTIME) -@interface DokanySupportEndWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/error/ErrorComponent.java b/src/main/java/org/cryptomator/ui/error/ErrorComponent.java index 554aa65f1..47798c920 100644 --- a/src/main/java/org/cryptomator/ui/error/ErrorComponent.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorComponent.java @@ -20,6 +20,8 @@ public interface ErrorComponent { default Stage show() { Stage stage = window(); stage.setScene(scene()); + stage.setMinWidth(420); + stage.setMinHeight(300); stage.show(); return stage; } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 30e40ea8c..fd480033c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -11,6 +11,8 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; +import java.time.Duration; +import java.time.Instant; import java.util.concurrent.TimeUnit; @FxApplicationScoped @@ -72,8 +74,12 @@ public class FxApplication { return null; }); - if (!environment.disableUpdateCheck()) { - appWindows.checkAndShowUpdateReminderWindow(); + var time14DaysAgo = Instant.now().minus(Duration.ofDays(14)); + if (!environment.disableUpdateCheck() // + && !settings.checkForUpdates.getValue() // + && settings.lastSuccessfulUpdateCheck.get().isBefore(time14DaysAgo) // + && settings.lastUpdateCheckReminder.get().isBefore(time14DaysAgo)) { + appWindows.showUpdateReminderWindow(); } migrateAndInformDokanyRemoval(); diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 0b8b6c70f..af98e284c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -7,7 +7,6 @@ package org.cryptomator.ui.fxapp; import dagger.Module; import dagger.Provides; -import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.lock.LockComponent; @@ -34,7 +33,6 @@ import java.io.InputStream; ErrorComponent.class, // HealthCheckComponent.class, // UpdateReminderComponent.class, // - DokanySupportEndComponent.class, // ShareVaultComponent.class}) abstract class FxApplicationModule { diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 33d8491a7..54acf62a3 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -5,7 +5,7 @@ import dagger.Lazy; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; @@ -48,14 +48,14 @@ public class FxApplicationWindows { private final Lazy preferencesWindow; private final QuitComponent.Builder quitWindowBuilder; private final UnlockComponent.Factory unlockWorkflowFactory; - private final UpdateReminderComponent.Factory updateReminderWindowBuilder; - private final DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder; + private final UpdateReminderComponent.Factory updateReminderWindowFactory; private final LockComponent.Factory lockWorkflowFactory; private final ErrorComponent.Factory errorWindowFactory; private final ExecutorService executor; private final VaultOptionsComponent.Factory vaultOptionsWindow; private final ShareVaultComponent.Factory shareVaultWindow; private final FilteredList visibleWindows; + private final Dialogs dialogs; @Inject public FxApplicationWindows(@PrimaryStage Stage primaryStage, // @@ -64,27 +64,27 @@ public class FxApplicationWindows { Lazy preferencesWindow, // QuitComponent.Builder quitWindowBuilder, // UnlockComponent.Factory unlockWorkflowFactory, // - UpdateReminderComponent.Factory updateReminderWindowBuilder, // - DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder, // + UpdateReminderComponent.Factory updateReminderWindowFactory, // LockComponent.Factory lockWorkflowFactory, // ErrorComponent.Factory errorWindowFactory, // VaultOptionsComponent.Factory vaultOptionsWindow, // ShareVaultComponent.Factory shareVaultWindow, // - ExecutorService executor) { + ExecutorService executor, // + Dialogs dialogs) { this.primaryStage = primaryStage; this.trayIntegration = trayIntegration; this.mainWindow = mainWindow; this.preferencesWindow = preferencesWindow; this.quitWindowBuilder = quitWindowBuilder; this.unlockWorkflowFactory = unlockWorkflowFactory; - this.updateReminderWindowBuilder = updateReminderWindowBuilder; - this.dokanySupportEndWindowBuilder = dokanySupportEndWindowBuilder; + this.updateReminderWindowFactory = updateReminderWindowFactory; this.lockWorkflowFactory = lockWorkflowFactory; this.errorWindowFactory = errorWindowFactory; this.executor = executor; this.vaultOptionsWindow = vaultOptionsWindow; this.shareVaultWindow = shareVaultWindow; this.visibleWindows = Window.getWindows().filtered(Window::isShowing); + this.dialogs = dialogs; } public void initialize() { @@ -142,15 +142,20 @@ public class FxApplicationWindows { CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater); } - public void checkAndShowUpdateReminderWindow() { - CompletableFuture.runAsync(() -> updateReminderWindowBuilder.create().checkAndShowUpdateReminderWindow(), Platform::runLater); + public void showUpdateReminderWindow() { + CompletableFuture.runAsync(() -> updateReminderWindowFactory.create().showUpdateReminderWindow(), Platform::runLater); } public void showDokanySupportEndWindow() { - CompletableFuture.runAsync(() -> dokanySupportEndWindowBuilder.create().showDokanySupportEndWindow(), Platform::runLater); + CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog( + mainWindow.get().window(), + stage -> { + showPreferencesWindow(SelectedPreferencesTab.VOLUME); + stage.close(); + } + ).build().showAndWait(), Platform::runLater); } - public CompletionStage startUnlockWorkflow(Vault vault, @Nullable Stage owner) { return CompletableFuture.supplyAsync(() -> { Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked."); diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java b/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java index aa69e0828..3903734c9 100644 --- a/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java +++ b/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java @@ -25,6 +25,8 @@ public interface HealthCheckComponent { default Stage showHealthCheckWindow() { Stage stage = window(); stage.setScene(startScene().get()); + stage.setMinWidth(420); + stage.setMinHeight(300); stage.show(); return stage; } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java index eefad55a2..4697864f7 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java @@ -20,7 +20,7 @@ public class HubConfig { public String devicesResourceUrl; /** - * A collection of String template processors to construct URIs related to this Hub instance. + * A collection of functions to construct URIs related to this Hub instance. */ @JsonIgnore public final URIProcessors URIs = new URIProcessors(); @@ -49,14 +49,20 @@ public class HubConfig { public class URIProcessors { + public final URIProcessor API = this::fromApiEndpoint; + /** * Resolves paths relative to the /api/ endpoint of this Hub instance. */ - public final StringTemplate.Processor API = template -> { - var path = template.interpolate(); + public URI fromApiEndpoint(String path) { var relPath = path.startsWith("/") ? path.substring(1) : path; return getApiBaseUrl().resolve(relPath); - }; + } + } + @FunctionalInterface + public interface URIProcessor { + + URI resolve(String path); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index 3bfb4ec8e..aa56a22e7 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -88,7 +88,7 @@ public class ReceiveKeyController implements FxController { * STEP 0 (Request): GET /api/config */ private void requestApiConfig() { - var configUri = hubConfig.URIs.API."config"; + var configUri = hubConfig.URIs.API.resolve("config"); var request = HttpRequest.newBuilder(configUri) // .GET() // .timeout(REQ_TIMEOUT) // @@ -122,7 +122,7 @@ public class ReceiveKeyController implements FxController { * STEP 1 (Request): GET user key for this device */ private void requestDeviceData() { - var deviceUri = hubConfig.URIs.API."devices/\{deviceId}"; + var deviceUri = hubConfig.URIs.API.resolve("devices/" + deviceId); var request = HttpRequest.newBuilder(deviceUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // @@ -162,7 +162,7 @@ public class ReceiveKeyController implements FxController { * STEP 2 (Request): GET vault key for this user */ private void requestVaultMasterkey(String encryptedUserKey) { - var vaultKeyUri = hubConfig.URIs.API."vaults/\{vaultId}/access-token"; + var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token"); var request = HttpRequest.newBuilder(vaultKeyUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // @@ -205,7 +205,7 @@ public class ReceiveKeyController implements FxController { */ @Deprecated private void requestLegacyAccessToken() { - var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}"; + var legacyAccessTokenUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/keys/" + deviceId); var request = HttpRequest.newBuilder(legacyAccessTokenUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java index b00d49874..186d06208 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java @@ -115,7 +115,7 @@ public class RegisterDeviceController implements FxController { workInProgress.set(true); - var userReq = HttpRequest.newBuilder(hubConfig.URIs.API."users/me") // + var userReq = HttpRequest.newBuilder(hubConfig.URIs.API.resolve("users/me")) // .GET() // .timeout(REQ_TIMEOUT) // .header("Authorization", "Bearer " + bearerToken) // @@ -143,7 +143,7 @@ public class RegisterDeviceController implements FxController { var now = Instant.now().toString(); var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now); var json = toJson(dto); - var deviceUri = hubConfig.URIs.API."devices/\{deviceId}"; + var deviceUri = hubConfig.URIs.fromApiEndpoint("devices/" + deviceId); var putDeviceReq = HttpRequest.newBuilder(deviceUri) // .PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // .timeout(REQ_TIMEOUT) // @@ -164,7 +164,7 @@ public class RegisterDeviceController implements FxController { private void migrateLegacyDevices(ECPublicKey userPublicKey) { try { // GET legacy access tokens - var getUri = hubConfig.URIs.API."devices/\{deviceId}/legacy-access-tokens"; + var getUri = hubConfig.URIs.API.resolve("devices/" + deviceId + "/legacy-access-tokens"); var getReq = HttpRequest.newBuilder(getUri).GET().timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build(); var getRes = httpClient.send(getReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (getRes.statusCode() != 200) { @@ -185,12 +185,12 @@ public class RegisterDeviceController implements FxController { LOG.warn("Failed to decrypt legacy access token for vault {}. Skipping migration.", entry.getKey()); } }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - var postUri = hubConfig.URIs.API."users/me/access-tokens"; + var postUri = hubConfig.URIs.fromApiEndpoint("users/me/access-tokens"); var postBody = JSON.writer().writeValueAsString(newAccessTokens); var postReq = HttpRequest.newBuilder(postUri).POST(HttpRequest.BodyPublishers.ofString(postBody)).timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build(); var postRes = httpClient.send(postReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (postRes.statusCode() != 200) { - throw new IOException(STR."Unexpected response from POST \{postUri}: \{postRes.statusCode()}"); + throw new IOException("Unexpected response from POST " + postUri + ": " + postRes.statusCode()); } } catch (IOException e) { // log and ignore: this is merely a best-effort attempt of migrating legacy devices. Failure is uncritical as this is merely a convenience feature. diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java index b2c912834..999ff7882 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java @@ -1,19 +1,26 @@ package org.cryptomator.ui.mainwindow; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; import javafx.beans.Observable; +import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.fxml.FXML; import javafx.scene.layout.StackPane; import javafx.stage.Stage; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.LicenseHolder; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultListManager; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.fxapp.UpdateChecker; +import org.cryptomator.ui.preferences.SelectedPreferencesTab; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; @MainWindowScoped public class MainWindowController implements FxController { @@ -22,22 +29,61 @@ public class MainWindowController implements FxController { private final Stage window; private final ReadOnlyObjectProperty selectedVault; + private final Settings settings; + private final FxApplicationWindows appWindows; + private final BooleanBinding updateAvailable; + private final LicenseHolder licenseHolder; - public StackPane root; + @FXML + private StackPane root; @Inject - public MainWindowController(@MainWindow Stage window, ObjectProperty selectedVault) { + public MainWindowController(@MainWindow Stage window, // + ObjectProperty selectedVault, // + Settings settings, // + FxApplicationWindows appWindows, // + UpdateChecker updateChecker, // + LicenseHolder licenseHolder) { this.window = window; this.selectedVault = selectedVault; + this.settings = settings; + this.appWindows = appWindows; + this.updateAvailable = updateChecker.updateAvailableProperty(); + this.licenseHolder = licenseHolder; + updateChecker.automaticallyCheckForUpdatesIfEnabled(); + } @FXML public void initialize() { LOG.trace("init MainWindowController"); + if (SystemUtils.IS_OS_WINDOWS) { root.getStyleClass().add("os-windows"); } window.focusedProperty().addListener(this::mainWindowFocusChanged); + + if (!neverTouched()) { + window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight()); + window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth()); + window.setX(settings.windowXPosition.get()); + window.setY(settings.windowYPosition.get()); + } + window.widthProperty().addListener((_, _, _) -> savePositionalSettings()); + window.heightProperty().addListener((_, _, _) -> savePositionalSettings()); + window.xProperty().addListener((_, _, _) -> savePositionalSettings()); + window.yProperty().addListener((_, _, _) -> savePositionalSettings()); + } + + private boolean neverTouched() { + return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); + } + + public void savePositionalSettings() { + settings.windowWidth.setValue(window.getWidth()); + settings.windowHeight.setValue(window.getHeight()); + settings.windowXPosition.setValue(window.getX()); + settings.windowYPosition.setValue(window.getY()); } private void mainWindowFocusChanged(Observable observable) { @@ -47,4 +93,43 @@ public class MainWindowController implements FxController { } } + @FXML + public void showGeneralPreferences() { + appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL); + } + + @FXML + public void showContributePreferences() { + appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE); + } + + @FXML + public void showUpdatePreferences() { + appWindows.showPreferencesWindow(SelectedPreferencesTab.UPDATES); + } + + public ReadOnlyBooleanProperty debugModeEnabledProperty() { + return settings.debugMode; + } + + public boolean getDebugModeEnabled() { + return debugModeEnabledProperty().get(); + } + + public BooleanBinding updateAvailableProperty() { + return updateAvailable; + } + + public boolean getUpdateAvailable() { + return updateAvailable.get(); + } + + public BooleanBinding licenseValidProperty(){ + return licenseHolder.validLicenseProperty(); + } + + public boolean getLicenseValid() { + return licenseHolder.isValidLicense(); + } + } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index 0b403bb47..b80804f2e 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -6,7 +6,6 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; -import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; @@ -14,10 +13,9 @@ import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageInitializer; +import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.fxapp.PrimaryStage; -import org.cryptomator.ui.health.HealthCheckComponent; import org.cryptomator.ui.migration.MigrationComponent; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.stats.VaultStatisticsComponent; import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; @@ -28,11 +26,10 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; -import javafx.stage.StageStyle; import java.util.Map; import java.util.ResourceBundle; -@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) +@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) abstract class MainWindowModule { @Provides @@ -41,9 +38,8 @@ abstract class MainWindowModule { static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) { initializer.accept(stage); stage.setTitle("Cryptomator"); - stage.initStyle(StageStyle.UNDECORATED); stage.setMinWidth(650); - stage.setMinHeight(440); + stage.setMinHeight(498); return stage; } @@ -85,16 +81,6 @@ abstract class MainWindowModule { @FxControllerKey(MainWindowController.class) abstract FxController bindMainWindowController(MainWindowController controller); - @Binds - @IntoMap - @FxControllerKey(MainWindowTitleController.class) - abstract FxController bindMainWindowTitleController(MainWindowTitleController controller); - - @Binds - @IntoMap - @FxControllerKey(ResizeController.class) - abstract FxController bindResizeController(ResizeController controller); - @Binds @IntoMap @FxControllerKey(VaultListController.class) diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java index d78192186..fbc96513b 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java @@ -18,22 +18,20 @@ public class MainWindowSceneFactory extends DefaultSceneFactory { protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN); - private final Lazy mainWindowTitleController; + private final Stage window; private final Lazy vaultListController; @Inject - public MainWindowSceneFactory(Settings settings, Lazy mainWindowTitleController, Lazy vaultListController) { + public MainWindowSceneFactory(Settings settings, @MainWindow Stage window, Lazy vaultListController) { super(settings); - this.mainWindowTitleController = mainWindowTitleController; + this.window = window; this.vaultListController = vaultListController; } @Override protected void setupDefaultAccelerators(Scene scene, Stage stage) { - if (SystemUtils.IS_OS_WINDOWS) { - scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close); - } else { - scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close); + if (!SystemUtils.IS_OS_WINDOWS) { + scene.getAccelerators().put(SHORTCUT_W, window::close); } scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault); scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java deleted file mode 100644 index f3c92790d..000000000 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowTitleController.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.cryptomator.ui.mainwindow; - -import org.cryptomator.common.LicenseHolder; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplicationTerminator; -import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.fxapp.UpdateChecker; -import org.cryptomator.ui.preferences.SelectedPreferencesTab; -import org.cryptomator.ui.traymenu.TrayMenuComponent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.fxml.FXML; -import javafx.scene.input.MouseButton; -import javafx.scene.layout.HBox; -import javafx.stage.Stage; - -@MainWindowScoped -public class MainWindowTitleController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class); - - private final Stage window; - private final FxApplicationTerminator terminator; - private final FxApplicationWindows appWindows; - private final boolean trayMenuInitialized; - private final UpdateChecker updateChecker; - private final BooleanBinding updateAvailable; - private final LicenseHolder licenseHolder; - private final Settings settings; - private final BooleanBinding showMinimizeButton; - - public HBox titleBar; - private double xOffset; - private double yOffset; - - @Inject - MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) { - this.window = window; - this.terminator = terminator; - this.appWindows = appWindows; - this.trayMenuInitialized = trayMenu.isInitialized(); - this.updateChecker = updateChecker; - this.updateAvailable = updateChecker.updateAvailableProperty(); - this.licenseHolder = licenseHolder; - this.settings = settings; - this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon); - } - - @FXML - public void initialize() { - LOG.trace("init MainWindowTitleController"); - updateChecker.automaticallyCheckForUpdatesIfEnabled(); - titleBar.setOnMousePressed(event -> { - xOffset = event.getSceneX(); - yOffset = event.getSceneY(); - - }); - titleBar.setOnMouseClicked(event -> { - if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) { - window.setFullScreen(!window.isFullScreen()); - } - }); - titleBar.setOnMouseDragged(event -> { - if (window.isFullScreen()) return; - window.setX(event.getScreenX() - xOffset); - window.setY(event.getScreenY() - yOffset); - }); - titleBar.setOnDragDetected(mouseDragEvent -> { - titleBar.startFullDrag(); - }); - titleBar.setOnMouseDragReleased(mouseDragEvent -> { - saveWindowSettings(); - }); - - window.setOnCloseRequest(event -> { - close(); - event.consume(); - }); - } - - private void saveWindowSettings() { - settings.windowXPosition.setValue(window.getX()); - settings.windowYPosition.setValue(window.getY()); - settings.windowWidth.setValue(window.getWidth()); - settings.windowHeight.setValue(window.getHeight()); - } - - @FXML - public void close() { - if (trayMenuInitialized) { - window.close(); - } else { - terminator.terminate(); - } - } - - @FXML - public void minimize() { - window.setIconified(true); - } - - @FXML - public void showPreferences() { - appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY); - } - - @FXML - public void showGeneralPreferences() { - appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL); - } - - @FXML - public void showContributePreferences() { - appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE); - } - - /* Getter/Setter */ - - public LicenseHolder getLicenseHolder() { - return licenseHolder; - } - - public BooleanBinding updateAvailableProperty() { - return updateAvailable; - } - - public boolean isUpdateAvailable() { - return updateAvailable.get(); - } - - public boolean isTrayIconPresent() { - return trayMenuInitialized; - } - - public ReadOnlyBooleanProperty debugModeEnabledProperty() { - return settings.debugMode; - } - - public boolean isDebugModeEnabled() { - return debugModeEnabledProperty().get(); - } - - public BooleanBinding showMinimizeButtonProperty() { - return showMinimizeButton; - } - - public boolean isShowMinimizeButton() { - // always show the minimize button if no tray icon is present OR it is explicitly enabled - return !trayMenuInitialized || settings.showMinimizeButton.get(); - } -} diff --git a/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java b/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java deleted file mode 100644 index 2c3838ea0..000000000 --- a/src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java +++ /dev/null @@ -1,194 +0,0 @@ -package org.cryptomator.ui.mainwindow; - -import org.cryptomator.common.settings.Settings; -import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.beans.binding.BooleanBinding; -import javafx.fxml.FXML; -import javafx.geometry.Rectangle2D; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Region; -import javafx.stage.Screen; -import javafx.stage.Stage; -import javafx.stage.WindowEvent; - -@MainWindow -public class ResizeController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(ResizeController.class); - - private final Stage window; - - public Region tlResizer; - public Region trResizer; - public Region blResizer; - public Region brResizer; - public Region tResizer; - public Region rResizer; - public Region bResizer; - public Region lResizer; - public Region lDefaultRegion; - public Region tDefaultRegion; - public Region rDefaultRegion; - public Region bDefaultRegion; - - private double origX, origY, origW, origH; - - private final Settings settings; - - private final BooleanBinding showResizingArrows; - - @Inject - ResizeController(@MainWindow Stage window, Settings settings) { - this.window = window; - this.settings = settings; - this.showResizingArrows = window.fullScreenProperty().not(); - } - - @FXML - public void initialize() { - LOG.trace("init ResizeController"); - - if (!neverTouched()) { - window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight()); - window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth()); - window.setX(settings.windowXPosition.get()); - window.setY(settings.windowYPosition.get()); - } - - window.setOnShowing(this::checkDisplayBounds); - } - - private boolean neverTouched() { - return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); - } - - private void checkDisplayBounds(WindowEvent evt) { - // Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000) - // See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453 - // If the position is (-32000, -32000), restore to the last saved position - if (window.getX() == -32000 && window.getY() == -32000) { - window.setX(settings.windowXPosition.get()); - window.setY(settings.windowYPosition.get()); - window.setWidth(settings.windowWidth.get()); - window.setHeight(settings.windowHeight.get()); - } - - if (isOutOfDisplayBounds()) { - // If the position is illegal, then the window appears on the main screen in the middle of the window. - LOG.debug("Resetting window position due to insufficient screen overlap"); - Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds(); - window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2); - window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2); - window.setWidth(window.getMinWidth()); - window.setHeight(window.getMinHeight()); - savePositionalSettings(); - } - } - - private boolean isOutOfDisplayBounds() { - // define a rect which is inset on all sides from the window's rect: - final double x = window.getX() + 20; // 20px left - final double y = window.getY() + 5; // 5px top - final double w = window.getWidth() - 40; // 20px left + 20px right - final double h = window.getHeight() - 25; // 5px top + 20px bottom - return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column - || isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column - || isRectangleOutOfScreen(x, y, w, 0) // Top pixel row - || isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row - } - - private boolean isRectangleOutOfScreen(double x, double y, double width, double height) { - return Screen.getScreensForRectangle(x, y, width, height).isEmpty(); - } - - private void startResize(MouseEvent evt) { - origX = window.getX(); - origY = window.getY(); - origW = window.getWidth(); - origH = window.getHeight(); - } - - @FXML - private void resizeTopLeft(MouseEvent evt) { - resizeTop(evt); - resizeLeft(evt); - } - - @FXML - private void resizeTopRight(MouseEvent evt) { - resizeTop(evt); - resizeRight(evt); - } - - @FXML - private void resizeBottomLeft(MouseEvent evt) { - resizeBottom(evt); - resizeLeft(evt); - } - - @FXML - private void resizeBottomRight(MouseEvent evt) { - resizeBottom(evt); - resizeRight(evt); - } - - @FXML - private void resizeTop(MouseEvent evt) { - startResize(evt); - double newY = evt.getScreenY(); - double dy = newY - origY; - double newH = origH - dy; - if (newH < window.getMaxHeight() && newH > window.getMinHeight()) { - window.setY(newY); - window.setHeight(newH); - } - } - - @FXML - private void resizeLeft(MouseEvent evt) { - startResize(evt); - double newX = evt.getScreenX(); - double dx = newX - origX; - double newW = origW - dx; - if (newW < window.getMaxWidth() && newW > window.getMinWidth()) { - window.setX(newX); - window.setWidth(newW); - } - } - - @FXML - private void resizeBottom(MouseEvent evt) { - double newH = evt.getSceneY(); - if (newH < window.getMaxHeight() && newH > window.getMinHeight()) { - window.setHeight(newH); - } - } - - @FXML - private void resizeRight(MouseEvent evt) { - double newW = evt.getSceneX(); - if (newW < window.getMaxWidth() && newW > window.getMinWidth()) { - window.setWidth(newW); - } - } - - @FXML - public void savePositionalSettings() { - settings.windowWidth.setValue(window.getWidth()); - settings.windowHeight.setValue(window.getHeight()); - settings.windowXPosition.setValue(window.getX()); - settings.windowYPosition.setValue(window.getY()); - } - - public BooleanBinding showResizingArrowsProperty() { - return showResizingArrows; - } - - public boolean isShowResizingArrows() { - return showResizingArrows.get(); - } -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index 372d29040..6f57a0d17 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -3,10 +3,11 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.removevault.RemoveVaultComponent; +import org.cryptomator.ui.dialogs.Dialogs; import javax.inject.Inject; import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.FileChooser; import javafx.stage.Stage; @@ -19,17 +20,22 @@ import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; public class VaultDetailMissingVaultController implements FxController { private final ObjectProperty vault; - private final RemoveVaultComponent.Builder removeVault; + private final ObservableList vaults; private final ResourceBundle resourceBundle; private final Stage window; - + private final Dialogs dialogs; @Inject - public VaultDetailMissingVaultController(ObjectProperty vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window) { + public VaultDetailMissingVaultController(ObjectProperty vault, // + ObservableList vaults, // + ResourceBundle resourceBundle, // + @MainWindow Stage window, // + Dialogs dialogs) { this.vault = vault; - this.removeVault = removeVault; + this.vaults = vaults; this.resourceBundle = resourceBundle; this.window = window; + this.dialogs = dialogs; } @FXML @@ -39,7 +45,7 @@ public class VaultDetailMissingVaultController implements FxController { @FXML void didClickRemoveVault() { - removeVault.vault(vault.get()).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait(); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java index 6e40d54b3..503e50ffd 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java @@ -3,12 +3,13 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import javax.inject.Inject; import javax.inject.Named; import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.Stage; @@ -18,14 +19,23 @@ public class VaultDetailUnknownErrorController implements FxController { private final ObjectProperty vault; private final FxApplicationWindows appWindows; private final Stage errorWindow; - private final RemoveVaultComponent.Builder removeVault; + private final ObservableList vaults; + private final Stage mainWindow; + private final Dialogs dialogs; @Inject - public VaultDetailUnknownErrorController(ObjectProperty vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) { + public VaultDetailUnknownErrorController(@MainWindow Stage mainWindow, // + ObjectProperty vault, // + ObservableList vaults, // + FxApplicationWindows appWindows, // + @Named("errorWindow") Stage errorWindow, // + Dialogs dialogs) { + this.mainWindow = mainWindow; this.vault = vault; + this.vaults = vaults; this.appWindows = appWindows; this.errorWindow = errorWindow; - this.removeVault = removeVault; + this.dialogs = dialogs; } @FXML @@ -40,6 +50,6 @@ public class VaultDetailUnknownErrorController implements FxController { @FXML void didClickRemoveVault() { - removeVault.vault(vault.get()).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault.get(), vaults).build().showAndWait(); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java index 6374d7ae3..75ce21dfe 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.mainwindow; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.Animations; @@ -12,21 +13,31 @@ import javax.inject.Inject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.layout.HBox; // unscoped because each cell needs its own controller public class VaultListCellController implements FxController { + private static final Insets COMPACT_INSETS = new Insets(6, 12, 6, 12); + private static final Insets DEFAULT_INSETS = new Insets(12); + private final ObjectProperty vault = new SimpleObjectProperty<>(); private final ObservableValue glyph; + private final ObservableValue compactMode; private AutoAnimator spinAnimation; /* FXML */ public FontAwesome5IconView vaultStateView; + @FXML + public HBox vaultListCell; @Inject - VaultListCellController() { + VaultListCellController(Settings settings) { this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState); + this.compactMode = settings.compactMode; } public void initialize() { @@ -34,6 +45,7 @@ public class VaultListCellController implements FxController { .onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) // .afterStop(() -> vaultStateView.setRotate(0)) // .build(); + this.vaultListCell.paddingProperty().bind(compactMode.map(c -> c ? COMPACT_INSETS : DEFAULT_INSETS)); } // TODO deduplicate w/ VaultDetailController @@ -68,6 +80,14 @@ public class VaultListCellController implements FxController { return vault.get(); } + public ObservableValue compactModeProperty() { + return compactMode; + } + + public boolean getCompactMode() { + return compactMode.getValue(); + } + public void setVault(Vault value) { vault.set(value); } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index a6baa0baf..97c03194f 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -5,8 +5,8 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; @@ -14,6 +14,7 @@ import javax.inject.Inject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.stage.Stage; import java.util.EnumSet; @@ -33,23 +34,32 @@ public class VaultListContextMenuController implements FxController { private final FxApplicationWindows appWindows; private final VaultService vaultService; private final KeychainManager keychain; - private final RemoveVaultComponent.Builder removeVault; private final VaultOptionsComponent.Factory vaultOptionsWindow; private final ObservableValue selectedVaultState; private final ObservableValue selectedVaultPassphraseStored; private final ObservableValue selectedVaultRemovable; private final ObservableValue selectedVaultUnlockable; private final ObservableValue selectedVaultLockable; + private final ObservableList vaults; + private final Dialogs dialogs; @Inject - VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) { + VaultListContextMenuController(ObjectProperty selectedVault, // + ObservableList vaults, // + @MainWindow Stage mainWindow, // + FxApplicationWindows appWindows, // + VaultService vaultService, // + KeychainManager keychain, // + VaultOptionsComponent.Factory vaultOptionsWindow, // + Dialogs dialogs) { this.selectedVault = selectedVault; + this.vaults = vaults; this.mainWindow = mainWindow; this.appWindows = appWindows; this.vaultService = vaultService; this.keychain = keychain; - this.removeVault = removeVault; this.vaultOptionsWindow = vaultOptionsWindow; + this.dialogs = dialogs; this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null); this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false); @@ -65,7 +75,7 @@ public class VaultListContextMenuController implements FxController { @FXML public void didClickRemoveVault() { var vault = Objects.requireNonNull(selectedVault.get()); - removeVault.vault(vault).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait(); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index e8ea21fe4..e353fe8ed 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.mainwindow; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.cryptofs.CryptoFileSystemProvider; @@ -8,8 +9,9 @@ import org.cryptomator.cryptofs.DirStructure; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.removevault.RemoveVaultComponent; +import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +26,6 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Side; -import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; import javafx.scene.control.ListView; import javafx.scene.input.ContextMenuEvent; @@ -33,6 +34,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; +import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.io.File; @@ -64,15 +66,17 @@ public class VaultListController implements FxController { private final VaultListCellFactory cellFactory; private final AddVaultWizardComponent.Builder addVaultWizard; private final BooleanBinding emptyVaultList; - private final RemoveVaultComponent.Builder removeVaultDialogue; private final VaultListManager vaultListManager; private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty(); private final ResourceBundle resourceBundle; private final FxApplicationWindows appWindows; + private final ObservableValue cellSize; + private final Dialogs dialogs; public ListView vaultList; public StackPane root; - public Button addVaultBtn; + @FXML + private HBox addVaultButton; @FXML private ContextMenu addVaultContextMenu; @@ -83,29 +87,36 @@ public class VaultListController implements FxController { VaultListCellFactory cellFactory, // VaultService vaultService, // AddVaultWizardComponent.Builder addVaultWizard, // - RemoveVaultComponent.Builder removeVaultDialogue, // VaultListManager vaultListManager, // ResourceBundle resourceBundle, // - FxApplicationWindows appWindows) { + FxApplicationWindows appWindows, // + Settings settings, // + Dialogs dialogs) { this.mainWindow = mainWindow; this.vaults = vaults; this.selectedVault = selectedVault; this.cellFactory = cellFactory; this.vaultService = vaultService; this.addVaultWizard = addVaultWizard; - this.removeVaultDialogue = removeVaultDialogue; this.vaultListManager = vaultListManager; this.resourceBundle = resourceBundle; this.appWindows = appWindows; + this.dialogs = dialogs; this.emptyVaultList = Bindings.isEmpty(vaults); selectedVault.addListener(this::selectedVaultDidChange); + cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0); } public void initialize() { vaultList.setItems(vaults); vaultList.setCellFactory(cellFactory); + + vaultList.prefHeightProperty().bind( + vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems())) + ); + selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty()); vaults.addListener((ListChangeListener.Change c) -> { while (c.next()) { @@ -171,7 +182,7 @@ public class VaultListController implements FxController { if (addVaultContextMenu.isShowing()) { addVaultContextMenu.hide(); } else { - addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0); + addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0); } } @@ -202,7 +213,7 @@ public class VaultListController implements FxController { private void pressedShortcutToRemoveVault() { final var vault = selectedVault.get(); if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) { - removeVaultDialogue.vault(vault).build().showRemoveVault(); + dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait(); } } @@ -247,6 +258,11 @@ public class VaultListController implements FxController { } } + @FXML + public void showPreferences() { + appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY); + } + // Getter and Setter public BooleanBinding emptyVaultListProperty() { @@ -265,5 +281,12 @@ public class VaultListController implements FxController { return draggingVaultOver.get(); } + public ObservableValue cellSizeProperty() { + return cellSize; + } + + public Double getCellSize() { + return cellSize.getValue(); + } } diff --git a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java index 40983c3f0..573bfc394 100644 --- a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java @@ -36,8 +36,8 @@ public class InterfacePreferencesController implements FxController { private final ResourceBundle resourceBundle; private final SupportedLanguages supportedLanguages; public ChoiceBox themeChoiceBox; - public CheckBox showMinimizeButtonCheckbox; public CheckBox showTrayIconCheckbox; + public CheckBox compactModeCheckbox; public ChoiceBox preferredLanguageChoiceBox; public ToggleGroup nodeOrientation; public RadioButton nodeOrientationLtr; @@ -63,9 +63,8 @@ public class InterfacePreferencesController implements FxController { themeChoiceBox.valueProperty().bindBidirectional(settings.theme); themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); - showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton); - showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon); + compactModeCheckbox.selectedProperty().bindBidirectional(settings.compactMode); preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags()); preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language); diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java index 2d569970d..136479bc0 100644 --- a/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java +++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesComponent.java @@ -31,7 +31,7 @@ public interface PreferencesComponent { Stage stage = window(); stage.setScene(scene().get()); stage.setMinWidth(420); - stage.setMinHeight(300); + stage.setMinHeight(400); stage.show(); stage.requestFocus(); return stage; diff --git a/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java b/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java index bff56bc3c..b36d7454e 100644 --- a/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java +++ b/src/main/java/org/cryptomator/ui/preferences/SupporterCertificateController.java @@ -5,6 +5,7 @@ import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.UiTheme; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.dialogs.Dialogs; import javax.inject.Inject; import javafx.application.Application; @@ -12,22 +13,36 @@ import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.control.TextFormatter; +import javafx.stage.Stage; + @PreferencesScoped public class SupporterCertificateController implements FxController { + private static final String DONATE_URI = "https://cryptomator.org/donate"; + private static final String SPONSORS_URI = "https://cryptomator.org/sponsors"; private static final String SUPPORTER_URI = "https://store.cryptomator.org/desktop"; private final Application application; + private final Stage window; private final LicenseHolder licenseHolder; private final Settings settings; - public TextArea supporterCertificateField; + private final Dialogs dialogs; + + @FXML + private TextArea supporterCertificateField; @Inject - SupporterCertificateController(Application application, LicenseHolder licenseHolder, Settings settings) { + SupporterCertificateController(Application application, // + @PreferencesWindow Stage window, // + LicenseHolder licenseHolder, // + Settings settings, // + Dialogs dialogs) { this.application = application; + this.window = window; this.licenseHolder = licenseHolder; this.settings = settings; + this.dialogs = dialogs; } @FXML @@ -35,6 +50,11 @@ public class SupporterCertificateController implements FxController { supporterCertificateField.setText(licenseHolder.getLicenseKey().orElse(null)); supporterCertificateField.textProperty().addListener(this::registrationKeyChanged); supporterCertificateField.setTextFormatter(new TextFormatter<>(this::removeWhitespaces)); + settings.licenseKey.addListener((_, _, newValue) -> { + if (newValue == null) { + supporterCertificateField.setText(null); + } + }); } private TextFormatter.Change removeWhitespaces(TextFormatter.Change change) { @@ -57,7 +77,23 @@ public class SupporterCertificateController implements FxController { application.getHostServices().showDocument(SUPPORTER_URI); } + @FXML + public void showDonate() { + application.getHostServices().showDocument(DONATE_URI); + } + + @FXML + public void showSponsors() { + application.getHostServices().showDocument(SPONSORS_URI); + } + + @FXML + void didClickRemoveCert() { + dialogs.prepareRemoveCertDialog(window, settings).build().showAndWait(); + } + public LicenseHolder getLicenseHolder() { return licenseHolder; } + } diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java deleted file mode 100644 index c8b3fd574..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.cryptomator.ui.removevault; - -import dagger.BindsInstance; -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javafx.scene.Scene; -import javafx.stage.Stage; - -@RemoveVaultScoped -@Subcomponent(modules = {RemoveVaultModule.class}) -public interface RemoveVaultComponent { - - @RemoveVaultWindow - Stage window(); - - @FxmlScene(FxmlFile.REMOVE_VAULT) - Lazy scene(); - - default void showRemoveVault() { - Stage stage = window(); - stage.setScene(scene().get()); - stage.sizeToScene(); - stage.show(); - } - - @Subcomponent.Builder - interface Builder { - - @BindsInstance - Builder vault(@RemoveVaultWindow Vault vault); - - RemoveVaultComponent build(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java deleted file mode 100644 index e486d65f0..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultController.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cryptomator.ui.removevault; - -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.stage.Stage; - -@RemoveVaultScoped -public class RemoveVaultController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(RemoveVaultController.class); - - private final Stage window; - private final Vault vault; - private final ObservableList vaults; - - @Inject - public RemoveVaultController(@RemoveVaultWindow Stage window, @RemoveVaultWindow Vault vault, ObservableList vaults) { - this.window = window; - this.vault = vault; - this.vaults = vaults; - } - - @FXML - public void close() { - window.close(); - } - - @FXML - public void finish() { - vaults.remove(vault); - LOG.debug("Removing vault {}.", vault.getDisplayName()); - window.close(); - } -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java deleted file mode 100644 index 2fcac4add..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.cryptomator.ui.removevault; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.fxapp.PrimaryStage; - -import javax.inject.Provider; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class RemoveVaultModule { - - @Provides - @RemoveVaultWindow - @RemoveVaultScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @RemoveVaultWindow - @RemoveVaultScoped - static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @RemoveVaultWindow Vault vault, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setTitle(String.format(resourceBundle.getString("removeVault.title"), vault.getDisplayName())); - stage.setResizable(false); - stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(primaryStage); - return stage; - } - - @Provides - @FxmlScene(FxmlFile.REMOVE_VAULT) - @RemoveVaultScoped - static Scene provideRemoveVaultScene(@RemoveVaultWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.REMOVE_VAULT); - } - - // ------------------ - - @Binds - @IntoMap - @FxControllerKey(RemoveVaultController.class) - abstract FxController bindRemoveVaultController(RemoveVaultController controller); -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java deleted file mode 100644 index aa2281541..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.removevault; - -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Scope -@Documented -@Retention(RetentionPolicy.RUNTIME) -public @interface RemoveVaultScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java b/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java deleted file mode 100644 index 7348e213b..000000000 --- a/src/main/java/org/cryptomator/ui/removevault/RemoveVaultWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.removevault; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Qualifier -@Documented -@Retention(RUNTIME) -@interface RemoveVaultWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 804f4cd67..4b78bf693 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -10,6 +10,7 @@ import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; @@ -25,6 +26,8 @@ import javafx.scene.Scene; import javafx.stage.Screen; import javafx.stage.Stage; import java.io.IOException; +import java.nio.file.ReadOnlyFileSystemException; +import java.util.concurrent.TimeUnit; /** * A multi-step task that consists of background activities as well as user interaction. @@ -46,6 +49,7 @@ public class UnlockWorkflow extends Task { private final FxApplicationWindows appWindows; private final KeyLoadingStrategy keyLoadingStrategy; private final ObjectProperty illegalMountPointException; + private final Dialogs dialogs; @Inject UnlockWorkflow(@PrimaryStage Stage mainWindow, // @@ -57,7 +61,8 @@ public class UnlockWorkflow extends Task { @FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy restartRequiredScene, // FxApplicationWindows appWindows, // @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, // - @UnlockWindow ObjectProperty illegalMountPointException) { + @UnlockWindow ObjectProperty illegalMountPointException, // + Dialogs dialogs) { this.mainWindow = mainWindow; this.window = window; this.vault = vault; @@ -68,6 +73,7 @@ public class UnlockWorkflow extends Task { this.appWindows = appWindows; this.keyLoadingStrategy = keyLoadingStrategy; this.illegalMountPointException = illegalMountPointException; + this.dialogs = dialogs; } @Override @@ -144,11 +150,36 @@ public class UnlockWorkflow extends Task { switch (throwable) { case IllegalMountPointException e -> handleIllegalMountPointError(e); case ConflictingMountServiceException _ -> handleConflictingMountServiceException(); + case ReadOnlyFileSystemException _ -> handleReadOnlyFileSystem(); default -> handleGenericError(throwable); } vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED); } + private void handleReadOnlyFileSystem() { + var readOnlyDialog = dialogs.prepareRetryIfReadonlyDialog(mainWindow, stage -> { + stage.close(); + this.retry(); + }).build(); + + Platform.runLater(readOnlyDialog::showAndWait); + } + + private void retry() { + try { + vault.getVaultSettings().usesReadOnlyMode.set(true); + var isLocked = vault.stateProperty().awaitState(VaultState.Value.LOCKED, 5, TimeUnit.SECONDS); + if (!isLocked) { + LOG.error("Vault did not changed to LOCKED state within 5 seconds. Aborting unlock retry."); + } else { + appWindows.startUnlockWorkflow(vault, mainWindow); + } + } catch (InterruptedException e) { + LOG.error("Waiting for LOCKED vault state was interrupted. Aborting unlock retry.", e); + Thread.currentThread().interrupt(); + } + } + @Override protected void cancelled() { LOG.debug("Unlock of '{}' canceled.", vault.getDisplayName()); diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java index d2c10f8fd..c27c98382 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java @@ -2,14 +2,11 @@ package org.cryptomator.ui.updatereminder; import dagger.Lazy; import dagger.Subcomponent; -import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import javafx.scene.Scene; import javafx.stage.Stage; -import java.time.Duration; -import java.time.Instant; @UpdateReminderScoped @Subcomponent(modules = {UpdateReminderModule.class}) @@ -21,16 +18,11 @@ public interface UpdateReminderComponent { @FxmlScene(FxmlFile.UPDATE_REMINDER) Lazy updateReminderScene(); - Settings settings(); - - default void checkAndShowUpdateReminderWindow() { - var now = Instant.now(); - if (!settings().checkForUpdates.getValue() && settings().lastSuccessfulUpdateCheck.get().isBefore(now.minus(Duration.ofDays(14)))) { - Stage stage = window(); - stage.setScene(updateReminderScene().get()); - stage.sizeToScene(); - stage.show(); - } + default void showUpdateReminderWindow() { + Stage stage = window(); + stage.setScene(updateReminderScene().get()); + stage.sizeToScene(); + stage.show(); } @Subcomponent.Factory diff --git a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java index 183298c44..a6fce0b79 100644 --- a/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java +++ b/src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java @@ -7,6 +7,7 @@ import org.cryptomator.ui.fxapp.UpdateChecker; import javax.inject.Inject; import javafx.fxml.FXML; import javafx.stage.Stage; +import java.time.Instant; @UpdateReminderScoped public class UpdateReminderController implements FxController { @@ -23,6 +24,11 @@ public class UpdateReminderController implements FxController { this.updateChecker = updateChecker; } + @FXML + public void initialize() { + settings.lastUpdateCheckReminder.set(Instant.now()); + } + @FXML public void cancel() { window.close(); diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java index 59a52adf2..5fe6746d7 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java @@ -57,9 +57,14 @@ public class GeneralVaultOptionsController implements FxController { } private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) { + var displayNameSetting = vault.getVaultSettings().displayName; if (!isFocussed) { var trimmed = vaultName.getText().trim(); - vault.getVaultSettings().displayName.set(trimmed); + if (!trimmed.isEmpty()) { + displayNameSetting.set(trimmed); //persist changes + } else { + vaultName.setText(displayNameSetting.get()); //revert changes + } } } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 106623985..a9a2042a3 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -17,6 +17,8 @@ import org.cryptomator.ui.preferences.VolumePreferencesController; import javax.inject.Inject; import javafx.application.Application; import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -50,7 +52,6 @@ public class MountOptionsController implements FxController { private final ObservableValue defaultMountFlags; private final ObservableValue mountpointDirSupported; private final ObservableValue mountpointDriveLetterSupported; - private final ObservableValue readOnlySupported; private final ObservableValue mountFlagsSupported; private final ObservableValue defaultMountServiceSelected; private final ObservableValue directoryPath; @@ -60,6 +61,7 @@ public class MountOptionsController implements FxController { private final ObservableValue selectedMountService; private final ObservableValue selectedMountServiceRequiresRestart; private final ObservableValue loopbackPortChangeable; + private final ObservableBooleanValue readOnlyOptionAllowed; //-- FXML objects -- @@ -108,10 +110,10 @@ public class MountOptionsController implements FxController { }); this.mountFlagsSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_FLAGS)); this.defaultMountServiceSelected = ObservableUtil.mapWithDefault(vaultSettings.mountService, _ -> false, true); - this.readOnlySupported = selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY)); this.mountpointDirSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_TO_EXISTING_DIR) || s.hasCapability(MountCapability.MOUNT_WITHIN_EXISTING_PARENT)); this.mountpointDriveLetterSupported = selectedMountService.map(s -> s.hasCapability(MountCapability.MOUNT_AS_DRIVE_LETTER)); this.loopbackPortChangeable = selectedMountService.map(s -> s.hasCapability(MountCapability.LOOPBACK_PORT) && vaultSettings.mountService.getValue() != null); + this.readOnlyOptionAllowed = BooleanBinding.booleanExpression(selectedMountService.map(s -> s.hasCapability(MountCapability.READ_ONLY))).or(vaultSettings.usesReadOnlyMode); } private MountService reselectMountService() { @@ -345,12 +347,12 @@ public class MountOptionsController implements FxController { return mountpointDriveLetterSupported.getValue(); } - public ObservableValue readOnlySupportedProperty() { - return readOnlySupported; + public ObservableValue readOnlyOptionAllowedProperty() { + return readOnlyOptionAllowed; } - public boolean isReadOnlySupported() { - return readOnlySupported.getValue(); + public boolean isReadOnlyOptionAllowed() { + return readOnlyOptionAllowed.getValue(); } public ObservableValue directoryPathProperty() { diff --git a/src/main/resources/css/dark_theme.css b/src/main/resources/css/dark_theme.css index 86d9aaa71..dae0d1898 100644 --- a/src/main/resources/css/dark_theme.css +++ b/src/main/resources/css/dark_theme.css @@ -100,6 +100,7 @@ .label-extra-large { -fx-font-family: 'Open Sans SemiBold'; + -fx-fill: TEXT_FILL; -fx-font-size: 1.5em; } @@ -181,31 +182,20 @@ -fx-border-width: 1px; } -.main-window .title { - -fx-background-color: CONTROL_BORDER_NORMAL, TITLE_BG; - -fx-background-insets: 0, 0 0 1px 0; +.main-window .button-bar { + -fx-background-color: MAIN_BG; + -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; + -fx-border-width: 1px 0 0 0; } -.main-window .title .button { - -fx-pref-height: 30px; - -fx-pref-width: 30px; - -fx-background-color: none; - -fx-padding: 0; +.main-window .button-left { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-width: 0 1px 0 0; } -.main-window .title .button .glyph-icon { - -fx-fill: white; -} - -.main-window .title .button:armed .glyph-icon { - -fx-fill: GRAY_8; -} - -.main-window .update-indicator { - -fx-background-color: white, RED_5; - -fx-background-insets: 1px, 2px; - -fx-background-radius: 6px, 5px; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0); +.main-window .button-right { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-width: 0 0 0 1px; } /******************************************************************************* @@ -322,23 +312,33 @@ -fx-fill: transparent; } -.button.toolbar-button { - -fx-min-height: 40px; - -fx-background-color: transparent; - -fx-background-insets: 0; - -fx-background-radius: 0; - -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; - -fx-border-width: 1px 0 0 0; - -fx-padding: 0; +/******************************************************************************* + * * + * NotificationBar * + * * + ******************************************************************************/ + +.notification-label { + -fx-text-fill: white; + -fx-font-weight: bold; } -.button.toolbar-button:focused { - -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; - -fx-background-insets: 0, 2px 1px 1px 1px; +.notification-debug { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: RED_5; } -.button.toolbar-button:armed { - -fx-background-color: CONTROL_BG_ARMED; +.notification-update { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: YELLOW_5; +} + +.notification-support { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: PRIMARY; } /******************************************************************************* @@ -394,16 +394,6 @@ -fx-background-color: MUTED_BG; } -/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/ -.badge-debug { - -fx-font-family: 'Open Sans Bold'; - -fx-font-size: 1.0em; - -fx-background-radius: 8px; - -fx-padding: 0.3em 0.55em 0.3em 0.55em; - -fx-background-color: RED_5; - -fx-background-radius: 2em; -} - /******************************************************************************* * * * Password Strength Indicator * diff --git a/src/main/resources/css/light_theme.css b/src/main/resources/css/light_theme.css index 88cf6d970..84df05b10 100644 --- a/src/main/resources/css/light_theme.css +++ b/src/main/resources/css/light_theme.css @@ -177,34 +177,24 @@ /* windows needs an explicit border: */ .main-window.os-windows { - -fx-border-color: TITLE_BG; - -fx-border-width: 1px; + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-width: 1px 0 0 0; } -.main-window .title { - -fx-background-color: TITLE_BG; +.main-window .button-bar { + -fx-background-color: MAIN_BG; + -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; + -fx-border-width: 1px 0 0 0; } -.main-window .title .button { - -fx-pref-height: 30px; - -fx-pref-width: 30px; - -fx-background-color: none; - -fx-padding: 0; +.main-window .button-bar .button-left { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-width: 0 1px 0 0; } -.main-window .title .button .glyph-icon { - -fx-fill: white; -} - -.main-window .title .button:armed .glyph-icon { - -fx-fill: GRAY_8; -} - -.main-window .update-indicator { - -fx-background-color: white, RED_5; - -fx-background-insets: 1px, 2px; - -fx-background-radius: 6px, 5px; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0); +.main-window .button-bar .button-right { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-width: 0 0 0 1px; } /******************************************************************************* @@ -321,23 +311,33 @@ -fx-fill: transparent; } -.button.toolbar-button { - -fx-min-height: 40px; - -fx-background-color: transparent; - -fx-background-insets: 0; - -fx-background-radius: 0; - -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent; - -fx-border-width: 1px 0 0 0; - -fx-padding: 0; +/******************************************************************************* + * * + * NotificationBar * + * * + ******************************************************************************/ + +.notification-label { + -fx-text-fill: white; + -fx-font-weight: bold; } -.button.toolbar-button:focused { - -fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; - -fx-background-insets: 0, 2px 1px 1px 1px; +.notification-debug { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: RED_5; } -.button.toolbar-button:armed { - -fx-background-color: CONTROL_BG_ARMED; +.notification-update { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: YELLOW_5; +} + +.notification-support { + -fx-min-height:24px; + -fx-max-height:24px; + -fx-background-color: PRIMARY; } /******************************************************************************* @@ -393,16 +393,6 @@ -fx-background-color: MUTED_BG; } -/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/ -.badge-debug { - -fx-font-family: 'Open Sans Bold'; - -fx-font-size: 1.0em; - -fx-background-radius: 8px; - -fx-padding: 0.3em 0.55em 0.3em 0.55em; - -fx-background-color: RED_5; - -fx-background-radius: 2em; -} - /******************************************************************************* * * * Password Strength Indicator * diff --git a/src/main/resources/fxml/dokany_support_end.fxml b/src/main/resources/fxml/dokany_support_end.fxml deleted file mode 100644 index 423a54c72..000000000 --- a/src/main/resources/fxml/dokany_support_end.fxml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml index 1555de504..52c620f21 100644 --- a/src/main/resources/fxml/preferences_about.fxml +++ b/src/main/resources/fxml/preferences_about.fxml @@ -17,12 +17,12 @@ - - + + - diff --git a/src/main/resources/fxml/preferences_contribute.fxml b/src/main/resources/fxml/preferences_contribute.fxml index 55d40b495..a6a52bf4e 100644 --- a/src/main/resources/fxml/preferences_contribute.fxml +++ b/src/main/resources/fxml/preferences_contribute.fxml @@ -3,13 +3,18 @@ + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + +

Mow Capitalgee-whiz Route4Me