Compare commits

...
Sign in to create a new pull request.

77 commits

Author SHA1 Message Date
9e0c2726ef feat: add empty-trash action to trash view context menu
Right-clicking inside the trash view now shows 'Empty trash' when
the trash is not empty, in addition to the existing per-item
restore/delete actions and sort options.
2026-07-25 22:54:15 +02:00
30e562c8c2 fix: nav_context_menu must produce one submenu per nav item
The segmented_button widget asserts that context_menu children
count matches model length. The yoda fork was building a single
flat menu for the clicked item instead of iterating over all
nav_model items like upstream, causing an assertion panic on
right-click in the sidebar (trash, folders, etc.).

Align with upstream: iterate self.nav_model.iter() and use
menu::nav_context() instead of menu::items().
2026-07-25 22:53:11 +02:00
542657b557 chore: refresh Cargo.lock after version bump to 1.3.0 2026-07-25 22:20:51 +02:00
8234e11ff3 chore: bump version to 1.3.0 2026-07-22 15:56:59 +02:00
d6e1572611 chore: refresh lockfile post-1.2.0 rebase
Leyoda 2026 – GPLv3
2026-07-03 15:28:40 +02:00
fd4a2ba610 fix: absorb libcosmic theme-v2 API (nav_context_menu, app_popup LiveSettings)
Leyoda 2026 – GPLv3
2026-07-03 15:26:15 +02:00
2a74748083 chore: use local cosmic-text checkout 2026-07-03 15:23:28 +02:00
Leyoda
17fc1cabbb yoda: cosmic-files customizations (squashed 21 commits)
This commit squashes the 21 local commits that customize cosmic-files for
the yoda stack, to allow a clean rebase on upstream/master.

Original commits (chronological):

- 9bcfe7a Cargo.toml: patch libcosmic via local path for dev builds
- 04abd13 yoda: depend on libcosmic-yoda (path) instead of upstream libcosmic
- 02adcc3 lockfile: libcosmic-yoda 0.1.0-yoda -> 0.1.0-yoda.2
- a025fd6 yoda: prefer cosmic-yoterm over upstream cosmic-term in terminal fallback
- e8d62ae yoda: add "Always use this app" toggle to OpenWith dialog
- 8fb2b15 yoda wayland-v5: redirect window_clipboard + cosmic-text to local forks
- 0595296 yoda: Dolphin-style quick actions toolbar under the headerbar
- 4b6d345 yoda: fix missing rename icon in toolbar
- 8b51af1 yoda: use pencil-symbolic for the Rename toolbar button
- 33a5c8f yoda: phase 2 - customizable toolbar (settings toggles per button)
- 1cf17dc yoda: phase 3 - drag-drop toolbar editor in Settings
- 11d4357 yoda: add up/down buttons next to drag handle in toolbar editor
- af843d2 yoda: direct drag-drop reorder on the toolbar itself
- 94c3e6c yoda: toolbar as segmented_button for working drag reorder
- f053819 yoda: toolbar icon-only + clean visual (Control style, 32px squares)
- 338354c Improve initial directory listing latency
- d080bc8 Resolve cosmic-files warnings without masking
- 69c35ab yoda: switch window_clipboard patch to public Forgejo fork
- 35e115f yoda: switch cosmic-text patch to public Forgejo fork
- 6f3adcd chore: clean feature-gated warnings
- 57ab1ec fix: clean files warnings for terminal build

