Compare commits

...
Author SHA1 Message Date
Armin Schrenk c0f3facab8 correct typos 2025-03-21 15:45:04 +01:00
Armin Schrenk b9512c9e93 cleanup 2025-03-21 15:37:04 +01:00
Armin Schrenk dd724220f8 enable clear() again 2025-03-21 15:31:58 +01:00
Armin Schrenk 94410f1839 more renaming 2025-03-21 15:24:40 +01:00
Armin Schrenk cc8aa00326 Renamed classes 2025-03-21 15:03:31 +01:00
Armin Schrenk e8e2fcb0b3 rename EventRegistry to Aggregator 2025-03-21 14:51:53 +01:00
Armin Schrenk 151b35355d no size restriction/lru needed 2025-03-21 14:49:19 +01:00
Armin Schrenk 37d5353f77 run the eventUpdate from app start 2025-03-21 13:46:20 +01:00
Armin Schrenk 7b6953445f remove comments 2025-03-21 13:45:59 +01:00
Armin Schrenk fcd3db63ce use a preemptive update status to not miss updates 2025-03-21 13:38:00 +01:00
Armin Schrenk d67085d57d doodles 2025-03-20 16:41:30 +01:00
Armin Schrenk 1f60d9f5e8 remove notification, if eventView is focused 2025-03-19 15:13:51 +01:00
Armin Schrenk 5378769467 adjust update-indicator 2025-03-19 15:12:40 +01:00
Armin Schrenk 913ed5e109 removed unused class 2025-03-19 14:53:24 +01:00
Armin Schrenk f4cfe19fdc more cleanup 2025-03-19 12:41:45 +01:00
Armin Schrenk a001dfd8a8 add equals to FileSystemEventBucket to ensure correct removal in collections 2025-03-19 12:30:23 +01:00
Armin Schrenk 893a4bcae9 the great rename 2025-03-19 12:25:26 +01:00
Armin Schrenk e61fb74367 removed unused classes 2025-03-19 12:06:02 +01:00
Armin Schrenk fbbbc1cb40 cleanup 2025-03-19 11:58:04 +01:00
Armin Schrenk f27f3c46c1 decouple eventMap insertion from event happening (to not spam FX thread) 2025-03-19 11:55:29 +01:00
Armin Schrenk 83b557b6be removed dead code 2025-03-18 18:02:55 +01:00
Armin Schrenk 32a0df06d0 move eventMap to event package 2025-03-18 18:02:46 +01:00
Armin Schrenk 5350e07f62 renamed VaultEventsMap.Key parameters for clarity 2025-03-18 17:57:15 +01:00
Armin Schrenk cc5c46743b Refactor EventMap:
* renamed to VaultEventsMap
* split between boilerplate and buissness logic (ObservableMapDecorator)
* add LRU cache
* fixed VaultEvent compareTo method
2025-03-18 17:55:32 +01:00
Armin Schrenk bc7f3fe7db more renaming 2025-03-18 12:17:39 +01:00
Armin Schrenk 750ca3f39c also key the eventMap for vault 2025-03-18 12:13:25 +01:00
Armin Schrenk 2bbffc3623 rename eventMap to make purpose clearer 2025-03-18 12:10:44 +01:00
Armin Schrenk 36dd98127d update to cryptofs 2.9.0-beta2 2025-03-18 11:38:52 +01:00
Armin Schrenk 6d55331ef2 add missing translation key 2025-03-13 10:36:40 +01:00
Armin Schrenk 181e45a596 renamed event list item localization keys 2025-03-12 15:27:24 +01:00
Armin Schrenk 3c8e506805 add tooltip for clear all button 2025-03-12 15:21:32 +01:00
Armin Schrenk f0a7379575 hide count if vault is locked 2025-03-12 10:50:36 +01:00
Armin Schrenk 0d12b11c48 cleanup 2025-03-12 10:01:09 +01:00
Armin Schrenk 14a025f769 size restrict the eventMap 2025-03-12 10:00:56 +01:00
Armin Schrenk 598fc9bbc8 complete themes 2025-03-12 10:00:34 +01:00
Armin Schrenk fbab2df00f add notification marker for new events 2025-03-12 09:38:56 +01:00
Armin Schrenk 48c2d49c86 revert default version string 2025-03-11 17:07:46 +01:00
Armin Schrenk 00d68393f6 use beta version of cryptoFs 2025-03-11 17:07:17 +01:00
Armin Schrenk 7a50ba0b43 Merge branch 'develop' into feature/event-view 2025-03-11 17:03:58 +01:00
Armin SchrenkandGitHub b2bee99286 Refactor: Out-of-bounds-check (#3778)
fixes #3723, fixes #3722
2025-03-11 16:03:49 +01:00
Armin Schrenk 88d384afdf reduce tooltip show duration 2025-03-07 11:50:53 +01:00
Armin Schrenk 7af82b831f add event count to viewer 2025-03-07 11:50:40 +01:00
Armin Schrenk a86f42fa44 replace eventList with a eventMap in model to group events 2025-03-07 11:21:23 +01:00
Sebastian Stenzel 70b0413777 replace labels with issue type
see https://github.blog/changelog/2025-01-13-evolving-github-issues-public-preview/#organize-your-work-with-issue-types
[ci skip]
2025-03-07 09:32:39 +01:00
Sebastian Stenzel 577a77af38 moved summary to the top for better link previews 2025-03-07 09:31:35 +01:00
Armin Schrenk 3ba7e9ba00 cleanup 2025-03-06 12:55:12 +01:00
Armin Schrenk b2250e8ce0 add Tooltip 2025-03-06 12:54:58 +01:00
Armin Schrenk f2a480c7a0 reworked event cell texts 2025-03-06 12:40:02 +01:00
Armin Schrenk bda38096e1 fix minor inconsitencies 2025-03-04 12:34:45 +01:00
Armin Schrenk 7e66a61294 add choicebox to filter for single vaults 2025-03-04 12:34:17 +01:00
clkueandGitHub 3aa627a467 fix unicode escaping for rtf file (#3609)
Signed-off-by: C1emency <>
Signed-off-by: clkue <clemens_kuennemann@protonmail.com>
2025-03-03 14:55:09 +01:00
Armin SchrenkandGitHub ae1b5fc925 Switch to ubuntu-22.04 for building the deb package 2025-03-03 12:05:25 +01:00
mindmonkandGitHub b1076f9c86 Merge pull request #3764 from cryptomator/feature/dialogBuilder-one-Button
Feature: Simple Dialog with one Button
2025-02-28 14:53:22 +01:00
Jan-Peter Klein ede1c72595 cancelButtonVisible BooleanProperty to boolean 2025-02-28 13:29:04 +01:00
Armin Schrenk d2fcd5b64f adjust stylings 2025-02-28 10:36:13 +01:00
Armin Schrenk 6d9704ffa2 adjust event description when locked 2025-02-28 10:36:03 +01:00
Armin Schrenk 40df3d015a remove optional wrapper of revealService 2025-02-28 10:01:52 +01:00
Armin Schrenk 4e22706d92 add alternative if revealService is not present 2025-02-28 09:51:58 +01:00
Armin Schrenk 341710f724 add time field to the eventcell 2025-02-27 18:56:41 +01:00
Armin Schrenk 8386183f7a * Hide button when not hovering or vault is locked
* localize
2025-02-27 17:25:31 +01:00
Armin Schrenk b76a7d6895 implement event views for all filesystemevents 2025-02-26 18:36:52 +01:00
Jan-Peter Klein 4a397a8151 code cleanup 2025-02-26 12:36:03 +01:00
Armin Schrenk 2239a3e6a3 reduce API to only consider vault events 2025-02-26 11:56:17 +01:00
tturturielloandGitHub 5318368879 Add underline effect to clickable notification labels (#3757) 2025-02-26 11:53:46 +01:00
Jan-Peter Klein 41a279da9d renamed property 2025-02-25 19:04:25 +01:00
Jan-Peter Klein 478c69f82c simple dialog without cancel button 2025-02-25 18:23:17 +01:00
Armin Schrenk e3073a3613 adjust wording for autoLock feature [skip ci] 2025-02-25 15:38:50 +01:00
Armin Schrenk 3333dc7f22 Fix not appearing removeCert dialog 2025-02-25 11:52:00 +01:00
Julian Raufelder 12ec6fbec8 Change /access-token deviceId header name to Hub-Device-ID 2025-02-19 14:25:34 +01:00
Armin Schrenk 034278c0a0 changes:
* implement reveal conflictResolved
* removed showVault context menu entry
* cleanup
2025-02-19 11:27:39 +01:00
Julian RaufelderandGitHub 92b3a9e1bd Merge pull request #3752 from cryptomator/feature/add-device-id-during-hub-unlock
Send DeviceId to Hub during vault key retrieval
2025-02-19 09:51:39 +01:00
Julian Raufelder fc7169f2a0 Send DeviceId to Hub during vault key retrieval
See https://github.com/cryptomator/hub/pull/320
2025-02-19 09:05:13 +01:00
Armin Schrenk c39710ede0 show event specific context menu 2025-02-18 18:32:06 +01:00
Armin SchrenkandGitHub cc0b6aed15 Update org.cryptomator.Cryptomator.metainfo.xml
* adjust screenshot captions
* reorder screenshots
* add keywords
2025-02-18 16:52:31 +01:00
Armin Schrenk f8a83c9cc8 scope eventListcell factory 2025-02-18 15:32:34 +01:00
Armin Schrenk 43ce64f01e adjust styling 2025-02-18 11:44:48 +01:00
mindmonkandGitHub d585a03f76 Merge pull request #3699 from cryptomator/feature/vault-scheme-tag
Add lastKnownKeyLoader entry to VaultSettings
2025-02-17 17:35:47 +01:00
Armin Schrenk f953be6237 Merge branch 'develop' into feature/event-view 2025-02-17 17:12:35 +01:00
Armin Schrenk e81bbe197b if mainWindow is closed without tray menu, terminate app 2025-02-17 16:46:25 +01:00
Armin Schrenk 738fa4da12 cleanup 2025-02-17 16:08:09 +01:00
dependabot[bot]andGitHub 9a08dcc46d Bump org.owasp:dependency-check-maven (#3748) 2025-02-17 10:04:30 +00:00
Armin Schrenk 74ef8d915d make eventView independent, but singleton window 2025-02-14 17:26:25 +01:00
Armin Schrenk 025a7a5582 add contextMenu to eventListCell 2025-02-14 17:11:43 +01:00
Armin Schrenk 3b2ddcf98d style the eventlist cell more 2025-02-14 16:47:15 +01:00
Armin SchrenkandGitHub c10fc0ee5b Update README.md
For build badge use develop branch.
2025-02-14 11:56:55 +01:00
aaa37e2c7a Feature: Add UTM parameters to download uri (#3735)
Co-authored-by: Tobias Hagemann <tobias.hagemann@skymatic.de>
2025-02-14 11:55:22 +01:00
Armin Schrenk 7af7c920e3 use same listCell factory pattern as in vault list 2025-02-14 11:54:25 +01:00
Armin Schrenk a4f79fb98a unify naming 2025-02-14 11:41:35 +01:00
Armin Schrenk f429f2d3e7 rename method 2025-02-14 10:51:48 +01:00
Armin Schrenk 4f6e091c13 first proper design draft 2025-02-13 16:58:05 +01:00
Jan-Peter Klein 2df779f7ab introduce isMasterkeyFileVault() 2025-02-13 15:44:10 +01:00
Jan-Peter Klein 5b9d9150c5 remove extra try catch block 2025-02-13 15:39:06 +01:00
Jan-Peter Klein f0aaec2058 comparison reversed 2025-02-13 15:15:29 +01:00
Jan-Peter Klein 4adc4a9175 removed unused StringBinding 2025-02-13 15:03:14 +01:00
Armin Schrenk 5a97060fac make the list synchronized 2025-02-13 11:06:48 +01:00
Armin Schrenk db96074119 replace AppEvent by UpdateEvent and emit it on available update 2025-02-12 11:12:40 +01:00
Jan-Peter Klein 7ecc10bc79 removed setVaultScheme method; lastKnownKeyLoader is now set within the create method 2025-02-12 10:35:21 +01:00
Armin Schrenk 44fe4a6f8a displayable event view window 2025-02-11 17:37:04 +01:00
Jan-Peter Klein 76a4ef50cb improve exception handling 2025-02-11 16:21:53 +01:00
Armin Schrenk ca2f80024a impl draft 2025-02-11 15:38:18 +01:00
Armin SchrenkandGitHub dcd7077b08 Feature: readd out of bounds check (#3729)
references #3091
2025-02-11 15:24:36 +01:00
Armin Schrenk 31482d7d18 [skip ci] update ide files 2025-02-11 12:37:45 +01:00
Armin Schrenk 5ef666154e use correct height in css [skip ci] 2025-02-11 10:16:34 +01:00
Armin Schrenk a810eff797 make menu buttons in vault list appear clickable 2025-02-10 18:29:57 +01:00
Jan-Peter Klein 664158eb84 add isHubVault method to KeyLoadingStrategy 2025-02-10 18:09:03 +01:00
Armin Schrenk 6b43881909 [skip ci] update screenshot url in metainfo file 2025-02-10 17:08:03 +01:00
Jan-Peter Klein 126004b1f8 improve error handling 2025-02-10 12:58:54 +01:00
Jan-Peter Klein ba34cfa9d5 renamed to lastKnownKeyLoader 2025-02-10 12:07:25 +01:00
Armin Schrenk 03b4ad85ef [skip ci] Merge branch 'main' into develop 2025-02-05 16:22:09 +01:00
Armin Schrenk 1abfcd495f Merge branch 'release/1.15.1' 2025-02-05 16:19:41 +01:00
Armin Schrenk 69964a80f1 finalize 1.15.1 2025-02-05 16:18:59 +01:00
Armin Schrenk aa8306ea4a prepare 1.15.1 2025-02-05 16:16:53 +01:00
CryptobotandGitHub 3fecde37f4 New Crowdin updates (#3718)
New translations strings.properties

Korean

[ci skip]
2025-02-05 16:16:20 +01:00
Armin Schrenk 2fc5fd99fb remove english "localization" 2025-02-05 16:11:36 +01:00
Armin Schrenk 7edacfea70 fixes #3721 2025-02-05 16:02:27 +01:00
Armin Schrenk 7020fa49d9 [skip ci] remove copyright notice 2025-02-05 11:44:58 +01:00
Armin Schrenk 7cea0bb33c update appstream screenshots 2025-02-05 11:44:40 +01:00
Armin Schrenk 3d622b18dc update flatpak metadata 2025-02-04 11:55:14 +01:00
KuleskoandGitHub 62cd506588 fix broken doc links (#3715) 2025-02-03 17:34:27 +01:00
Armin Schrenk cd830f27a0 update IDE file 2025-02-03 12:54:34 +01:00
Armin Schrenk 3f75018369 fix link in README 2025-02-03 12:54:25 +01:00
Armin Schrenk 270a4c594d [skip ci] Merge branch 'main' into develop 2025-02-03 11:34:16 +01:00
Armin Schrenk a5fdc5755b Merge branch 'release/1.15.0' 2025-02-03 11:16:18 +01:00
Armin Schrenk 0a5a554714 finalize 1.15.0 2025-02-03 11:15:59 +01:00
Armin Schrenk 976f22c27a Merge branch 'develop' into release/1.15.0 2025-02-03 11:12:20 +01:00
CryptobotandGitHub 8267574697 New Crowdin updates (#3697)
New translations strings.properties

Afrikaans; Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Macedonian; Marathi; Norwegian; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Sinhala; Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Urdu (Pakistan); Uyghur; Vietnamese; 

[ci skip]
2025-02-03 10:56:49 +01:00
mindmonkandGitHub 9077c964b3 Fix VaultListCell Padding in Compact Mode (#3709) 2025-02-03 10:50:06 +01:00
Armin Schrenk f80467a3a4 Fixes #3705 2025-01-28 15:09:52 +01:00
Julian RaufelderandGitHub 8c462c4cb7 RIP Twitter
[ci skip]
2025-01-23 12:21:54 +01:00
Jan-Peter Klein b25b5bd5a3 add keyLoader to observables 2025-01-22 16:59:52 +01:00
Jan-Peter Klein 6365c22297 add keyLoader entry to VaultSettings 2025-01-22 16:46:18 +01:00
Armin Schrenk d7dd24b94e linting 2025-01-22 16:26:25 +01:00
Armin Schrenk aee8844558 Fixes #3698 2025-01-22 16:25:56 +01:00
Armin Schrenk c536149c62 specify JDK 23 also in debian control file
References #3641

(cherry picked from commit ca1ae8181b)
2025-01-22 14:12:56 +01:00
Armin Schrenk ca1ae8181b specify JDK 23 also in debian control file
References #3641
2025-01-22 14:11:19 +01:00
Armin Schrenk 2b19c9757b prepare 1.15.0 2025-01-22 12:06:00 +01:00
Armin Schrenk d6871e3d82 clean up pom 2025-01-22 11:58:09 +01:00
CryptobotandGitHub 0432a64537 New Crowdin updates (#3606)
New translations strings.properties

Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Uyghur; Vietnamese; 

[ci skip]
2025-01-22 11:10:17 +01:00
06988b06c7 Feature: Retry if Read-only (#3695)
Closes #3261, closes #2085.

Co-authored-by: Armin Schrenk <armin.schrenk@skymatic.de>
2025-01-22 11:02:48 +01:00
dependabot[bot]andGitHub 9b72eb8219 Bump the java-production-dependencies group across 1 directory with 8 updates (#3694) 2025-01-21 16:06:50 +00:00
Armin Schrenk ea188f6176 for testing add mockito as javaagent 2025-01-21 15:53:49 +01:00
dependabot[bot]andGitHub cf67d022c8 Bump the java-test-dependencies group across 1 directory with 2 updates (#3671) 2025-01-21 13:36:52 +00:00
dependabot[bot]andGitHub 7ba6e12799 Bump the maven-build-plugins group across 1 directory with 3 updates (#3688) 2025-01-21 13:35:13 +00:00
Armin Schrenk b512518ccc update README [skip ci] 2025-01-21 11:46:30 +01:00
Armin Schrenk 93dfd35627 update Windows build scripts 2025-01-21 11:46:15 +01:00
Armin Schrenk 50b92f9510 fix URL in README [skip ci] 2025-01-21 11:43:38 +01:00
Armin Schrenk 7cca8922d8 Closes #3623 2025-01-21 11:39:14 +01:00
Armin SchrenkandGitHub c00889b1c4 update to javafx 23.0.1 (#3691) 2025-01-21 11:32:48 +01:00
Armin Schrenk b6224f355d Fixes #3693 2025-01-21 10:44:28 +01:00
Armin Schrenk 9a8f96d432 remove unused dll from windows build [skip ci] 2025-01-20 19:18:08 +01:00
Sebastian StenzelandGitHub 3cc199227b Include AppStream metadata in AppImage (#3687) 2025-01-20 17:59:59 +01:00
Sebastian StenzelandGitHub 3d0647bce3 fix appimage build (#3686)
* remove unsupported parameter `--sign-args`

apparently no longer wanted, despite being documented, see https://github.com/AppImage/appimagetool/issues/39

* add usr/share/metainfo/Cryptomator.appdata.xml

* appimagetool fails when adding AppStream metadata

see https://github.com/AppImage/AppImageKit/issues/603
2025-01-18 12:18:25 +01:00
DrSheppardandGitHub 192f35a9dd resolve #3055 (again) (#3685)
* use new build tool with runtime support instead old build tool for appimage

* Update workflow file to use newer appimagetool
2025-01-18 11:09:42 +01:00
Armin Schrenk f2b4c9a35b Update integrations-linux to 1.5.2
Fixes #3611
2025-01-17 18:05:10 +01:00
Armin SchrenkandGitHub 3b8fec4c5a Feature: Use system certificate stores/custom pkcs12 file (#3675)
* for Windows use Windows certificate
* for macOS use macOS Keychain
* for Linux use a custom PKCS12 file under /etc/cryptomator/certs.p12
2025-01-17 15:12:38 +01:00
DrSheppardandGitHub 60b74a018d AppImage: Use new build tool with runtime2 support (#3586)
Closes #3055
2025-01-17 13:49:11 +01:00
Armin Schrenk be69e04f51 fix build due to breaking changes in cryptofs 2025-01-17 13:28:14 +01:00
Armin Schrenk 194f6009df Update cryptofs to version 2.7.2 2025-01-17 13:22:45 +01:00
Sebastian StenzelandGitHub 910a2eace8 build AppImage on ubuntu-24.04-arm (#3681) 2025-01-17 09:08:02 +01:00
Jan-Peter Klein 3c95618eec changed dokany dialog icon to EXCLAMATION 2025-01-15 16:16:40 +01:00
Armin Schrenk fc709eb700 use caffeine for building a cache 2025-01-14 19:16:46 +01:00
Julian Raufelder a6c60ac5d0 Hello 2025 🎉 2025-01-14 16:34:32 +01:00
mindmonkandGitHub b88b9c8f92 Merge pull request #3676 from cryptomator/feature/change-zulu-to-temurin
Changed JAVA_DIST zulu to temurin
2025-01-13 13:25:55 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2b8cd36b7b Bump ch.qos.logback:logback-core from 1.5.12 to 1.5.13 (#3655)
Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.5.12 to 1.5.13.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.12...v_1.5.13)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-12 15:27:30 +01:00
Jan-Peter Klein 3d23deb744 changed JAVA_DIST zulu to temurin 2025-01-12 14:42:04 +01:00
147 changed files with 2732 additions and 744 deletions
+9 -9
View File
@@ -1,7 +1,14 @@
name: Bug Report
description: Create a report to help us improve
labels: ["type:bug"]
type: "Bug"
body:
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your problem.
validations:
required: true
- type: checkboxes
id: terms
attributes:
@@ -11,13 +18,6 @@ body:
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your problem.
validations:
required: true
- type: textarea
id: software-versions
attributes:
@@ -97,4 +97,4 @@ body:
id: further-info
attributes:
label: Anything else?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
+8 -8
View File
@@ -1,7 +1,14 @@
name: Feature Request
description: Suggest an idea for this project
labels: ["type:feature-request"]
type: "Feature"
body:
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your feature request.
validations:
required: true
- type: checkboxes
id: terms
attributes:
@@ -11,13 +18,6 @@ body:
required: true
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
required: true
- type: input
id: summary
attributes:
label: Summary
placeholder: Please summarize your feature request.
validations:
required: true
- type: textarea
id: motivation
attributes:
+13 -12
View File
@@ -10,7 +10,7 @@ on:
required: false
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
jobs:
@@ -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,6 +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 }}\""
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\""
--resource-dir dist/linux/resources
- name: Patch Cryptomator.AppDir
run: |
@@ -132,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
@@ -152,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
@@ -175,4 +176,4 @@ jobs:
files: |
cryptomator-*.AppImage
cryptomator-*.zsync
cryptomator-*.asc
cryptomator-*.asc
+1 -1
View File
@@ -6,7 +6,7 @@ on:
types: [labeled]
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: 23
defaults:
+1 -1
View File
@@ -7,7 +7,7 @@ on:
env:
JDK_VERSION: '23.0.1+11'
JDK_VENDOR: zulu
JDK_VENDOR: temurin
RUNTIME_VERSION_HELPER: >
public class Test {
public static void main(String[] args) {
+6 -6
View File
@@ -16,19 +16,19 @@ on:
type: boolean
env:
JAVA_DIST: 'zulu'
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/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'
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:
name: Build Debian Package
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- id: versions
+1 -1
View File
@@ -22,7 +22,7 @@ on:
value: ${{ jobs.determine-version.outputs.type }}
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: 23
jobs:
+4 -4
View File
@@ -14,7 +14,7 @@ on:
types: [published]
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
jobs:
@@ -35,8 +35,8 @@ jobs:
architecture: x64
output-suffix: x64
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'
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
@@ -100,7 +100,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"
+4 -4
View File
@@ -15,7 +15,7 @@ on:
type: boolean
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: '23.0.1+11'
jobs:
@@ -36,8 +36,8 @@ jobs:
architecture: aarch64
output-suffix: arm64
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
@@ -101,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"
+1 -1
View File
@@ -4,7 +4,7 @@ on:
pull_request:
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: 23
defaults:
+1 -1
View File
@@ -11,7 +11,7 @@ defaults:
shell: bash
env:
JAVA_DIST: 'zulu'
JAVA_DIST: 'temurin'
JAVA_VERSION: 23
jobs:
+6 -6
View File
@@ -17,8 +17,8 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '23.0.1+11'
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'
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/"
+9 -8
View File
@@ -14,17 +14,16 @@
<option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.52/dagger-compiler-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.52/dagger-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.52/dagger-spi-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.24-1.0.20/symbol-processing-api-1.9.24-1.0.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
@@ -35,6 +34,8 @@
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
@@ -45,7 +46,7 @@
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled --enable-preview" />
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled" />
</option>
</component>
</project>
+7 -7
View File
@@ -1,9 +1,9 @@
[![cryptomator](cryptomator.png)](https://cryptomator.org/)
[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
[![Build](https://github.com/cryptomator/cryptomator/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop)
[![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)
@@ -32,9 +32,9 @@ Become our Gold Sponsor and showcase your brand to a targeted audience! Please c
### 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).
<a href="https://www.macstadium.com/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
<a href="https://www.macstadium.com/company/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
---
@@ -54,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
@@ -72,13 +72,13 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
### Security Architecture
For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/en/latest/security/architecture/).
For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/security/architecture/).
## Building
### Dependencies
* JDK 22 (e.g. temurin, zulu)
* JDK 23 (e.g. temurin, zulu)
* Maven 3
### Run Maven
+7 -6
View File
@@ -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
+21 -6
View File
@@ -1,11 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2018 Armin Schrenk <armin.schrenk@zoho.eu> -->
<component type="desktop-application">
<id>org.cryptomator.Cryptomator</id>
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<name>Cryptomator</name>
<summary>Encryption made easy and optimized for the cloud</summary>
<summary>Encryption for your cloud made easy</summary>
<keywords>
<keyword>encryption</keyword>
<keyword>security</keyword>
<keyword>privacy</keyword>
</keywords>
<description>
<p>
@@ -44,12 +49,16 @@
<screenshots>
<screenshot type="default">
<caption>Light theme</caption>
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
<caption>Encrypts your data, protects your privacy</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
</screenshot>
<screenshot>
<caption>Dark theme</caption>
<image>https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png</image>
<caption>Dark theme available</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
</screenshot>
<screenshot>
<caption>Easy to use - work on encrypted files as if they were not</caption>
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
</screenshot>
</screenshots>
@@ -74,6 +83,12 @@
</content_rating>
<releases>
<release date="2025-02-05" version="1.15.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.1</url>
</release>
<release date="2025-02-03" version="1.15.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.0</url>
</release>
<release date="2024-11-19" version="1.14.2">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.14.2</url>
</release>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
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
+2 -2
View File
@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
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+
+2 -1
View File
@@ -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
+4 -4
View File
@@ -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"
+1 -1
View File
@@ -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.\
+1 -1
View File
@@ -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%"^
+5 -5
View File
@@ -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 `
+1 -1
View File
@@ -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
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -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
+33 -15
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.15.0-SNAPSHOT</version>
<version>1.16.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -33,43 +33,46 @@
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.7.1</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.4.0</cryptomator.integrations.version>
<cryptomator.cryptofs.version>2.9.0-beta2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.1</cryptomator.integrations.linux.version>
<cryptomator.integrations.linux.version>1.5.2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.0.2</cryptomator.fuse.version>
<cryptomator.webdav.version>2.0.7</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.17.0</commons-lang3.version>
<dagger.version>2.52</dagger.version>
<dagger.version>2.55</dagger.version>
<easybind.version>2.2</easybind.version>
<jackson.version>2.18.1</jackson.version>
<javafx.version>22.0.2</javafx.version>
<jackson.version>2.18.2</jackson.version>
<javafx.version>23.0.1</javafx.version>
<jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.5.12</logback.version>
<logback.version>1.5.16</logback.version>
<slf4j.version>2.0.16</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version>
<!-- test dependencies -->
<junit.jupiter.version>5.11.3</junit.jupiter.version>
<mockito.version>5.14.2</mockito.version>
<junit.jupiter.version>5.11.4</junit.jupiter.version>
<mockito.version>5.15.2</mockito.version>
<hamcrest.version>3.0</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
<dependency-check.version>11.1.0</dependency-check.version>
<dependency-check.version>12.1.0</dependency-check.version>
<jacoco.version>0.8.12</jacoco.version>
<license-generator.version>2.4.0</license-generator.version>
<junit-tree-reporter.version>1.3.0</junit-tree-reporter.version>
<license-generator.version>2.5.0</license-generator.version>
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
<mvn-compiler.version>3.13.0</mvn-compiler.version>
<mvn-resources.version>3.3.1</mvn-resources.version>
<mvn-dependency.version>3.8.1</mvn-dependency.version>
<mvn-surefire.version>3.5.2</mvn-surefire.version>
<mvn-jar.version>3.4.2</mvn-jar.version>
<!-- Property used by surefire to determine jacoco engine -->
<surefire.jacoco.args></surefire.jacoco.args>
</properties>
<dependencies>
@@ -201,6 +204,12 @@
<version>2.0.1</version>
</dependency>
<!-- Caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.0</version>
</dependency>
<!-- JUnit / Mockito / Hamcrest -->
<dependency>
<groupId>org.junit.jupiter</groupId>
@@ -302,7 +311,6 @@
<compilerArgs>
<arg>-Adagger.fastInit=enabled</arg>
<arg>-Adagger.formatGeneratedSource=enabled</arg>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
@@ -329,11 +337,11 @@
</dependency>
</dependencies>
<configuration>
<argLine>--enable-preview</argLine>
<reportFormat>plain</reportFormat>
<consoleOutputReporter>
<disable>true</disable>
</consoleOutputReporter>
<argLine>@{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar}</argLine>
<statelessTestsetInfoReporter
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
</statelessTestsetInfoReporter>
@@ -343,6 +351,13 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>jar-paths-to-properties</id>
<phase>validate</phase>
<goals>
<goal>properties</goal>
</goals>
</execution>
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
<execution>
<id>copy-mods</id>
@@ -415,6 +430,9 @@
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>surefire.jacoco.args</propertyName>
</configuration>
</execution>
<execution>
<id>report</id>
+7
View File
@@ -1,4 +1,5 @@
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;
@@ -13,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;
@@ -51,11 +55,14 @@ open module org.cryptomator.desktop {
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, //
GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, //
@@ -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<KeychainAccessProvider> 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) {
@@ -58,6 +58,7 @@ public class VaultSettings {
public final StringExpression mountName;
public final StringProperty mountService;
public final IntegerProperty port;
public final StringProperty lastKnownKeyLoader;
VaultSettings(VaultSettingsJson json) {
this.id = json.id;
@@ -74,6 +75,7 @@ public class VaultSettings {
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.port = new SimpleIntegerProperty(this, "port", json.port);
this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
@@ -99,7 +101,7 @@ public class VaultSettings {
}
Observable[] observables() {
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
}
public static VaultSettings withRandomId() {
@@ -130,6 +132,7 @@ public class VaultSettings {
json.mountPoint = mountPoint.map(Path::toString).getValue();
json.mountService = mountService.get();
json.port = port.get();
json.lastKnownKeyLoader = lastKnownKeyLoader.get();
return json;
}
@@ -48,6 +48,9 @@ class VaultSettingsJson {
@JsonProperty("mountService")
String mountService;
@JsonProperty("lastKnownKeyLoader")
String lastKnownKeyLoader;
@JsonProperty("port")
int port = VaultSettings.DEFAULT_PORT;
@@ -10,6 +10,7 @@ package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@@ -18,6 +19,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
@@ -44,6 +46,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;
@@ -73,6 +76,7 @@ public class Vault {
private final ObjectBinding<Mountpoint> mountPoint;
private final Mounter mounter;
private final Settings settings;
private final FileSystemEventAggregator fileSystemEventAggregator;
private final BooleanProperty showingStats;
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@@ -84,7 +88,8 @@ public class Vault {
VaultState state, //
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
VaultStats stats, //
Mounter mounter, Settings settings) {
Mounter mounter, Settings settings, //
FileSystemEventAggregator fileSystemEventAggregator) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
@@ -101,6 +106,7 @@ public class Vault {
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.mounter = mounter;
this.settings = settings;
this.fileSystemEventAggregator = fileSystemEventAggregator;
this.showingStats = new SimpleBooleanProperty(false);
this.quickAccessEntry = new AtomicReference<>(null);
}
@@ -111,15 +117,22 @@ public class Vault {
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
Set<FileSystemFlags> 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);
@@ -135,6 +148,7 @@ public class Vault {
.withFlags(flags) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
.withFilesystemEventConsumer(this::consumeVaultEvent) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
@@ -243,6 +257,11 @@ public class Vault {
}
}
private void consumeVaultEvent(FilesystemEvent e) {
fileSystemEventAggregator.put(this, e);
}
// ******************************************************************************
// Observable Properties
// *******************************************************************************
@@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
@@ -49,9 +50,9 @@ public class VaultListManager {
@Inject
public VaultListManager(ObservableList<Vault> vaultList, //
AutoLocker autoLocker, //
List<MountService> mountServices,
VaultComponent.Factory vaultComponentFactory,
ResourceBundle resourceBundle,
List<MountService> mountServices, //
VaultComponent.Factory vaultComponentFactory, //
ResourceBundle resourceBundle, //
Settings settings) {
this.vaultList = vaultList;
this.autoLocker = autoLocker;
@@ -114,6 +115,10 @@ public class VaultListManager {
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
try {
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
var keyIdScheme = wrapper.get().getKeyId().getScheme();
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
}
var vaultState = determineVaultState(vaultSettings.path.get());
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
wrapper.reloadConfig();
@@ -0,0 +1,8 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import java.nio.file.Path;
public record FSEventBucket(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}
@@ -0,0 +1,5 @@
package org.cryptomator.event;
import org.cryptomator.cryptofs.event.FilesystemEvent;
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}
@@ -0,0 +1,101 @@
package org.cryptomator.event;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
@Singleton
public class FileSystemEventAggregator {
private final ConcurrentHashMap<FSEventBucket, FSEventBucketContent> map;
private final AtomicBoolean hasUpdates;
@Inject
public FileSystemEventAggregator() {
this.map = new ConcurrentHashMap<>();
this.hasUpdates = new AtomicBoolean(false);
}
/**
* Adds the given event to the map. If a bucket for this event already exists, only the count is updated and the event set as the most recent one.
*
* @param v Vault where the event occurred
* @param e Actual {@link FilesystemEvent}
*/
public void put(Vault v, FilesystemEvent e) {
var key = computeKey(v, e);
hasUpdates.set(true);
map.compute(key, (k, val) -> {
if (val == null) {
return new FSEventBucketContent(e, 1);
} else {
return new FSEventBucketContent(e, val.count() + 1);
}
});
}
/**
* Removes an event bucket from the map.
*/
public FSEventBucketContent remove(FSEventBucket key) {
hasUpdates.set(true);
return map.remove(key);
}
/**
* Clears the event map.
*/
public void clear() {
hasUpdates.set(true);
map.clear();
}
public boolean hasUpdates() {
return hasUpdates.get();
}
/**
* Clones the map entries into a collection.
* <p>
* The collection is first cleared, then all map entries are added in one bulk operation. Cleans the hasUpdates status.
*
* @param target collection which is first cleared and then the EntrySet copied to.
*/
public void cloneTo(Collection<Map.Entry<FSEventBucket, FSEventBucketContent>> target) {
hasUpdates.set(false);
target.clear();
target.addAll(map.entrySet());
}
/**
* Method to compute the identifying key for a given filesystem event
*
* @param v Vault where the event occurred
* @param event Actual {@link FilesystemEvent}
* @return a {@link FSEventBucket} used in the map and lru cache
*/
private static FSEventBucket computeKey(Vault v, FilesystemEvent event) {
var p = switch (event) {
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
};
return new FSEventBucket(v, p, event.getClass());
}
}
@@ -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> ipcMessageHandler;
private final ShutdownHook shutdownHook;
private final SecureRandom csprng;
@Inject
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> 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.
*
@@ -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);
}
}
}
@@ -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<SSLContextProvider> loadAll() {
return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class);
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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
* <p>
* 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");
}
}
@@ -26,7 +26,7 @@ public class CreateNewVaultExpertSettingsController implements FxController {
public static final int MAX_SHORTENING_THRESHOLD = 220;
public static final int MIN_SHORTENING_THRESHOLD = 36;
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening";
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening";
private final Stage window;
private final Lazy<Application> application;
@@ -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<Scene> 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);
}
}
}
}
@@ -76,8 +76,10 @@ public class ReadmeGenerator {
input.chars().forEachOrdered(c -> {
if (c < 128) {
sb.append((char) c);
} else if (c <= 0xFF) {
sb.append("\\'").append(String.format("%02X", c));
} else if (c < 0xFFFF) {
sb.append("\\u").append(c);
sb.append("\\uc1\\u").append(c);
}
});
}
@@ -13,6 +13,7 @@ public enum FxmlFile {
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
ERROR("/fxml/error.fxml"), //
EVENT_VIEW("/fxml/eventview.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
@@ -7,6 +7,7 @@ public enum FontAwesome5Icon {
ANCHOR("\uF13D"), //
ARROW_UP("\uF062"), //
BAN("\uF05E"), //
BELL("\uF0F3"), //
BUG("\uF188"), //
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
@@ -15,10 +16,12 @@ public enum FontAwesome5Icon {
CLIPBOARD("\uF328"), //
COG("\uF013"), //
COGS("\uF085"), //
COMPRESS_ALT("\uF422"), //
COPY("\uF0C5"), //
CROWN("\uF521"), //
DONATE("\uF4B9"), //
EDIT("\uF044"), //
ELLIPSIS_V("\uF142"), //
EXCHANGE_ALT("\uF362"), //
EXCLAMATION("\uF12A"), //
EXCLAMATION_CIRCLE("\uF06A"), //
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
@@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController {
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
.whenCompleteAsync((result, exception) -> {
if (exception == null) {
vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
window.setScene(successScene.get());
} else {
@@ -2,31 +2,34 @@ 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 javax.inject.Singleton;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import java.util.ResourceBundle;
import java.util.function.Consumer;
@Singleton
@FxApplicationScoped
public class Dialogs {
private final ResourceBundle resourceBundle;
private final StageFactory stageFactory;
@Inject
public Dialogs(ResourceBundle resourceBundle) {
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);
return new SimpleDialog.Builder(resourceBundle, stageFactory);
}
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
@@ -35,7 +38,7 @@ public class Dialogs {
.setMessageKey("removeVault.message") //
.setDescriptionKey("removeVault.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeVault.confirmBtn") //
.setOkButtonKey("generic.button.remove") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
LOG.debug("Removing vault {}.", vault.getDisplayName());
@@ -51,7 +54,7 @@ public class Dialogs {
.setMessageKey("removeCert.message") //
.setDescriptionKey("removeCert.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeCert.confirmBtn") //
.setOkButtonKey("generic.button.remove") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
settings.licenseKey.set(null);
@@ -65,11 +68,23 @@ public class Dialogs {
.setTitleKey("dokanySupportEnd.title") //
.setMessageKey("dokanySupportEnd.message") //
.setDescriptionKey("dokanySupportEnd.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey("generic.button.close") //
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
.setOkAction(Stage::close) //
.setCancelAction(cancelAction);
}
public SimpleDialog.Builder prepareRetryIfReadonlyDialog(Stage window, Consumer<Stage> 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);
}
}
@@ -2,6 +2,7 @@ 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;
@@ -17,24 +18,24 @@ 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 = new Stage();
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)),
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
new SimpleDialogController(resolveText(builder.messageKey, null), //
resolveText(builder.descriptionKey, null), //
builder.icon, //
resolveText(builder.okButtonKey, null), //
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
() -> builder.okAction.accept(dialogStage), //
() -> builder.cancelAction.accept(dialogStage)), //
Scene::new, builder.resourceBundle);
dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot()));
@@ -60,19 +61,20 @@ public class SimpleDialog {
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<Stage> okAction = Stage::close;
private Consumer<Stage> cancelAction = Stage::close;
public Builder(ResourceBundle resourceBundle) {
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) {
this.resourceBundle = resourceBundle;
this.stageFactory = stageFactory;
}
public Builder setOwner(Stage owner) {
@@ -122,11 +124,10 @@ public class SimpleDialog {
}
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.");
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.");
try {
return new SimpleDialog(this);
@@ -14,6 +14,7 @@ public class SimpleDialogController implements FxController {
private final String cancelButtonText;
private final Runnable okAction;
private final Runnable cancelAction;
private final boolean cancelButtonVisible;
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
this.message = message;
@@ -23,6 +24,11 @@ public class SimpleDialogController implements FxController {
this.cancelButtonText = cancelButtonText;
this.okAction = okAction;
this.cancelAction = cancelAction;
this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
}
public boolean isCancelButtonVisible() {
return cancelButtonVisible;
}
public String getMessage() {
@@ -0,0 +1,329 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
import org.cryptomator.integrations.revealpath.RevealFailedException;
import org.cryptomator.integrations.revealpath.RevealPathService;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Function;
public class EventListCellController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(EventListCellController.class);
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
private final FileSystemEventAggregator fileSystemEventAggregator;
@Nullable
private final RevealPathService revealService;
private final ResourceBundle resourceBundle;
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
private final StringProperty eventMessage;
private final StringProperty eventDescription;
private final ObjectProperty<FontAwesome5Icon> eventIcon;
private final ObservableValue<String> eventCount;
private final ObservableValue<Boolean> vaultUnlocked;
private final ObservableValue<String> readableTime;
private final ObservableValue<String> readableDate;
private final ObservableValue<String> message;
private final ObservableValue<String> description;
private final ObservableValue<FontAwesome5Icon> icon;
private final BooleanProperty actionsButtonVisible;
private final Tooltip eventTooltip;
@FXML
HBox root;
@FXML
ContextMenu eventActionsMenu;
@FXML
Button eventActionsButton;
@Inject
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
this.fileSystemEventAggregator = fileSystemEventAggregator;
this.revealService = revealService.orElseGet(() -> null);
this.resourceBundle = resourceBundle;
this.eventEntry = new SimpleObjectProperty<>(null);
this.eventMessage = new SimpleStringProperty();
this.eventDescription = new SimpleStringProperty();
this.eventIcon = new SimpleObjectProperty<>();
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
this.actionsButtonVisible = new SimpleBooleanProperty();
this.eventTooltip = new Tooltip();
eventTooltip.setShowDelay(Duration.millis(500.0));
}
@FXML
public void initialize() {
actionsButtonVisible.bind(Bindings.createBooleanBinding(this::determineActionsButtonVisibility, root.hoverProperty(), eventActionsMenu.showingProperty(), vaultUnlocked));
vaultUnlocked.addListener((_, _, newValue) -> eventActionsMenu.hide());
Tooltip.install(root, eventTooltip);
}
private boolean determineActionsButtonVisibility() {
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
}
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
eventEntry.set(item);
eventActionsMenu.hide();
eventActionsMenu.getItems().clear();
eventTooltip.setText(item.getKey().vault().getDisplayName());
addAction("generic.action.dismiss", () -> {
fileSystemEventAggregator.remove(item.getKey());
});
switch (item.getValue().mostRecentEvent()) {
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
}
}
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, convertVaultPathToSystemPath(bfe.ciphertextPath())));
} else {
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.ciphertextPath()).toString()));
}
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
}
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
eventIcon.setValue(FontAwesome5Icon.CHECK);
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
} else {
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
}
}
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT);
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
} else {
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
}
}
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
eventIcon.setValue(FontAwesome5Icon.BAN);
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
} else {
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
}
}
private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) {
eventIcon.setValue(FontAwesome5Icon.TIMES);
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
if (revealService != null) {
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
} else {
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
}
}
private void addAction(String localizationKey, Runnable action) {
var entry = new MenuItem(resourceBundle.getString(localizationKey));
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
entry.setOnAction(_ -> action.run());
eventActionsMenu.getItems().addLast(entry);
}
private FontAwesome5Icon selectIcon() {
if (vaultUnlocked.getValue()) {
return eventIcon.getValue();
} else {
return FontAwesome5Icon.LOCK;
}
}
private String selectMessage() {
if (vaultUnlocked.getValue()) {
return eventMessage.getValue();
} else {
return resourceBundle.getString("eventView.entry.vaultLocked.message");
}
}
private String selectDescription() {
if (vaultUnlocked.getValue()) {
return eventDescription.getValue();
} else if (eventEntry.getValue() != null) {
var e = eventEntry.getValue().getKey();
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
} else {
return "";
}
}
@FXML
public void toggleEventActionsMenu() {
var e = eventEntry.get();
if (e != null) {
if (eventActionsMenu.isShowing()) {
eventActionsMenu.hide();
} else {
eventActionsMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
}
}
}
private Path convertVaultPathToSystemPath(Path p) {
if (!(p instanceof CryptoPath)) {
throw new IllegalArgumentException("Path " + p + " is not a vault path");
}
var v = eventEntry.getValue().getKey().vault();
if (!v.isUnlocked()) {
return Path.of(System.getProperty("user.home"));
}
var mountUri = v.getMountPoint().uri();
var internalPath = p.toString().substring(1);
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
}
private void reveal(RevealPathService s, Path p) {
try {
s.reveal(p);
} catch (RevealFailedException e) {
LOG.warn("Failed to show path {}", p, e);
}
}
private void copyToClipboard(String s) {
var content = new ClipboardContent();
content.putString(s);
Clipboard.getSystemClipboard().setContent(content);
}
//-- property accessors --
public ObservableValue<String> messageProperty() {
return message;
}
public String getMessage() {
return message.getValue();
}
public ObservableValue<String> countProperty() {
return eventCount;
}
public String getCount() {
return eventCount.getValue();
}
public ObservableValue<String> descriptionProperty() {
return description;
}
public String getDescription() {
return description.getValue();
}
public ObservableValue<FontAwesome5Icon> iconProperty() {
return icon;
}
public FontAwesome5Icon getIcon() {
return icon.getValue();
}
public ObservableValue<Boolean> actionsButtonVisibleProperty() {
return actionsButtonVisible;
}
public boolean isActionsButtonVisible() {
return actionsButtonVisible.getValue();
}
public ObservableValue<String> eventLocalTimeProperty() {
return readableTime;
}
public String getEventLocalTime() {
return readableTime.getValue();
}
public ObservableValue<String> eventLocalDateProperty() {
return readableDate;
}
public String getEventLocalDate() {
return readableDate.getValue();
}
public ObservableValue<Boolean> vaultUnlockedProperty() {
return vaultUnlocked;
}
public boolean isVaultUnlocked() {
return vaultUnlocked.getValue();
}
}
@@ -0,0 +1,66 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
@EventViewScoped
public class EventListCellFactory implements Callback<ListView<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
private final FxmlLoaderFactory fxmlLoaders;
@Inject
EventListCellFactory(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
this.fxmlLoaders = fxmlLoaders;
}
@Override
public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
try {
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
} catch (IOException e) {
throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
}
}
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
private final Parent root;
private final EventListCellController controller;
public Cell(Parent root, EventListCellController controller) {
this.root = root;
this.controller = controller;
}
@Override
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
this.getStyleClass().remove("list-cell");
} else {
this.getStyleClass().addLast("list-cell");
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(root);
controller.setEventEntry(item);
}
}
}
}
@@ -0,0 +1,35 @@
package org.cryptomator.ui.eventview;
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;
@EventViewScoped
@Subcomponent(modules = {EventViewModule.class})
public interface EventViewComponent {
@EventViewWindow
Stage window();
@FxmlScene(FxmlFile.EVENT_VIEW)
Lazy<Scene> scene();
default Stage showEventViewerWindow() {
Stage stage = window();
stage.setScene(scene().get());
stage.sizeToScene();
stage.show();
stage.requestFocus();
return stage;
}
@Subcomponent.Factory
interface Factory {
EventViewComponent create();
}
}
@@ -0,0 +1,132 @@
package org.cryptomator.ui.eventview;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxFSEventList;
import javax.inject.Inject;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.util.StringConverter;
import java.util.Map;
import java.util.ResourceBundle;
@EventViewScoped
public class EventViewController implements FxController {
private final FilteredList<Map.Entry<FSEventBucket, FSEventBucketContent>> filteredEventList;
private final ObservableList<Vault> vaults;
private final FileSystemEventAggregator aggregator;
private final SortedList<Map.Entry<FSEventBucket, FSEventBucketContent>> sortedEventList;
private final ObservableList<Vault> choiceBoxEntries;
private final ResourceBundle resourceBundle;
private final EventListCellFactory cellFactory;
@FXML
ChoiceBox<Vault> vaultFilterChoiceBox;
@FXML
ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView;
@Inject
public EventViewController(FxFSEventList fxFSEventList, ObservableList<Vault> vaults, ResourceBundle resourceBundle, EventListCellFactory cellFactory, FileSystemEventAggregator aggregator) {
this.filteredEventList = fxFSEventList.getObservableList().filtered(_ -> true);
this.vaults = vaults;
this.aggregator = aggregator;
this.sortedEventList = new SortedList<>(filteredEventList, this::compareBuckets);
this.choiceBoxEntries = FXCollections.observableArrayList();
this.resourceBundle = resourceBundle;
this.cellFactory = cellFactory;
}
/**
* Comparison method for the lru cache. During comparsion the map is accessed.
* First the entries are compared by the event timestamp, then vaultId, then identifying path and lastly by class name.
*
* @param left an entry of a {@link FSEventBucket} and its content
* @param right another entry of a {@link FSEventBucket} plus content, compared to {@code left}
* @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
*/
private int compareBuckets(Map.Entry<FSEventBucket, FSEventBucketContent> left, Map.Entry<FSEventBucket, FSEventBucketContent> right) {
var t1 = left.getValue().mostRecentEvent().getTimestamp();
var t2 = right.getValue().mostRecentEvent().getTimestamp();
var timeComparison = t1.compareTo(t2);
if (timeComparison != 0) {
return -timeComparison; //we need the reverse timesorting
}
var vaultIdComparison = left.getKey().vault().getId().compareTo(right.getKey().vault().getId());
if (vaultIdComparison != 0) {
return vaultIdComparison;
}
var pathComparison = left.getKey().idPath().compareTo(right.getKey().idPath());
if (pathComparison != 0) {
return pathComparison;
}
return left.getKey().c().getName().compareTo(right.getKey().c().getName());
}
@FXML
public void initialize() {
choiceBoxEntries.add(null);
choiceBoxEntries.addAll(vaults);
vaults.addListener((ListChangeListener<? super Vault>) c -> {
while (c.next()) {
choiceBoxEntries.removeAll(c.getRemoved());
choiceBoxEntries.addAll(c.getAddedSubList());
}
});
eventListView.setCellFactory(cellFactory);
eventListView.setItems(sortedEventList);
vaultFilterChoiceBox.setItems(choiceBoxEntries);
vaultFilterChoiceBox.valueProperty().addListener(this::applyVaultFilter);
vaultFilterChoiceBox.setConverter(new VaultConverter(resourceBundle));
}
private void applyVaultFilter(ObservableValue<? extends Vault> v, Vault oldV, Vault newV) {
if (newV == null) {
filteredEventList.setPredicate(_ -> true);
} else {
filteredEventList.setPredicate(e -> e.getKey().vault().equals(newV));
}
}
@FXML
void clearEvents() {
aggregator.clear();
}
private static class VaultConverter extends StringConverter<Vault> {
private final ResourceBundle resourceBundle;
VaultConverter(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
@Override
public String toString(Vault v) {
if (v == null) {
return resourceBundle.getString("eventView.filter.allVaults");
} else {
return v.getDisplayName();
}
}
@Override
public Vault fromString(String displayLanguage) {
throw new UnsupportedOperationException();
}
}
}
@@ -0,0 +1,67 @@
package org.cryptomator.ui.eventview;
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 org.cryptomator.ui.fxapp.FxFSEventList;
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 EventViewModule {
@Provides
@EventViewScoped
@EventViewWindow
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, FxFSEventList fxFSEventList) {
Stage stage = factory.create();
stage.setHeight(498);
stage.setTitle(resourceBundle.getString("eventView.title"));
stage.setResizable(true);
stage.initModality(Modality.NONE);
stage.focusedProperty().addListener((_,_,isFocused) -> {
if(isFocused) {
fxFSEventList.unreadEventsProperty().setValue(false);
}
});
return stage;
}
@Provides
@EventViewScoped
@EventViewWindow
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@FxmlScene(FxmlFile.EVENT_VIEW)
@EventViewScoped
static Scene provideEventViewerScene(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.EVENT_VIEW);
}
@Binds
@IntoMap
@FxControllerKey(EventViewController.class)
abstract FxController bindEventViewController(EventViewController controller);
@Binds
@IntoMap
@FxControllerKey(EventListCellController.class)
abstract FxController bindEventListCellController(EventListCellController controller);
}
@@ -0,0 +1,13 @@
package org.cryptomator.ui.eventview;
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 EventViewScoped {
}
@@ -0,0 +1,14 @@
package org.cryptomator.ui.eventview;
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 EventViewWindow {
}
@@ -43,8 +43,8 @@ public class AutoUnlocker {
private CompletionStage<Void> unlockSequentially(Stream<Vault> vaultStream) {
// this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow.
return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null),
(prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)),
(prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere
(prevUnlock, nextVault) -> prevUnlock.thenCompose(_ -> appWindows.startUnlockWorkflow(nextVault, null)),
(_, nextUnlock) -> nextUnlock.exceptionally(_ -> null) // we don't care here about the exception, logged elsewhere
);
}
@@ -29,9 +29,10 @@ public class FxApplication {
private final FxApplicationStyle applicationStyle;
private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList; //not unused! By injecting it here, the object gets initiated the service starts
@Inject
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker, FxFSEventList fxFSEventList) {
this.startupTime = startupTime;
this.environment = environment;
this.settings = settings;
@@ -41,6 +42,7 @@ public class FxApplication {
this.applicationStyle = applicationStyle;
this.applicationTerminator = applicationTerminator;
this.autoUnlocker = autoUnlocker;
this.fxFSEventList = fxFSEventList;
}
public void start() {
@@ -8,6 +8,7 @@ package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
@@ -19,6 +20,9 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Named;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
@@ -33,7 +37,8 @@ import java.io.InputStream;
ErrorComponent.class, //
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class})
ShareVaultComponent.class, //
EventViewComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -66,4 +71,10 @@ abstract class FxApplicationModule {
return builder.build();
}
@Provides
@FxApplicationScoped
static EventViewComponent provideEventViewComponent(EventViewComponent.Factory factory) {
return factory.create();
}
}
@@ -6,7 +6,9 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.dialogs.SimpleDialog;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.eventview.EventViewComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
@@ -51,6 +53,7 @@ public class FxApplicationWindows {
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final Lazy<EventViewComponent> eventViewWindow;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
@@ -69,6 +72,7 @@ public class FxApplicationWindows {
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
Lazy<EventViewComponent> eventViewWindow, //
ExecutorService executor, //
Dialogs dialogs) {
this.primaryStage = primaryStage;
@@ -80,6 +84,7 @@ public class FxApplicationWindows {
this.updateReminderWindowFactory = updateReminderWindowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.eventViewWindow = eventViewWindow;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
@@ -93,17 +98,17 @@ public class FxApplicationWindows {
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
desktop.setPreferencesHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ANY));
desktop.setPreferencesHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ANY));
}
// register preferences shortcut
if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
desktop.setAboutHandler(evt -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
desktop.setAboutHandler(_ -> showPreferencesWindow(SelectedPreferencesTab.ABOUT));
}
// register app reopen listener
if (desktop.isSupported(Desktop.Action.APP_EVENT_REOPENED)) {
desktop.addAppEventListener((AppReopenedListener) e -> showMainWindow());
desktop.addAppEventListener((AppReopenedListener) _ -> showMainWindow());
}
// observe visible windows
@@ -135,11 +140,12 @@ public class FxApplicationWindows {
}
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) //
.whenComplete(this::reportErrors);
}
public void showQuitWindow(QuitResponse response, boolean forced) {
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response, forced), Platform::runLater);
}
public void showUpdateReminderWindow() {
@@ -147,13 +153,14 @@ public class FxApplicationWindows {
}
public void showDokanySupportEndWindow() {
CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog(
mainWindow.get().window(),
stage -> {
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
stage.close();
}
).build().showAndWait(), Platform::runLater);
CompletableFuture.runAsync(() -> createDokanySupportEndDialog().showAndWait(), Platform::runLater);
}
private SimpleDialog createDokanySupportEndDialog() {
return dialogs.prepareDokanySupportEndDialog(mainWindow.get().window(), stage -> {
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
stage.close();
}).build();
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
@@ -162,8 +169,7 @@ public class FxApplicationWindows {
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
return unlockWorkflowFactory.create(vault, owner).unlockWorkflow();
}, Platform::runLater) //
.thenAcceptAsync(UnlockWorkflow::run, executor)
.exceptionally(e -> {
.thenAcceptAsync(UnlockWorkflow::run, executor).exceptionally(e -> {
showErrorWindow(e, owner == null ? primaryStage : owner, null);
return null;
});
@@ -182,6 +188,11 @@ public class FxApplicationWindows {
});
}
public CompletionStage<Stage> showEventViewer() {
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
}
/**
* Displays the generic error scene in the given window.
*
@@ -199,5 +210,4 @@ public class FxApplicationWindows {
LOG.error("Failed to display stage", error);
}
}
}
@@ -0,0 +1,65 @@
package org.cryptomator.ui.fxapp;
import org.cryptomator.event.FSEventBucket;
import org.cryptomator.event.FSEventBucketContent;
import org.cryptomator.event.FileSystemEventAggregator;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped
public class FxFSEventList {
private final ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> events;
private final FileSystemEventAggregator eventAggregator;
private final ScheduledFuture<?> scheduledTask;
private final BooleanProperty unreadEvents;
@Inject
public FxFSEventList(FileSystemEventAggregator fsEventAggregator, ScheduledExecutorService scheduler) {
this.events = FXCollections.observableArrayList();
this.eventAggregator = fsEventAggregator;
this.unreadEvents = new SimpleBooleanProperty(false);
this.scheduledTask = scheduler.scheduleWithFixedDelay(() -> {
if (fsEventAggregator.hasUpdates()) {
flush();
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
/**
* Starts the clone task on the FX thread and wait till it is completed
*/
private void flush() {
var latch = new CountDownLatch(1);
Platform.runLater(() -> {
eventAggregator.cloneTo(events);
unreadEvents.setValue(true);
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public ObservableList<Map.Entry<FSEventBucket, FSEventBucketContent>> getObservableList() {
return events;
}
public BooleanProperty unreadEventsProperty() {
return unreadEvents;
}
}
@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,6 +30,33 @@ public interface KeyLoadingStrategy extends MasterkeyLoader {
@Override
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
/**
* Determines whether the provided key loader scheme corresponds to a Hub Vault.
* <p>
* This method compares the {@code keyLoader} parameter with the known Hub Vault schemes
* {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTP} and {@link HubKeyLoadingStrategy#SCHEME_HUB_HTTPS}.
*
* @param keyLoader A string representing the key loader scheme to be checked.
* @return {@code true} if the given key loader scheme represents a Hub Vault; {@code false} otherwise.
*/
static boolean isHubVault(String keyLoader) {
return HubKeyLoadingStrategy.SCHEME_HUB_HTTP.equals(keyLoader) || HubKeyLoadingStrategy.SCHEME_HUB_HTTPS.equals(keyLoader);
}
/**
* Determines whether the provided key loader scheme corresponds to a Masterkey File Vault.
* <p>
* This method checks if the {@code keyLoader} parameter matches the known Masterkey File Vault scheme
* {@link MasterkeyFileLoadingStrategy#SCHEME}.
* </p>
*
* @param keyLoader A string representing the key loader scheme to be checked.
* @return {@code true} if the given key loader scheme represents a Masterkey File Vault; {@code false} otherwise.
*/
static boolean isMasterkeyFileVault(String keyLoader) {
return MasterkeyFileLoadingStrategy.SCHEME.equals(keyLoader);
}
/**
* Allows the loader to try and recover from an exception thrown during the last attempt.
*
@@ -165,6 +165,7 @@ public class ReceiveKeyController implements FxController {
var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
var request = HttpRequest.newBuilder(vaultKeyUri) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Hub-Device-ID", deviceId) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
@@ -1,13 +1,5 @@
package org.cryptomator.ui.mainwindow;
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;
@@ -21,6 +13,17 @@ 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.geometry.Rectangle2D;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -63,27 +66,76 @@ public class MainWindowController implements FxController {
}
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());
int x = settings.windowXPosition.get();
int y = settings.windowYPosition.get();
int width = settings.windowWidth.get();
int height = settings.windowHeight.get();
if (windowPositionSaved(x, y, width, height)) {
window.setX(x);
window.setY(y);
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
}
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
window.setOnShowing(this::checkDisplayBounds);
settings.windowXPosition.bind(window.xProperty());
settings.windowYPosition.bind(window.yProperty());
settings.windowWidth.bind(window.widthProperty());
settings.windowHeight.bind(window.heightProperty());
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
private boolean windowPositionSaved(int x, int y, int width, int height) {
return x != 0 || y != 0 || width != 0 || height != 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 checkDisplayBounds(WindowEvent windowEvent) {
int x = settings.windowXPosition.get();
int y = settings.windowYPosition.get();
int width = settings.windowWidth.get();
int height = settings.windowHeight.get();
// 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(x);
window.setY(y);
window.setWidth(width);
window.setHeight(height);
}
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
LOG.debug("Resetting window position due to insufficient screen overlap");
var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
//check if we can keep width and height
if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
//if so, keep window size
window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
}
//reset position of upper left corner
window.setX(centeredX);
window.setY(centeredY);
}
}
private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
// define a rect which is inset on all sides from the window's rect:
final int shrinkedX = x + 20; // 20px left
final int shrinkedY = y + 5; // 5px top
final int shrinkedWidth = width - 40; // 20px left + 20px right
final int shrinkedHeigth = height - 25; // 5px top + 20px bottom
return isRectangleWithinBounds(shrinkedX, shrinkedY, 0, shrinkedHeigth) // Left pixel column
&& isRectangleWithinBounds(shrinkedX + shrinkedWidth, shrinkedY, 0, shrinkedHeigth) // Right pixel column
&& isRectangleWithinBounds(shrinkedX, shrinkedY, shrinkedWidth, 0) // Top pixel row
&& isRectangleWithinBounds(shrinkedX, shrinkedY + shrinkedHeigth, shrinkedWidth, 0); // Bottom pixel row
}
private boolean isRectangleWithinBounds(int x, int y, int width, int height) {
return !Screen.getScreensForRectangle(x, y, width, height).isEmpty();
}
private void mainWindowFocusChanged(Observable observable) {
@@ -1,6 +1,7 @@
package org.cryptomator.ui.mainwindow;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
@@ -14,9 +15,11 @@ 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.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import javax.inject.Named;
@@ -35,11 +38,19 @@ abstract class MainWindowModule {
@Provides
@MainWindow
@MainWindowScoped
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer, FxApplicationTerminator terminator, Lazy<TrayMenuComponent> trayMenu) {
initializer.accept(stage);
stage.setTitle("Cryptomator");
stage.setMinWidth(650);
stage.setMinHeight(498);
stage.setOnCloseRequest(e -> {
if (!trayMenu.get().isInitialized()) {
terminator.terminate();
e.consume();
} else {
stage.close();
}
});
return stage;
}
@@ -13,10 +13,16 @@ 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> vault = new SimpleObjectProperty<>();
private final ObservableValue<FontAwesome5Icon> glyph;
private final ObservableValue<Boolean> compactMode;
@@ -25,6 +31,8 @@ public class VaultListCellController implements FxController {
/* FXML */
public FontAwesome5IconView vaultStateView;
@FXML
public HBox vaultListCell;
@Inject
VaultListCellController(Settings settings) {
@@ -37,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
@@ -10,6 +10,7 @@ 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.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
@@ -26,6 +27,7 @@ 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;
@@ -34,7 +36,6 @@ 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;
@@ -66,6 +67,7 @@ public class VaultListController implements FxController {
private final VaultListCellFactory cellFactory;
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final BooleanProperty unreadEvents;
private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle;
@@ -76,7 +78,7 @@ public class VaultListController implements FxController {
public ListView<Vault> vaultList;
public StackPane root;
@FXML
private HBox addVaultButton;
private Button addVaultButton;
@FXML
private ContextMenu addVaultContextMenu;
@@ -91,7 +93,8 @@ public class VaultListController implements FxController {
ResourceBundle resourceBundle, //
FxApplicationWindows appWindows, //
Settings settings, //
Dialogs dialogs) {
Dialogs dialogs, //
FxFSEventList fxFSEventList) {
this.mainWindow = mainWindow;
this.vaults = vaults;
this.selectedVault = selectedVault;
@@ -104,6 +107,7 @@ public class VaultListController implements FxController {
this.dialogs = dialogs;
this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty();
selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
@@ -263,6 +267,11 @@ public class VaultListController implements FxController {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showEventViewer() {
appWindows.showEventViewer();
unreadEvents.setValue(false);
}
// Getter and Setter
public BooleanBinding emptyVaultListProperty() {
@@ -289,4 +298,11 @@ public class VaultListController implements FxController {
return cellSize.getValue();
}
public ObservableValue<Boolean> unreadEventsPresentProperty() {
return unreadEvents;
}
public boolean getUnreadEventsPresent() {
return unreadEvents.getValue();
}
}
@@ -16,7 +16,7 @@ import javafx.fxml.FXML;
public class WelcomeController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.7/desktop/getting-started/";
private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/desktop/getting-started/";
private final Application application;
private final BooleanBinding noVaultPresent;
@@ -10,7 +10,7 @@ import javafx.stage.Stage;
public class MigrationImpossibleController implements FxController {
private static final String HELP_URI = "https://docs.cryptomator.org/en/1.7/help/manual-migration/";
private static final String HELP_URI = "https://docs.cryptomator.org/help/manual-migration/";
private final Application application;
private final Stage window;
@@ -19,6 +19,8 @@ import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -32,7 +34,10 @@ import java.util.ResourceBundle;
@PreferencesScoped
public class UpdatesPreferencesController implements FxController {
private static final String DOWNLOADS_URI = "https://cryptomator.org/downloads";
private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" //
+ "?utm_source=cryptomator-desktop" //
+ "&utm_medium=update-notification&" //
+ "utm_campaign=app-update-%s";
private final Application application;
private final Environment environment;
@@ -50,6 +55,7 @@ public class UpdatesPreferencesController implements FxController {
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
private final DateTimeFormatter formatter;
private final BooleanBinding upToDate;
private final String downloadsUri;
/* FXML */
public CheckBox checkForUpdatesCheckbox;
@@ -65,12 +71,13 @@ public class UpdatesPreferencesController implements FxController {
this.latestVersion = updateChecker.latestVersionProperty();
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
this.currentVersion = updateChecker.getCurrentVersion();
this.currentVersion = environment.getAppVersion();
this.updateAvailable = updateChecker.updateAvailableProperty();
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
this.checkFailed = updateChecker.checkFailedProperty();
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII));
}
public void initialize() {
@@ -93,7 +100,7 @@ public class UpdatesPreferencesController implements FxController {
@FXML
public void visitDownloadsPage() {
application.getHostServices().showDocument(DOWNLOADS_URI);
application.getHostServices().showDocument(downloadsUri);
}
@FXML
@@ -22,7 +22,7 @@ import java.util.ResourceBundle;
@PreferencesScoped
public class VolumePreferencesController implements FxController {
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
public static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/desktop/volume-type/";
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
@@ -3,7 +3,7 @@ package org.cryptomator.ui.sharevault;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javax.inject.Inject;
import javafx.application.Application;
@@ -33,8 +33,7 @@ public class ShareVaultController implements FxController {
this.window = window;
this.application = application;
this.vault = vault;
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
this.hubVault = KeyLoadingStrategy.isHubVault(vault.getVaultSettings().lastKnownKeyLoader.get());
}
@FXML
@@ -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<Void> {
private final FxApplicationWindows appWindows;
private final KeyLoadingStrategy keyLoadingStrategy;
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
private final Dialogs dialogs;
@Inject
UnlockWorkflow(@PrimaryStage Stage mainWindow, //
@@ -57,7 +61,8 @@ public class UnlockWorkflow extends Task<Void> {
@FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy<Scene> restartRequiredScene, //
FxApplicationWindows appWindows, //
@UnlockWindow KeyLoadingStrategy keyLoadingStrategy, //
@UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
@UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException, //
Dialogs dialogs) {
this.mainWindow = mainWindow;
this.window = window;
this.vault = vault;
@@ -68,6 +73,7 @@ public class UnlockWorkflow extends Task<Void> {
this.appWindows = appWindows;
this.keyLoadingStrategy = keyLoadingStrategy;
this.illegalMountPointException = illegalMountPointException;
this.dialogs = dialogs;
}
@Override
@@ -144,11 +150,36 @@ public class UnlockWorkflow extends Task<Void> {
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());
@@ -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<String> defaultMountFlags;
private final ObservableValue<Boolean> mountpointDirSupported;
private final ObservableValue<Boolean> mountpointDriveLetterSupported;
private final ObservableValue<Boolean> readOnlySupported;
private final ObservableValue<Boolean> mountFlagsSupported;
private final ObservableValue<Boolean> defaultMountServiceSelected;
private final ObservableValue<String> directoryPath;
@@ -60,6 +61,7 @@ public class MountOptionsController implements FxController {
private final ObservableValue<MountService> selectedMountService;
private final ObservableValue<Boolean> selectedMountServiceRequiresRestart;
private final ObservableValue<Boolean> 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<Boolean> readOnlySupportedProperty() {
return readOnlySupported;
public ObservableValue<Boolean> readOnlyOptionAllowedProperty() {
return readOnlyOptionAllowed;
}
public boolean isReadOnlySupported() {
return readOnlySupported.getValue();
public boolean isReadOnlyOptionAllowed() {
return readOnlyOptionAllowed.getValue();
}
public ObservableValue<String> directoryPathProperty() {
@@ -3,6 +3,7 @@ package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.slf4j.Logger;
@@ -42,11 +43,11 @@ public class VaultOptionsController implements FxController {
window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab());
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
var vaultKeyLoader = vault.getVaultSettings().lastKnownKeyLoader.get();
if(!KeyLoadingStrategy.isMasterkeyFileVault(vaultKeyLoader)){
tabPane.getTabs().remove(keyTab);
}
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
if(!KeyLoadingStrategy.isHubVault(vaultKeyLoader)){
tabPane.getTabs().remove(hubTab);
}
@@ -15,7 +15,7 @@ import java.io.UncheckedIOException;
@WrongFileAlertScoped
public class WrongFileAlertController implements FxController {
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.7/desktop/accessing-vaults/";
private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/desktop/accessing-vaults/";
private final Application app;
private final Stage window;
+72 -2
View File
@@ -183,19 +183,37 @@
}
.main-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
}
.main-window .button-left {
.main-window .button-bar .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-right {
.main-window .button-bar .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-left:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.main-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
/*******************************************************************************
@@ -312,6 +330,42 @@
-fx-fill: transparent;
}
/*******************************************************************************
* *
* Event List *
* *
******************************************************************************/
.event-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.event-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.event-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.event-window .list-view .list-cell:hover {
-fx-background-color: CONTROL_BG_SELECTED;
}
.event-window .list-view .list-cell:selected {
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
/*******************************************************************************
* *
* NotificationBar *
@@ -341,6 +395,12 @@
-fx-background-color: PRIMARY;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
-fx-underline:true;
}
/*******************************************************************************
* *
* ScrollBar *
@@ -575,6 +635,16 @@
-fx-graphic-text-gap: 9px;
}
/*******************************************************************************
* *
* Update indicator
* *
******************************************************************************/
.update-indicator {
-fx-fill: RED_5;
}
/*******************************************************************************
* *
* Hyperlinks *
+76 -2
View File
@@ -182,6 +182,8 @@
}
.main-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
@@ -190,11 +192,27 @@
.main-window .button-bar .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.main-window .button-bar .button-left:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.main-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
/*******************************************************************************
@@ -283,6 +301,10 @@
-fx-font-size: 1.0em;
}
.list-cell .header-misc {
-fx-font-size: 1.0em;
}
.list-cell .detail-label {
-fx-text-fill: TEXT_FILL_MUTED;
-fx-font-size: 0.8em;
@@ -311,6 +333,42 @@
-fx-fill: transparent;
}
/*******************************************************************************
* *
* Event List *
* *
******************************************************************************/
.event-window .button-bar {
-fx-min-height:42px;
-fx-max-height:42px;
-fx-background-color: MAIN_BG;
-fx-border-color: transparent transparent CONTROL_BORDER_NORMAL transparent;
-fx-border-width: 0 0 1px 0;
}
.event-window .button-bar .button-right {
-fx-border-color: transparent transparent transparent CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
-fx-background-color: MAIN_BG;
-fx-background-radius: 0px;
-fx-min-height: 42px;
-fx-max-height: 42px;
}
.event-window .button-bar .button-right:armed {
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_ARMED;
}
.event-window .list-view .list-cell:hover {
-fx-background-color: CONTROL_BG_SELECTED;
}
.event-window .list-view .list-cell:selected {
-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-fx-background-insets: 0, 0 0 0 3px;
}
/*******************************************************************************
* *
* NotificationBar *
@@ -340,6 +398,12 @@
-fx-background-color: PRIMARY;
}
.notification-debug:hover .notification-label,
.notification-update:hover .notification-label,
.notification-support:hover .notification-label {
-fx-underline:true;
}
/*******************************************************************************
* *
* ScrollBar *
@@ -574,6 +638,16 @@
-fx-graphic-text-gap: 9px;
}
/*******************************************************************************
* *
* Update indicator
* *
******************************************************************************/
.icon-update-indicator {
-fx-fill: RED_5;
}
/*******************************************************************************
* *
* Hyperlinks *
@@ -786,11 +860,11 @@
/*******************************************************************************
* *
* Add Vault - MenuItem *
* Dropdown button context menu
* *
******************************************************************************/
.add-vault-menu-item {
.dropdown-button-context-menu-item {
-fx-padding: 4px 8px;
}
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Tooltip?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.eventview.EventViewController"
minWidth="300"
prefWidth="300"
styleClass="event-window"
>
<HBox styleClass="button-bar" alignment="CENTER">
<padding>
<Insets left="6" />
</padding>
<ChoiceBox fx:id="vaultFilterChoiceBox"/>
<Region HBox.hgrow="ALWAYS"/>
<Button styleClass="button-right" onAction="#clearEvents" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="TRASH" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%eventView.clearListButton.tooltip"/>
</tooltip>
</Button>
</HBox>
<ListView fx:id="eventListView" fixedCellSize="60" VBox.vgrow="ALWAYS"/>
</VBox>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.eventview.EventListCellController"
prefHeight="60"
prefWidth="200"
spacing="12"
alignment="CENTER_LEFT"
fx:id="root">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView glyph="${controller.icon}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<HBox spacing="4">
<Label styleClass="header-label" text="${controller.message}"/>
<Label styleClass="header-misc" text="${controller.count}" visible="${controller.vaultUnlocked}"/>
</HBox>
<Label text="${controller.description}"/>
</VBox>
<Button fx:id="eventActionsButton" contentDisplay="GRAPHIC_ONLY" onAction="#toggleEventActionsMenu" managed="${controller.actionsButtonVisible}" visible="${controller.actionsButtonVisible}">
<graphic>
<FontAwesome5IconView glyph="ELLIPSIS_V" glyphSize="16"/>
</graphic>
</Button>
<VBox alignment="CENTER" maxWidth="64" minWidth="64" visible="${!controller.actionsButtonVisible}" managed="${!controller.actionsButtonVisible}">
<Label text="${controller.eventLocalTime}" />
<Label text="${controller.eventLocalDate}" />
</VBox>
<fx:define>
<ContextMenu fx:id="eventActionsMenu"/>
</fx:define>
</HBox>
@@ -22,7 +22,7 @@
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
<Label text="© 2016 2024 Skymatic GmbH"/>
<Label text="© 2016 2025 Skymatic GmbH"/>
</VBox>
</HBox>
+10 -10
View File
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
@@ -41,7 +41,7 @@
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
</buttons>
</ButtonBar>
+36 -20
View File
@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.shape.Circle?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.layout.AnchorPane?>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="root"
@@ -35,33 +40,44 @@
</VBox>
</StackPane>
<HBox styleClass="button-bar">
<HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
<Button fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="PLUS" glyphSize="16"/>
</graphic>
</Button>
<Region HBox.hgrow="ALWAYS"/>
<HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
<StackPane>
<Button onMouseClicked="#showEventViewer" styleClass="button-right" minWidth="20" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<FontAwesome5IconView glyph="BELL" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.vaultlist.showEventsButton.tooltip"/>
</tooltip>
</Button>
<AnchorPane mouseTransparent="true" minWidth="12" maxWidth="12" minHeight="12" maxHeight="12" StackPane.alignment="CENTER">
<Circle radius="4" styleClass="icon-update-indicator" AnchorPane.topAnchor="-8" AnchorPane.rightAnchor="-6" visible="${controller.unreadEventsPresent}" />
</AnchorPane>
</StackPane>
<Button onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20" contentDisplay="GRAPHIC_ONLY">
<graphic>
<FontAwesome5IconView glyph="COG" glyphSize="16"/>
</graphic>
</Button>
</HBox>
</VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault">
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
</items>
+12 -17
View File
@@ -1,33 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="vaultListCell"
fx:controller="org.cryptomator.ui.mainwindow.VaultListCellController"
prefHeight="60"
prefWidth="200"
spacing="12"
alignment="CENTER_LEFT">
<!-- Remark Check the containing list view for a fixed cell size before editing height properties -->
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</children>
<VBox alignment="CENTER" minWidth="20">
<FontAwesome5IconView fx:id="vaultStateView" glyph="${controller.glyph}" HBox.hgrow="NEVER" glyphSize="16"/>
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>
</Label>
</VBox>
</HBox>
@@ -54,7 +54,7 @@
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlySupported}" managed="${controller.readOnlySupported}"/>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlyOptionAllowed}" managed="${controller.readOnlyOptionAllowed}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
+37 -3
View File
@@ -2,6 +2,7 @@
additionalStyleSheets=
# Generics
generic.action.dismiss=Dismiss
## Button
generic.button.apply=Apply
generic.button.back=Back
@@ -177,7 +178,7 @@ hub.registerFailed.description.generic=An error was thrown in the registration p
hub.registerFailed.description.deviceAlreadyExists=This device is already registered for a different user. Try to change the user account or use a different device.
### Unauthorized
hub.unauthorized.message=Access denied
hub.unauthorized.description=Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it.
hub.unauthorized.description=You are not authorized to open this vault. Contact the vault's owner to request access.
### Requires Account Initialization
hub.requireAccountInit.message=Action required
hub.requireAccountInit.description.0=To proceed, please complete the steps required in your
@@ -283,7 +284,7 @@ preferences.title=Preferences
## General
preferences.general=General
preferences.general.startHidden=Hide window when starting Cryptomator
preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
preferences.general.debugLogging=Enable debug logging
preferences.general.debugDirectory=Reveal log files
preferences.general.autoStart=Launch Cryptomator on system start
@@ -395,6 +396,7 @@ main.vaultlist.contextMenu.vaultoptions=Show Vault Options
main.vaultlist.contextMenu.reveal=Reveal Drive
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
main.vaultlist.showEventsButton.tooltip=Open event view
##Notificaition
main.notification.updateAvailable=Update is available.
main.notification.support=Support Cryptomator.
@@ -553,6 +555,12 @@ dokanySupportEnd.message=Support end for Dokany
dokanySupportEnd.description=The volume type Dokany is no longer supported by Cryptomator. Your settings are adjusted to use the default volume type now. You can view the default type in the preferences.
dokanySupportEnd.preferencesBtn=Open Preferences
#Retry If Readonly
retryIfReadonly.title=Restricted Vault Access
retryIfReadonly.message=No write access to vault directory
retryIfReadonly.description=Cryptomator cannot write to the vault directory. You can change the vault to be read-only and try again. This option can be disabled in the vault options.
retryIfReadonly.retry=Change and Retry
# Share Vault
shareVault.title=Share Vault
shareVault.message=Would you like to share your vault with others?
@@ -571,4 +579,30 @@ shareVault.hub.message=How to share a Hub vault
shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
shareVault.hub.openHub=Open Cryptomator Hub
shareVault.hub.openHub=Open Cryptomator Hub
# Event View
eventView.title=Events
eventView.filter.allVaults=All
eventView.clearListButton.tooltip=Dismiss all
## event list entries
eventView.entry.vaultLocked.message=***********
eventView.entry.vaultLocked.description=Unlock "%s" for details
eventView.entry.conflictResolved.message=Resolved conflict
eventView.entry.conflictResolved.showDecrypted=Show decrypted file
eventView.entry.conflictResolved.copyDecrypted=Copy decrypted path
eventView.entry.conflict.message=Conflict resolution failed
eventView.entry.conflict.showDecrypted=Show decrypted, original file
eventView.entry.conflict.copyDecrypted=Copy decrypted, original path
eventView.entry.conflict.showEncrypted=Show conflicting, encrypted file
eventView.entry.conflict.copyEncrypted=Copy conflicting, encrypted path
eventView.entry.decryptionFailed.message=Decryption failed
eventView.entry.decryptionFailed.showEncrypted=Show encrypted file
eventView.entry.decryptionFailed.copyEncrypted=Copy encrypted path
eventView.entry.brokenDirFile.message=Broken directory link
eventView.entry.brokenDirFile.showEncrypted=Show broken, encrypted link
eventView.entry.brokenDirFile.copyEncrypted=Copy path of broken link
eventView.entry.brokenFileNode.message=Broken filesystem node
eventView.entry.brokenFileNode.showEncrypted=Show broken, encrypted node
eventView.entry.brokenFileNode.copyEncrypted=Copy path of broken, encrypted node
eventView.entry.brokenFileNode.copyDecrypted=Copy decrypted path
@@ -118,4 +118,6 @@
#Dokany Support End
#Retry If Readonly
# Share Vault
+12 -3
View File
@@ -13,6 +13,7 @@ generic.button.copied=تم النسخ!
generic.button.done=تم
generic.button.next=التالي
generic.button.print=طباعة
generic.button.remove=حذف
# Error
error.message=حدث خطأ ما
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=افتح الان
removeVault.title=احذف الحافظة
removeVault.message=حذف المخزن؟
removeVault.description=سيؤدي هذا إلى نسيان Cryptomator لهذا المخزن فقط. يمكنك إضافته مرة أخرى لاحقاً. لن يتم حذف أي من الملفات المشفرة من القرص الصلب الخاص بك.
removeVault.confirmBtn=احذف الحافظة
# Change Password
changepassword.title=تغيير كلمة المرور
@@ -176,7 +176,6 @@ hub.registerFailed.description.generic=حدث خطأ في عملية تسجيل
hub.registerFailed.description.deviceAlreadyExists=هذا الجهاز مسجل لمستخدم مختلف بالفعل. حاول تغيير حساب المستخدم أو استخدام جهاز مختلف.
### Unauthorized
hub.unauthorized.message=تم رفض الوصول
hub.unauthorized.description=لم يتم بعد منح الإذن لجهازك بالوصول إلى هذا المخزن. اطلب من مالك المخزن أن يأذن بذلك.
### Requires Account Initialization
hub.requireAccountInit.message=مطلوب اتخاذ إجراء
hub.requireAccountInit.description.0=للمتابعة، يرجى إكمال الخطوات المطلوبة في
@@ -287,6 +286,7 @@ preferences.general.debugLogging=تمكين سجلات التصحيح
preferences.general.debugDirectory=عرض ملفات السجل
preferences.general.autoStart=تشغيل Cryptomator عند بدء تشغيل النظام
preferences.general.keychainBackend=تخزين كلمات المرور مع
preferences.general.quickAccessService=إضافة الخزانات المفتوحة إلى منطقة الوصول السريع
## Interface
preferences.interface=الواجهة
preferences.interface.theme=الشكل والمظهر
@@ -300,6 +300,7 @@ preferences.interface.interfaceOrientation=اتجاه الواجهة
preferences.interface.interfaceOrientation.ltr=من اليسار إلى اليمين
preferences.interface.interfaceOrientation.rtl=من اليمين إلى اليسار
preferences.interface.showTrayIcon=إظهار أيقونة اللوحة (يتطلب إعادة تشغيل)
preferences.interface.compactMode=تمكين قائمة الخزنة المدمجة
## Volume
preferences.volume=القرص الإفتراضي
preferences.volume.type=نوع القرص الافتراضي
@@ -333,12 +334,14 @@ preferences.contribute.registeredFor=شهادة الداعم مسجلة لـ %s
preferences.contribute.noCertificate=ادعم Cryptomator واحصل على شهادة الداعم. إنها مثل مفتاح الترخيص لكن للأشخاص الرائعين الذين يستخدمون البرامج المجانية ؛-)
preferences.contribute.getCertificate=ليس لديك واحدة بعد؟ تعلم كيف يمكنك الحصول عليها.
preferences.contribute.promptText=قم بلصق رمز شهادة الداعم هنا
preferences.contribute.thankYou=نشكرك على دعمك لتطوير Cryptomator مفتوح المصدر!
preferences.contribute.donate=تبرع
preferences.contribute.sponsor=الراعي
### Remove License Key Dialog
removeCert.title=إزالة الشهادة
removeCert.message=إزالة شهادة الداعم؟
removeCert.confirmBtn=حذف
removeCert.description=الميزات الأساسية لـ Cryptomator غير متأثرة بهذا. لا يتم تقييد الوصول إلى خزاناتك ولا يتم تخفيض مستوى الأمان.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -388,7 +391,11 @@ main.vaultlist.contextMenu.unlock=فتح…
main.vaultlist.contextMenu.unlockNow=افتح الان
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
main.vaultlist.contextMenu.reveal=اظهار القرص
main.vaultlist.addVaultBtn.menuItemNew=إنشاء مخزن جديد...
main.vaultlist.addVaultBtn.menuItemExisting=افتح مخزن موجود...
##Notificaition
main.notification.updateAvailable=هناك تحديث متاح.
main.notification.support=دعم Cryptomator.
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=شكرا لاختيار Cryptomator لحماية ملفاتك. إذا كنت بحاجة إلى أية مساعدة، تحقق من دليل وتعليمات الإستخدام:
@@ -544,6 +551,8 @@ dokanySupportEnd.message=انتهاء الدعم لـDokany
dokanySupportEnd.description=نوع وحدة التخزين Dokany لم يعد مدعوماً من قبل Cryptomator. تم تعديل إعداداتك لاستخدام نوع وحدة التخزين الافتراضي الآن. يمكنك عرض النوع الافتراضي في التفضيلات.
dokanySupportEnd.preferencesBtn=فتح التفضيلات
#Retry If Readonly
# Share Vault
shareVault.title=مشاركة الخزانة
shareVault.message=هل ترغب في مشاركة خزانتك مع الآخرين؟
@@ -13,6 +13,7 @@ generic.button.copied=Күсермә алынды!
generic.button.done=Тамам
generic.button.next=Киләһе
generic.button.print=Баҫтыр
generic.button.remove=Алып ташлау
# Error
error.message=Хата килеп сыҡты
@@ -104,7 +105,6 @@ addvaultwizard.success.unlockNow=Хәҙер бикте ас
removeVault.title="%s" һаҡлағысын алып ташла
removeVault.message=Һаҡлағысты алып ташларғамы?
removeVault.description=Cryptomator был һаҡлағысты ғына онотасаҡ. Һеҙ уны яңынан өҫтәй алаһығыҙ. Ҡаты дисктан шифрланған файлдар юйылмаясаҡ.
removeVault.confirmBtn=Һаҡлағысты алып ташла
# Change Password
changepassword.title=Серһүҙҙе үҙгәртеү
@@ -164,7 +164,6 @@ hub.registerSuccess.unlockBtn=Биген ас
### Registration Failed
### Unauthorized
hub.unauthorized.message=Инеү кире ҡағылды
hub.unauthorized.description=Һеҙҙең йыһаз әлегә был һаҡлағысҡа инеү хоҡуғына эйә түгел. Һаҡлағыс хужаһынан рөхсәт һорағыҙ.
### Requires Account Initialization
hub.requireAccountInit.message=Эш-хәрәкәт кәрәкле
hub.requireAccountInit.description.0=Дауам итер өсөн, кәрәкле аҙымдар тамамланырға тейеш урын:
@@ -316,7 +315,6 @@ preferences.contribute.getCertificate=Юҡ мы әллә? Уны нисек ал
preferences.contribute.promptText=Ярҙамсы сертификатын бында йәбештерегеҙ
### Remove License Key Dialog
removeCert.confirmBtn=Алып ташлау
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -517,4 +515,6 @@ updateReminder.yesAutomatically=Эйе, автоматик рәүештә
#Dokany Support End
dokanySupportEnd.preferencesBtn=Көйләүҙәрҙе ас
#Retry If Readonly
# Share Vault
@@ -13,6 +13,7 @@ generic.button.copied=Скапіявана!
generic.button.done=Файна
generic.button.next=Далей
generic.button.print=Друкаваць
generic.button.remove=Выдаліць
# Error
error.message=Адбылася памылка
@@ -104,7 +105,6 @@ addvaultwizard.success.unlockNow=Разамкнуць зараз
removeVault.title=Выдаліць "%s"
removeVault.message=Ці выдаліць скарбніцу?
removeVault.description=Гэта прывядзе толькі да таго, што Cryptomator забудзецца на гэтую скрынку. Ты зможаш дадаць яе пазней ізноў. Аніякія зашыфраваныя файлы з тайго жорсткага дыску не выдаляцьмуцца.
removeVault.confirmBtn=Выдаліць скарбніцу
# Change Password
changepassword.title=Змяніць пароль
@@ -157,7 +157,6 @@ hub.registerSuccess.unlockBtn=Адамкнуць
### Registration Failed
### Unauthorized
hub.unauthorized.message=Адмова ў доступе
hub.unauthorized.description=Тваёй прыладзе ў дадзены момант не дазволена мець доступ да гэтай скрабніцы. Запытайся ўладальніка скрабніцы за дазволам.
### Requires Account Initialization
### License Exceeded
hub.invalidLicense.message=Несапраўдная ліцэнзія Hub
@@ -303,7 +302,6 @@ preferences.contribute.getCertificate=Яшчэ ня маеш такога? Да
preferences.contribute.promptText=Устаў код сэртыфікату ахвяравальніка сюды
### Remove License Key Dialog
removeCert.confirmBtn=Выдаліць
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -497,4 +495,6 @@ updateReminder.yesAutomatically=Так, аўтаматычна
#Dokany Support End
dokanySupportEnd.preferencesBtn=Адчыніць налады
#Retry If Readonly
# Share Vault
@@ -13,6 +13,7 @@ generic.button.copied=Копирано!
generic.button.done=Готово
generic.button.next=Напред
generic.button.print=Отпечатване
generic.button.remove=Премахване
# Error
error.message=Възникна грешка
@@ -104,7 +105,6 @@ addvaultwizard.success.unlockNow=Отключване сега
removeVault.title=Премахване на „%s“
removeVault.message=Премахване на хранилище?
removeVault.description=По този начин Криптоматор ще забрави за това хранилище. Можете да го добавите отново. Шифрованите файлове няма да бъдат премахнати от твърдия диск.
removeVault.confirmBtn=Премахване
# Change Password
changepassword.title=Промяна на парола
@@ -164,7 +164,6 @@ hub.registerSuccess.unlockBtn=Отключване
### Registration Failed
### Unauthorized
hub.unauthorized.message=Отказан достъп
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
### Requires Account Initialization
hub.requireAccountInit.message=Необходимо е действие
hub.requireAccountInit.description.0=За да продължите завършете необходимите стъпки в
@@ -316,7 +315,6 @@ preferences.contribute.getCertificate=Все още нямате? Научете
preferences.contribute.promptText=Поставете тук кода на сертификата за дарение
### Remove License Key Dialog
removeCert.confirmBtn=Премахване
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -517,4 +515,6 @@ updateReminder.yesAutomatically=Да, автоматично
#Dokany Support End
dokanySupportEnd.preferencesBtn=Към настройките
#Retry If Readonly
# Share Vault
@@ -13,6 +13,7 @@ generic.button.copied=কপি হয়েছে!
generic.button.done=সম্পন্ন হয়েছে
generic.button.next=পরবর্তী
generic.button.print=প্রিন্ট
generic.button.remove=বাতিল
# Error
error.message=ত্রুটি %s
@@ -127,7 +128,6 @@ lock.forced.retryBtn=পুনরায় চেষ্টা করুন
## Contribution
### Remove License Key Dialog
removeCert.confirmBtn=বাতিল
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -182,4 +182,6 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
#Dokany Support End
#Retry If Readonly
# Share Vault
@@ -13,6 +13,7 @@ generic.button.copied=Kopirano!
generic.button.done=Gotovo
generic.button.next=Sljedeće
generic.button.print=Ispis
generic.button.remove=Ukloni
# Error
error.message=Došlo je do greške
@@ -78,7 +79,6 @@ addvaultwizard.success.unlockNow=Otključaj sada
# Remove Vault
removeVault.title=Ukloni Sef
removeVault.description=Ovo će samo natjerati Cryptomator da zaboravi na ovaj sef. Možete ga dodati ponovo kasnije. Nijedna enkriptovana datoteka neće se izbrisati s vašeg diska.
removeVault.confirmBtn=Ukloni Sef
# Change Password
changepassword.title=Promjeni lozinku
@@ -175,7 +175,6 @@ preferences.updates.updateAvailable=Dostupno ažuriranje na verziju %s.
## Contribution
### Remove License Key Dialog
removeCert.confirmBtn=Ukloni
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -319,4 +318,6 @@ quit.lockAndQuitBtn=Zaključaj i zatvori
#Dokany Support End
#Retry If Readonly
# Share Vault
+21 -3
View File
@@ -13,6 +13,7 @@ generic.button.copied=Copiat!
generic.button.done=Fet
generic.button.next=Següent
generic.button.print=Imprimeix
generic.button.remove=Elimina
# Error
error.message=S'ha produït un error
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=Desbloqueja ara
removeVault.title=Elimina la caixa forta
removeVault.message=Voleu eliminar la caixa forta?
removeVault.description=Cryptomator simplement deixarà de mostrar la caixa forta. Podeu tornar a afegir-la més endavant. Cap fitxer xifrat serà eliminat del disc dur.
removeVault.confirmBtn=Elimina la caixa forta
# Change Password
changepassword.title=Canvia la contrasenya
@@ -176,7 +176,6 @@ hub.registerFailed.description.generic=S'ha produït un error en el procés de r
hub.registerFailed.description.deviceAlreadyExists=El dispositiu ja ha estat registrat per un altre usuari. Mireu de canviar el compte d'usuari o feu servir un dispositiu diferent.
### Unauthorized
hub.unauthorized.message=Accés denegat
hub.unauthorized.description=El vostre dispositiu no ha estat encara autoritzat a accedir a aquesta caixa forta. Demaneu autorització al propietari.
### Requires Account Initialization
hub.requireAccountInit.message=Acció necessària
hub.requireAccountInit.description.0=Per a continuar, si us plau, seguiu els passos necessaris en el vostre
@@ -287,6 +286,7 @@ preferences.general.debugLogging=Habilita el registre de depuració
preferences.general.debugDirectory=Mostra els fitxers de registres
preferences.general.autoStart=Executa Cryptomator en engegar el sistema
preferences.general.keychainBackend=Desar contrasenyes amb
preferences.general.quickAccessService=Afegeix caixes fortes desblocades a l'àrea d'accés directe
## Interface
preferences.interface=Interfície
preferences.interface.theme=Aparença
@@ -300,6 +300,7 @@ preferences.interface.interfaceOrientation=Orientació de la interfície
preferences.interface.interfaceOrientation.ltr=Esquerra a dreta
preferences.interface.interfaceOrientation.rtl=Dreta a esquerra
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
preferences.interface.compactMode=Activa la llista compacta de caixes fortes
## Volume
preferences.volume=Unitat virtual
preferences.volume.type=Tipus de volum per defecte
@@ -319,6 +320,13 @@ preferences.updates.currentVersion=Versió actual: %s
preferences.updates.autoUpdateCheck=Comprova automàticament si hi ha actualitzacions
preferences.updates.checkNowBtn=Comprova-ho ara
preferences.updates.updateAvailable=L'actualització a la versió %s es troba disponible.
preferences.updates.lastUpdateCheck=Darrera comprovació: %s
preferences.updates.lastUpdateCheck.never=mai
preferences.updates.lastUpdateCheck.recently=recentment
preferences.updates.lastUpdateCheck.daysAgo=Fa %s dies
preferences.updates.lastUpdateCheck.hoursAgo=fa %s hores
preferences.updates.checkFailed=Ha fallat la cerca d'actualitzacions. Si us plau, comproveu la connexió a Internet o torneu a provar-ho més tard.
preferences.updates.upToDate=Cryptomator està actualitzat.
## Contribution
preferences.contribute=Doneu-nos suport
@@ -326,9 +334,13 @@ preferences.contribute.registeredFor=Certificat de col·laborador registrat per
preferences.contribute.noCertificate=Doneu suport a Cryptomator i rebeu un certificat de col·laborador. És com una clau de llicència però per a gent meravellosa que fa servir programari lliure. ;-)
preferences.contribute.getCertificate=Encara no en teniu cap? Sapigueu com aconseguir-ne un.
preferences.contribute.promptText=Enganxeu aquí el certificat de col·laborador
preferences.contribute.thankYou=Gràcies pel vostre suport al desenvolupament open-source de Cryptomator!
preferences.contribute.donate=Feu un donatiu
preferences.contribute.sponsor=Patrocinador
### Remove License Key Dialog
removeCert.confirmBtn=Elimina
removeCert.title=Suprimeix certificat
removeCert.description=Les característiques principals de Cryptomator no es veuen afectades per això. Ni l'accés a les vostres caixes fortes s'ha restringit ni el nivell de seguretat ha estat rebaixat.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -378,7 +390,11 @@ main.vaultlist.contextMenu.unlock=Desbloca…
main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
main.vaultlist.contextMenu.reveal=Mostra la unitat
main.vaultlist.addVaultBtn.menuItemNew=Crea una nova caixa forta…
main.vaultlist.addVaultBtn.menuItemExisting=Obri una caixa forta existent...
##Notificaition
main.notification.updateAvailable=Hi ha una actualització disponible.
main.notification.support=Doneu suport a Cryptomator.
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
@@ -531,6 +547,8 @@ updateReminder.yesAutomatically=Sí, automàticament
#Dokany Support End
dokanySupportEnd.preferencesBtn=Obrir les Preferències
#Retry If Readonly
# Share Vault
shareVault.title=Comparteix la caixa forta
shareVault.message=Voleu compartir la vostra caixa forta amb altres persones?
@@ -13,6 +13,7 @@ generic.button.copied=Zkopírováno!
generic.button.done=Hotovo
generic.button.next=Další
generic.button.print=Tisk
generic.button.remove=Odstranit
# Error
error.message=Chyba %s
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=Odemknout nyní
removeVault.title=Odstranit trezor
removeVault.message=Odstranit trezor?
removeVault.description=Toto jen umožní Cryptomatoru zapomenout tento trezor. Později jej můžete opět přidat. Žádné šifrované soubory nebudou z vašeho disku smazány.
removeVault.confirmBtn=Odstranit trezor
# Change Password
changepassword.title=Změnit heslo
@@ -137,6 +137,7 @@ unlock.success.revealBtn=Zobrazit jednotku
unlock.error.customPath.message=Nelze připojit trezor k vlastní cestě
unlock.error.customPath.description.notSupported=Pokud chcete pokračovat v používání vlastní cesty, přejděte do nastavení a vyberte typ hlasitosti, který ji podporuje. V opačném případě přejděte do možností trezoru a vyberte podporovaný přípojný bod.
unlock.error.customPath.description.notExists=Cesta k připojení neexistuje. Buď ji vytvořte ve vašem lokálním souborovém systému, nebo ji změňte v možnostech trezoru.
unlock.error.customPath.description.inUse=Označení úložiště nebo vlastní cesta "%s" se již používá.
unlock.error.restartRequired.message=Nelze odemknout trezor
unlock.error.restartRequired.description=Změňte typ svazku v možnostech trezoru nebo restartujte Cryptomator.
unlock.error.title=Odemknutí "%s" selhalo
@@ -171,7 +172,6 @@ hub.registerFailed.description.generic=Došlo k chybě v registračním procesu.
hub.registerFailed.description.deviceAlreadyExists=Toto zařízení je již registrováno pro jiného uživatele. Zkuste změnit uživatelský účet nebo použijte jiné zařízení.
### Unauthorized
hub.unauthorized.message=Přístup odepřen
hub.unauthorized.description=Vaše zařízení dosud nebylo oprávněno k přístupu k tomuto trezoru. Požádejte vlastníka trezoru, aby jej autorizoval.
### Requires Account Initialization
hub.requireAccountInit.message=Požadována akce
hub.requireAccountInit.description.0=Chcete-li pokračovat, vyplňte prosím požadované kroky ve vašem
@@ -315,7 +315,6 @@ preferences.contribute.getCertificate=Ještě žádný nemáte? Naučte se, jak
preferences.contribute.promptText=Sem vložte kód certifikátu podporovatele
### Remove License Key Dialog
removeCert.confirmBtn=Odstranit
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -516,6 +515,8 @@ dokanySupportEnd.title=Oznámení o zastaralosti
dokanySupportEnd.message=Konec podpory pro Dokany
dokanySupportEnd.preferencesBtn=Otevřít předvolby
#Retry If Readonly
# Share Vault
shareVault.title=Sdílet trezor
shareVault.message=Chcete sdílet svůj trezor s ostatními?
@@ -13,6 +13,7 @@ generic.button.copied=Kopieret!
generic.button.done=Færdig
generic.button.next=Næste
generic.button.print=Print
generic.button.remove=Fjern
# Error
error.message=Der opstod en fejl
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=Lås op nu
removeVault.title=Fjern "%s"
removeVault.message=Fjern boks?
removeVault.description=Dette får blot Cryptomator til at glemme boksen. Du kan tilføje den igen senere. Ingen krypterede filer bliver slettet fra harddisken.
removeVault.confirmBtn=Fjern boks
# Change Password
changepassword.title=Skift adgangskode
@@ -176,7 +176,7 @@ hub.registerFailed.description.generic=Der opstod en fejl i registreringsprocess
hub.registerFailed.description.deviceAlreadyExists=Denne enhed er allerede registreret af en anden bruger. Prøv at ændre brugerkontoen eller brug en anden enhed.
### Unauthorized
hub.unauthorized.message=Adgang nægtet
hub.unauthorized.description=Din enhed er endnu ikke blevet godkendt til at få adgang til denne boks. Spørg boks-ejeren om godkendelse.
hub.unauthorized.description=Du har ikke tilladelse til at åbne denne boks. Kontant ejeren af boksen for at anmode om adgang.
### Requires Account Initialization
hub.requireAccountInit.message=Handling påkrævet
hub.requireAccountInit.description.0=For at fortsætte, skal du fuldføre de nødvendige trin i din
@@ -287,6 +287,7 @@ preferences.general.debugLogging=Aktivér fejllogning
preferences.general.debugDirectory=Vis logfiler
preferences.general.autoStart=Start Cryptomator automatisk ved opstart
preferences.general.keychainBackend=Gem adgangskoder med
preferences.general.quickAccessService=Tilføj oplåste bokse til området hurtig adgang
## Interface
preferences.interface=Brugerflade
preferences.interface.theme=Udseende
@@ -319,6 +320,7 @@ preferences.updates.currentVersion=Nuværende version: %s
preferences.updates.autoUpdateCheck=Søg automatisk efter opdateringer
preferences.updates.checkNowBtn=Kontrollér nu
preferences.updates.updateAvailable=Opdatering til version %s er tilgængelig.
preferences.updates.checkFailed=Søgning efter opdateringer fejlede. Tjek din internetforbindelse eller forsøg igen senere.
## Contribution
preferences.contribute=Støt os
@@ -328,7 +330,6 @@ preferences.contribute.getCertificate=Har du ikke et allerede? Se her hvordan du
preferences.contribute.promptText=Indsæt koden for supporter-certifikatet her
### Remove License Key Dialog
removeCert.confirmBtn=Fjern
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -379,6 +380,8 @@ main.vaultlist.contextMenu.unlockNow=Lås op nu
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
main.vaultlist.contextMenu.reveal=Vis drev
##Notificaition
main.notification.updateAvailable=Opdatering er tilgængelig.
main.notification.support=Støt Cryptomator.
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang:
@@ -531,6 +534,8 @@ updateReminder.yesAutomatically=Ja, automatisk
#Dokany Support End
dokanySupportEnd.preferencesBtn=Åbn Indstillinger
#Retry If Readonly
# Share Vault
shareVault.title=Del Boks
shareVault.message=Vil du dele din boks med andre?
@@ -13,6 +13,7 @@ generic.button.copied=Kopiert!
generic.button.done=Fertig
generic.button.next=Weiter
generic.button.print=Drucken
generic.button.remove=Entfernen
# Error
error.message=Ein Fehler ist aufgetreten
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=Jetzt entsperren
removeVault.title=„%s“ entfernen
removeVault.message=Tresor entfernen?
removeVault.description=Dieser Tresor wird nur aus der Tresorliste entfernt; du kannst ihn später jederzeit wieder hinzufügen. Es werden keine verschlüsselten Daten gelöscht.
removeVault.confirmBtn=Tresor entfernen
# Change Password
changepassword.title=Passwort ändern
@@ -176,7 +176,6 @@ hub.registerFailed.description.generic=Im Registrierungsprozess ist ein Fehler a
hub.registerFailed.description.deviceAlreadyExists=Dieses Gerät ist bereits für einen anderen Benutzer registriert. Ändere das Benutzerkonto oder verwende ein anderes Gerät.
### Unauthorized
hub.unauthorized.message=Zugriff verweigert
hub.unauthorized.description=Dein Gerät wurde noch nicht für den Zugriff auf diesen Tresor autorisiert. Bitte den Tresorbesitzer, dein Gerät zu autorisieren.
### Requires Account Initialization
hub.requireAccountInit.message=Aktion erforderlich
hub.requireAccountInit.description.0=Um fortzufahren, führe bitte die erforderlichen Schritte in deinem
@@ -245,7 +244,7 @@ health.checkList.selectAllButton=Alle Prüfungen auswählen
health.checkList.deselectAllButton=Alle Prüfungen abwählen
health.check.runBatchBtn=Ausgewählte Prüfungen ausführen
## Detail view
health.check.detail.noSelectedCheck=Wähle für die Ergebnisse eine abgeschlossene Intregritätsprüfung in der Liste links aus.
health.check.detail.noSelectedCheck=Wähle für die Ergebnisse eine abgeschlossene Integritätsprüfung in der Liste links aus.
health.check.detail.checkScheduled=Die Prüfung ist geplant.
health.check.detail.checkRunning=Die Prüfung läuft derzeit …
health.check.detail.checkSkipped=Die Prüfung wurde nicht zur Ausführung ausgewählt.
@@ -287,7 +286,7 @@ preferences.general.debugLogging=Diagnoseprotokoll aktivieren
preferences.general.debugDirectory=Protokolldateien anzeigen
preferences.general.autoStart=Cryptomator beim Systemstart starten
preferences.general.keychainBackend=Passwörter speichern mit
preferences.general.quickAccessService=Die entsperrten Tresore zum Schnellzugriff hinzufügen
preferences.general.quickAccessService=Entsperrte Tresore zum Schnellzugriff hinzufügen
## Interface
preferences.interface=Benutzeroberfläche
preferences.interface.theme=Erscheinungsbild
@@ -301,7 +300,7 @@ preferences.interface.interfaceOrientation=Oberflächenausrichtung
preferences.interface.interfaceOrientation.ltr=Von links nach rechts
preferences.interface.interfaceOrientation.rtl=Von rechts nach links
preferences.interface.showTrayIcon=Symbol im Infobereich anzeigen (Neustart erforderlich)
preferences.interface.compactMode=Kompakte Tresoransicht einschalten
preferences.interface.compactMode=Kompakte Tresorliste
## Volume
preferences.volume=Virtuelles Laufwerk
preferences.volume.type=Standard-Laufwerkstyp
@@ -343,7 +342,6 @@ preferences.contribute.sponsor=Sponsern
removeCert.title=Zertifikat entfernen
removeCert.message=Supporter-Zertifikat entfernen?
removeCert.description=Die Kernfunktionen von Cryptomator sind davon nicht betroffen. Weder der Zugriff auf deine Tresore ist eingeschränkt, noch wird das Sicherheitsniveau verringert.
removeCert.confirmBtn=Entfernen
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -548,11 +546,13 @@ updateReminder.yesOnce=Ja, einmalig
updateReminder.yesAutomatically=Ja, automatisch
#Dokany Support End
dokanySupportEnd.title=Hinweis: nicht mehr verwendet
dokanySupportEnd.message=Ende der Unterstützung für Dokany
dokanySupportEnd.title=Hinweis zur Ausmusterung
dokanySupportEnd.message=Supportende für Dokany
dokanySupportEnd.description=Der Laufwerkstyp Dokany wird von Cryptomator nicht weiter unterstützt. Deine Einstellungen wurden angepasst, dass der Standard-Laufwerkstyp verwendet wird. Du kannst den Standardtyp in den Einstellungen anzeigen lassen.
dokanySupportEnd.preferencesBtn=Einstellungen öffnen
#Retry If Readonly
# Share Vault
shareVault.title=Tresor teilen
shareVault.message=Möchtest du deinen Tresor mit anderen teilen?
@@ -13,6 +13,7 @@ generic.button.copied=Αντιγράφηκε!
generic.button.done=Κλείσιμο
generic.button.next=Επόμενο
generic.button.print=Εκτύπωση
generic.button.remove=Αφαίρεση
# Error
error.message=Παρουσιάστηκε σφάλμα
@@ -105,7 +106,6 @@ addvaultwizard.success.unlockNow=Ξεκλείδωμα τώρα
removeVault.title=Διαγραφή "%s"
removeVault.message=Διαγραφή κρύπτης;
removeVault.description=Αυτό θα κάνει το Cryptomator να ξεχάσει αυτή την κρύπτη. Μπορείτε να την προσθέσετε ξανά. Κανένα κρυπτογραφημένο αρχείο δε θα διαγραφεί από τον σκληρό σας δίσκο.
removeVault.confirmBtn=Διαγραφή Κρύπτης
# Change Password
changepassword.title=Αλλαγή κωδικού πρόσβασης
@@ -176,7 +176,7 @@ hub.registerFailed.description.generic=Παρουσιάστηκε σφάλμα
hub.registerFailed.description.deviceAlreadyExists=Αυτή η συσκευή είναι ήδη εγγεγραμμένη για διαφορετικό χρήστη. Προσπαθήστε να αλλάξετε τον λογαριασμό χρήστη ή να χρησιμοποιήσετε διαφορετική συσκευή.
### Unauthorized
hub.unauthorized.message=Δεν επιτρέπεται η πρόσβαση
hub.unauthorized.description=Η συσκευή σας δεν έχει ακόμη εξουσιοδοτηθεί να έχει πρόσβαση σε αυτή την κρύπτη. Ζητήστε από τον κάτοχο της κρύπτης να την εξουσιοδοτήσει.
hub.unauthorized.description=Δεν είστε εξουσιοδοτημένοι να ανοίξετε αυτή την κρύπτη. Επικοινωνήστε με τον ιδιοκτήτη τς κρύπτης για να ζητήσετε πρόσβαση.
### Requires Account Initialization
hub.requireAccountInit.message=Απαιτείται ενέργεια
hub.requireAccountInit.description.0=Για να συνεχίσετε, παρακαλούμε ολοκληρώστε τα βήματα που απαιτούνται στο δικό σας
@@ -343,7 +343,6 @@ preferences.contribute.sponsor=Χορηγός
removeCert.title=Αφαίρεση Πιστοποιητικού
removeCert.message=Αφαίρεση πιστοποιητικού υποστηρικτή;
removeCert.description=Οι βασικές λειτουργίες του Cryptomator δεν επηρεάζονται από αυτό. Ούτε η πρόσβαση στις κρύπτες σας είναι περιορισμένη ούτε το επίπεδο ασφάλειας μειώνεται.
removeCert.confirmBtn=Αφαίρεση
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -553,6 +552,12 @@ dokanySupportEnd.message=Τέλος υποστήριξης για Dokany
dokanySupportEnd.description=Ο τύπος τόμου Dokany δεν υποστηρίζεται πλέον από το Cryptomator. Οι ρυθμίσεις σας έχουν προσαρμοστεί για να χρησιμοποιούν τον προεπιλεγμένο τύπο τόμου. Μπορείτε να δείτε τον προεπιλεγμένο τύπο στις προτιμήσεις.
dokanySupportEnd.preferencesBtn=Άνοιγμα Προτιμήσεων
#Retry If Readonly
retryIfReadonly.title=Περιορισμένη Πρόσβαση Κρύπτης
retryIfReadonly.message=Δεν υπάρχει πρόσβαση εγγραφής στον κατάλογο κρύπτης
retryIfReadonly.description=Το Cryptomator δεν μπορεί να γράψει στον κατάλογο κρύπτης. Μπορείτε να αλλάξετε την κρύπτη ώστε να είναι μόνο για ανάγνωση και να προσπαθήσετε ξανά. Αυτή η επιλογή μπορεί να απενεργοποιηθεί στις επιλογές κρύπτης.
retryIfReadonly.retry=Αλλαγή και Επανάληψη
# Share Vault
shareVault.title=Κοινή χρήση Κρύπτης
shareVault.message=Θα θέλατε να μοιραστείτε την κρύπτη σας με άλλους;

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