Original tip preserved as tag backup/pre-rebase-upstream-20260524.
2026-07-03 15:21:21 +02:00
norepro
43a4228ca4
feat: support MIME subclasses in file chooser dialog 2026-07-02 17:52:14 +02:00
Jeremy Soller
3ef7913abd Epoch 1.2.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-06-30 13:13:13 -06:00
ZlordHUN
49107187a4
feat: view folder's content in trash
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-29 18:39:12 +02:00
Michael Murphy
50deb28a11
i18n: translation updates from weblate (#1822)
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/uk/
Translation: Pop OS/COSMIC Files

Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Sami BABAT <samibabat@gmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: Марко М. Костић <marko.m.kostic@gmail.com>
2026-06-26 18:31:02 +02:00
Hosted Weblate
8883d7cd64
i18n: translation updates from weblate
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Sami BABAT <samibabat@gmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: Марко М. Костић <marko.m.kostic@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/uk/
Translation: Pop OS/COSMIC Files
2026-06-26 18:01:57 +02:00
Fred
4d6788c9fd
feat: add SHA256 checksums to file properties 2026-06-26 16:57:13 +02:00
JADella94
cd427c746c
fix: panic when a second zip extract dialog is opened
* Syncs extracted zip files on the compio executor's thread
* Close the first zip extract dialog before opening a new one to fix a panic
---------

Co-authored-by: James A DellaMorte <dellamorte.james@comcast.net>
2026-06-24 20:12:10 +02:00
Jeremy Soller
c653b69c75 Epoch 1.1.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-06-23 10:15:44 -06:00
Gene Wood
65f5f454be
feat: add PageUp and PageDown bindings 2026-06-22 23:37:41 +02:00
Gene Wood
53a33dbe74 Clear hover highlights when navigating with keyboard
Keyboard navigation (arrow keys) did not clear item hover state, causing
items to retain their grey hover background after the mouse left the
window, resulting in multiple items appearing highlighted simultaneously.

Fixes #1865
2026-06-18 14:41:13 -04:00
Michael Aaron Murphy
7d3487da7b fix(mime_app): unalias mime types when associating apps
Fixes associations for `.iso`, where many apps define `application/x-cd-image` instead of `application/vnd.efi.iso`.
Popsicle and GNOME Disks will now appear as exact matches for opening `.iso` files with this change.
2026-06-16 23:05:07 -04:00
Jeremy Soller
9a3eb1cdea
Mime app handling improvements (#1850)
- Fix empty files getting `application/x-zerosize` as their mime types
    - May be tested by creating an empty `test.cpp` file
- Fix the wrong app being selected as a default if that app was set as
default for another mime type
- Include video players (such as cosmic-player) as suggested apps for
audio files
- Do the same for text-based mime types so that cosmic-edit will be
displayed as a suggestion for `.cpp` files
- Allow related apps to be selected in the Open With dropdown in the
properties context drawer.

---
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-11 19:09:08 -06:00
Michael Aaron Murphy
e8fdb28a24
feat(mime_app): show related apps in open with dropdown in preview 2026-06-11 18:56:23 +02:00
Michael Aaron Murphy
eda8291678
fix(mime_app): track mime type app default by mime 2026-06-11 17:54:12 +02:00
Michael Aaron Murphy
6d4af69318
feat(mime_app): include text editors for text-based mime types 2026-06-11 17:29:42 +02:00
Michael Aaron Murphy
256a6cba19
feat(mime_app): include video players for audio mime types 2026-06-11 17:29:19 +02:00
Michael Aaron Murphy
3cd0d0c00a
refactor: move mime app logic to mime_app module 2026-06-11 17:29:17 +02:00
Michael Aaron Murphy
64998b37a1
fix: do not use application/x-zerosize as mime type for empty files 2026-06-11 15:52:30 +02:00
Jeremy Soller
48eef3906d
improv: use sort-icons instead of unicode arrow glyphs (#1846)
This replaces the up/down arrow glyphs in the context menu for sort
direction with actual sort icons.
This way the icon is not dependent on the user's system font. For
example "Adwaita Sans" contains the up arrow but not the down arrow,
which means the arrow changes shape (depending on the chosen fallback
font) when you switch direction.

<img width="508" height="498" alt="Screenshot_2026-06-09_14-29-58"
src="https://github.com/user-attachments/assets/265eada7-d7c2-4f76-b63d-38c1879e6e85"
/>
<img width="515" height="453" alt="Screenshot_2026-06-09_14-30-09"
src="https://github.com/user-attachments/assets/d2634e96-5245-422a-b236-c49d7c65e79c"
/>

I took the liberty to move the icon from the end of the word to the left
side and also preserve an empty space when it's not drawn. It may need
UX approval.

Relevant mattermost thread:
https://chat.pop-os.org/pop-os/pl/i3nphjm85fbpdfmnd9pt9abs6y

Fixes https://github.com/pop-os/cosmic-files/issues/1844

___
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-10 16:56:30 -06:00
Levi Portenier
54f77f95a6
Speed up app icons and take added association into account (#1847)
- instead of loading app icons, creates `icon()` and lazy load when
needed
- takes user Added Association into account instead of just relying on
the desktop files "no display".


I also changed `is_default`'s ordering from `SecCst` to `Relaxed`. Let
me know if that's wrong, but from what I see it's not necessary in this
case, `Relaxed` is fine.

Before:
<img width="3755" height="1742" alt="image"
src="https://github.com/user-attachments/assets/bd9666fe-4e6b-4506-9081-905e31bb6c93"
/>



After:
<img width="3743" height="1717" alt="image"
src="https://github.com/user-attachments/assets/da439ea7-7f90-433b-80d7-1420f1aebde0"
/>

___
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-10 11:17:32 -06:00
Hojjat
42577aa467 fix: take user's Added Association into account when hiding apps 2026-06-09 16:51:21 -06:00
Hojjat
4ac289f40a improv: lazy load app icons for mime apps 2026-06-09 16:51:21 -06:00
Michael Aaron Murphy
c6c9eac97e fix: hide apps without declared mimetype support 2026-06-09 18:10:24 -04:00
Hojjat
af65a6b1c0 improv: use sort-icons instead of unicode arrow glyphs 2026-06-09 14:36:14 -06:00
Jeremy Soller
1d88fd16d6 Epoch 1.0.16 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-06-09 11:14:26 -06:00
Jeremy Soller
d04c92229c
Sort "other applications" in open with dialog (#1840)
Quick fix that sorts the other apps section.

---
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-09 10:30:34 -06:00
Michael Aaron Murphy
814b49114b
fix: sort other apps section 2026-06-08 18:24:52 +02:00
Jeremy Soller
245dfaa9b0
fix: hide no-display apps in open-with select (#1835)
Prevents desktop entries with `NoDisplay` set from appearing in the
"Open With" section.

Closes #1833 

---
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
2026-06-08 09:46:11 -06:00
Michael Aaron Murphy
7c7970d0c0
fix: hide no-display apps only in other category 2026-06-06 20:26:45 +02:00
Michael Aaron Murphy
fc8f2b4000
fix: hide no-display apps in open-with select 2026-06-06 20:09:36 +02:00
Jeremy Soller
c605dbecaf Epoch 1.0.15 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-06-02 11:35:01 -06:00
Michael Aaron Murphy
fca0753781 fix: gate desktop features for no-default-features build 2026-06-02 09:52:14 -04:00
Michael Aaron Murphy
bb1ae9d458 feat: reload mime app cache when mime app config is updated 2026-06-02 09:52:14 -04:00
Michael Aaron Murphy
0b3d8a9f3f build: use cargo vendor --locked 2026-06-02 09:52:14 -04:00
Michael Aaron Murphy
53cfeebe57 feat: improve mime app detection & do it faster 2026-06-02 09:52:14 -04:00
Hojjat
bf01bb31a3 chore: update libcosmic to fix item highlight in wayland context menu 2026-05-28 21:09:44 +02:00
Ashley Wulber
18eb41930a fix: cosmic-protocols 2026-05-26 13:36:10 -06:00
Hojjat
355423a027 chore: fixes after libcosmic update 2026-05-26 14:14:02 -04:00
Jeremy Soller
cdec335ac7 Epoch 1.0.14 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-05-26 14:14:02 -04:00
Jeremy Soller
2a3c9f12d9
i18n: translation update from Hosted Weblate (#1806)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Files](https://hosted.weblate.org/projects/pop-os/cosmic-files/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-files/horizontal-auto.svg)
2026-05-26 09:06:59 -06:00
Hosted Weblate
5d1c056bed
i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Dan <jonweblin2205@protonmail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Mattias Eriksson <snaggen@gmail.com>
Co-authored-by: Thomas Worofsky <thomas.worofsky@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: oddib <60390653+oddib@users.noreply.github.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ru/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/sv/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hant/
Translation: Pop OS/COSMIC Files
2026-05-25 21:11:35 +02:00
Mario
784200a253
Fix 24h time format in file dialog (#1812)
* Assign parameter config the proper way
* Don't overwrite military time

---------

Co-authored-by: Rrogntudju <rrogntudju@example.com>
2026-05-18 11:57:04 -06:00
Oleg Bespalov
03e537abad feat: add text preview for thumbnails and gallery
Preview text files (≤8 MiB) in grid and gallery: read up to 256 KiB,
handle invalid UTF-8, skip empty files. Add unit tests.

Lets users peek at .txt without opening; size caps avoid blocking the UI.
2026-05-15 14:09:14 -06:00
Levi Portenier
accb9fd418
fix: don't overwrite military time in Message::Config() (#1803)
- [x] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x ] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.

Closes #1798
2026-05-12 17:05:30 -06:00
Jeremy Soller
f9b215dbd4 Epoch 1.0.13 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-05-12 12:00:19 -06:00
Michael Murphy
77615fc6b5
i18n: translation update from Hosted Weblate (#1763)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Files](https://hosted.weblate.org/projects/pop-os/cosmic-files/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-files/horizontal-auto.svg)
2026-05-12 17:21:12 +02:00
Jeremy Soller
048ed3187b
Merge pull request #1793 from pop-os/icons
Fix small file icons from alternative icon themes
2026-05-12 09:13:49 -06:00
Rrogntudju
b4df354585 don't overwrite military time 2026-05-11 20:07:35 -04:00
lorduskordus
4b5c6d2c1b fix(i18n): use separate 'rename' keys for menu and dialog confirm button 2026-05-11 16:49:33 -06:00
Jeremy Soller
3548615d40
Merge pull request #1777 from norepro/add-clear-recents-button
feat(files): Add button to clear recents
2026-05-11 15:04:34 -06:00
Hosted Weblate
55c8a54f28
i18n: translation updates from weblate
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Arve Eriksson <031299870@telia.com>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: BoneNI <bounkirdni@gmail.com>
Co-authored-by: Dan <jonweblin2205@protonmail.com>
Co-authored-by: David Carvalho <david.snt.carvalho@gmail.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Fedorov Alexei <aleksejfedorov963@gmail.com>
Co-authored-by: Feike Donia <feikedonia@proton.me>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Nara Díaz Viñolas <nara.diaz.vinolas@gmail.com>
Co-authored-by: Tadas Misiūnas <tadujo@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: Vilius Paliokas <viliuspaliokas@gmail.com>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: Марко М. Костић <marko.m.kostic@gmail.com>
Co-authored-by: 麋麓 BigELK176 <BigELK176@gmail.com>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ar/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ca/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/de/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ru/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/sr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/sv/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/uk/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hant/
Translation: Pop OS/COSMIC Files
2026-05-11 12:23:52 +02:00
Michael Aaron Murphy
095940e9a9
fix: small grid file icons 2026-05-06 16:40:30 +02:00
Michael Aaron Murphy
6946e95c88
fix: prefer SVG file icons 2026-05-06 16:40:30 +02:00
Jeremy Soller
750c92c841 Epoch 1.0.12 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-05-05 12:03:22 -06:00
Jeremy Soller
b8e02b7df8
Merge pull request #1776 from norepro/network-sidebar-name
fix: Use name for network location
2026-05-04 13:47:08 -06:00
Michael Aaron Murphy
cd48e4fa30 perf: reduce size of DialogPage enum by 1000 bytes 2026-05-01 19:10:23 +02:00
Michael Aaron Murphy
72b40aece3 perf: use Box with tab::Item to reduce message size by 800 bytes 2026-05-01 19:10:23 +02:00
Michael Aaron Murphy
7c47cbbb29 chore: add rust-toolchain to enforce 1.93 2026-05-01 19:10:23 +02:00
Michael Aaron Murphy
f8323171e7 fix: remove match if let guards for Rust 1.93 2026-05-01 19:10:23 +02:00
Jeremy Soller
506f86eff1
Merge pull request #1775 from pop-os/field-codes
fix: improve desktop entry field code handling
2026-04-30 07:49:37 -06:00
Michael Aaron Murphy
37e8734e7b
fix(mime-app): handle no-path command exec 2026-04-30 00:12:28 +02:00
Michael Aaron Murphy
1fcd0dccc8
fix: %F and %U field code handling
Fixes %F path arguments being ignored
2026-04-29 21:52:17 +02:00
Michael Aaron Murphy
d775f3e5e8
fix: improve desktop entry field code handling
- The %f and %u field codes may now be expanded within a word
- Handle field code escapes (%%)
- Support the %c and %k field codes

This will notably fix desktop entries and context menu actions
that pass files as a long argument, such as `--option=%f`.
2026-04-29 17:31:42 +02:00
Vukašin Vojinović
d5dbcc7677 chore: add rustfmt config
Also adds a Zed editor config for automatic formatting with nightly.
2026-04-29 00:53:57 +02:00
Vukašin Vojinović
e91a984da9 chore: clippy 2026-04-29 00:53:57 +02:00
Vukašin Vojinović
93e31d433a chore: update dependencies 2026-04-29 00:53:57 +02:00
Will Sheehan
e7fa2d0fa5 Use existing clear-recents-history string 2026-04-25 00:11:05 -07:00
Will Sheehan
accf5b2ba6 Add button to clear recents 2026-04-25 00:00:29 -07:00
Will Sheehan
38e4fd3ec7 Use name for network location if available 2026-04-24 23:15:31 -07:00
64 changed files with 4605 additions and 2189 deletions

15
.zed/settings.json Normal file
View file

@ -0,0 +1,15 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

1668
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,17 @@
[package]
name = "cosmic-files"
version = "1.0.11"
version = "1.3.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024"
license = "GPL-3.0-only"
rust-version = "1.90"
rust-version = "1.93"
[dependencies]
anyhow = "1"
jiff = "0.2"
jiff-icu = "0.2"
icu = { version = "2.1.1", features = ["compiled_data"] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
icu = { version = "2.2.0", features = ["compiled_data"] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true }
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
dirs = "6.0.0"
gio = { version = "0.21", optional = true }
@ -24,7 +24,7 @@ log = "0.4"
mime_guess = "2"
notify-debouncer-full = "0.7"
notify-rust = { version = "4", optional = true }
open = "5.3.3"
open = "5.3.4"
paste = "1.0"
regex = "1"
rustc-hash = "2.1"
@ -33,18 +33,18 @@ shlex = { version = "1.3" }
tempfile = "3"
tikv-jemallocator = { version = "0.6", optional = true }
tokio = { version = "1", features = ["process", "sync"] }
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
trash = "5.2.6"
url = "2.5"
walkdir = "2.5.0"
wayland-client = { version = "0.31.13", optional = true }
wayland-client = { version = "0.31.14", optional = true }
xdg = { version = "3.0", optional = true }
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
# Compression
bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate
flate2 = "1.1"
tar = "0.4.44"
tar = "0.4.45"
lzma-rust2 = { version = "0.16", optional = true }
ordermap = { version = "1.1.0", features = ["serde"] }
ordermap = { version = "1.2.0", features = ["serde"] }
# Internationalization
i18n-embed = { version = "0.16", features = [
"fluent-system",
@ -56,15 +56,17 @@ slotmap = "1.1.1"
recently-used-xbel = "1.2.0"
zip = "8"
md-5 = "0.10.6"
sha2 = "0.10"
png = "0.18"
jxl-oxide = { version = "0.12.5", features = ["image"] }
num_cpus = "1.17.0"
filetime = "0.2"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
thiserror = "2.0.18"
atomic_float = "1.1.0"
num_enum = "0.7.6"
bstr = "1.12.1"
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
[dependencies.compio]
@ -72,8 +74,9 @@ version = "0.18"
default-features = false
features = ["fs", "io", "macros", "polling", "runtime"]
[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch).
[dependencies.libcosmic-yoda]
path = "../libcosmic"
default-features = false
#TODO: a11y feature crashes
features = [
@ -82,7 +85,7 @@ features = [
"autosize",
"multi-window",
"tokio",
"winit",
"wayland",
"surface-message",
]
@ -110,15 +113,15 @@ default = [
"wayland",
"wgpu",
]
dbus-config = ["libcosmic/dbus-config"]
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
dbus-config = ["libcosmic-yoda/dbus-config"]
desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
desktop-applet = []
gvfs = ["dep:gio", "dep:glib"]
io-uring = ["compio/io-uring"]
jemalloc = ["dep:tikv-jemallocator"]
notify = ["dep:notify-rust"]
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic/wgpu"]
wayland = ["libcosmic-yoda/wayland", "dep:cctk", "dep:wayland-client"]
wgpu = ["libcosmic-yoda/wgpu"]
[profile.dev]
opt-level = 1
@ -144,20 +147,23 @@ fastrand = "2"
test-log = "0.2"
tokio = { version = "1", features = ["rt", "macros"] }
# [patch.'https://github.com/pop-os/cosmic-text']
# cosmic-text = { path = "../cosmic-text" }
# Yoda fork — libcosmic dep is now a direct path dep (libcosmic-yoda above),
# no [patch] block needed anymore. Keeping the block below would be a no-op
# since nothing in the dep graph still asks for pop-os/libcosmic.git.
# [patch.'https://github.com/pop-os/libcosmic']
# libcosmic = { path = "../libcosmic" }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
# Yoda wayland cut: redirect window_clipboard (x11 gated behind opt-in
# feature) to our public Forgejo fork and cosmic-text to the local checkout
# carrying PR#503 plus the EAW monospace width fix. The window_clipboard patch
# is needed to consolidate the
# upstream pop-os/libcosmic chain (pulled by cosmic-settings-daemon) onto
# the same fork iced uses, otherwise cargo compiles two versions.
[patch.'https://github.com/pop-os/window_clipboard.git']
window_clipboard = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
dnd = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" }
# [patch.'https://github.com/pop-os/smithay-clipboard']
# smithay-clipboard = { path = "../smithay-clipboard" }
[patch.'https://github.com/pop-os/cosmic-text.git']
cosmic-text = { path = "../cosmic-text" }
[workspace]
members = ["cosmic-files-applet"]

View file

@ -1,4 +1,5 @@
use std::{env, fs, path::PathBuf};
use std::path::PathBuf;
use std::{env, fs};
use xdgen::{App, Context, FluentString};
fn main() {

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-files-applet"
version = "1.0.11"
version = "1.2.0"
edition = "2024"
[dependencies]

42
debian/changelog vendored
View file

@ -1,3 +1,45 @@
cosmic-files (1.2.0) noble; urgency=medium
* Epoch 1.2.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 30 Jun 2026 12:21:07 -0600
cosmic-files (1.1.0) noble; urgency=medium
* Epoch 1.1.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 23 Jun 2026 08:47:18 -0600
cosmic-files (1.0.16) noble; urgency=medium
* Epoch 1.0.16 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 09 Jun 2026 10:26:05 -0600
cosmic-files (1.0.15) noble; urgency=medium
* Epoch 1.0.15 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 02 Jun 2026 11:02:29 -0600
cosmic-files (1.0.14) noble; urgency=medium
* Epoch 1.0.14 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 26 May 2026 08:30:59 -0600
cosmic-files (1.0.13) noble; urgency=medium
* Epoch 1.0.13 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 12 May 2026 09:39:14 -0600
cosmic-files (1.0.12) noble; urgency=medium
* Epoch 1.0.12 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 05 May 2026 10:23:57 -0600
cosmic-files (1.0.11) noble; urgency=medium
* Epoch 1.0.11 version update

View file

@ -0,0 +1,78 @@
# Local performance and portal notes
Date: 2026-05-05
This repository carries a local patch aimed at improving initial directory
display latency in large folders, plus a system-level workaround for a COSMIC
portal FileChooser crash observed with Firefox and Chromium.
## Directory listing latency
The slow path was the initial construction of `Item` values in `src/tab.rs`.
On a test folder with about 2000 entries, raw filesystem enumeration and stat
calls completed in a few milliseconds, while COSMIC Files took multiple seconds
before showing the directory.
The local patch keeps initial item construction cheap:
- directory child counts are no longer computed synchronously in
`item_from_entry` and `item_from_gvfs_info`;
- initial MIME detection uses extension-based `mime_guess`;
- regular files use a generic file icon during the initial scan instead of
resolving the full MIME icon immediately.
This keeps folders and `.desktop` files special-cased, while avoiding expensive
per-file work for ordinary files during first paint.
Measured locally during investigation:
- before the MIME/icon changes: about 3.1 seconds for `~/Téléchargements`;
- after avoiding full MIME icon resolution during scan: below the temporary
100 ms perf-log threshold for the same folder.
## File chooser portal workaround
Firefox and Chromium were failing to open `Save As` on the first attempt because
`xdg-desktop-portal-cosmic` crashed while handling `FileChooser`.
Logs showed:
```text
Backend call failed: Remote peer disconnected
xdg-desktop-portal-cosmic ... status=11/SEGV
```
The working local system workaround is to remove
`org.freedesktop.impl.portal.FileChooser` from:
```text
/usr/share/xdg-desktop-portal/portals/cosmic.portal
```
so the file chooser falls back to GTK. The resulting local `cosmic.portal`
keeps COSMIC for the other interfaces:
```ini
[portal]
DBusName=org.freedesktop.impl.portal.desktop.cosmic
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast
UseIn=COSMIC
```
Backups created locally:
```text
/usr/share/xdg-desktop-portal/portals/cosmic.portal.bak-codex-20260505-filechooser
/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505
/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505-2
```
After editing portal files, restart:
```sh
systemctl --user restart xdg-desktop-portal.service xdg-desktop-portal-gtk.service
```
Package updates may overwrite `/usr/share/xdg-desktop-portal/portals/cosmic.portal`.
If `Save As` starts needing two attempts again, re-check that `FileChooser` has
not been reintroduced in `cosmic.portal`.

View file

@ -1,6 +1,8 @@
use cosmic_files::operation::recursive::Method;
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
use std::{error::Error, io, path::PathBuf};
use cosmic_files::operation::recursive::{Context, Method};
use cosmic_files::operation::{Controller, ReplaceResult};
use std::error::Error;
use std::io;
use std::path::PathBuf;
#[compio::main]
async fn main() -> Result<(), Box<dyn Error>> {

View file

@ -1,15 +1,12 @@
use cosmic::{
Application, Element,
app::{self, Core, Settings, Task},
executor,
iced::{Subscription, window},
widget,
};
use cosmic::app::{self, Core, Settings, Task};
use cosmic::iced::{Subscription, window};
use cosmic::{Application, Element, executor, widget};
use cosmic_files::dialog::{
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
DialogMessage, DialogResult, DialogSettings,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let log_format = tracing_subscriber::fmt::format()

View file

@ -372,3 +372,14 @@ show-recents = مجلد الحديثة في الشريط الجانبي
clear-recents-history = امحُ التأريخ الحديث
copy-path = انسخ المسار
mixed = مختلط
context-action = إجراء السياق
context-action-confirm-title = شغِّل "{ $name }"؟
context-action-confirm-warning =
سيُشغِّل هذا على { $items } { $items ->
[one] عنصر
[two] عنصرين
[few] عناصر
[many] عنصرًا
*[other] عنصر
}.
run = شغِّل

View file

@ -1,11 +1,11 @@
cosmic-files = Fitxers del COSMIC
empty-folder = Carpeta buida
empty-folder-hidden = Carpeta buida (té elements ocults)
empty-folder-hidden = Carpeta buida (conté elements ocults)
no-results = No s'ha trobat cap resultat
filesystem = Sistema de fitxers
home = Inici
networks = Xarxes
notification-in-progress = Hi ha operacions amb fitxers en curs.
notification-in-progress = Operacions de fitxers en curs
trash = Paperera
recents = Recents
undo = Desfés
@ -29,14 +29,14 @@ details = Detalls
dismiss = Descarta el missatge
operations-running =
{ $running } { $running ->
[one] operacion
[one] operació
*[other] operacions
} en curs ({ $percent }%)...
operations-running-finished =
{ $running }{ $running ->
[one] operacion
[one] operació
*[other] operacions
} en curs ({ $percent }%), { $finished } acabat...
} en curs ({ $percent }%), { $finished } acabada/es...
pause = Pausa
resume = Reprèn
@ -54,7 +54,7 @@ extract-password-required = Cal una contrasenya
## Empty Trash Dialog
empty-trash = Buida la paperera
empty-trash-warning = Voleu suprimir permanentment tots els fitxers de la paperera?
empty-trash-warning = Els elements dins la Paperera s'eliminaran permanentment
## Mount Error Dialog
@ -64,17 +64,17 @@ mount-error = No es pot accedir a la unitat
create-new-file = Crea un nou fitxer
create-new-folder = Crea una nova carpeta
file-name = Nom del fixer
file-name = Nom del fitxer
folder-name = Nom de la carpeta
file-already-exists = Ja existeix un fitxer amb aquest nom.
folder-already-exists = Ja existeix una carpeta amb aquest nom.
name-hidden = Els noms que comencin amb "." seran ocults.
name-invalid = El nom no pot ser "{ $filename }".
file-already-exists = Ja existeix un fitxer amb aquest nom
folder-already-exists = Ja existeix una carpeta amb aquest nom
name-hidden = Els noms que comencin amb "." s'ocultaran.
name-invalid = El nom no pot ser "{ $filename }"
name-no-slashes = El nom no pot contenir barres.
## Open/Save Dialog
cancel = Cancel·la
cancel = Cancel·lar
create = Crea
open = Obre
open-file = Obre el fixer
@ -352,3 +352,14 @@ sort-newest-first = Primer més recents
sort-oldest-first = Primer més antics
sort-smallest-to-largest = De petit a gran
sort-largest-to-smallest = De gran a petit
run = Executa
extract-to-title = Extreu a una carpeta
empty-trash-title = Voleu buidar la paperera?
extract-to = Extreu a...
delete = Suprimeix
comment = Gestor de fitxers de l'escriptori COSMIC
copy-to-title = Escolliu el destí de la còpia
copy-to-button-label = Copia
move-to-title = Escolliu el destí del trasllat
move-to-button-label = Mou
keywords = Carpeta;Fitxers;Arxius;Gestor;Explorador;

View file

@ -17,7 +17,7 @@ size = Velikost
## Empty Trash Dialog
empty-trash = Vysypat koš
empty-trash = Vyprázdnit koš
empty-trash-warning = Položky v koši budou trvale smazány
## New File/Folder Dialog
@ -195,8 +195,8 @@ deleted =
[few] položky
*[other] položek
} z koše
emptying-trash = Vysypávání koše ({ $progress })...
emptied-trash = Koš vysypán
emptying-trash = Vyprazdňování koše ({ $progress })...
emptied-trash = Koš vyprázdněn
restoring =
Obnovování { $items } { $items ->
[one] položky
@ -403,7 +403,7 @@ sort-largest-to-smallest = Od největšího po nejmenší
gallery-preview = Náhled galerie
sort = Řazení
sort-a-z = A-Z
empty-trash-title = Vysypat koš?
empty-trash-title = Vyprázdnit koš?
type-to-search-select = Vybere první shodující se soubor nebo složku
pasted-image = Vložený obrázek
pasted-text = Vložený text
@ -418,3 +418,13 @@ show-recents = Složka „Nedávné“ v postranním panelu
copy-path = Kopírovat cestu
clear-recents-history = Vymazat historii „Nedávné“
mixed = Různé
context-action = Kontextová akce
context-action-confirm-title = Spustit „{ $name }“?
context-action-confirm-warning =
Tato akce se spustí pro { $items } { $items ->
[one] položku
[few] položky
*[other] položek
}.
run = Spustit
rename-confirm = Přejmenovat

View file

@ -429,3 +429,12 @@ remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen
move-to = Verschieben nach...
copy-path = Pfad kopieren
mixed = Gemischt
context-action = Kontextaktion
context-action-confirm-title = „{ $name }“ ausführen?
context-action-confirm-warning =
Dies wird ausgeführt auf { $items } { $items ->
[one] Element
*[other] Elementen
}.
run = Ausführen
rename-confirm = Umbenennen

View file

@ -1,15 +1,330 @@
empty-folder = Άδειος φάκελος
no-results = Δεν βρέθηκαν αποτελέσματα
trash = Κάδος Ανακύκλωσης
trash = Απορρίμματα
recents = Πρόσφατα
cosmic-files = COSMIC Αρχεία
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά αντικείμενα)
cosmic-files = Αρχεία COSMIC
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά στοιχεία)
filesystem = Σύστημα αρχείων
home = Προσωπικός φάκελος
networks = Δίκτυο
comment = Αρχεία για το COSMIC περιβάλλον
keywords = Φάκελος;Διαχειριστής;
networks = Δίκτυα
comment = Διαχείριση αρχείων για το περιβάλλον επιφάνειας εργασίας COSMIC
keywords = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager;
rename = Μετονομασία...
close-tab = Κλείσιμο καρτέλας
light = Φωτεινό
dark = Σκοτεινό
light = Ανοιχτόχρωμο
dark = Σκουρόχρωμο
connect = Σύνδεση
dismiss = Απόρριψη μηνύματος
copy_noun = Αντιγραφή
open-file = Άνοιγμα αρχείου
save = Αποθήκευση
password = Κωδικός πρόσβασης
remove = Αφαίρεση
create = Δημιουργία
pause = Παύση
quit = Έξοδος
calculating = Υπολογισμός...
keep = Διατήρηση
edit = Επεξεργασία
connecting = Σύνδεση...
copy = Αντιγραφή
theme = Θέμα
appearance = Εμφάνιση
name = Όνομα
resume = Συνέχιση
username = Όνομα χρήστη
delete = Διαγραφή
repository = Αποθετήριο
support = Υποστήριξη
eject = Εξαγωγή
group = Ομάδα
skip = Παράλειψη
paste = Επικόλληση
menu-settings = Ρυθμίσεις...
view = Προβολή
undo = Αναίρεση
details = Λεπτομέρειες
sort-a-z = Α
extract-here = Αποσυμπίεση
cancel = Ακύρωση
sort-z-a = Ω-Α
open = Άνοιγμα
history = Ιστορικό
domain = Τομέας
sort = Ταξινόμηση
settings = Ρυθμίσεις
pending = Σε εκκρεμότητα
open-folder = Άνοιγμα φακέλου
replace = Αντικατάσταση
cut = Αποκοπή
file = Αρχείο
today = Σήμερα
compress = Συμπίεση...
size = Μέγεθος
related-apps = Σχετικές εφαρμογές
zoom-in = Μεγέθυνση
select-all = Επιλογή όλων
icon-size-and-spacing = Μέγεθος και απόσταση εικονιδίων
new-window = Νέο παράθυρο
zoom-out = Σμίκρυνση
default-size = Προεπιλεγμένο μέγεθος
create-archive = Δημιουργία συμπιεσμένου αρχείου
other-apps = Άλλες εφαρμογές
rename-folder = Μετονομασία φακέλου
folder-name = Όνομα φακέλου
connect-anonymously = Ανώνυμη σύνδεση
replace-with = Αντικατάσταση με
mounted-drives = Προσαρτημένες μονάδες
desktop-view-options = Επιλογές προβολής επιφάνειας εργασίας...
show-on-desktop = Εμφάνιση στην επιφάνεια εργασίας
trash-folder-icon = Εικονίδιο φακέλου απορριμμάτων
open-with = Άνοιγμα με
keep-both = Διατήρηση αμφότερων
icon-size = Μέγεθος εικονιδίων
open-with-title = Πώς θέλετε να ανοίξετε το «{ $name }»;
extract-password-required = Απαιτείται κωδικός πρόσβασης
rename-file = Μετονομασία αρχείου
file-name = Όνομα αρχείου
save-file = Αποθήκευση αρχείου
name-hidden = Τα ονόματα που ξεκινούν με «.» θα αποκρύπτονται
folder-already-exists = Υπάρχει ήδη ένας φάκελος με αυτό το όνομα
empty-trash = Άδειασμα απορριμμάτων
permanently-delete-question = Οριστική διαγραφή;
copy-to-button-label = Αντιγραφή
move-to-button-label = Μετακίνηση
run = Εκτέλεση
copy-to-title = Επιλογή προορισμού αντιγραφής
sort-newest-first = Πρώτα τα νεότερα
default-app = { $name } (προεπιλογή)
renamed = Έγινε μετονομασία από «{ $from }» σε «{ $to }»
read-execute = Ανάγνωση και εκτέλεση
deleted =
Έγινε διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash }
item-modified = Ημερομηνία τροποποίησης: { $modified }
list-view = Προβολή λίστας
reload-folder = Επαναφόρτωση φακέλου
favorite-path-error = Σφάλμα ανοίγματος καταλόγου
progress = { $percent }%
remove-from-sidebar = Αφαίρεση από την πλαϊνή στήλη
restoring =
Ανάκτηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash } ({ $progress })...
network-drive-error = Αδυναμία πρόσβασης σε μονάδα δικτύου
gallery-preview = Προεπισκόπηση συλλογής
sort-smallest-to-largest = Από τα μικρότερα στα μεγαλύτερα
removing-from-recents =
Αφαίρεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { recents }
type-to-search-enter-path = Εισέρχεται στη διαδρομή προς τον κατάλογο ή το αρχείο
emptying-trash = Άδειασμα φακέλου «{ trash }» ({ $progress })...
trashed-on = Ημερομηνία διαγραφής
compressing =
Συμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }» ({ $progress })...
move-to-trash = Μετακίνηση στα απορρίμματα
menu-about = Σχετικά με τα Αρχεία COSMIC...
setting-executable-and-launching = Ορισμός του «{ $name }» ως εκτελέσιμου και εκκίνηση
open-multiple-files = Άνοιγμα πολλαπλών αρχείων
menu-open-with = Άνοιγμα με...
extracted =
Έγινε αποσυμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από το αρχείο «{ $from }» στον φάκελο «{ $to }»
create-new-folder = Δημιουργία νέου φακέλου
original-file = Πρωτότυπο αρχείο
read-write-execute = Ανάγνωση, εγγραφή και εκτέλεση
set-permissions = Έγινε ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
sort-by-size = Ταξινόμηση κατά μέγεθος
item-size = Μέγεθος: { $size }
permanently-deleting =
Οριστική διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
}
read-write = Ανάγνωση και εγγραφή
none = Κανένα
items = Στοιχεία: { $items }
type = Τύπος: { $mime }
compressed =
Έγινε συμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }»
replace-warning = Θέλετε να το αντικαταστήσετε με αυτό που αποθηκεύετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
new-file = Νέο αρχείο...
open-in-terminal = Άνοιγμα σε τερματικό
open-multiple-folders = Άνοιγμα πολλαπλών φακέλων
remember-password = Απομνημόνευση κωδικού πρόσβασης
show-details = Εμφάνιση λεπτομερειών
grid-spacing = Απόσταση πλέγματος
extract-to = Αποσυμπίεση σε...
add-network-drive = Προσθήκη μονάδας δικτύου
copying =
Αντιγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
sort-oldest-first = Πρώτα τα παλαιότερα
create-new-file = Δημιουργία νέου αρχείου
sort-by-trashed = Ταξινόμηση κατά ημερομηνία διαγραφής
replace-warning-operation = Θέλετε να το αντικαταστήσετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
try-again = Δοκιμή ξανά
copied =
Έγινε αντιγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
other = Άλλο
open-in-new-window = Άνοιγμα σε νέο παράθυρο
sort-by-modified = Ταξινόμηση κατά ημερομηνία τροποποίησης
list-directories-first = Παράθεση των καταλόγων πρώτα
read-only = Μόνο ανάγνωση
browse-store = Περιήγηση στο { $store }
enter-server-address = Εισαγάγετε τη διεύθυνση διακομιστή
remove-from-recents = Αφαίρεση από τα πρόσφατα
apply-to-all = Εφαρμογή σε όλα
moving =
Μετακίνηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
change-wallpaper = Αλλαγή ταπετσαρίας...
network-drive-description =
Οι διευθύνσεις διακομιστών αποτελούνται από ένα πρόθεμα πρωτοκόλλου και μια διεύθυνση.
Παραδείγματα: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
Διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash } ({ $progress })...
single-click = Μονό κλικ για άνοιγμα
setting-permissions = Ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
owner = Κάτοχος
creating = Δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
execute-only = Μόνο εκτέλεση
open-item-location = Άνοιγμα τοποθεσίας στοιχείου
set-executable-and-launched = Έγινε ορισμός του «{ $name }» ως εκτελέσιμου και εκκινήθηκε
mount-error = Αδυναμία πρόσβασης στη μονάδα
grid-view = Προβολή πλέγματος
set-and-launch = Ορισμός και εκκίνηση
removed-from-recents =
Έγινε αφαίρεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { recents }
add-to-sidebar = Προσθήκη στην πλαϊνή στήλη
item-created = Ημερομηνία δημιουργίας: { $created }
network-drive-schemes =
Διαθέσιμα πρωτόκολλα,Πρόθεμα
AppleTalk,afp://
File Transfer Protocol,ftp:// ή ftps://
Network File System,nfs://
Server Message Block,smb://
SSH File Transfer Protocol,sftp:// ή ssh://
WebDAV,dav:// ή davs://
set-executable-and-launch = Ορισμός ως εκτελέσιμο και εκκίνηση
restored =
Έγινε ανάκτηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τα { trash }
type-to-search-recursive = Κάνει αναζήτηση στον τρέχοντα φάκελο και όλους τους υποφακέλους
progress-paused = { $percent }%, σε παύση
cancelled = Ακυρωμένες
new-folder = Νέος φάκελος...
match-desktop = Συμφωνία με την επιφάνεια εργασίας
operations-running-finished =
Εκτέλεση { $running } { $running ->
[one] διεργασίας
*[other] διεργασιών
} ({ $percent }%), { $finished } ολοκληρωμένες...
sort-by-name = Ταξινόμηση κατά όνομα
edit-history = Ιστορικό επεξεργασιών
show-hidden-files = Εμφάνιση κρυφών αρχείων
progress-failed = { $percent }%, απέτυχε
item-accessed = Ημερομηνία πρόσβασης: { $accessed }
extract-to-title = Αποσυμπίεση σε φάκελο
extracting =
Αποσυμπίεση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από το αρχείο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
permanently-deleted =
Έγινε οριστική διαγραφή { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
}
complete = Ολοκληρωμένες
write-execute = Εγγραφή και εκτέλεση
desktop-folder-content = Περιεχόμενο φακέλου επιφάνειας εργασίας
renaming = Μετονομασία από «{ $from }» σε «{ $to }»
set-executable-and-launch-description = Θέλετε να ορίσετε το «{ $name }» ως εκτελέσιμο και να το εκκινήσετε;
no-history = Δεν υπάρχουν στοιχεία στο ιστορικό.
emptied-trash = Έγινε άδειασμα του φακέλου «{ trash }»
sort-largest-to-smallest = Από τα μεγαλύτερα στα μικρότερα
restore-from-trash = Ανάκτηση από τα απορρίμματα
moved =
Έγινε μετακίνηση { $items } { $items ->
[one] στοιχείου
*[other] στοιχείων
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
progress-cancelled = { $percent }%, ακυρώθηκε
open-in-new-tab = Άνοιγμα σε νέα καρτέλα
unknown-folder = άγνωστος φάκελος
created = Έγινε δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
delete-permanently = Οριστική διαγραφή
write-only = Μόνο εγγραφή
display-settings = Ρυθμίσεις οθόνης...
new-tab = Νέα καρτέλα
failed = Αποτυχημένες
modified = Ημερομηνία τροποποίησης
desktop-appearance = Εμφάνιση επιφάνειας εργασίας...
file-already-exists = Υπάρχει ήδη ένα αρχείο με αυτό το όνομα
permanently-delete-warning = Θα διαγραφούν οριστικά τα εξής: { $target }. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.
favorite-path-error-description =
Αδυναμία ανοίγματος του «{ $path }»
Το «{ $path }» ενδέχεται να μην υπάρχει ή να μην έχετε το δικαίωμα να το ανοίξετε
Θέλετε να το αφαιρέσετε από την πλαϊνή στήλη;
empty-trash-warning = Τα στοιχεία του φακέλου «Απορρίμματα» θα διαγραφούν οριστικά
empty-trash-title = Άδειασμα απορριμμάτων;
type-to-search = Πληκτρολόγηση για αναζήτηση
notification-in-progress = Βρίσκονται σε εξέλιξη διεργασίες αρχείων
name-no-slashes = Το όνομα δεν μπορεί να περιέχει καθέτους
replace-title = Το «{ $filename }» υπάρχει ήδη σε αυτήν την τοποθεσία
name-invalid = Το όνομα δεν μπορεί να είναι «{ $filename }»
operations-running =
Εκτέλεση { $running } { $running ->
[one] διεργασίας
*[other] διεργασιών
} ({ $percent }%)...
context-action-confirm-title = Εκτέλεση του «{ $name }»;
pasted-image = Επικολλημένη εικόνα
pasted-text = Επικολλημένο κείμενο
pasted-video = Επικολλημένο βίντεο
show-recents = Φάκελος «Πρόσφατα» στην πλαϊνή στήλη
move-to = Μετακίνηση σε...
copy-path = Αντιγραφή διαδρομής
move-to-title = Επιλογή προορισμού μετακίνησης
selected-items = { $items } επιλεγμένα στοιχεία
context-action = Ενέργεια περιβάλλοντος
copy-to = Αντιγραφή σε...
mixed = Μικτό
type-to-search-select = Επιλέγει την πρώτη αντιστοιχία αρχείου ή φακέλου
clear-recents-history = Απαλοιφή ιστορικού πρόσφατων
context-action-confirm-warning =
Θα εκτελεστεί σε { $items } { $items ->
[one] στοιχείο
*[other] στοιχεία
}.
rename-confirm = Μετονομασία

View file

@ -96,6 +96,7 @@ save-file = Save file
## Open With Dialog
open-with-title = How do you want to open "{$name}"?
open-with-set-default = Always use this app for this file type
browse-store = Browse {$store}
other-apps = Other applications
related-apps = Related applications
@ -116,6 +117,7 @@ permanently-delete-warning = {$target} will be permanently deleted. This action
## Rename Dialog
rename-file = Rename file
rename-folder = Rename folder
rename-confirm = Rename
## Replace Dialog
replace = Replace
@ -138,6 +140,11 @@ open-with = Open with
owner = Owner
group = Group
other = Other
toolbar = Toolbar
toolbar-available = Available
toolbar-empty-hint = No buttons. Drag or add from below.
toolbar-reset = Reset to defaults
parent-directory = Parent directory
mixed = Mixed
### Mode 0
none = None
@ -302,6 +309,9 @@ item-created = Created: {$created}
item-modified = Modified: {$modified}
item-accessed = Accessed: {$accessed}
calculating = Calculating...
checksum = {$kind} checksum
calculate = Calculate
error = Error
## Settings
settings = Settings

View file

@ -405,3 +405,13 @@ removed-from-recents =
[one] kohde
*[other] kohdetta
} viimeaikaisista
mixed = Sekoitettu
context-action = Kontekstitoiminto
context-action-confirm-title = Suoritetaanko "{ $name }"?
context-action-confirm-warning =
Tämä suorittaa { $items } { $items ->
[one] kohteen
*[other] kohdetta
}.
run = Suorita
rename-confirm = Nimeä uudelleen

View file

@ -92,6 +92,7 @@ save-file = Enregistrer fichier
## Open With Dialog
open-with-title = Comment souhaitez-vous ouvrir "{ $name }"?
open-with-set-default = Toujours utiliser cette application pour ce type de fichier
browse-store = Parcourir { $store }
## Permanently delete Dialog
@ -130,6 +131,11 @@ open-with = Ouvrir avec
owner = Propriétaire
group = Groupe
other = Autre
toolbar = Barre d'outils
toolbar-available = Disponibles
toolbar-empty-hint = Aucun bouton. Glisser-déposer ou ajouter depuis la liste ci-dessous.
toolbar-reset = Rétablir par défaut
parent-directory = Dossier parent
### Mode 0
@ -437,3 +443,12 @@ show-recents = Dossier Récents dans la barre latérale
copy-path = Copier le chemin
clear-recents-history = Effacer l'historique des Récents
mixed = Mixte
context-action-confirm-title = Exécuter "{ $name }"?
context-action-confirm-warning =
Cela exécutera sur { $items } { $items ->
[one] élément
*[other] éléments
}.
run = Exécuter
context-action = Action contextuelle
rename-confirm = Renommer

View file

@ -434,3 +434,12 @@ show-recents = Fillteán le déanaí sa bharra taoibh
clear-recents-history = Glan stair na n-earraí le déanaí
copy-path = Cóipeáil an chosán
mixed = Measctha
context-action = Gníomh comhthéacsúil
context-action-confirm-title = Rith "{ $name }"?
context-action-confirm-warning =
Rithfidh sé seo ar { $items } { $items ->
[one] mhír
*[other] míreanna
}.
run = Rith
rename-confirm = Athainmnigh

View file

@ -2,7 +2,7 @@ cosmic-files = COSMIC Fájlok
comment = Fájlkezelő a COSMIC asztali környezethez
keywords = mappa;fájl;kezelő;
empty-folder = Üres mappa
empty-folder-hidden = Üres mappa (Rejtett elemeket tartalmaz)
empty-folder-hidden = Üres mappa (rejtett elemeket tartalmaz)
no-results = Nincs találat
filesystem = Fájlrendszer
home = Saját mappa
@ -17,7 +17,7 @@ desktop-view-options = Asztali nézet beállításai…
show-on-desktop = Megjelenítés az asztalon
desktop-folder-content = Asztal mappa tartalma
mounted-drives = Csatolt meghajtók
trash-folder-icon = Kuka ikonja
trash-folder-icon = Kuka ikon
icon-size-and-spacing = Ikonméret és távolság
icon-size = Ikonméret
grid-spacing = Rácsköz
@ -47,18 +47,18 @@ resume = Folytatás
## Compress Dialog
create-archive = Tömörített fájl létrehozása
create-archive = Archívum létrehozása
## Extract Dialog
extract-password-required = Jelszó szükséges
extract-to = Kibontás ide…
extract-to-title = Kibontás mappába
extract-to-title = Kibontási cél kiválasztása
## Empty Trash Dialog
empty-trash = Kuka ürítése
empty-trash-warning = A kukában lévő összes elem véglegesen törölve lesz
empty-trash = A Kuka ürítése
empty-trash-warning = A Kukában lévő összes elem véglegesen törölve lesz
## Mount Error Dialog
@ -93,7 +93,7 @@ save-file = Fájl mentése
## Open With Dialog
open-with-title = Hogyan szeretnéd megnyitni a következőt: „{ $name }”?
open-with-title = Hogyan szeretnéd megnyitni a(z) „{ $name }” fájlt?
browse-store = { $store } böngészése
other-apps = Egyéb alkalmazások
related-apps = Hasonló alkalmazások
@ -103,7 +103,7 @@ related-apps = Hasonló alkalmazások
selected-items = A(z) { $items } kijelölt elem
permanently-delete-question = Végleges törlés?
delete = Törlés
permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem visszavonható.
permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem vonható vissza.
## Rename Dialog
@ -113,7 +113,7 @@ rename-folder = Mappa átnevezése
## Replace Dialog
replace = Csere
replace-title = „{ $filename }” már létezik
replace-title = „{ $filename }” már létezik ezen a helyen
replace-warning = Szeretnéd lecserélni a meglévő fájlt a mentendő fájllal? A cserével felülírod annak tartalmát.
replace-warning-operation = Szeretnéd lecserélni? A csere felülírja annak tartalmát.
original-file = Eredeti fájl
@ -133,7 +133,7 @@ set-and-launch = Beállítás és indítás
open-with = Megnyitás ezzel
owner = Tulajdonos
group = Csoport
other = Többi
other = Mások
### Mode 0
@ -172,7 +172,7 @@ read-write-execute = Olvasás, írás és végrehajtás
favorite-path-error = Hiba a könyvtár megnyitásakor
favorite-path-error-description =
Nem sikerült megnyitni ezt: „{ $path }”
„{ $path }” lehet nem létezik, vagy nincs jogosultságod a megnyitásához
A(z) „{ $path }” útvonal lehet, hogy nem létezik, vagy nincs jogosultságod a megnyitásához
Szeretnéd eltávolítani az oldalsávról?
remove = Eltávolítás
@ -187,9 +187,9 @@ keep = Megtartás
## Add Network Drive
add-network-drive = Hálózati meghajtó hozzáadása
connect = Csatlakozás
connect-anonymously = Csatlakozás névtelenül
connecting = Csatlakozás…
connect = Kapcsolódás
connect-anonymously = Kapcsolódás névtelenül
connecting = Kapcsolódás…
domain = Tartomány
enter-server-address = Add meg a kiszolgáló címét
network-drive-description =
@ -209,7 +209,7 @@ network-drive-schemes =
network-drive-error = Nem érhető el a hálózati meghajtó
password = Jelszó
remember-password = Jelszó megjegyzése
try-again = Újra
try-again = Próbáld újra
username = Felhasználónév
## Operations
@ -217,7 +217,7 @@ username = Felhasználónév
cancelled = Megszakítva
edit-history = Fájlműveleti előzmények
history = Előzmények
no-history = Nem találhatók elemek az előzményekben
no-history = Nem találhatók elemek az előzményekben.
pending = Függőben
progress = { $percent }%
progress-cancelled = { $percent }%, megszakítva
@ -251,13 +251,13 @@ deleting =
{ $items } { $items ->
[one] elem
*[other] elem
} törlése a kukából ({ $progress })…
} törlése a Kukából ({ $progress })…
deleted =
{ $items } { $items ->
[one] elem
*[other] elem
} törölve a kukából
emptying-trash = { trash } kiürítése ({ $progress })…
} törölve a Kukából
emptying-trash = { trash } ürítése ({ $progress })…
emptied-trash = { trash } kiürítve
extracting =
{ $items } { $items ->
@ -309,12 +309,12 @@ restoring =
{ $items } { $items ->
[one] elem
*[other] elem
} visszaállítása a kukából ({ $progress })…
} visszaállítása a Kukából ({ $progress })…
restored =
{ $items } { $items ->
[one] elem
*[other] elem
} visszaállítva a kukából
} visszaállítva a Kukából
unknown-folder = ismeretlen mappa
## Open with
@ -330,7 +330,7 @@ items = Elemek: { $items }
item-size = Méret: { $size }
item-created = Létrehozva: { $created }
item-modified = Módosítva: { $modified }
item-accessed = Hozzáférve: { $accessed }
item-accessed = Utolsó hozzáférés: { $accessed }
calculating = Számítás…
## Settings
@ -349,8 +349,8 @@ light = Világos
### Type to Search
type-to-search = Gépeléssel keresés
type-to-search-recursive = Keresés a jelenlegi mappában és almappákban
type-to-search-enter-path = Elérési út megadása
type-to-search-recursive = Keresés a jelenlegi mappában és az almappákban
type-to-search-enter-path = Elérési út megnyitása
# Context menu
add-to-sidebar = Hozzáadás az oldalsávhoz
compress = Tömörítés…
@ -360,8 +360,8 @@ extract-here = Kibontás
new-file = Új fájl…
new-folder = Új mappa…
open-in-terminal = Megnyitás a terminálban
move-to-trash = Kukába helyezés
restore-from-trash = Visszaállítás a kukából
move-to-trash = Áthelyezés a Kukába
restore-from-trash = Visszaállítás a Kukából
remove-from-sidebar = Eltávolítás az oldalsávról
sort-by-name = Név szerinti rendezés
sort-by-modified = Módosítás szerinti rendezés
@ -372,8 +372,8 @@ remove-from-recents = Eltávolítás a legutóbbiak közül
## Desktop
change-wallpaper = Háttérkép cseréje…
desktop-appearance = Asztali megjelenés…
display-settings = Képernyő-beállítások…
desktop-appearance = Asztal megjelenése
display-settings = Kijelzőbeállítások…
# Menu
@ -406,7 +406,7 @@ grid-view = Rácsnézet
list-view = Listanézet
show-hidden-files = Rejtett fájlok megjelenítése
list-directories-first = Könyvtárak listázása először
gallery-preview = Galéria előnézet
gallery-preview = Galéria-előnézet
menu-settings = Beállítások…
menu-about = A COSMIC Fájlok névjegye…
@ -415,14 +415,14 @@ menu-about = A COSMIC Fájlok névjegye…
sort = Rendezés
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Legújabb előre
sort-oldest-first = Legrégibb előre
sort-newest-first = Legújabb elöl
sort-oldest-first = Legrégibb elöl
sort-smallest-to-largest = Legkisebbtől a legnagyobbig
sort-largest-to-smallest = Legnagyobbtól a legkisebbig
repository = Tároló
support = Támogatás
progress-failed = { $percent }%, sikertelen
empty-trash-title = Kuka ürítése?
empty-trash-title = Kiüríted a Kukát?
type-to-search-select = Kijelöli az első egyező fájlt vagy mappát
pasted-image = Beillesztett kép
pasted-text = Beillesztett szöveg
@ -437,3 +437,12 @@ show-recents = Legutóbbiak mappa megjelenítése az oldalsávban
copy-path = Útvonal másolása
clear-recents-history = Legutóbbiak előzményének törlése
mixed = Vegyes
context-action = Helyi művelet
context-action-confirm-title = Futtatod ezt: „{ $name }”?
context-action-confirm-warning =
Ez a művelet { $items } { $items ->
[one] elemen
*[other] elemen
} fog lefutni.
run = Futtatás
rename-confirm = Átnevezés

View file

@ -319,3 +319,12 @@ show-recents = Map terbaru di bilah sisi
clear-recents-history = Bersihkan riwayat Terbaru
copy-path = Salin jalur
mixed = Bercampur
context-action = Tindakan konteks
context-action-confirm-title = Jalankan "{ $name }"?
run = Jalankan
context-action-confirm-warning =
Ini akan dijalankan pada { $items } { $items ->
[one] item
*[other] item
}.
rename-confirm = Ganti nama

View file

@ -319,3 +319,12 @@ show-recents = Бүйір панеліндегі «Жуырдағы құжатт
clear-recents-history = Жуырдағылар тарихын өшіру
copy-path = Орналасқан жолын көшіру
mixed = Аралас
context-action = Контекст әрекеті
context-action-confirm-title = "{ $name }" орындау керек пе?
context-action-confirm-warning =
Бұл { $items } орындалады { $items ->
[one] нәрсеге
*[other] нәрсеге
}.
run = Орындау
rename-confirm = Атын өзгерту

View file

@ -0,0 +1,330 @@
sort-newest-first = Pêşî ya herî nû
connect = Girê bide
deleted =
{ $items } hat jêbirin{ $items ->
[one] hêman
*[other] hêman
} ji { trash }
dismiss = Peyamê paşguh bike
list-view = Dîtina lîsteyê
copy_noun = Jê bigire
favorite-path-error = Şaşetiya vekirina rêgehê
progress = { $percent }%
open-file = Pelê veke
remove-from-sidebar = Ji benda kêlekê rake
related-apps = Bernameyên têkildar
network-drive-error = Nikaribû bigihêje ajokarê torê
gallery-preview = Pêşdîtina galeriyê
sort-smallest-to-largest = Biçûktir bo mezintir
zoom-in = Nêzîk bike
select-all = Hemûyan hilbijêre
removing-from-recents =
{ $items } tê rakirin { $items ->
[one] hêman
*[other] hêman
} ji { recents }
password = Borînpeyv
cosmic-files = Pelên COSMIC
emptying-trash = { trash } tê valakirin({ $progress })...
new-window = Çarçoveya nû
remove = Rake
zoom-out = Dûr bike
compressing =
{ $items } tê guvaştin { $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }" ({ $progress })...
move-to-trash = Bilivîne bo jêbirdankê
menu-about = Derbarê Pelên COSMIC...
file-already-exists = Pelek bi heman navî jixwe heye
setting-executable-and-launching = "{ $name }" wekî pelê xebitandinê tê sazkirin û xebitandin
open-multiple-files = Gelek pelan veke
default-size = Mezinahiya berdest
name-hidden = Navên ku bi "." tê destpêkirin wê werin veşartin
extracted =
{ $items } hate derxistin { $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }"
create-new-folder = Peldanka nû biafrîne
folder-already-exists = Peldankek bi heman navî jixwe heye
create-archive = Erşîvê biafrîne
permanently-delete-warning = { $target } wê bi mayînde were jêbirin û nayê vegerandin.
favorite-path-error-description =
Nikaribû "{ $path }" veke.
"{ $path }" dibe ku tune be yan jî mafê te yê
vekirina wê tune be.
Tu dixwazî wê ji benda kêlekê rakî?
empty-trash-warning = Hêmanên di jêbirdankê de wê bi mayînde werin jêbirin
other-apps = Bernameyên din
set-permissions = Maf hatine sazkirin ji bo "{ $name }" bo { $mode }
quit = Biqedîne
sort-by-size = Li gorî mezinahiyê rêz bike
rename = Navê biguhêrîne...
empty-folder-hidden = Peldanka vala (tiştên veşartî tê de hene)
keep = Bihêle
operations-running =
{ $running } { $running ->
[one] pêvajo
*[other] pêvajo
} dixebite ({ $percent }%)...
permanently-deleting =
{ $items } bi mayînde tê jêbirin { $items ->
[one] hêman
*[other] hêman
}
edit = Biguhêrîne
copy = Jê bigire
none = Ne yek
no-results = Tu encam nehatin dîtin
theme = Rûkar
compressed =
{ $items } hate guvaştin{ $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }"
appearance = Xuyang
rename-folder = Navê peldankê biguherîne
new-file = Pela nû...
close-tab = Rûgerê bigire
open-in-terminal = Di termînalê de veke
open-multiple-folders = Gelek peldankan veke
remember-password = Borînpeyvê bi bîr bîne
username = Navê bikarhêner
light = Ronî
extract-to = Derxîne bo...
add-network-drive = Ajokarê torê tevlî bike
copying =
{ $items } tê jêgirtin{ $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }" ({ $progress })...
create-new-file = Pelê nû biafrîne
sort-by-trashed = Li gorî demê rêz bike
support = Piştgirî
try-again = Dîsa hewl bide
eject = Biavêje
copied =
{ $items } hate jêgirtin { $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }"
open-in-new-window = Di çarçoveyeke nû de veke
empty-folder = Peldanka vala
empty-trash = Jêbirdankê vala bike
sort-by-modified = Li gorî guhertinê rêz bike
list-directories-first = Pêşî rêgehan lîste bike
folder-name = Navê peldankê
browse-store = { $store } bigere
remove-from-recents = Ji dawiyê rake
paste = Pêve bike
empty-trash-title = Jêbirdankê vala bike?
menu-settings = Sazkarî...
moving =
{ $items } tê livandin{ $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }" ({ $progress })...
change-wallpaper = Wêneyê dîwêr biguhêrîne...
network-drive-description =
Di nav navnîşanên rajekarê de pêşgira protokolekê û navnîşanek heye.
Mînak: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
{ $items } tê jêbirin { $items ->
[one] hêman
*[other] hêman
} ji { trash } ({ $progress })...
single-click = Tikandina yekane bo vekriinê
view = Dîtin
setting-permissions = Maf tên sazkirin ji bo "{ $name }" bo { $mode }
creating = "{ $name }" tê afirandin di "{ $parent }" de
execute-only = Tenê xebitandin
open-item-location = Cihê hêmanê veke
type-to-search = Bo lêgerînê binivîse
set-executable-and-launched = "{ $name }" wekî pelê xebitandinê hat sazkirin û xebitandin
mounted-drives = Ajokarên siwarkirî
sort-a-z = A-Z
mount-error = Nikare bigihêje ajokerê
extract-here = Derxîne
grid-view = Dîtina torê
add-to-sidebar = Tevlî benda kêlekê bike
dark = Tarî
notification-in-progress = Pêvajoyên pelê didome
home = Rûpela sereke
sort-z-a = Z-A
type-to-search-recursive = Di peldank û binpeldankên heyî de digere
history = Dîrok
progress-paused = { $percent }%, hate rawestandin
desktop-view-options = Vebijêrkên dîtina sermaseyê...
show-on-desktop = Li ser sermaseyê nîşan bide
cancelled = Têkbirî
new-folder = Peldankan nû...
match-desktop = Li gorî sermaseyê
name-no-slashes = Nav nabe ku "/" tê de hebe
operations-running-finished =
{ $running } { $running ->
[one] pêvajo
*[other] pêvajo
} dixebite ({ $percent }%), { $finished } qediya...
sort-by-name = Li gorî navê rêz bike
edit-history = Dîrokê biguhêrînê
show-hidden-files = Pelên veşartî nîşan bide
progress-failed = { $percent }%, têk çû
trash-folder-icon = Îkona peldanka jêbirdankê
extract-to-title = Derxîne bo peldankê
icon-size = Mezinahiya îkonê
open-with-title = Tu çawa dixwazî "{ $name }" vekî?
extracting =
{ $items } tê derxistin { $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo"{ $to }" ({ $progress })...
settings = Sazkarî
complete = Qediya
permanently-delete-question = Bi mayînde jê bibe?
extract-password-required = Borînpeyv pêdivî ye
pending = Li bendê ye
desktop-folder-content = Naveroka peldanka sermaseyê
no-history = Tu hêman di dîrok de tune ne.
open-folder = Peldankê veke
emptied-trash = { trash } hate valakirin
rename-file = Navê pelê biguherîne
sort-largest-to-smallest = Mezintir bo biçûktir
restore-from-trash = Ji jêbirdankê vegerîne
cut = Jê bike
moved =
{ $items } hate livandin { $items ->
[one] hêman
*[other] hêman
} ji "{ $from }" bo "{ $to }"
progress-cancelled = { $percent }%, hat têkbirin
open-in-new-tab = Di rûgerê nû de veke
file = Pel
file-name = Navê pelê
save-file = Pelê tomar bike
created = "{ $name }" hate afirandin di "{ $parent }" de
delete-permanently = Bi mayînde jê bibe
display-settings = Sazkariyên dîmenderê...
new-tab = Rûgera nû
failed = Têk çû
name-invalid = Nav nabe ku \"{ $filename }\" be
desktop-appearance = Xuyanga sermaseyê...
comment = Rêveberê pelê ji bo sermaseya COSMIC
keywords = Rêveber;Peldank;
filesystem = Pergala pelê
networks = Tor
recents = Dawî
open = Veke
cancel = Têk bibe
trash = Jêbirdank
undo = Vegerîne
today = Îro
name = Nav
modified = Guhertî
size = Mezinahî
details = Hûrgilî
pause = Rawestîne
resume = Bidomîne
copy-to-title = Rêgeha jêgirtinê hilbijêre
copy-to-button-label = Jê bigire
move-to-title = Rêgeha livandinê hilbijêre
move-to-button-label = Bilivîne
create = Biafrîne
save = Tomar bike
context-action = Çalakiya naverokê
context-action-confirm-title = "{ $name }" bixebtîne?
context-action-confirm-warning =
Ev ê li ser { $items } { $items ->
[one] hêman
*[other] hêman
} bixebite.
run = Bixebitîne
selected-items = { $items } hêmanên hilbijartî
delete = Jê bibe
rename-confirm = Navê biguherîne
pasted-image = Wêneya pêvekirî
pasted-text = Nivîsa pêvekirî
pasted-video = Vîdiyoya pêvekirî
copy-path = Rêgehê jê bigire
show-recents = Peldanka dawî di benda kêlekê de
clear-recents-history = Dîroka dawî pak bike
compress = Biguvşîne...
copy-to = Jê bigire bo...
move-to = Bilivîne bo...
default-app = { $name } (berdest)
renamed = Nav hate guhertin ji "{ $from }" bo "{ $to }"
read-execute = Xwendin û xebitandin
item-modified = Guhertin: { $modified }
reload-folder = Peldankê ji nû ve bar bike
restoring =
{ $items } tê vegerandin { $items ->
[one] hêman
*[other] hêman
} ji { trash } ({ $progress })...
icon-size-and-spacing = Mezinahiya îkonan û valahiya di navbera wan de
type-to-search-enter-path = Riya rêgehê yan jî pelê têdixîne
menu-open-with = Veke bi...
original-file = Pelê resen
read-write-execute = Xwendin, nivîsandin û xebitandin
calculating = Tê jimartin...
item-size = Mezinahî: { $size }
connecting = Tê girêdan...
read-write = Xwendin û nivîsandin
items = Hêman: { $items }
type = Cûre: { $mime }
replace-warning = Tu dixwazî bi ya ku tu niha tomar dikî biguhêrînî? Naveroka berê wê were jêbirin.
show-details = Hûrgiliyan nîşan bide
grid-spacing = Valahiya torê
sort-oldest-first = Pêşî ya herî kevn
repository = Embar
replace-warning-operation = Tu dixwazî biguherînî? Naveroka berê wê were jêbirin.
other = Ên din
read-only = Tenê xwendin
enter-server-address = Navnîşana rajekarê têxîne
connect-anonymously = Bi awayekî anonîm girê bide
group = Kom
apply-to-all = Bo hemûyan bisepîne
skip = Derbas bike
replace-with = Biguhêrîne bi
owner = Xwedî
set-and-launch = Saz bike û dest pê bike
removed-from-recents =
{ $items } hate rakirin { $items ->
[one] hêman
*[other] hêman
} ji { recents }
item-created = Afirandin:{ $created }
network-drive-schemes =
Protokolên berdest,pêşgirê
AppleTalk,afp://
Protokola şandina pelê,ftp:// an ftps://
Pergala pelê torê,nfs://
Astengiya peyama rajekarê,smb://
SSH protokola şandina pelê,sftp:// an ssh://
WebDAV, dav:// an davs://
set-executable-and-launch = Wekî pelê xebitandinê saz bike û bixebitîne
restored =
{ $items } hate vegerandin { $items ->
[one] hêman
*[other] hêman
} ji { trash }
domain = Navper
sort = Rêz bike
item-accessed = Gihîştin: { $accessed }
open-with = Veke bi
keep-both = Herduyan bihêle
permanently-deleted =
{ $items } bi mayînde hate jêbirin{ $items ->
[one] hêman
*[other] hêman
}
write-execute = Nivîsandin û xebitandin
renaming = Navê ji "{ $from }" biguhêrîne bo "{ $to }"
set-executable-and-launch-description = Tu dixwazî "{ $name }" wekî pelê xebitandinê saz bikî û bixebtînî?
replace = Biguhêrîne
replace-title = "{ $filename }" jixwe di vê cihê de heye
unknown-folder = Peldanka nenas
write-only = Tenê nivîsandin
trashed-on = Li Jêbirdankê hatiye avêtin
mixed = Tevlihev
type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre

View file

@ -277,3 +277,4 @@ copy-to-button-label = 복사
move-to-button-label = 이동
clear-recents-history = 최근 기록 비우기
copy-path = 복사 경로
move-to-title = 이동 위치 선택

0
i18n/lo/cosmic_files.ftl Normal file
View file

View file

@ -1,5 +1,5 @@
progress = { $percent }%
cosmic-files = Cosmic Files
cosmic-files = COSMIC Failai
empty-folder = Tuščias aplankas
empty-folder-hidden = Tuščias aplankas (turi paslėptų failų)
no-results = Rezultatų nėra
@ -76,7 +76,7 @@ delete = Ištrinti
permanently-delete-warning = { $target } bus ištrintas visam laikui. Šis veiksmas yra negrįžtamas.
rename-file = Pervadinti failą
rename-folder = Pervadinti aplanką
replace = Pakeisti
replace = Keisti
replace-title = „{ $filename }“ jau egzistuoja šioje vietoje
replace-warning = Ar norite pakeisti tai su tuo, kas yra įrašoma? Keičiant bus pakeistas turinys.
replace-warning-operation = Ar norite pakeisti tai? Pakeičiant bus keičiamas turinys.
@ -244,7 +244,7 @@ item-created = Sukurtas: { $created }
item-modified = Modifikuota: { $modified }
item-accessed = Paskutinė prieiga: { $accessed }
calculating = Skaičiuojama...
settings = Nustatymai
settings = Nuostatos
single-click = Vieno paspaudimo atidarymas
appearance = Išvaizda
match-desktop = Pagal darbalaukio temą
@ -280,18 +280,18 @@ quit = Išeiti
edit = Redaguoti
cut = Iškirpti
copy = Kopijuoti
paste = Įklijuoti
select-all = Pažymėti viską
zoom-in = Priartinti
paste = Įti
select-all = Žymėti viską
zoom-in = Artinti
default-size = Numatytas dydis
zoom-out = Nutolinti
zoom-out = Tolinti
view = Rodymas
grid-view = Tinklelio išdėstymas
list-view = Sąrašo išdėstymas
show-hidden-files = Rodyti paslėptus failus
list-directories-first = Pirmiau pateikti aplankus
gallery-preview = Galerijos peržiūra
menu-settings = Nustatymai...
menu-settings = Nuostatos...
menu-about = Apie COSMIC Files...
sort = Rikiuoti
sort-a-z = A-Ž
@ -302,7 +302,7 @@ sort-smallest-to-largest = Nuo mažiausio iki didžiausio
sort-largest-to-smallest = Nuo didžiausio iki mažiausio
dark = Tamsus
light = Šviesus
comment = COSMIC desktop failų tvarkyklė
comment = COSMIC aplinkos failų tvarkyklė
keywords = Aplankas;Tvarkyklė;
copy-to-title = Pasirinkti kopijavimo vietą
copy-to-button-label = Kopijuoti
@ -317,3 +317,4 @@ clear-recents-history = Išvalyti Neseniai naudotų istoriją
copy-to = Kopijuoti į...
move-to = Perkeltiį į...
copy-path = Kopijuoti kelią
theme = Stilius

View file

@ -98,7 +98,7 @@ permanently-delete-question = Slett permanent
permanently-delete-warning = Er du sukker på om du vil permanent slette { $target }? Dette kan ikke angres.
rename-file = Endre filnavn
rename-folder = Endre mappenavn
replace-title = "{ $filename }" finnes allerede på denne plasseringen.
replace-title = «{ $filename }» finnes allerede på denne plasseringen
replace-warning = Vil du bytte den ut med filen du lagrer? Dette vil overskrive innholdet.
replace-warning-operation = Vil du erstatte den? Dette vil overskrive innholdet.
original-file = Orginalfil
@ -122,8 +122,8 @@ read-write = Kun skrive- og lesbar
read-write-execute = Lesbar, skrivbar og kjørbar
favorite-path-error = Feil ved åpning av mappen
favorite-path-error-description =
Kunne ikke åpne "{ $path }".
Den kan ikke eksistere eller så har du ikke tilgang til å åpne den.
Klarte ikke åpne «{ $path }».
«{ $path }» finnes ikke eller så har du ikke tilgang til å åpne den.
Vil du fjerne den fra sidepanelet?
keep = Behold
@ -140,7 +140,7 @@ zoom-out = Zoom ut
cut = Klipp ut
set-and-launch = Still inn og start
domain = Domene
enter-server-address = Angi serveradresse
enter-server-address = Angi tjeneradresse
network-drive-description =
Serveradresser inkluderer ett protokollprefiks og en addresse.
Examples: ssh://192.168.0.1, ftp://[2001:db8::1]

View file

@ -438,3 +438,12 @@ show-recents = Ostatnie katalogi w panelu bocznym
clear-recents-history = Wyczyść bierzącą historię
copy-path = Skopiuj ścieżkę
mixed = Mieszane
context-action = Akcja sytuacyjna
context-action-confirm-title = Uruchomić "{ $name }"?
context-action-confirm-warning =
Zostanie uruchomionych { $items } { $items ->
[one] element
*[other] elementów
}.
run = Uruchom
rename-confirm = Zmień nazwę

View file

@ -437,3 +437,12 @@ show-recents = Pasta de recentes na barra lateral
clear-recents-history = Limpar histórico de recentes
copy-path = Copiar caminho
mixed = Misto
context-action = Ação de contexto
context-action-confirm-title = Executar "{ $name }"?
context-action-confirm-warning =
Isso será executado em { $items } { $items ->
[one] item
*[other] itens
}.
run = Executar
rename-confirm = Renomear

View file

@ -1,12 +1,12 @@
cosmic-files = Ficheiros COSMIC
comment = Gerenciador de arquivos do COSMIC
keywords = Pastas;Gerenciador;Arquivos;Gestor;Explorer;
keywords = Pasta;Gestor;
empty-folder = Pasta vazia
empty-folder-hidden = Pasta vazia (tem ficheiros ocultos)
no-results = Nenhum resultado encontrado
filesystem = Sistema de ficheiros
home = Pasta Pessoal
notification-in-progress = Operações em curso.
notification-in-progress = Operações em curso
trash = Lixo
undo = Anular
# List view
@ -204,7 +204,7 @@ desktop-view-options = Opções de visualização da área de trabalho...
show-on-desktop = Mostrar na área de trabalho
desktop-folder-content = Conteúdo da pasta da área de trabalho
mounted-drives = Dispositivos montados
trash-folder-icon = Ícone do lixo
trash-folder-icon = Ícone da pasta lixo
icon-size-and-spacing = Tamanho e espaçamento do ícone
icon-size = Tamanho do ícone
grid-spacing = Espaçamento entre ícones
@ -352,3 +352,6 @@ sort-newest-first = Mais recentes primeiro
sort-oldest-first = Mais antigos primeiro
sort-smallest-to-largest = Do menor para o maior
sort-largest-to-smallest = Do maior para o menor
context-action-confirm-title = Executar "{ $name }"?
run = Executar
rename-confirm = Renomear

View file

@ -381,3 +381,12 @@ show-recents = «Недавние документы» в бок. панели
clear-recents-history = Очистить историю недавних
copy-path = Копировать путь
mixed = Смешанные
context-action = Контекстная команда
context-action-confirm-title = Выполнить «{ $name }»?
context-action-confirm-warning =
Команда затронет { $items } { $items ->
[one] элемент
*[other] элем.
}.
run = Выполнить
rename-confirm = Переименовать

View file

@ -0,0 +1,330 @@
open-file = Отвори датотеку
quit = Изађи
cancel = Откажи
open = Отвори
run = Покрени
connect = Повежи
save = Сачувај
password = Лозинка
remove = Уклони
appearance = Изглед
username = Корисничко име
light = Светла
dark = Тамна
settings = Подешавања
replace = Замени
size = Величина
sort-newest-first = Најновије прво
default-app = { $name } (подразумевано)
renamed = Преименована „{ $from }“ у „{ $to }“
read-execute = Читање и извршавање
deleted =
Обрисано је { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash }
item-modified = Измењено: { $modified }
dismiss = Одбаци поруку
list-view = Преглед у виду списака
reload-folder = Поново учитај фасциклу
copy_noun = Копија
favorite-path-error = Грешка при отварању директоријума
progress = { $percent }%
remove-from-sidebar = Уклони из бочне траке
related-apps = Повезани програми
restoring =
Враћање { $items } { $items ->
[one] ставке
*[other] ставки
} из { trash } ({ $progress })...
network-drive-error = Немогуће приступити мрежном уређају
gallery-preview = Преглед галерије
sort-smallest-to-largest = Од најмање до највеће
zoom-in = Увећајте приказ
select-all = Означи све
icon-size-and-spacing = Величина и размак иконица
removing-from-recents =
Уклањање { $items } { $items ->
[one] ставке
*[other] ставки
} из { recents }
cosmic-files = Космик датотеке
type-to-search-enter-path = Уноси путању до директоријума или датотеке
trash = Смеће
emptying-trash = Пражњење { trash } ({ $progress })...
trashed-on = Премештено у смеће
new-window = Нови прозор
zoom-out = Умањите приказ
compressing =
Сажимање { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
move-to-trash = Премести у смеће
menu-about = О програму Космик Датотеке...
setting-executable-and-launching = Подешавање „{ $name }“ као извршне датотеке и покретање
open-multiple-files = Отвори више датотека
default-size = Подразумевана величина
menu-open-with = Отвори програмом...
extracted =
Извлачено { $items } { $items ->
[one] ставка
*[other] ставки
} из „{ $from }“ у „{ $to }“
create-new-folder = Направи нову фасциклу
original-file = Изворна датотека
create = Направи
create-archive = Направи архиву
read-write-execute = Читање, уписивање и извршавање
other-apps = Остали програми
set-permissions = Овлашћења за „{ $name }“ су постављена на { $mode }
pause = Паузирај
calculating = Израчунавам…
sort-by-size = Поређај по величини
rename = Преименуј...
empty-folder-hidden = Празна фасцикла (има сакривених ставки)
keep = Задржи
item-size = Величина: { $size }
permanently-deleting =
Трајно брисање { $items } { $items ->
[one] ставке
*[other] ставки
}
edit = Уреди
connecting = Повезујем се...
read-write = Читање и уписивање
copy = Умножи
none = Ништа
items = Ставки: { $items }
no-results = Нису пронађени резултати
theme = Тема
type = Врста: { $mime }
compressed =
Сажето { $items } { $items ->
[one] ставка
*[other] ставки
} из „{ $from }“ у „{ $to }“
replace-warning = Да ли желите да га замените оним који снимате? Замена ће преписати његов садржај.
rename-folder = Преименуј фасциклу
new-file = Нова датотека...
close-tab = Затвори језичак
name = Назив
open-in-terminal = Отвори у терминалу
resume = Настави
open-multiple-folders = Отвори више фасцикла
remember-password = Запамти лозинку
show-details = Прикажи детаље
grid-spacing = Размак мреже
extract-to = Распакуј у...
add-network-drive = Додај мрежни уређај
copying =
Умножавање { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
delete = Обриши
sort-oldest-first = Најстарије прво
repository = Ризница
create-new-file = Направи нову датотеку
sort-by-trashed = Поређај по времену брисања
replace-warning-operation = Да ли желите да га замените? Замена ће преписати његов садржај.
support = Подршка
try-again = Покушај поново
eject = Избаци
copied =
Умножено { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“
other = Друго
open-in-new-window = Отвори у новом прозору
empty-folder = Празна фасцикла
sort-by-modified = Поређај по датуму измене
list-directories-first = Прикажи директоријуме прво
read-only = Само за читање
folder-name = Назив фасцикле
browse-store = Разгледај { $store }
enter-server-address = Унесите адресу сервера
remove-from-recents = Уклони из недавних
connect-anonymously = Повежи се анонимно
group = Група
apply-to-all = Примени на све
skip = Прескочи
paste = Залепи
menu-settings = Подешавања...
moving =
Премештање { $items } { $items ->
[one] датотеке
*[other] датотека
} из „{ $from }“ у „{ $to }“ ({ $progress })...
replace-with = Замени са
recents = Недавно
change-wallpaper = Промени позадину...
network-drive-description =
Адресе сервера укључују префикс протокола и адресу.
Примери: ssh://192.168.0.1, ftp://[2001:db8::1]
deleting =
Брише се { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash } ({ $progress })...
single-click = Један клик за отварање
view = Преглед
undo = Опозови
setting-permissions = Подешавање овлашћења за „{ $name }“ на { $mode }
owner = Власник
creating = Правим „{ $name }“ у „{ $parent }“
execute-only = Само извршавање
open-item-location = Отвори локацију ставке
details = Детаљи
set-executable-and-launched = Постављено „{ $name }“ као извршну датотеку и покренуто
mounted-drives = Прикључени дискови
sort-a-z = А
mount-error = Немогуће приступити уређају
extract-here = Извуци
grid-view = Преглед у виду мреже
filesystem = Систем датотека
set-and-launch = Подеси и покрени
removed-from-recents =
Уклоњено { $items } { $items ->
[one] ставке
*[other] ставки
} из { recents }
add-to-sidebar = Додај у страничник
item-created = Направљено: { $created }
network-drive-schemes =
Доступни протоколи,Префикс
ЕплТок,afp://
Протокол за пренос датотека,ftp:// или ftps://
Мрежни систем датотека,nfs://
Серверски блок порука,smb://
SSH протокол за пренос датотека,sftp:// или ssh://
ВебДАВ,dav:// или davs://
home = Лична
set-executable-and-launch = Постави као извршну и покрени
restored =
Враћено { $items } { $items ->
[one] ставка
*[other] ставки
} из { trash }
sort-z-a = Ш-А
type-to-search-recursive = Претражује тренутну фасциклу и све подфасцикле
history = Историјат
progress-paused = { $percent }%, паузирано
desktop-view-options = Могућности приказа радне површине...
show-on-desktop = Прикажи на радној површини
cancelled = Отказано
new-folder = Нова фасцикла...
match-desktop = Прати радну површину
domain = Домен
operations-running-finished =
{ $running } { $running ->
[one] радња покренута
*[other] радње покренуте
} ({ $percent }%), { $finished } завршено...
sort-by-name = Поређај по називу
edit-history = Историјат уређивања
sort = Поређај
show-hidden-files = Прикажи скривене датотеке
progress-failed = { $percent }%, није успело
trash-folder-icon = Иконица фасцикле Смеће
item-accessed = Приступљено: { $accessed }
extract-to-title = Распакуј у фасциклу
open-with = Отвори помоћу
keep-both = Задржи оба
icon-size = Величина иконице
open-with-title = Како желите да отворите „{ $name }“?
extracting =
Извлачење { $items } { $items ->
[one] ставке
*[other] ставки
} из „{ $from }“ у „{ $to }“ ({ $progress })...
permanently-deleted =
Трајно обрисано { $items } { $items ->
[one] ставке
*[other] ставки
}
complete = Завршено
write-execute = Писање и извршавање
extract-password-required = Потребна је лозинка
pending = У току
desktop-folder-content = Садржај фасцикле радне површине
renaming = Преименовање „{ $from }“ у „{ $to }“
set-executable-and-launch-description = Да ли желите да поставите „{ $name }“ као извршну и покренете је?
no-history = Нема ставки у историјату.
open-folder = Отвори фасциклу
emptied-trash = Опражњено { trash }
rename-file = Преименуј датотеку
sort-largest-to-smallest = Од највеће до најмање
restore-from-trash = Врати из смећа
cut = Исеци
moved =
Премештено { $items } { $items ->
[one] датотеке
*[other] датотека
} из „{ $from }“ у „{ $to }“
progress-cancelled = { $percent }%, отказано
open-in-new-tab = Отвори у новом језичку
unknown-folder = непозната фасцикла
file = Датотека
file-name = Назив датотеке
save-file = Сачувај датотеку
created = Направљено „{ $name }“ у „{ $parent }“
delete-permanently = Трајно обриши
networks = Мреже
write-only = Само писање
today = Данас
display-settings = Подешавања екрана...
new-tab = Нови језичак
failed = Неуспешно
modified = Измењено
desktop-appearance = Изглед радне површине...
file-already-exists = Датотека са овим називом већ постоји
name-hidden = Називи који почињу тачком „.“ ће бити сакривени
folder-already-exists = Фасцикла са овим називом већ постоји
permanently-delete-warning = { $target } ће бити трајно обрисано. Ова радња се не може поништити.
favorite-path-error-description =
Не можемо да отворимо „{ $path }“
„{ $path }“ можда не постоји или немате дозволу за његово отварање
Желите ли да га уклоните из бочне површи?
empty-trash-warning = Ставке у смећу биће трајно обрисане
empty-trash = Испразни смеће
empty-trash-title = Испразнити смеће?
type-to-search = Куцајте за претрагу
notification-in-progress = Радње са датотекама су у току
name-no-slashes = Назив не може садржати косе црте
permanently-delete-question = Трајно обриши?
replace-title = „{ $filename }“ већ постоји на овој локацији
name-invalid = Назив не може бити „{ $filename }“
operations-running =
{ $running } { $running ->
[one] радња покренута
*[other] радње покренуте
} ({ $percent }%)...
comment = Управник датотека за Космик радну површину
keywords = Folder;Manager;Фасцикла;Управник;fascikla;upravnik;
copy-to-title = Изабери одредиште умножавања
copy-to-button-label = Умножи
move-to-title = Изабери одредиште премештања
move-to-button-label = Помери
context-action = Контекстна радња
context-action-confirm-title = Покрени „{ $name }“?
context-action-confirm-warning =
Ово ће се извршити на { $items } { $items ->
[one] ставку
*[other] ставки
}.
selected-items = Изабраних { $items } ставки
mixed = Помешано
pasted-image = Убачена слика
pasted-text = Убачен текст
pasted-video = Убачен видео
show-recents = Недавна фасцикла у бочној површи
type-to-search-select = Обира прву подударајућу датотеку или фасциклу
clear-recents-history = Очисти историју недавних
compress = Сажми...
copy-to = Умножи у...
move-to = Помери у...
copy-path = Умножи путању
rename-confirm = Преименуј

View file

@ -410,3 +410,12 @@ move-to = Flytta till...
show-recents = Mapp för senast använda filer i sidofältet
clear-recents-history = Töm historik för Senaste
copy-path = Kopiera sökväg
context-action = Kontextåtgärd
context-action-confirm-title = Kör "{ $name }"?
context-action-confirm-warning =
Detta kommer att köras på { $items } { $items ->
[one] objekt
*[other] objekt
}.
run = Kör
rename-confirm = Byt namn

View file

@ -5,7 +5,7 @@ filesystem = Файлова система
home = Домівка
trash = Смітник
recents = Нещодавні
undo = Відмінити
undo = Скасувати
# List view
name = Назва
modified = Змінено
@ -82,10 +82,11 @@ copying =
Копіювання { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» в «{ $to }» ({ $progress })...
} з «{ $from }» до «{ $to }» ({ $progress })...
copied =
Скопійовано { $items } { $items ->
[one] елемент
[few] елементи
*[other] елеменів
} з «{ $from }» в «{ $to }»
emptying-trash = Спорожнення { trash } ({ $progress })...
@ -94,11 +95,12 @@ moving =
Переміщення { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» в «{ $to }» ({ $progress })...
} з «{ $from }» до «{ $to }» ({ $progress })...
moved =
Переміщено { $items } { $items ->
[one] елемент
*[other] елементи
[few] елементи
*[other] елементів
} з «{ $from }» в «{ $to }»
renaming = Перейменування «{ $from }» на «{ $to }»
renamed = Перейменовано «{ $from }» на «{ $to }»
@ -110,7 +112,8 @@ restoring =
restored =
Відновлено { $items } { $items ->
[one] елемент
*[other] елементи
[few] елементи
*[other] елементів
} з { trash }
unknown-folder = невідома тека
@ -223,7 +226,7 @@ permanently-delete-question = Остаточно видалити?
delete = Видалити
permanently-delete-warning = { $target } буде остаточно видалено. Цю дію не можна скасувати.
set-executable-and-launch = Зробити виконуваним і запустити
set-executable-and-launch-description = Бажаєте зробити "{ $name }" виконуваним і запустити його?
set-executable-and-launch-description = Бажаєте зробити «{ $name }» виконуваним і запустити його?
set-and-launch = Зробити і запустити
open-with = Відкрити за допомогою
owner = Власник
@ -245,7 +248,7 @@ favorite-path-error-description =
Вилучити з бічної панелі?
keep = Залишити
add-network-drive = Додати мережевий диск
connect = З'єднати
connect = Зєднати
connect-anonymously = З'єднатись анонімно
connecting = З'єднання…
domain = Домен
@ -274,12 +277,13 @@ compressing =
Стиснення { $items } { $items ->
[one] елемента
*[other] елементів
} з "{ $from }" до "{ $to }" ({ $progress })...
} з «{ $from }» до «{ $to }» ({ $progress })...
compressed =
Стиснуто { $items } { $items ->
[one] елемент
*[other] елементи
} з "{ $from }" до "{ $to }"
[few] елементи
*[other] елементів
} з «{ $from }» до «{ $to }»
deleting =
Видалення { $items } { $items ->
[one] елемента
@ -288,17 +292,19 @@ deleting =
deleted =
Видалено { $items } { $items ->
[one] елемент
[few] елементи
*[other] елементи
} з { trash }
extracting =
Видобування { $items } { $items ->
[one] елемента
*[other] елементів
} з «{ $from }» в «{ $to }» ({ $progress })...
} з «{ $from }» до «{ $to }» ({ $progress })...
extracted =
Видобуто { $items } { $items ->
[one] елемент
*[other] елементи
[few] елементи
*[other] елементів
} з «{ $from }» в «{ $to }»
setting-executable-and-launching = Надання «{ $name }» прав на виконання та запуск
set-executable-and-launched = «{ $name }» надано права на виконання і відкрито
@ -343,7 +349,8 @@ permanently-deleting =
permanently-deleted =
Остаточно вилучено { $items } { $items ->
[one] елемент
*[other] елементи
[few] елементи
*[other] елементів
}
removing-from-recents =
Вилучення { $items } { $items ->
@ -353,13 +360,14 @@ removing-from-recents =
removed-from-recents =
Вилучено { $items } { $items ->
[one] елемент
*[other] елементи
[few] елементи
*[other] елементів
} з { recents }
empty-trash-title = Спорожити смітник?
type-to-search-select = Вибирає перший відповідний файл або папку
pasted-image = Вставлене Зображення
pasted-text = Вставлений Текст
pasted-video = Вставлене Видиво
type-to-search-select = Вибирає перший відповідний файл або теку
pasted-image = Вставлене зображення
pasted-text = Вставлений текст
pasted-video = Вставлене видиво
copy-to-button-label = Копіювати
move-to-button-label = Перемістити
copy-to = Копіювати до…
@ -371,3 +379,13 @@ keywords = Тека;Папка;Провідник;Менеджер;Катало
show-recents = Тека «Нещодавні» на бічній панелі
copy-path = Копіювати шлях
clear-recents-history = Очистити нещодавні
context-action-confirm-title = Запустити «{ $name }»?
run = Виконати
context-action-confirm-warning =
Запуститься для { $items } { $items ->
[one] елемента
*[other] елементів
}.
context-action = Контекстна дія
rename-confirm = Перейменувати
mixed = Змішано

View file

@ -167,7 +167,7 @@ read-write-execute = 读取、写入和执行
## Favorite Path Error Dialog
favorite-path-error = 打开路径时出错
favorite-path-error = 打开目录时出错
favorite-path-error-description =
无法打开 "{ $path }" 。
"{ $path }" 可能不存在或您没有权限打开它。
@ -348,7 +348,7 @@ light = 亮色模式
type-to-search = 输入即可搜索
type-to-search-recursive = 搜索当前文件夹及其所有子文件夹
type-to-search-enter-path = 输入文件夹或文件路径
type-to-search-enter-path = 输入文件夹或文件目录
# Context menu
add-to-sidebar = 加入侧边栏
compress = 压缩…
@ -437,3 +437,12 @@ clear-recents-history = 清除最近访问历史
copy-path = 复制文件路径
show-recents = 侧边栏中的最近访问
mixed = 混合
context-action-confirm-title = 运行 “{ $name }”
run = 运行
context-action-confirm-warning =
该行动将会在 { $items } { $items ->
[one] 项目
*[other] 项目
} 上运行。
context-action = 环境行动
rename-confirm = 重命名

View file

@ -42,7 +42,7 @@ name-no-slashes = 名稱不能包含斜線
## Open/Save Dialog
cancel = 取消
create = 創作
create = 建立
open = 開啟
open-file = 開啟檔案
open-folder = 開啟資料夾
@ -358,7 +358,7 @@ calculating = 計算中...
single-click = 點按以開啟
type-to-search = 輸入進行搜尋
type-to-search-recursive = 搜尋目前資料夾及全部子資料夾
type-to-search-enter-path = 輸入目錄或檔案的路徑
type-to-search-enter-path = 輸入目錄或檔案的目錄
delete-permanently = 永久刪除
eject = 彈出
remove-from-recents = 從最近項目中移除
@ -384,3 +384,12 @@ set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟
setting-permissions = 設定「{ $name }」的權限至 { $mode }
set-permissions = 設定「{ $name }」的權限至 { $mode }
mixed = 混合
context-action = 環境行動
context-action-confirm-title = 執行「{ $name }」嗎?
context-action-confirm-warning =
該行動將會在 { $items } { $items ->
[one] 項目
*[other] 項目
} 上執行。
run = 執行
rename-confirm = 重新命名

View file

@ -48,7 +48,7 @@ build-debug *args:
# Compiles with release profile
build-release *args: (build-debug '--release' args)
# Compiles applet with release profile
build-release-applet *args:
cargo build --package {{applet-name}} --release {{args}}
@ -111,7 +111,7 @@ uninstall:
vendor:
#!/usr/bin/env bash
mkdir -p .cargo
cargo vendor --sync Cargo.toml | head -n -1 > .cargo/config.toml
cargo vendor --locked --sync Cargo.toml | head -n -1 > .cargo/config.toml
echo 'directory = "vendor"' >> .cargo/config.toml
echo >> .cargo/config.toml
echo '[env]' >> .cargo/config.toml

3
rust-toolchain.toml Normal file
View file

@ -0,0 +1,3 @@
[toolchain]
channel = "1.93.0"
components = ["clippy", "rustfmt"]

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
imports_granularity = "Module"

1088
src/app.rs

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,14 @@
use crate::{
mime_icon::mime_for_path,
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
};
use crate::mime_icon::mime_for_path;
use crate::operation::{Controller, OpReader, OperationError, OperationErrorType};
use cosmic::iced::futures;
use jiff::{Zoned, civil::DateTime, tz::TimeZone};
use std::{
collections::HashSet,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
time::SystemTime,
};
use jiff::Zoned;
use jiff::civil::DateTime;
use jiff::tz::TimeZone;
use std::collections::HashSet;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use zip::result::ZipError;
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
@ -47,7 +45,7 @@ pub fn extract(
new_dir: &Path,
password: &Option<String>,
controller: &Controller,
) -> Result<(), OperationError> {
) -> Result<(Vec<PathBuf>, HashSet<PathBuf>), OperationError> {
let mime = mime_for_path(path, None, false);
let password = password.as_deref();
match mime.essence_str() {
@ -57,13 +55,15 @@ pub fn extract(
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?;
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default())
}
"application/x-tar" => OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?,
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default()),
"application/zip" => fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
@ -77,7 +77,7 @@ pub fn extract(
OperationError::from_kind(OperationErrorType::PasswordRequired, controller)
}
_ => OperationError::from_err(e, controller),
})?,
}),
#[cfg(feature = "bzip2")]
"application/x-bzip"
| "application/x-bzip-compressed-tar"
@ -87,7 +87,8 @@ pub fn extract(
.map(bzip2::read::BzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?,
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default()),
#[cfg(feature = "lzma-rust2")]
"application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller.clone())
@ -95,14 +96,14 @@ pub fn extract(
.map(|reader| lzma_rust2::XzReader::new(reader, true))
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?;
.map_err(|e| OperationError::from_err(e, controller))
.map(|_| Default::default())
}
_ => Err(OperationError::from_err(
format!("unsupported mime type {mime:?}"),
controller,
))?,
)),
}
Ok(())
}
// From https://docs.rs/zip/latest/zip/read/struct.ZipArchive.html#method.extract, with cancellation and progress added
@ -111,8 +112,9 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
directory: P,
password: Option<&str>,
controller: Controller,
) -> zip::result::ZipResult<()> {
use std::{ffi::OsString, fs};
) -> zip::result::ZipResult<(Vec<PathBuf>, HashSet<PathBuf>)> {
use std::ffi::OsString;
use std::fs;
use zip::result::ZipError;
fn make_writable_dir_all<T: AsRef<Path>>(
@ -283,10 +285,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
}
}
// Flush files to disk
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
Ok(())
Ok((written_files, target_dirs))
}
fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option<SystemTime> {

View file

@ -1,13 +1,9 @@
// Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use std::{
collections::VecDeque,
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
};
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
/// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
pub fn channel<Message>() -> (Sender<Message>, Receiver<Message>) {

View file

@ -2,12 +2,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
use std::{
borrow::Cow,
error::Error,
path::{Path, PathBuf},
str,
};
use std::borrow::Cow;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::str;
use url::Url;
#[derive(Clone, Copy, Debug)]
@ -132,9 +130,11 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
match mime.as_str() {
"text/uri-list" => {
let text = str::from_utf8(&data)?;
let lines = text.lines();
for line in text.lines() {
for line in text.lines().filter(|line| {
let line = line.trim();
!line.is_empty() && !line.starts_with('#')
}) {
let url = Url::parse(line)?;
match url.to_file_path() {
Ok(path) => paths.push(path),

View file

@ -1,20 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
use std::any::TypeId;
use std::num::NonZeroU16;
use std::path::PathBuf;
use cosmic::{
Application,
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
iced::Subscription,
theme,
};
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
use cosmic::cosmic_config::{self, CosmicConfigEntry};
use cosmic::iced::Subscription;
use cosmic::{Application, theme};
use serde::{Deserialize, Serialize};
use crate::{
FxOrderMap,
app::App,
tab::{HeadingOptions, Location, View},
};
use crate::FxOrderMap;
use crate::app::App;
use crate::tab::{HeadingOptions, Location, View};
pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
@ -172,6 +170,11 @@ pub struct Config {
pub show_details: bool,
pub show_recents: bool,
pub tab: TabConfig,
/// Yoda phase 3: Dolphin-style quick actions toolbar. An ordered list
/// of enabled buttons — position in the vec drives the toolbar order.
/// Reorder in Settings via drag-drop; items not in the vec are
/// hidden. Default = the minimal-6 set from phase 1.
pub toolbar: Vec<ToolbarAction>,
pub type_to_search: TypeToSearch,
}
@ -236,11 +239,97 @@ impl Default for Config {
show_details: false,
show_recents: true,
tab: TabConfig::default(),
toolbar: default_toolbar(),
type_to_search: TypeToSearch::Recursive,
}
}
}
/// Yoda phase 3: ordered enum of quick-action toolbar buttons.
/// The Config stores `Vec<ToolbarAction>` so the user can pick BOTH
/// visibility (just include/exclude the variant) AND order (position in
/// the vec). Drag-drop reorder in the Settings page moves items around.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ToolbarAction {
LocationUp,
Reload,
NewFolder,
NewFile,
Rename,
Delete,
Cut,
Copy,
Paste,
ToggleShowHidden,
OpenTerminal,
}
impl ToolbarAction {
/// Stable list of every supported action. Ordered roughly by logical
/// grouping (location → create/edit → clipboard → view/misc) so that
/// the default enabled set follows a sensible shape and the Settings
/// row for a not-yet-enabled action lands in a predictable spot.
pub const ALL: &'static [Self] = &[
Self::LocationUp,
Self::Reload,
Self::NewFolder,
Self::NewFile,
Self::Rename,
Self::Delete,
Self::Cut,
Self::Copy,
Self::Paste,
Self::ToggleShowHidden,
Self::OpenTerminal,
];
/// u8 discriminant used to carry the action over a DnD mime payload.
pub const fn to_u8(self) -> u8 {
match self {
Self::LocationUp => 0,
Self::Reload => 1,
Self::NewFolder => 2,
Self::NewFile => 3,
Self::Rename => 4,
Self::Delete => 5,
Self::Cut => 6,
Self::Copy => 7,
Self::Paste => 8,
Self::ToggleShowHidden => 9,
Self::OpenTerminal => 10,
}
}
pub const fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::LocationUp),
1 => Some(Self::Reload),
2 => Some(Self::NewFolder),
3 => Some(Self::NewFile),
4 => Some(Self::Rename),
5 => Some(Self::Delete),
6 => Some(Self::Cut),
7 => Some(Self::Copy),
8 => Some(Self::Paste),
9 => Some(Self::ToggleShowHidden),
10 => Some(Self::OpenTerminal),
_ => None,
}
}
}
/// Default set shown on a fresh install — same "minimal 6" as phase 1/2.
pub fn default_toolbar() -> Vec<ToolbarAction> {
vec![
ToolbarAction::NewFolder,
ToolbarAction::Rename,
ToolbarAction::Delete,
ToolbarAction::Cut,
ToolbarAction::Copy,
ToolbarAction::Paste,
]
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
#[serde(default)]
pub struct DesktopConfig {

View file

@ -4,7 +4,8 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::{mime_app, spawn_detached::spawn_detached};
use crate::mime_app;
use crate::spawn_detached::spawn_detached;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum ContextActionSelection {
@ -46,7 +47,7 @@ impl ContextActionPreset {
}
for step in &self.steps {
let Some(commands) = mime_app::exec_to_command(step, paths) else {
let Some(commands) = mime_app::exec_to_command(step, &self.name, None, paths) else {
log::warn!(
"failed to parse context action {:?}: invalid Exec {:?}",
self.name,

View file

@ -1,58 +1,46 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
Application, ApplicationExt, Element,
app::{Core, Task, context_drawer, cosmic::Cosmic},
cosmic_config, cosmic_theme, executor,
iced::core::widget::operation,
iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper},
iced::widget::scrollable::AbsoluteOffset,
iced::{
self, Alignment, Event, Length, Size, Subscription,
core::SmolStr,
event,
futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named},
mouse, stream,
widget::scrollable,
window,
},
theme,
widget::{
self, Operation,
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
segmented_button,
},
use cosmic::app::cosmic::Cosmic;
use cosmic::app::{Core, Task, context_drawer};
use cosmic::iced::core::SmolStr;
use cosmic::iced::core::widget::operation;
use cosmic::iced::futures::{self, SinkExt};
use cosmic::iced::keyboard::key::{Named, Physical};
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper};
use cosmic::iced::widget::scrollable;
use cosmic::iced::widget::scrollable::AbsoluteOffset;
use cosmic::iced::{
self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window,
};
use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{Action as MenuAction, KeyBind};
use cosmic::widget::{self, Operation, segmented_button};
use cosmic::{Application, ApplicationExt, Element, cosmic_config, cosmic_theme, executor, theme};
use mime_guess::{Mime, mime};
use notify_debouncer_full::{
DebouncedEvent, Debouncer, RecommendedCache, new_debouncer,
notify::{self, RecommendedWatcher},
};
use notify_debouncer_full::notify::{self, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
use recently_used_xbel::update_recently_used;
use rustc_hash::{FxHashMap, FxHashSet};
use std::{
any::TypeId,
collections::{HashMap, VecDeque},
env, fmt, fs,
path::PathBuf,
time::{self, Instant},
};
use std::any::TypeId;
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
use std::time::{self, Instant};
use std::{env, fmt, fs};
use crate::{
app::{
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
},
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch},
fl, home_dir,
key_bind::key_binds,
localize::LANGUAGE_SORTER,
menu,
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
tab::{self, ItemMetadata, Location, SearchLocation, Tab},
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
use crate::app::{
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
};
use crate::config::{
Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch,
};
use crate::key_bind::key_binds;
use crate::localize::LANGUAGE_SORTER;
use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage};
use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab};
use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default};
use crate::{fl, home_dir, menu, mime_icon};
#[derive(Clone, Debug)]
pub struct DialogMessage(cosmic::Action<Message>);
@ -468,7 +456,7 @@ enum Message {
Escape,
Filename(String),
Filter(usize),
Key(Modifiers, Key, Option<SmolStr>),
Key(Modifiers, Key, Physical, Option<SmolStr>),
ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems),
Mouse(window::Id, mouse::Button),
@ -487,7 +475,7 @@ enum Message {
TabMessage(tab::Message),
TabRescan(
Location,
Option<tab::Item>,
Option<Box<tab::Item>>,
Vec<tab::Item>,
Option<Vec<PathBuf>>,
),
@ -587,7 +575,7 @@ impl App {
space_s,
space_l,
..
} = theme::active().cosmic().spacing;
} = theme::spacing();
let is_condensed = self.core().is_condensed();
let mut col = widget::column::with_capacity(2).spacing(space_xxs);
@ -744,11 +732,17 @@ impl App {
fn rescan_tab(&self, selection_paths: Option<Vec<PathBuf>>) -> Task<Message> {
let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes;
#[cfg(feature = "gvfs")]
let mounter_items = self.mounter_items.clone();
Task::future(async move {
let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok((parent_item_opt, mut items)) => {
Ok((parent_item_opt, items)) => {
#[cfg(feature = "gvfs")]
let mut items = items;
#[cfg(not(feature = "gvfs"))]
let items = items;
#[cfg(feature = "gvfs")]
{
let mounter_paths: Box<[_]> = mounter_items
@ -837,9 +831,10 @@ impl App {
fn update_config(&mut self) -> Task<Message> {
self.core.window.show_context = self.flags.config.dialog.show_details;
self.tab.config = self.flags.config.dialog_tab();
let config = self.flags.config.dialog_tab();
self.tab.config.view = config.view;
self.update_nav_model();
self.update(Message::TabMessage(tab::Message::Config(self.tab.config)))
self.update(Message::TabMessage(tab::Message::Config(config)))
}
fn with_dialog_config<F: Fn(&mut DialogConfig)>(&mut self, f: F) -> Task<Message> {
@ -902,6 +897,8 @@ impl App {
if let Some(path) = favorite.path_opt() {
let name = if matches!(favorite, Favorite::Home) {
fl!("home")
} else if let Favorite::Network { name, .. } = favorite {
name.clone()
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
@ -1125,7 +1122,7 @@ impl Application for App {
}
fn dialog(&self) -> Option<Element<'_, Message>> {
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
//TODO: should gallery view just be a dialog?
if self.tab.gallery {
@ -1403,7 +1400,10 @@ impl Application for App {
Message::Config(config) => {
if config != self.flags.config {
log::info!("update config");
// Don't overwrite military time
let military_time = self.flags.config.tab.military_time;
self.flags.config = config;
self.flags.config.tab.military_time = military_time;
return self.update_config();
}
}
@ -1456,16 +1456,16 @@ impl Application for App {
}
return self.rescan_tab(None);
}
Message::Key(modifiers, key, text) => {
Message::Key(modifiers, key, physical_key, text) => {
for (key_bind, action) in &self.key_binds {
if key_bind.matches(modifiers, &key) {
if key_bind.matches(modifiers, &key, Some(&physical_key)) {
return self.update(Message::from(action.message()));
}
}
// Check key binds from accept label
if let Some(key_bind) = &self.accept_label.key_bind_opt
&& key_bind.matches(modifiers, &key)
&& key_bind.matches(modifiers, &key, Some(&physical_key))
{
return self.update(if self.flags.kind.save() {
Message::Save(false)
@ -1801,6 +1801,7 @@ impl Application for App {
let autosize_id = widget::Id::unique();
commands.push(self.update(Message::Surface(
cosmic::surface::action::app_popup(
|_: &Self| cosmic::surface::action::LiveSettings::default(),
move |app: &mut Self| -> SctkPopupSettings {
let anchor_rect = Rectangle {
x: point.x as i32,
@ -1925,11 +1926,13 @@ impl Application for App {
items.retain(|item| {
// Directories are always shown
item.metadata.is_dir()
// Check for mime type match (first because it is faster)
|| mimes.iter().any(|filter_mime| {
if filter_mime.subtype() == mime::STAR {
filter_mime.type_() == item.mime.type_()
} else {
*filter_mime == item.mime
|| mime_icon::is_mime_subclass_of(&item.mime, filter_mime)
}
})
// Check for glob match (last because it is slower)
@ -2017,7 +2020,7 @@ impl Application for App {
/// Creates a view after each update.
fn view(&self) -> Element<'_, Message> {
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
let mut col = widget::column::with_capacity(2);
@ -2056,11 +2059,14 @@ impl Application for App {
},
Event::Keyboard(KeyEvent::KeyPressed {
key,
physical_key,
modifiers,
text,
..
}) => match status {
event::Status::Ignored => Some(Message::Key(modifiers, key, text)),
event::Status::Ignored => {
Some(Message::Key(modifiers, key, physical_key, text))
}
event::Status::Captured => {
if key == Key::Named(Named::Escape) {
Some(Message::Escape)

View file

@ -1,11 +1,10 @@
use cosmic::{
iced::core::keyboard::key::Named,
iced::keyboard::Key,
widget::menu::key_bind::{KeyBind, Modifier},
};
use cosmic::iced::core::keyboard::key::Named;
use cosmic::iced::keyboard::Key;
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
use std::collections::HashMap;
use crate::{app::Action, tab};
use crate::app::Action;
use crate::tab;
//TODO: load from config
pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
@ -31,12 +30,16 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
bind!([], Key::Named(Named::F5), Reload);
bind!([], Key::Named(Named::Home), SelectFirst);
bind!([], Key::Named(Named::End), SelectLast);
bind!([], Key::Named(Named::PageDown), ItemPageDown);
bind!([], Key::Named(Named::PageUp), ItemPageUp);
bind!([Shift], Key::Named(Named::ArrowDown), ItemDown);
bind!([Shift], Key::Named(Named::ArrowLeft), ItemLeft);
bind!([Shift], Key::Named(Named::ArrowRight), ItemRight);
bind!([Shift], Key::Named(Named::ArrowUp), ItemUp);
bind!([Shift], Key::Named(Named::Home), SelectFirst);
bind!([Shift], Key::Named(Named::End), SelectLast);
bind!([Shift], Key::Named(Named::PageDown), ItemPageDown);
bind!([Shift], Key::Named(Named::PageUp), ItemPageUp);
bind!([Ctrl, Shift], Key::Character("n".into()), NewFolder);
bind!([], Key::Named(Named::Enter), Open);
bind!([Ctrl], Key::Character(" ".into()), Preview);

View file

@ -1,9 +1,7 @@
use cosmic::widget;
use image::ImageReader;
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
/// Bytes per pixel in RGBA format (Red, Green, Blue, Alpha = 4 bytes)
pub const RGBA_BYTES_PER_PIXEL: u64 = 4;

View file

@ -1,15 +1,16 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{app::Settings, iced::Limits};
use std::{env, fs, path::PathBuf, process};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use cosmic::app::Settings;
use cosmic::iced::Limits;
use std::path::PathBuf;
use std::{env, fs, process};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use crate::{
app::{App, Flags},
config::{Config, State},
tab::Location,
};
use crate::app::{App, Flags};
use crate::config::{Config, State};
use crate::tab::Location;
pub mod app;
mod archive;
@ -37,6 +38,7 @@ mod zoom;
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
#[cfg(feature = "gvfs")]
pub(crate) fn err_str<T: ToString>(err: T) -> String {
err.to_string()
}

View file

@ -1,11 +1,10 @@
use cosmic::iced::{core as iced_core, widget as iced_widget};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Operation, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
renderer,
};
pub fn loaded_image<'a, Message: 'static, Theme>(
handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle,

View file

@ -1,13 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use i18n_embed::{
DefaultLocalizer, LanguageLoader, Localizer,
fluent::{FluentLanguageLoader, fluent_language_loader},
};
use icu::collator::{
Collator, CollatorBorrowed, CollatorPreferences, options::CollatorOptions,
preferences::CollationNumericOrdering,
};
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
use icu::collator::options::CollatorOptions;
use icu::collator::preferences::CollationNumericOrdering;
use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences};
use icu::locale::Locale;
use rust_embed::RustEmbed;
use std::sync::LazyLock;

View file

@ -1,30 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
Element,
app::Core,
iced::{
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
keyboard::Modifiers,
},
theme,
widget::{
self, Row, button, column, container, divider,
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
responsive_menu_bar, space, text,
},
use cosmic::app::Core;
use cosmic::iced::advanced::widget::text::Style as TextStyle;
use cosmic::iced::keyboard::Modifiers;
use cosmic::iced::{Alignment, Background, Border, Length};
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
use cosmic::widget::{
self, Row, button, column, container, divider, responsive_menu_bar, space, text,
};
use cosmic::{Element, theme};
#[cfg(feature = "desktop")]
use i18n_embed::LanguageLoader;
use mime_guess::Mime;
use std::{collections::HashMap, sync::LazyLock};
use std::collections::HashMap;
use std::sync::LazyLock;
use crate::{
app::{Action, Message},
config::{Config, ContextActionPreset},
fl,
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
trash::{Trash, TrashExt},
use crate::app::{Action, Message};
use crate::config::{Config, ContextActionPreset};
use crate::fl;
use crate::tab::{
self, HeadingOptions, ItemMetadata, Location, LocationMenuAction, SearchLocation, Tab,
};
use crate::trash::{Trash, TrashExt};
static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
@ -38,7 +36,7 @@ macro_rules! menu_button {
.height(Length::Fixed(24.0))
.align_y(Alignment::Center)
)
.padding([theme::active().cosmic().spacing.space_xxs, 16])
.padding([theme::spacing().space_xxs, 16])
.width(Length::Fill)
.class(theme::Button::MenuItem)
);
@ -72,17 +70,19 @@ pub fn context_menu<'a>(
String::new()
};
fn key_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
let mut color = theme.cosmic().background(false).component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
let mut color = theme.cosmic().background(false).component.on;
color.alpha *= 0.5;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
@ -110,18 +110,25 @@ pub fn context_menu<'a>(
let (sort_name, sort_direction, _) = tab.sort_options();
let sort_item = |label, variant| {
menu_item(
format!(
"{} {}",
label,
match (sort_name == variant, sort_direction) {
(true, true) => "\u{2B07}",
(true, false) => "\u{2B06}",
_ => "",
}
),
Action::ToggleSort(variant),
let key = find_key(&Action::ToggleSort(variant));
let leading: Element<'a, tab::Message> = if sort_name == variant {
let icon_name = if sort_direction {
"view-sort-ascending-symbolic"
} else {
"view-sort-descending-symbolic"
};
widget::icon::from_name(icon_name).size(14).into()
} else {
space::horizontal().width(Length::Fixed(14.0)).into()
};
menu_button!(
leading,
space::horizontal().width(Length::Fixed(theme::spacing().space_xxs.into())),
text::body(label),
space::horizontal(),
text::body(key).class(theme::Text::Custom(key_style))
)
.on_press(tab::Message::ContextAction(Action::ToggleSort(variant)))
.into()
};
@ -131,6 +138,7 @@ pub fn context_menu<'a>(
let mut selected_desktop_entry = None;
let mut selected_types: Vec<Mime> = vec![];
let mut selected_mount_point = 0;
let mut any_trash_item = false;
if let Some(items) = tab.items_opt() {
for item in items {
if item.selected {
@ -143,15 +151,17 @@ pub fn context_menu<'a>(
Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => {
selected_trash_only = true
}
Some(Location::Path(path)) => {
Some(Location::Path(path))
if selected == 1
&& path.extension().and_then(|s| s.to_str()) == Some("desktop")
{
selected_desktop_entry = Some(&**path);
}
&& path.extension().and_then(|s| s.to_str()) == Some("desktop") =>
{
selected_desktop_entry = Some(&**path);
}
_ => (),
}
if matches!(&item.metadata, ItemMetadata::Trash { .. }) {
any_trash_item = true;
}
selected_types.push(item.mime.clone());
}
}
@ -196,11 +206,11 @@ pub fn context_menu<'a>(
if !Trash::is_empty() {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
}
} else if let Some(entry) = selected_desktop_entry {
} else if let Some(_entry) = selected_desktop_entry {
children.push(menu_item(fl!("open"), Action::Open).into());
#[cfg(feature = "desktop")]
{
children.extend(entry.desktop_actions.into_iter().enumerate().map(
children.extend(_entry.desktop_actions.into_iter().enumerate().map(
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
));
}
@ -273,27 +283,38 @@ pub fn context_menu<'a>(
//TODO: Print?
children.push(menu_item(fl!("show-details"), Action::Preview).into());
if matches!(tab.mode, tab::Mode::App) {
if any_trash_item {
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
}
children.push(divider::horizontal::light().into());
if tab.location.is_recents() {
children.push(
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into(),
);
children.push(divider::horizontal::light().into());
}
if selected_mount_point == 0 {
if modifiers.shift() && !modifiers.control() {
children.push(
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(),
);
} else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
children.push(menu_item(fl!("delete-permanently"), Action::Delete).into());
} else {
if matches!(tab.mode, tab::Mode::App) {
children.push(divider::horizontal::light().into());
children
.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
}
children.push(divider::horizontal::light().into());
if tab.location.is_recents() {
children.push(
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
);
children.push(divider::horizontal::light().into());
}
if selected_mount_point == 0 {
if modifiers.shift() && !modifiers.control() {
children.push(
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete)
.into(),
);
} else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject).into());
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject).into());
}
} else {
//TODO: need better designs for menu with no selection
@ -397,6 +418,12 @@ pub fn context_menu<'a>(
if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
}
if !Trash::is_empty() {
if !children.is_empty() {
children.push(divider::horizontal::light().into());
}
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
}
if !children.is_empty() {
children.push(divider::horizontal::light().into());
}
@ -421,7 +448,7 @@ pub fn context_menu<'a>(
//TODO: move style to libcosmic
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
let component = &cosmic.background(false).component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
@ -579,7 +606,7 @@ pub fn dialog_menu(
])
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360))
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
.spacing(theme::spacing().space_xxxs.into())
.into()
}
@ -634,7 +661,7 @@ pub fn menu_bar<'a>(
responsive_menu_bar()
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360))
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
.spacing(theme::spacing().space_xxxs.into())
.into_element(
core,
key_binds,
@ -815,7 +842,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess
.padding(1)
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
let component = &cosmic.background(false).component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),

View file

@ -1,155 +1,171 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "desktop")]
use cosmic::desktop;
use bstr::{BString, ByteSlice, ByteVec};
use cosmic::widget;
pub use mime_guess::Mime;
use rustc_hash::FxHashMap;
use std::{
cmp::Ordering,
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
process,
time::Instant,
};
#[cfg(feature = "desktop")]
use notify_debouncer_full::notify;
use rustc_hash::{FxHashMap, FxHashSet};
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock, atomic};
use std::time::{self, Instant};
use std::{fs, io, process};
// Supported exec key field codes
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"];
// Deprecated field codes. The spec advises to ignore these handlers.
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"];
#[cfg(feature = "desktop")]
pub async fn watch(mut emitter: impl FnMut() + 'static + Send) {
let watcher_result = notify_debouncer_full::new_debouncer(
time::Duration::from_millis(250),
Some(time::Duration::from_millis(250)),
move |event_res: notify_debouncer_full::DebounceEventResult| {
let Ok(events) = event_res else {
return;
};
if events.iter().any(|event| {
event.kind.is_create() || event.kind.is_modify() || event.kind.is_remove()
}) {
emitter();
}
},
);
if let Ok(mut watcher) = watcher_result {
let system_paths = cosmic_mime_apps::list_paths();
let local_paths = (|| {
let base_dirs = xdg::BaseDirectories::new();
let Some(home) = base_dirs.get_config_home() else {
return Err(std::io::Error::other("XDG config home not set"));
};
let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") else {
return Err(std::io::Error::other("XDG_CURRENT_DESKTOP unset"));
};
let default_mimeapps = home.join("mimeapps.list");
let desktop_mimeapps =
home.join([&desktop.to_ascii_lowercase(), "-mimeapps.list"].concat());
Ok([desktop_mimeapps, default_mimeapps])
})()
.ok();
for path in system_paths
.iter()
.chain(local_paths.as_ref().into_iter().flatten())
{
_ = watcher.watch(path.as_path(), notify::RecursiveMode::NonRecursive);
}
std::future::pending().await
}
}
pub fn exec_to_command(
exec: &str,
entry_name: &str,
entry_path: Option<&Path>,
path_opt: &[impl AsRef<OsStr>],
) -> Option<Vec<process::Command>> {
let args_vec = shlex::split(exec)?;
let program = args_vec.first()?;
// Skip program to make indexing easier
let args_vec = &args_vec[1..];
let arguments = shlex::split(exec)?;
// Base Command instance(s)
// 1. We may need to launch multiple of the same process.
// 2. Each of those processes will need to be passed args from exec.
// 3. Each of those args may appear in any order.
// 4. Arg order should be preserved.
//
// So, we'll go through exec in two passes. The first pass handles paths (%f etc) and args up
// to the field code followed by the second which passes extra, non-% args to each processes.
//
// While it'd be marginally faster to process everything in one pass, that's problematic:
// 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck
// doesn't know we'll only use it once)
// 2. We have to keep track of which modifier (%f etc) we've used/seen already
// 3. We have to keep track of which processes received non-modifier args which gets messy fast
// 4. `exec` is likely small so looping over it twice is not a big deal
let field_code_pos = args_vec
if arguments.is_empty() {
tracing::error!("command does not contain any arguments");
return None;
}
let mut commands = Vec::new();
let paths = path_opt
.iter()
.position(|arg| EXEC_HANDLERS.contains(&arg.as_str()));
let args_handler = field_code_pos.and_then(|i| args_vec.get(i));
// msrv
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}"));
// Number of args before the field code.
// This won't be an off by one err below because take is not zero indexed.
let field_code_pos = field_code_pos.unwrap_or_default();
let mut processes = match args_handler.map(String::as_str) {
Some("%f") => {
let mut processes = Vec::with_capacity(path_opt.len());
.map(AsRef::as_ref)
.map(Some)
// Add a single `None` if no path was given.
.chain(std::iter::repeat_n(
None,
if path_opt.is_empty() { 1 } else { 0 },
));
for path in path_opt.iter().map(AsRef::as_ref) {
// TODO: %f and %F need to handle non-file URLs (see spec)
if from_file_or_dir(path).is_none() {
log::warn!("Desktop file expects a file path instead of a URL: {path:?}");
for path in paths {
let mut batch_process = false;
let mut args = Vec::with_capacity(arguments.len());
let mut field_code_used = false;
for argument in arguments.iter().skip(1) {
let mut new_argument = BString::new(Vec::with_capacity(argument.capacity()));
let mut chars = argument.chars();
while let Some(char) = chars.next() {
// https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html
if char == '%' {
match chars.next() {
Some('%') => new_argument.push_char(char),
Some('c') => new_argument.push_str(entry_name),
Some('k') => {
if let Some(path) = entry_path {
new_argument.push_str(path.as_os_str().as_bytes());
}
}
// %f and %u behave the same in a file manager.
Some('f' | 'u') => {
if let Some(path) = path
&& !field_code_used
{
// TODO: files on remote file systems should be copied to a temporary local file.
batch_process = true;
field_code_used = true;
new_argument.push_str(path.as_bytes());
}
}
// %F and %U behave the same in a file manager.
Some('F') | Some('U') => {
if !field_code_used && new_argument.is_empty() {
field_code_used = true;
for path in path_opt.iter().map(AsRef::as_ref) {
args.push(BString::new(path.as_bytes().to_owned()));
}
}
}
_ => (),
}
} else {
new_argument.push_char(char);
}
// Passing multiple paths to %f should open an instance per path
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(AsRef::as_ref)
.take(field_code_pos)
.chain(std::iter::once(path)),
);
processes.push(process);
}
processes
}
Some("%F") => {
// TODO: %f and %F need to handle non-file URLs (see spec)
for invalid in path_opt
.iter()
.map(AsRef::as_ref)
.filter(|&path| from_file_or_dir(path).is_none())
{
log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}");
if !new_argument.is_empty() {
args.push(new_argument);
}
// Launch one instance with all args
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(path_opt.iter().map(AsRef::as_ref)),
);
vec![process]
}
Some("%u") => path_opt
.iter()
.map(|path| {
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(std::iter::once(path.as_ref())),
);
process
})
.collect(),
Some("%U") => {
let mut process = process::Command::new(program);
process.args(
args_vec
.iter()
.map(OsStr::new)
.take(field_code_pos)
.chain(path_opt.iter().map(AsRef::as_ref)),
);
vec![process]
}
Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"),
None => vec![process::Command::new(program)],
};
// Pass 2: Add remaining arguments that are not % to each process
for arg in args_vec.iter().skip(field_code_pos) {
match arg.as_str() {
// Consume path field codes or fail on codes we don't handle yet
field_code if arg.starts_with('%') => {
if !EXEC_HANDLERS.contains(&field_code)
&& !DEPRECATED_HANDLERS.contains(&field_code)
{
log::warn!("unsupported Exec code {field_code:?} in {exec:?}");
let mut command = process::Command::new(&arguments[0]);
for arg in args {
match arg.to_os_str() {
Ok(arg) => {
command.arg(arg);
}
Err(_) => {
tracing::error!("invalid string encoding in command");
return None;
}
}
arg => {
for process in &mut processes {
process.arg(arg);
}
}
}
commands.push(command);
if !batch_process {
break;
}
}
#[cfg(debug_assertions)]
for command in &processes {
for command in &commands {
log::debug!(
"Parsed program {} with args: {:?}",
command.get_program().to_string_lossy(),
@ -157,13 +173,14 @@ pub fn exec_to_command(
);
}
Some(processes)
Some(commands)
}
fn from_file_or_dir(path: impl AsRef<Path>) -> Option<url::Url> {
url::Url::from_file_path(&path)
.ok()
.or_else(|| url::Url::from_directory_path(&path).ok())
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MimeAppMatch {
Exact,
Related,
Other,
}
#[derive(Clone, Debug)]
@ -172,14 +189,42 @@ pub struct MimeApp {
pub path: Option<PathBuf>,
pub name: String,
pub exec: Option<String>,
pub icon: widget::icon::Handle,
pub is_default: bool,
icon_name: Box<str>,
icon: std::sync::OnceLock<widget::icon::Handle>,
is_default: Arc<RwLock<FxHashSet<Box<str>>>>,
no_display: Arc<AtomicBool>,
}
impl MimeApp {
//TODO: move to libcosmic, support multiple files
pub fn command<O: AsRef<OsStr>>(&self, path_opt: &[O]) -> Option<Vec<process::Command>> {
exec_to_command(self.exec.as_deref()?, path_opt)
exec_to_command(
self.exec.as_deref()?,
&self.name,
self.path.as_deref(),
path_opt,
)
}
pub fn is_default(&self, mime: &Mime) -> bool {
self.is_default.read().unwrap().contains(mime.essence_str())
}
pub fn no_display(&self) -> bool {
self.no_display.load(atomic::Ordering::Relaxed)
}
pub fn icon(&self) -> widget::icon::Handle {
self.icon
.get_or_init(|| {
let name = &*self.icon_name;
if name.starts_with('/') {
cosmic::widget::icon::from_path(PathBuf::from(name))
} else {
cosmic::widget::icon::from_name(name).size(32).handle()
}
})
.clone()
}
}
@ -190,38 +235,10 @@ impl AsRef<str> for MimeApp {
}
}
#[cfg(feature = "desktop")]
impl From<&desktop::DesktopEntryData> for MimeApp {
fn from(app: &desktop::DesktopEntryData) -> Self {
Self {
id: app.id.clone(),
path: app.path.clone(),
name: app.name.clone(),
exec: app.exec.clone(),
icon: match &app.icon {
desktop::fde::IconSource::Name(name) => {
widget::icon::from_name(name.as_str()).size(32).handle()
}
desktop::fde::IconSource::Path(path) => widget::icon::from_path(path.clone()),
},
is_default: false,
}
}
}
#[cfg(feature = "desktop")]
fn filename_eq(path_opt: &Option<PathBuf>, filename: &str) -> bool {
path_opt
.as_ref()
.and_then(|path| path.file_name())
.is_some_and(|x| x == filename)
}
pub struct MimeAppCache {
apps: Vec<MimeApp>,
cache: FxHashMap<Mime, Vec<MimeApp>>,
icons: FxHashMap<Mime, Box<[widget::icon::Handle]>>,
terminals: Vec<MimeApp>,
apps: Vec<Arc<MimeApp>>,
cache: FxHashMap<Mime, Vec<Arc<MimeApp>>>,
terminals: Vec<Arc<MimeApp>>,
}
impl MimeAppCache {
@ -229,154 +246,273 @@ impl MimeAppCache {
let mut mime_app_cache = Self {
apps: Vec::new(),
cache: FxHashMap::default(),
icons: FxHashMap::default(),
terminals: Vec::new(),
};
mime_app_cache.reload();
mime_app_cache
}
pub fn get_apps_for_mime(
&self,
mime_type: &Mime,
include_other: bool,
) -> Vec<(&Arc<MimeApp>, MimeAppMatch)> {
let mut results = Vec::new();
let mut dedupe = FxHashSet::default();
// start with exact matches
results.extend(
self.get(mime_type)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Exact)),
);
let include_mime = match mime_type.type_().as_str() {
"audio" => Some("video/mp4".parse::<Mime>().expect("video/mp4 mime")),
"text" => Some(mime_guess::mime::TEXT_PLAIN),
_ => None,
};
if let Some(mime) = include_mime {
results.extend(
self.get(&mime)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Exact)),
);
}
// grab matches based off of subclass / parent mime type
if let Some(parent_types) = crate::mime_icon::parent_mime_types(mime_type) {
for parent_type in parent_types {
results.extend(
self.get(&parent_type)
.iter()
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Related)),
);
}
}
if include_other {
results.extend({
let mut apps = self
.apps()
.iter()
.filter(|mime_app| !mime_app.no_display())
.filter(|&mime_app| dedupe.insert(&mime_app.id))
.map(|mime_app| (mime_app, MimeAppMatch::Other))
.collect::<Vec<_>>();
apps.sort_by(|(a, _), (b, _)| {
crate::localize::LANGUAGE_SORTER.compare(&a.name, &b.name)
});
apps
});
}
results
}
#[cfg(not(feature = "desktop"))]
pub fn reload(&mut self) {}
// Only available when using desktop feature of libcosmic, which only works on Unix-likes
/// Reload mime types and their known app associations and defaults.
#[cfg(feature = "desktop")]
pub fn reload(&mut self) {
use crate::localize::LANGUAGE_SORTER;
use crate::mime_icon;
use cosmic::desktop::fde;
use std::borrow::Cow;
let start = Instant::now();
self.apps.clear();
self.cache.clear();
self.icons.clear();
self.terminals.clear();
//TODO: get proper locale?
let locale = &[];
// Load desktop applications by supported mime types
//TODO: hashmap for all apps by id?
let all_apps: Box<[_]> = desktop::load_applications(locale, false, None).collect();
for app in &all_apps {
//TODO: just collect apps that can be executed with a file argument?
if !app.mime_types.is_empty() {
self.apps.push(MimeApp::from(app));
}
for mime in &app.mime_types {
let apps = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(1));
if !apps.iter().any(|x| x.id == app.id) {
apps.push(MimeApp::from(app));
}
}
for category in &app.categories {
if category == "TerminalEmulator" {
self.terminals.push(MimeApp::from(app));
break;
}
}
}
let mut list = cosmic_mime_apps::List::default();
let paths = cosmic_mime_apps::list_paths();
list.load_from_paths(&paths);
let locales = fde::get_languages_from_env();
let desktop_entries = fde::Iter::new(fde::default_paths()).entries(Some(&locales));
let mime_icon_cache = mime_icon::MIME_ICON_CACHE.lock().unwrap();
let shared_mime_info = &mime_icon_cache.shared_mime_info;
let mut aliased_mimes = FxHashMap::default();
for (mime, filenames) in list
.added_associations
.iter()
.chain(list.default_apps.iter())
{
for filename in filenames {
log::trace!("add {mime}={filename}");
let apps = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(1));
if !apps.iter().any(|x| filename_eq(&x.path, filename)) {
if let Some(app) = all_apps.iter().find(|&x| filename_eq(&x.path, filename)) {
apps.push(MimeApp::from(app));
} else {
log::info!(
"failed to add association for {mime:?}: application {filename:?} not found"
);
for desktop_entry in desktop_entries {
let name = desktop_entry
.name(&locales)
.unwrap_or_else(|| Cow::Borrowed(desktop_entry.id()));
let app = Arc::new(MimeApp {
id: desktop_entry.appid.clone(),
path: Some(desktop_entry.path.clone()),
name: name.into(),
exec: desktop_entry.exec().map(String::from),
icon_name: desktop_entry.icon().unwrap_or_default().into(),
icon: std::sync::OnceLock::new(),
is_default: Arc::new(RwLock::default()),
no_display: Arc::new(AtomicBool::new(false)),
});
tracing::info!(target: "mime-apps", id = app.id, "detected desktop entry");
self.apps.push(app.clone());
if desktop_entry
.categories()
.into_iter()
.flatten()
.any(|c| c == "TerminalEmulator")
{
self.terminals.push(app.clone());
}
// Cache associations defined by the desktop entry.
let mime_types = desktop_entry.mime_type().unwrap_or_else(Vec::new);
let associated_mime_types = mime_types.iter().filter_map(|m| {
m.parse::<Mime>().ok().map(|mime| {
if let Some(unaliased) = shared_mime_info.unalias_mime_type(&mime) {
aliased_mimes.insert(unaliased.clone(), mime);
return unaliased;
}
mime
})
});
for mime in associated_mime_types {
let apps = self.cache.entry(mime.clone()).or_default();
if apps.iter().all(|cached_app| cached_app.id != app.id) {
apps.push(app.clone());
}
}
}
// Cache added associations from mimeapps lists.
for (mut added_mime, added_apps) in &list.added_associations {
let _unaliased;
if let Some(unaliased) = shared_mime_info.unalias_mime_type(added_mime) {
aliased_mimes.insert(unaliased.clone(), added_mime.clone());
_unaliased = unaliased;
added_mime = &_unaliased;
}
for added_app in added_apps {
if let Some(app) = self
.apps
.iter()
.find(|cached| cached.id.as_str() == added_app.as_ref())
{
let apps = self.cache.entry(added_mime.clone()).or_default();
if apps.iter().all(|cached_app| cached_app.id != app.id) {
apps.push(app.clone());
}
}
}
}
for (mime, filenames) in list.removed_associations.iter() {
for filename in filenames {
log::trace!("remove {mime}={filename}");
if let Some(apps) = self.cache.get_mut(mime) {
apps.retain(|x| !filename_eq(&x.path, filename));
// Remove associations
for (mut removed_mime, removed_apps) in &list.removed_associations {
let _unaliased;
if let Some(unaliased) = shared_mime_info.unalias_mime_type(removed_mime) {
aliased_mimes.insert(unaliased.clone(), removed_mime.clone());
_unaliased = unaliased;
removed_mime = &_unaliased;
}
for removed_app in removed_apps {
if let Some(app) = self
.apps
.iter()
.find(|cached| cached.id.as_str() == removed_app.as_ref())
&& let Some(apps) = self.cache.get_mut(removed_mime)
{
apps.retain(|cached_app| cached_app.id != app.id);
}
}
}
for (mime, filenames) in list.default_apps.iter() {
for filename in filenames {
log::trace!("default {mime}={filename}");
if let Some(apps) = self.cache.get_mut(mime) {
let mut found = false;
for app in apps.iter_mut() {
if filename_eq(&app.path, filename) {
app.is_default = true;
found = true;
} else {
app.is_default = false;
}
}
// Fetch defaults and sort apps by their default precedence.
for (mime, mut apps) in std::mem::take(&mut self.cache).into_iter() {
let defaults = list.default_app_for(&mime);
let aliased_defaults = aliased_mimes
.get(&mime)
.and_then(|mime| list.default_app_for(mime));
let cache = self
.cache
.entry(mime.clone())
.or_insert_with(|| Vec::with_capacity(apps.len()));
// Sort cached apps for this mime by default precedence.
for default in defaults
.into_iter()
.flatten()
.chain(aliased_defaults.into_iter().flatten())
{
let default = default.strip_suffix(".desktop").unwrap_or(default.as_ref());
let mut found_any = false;
apps.retain(|app| {
let found = app.id.as_str() == default;
if found {
break;
app.is_default
.write()
.unwrap()
.insert(mime.essence_str().into());
cache.push(app.clone());
found_any = true;
}
log::debug!(
"failed to set default for {mime:?}: application {filename:?} not found"
);
!found
});
if !found_any && let Some(app) = self.apps.iter().find(|app| app.id == default) {
app.is_default
.write()
.unwrap()
.insert(mime.essence_str().into());
cache.push(app.clone());
}
}
// Sort remaining apps by name
apps.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.name, &b.name));
cache.extend_from_slice(&apps);
tracing::debug!(target: "mime-apps", mime = mime.essence_str(), apps = ?(cache.iter().map(|app| &*app.id).collect::<Vec<&str>>()), "mime defaults found")
}
// Sort apps by name
self.apps
.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
for apps in self.cache.values_mut() {
apps.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
let associated: rustc_hash::FxHashSet<&str> = self
.cache
.values()
.flatten()
.map(|app| app.id.as_str())
.collect();
for app in &self.apps {
app.no_display.store(
!associated.contains(app.id.as_str()),
atomic::Ordering::Relaxed,
);
}
// Copy icons to special cache
//TODO: adjust dropdown API so this is no longer needed
self.icons.extend(self.cache.iter().map(|(mime, apps)| {
(
mime.clone(),
apps.iter().map(|app| app.icon.clone()).collect(),
)
}));
let elapsed = start.elapsed();
log::info!("loaded mime app cache in {elapsed:?}");
tracing::info!(target: "mime-apps", "loaded mime app cache in {elapsed:?}");
}
pub fn apps(&self) -> &[MimeApp] {
pub fn apps(&self) -> &[Arc<MimeApp>] {
&self.apps
}
pub fn get(&self, key: &Mime) -> &[MimeApp] {
pub fn get(&self, key: &Mime) -> &[Arc<MimeApp>] {
self.cache.get(key).map_or(&[], Vec::as_slice)
}
pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] {
self.icons.get(key).map_or(&[], Box::as_ref)
pub fn icons(&self, key: &Mime) -> Vec<widget::icon::Handle> {
self.cache
.get(key)
.map_or_else(Vec::new, |apps| apps.iter().map(|app| app.icon()).collect())
}
fn get_default_terminal(&self) -> Option<String> {
@ -394,14 +530,19 @@ impl MimeAppCache {
.map(|string| string.trim().replace(".desktop", ""))
}
pub fn terminal(&self) -> Option<&MimeApp> {
pub fn terminal(&self) -> Option<&Arc<MimeApp>> {
//TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec
// The current approach works but might not adhere to the spec (yet)
// Look for and return preferred terminals
//TODO: fallback order beyond cosmic-term?
let mut preference_order = vec!["com.system76.CosmicTerm".to_string()];
// Yoda: cosmic-yoterm (our fork) wins over upstream cosmic-term if both
// are installed — useful when xdg-mime default is not set.
let mut preference_order = vec![
"com.aditua.CosmicYoterm".to_string(),
"com.system76.CosmicTerm".to_string(),
];
if let Some(id) = self.get_default_terminal() {
preference_order.insert(0, id);
@ -475,11 +616,43 @@ impl Default for MimeAppCache {
mod tests {
use super::exec_to_command;
#[test]
fn keys_within_words() {
let exec = "/usr/bin/foo --option=%f";
let paths = ["file1"];
let commands = exec_to_command(exec, "keys_within_words", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
assert_eq!(
"--option=file1",
command.get_args().next().unwrap().to_str().unwrap()
);
}
#[test]
fn no_path_f_field_code() {
let exec = "/usr/bin/foo %f";
let paths: [&str; 0] = [];
let commands = exec_to_command(exec, "no_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
assert_eq!(0, command.get_args().len());
}
#[test]
fn one_path_f_field_code() {
let exec = "/usr/bin/foo %f";
let paths = ["file1"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "one_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -494,31 +667,40 @@ mod tests {
#[test]
#[allow(non_snake_case)]
fn one_path_F_field_code() {
let exec = "/usr/bin/bar %F";
let paths = ["cat"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let exec = "/usr/bin/cosmic-term -w %F";
let paths = ["/home/user"];
let commands = exec_to_command(exec, "one_path_F_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap());
assert_eq!("cat", command.get_args().next().unwrap().to_str().unwrap());
assert_eq!(
"/usr/bin/cosmic-term",
command.get_program().to_str().unwrap()
);
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
}
#[test]
fn one_path_u_field_code() {
let exec = "/usr/bin/foobar %u";
let paths = ["/home/josh/krumpli"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let exec = "/usr/bin/cosmic-term -w %u";
let paths = ["/home/user"];
let commands = exec_to_command(exec, "one_path_u_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!("/usr/bin/foobar", command.get_program().to_str().unwrap());
assert_eq!(
*paths.first().unwrap(),
command.get_args().next().unwrap().to_str().unwrap()
"/usr/bin/cosmic-term",
command.get_program().to_str().unwrap()
);
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
}
#[test]
@ -526,7 +708,8 @@ mod tests {
fn one_path_U_field_code() {
let exec = "/usr/bin/rmrfbye %U";
let paths = ["/"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "one_path_U_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -542,7 +725,8 @@ mod tests {
"/usr/share/games/psp/miku.iso",
"/usr/share/games/psp/eternia.iso",
];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "mult_path_f_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) {
@ -562,7 +746,8 @@ mod tests {
"/usr/share/games/doom2/hr.wad",
"/usr/share/games/doom2/hrmus.wad",
];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "mult_path_F_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -584,7 +769,8 @@ mod tests {
"https://redox-os.org/",
"https://system76.com/",
];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "mult_path_u_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) {
@ -607,7 +793,8 @@ mod tests {
"frieren01.mkv",
"rtmp://example.org/this/video/doesnt/exist.avi",
];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "mult_path_U_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -635,7 +822,8 @@ mod tests {
"@@u",
];
let paths = ["file1.rs", "file2.rs"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "flatpak_style_exec", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -658,7 +846,8 @@ mod tests {
"file:///usr/share/games/roguelike/mods/mod1",
"file:///usr/share/games/roguelike/mods/mod2",
];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "multiple_field_codes", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();
@ -691,7 +880,8 @@ mod tests {
];
let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"];
let args_trailing = ["@@"];
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
let commands = exec_to_command(exec, "sandwiched_field_code", None, &paths)
.expect("Should parse valid exec");
assert_eq!(1, commands.len());
let command = commands.first().unwrap();

View file

@ -3,11 +3,9 @@
use cosmic::widget::icon;
use mime_guess::Mime;
use rustc_hash::FxHashMap;
use std::{
fs,
path::Path,
sync::{LazyLock, Mutex},
};
use std::fs;
use std::path::Path;
use std::sync::{LazyLock, Mutex};
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
@ -17,28 +15,21 @@ struct MimeIconKey {
size: u16,
}
struct MimeIconCache {
#[derive(Default)]
pub struct MimeIconCache {
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
#[cfg(unix)]
shared_mime_info: xdg_mime::SharedMimeInfo,
pub shared_mime_info: xdg_mime::SharedMimeInfo,
}
impl MimeIconCache {
pub fn new() -> Self {
Self {
cache: FxHashMap::default(),
#[cfg(unix)]
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
}
}
#[cfg(not(unix))]
pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
None
}
#[cfg(unix)]
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
self.cache
.entry(key)
.or_insert_with_key(|key| {
@ -47,7 +38,7 @@ impl MimeIconCache {
return None;
}
let icon_name = icon_names.remove(0);
let mut named = icon::from_name(icon_name).size(key.size);
let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size);
if !icon_names.is_empty() {
let fallback_names =
icon_names.into_iter().map(std::borrow::Cow::from).collect();
@ -58,8 +49,9 @@ impl MimeIconCache {
.clone()
}
}
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
pub static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
LazyLock::new(|| Mutex::new(MimeIconCache::default()));
#[cfg(not(unix))]
pub fn mime_for_path(
@ -80,6 +72,7 @@ pub fn mime_for_path(
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
// Try the shared mime info cache first
let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
gb.zero_size(false);
if remote {
if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
gb.file_name(file_name);
@ -114,7 +107,10 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
match mime_icon_cache.get(MimeIconKey { mime, size }) {
Some(handle) => handle,
None => icon::from_name(FALLBACK_MIME_ICON).size(size).handle(),
None => icon::from_name(FALLBACK_MIME_ICON)
.prefer_svg(true)
.size(size)
.handle(),
}
}
@ -128,3 +124,11 @@ pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
}
pub fn is_mime_subclass_of(mime_type: &Mime, base: &Mime) -> bool {
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
mime_icon_cache
.shared_mime_info
.mime_type_subclass(mime_type, base)
}

View file

@ -1,18 +1,20 @@
use cosmic::{
Task,
iced::{Subscription, futures::SinkExt, stream},
widget,
};
use gio::{glib, prelude::*};
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
use cosmic::iced::futures::SinkExt;
use cosmic::iced::{Subscription, stream};
use cosmic::{Task, widget};
use gio::glib;
use gio::prelude::*;
use std::any::TypeId;
use std::cell::Cell;
use std::future::pending;
use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc;
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
use crate::{
config::IconSizes,
err_str,
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
};
use crate::config::IconSizes;
use crate::err_str;
use crate::tab::{self, ChecksumState, DirSize, ItemMetadata, ItemThumbnail, Location};
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
@ -214,6 +216,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: scan directory size on gvfs mounts?
dir_size: DirSize::NotDirectory,
cut: false,
checksums: ChecksumState::default(),
});
}
Ok(items)

View file

@ -1,13 +1,13 @@
use cosmic::{Task, iced::Subscription, widget};
use std::{
collections::BTreeMap,
fmt,
path::PathBuf,
sync::{Arc, LazyLock},
};
use cosmic::iced::Subscription;
use cosmic::{Task, widget};
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};
use tokio::sync::mpsc;
use crate::{config::IconSizes, tab};
use crate::config::IconSizes;
use crate::tab;
#[cfg(feature = "gvfs")]
mod gvfs;
@ -75,10 +75,10 @@ impl MounterItem {
}
}
pub fn icon(&self, symbolic: bool) -> Option<widget::icon::Handle> {
pub fn icon(&self, _symbolic: bool) -> Option<widget::icon::Handle> {
match self {
#[cfg(feature = "gvfs")]
Self::Gvfs(item) => item.icon(symbolic),
Self::Gvfs(item) => item.icon(_symbolic),
Self::None => unreachable!(),
}
}
@ -103,6 +103,7 @@ impl MounterItem {
pub type MounterItems = Vec<MounterItem>;
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum MounterMessage {
Items(MounterItems),
MountResult(MounterItem, Result<bool, String>),

View file

@ -3,21 +3,17 @@
use std::time::Instant;
use crate::tab::DOUBLE_CLICK_DURATION;
use cosmic::{
Element, Renderer, Theme,
iced::core::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
border::Border,
event::Event,
layout,
mouse::{self, click},
overlay,
renderer::{self, Quad, Renderer as _},
touch,
widget::{Operation, Tree, tree},
},
widget::Id,
use cosmic::iced::core::border::Border;
use cosmic::iced::core::event::Event;
use cosmic::iced::core::mouse::{self, click};
use cosmic::iced::core::renderer::{self, Quad, Renderer as _};
use cosmic::iced::core::widget::{Operation, Tree, tree};
use cosmic::iced::core::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
overlay, touch,
};
use cosmic::widget::Id;
use cosmic::{Element, Renderer, Theme};
/// Emit messages on mouse events.
#[allow(missing_debug_implementations)]

View file

@ -1,20 +1,15 @@
use crate::{
app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID},
archive,
config::IconSizes,
fl,
spawn_detached::spawn_detached,
tab,
};
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
use std::{
borrow::Cow,
fmt::Formatter,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
use crate::app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID};
use crate::config::IconSizes;
use crate::spawn_detached::spawn_detached;
use crate::{archive, fl, tab};
use cosmic::iced::futures::channel::mpsc::Sender;
use cosmic::iced::futures::{self, SinkExt, StreamExt, stream};
use std::borrow::Cow;
use std::fmt::Formatter;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::{Mutex as TokioMutex, mpsc};
use walkdir::WalkDir;
use zip::AesMode::Aes256;
@ -39,7 +34,7 @@ async fn handle_replace(
conflict_count: usize,
) -> ReplaceResult {
let item_from = match tab::item_from_path(file_from, IconSizes::default()) {
Ok(ok) => ok,
Ok(ok) => Box::new(ok),
Err(err) => {
log::warn!("{err}");
return ReplaceResult::Cancel;
@ -47,7 +42,7 @@ async fn handle_replace(
};
let item_to = match tab::item_from_path(file_to, IconSizes::default()) {
Ok(ok) => ok,
Ok(ok) => Box::new(ok),
Err(err) => {
log::warn!("{err}");
return ReplaceResult::Cancel;
@ -972,11 +967,13 @@ impl Operation {
password,
} => {
let controller_clone = controller.clone();
compio::runtime::spawn_blocking(
move || -> Result<OperationSelection, OperationError> {
compio::runtime::spawn(async move {
let extracted = compio::runtime::spawn_blocking(move || {
let controller = controller_clone;
let total_paths = paths.len();
let mut op_sel = OperationSelection::default();
let mut written_files = Vec::new();
let mut target_dirs = std::collections::HashSet::new();
for (i, path) in paths.iter().enumerate() {
futures::executor::block_on(async {
controller
@ -1000,16 +997,32 @@ impl Operation {
op_sel.ignored.push(path.clone());
op_sel.selected.push(new_dir.clone());
crate::archive::extract(path, &new_dir, &password, &controller)?;
let (files, dirs) = crate::archive::extract(
path,
&new_dir,
&password,
&controller,
)?;
written_files.extend(files);
target_dirs.extend(dirs);
}
}
Ok(op_sel)
},
)
Ok::<_, OperationError>((op_sel, written_files, target_dirs))
})
.await
.map_err(wrap_compio_spawn_error)??;
let (op_sel, written_files, target_dirs) = extracted;
if !written_files.is_empty() || !target_dirs.is_empty() {
sync_to_disk(written_files, target_dirs).await;
}
Ok::<_, OperationError>(op_sel)
})
.await
.map_err(wrap_compio_spawn_error)?
}
.await
.map_err(wrap_compio_spawn_error)?,
Self::Move {
paths,
to,
@ -1143,10 +1156,26 @@ impl Operation {
paths.push(item.original_path());
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
.await
.map_err(wrap_compio_spawn_error)?
.map_err(|e| OperationError::from_err(e, &controller))?;
// Items with .trashinfo id use standard restore; sub-items use manual move
if item
.id
.to_str()
.map_or(false, |s| s.ends_with(".trashinfo"))
{
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
.await
.map_err(wrap_compio_spawn_error)?
.map_err(|e| OperationError::from_err(e, &controller))?;
} else {
let from = PathBuf::from(&item.id);
let to = item.original_path();
if let Some(parent) = to.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| OperationError::from_err(e, &controller))?;
}
std::fs::rename(&from, &to)
.map_err(|e| OperationError::from_err(e, &controller))?;
}
}
Ok(OperationSelection {
ignored: Vec::new(),
@ -1243,28 +1272,23 @@ fn wrap_compio_spawn_error(err: Box<dyn std::any::Any + Send>) -> OperationError
#[cfg(test)]
mod tests {
use std::{
fs::{self, File},
io,
path::PathBuf,
};
use std::fs::{self, File};
use std::io;
use std::path::PathBuf;
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
use cosmic::iced::futures::channel::mpsc;
use cosmic::iced::futures::{StreamExt, future};
use log::debug;
use test_log::test;
use tokio::sync;
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
use crate::{
app::{
DialogPage, Message,
test_utils::{
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
filter_files, simple_fs,
},
},
fl,
use crate::app::test_utils::{
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, filter_files,
simple_fs,
};
use crate::app::{DialogPage, Message};
use crate::fl;
/// Simple wrapper around `[Operation::Copy]`
pub async fn operation_copy(

View file

@ -1,4 +1,5 @@
use std::{fs, io, path::Path};
use std::path::Path;
use std::{fs, io};
use crate::operation::OperationError;

View file

@ -6,15 +6,21 @@ use crate::operation::{OperationError, sync_to_disk};
use anyhow::Context as AnyhowContext;
use compio::BufResult;
use compio::buf::{IntoInner, IoBuf};
use compio::driver::{ToSharedFd, op::AsyncifyFd};
use compio::driver::ToSharedFd;
use compio::driver::op::AsyncifyFd;
use compio::io::{AsyncReadAt, AsyncWriteAt};
use cosmic::iced::futures;
#[cfg(feature = "gvfs")]
use futures::{FutureExt, StreamExt};
use std::cell::Cell;
use std::error::Error;
use std::fs;
use std::future::Future;
use std::ops::ControlFlow;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::time::Instant;
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf};
use walkdir::WalkDir;
#[cfg(feature = "gvfs")]
@ -473,12 +479,12 @@ impl Op {
progress.total_bytes = metadata.as_ref().map(|m| m.len());
(ctx.on_progress)(self, &progress);
if let Some(metadata) = metadata.as_ref() {
if let Err(why) = to_file.set_permissions(metadata.permissions()).await {
// This error is not propagated upwards as some filesystems do not support setting permissions
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
}
if let Some(metadata) = metadata.as_ref()
&& let Err(why) = to_file.set_permissions(metadata.permissions()).await
{
// This error is not propagated upwards as some filesystems do not support setting permissions
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,14 @@
use image::DynamicImage;
use md5::{Digest, Md5};
use rustc_hash::FxHashMap;
use std::error::Error;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::{
error::Error,
fs::{self, File},
io::{self, BufReader, BufWriter},
path::{Path, PathBuf},
sync::LazyLock,
time::UNIX_EPOCH,
};
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::time::UNIX_EPOCH;
use tempfile::NamedTempFile;
use url::Url;

View file

@ -5,12 +5,12 @@
use cosmic::desktop::fde::GenericEntry;
use mime_guess::Mime;
use rustc_hash::FxHashMap;
#[cfg(feature = "desktop")]
use std::{fs, time::Instant};
use std::{
fs,
path::Path,
process,
sync::{LazyLock, Mutex},
time::Instant,
};
#[derive(Clone, Debug)]

View file

@ -1,11 +1,29 @@
use cosmic::widget;
use regex::Regex;
use std::{collections::HashSet, path::PathBuf};
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::{
config::IconSizes,
tab::{Item, SearchItem},
};
use crate::config::IconSizes;
use crate::tab::{Item, SearchItem};
fn percent_decode(s: &str) -> Option<String> {
let (mut r, mut b) = (String::with_capacity(s.len()), s.bytes());
while let Some(c) = b.next() {
if c == b'%' {
let (hi, lo) = (b.next()?, b.next()?);
let h = |x: u8| match x {
b'0'..=b'9' => Some(x - b'0'),
b'a'..=b'f' => Some(x - b'a' + 10),
b'A'..=b'F' => Some(x - b'A' + 10),
_ => None,
};
r.push((h(hi)? << 4 | h(lo)?) as char);
} else {
r.push(c as char);
}
}
Some(r)
}
pub trait TrashExt {
fn is_empty() -> bool {
@ -27,7 +45,7 @@ pub trait TrashExt {
Vec::new()
}
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(_callback: F, _regex: &Regex) {}
fn icon(icon_size: u16) -> widget::icon::Handle {
widget::icon::from_name(if Self::is_empty() {
@ -50,6 +68,46 @@ pub trait TrashExt {
}
}
/// Derive the actual filesystem path of a trashed item from its .trashinfo path
/// (or return the id directly if it's already a filesystem path).
pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
let id_path = Path::new(&item.id);
if id_path.extension().map_or(false, |e| e == "trashinfo") {
let trash_root = id_path.parent()?.parent()?;
let file_name = id_path.file_stem()?;
Some(trash_root.join("files").join(file_name))
} else {
Some(PathBuf::from(&item.id))
}
}
/// For a path inside a trash `files/` directory, reconstruct the original path
/// by reading the parent `.trashinfo` file.
///
/// Given `~/.local/share/Trash/files/folder/sub/file.txt`:
/// - The top-level trashed item is `folder`
/// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path
/// - Compute: `<original_path>/sub/file.txt`
pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
let files = p.ancestors().find(|a| a.ends_with("files"))?;
let root = files.parent()?;
let top = p.strip_prefix(files).ok()?.components().next()?;
let info =
std::fs::read_to_string(root.join("info").join(top).with_extension("trashinfo")).ok()?;
let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?;
let rel = p.strip_prefix(files.join(top)).ok()?;
let mut result = PathBuf::from(&orig);
if !rel.as_os_str().is_empty() {
result.push(rel);
}
Some(result)
}
/// Check whether a path is inside any trash `files/` directory.
pub fn is_trash_path(path: &Path) -> bool {
path.ancestors().any(|a| a.ends_with("files"))
}
pub struct Trash;
// This config statement is from trash::os_limited
@ -81,7 +139,8 @@ impl TrashExt for Trash {
}
fn scan(sizes: IconSizes) -> Vec<Item> {
use crate::{localize::LANGUAGE_SORTER, tab::item_from_trash_entry};
use crate::localize::LANGUAGE_SORTER;
use crate::tab::item_from_trash_entry;
use std::cmp::Ordering;
let entries = match trash::os_limited::list() {
@ -142,4 +201,12 @@ impl TrashExt for Trash {
not(target_os = "android")
)
)))]
impl TrashExt for Trash {}
impl TrashExt for Trash {
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
log::warn!(
"searching trash not supported on this platform for pattern {:?}",
regex.as_str()
);
drop(callback);
}
}

View file

@ -1,6 +1,7 @@
use std::num::NonZeroU16;
use crate::{config::IconSizes, tab::View};
use crate::config::IconSizes;
use crate::tab::View;
static DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap();
static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap();