Compare commits

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

158 commits

Author SHA1 Message Date
20842026d7 chore: fige Cargo.lock (epoch-1.9.0, [patch] locaux)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:03:54 +02:00
4b5fcfe5d7 merge: epoch-1.9.0 d'upstream
# Conflicts:
#	Cargo.lock
#	src/menu.rs
2026-09-24 20:51:53 +02:00
Hojjat
d1f0af78e6 chore: update libcosmic to fix esc on context-menu 2026-09-23 10:44:56 -06:00
Hojjat
87f407e3ed chore: update libcosmic to fix shift+right-click 2026-09-22 10:08:24 -06:00
Huy Nguyen
e2cad979ce
feat(dialog): persist global sort option (#2039)
Add dialog_sort_name field to State to track the dialog sort option.
Add state_handler and state fields to Dialog to load saved state.
Implement tab::Command::SetSort for Dialog to apply and persist sort
options.
2026-09-22 10:06:06 -06:00
Jeremy Soller
b326e48cf6
Make Cosmic Files show .exe icons (#2070)
* [Feature Request] Make Cosmic Files show .exe icons like Dolphin

* Sped Up Icon Extraction

* Compacted Code

* Replace wrestool with pelite crate for .exe icon extraction

* Run PE icon extraction as external thumbnailer

* Use dedicated external EXE thumbnailer

* Remove embedded EXE thumbnailer

* Move cosmic-files-thumbnailer to new crate

---------

Co-authored-by: ZlordHUN <137647604+ZlordHUN@users.noreply.github.com>
2026-09-21 13:25:53 -06:00
Jason Rodney Hansen
c0b8a8c60a fix: drag selection
Fixes bug where drag selection only worked if the pointer passed through an
item.
2026-09-21 13:09:39 -06:00
Michael Murphy
fe71aca0e5
i18n: translation update from Hosted Weblate (#2040)
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/matrix-auto.svg)
2026-09-15 21:51:43 +02:00
Michael Aaron Murphy
d6e7618b76 chore: update pinned cosmic-protocols 2026-09-15 18:33:17 +02:00
Michael Aaron Murphy
c576bbcd60 Epoch 1.9.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-09-15 18:33:17 +02:00
Hojjat
762fc8fb48 chore: update libcosmic & cargo +nightly fmt 2026-09-14 20:25:48 +02:00
Hojjat
7ba55f66cc chore: adopt typed surface actions from libcosmic 2026-09-14 20:25:48 +02:00
Hojjat
b7005660f1 feat: use libcosmic's context_menu widget for the breadcrumb menu 2026-09-14 20:25:48 +02:00
Hojjat
77f062647c feat: use libcosmic's context_menu widget for the file context menu 2026-09-14 20:25:48 +02:00
Hosted Weblate
00233428b3
i18n: translation updates from weblate
Co-authored-by: Amadɣas <massiin@proton.me>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: Satakun Utama <galaxytomostore@gmail.com>
Co-authored-by: Thomas Worofsky <thomas.worofsky@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: taijuin Lee <taijuin@gmail.com>
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/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kab/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/oc/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/th/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hant/
Translation: Pop OS/COSMIC Files
2026-09-13 01:56:28 +02:00
98159d317c chore: fige Cargo.lock 2026-09-10 18:12:37 +02:00
e8eb42b507 Merge tag 'epoch-1.8.0' into local/libcosmic-path-patch
Epoch 1.8.0

# Conflicts:
#	Cargo.lock
2026-09-10 17:57:28 +02:00
dd57647ced chore: fige Cargo.lock (reordonnancement patch.unused par cargo) 2026-09-04 10:48:53 +02:00
Levi Portenier
089ad2b417
Epoch 1.8.0 version update (#2036)
Generated by cosmic-epoch scripts/version-update.sh
2026-09-02 13:02:27 -06:00
Jeremy Soller
1761b9b84b Epoch 1.8.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-09-02 08:14:58 -06:00
Josh Megnauth
06278ee7f1 Change focus only if selected tab is closed
Closes: #1921

Closing a tab other than the focused tab shouldn't change the focus.
2026-09-01 14:29:34 -06:00
Michael Murphy
a412787d69
i18n: translation update from Hosted Weblate (#2016)
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/matrix-auto.svg)
2026-09-01 19:45:10 +02:00
Hosted Weblate
f3b8f6f585
i18n: translation updates from weblate
Co-authored-by: Alex <aleha@users.noreply.hosted.weblate.org>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Dan <jonweblin2205@protonmail.com>
Co-authored-by: David Carvalho <david.snt.carvalho@gmail.com>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Kurt Harren Catindig <kurtharrencatindig03@gmail.com>
Co-authored-by: Mattias Eriksson <snaggen@gmail.com>
Co-authored-by: VandaL <vandalhj@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: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/cs/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/el/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fr/
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/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
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-09-01 17:00:54 +00:00
Hojjat
de739425cb fix: rubber band selection not redrawing on mouse movement 2026-08-31 17:21:58 +02:00
35334ba93b chore: fige Cargo.lock tel que resolu apres le merge 1.7.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 07:58:51 +02:00
deb6000c10 Merge tag 'epoch-1.7.0' into local/libcosmic-path-patch
Epoch 1.7.0

# Conflicts:
#	Cargo.lock
#	src/app.rs
2026-08-27 07:52:59 +02:00
Jeremy Soller
67af455898 Epoch 1.7.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-08-25 11:28:07 -06:00
Konstantinos
b46b77f3a7 Align center for text on items in grid view. fixes #754 2026-08-24 17:08:53 -06:00
Jeremy Soller
9642fef9cd
detect remote filesystems so SFTP browsing is not slow (#1996)
- [x] I have disclosed use of any AI generated code in my commit
messages. (didnt use any)
- 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.

Fixes #582 

filesystem::remote only comes from query_filesystem_info(), but both
scan
paths read it from enumerate_children(), where it is always missing.
Every
remote file was treated as local, so each guard that skips expensive
work on
remote filesystems did nothing.

Also request only the attributes actually used instead of "*", add
is_dir to
ItemMetadata::GvfsPath, and count children in the background for visible
items.
2026-08-24 15:33:05 -06:00
Paul K
0afac77e83
Merge branch 'master' into fix-remote-browsing-speed 2026-08-24 22:44:41 +02:00
Michael Aaron Murphy
e84d3be4fd
i18n: update translations from weblate 2026-08-24 20:17:19 +02:00
David Carvalho
bb8b61f48b
i18n(pt-BR): update translations from Weblate
Currently translated at 100.0% (244 of 244 strings)

Translation: Pop OS/COSMIC Files
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pt_BR/
2026-08-23 11:38:21 +02:00
Jeremy Soller
7cebbef82f
feat: allow renaming sidebar favorites to custom names (#1911)
Closes #1474 



https://github.com/user-attachments/assets/69fb51ca-225c-43c9-91c9-8f48375e39c4

- [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-08-21 07:20:21 -06:00
0f6f9509ae chore: fige Cargo.lock tel que resolu avec les dependances locales
Resolution obtenue avec les patchs de chemin du workspace (libcosmic-yoda,
cosmic-protocols, window_clipboard, cosmic-text). Pas de changement de code,
uniquement le verrou remis en coherence avec l'arbre local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 09:54:41 +02:00
César Mendonça
08a637767d refactor: rename favorite renaming action to "Change sidebar label"
Address review feedback on the sidebar favorite renaming feature:

- Context menu item and dialog title now share the same
  "change-sidebar-label" translation key
- Rename NavMenuAction::RenameFavorite and DialogPage::RenameFavorite
  to ChangeSidebarLabel, with the dialog field renamed to "label"
- Rename Favorite::with_name to Favorite::with_label
- Dialog confirm button now uses the existing "save" key instead of
  "rename-confirm"
- Replace the "rename-favorite"/"favorite-name" keys with
  "sidebar-label" in en and pt-BR translations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRJDQDBKhCqipbcZm7U1v9
2026-08-20 23:16:06 -03:00
César Mendonça
3d520858f3 feat: allow renaming sidebar favorites to custom names
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:16:06 -03:00
Jeremy Soller
28546795b0
fix: handle file mtime overflow (#1959)
Modification time is doing an unchecked add. Files with bad mtime will
overflow and crash the app.

Use a checked add instead, returning None and displaying an empty mtime
instead.

This only affects mtimes via GVFS. Viewing local files works because it
interprets the mtime directly instead of relying on addition.

Fixes #1886 

- [x] I have disclosed use of any AI generated code in my commit
messages.
- [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-08-20 17:21:59 -06:00
Paul K
cca10d2fd3
detect remote filesystems so SFTP browsing is not slow 2026-08-20 17:11:10 +02:00
Will Sheehan
eebf5a9d4c do not panic in truly invalid timestamps 2026-08-19 21:46:45 -07:00
norepro
a5422167cf Return None on mtime overflow 2026-08-19 20:30:31 -07:00
Jeremy Soller
3aa55b07b7
add search field in 'open with' dialog (#1943)
The "Open With" context menu item, which allows you to open a file, now
includes a search option by application name.

Demo:


https://github.com/user-attachments/assets/b4ff5da3-4bdf-4a0f-a20b-b6783f293fef



- [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-08-19 16:01:52 -06:00
1488e2b01b Merge remote-tracking branch 'upstream/master' into local/libcosmic-path-patch
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/app.rs
#	src/dialog.rs
#	src/menu.rs
#	src/tab.rs
2026-08-19 22:02:40 +02:00
d78a22d87c feat: add "edit desktop entry" to the desktop file context menu
Opening a .desktop file launches the application it describes, so the
file manager offered no way to look at or change the file itself: the
only way out was a terminal. Reported upstream on 2025-05-11 and still
open.

Add an "Edit desktop entry" action to the context menu, next to the
entry own desktop actions. It opens the file with the default
text/plain handler instead of its application/x-desktop one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:42:34 +02:00
sanua356
fd6d4523cd add submit form on focus search input, change import search_input 2026-08-19 09:35:15 +03:00
Hosted Weblate
9bdba47514
i18n: translation updates from weblate
Co-authored-by: Alex <aleha@users.noreply.hosted.weblate.org>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jens Peter Nielsen <mail@jp-nielsen.dk>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Kartik Nayak <65574961+kodebarista@users.noreply.github.com>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: Sproston <ojspr2007@gmail.com>
Co-authored-by: Steen Rabol <steen@rabol.dev>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: sicKat <giuseppecatillo2003@gmail.com>
Co-authored-by: the-forest-watches <lucastronks@protonmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/oc/
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/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hans/
Translation: Pop OS/COSMIC Files
2026-08-19 04:51:30 +00:00
chris-010
3c9a78ac9f fix: accept a URI typed into the location bar from any location
The location bar inherits its type from the tab's current location
(Message::EditLocationEnable), and the view has exactly two branches, next
to the existing "TODO: allow editing other locations": a Location::Network
is edited through with_uri, anything else through with_path. Typing
smb://server/share while standing in a local folder therefore produced
Path("smb://server/share"), which is not a directory, and the navigation
was dropped. The reverse direction was broken the same way: from the
Networks view a local path was passed to with_uri and became a bogus
network location.

EditLocationSubmit now decides by parsing the text instead of by the
location it came from:

  * parses as a URL with a scheme other than "file" and has a host
    -> Location::Network, using url::Url::as_str(), which also normalizes
       away surrounding whitespace.
  * fails to parse and the current location is a Network
    -> Location::Path, with the text untrimmed, because a directory name
       may legitimately end in a space.
  * anything else is left as it was, so network:/// and foo:bar keep
    behaving exactly as before.

The reliable indicator for "this is a path" is that parsing fails, not
that the host is missing: network:/// and file:///home both parse with
has_host() == false. Verified against url 2.5.8.

An unmounted share cannot be resolved, since Tab::resolve sends network
locations through mounter.dir_info, so resolve() returns None and the
navigation would be dropped again. Navigating there is what triggers the
mount, because Cmd::NetworkScan mounts the location it is asked to scan,
so the typed URI is used as a fallback. That fallback is restricted to
input recognised by the first rule above; applying it to any network
location would let unparseable text through, and because with_uri keeps
the display name and path of the previous location, the window would look
normal while the listing stayed empty.

Known cosmetic side effect: while the share is not mounted, resolve()
yields nothing and the tab shows the raw URI as its name. It is replaced
by the proper name as soon as the mount completes.

Drafted with AI assistance (Claude Code); the change was reviewed, built
and tested by me on real hardware as described.

Signed-off-by: chris-010 <10660568+chris-010@users.noreply.github.com>
2026-08-18 16:01:32 -06:00
Jeremy Soller
e96b915f63
Epoch 1.6.0 version update (#1985)
Generated by cosmic-epoch scripts/version-update.sh
2026-08-18 15:27:42 -06:00
Jeremy Soller
9ff853c46d Epoch 1.6.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-08-18 13:58:04 -06:00
Jeremy Soller
6e210a3753
Add context menu to text fields and Selectable Text widget (#1815)
- Adds context menu to text input fields
- Adds selectable text to context drawer, but only to file name,
created/modified/accessed date. I'm sure more places could use the
selectable text.
- Also pulls the latest changes in Iced and libcosmic that removes
"redraw on any message", which means moving the cursor doesn't
automatically call a redraw, widgets need to ask for it. I have found
all places that previously was working normally because we were
redrawing almost unconditionally. But it is possible that a custom
widget somewhere still isn't asking for a redraw explicitly when its
state changes. So, for QA I'd keep an eye on that.

Depends on:
- [x] https://github.com/pop-os/libcosmic/pull/1288
- [x] https://github.com/pop-os/iced/pull/378
- [x] https://github.com/pop-os/libcosmic/pull/1384
- [x] https://github.com/pop-os/libcosmic/pull/1388


https://github.com/user-attachments/assets/caf8f940-0793-4665-844a-b6153b35d7bb



___
- [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-08-18 07:22:09 -06:00
Michael Murphy
9c841dbacd
i18n: translation update from Hosted Weblate (#1948) 2026-08-18 14:10:18 +02:00
Hosted Weblate
991e5b234e
i18n: translation updates from weblate
Co-authored-by: Alex <aleha@users.noreply.hosted.weblate.org>
Co-authored-by: Andi Chandler <andi@gowling.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jens Peter Nielsen <mail@jp-nielsen.dk>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Kartik Nayak <65574961+kodebarista@users.noreply.github.com>
Co-authored-by: Martin Meizoso <martinmeizoso@gmail.com>
Co-authored-by: Michael Millet <mikevlet@protonmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: Sproston <ojspr2007@gmail.com>
Co-authored-by: Steen Rabol <steen@rabol.dev>
Co-authored-by: Walter William Beckerleg Bruckman <spayk.99@protonmail.com>
Co-authored-by: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: sicKat <giuseppecatillo2003@gmail.com>
Co-authored-by: the-forest-watches <lucastronks@protonmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: 김유빈 <k.sein1016@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/en_GB/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/fi/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/gl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/it/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/ko/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/nl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/oc/
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/sat/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/zh_Hans/
Translation: Pop OS/COSMIC Files
2026-08-18 05:51:50 +00:00
Hojjat
78ac96c613 chore: update libcosmic 2026-08-17 11:17:19 -06:00
Hojjat
55c1b0afe0 feat: add context menu to text input and selectable text fields
chore: update libcosmic
2026-08-17 11:17:19 -06:00
sanua356
e3aa2a0ec5 fix incorrect search 2026-08-16 11:14:10 +03:00
sanua356
6273fe6505 add autofocus, move label to placeholder, add search and clear icons 2026-08-16 10:30:57 +03:00
Jeremy Soller
5bdfffa494
fix: infinite dialogs with network mount failure (#1956)
Selecting a broken network mount triggers an infinite loop of dialogs
that requires killing the app:

1. Click invalid network mount
2. Try to mount network path
3. Show failure dialog
4. Trigger `NetworkDriveOpenEntityAfterMount`
* BUG: This fires even if the mount failed or the user canceled the
dialog!
6. Goto 2

Fix by having `network_drive(...)` return a Boolean indicating whether
the mount actually happened and using that to break the infinite error
loop.

Fixes #1629 

Sample broken network path that triggers the infinite dialog when
selected:

**~/.config/cosmic/com.system76.CosmicFiles/v1/favorites**

```ron
[
    Home,
    Documents,
    Downloads,
    Network(
        uri: "sftp://myserver/shared",
        name: "",
        path: "/tmp/fake-network/sftp-host=myserver/mnt",
    ),
]
```

- [x] I have disclosed use of any AI generated code in my commit
messages.
- [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-08-11 07:46:35 -06:00
Will Sheehan
95035166d3 fix infinite dialog on network mount failure 2026-08-04 23:09:19 -07:00
Jeremy Soller
24e34eaa0f
Epoch 1.5.0 version update (#1946)
Generated by cosmic-epoch scripts/version-update.sh
2026-07-28 18:07:38 -06:00
Jeremy Soller
0875cda769 Epoch 1.5.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-07-28 09:14:14 -06:00
Michael Murphy
ebce599f3a
i18n: translation updates from weblate (#1891)
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/fa/
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/hi/
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/it/
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/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
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/sat/
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

Co-authored-by: Ahrakiel <christopherbatrier@gmail.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alex <aleha@users.noreply.hosted.weblate.org>
Co-authored-by: Aman Alam <aalam@users.noreply.hosted.weblate.org>
Co-authored-by: Arve Eriksson <arve.eriksson@startmail.com>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.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: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Kartik Nayak <65574961+kodebarista@users.noreply.github.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Saeed <s.zare.org.82@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: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: abdelbasset jabrane <naminio201@users.noreply.hosted.weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: rbrule <raphaelbrule22@gmail.com>
Co-authored-by: taijuin Lee <taijuin@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: 麋麓 BigELK176 <BigELK176@gmail.com>
2026-07-28 16:41:17 +02:00
Hosted Weblate
d6f6179e67
i18n: translation updates from weblate
Co-authored-by: Ahrakiel <christopherbatrier@gmail.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alex <aleha@users.noreply.hosted.weblate.org>
Co-authored-by: Aman Alam <aalam@users.noreply.hosted.weblate.org>
Co-authored-by: Arve Eriksson <arve.eriksson@startmail.com>
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.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: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Isaac Subirana <isaacsubiranac@gmail.com>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Co-authored-by: Julien Brouillard <julienbrouillard1@gmail.com>
Co-authored-by: Kartik Nayak <65574961+kodebarista@users.noreply.github.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Saeed <s.zare.org.82@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: Wilbur Lindqvist <william@willmantrade.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Co-authored-by: abdelbasset jabrane <naminio201@users.noreply.hosted.weblate.org>
Co-authored-by: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>
Co-authored-by: lorduskordus <lorduskordus@gmail.com>
Co-authored-by: rbrule <raphaelbrule22@gmail.com>
Co-authored-by: taijuin Lee <taijuin@gmail.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Co-authored-by: Димко <Dymkovych@proton.me>
Co-authored-by: 麋麓 BigELK176 <BigELK176@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/fa/
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/hi/
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/it/
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/lt/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pa/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-files/pl/
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/sat/
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-07-28 07:02:19 +02:00
sanua356
9a4f78aa75 remove extra translations 2026-07-26 17:36:56 +03:00
sanua356
a1c1aff60c add search field in 'open with' dialog 2026-07-26 14:46:03 +03:00
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
Jeremy Soller
9a21542825
fix: not all files directory are trash (#1933)
At the moment any directory anywhere named "files" is considered trash
by cosmic-files and shown as empty.
This tightens the check on what is a trash directory with a fallback
where it also expect an info directory next to the files directory(only
used if the system doesn't report any trash directory).

As an added bonus I fixed the let _ = THUMB_SEMAPHORE.acquire() that was
immediately discarded and thus dead code.

- [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-07-21 06:55:26 -06:00
Jeremy Soller
7ae2621f70
Epoch 1.4.0 version update (#1938)
Generated by cosmic-epoch scripts/version-update.sh
2026-07-20 18:14:18 -06:00
Jeremy Soller
eb04ae28da Epoch 1.4.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-07-20 14:36:15 -06:00
RipleyTom
dc36bdc8d3
fix: fixes original_path_for_trash_child 2026-07-19 14:03:04 +02:00
RipleyTom
8ef1082a4c
fix: not all files directory are trash 2026-07-18 19:13:12 +02:00
Joel
a93fc1d9ee
fix: fall back to GIO on destination open failure for MTP copies
* fix: fall back to GIO on destination open failure for MTP copies
* Preserve error source chain in non-gvfs open failure path
2026-07-15 12:42:17 -06:00
Michael Aaron Murphy
5278c67957 fix(libcosmic): crash when right clicking mounted path in nav bar
Contains fix from https://github.com/pop-os/libcosmic/pull/1354
2026-07-14 10:25:57 -06:00
Jeremy Soller
6c45017b24 Epoch 1.3.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-07-13 09:18:38 -06:00
Ashley Wulber
b1fbc1d538 chore: update libcosmic 2026-07-09 17:23:33 -06:00
Ashley Wulber
c4a9096ae2 fix: update iced
popup blur & corner cleanup
2026-07-07 11:36:24 -04:00
Jeremy Soller
fab53e54d6
chore: update libcosmic (#1713)
- [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-07-06 07:11:32 -06: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
Ashley Wulber
168ff6d6f8 chore: updated deps 2026-07-02 14:23:12 -04:00
norepro
43a4228ca4
feat: support MIME subclasses in file chooser dialog 2026-07-02 17:52:14 +02:00
Ashley Wulber
908218e3e0 fix: remove background style for container around tab bar 2026-07-01 15:18:11 -04:00
Ashley Wulber
8fc5182486 fix: popup corner improvements 2026-07-01 15:18:11 -04:00
Ashley Wulber
e00725a450 chore: update for navbar popup 2026-07-01 15:18:11 -04:00
Ashley Wulber
0e761ba811 fix: no blur for desktop mode
fix: desktop blur

chore: update libcosmic

chore: update libcosmic

chore: update libcosmic
2026-07-01 15:18:11 -04:00
Ashley Wulber
674a33e3de cctk 2026-07-01 15:18:11 -04:00
Ashley Wulber
6fe510a67a chore: update libcosmic 2026-07-01 15:18:09 -04: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
81 changed files with 7542 additions and 3685 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"],
},
},
},
},
}

2885
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

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}; use xdgen::{App, Context, FluentString};
fn main() { fn main() {

View file

@ -1,10 +1,10 @@
[package] [package]
name = "cosmic-files-applet" name = "cosmic-files-applet"
version = "1.0.11" version = "1.9.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
log = "0.4" log.workspace = true
zbus = "4" # Blocking connection in zbus 5 hangs zbus = "4" # Blocking connection in zbus 5 hangs
[dependencies.cosmic-files] [dependencies.cosmic-files]

View file

@ -0,0 +1,8 @@
[package]
name = "cosmic-files-thumbnailer"
version = "1.9.0"
edition = "2024"
[dependencies]
image.workspace = true
pelite = "0.10"

View file

@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-3.0-only
use image::{DynamicImage, ImageFormat, RgbaImage};
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::io;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
thumbnail_from_args(std::env::args_os().skip(1))
}
#[allow(dead_code)]
pub fn thumbnail_from_args(mut args: impl Iterator<Item = OsString>) -> Result<(), Box<dyn Error>> {
let usage = || {
io::Error::new(
io::ErrorKind::InvalidInput,
"usage: cosmic-files-thumbnailer OUTPUT --size SIZE INPUT",
)
};
let output = args.next().ok_or_else(usage)?;
if args.next().as_deref() != Some(OsStr::new("--size")) {
return Err(usage().into());
}
let size = args
.next()
.and_then(|arg| arg.into_string().ok())
.and_then(|arg| arg.parse::<u32>().ok())
.filter(|size| *size > 0)
.ok_or_else(usage)?;
let input = args.next().ok_or_else(usage)?;
if args.next().is_some() {
return Err(usage().into());
}
thumbnail(Path::new(&input), Path::new(&output), size)
}
#[allow(dead_code)]
pub fn thumbnail(input: &Path, output: &Path, size: u32) -> Result<(), Box<dyn Error>> {
let icon = thumbnail_image(input, size)?;
DynamicImage::ImageRgba8(icon).save_with_format(output, ImageFormat::Png)?;
Ok(())
}
pub fn thumbnail_image(input: &Path, size: u32) -> Result<RgbaImage, Box<dyn Error>> {
if size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"thumbnail size must be positive",
)
.into());
}
// Map the PE so only the headers and resource pages touched by pelite are read.
let data = pelite::FileMap::open(input)?;
let bytes = data.as_ref();
if bytes.len() < 64 || !bytes.starts_with(b"MZ") {
return Err(io::Error::new(io::ErrorKind::InvalidData, "input is not a PE file").into());
}
let file = pelite::PeFile::from_bytes(bytes).map_err(invalid_data)?;
let resources = file.resources().map_err(invalid_data)?;
let (_name, group) = resources
.icons()
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PE file has no icon group"))?
.map_err(invalid_data)?;
let mut ico = Vec::new();
group.write(&mut ico).map_err(invalid_data)?;
Ok(
DynamicImage::ImageRgba8(largest_icon(&ico).map_err(invalid_data)?)
.thumbnail(size, size)
.into_rgba8(),
)
}
fn invalid_data(error: impl std::fmt::Display) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error.to_string())
}
fn largest_icon(ico: &[u8]) -> Result<RgbaImage, String> {
if ico.len() < 6 {
return Err("ICO header is truncated".to_string());
}
let count = usize::from(u16::from_le_bytes([ico[4], ico[5]]));
let mut best: Option<(u64, RgbaImage)> = None;
let mut errors = Vec::new();
for i in 0..count.min(32) {
let entry = 6 + i * 16;
if ico.len() < entry + 16 {
break;
}
let size = u32::from_le_bytes([
ico[entry + 8],
ico[entry + 9],
ico[entry + 10],
ico[entry + 11],
]) as usize;
let offset = u32::from_le_bytes([
ico[entry + 12],
ico[entry + 13],
ico[entry + 14],
ico[entry + 15],
]) as usize;
let Some(end) = offset.checked_add(size) else {
continue;
};
let Some(data) = ico.get(offset..end) else {
continue;
};
match decode_icon(&ico[entry..entry + 16], data) {
Ok(image) => {
let pixels = u64::from(image.width()) * u64::from(image.height());
if best
.as_ref()
.is_none_or(|(best_pixels, _)| pixels > *best_pixels)
{
best = Some((pixels, image));
}
}
Err(err) => errors.push(format!("frame {i}: {err}")),
}
}
best.map(|(_, image)| image).ok_or_else(|| {
if errors.is_empty() {
"PE icon contains no complete frames".to_string()
} else {
format!("PE icon could not be decoded ({})", errors.join("; "))
}
})
}
fn decode_icon(entry: &[u8], data: &[u8]) -> Result<RgbaImage, String> {
if data.starts_with(b"\x89PNG\r\n\x1a\n") {
return image::ImageReader::with_format(io::Cursor::new(data), ImageFormat::Png)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string());
}
let capacity = 22usize
.checked_add(data.len())
.ok_or_else(|| "frame is too large".to_string())?;
let data_len = u32::try_from(data.len()).map_err(|_| "frame is too large".to_string())?;
let mut ico = Vec::with_capacity(capacity);
ico.extend_from_slice(&[0, 0, 1, 0, 1, 0]);
ico.extend_from_slice(entry);
ico[14..18].copy_from_slice(&data_len.to_le_bytes());
ico[18..22].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(data);
image::ImageReader::with_format(io::Cursor::new(ico), ImageFormat::Ico)
.decode()
.map(DynamicImage::into_rgba8)
.map_err(|err| err.to_string())
}
#[cfg(test)]
mod tests {
use super::{largest_icon, thumbnail_from_args};
use image::{DynamicImage, ImageFormat, Rgb, RgbImage, Rgba, RgbaImage};
use std::ffi::OsString;
use std::io::Cursor;
#[test]
fn malformed_thumbnail_request_returns_an_error() {
let args = [OsString::from("output.png")];
assert!(thumbnail_from_args(args.into_iter()).is_err());
}
#[test]
fn largest_icon_skips_invalid_entries() {
let image = RgbaImage::from_pixel(2, 2, Rgba([1, 2, 3, 255]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgba8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 2, 0];
let mut invalid_entry = [0; 16];
invalid_entry[8..12].copy_from_slice(&4u32.to_le_bytes());
invalid_entry[12..16].copy_from_slice(&u32::MAX.to_le_bytes());
ico.extend_from_slice(&invalid_entry);
let mut valid_entry = [0; 16];
valid_entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 32, 0]);
valid_entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
valid_entry[12..16].copy_from_slice(&38u32.to_le_bytes());
ico.extend_from_slice(&valid_entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid icon after malformed entry");
assert_eq!(decoded.dimensions(), (2, 2));
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
#[test]
fn dib_and_mask_is_applied() {
let mut dib = vec![0; 40];
dib[0..4].copy_from_slice(&40u32.to_le_bytes());
dib[4..8].copy_from_slice(&2i32.to_le_bytes());
dib[8..12].copy_from_slice(&4i32.to_le_bytes());
dib[12..14].copy_from_slice(&1u16.to_le_bytes());
dib[14..16].copy_from_slice(&24u16.to_le_bytes());
// Two bottom-up BGR rows, padded to four-byte boundaries.
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
dib.extend_from_slice(&[0, 0, 255, 0, 0, 255, 0, 0]);
// The first mask row is the bottom image row; its first pixel is transparent.
dib.extend_from_slice(&[0b1000_0000, 0, 0, 0]);
dib.extend_from_slice(&[0, 0, 0, 0]);
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(dib.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&dib);
let decoded = largest_icon(&ico).expect("valid DIB icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([255, 0, 0, 255]));
assert_eq!(decoded.get_pixel(0, 1), &Rgba([255, 0, 0, 0]));
}
#[test]
fn rgb_png_icon_is_decoded() {
let image = RgbImage::from_pixel(2, 2, Rgb([1, 2, 3]));
let mut png = Cursor::new(Vec::new());
DynamicImage::ImageRgb8(image)
.write_to(&mut png, ImageFormat::Png)
.unwrap();
let png = png.into_inner();
let mut ico = vec![0, 0, 1, 0, 1, 0];
let mut entry = [0; 16];
entry[..8].copy_from_slice(&[2, 2, 0, 0, 1, 0, 24, 0]);
entry[8..12].copy_from_slice(&u32::try_from(png.len()).unwrap().to_le_bytes());
entry[12..16].copy_from_slice(&22u32.to_le_bytes());
ico.extend_from_slice(&entry);
ico.extend_from_slice(&png);
let decoded = largest_icon(&ico).expect("valid RGB PNG icon");
assert_eq!(decoded.get_pixel(0, 0), &Rgba([1, 2, 3, 255]));
}
}

84
debian/changelog vendored
View file

@ -1,3 +1,87 @@
cosmic-files (1.9.0) noble; urgency=medium
* Epoch 1.9.0 version update
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 20:35:19 +0200
cosmic-files (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:14:53 -0600
cosmic-files (1.7.0) noble; urgency=medium
* Epoch 1.7.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:20:22 -0600
cosmic-files (1.6.0) noble; urgency=medium
* Epoch 1.6.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 18 Aug 2026 07:13:48 -0600
cosmic-files (1.5.0) noble; urgency=medium
* Epoch 1.5.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 28 Jul 2026 09:14:09 -0600
cosmic-files (1.4.0) noble; urgency=medium
* Epoch 1.4.0 version update
-- Jeremy Soller <jeremy@system76.com> Mon, 20 Jul 2026 14:36:06 -0600
cosmic-files (1.3.0) noble; urgency=medium
* Epoch 1.3.0 version update
-- Jeremy Soller <jeremy@system76.com> Sun, 12 Jul 2026 13:21:18 -0600
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 cosmic-files (1.0.11) noble; urgency=medium
* Epoch 1.0.11 version update * Epoch 1.0.11 version update

View file

@ -1,4 +1,6 @@
tar-ignore=.github tar-ignore=.github
tar-ignore=.vscode tar-ignore=target/.rustc_info.json
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=vendor tar-ignore=vendor
tar-ignore=target

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::recursive::{Context, Method};
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context}; use cosmic_files::operation::{Controller, ReplaceResult};
use std::{error::Error, io, path::PathBuf}; use std::error::Error;
use std::io;
use std::path::PathBuf;
#[compio::main] #[compio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {

View file

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

View file

@ -84,7 +84,7 @@ settings = الإعدادات
### Appearance ### Appearance
appearance = المظهر appearance = المظهر
theme = النسق theme = السمة
match-desktop = طابق سطح المكتب match-desktop = طابق سطح المكتب
dark = داكن dark = داكن
light = فاتح light = فاتح
@ -110,7 +110,7 @@ new-tab = لسان جديد
new-window = نافذة جديدة new-window = نافذة جديدة
rename = غيِّر الاسم... rename = غيِّر الاسم...
close-tab = أغلق اللسان close-tab = أغلق اللسان
quit = أنهِ quit = إنهِ
## Edit ## Edit
@ -147,7 +147,7 @@ icon-size-and-spacing = حجم الأيقونة والتباعد
icon-size = حجم الأيقونة icon-size = حجم الأيقونة
grid-spacing = تباعد شبكي grid-spacing = تباعد شبكي
trashed-on = مهمل trashed-on = مهمل
details = التفاصيل details = تفاصيل
pause = ألبث pause = ألبث
resume = استئناف resume = استئناف
create-archive = أنشئ أرشيف create-archive = أنشئ أرشيف
@ -372,3 +372,18 @@ show-recents = مجلد الحديثة في الشريط الجانبي
clear-recents-history = امحُ التأريخ الحديث clear-recents-history = امحُ التأريخ الحديث
copy-path = انسخ المسار copy-path = انسخ المسار
mixed = مختلط mixed = مختلط
context-action = إجراء السياق
context-action-confirm-title = شغِّل "{ $name }"؟
context-action-confirm-warning =
سيُشغِّل هذا على { $items } { $items ->
[one] عنصر
[two] عنصرين
[few] عناصر
[many] عنصرًا
*[other] عنصر
}.
run = شغِّل
checksum = تدقيق المجموع { $kind }
calculate = احسب
error = خطأ
rename-confirm = أعد التسمية

View file

@ -1,11 +1,11 @@
cosmic-files = Fitxers del COSMIC cosmic-files = Fitxers del COSMIC
empty-folder = Carpeta buida 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 no-results = No s'ha trobat cap resultat
filesystem = Sistema de fitxers filesystem = Sistema de fitxers
home = Inici home = Inici
networks = Xarxes networks = Xarxes
notification-in-progress = Hi ha operacions amb fitxers en curs. notification-in-progress = Operacions de fitxers en curs
trash = Paperera trash = Paperera
recents = Recents recents = Recents
undo = Desfés undo = Desfés
@ -29,14 +29,14 @@ details = Detalls
dismiss = Descarta el missatge dismiss = Descarta el missatge
operations-running = operations-running =
{ $running } { $running -> { $running } { $running ->
[one] operacion [one] operació
*[other] operacions *[other] operacions
} en curs ({ $percent }%)... } en curs ({ $percent }%)...
operations-running-finished = operations-running-finished =
{ $running }{ $running -> { $running }{ $running ->
[one] operacion [one] operació
*[other] operacions *[other] operacions
} en curs ({ $percent }%), { $finished } acabat... } en curs ({ $percent }%), { $finished } acabada/es...
pause = Pausa pause = Pausa
resume = Reprèn resume = Reprèn
@ -54,7 +54,7 @@ extract-password-required = Cal una contrasenya
## Empty Trash Dialog ## Empty Trash Dialog
empty-trash = Buida la paperera 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 ## Mount Error Dialog
@ -64,20 +64,20 @@ mount-error = No es pot accedir a la unitat
create-new-file = Crea un nou fitxer create-new-file = Crea un nou fitxer
create-new-folder = Crea una nova carpeta create-new-folder = Crea una nova carpeta
file-name = Nom del fixer file-name = Nom del fitxer
folder-name = Nom de la carpeta folder-name = Nom de la carpeta
file-already-exists = Ja existeix un fitxer amb aquest nom. file-already-exists = Ja existeix un fitxer amb aquest nom
folder-already-exists = Ja existeix una carpeta amb aquest nom. folder-already-exists = Ja existeix una carpeta amb aquest nom
name-hidden = Els noms que comencin amb "." seran ocults. name-hidden = Els noms que comencin amb "." s'ocultaran.
name-invalid = El nom no pot ser "{ $filename }". name-invalid = El nom no pot ser "{ $filename }"
name-no-slashes = El nom no pot contenir barres. name-no-slashes = El nom no pot contenir barres.
## Open/Save Dialog ## Open/Save Dialog
cancel = Cancel·la cancel = Cancel·lar
create = Crea create = Crea
open = Obre open = Obre
open-file = Obre el fixer open-file = Obre el fitxer
open-folder = Obre la carpeta open-folder = Obre la carpeta
open-in-new-tab = Obre en una pestanya nova open-in-new-tab = Obre en una pestanya nova
open-in-new-window = Obre en una finestra nova open-in-new-window = Obre en una finestra nova
@ -352,3 +352,14 @@ sort-newest-first = Primer més recents
sort-oldest-first = Primer més antics sort-oldest-first = Primer més antics
sort-smallest-to-largest = De petit a gran sort-smallest-to-largest = De petit a gran
sort-largest-to-smallest = De gran a petit 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 Dialog
empty-trash = Vysypat koš empty-trash = Vyprázdnit koš
empty-trash-warning = Položky v koši budou trvale smazány empty-trash-warning = Položky v koši budou trvale smazány
## New File/Folder Dialog ## New File/Folder Dialog
@ -195,8 +195,8 @@ deleted =
[few] položky [few] položky
*[other] položek *[other] položek
} z koše } z koše
emptying-trash = Vysypávání koše ({ $progress })... emptying-trash = Vyprazdňování koše ({ $progress })...
emptied-trash = Koš vysypán emptied-trash = Koš vyprázdněn
restoring = restoring =
Obnovování { $items } { $items -> Obnovování { $items } { $items ->
[one] položky [one] položky
@ -403,7 +403,7 @@ sort-largest-to-smallest = Od největšího po nejmenší
gallery-preview = Náhled galerie gallery-preview = Náhled galerie
sort = Řazení sort = Řazení
sort-a-z = A-Z 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 type-to-search-select = Vybere první shodující se soubor nebo složku
pasted-image = Vložený obrázek pasted-image = Vložený obrázek
pasted-text = Vložený text pasted-text = Vložený text
@ -418,3 +418,19 @@ show-recents = Složka „Nedávné“ v postranním panelu
copy-path = Kopírovat cestu copy-path = Kopírovat cestu
clear-recents-history = Vymazat historii „Nedávné“ clear-recents-history = Vymazat historii „Nedávné“
mixed = Různé 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
checksum = Kontrolní součet { $kind }
calculate = Vypočítat
error = Chyba
search-application = Hledat podle názvu aplikace
sidebar-label = Popisek v postranním panelu
change-sidebar-label = Změnit popisek

View file

@ -1,35 +1,43 @@
cosmic-files = COSMIC Filer cosmic-files = COSMIC Filhåndtering
empty-folder = Tom mappe empty-folder = Tom mappe
empty-folder-hidden = Tom mappe (har skjulte filer) empty-folder-hidden = Tom mappe (har skjulte emner)
no-results = Ingen resultater no-results = Fandt ingen resultater
filesystem = Filsystem filesystem = Filsystem
home = Hjem home = Hjem
networks = Netværk networks = Netværk
notification-in-progress = Filoperationer er igangværende. notification-in-progress = Fil-operationer er igang
trash = Skraldespand trash = Papirkurv
recents = Seneste recents = Seneste
undo = Fortryd undo = Fortryd
today = I dag today = I dag
# Desktop view options # Desktop view options
desktop-view-options = Indstillinger for skrivebordsudseende... desktop-view-options = Valgmuligheder for skrivebordsvisning...
show-on-desktop = Vis på Skrivebordet show-on-desktop = Vis på Skrivebord
desktop-folder-content = Indhold på Skrivebordet desktop-folder-content = Skrivebords-mappeindhold
mounted-drives = Monterede drev mounted-drives = Monterede drev
trash-folder-icon = Skraldespandsikon trash-folder-icon = Papirkurv-ikon
icon-size-and-spacing = Ikonstørrelse og afstand icon-size-and-spacing = Ikonstørrelse og -afstand
icon-size = Ikonstørrelse icon-size = Ikonstørrelse
# List view # List view
name = Navn name = Navn
modified = Ændret modified = Ændret
trashed-on = Smidt ud trashed-on = Kasseret
size = Størrelse size = Størrelse
# Progress footer # Progress footer
details = Detaljer details = Detaljer
dismiss = Afvis besked dismiss = Luk meddelelse
operations-running = { $running } operationer er i gang ({ $percent }%)... operations-running =
operations-running-finished = { $running } operationer er i gang ({ $percent }%), { $finished } færdiggjort... { $running } { $running ->
[one] operation
*[other] operationer
} kører ({ $percent }%)...
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operationer
} kører ({ $percent }%), { $finished } færdig...
pause = Pause pause = Pause
resume = Fortsæt resume = Genoptag
# Dialogs # Dialogs
@ -40,12 +48,12 @@ create-archive = Opret arkiv
## Empty Trash Dialog ## Empty Trash Dialog
empty-trash = Tøm skraldespand empty-trash = Tøm papirkurv
empty-trash-warning = Er du sikker på du vil slette alle objekter i Skraldespanden permanent? empty-trash-warning = Emner i Papirkurv-mappen vil blive slettet permanent
## Mount Error Dialog ## Mount Error Dialog
mount-error = Kan ikke tilgå drev mount-error = Ude af stand til at tilgå drev
## New File/Folder Dialog ## New File/Folder Dialog
@ -53,22 +61,22 @@ create-new-file = Opret ny fil
create-new-folder = Opret ny mappe create-new-folder = Opret ny mappe
file-name = Filnavn file-name = Filnavn
folder-name = Mappenavn folder-name = Mappenavn
file-already-exists = En fil med det navn eksisterer allerede. file-already-exists = En fil med det navn eksisterer allerede
folder-already-exists = En mappe med det navn eksisterer allerede. folder-already-exists = En mappe med det navn eksisterer allerede
name-hidden = Navne begyndende med "." vil blive skjult. name-hidden = Navne begyndende med "." vil være skjulte
name-invalid = Navn kan ikke være "{ $filename }". name-invalid = Navn må ikke være "{ $filename }"
name-no-slashes = Navn kan ikke indeholde skråstreg. name-no-slashes = Navn må ikke indeholde skråstreger
## Open/Save Dialog ## Open/Save Dialog
cancel = Annullér cancel = Afbryd
create = Opret create = Opret
open = Åbn open = Åbn
open-file = Åbn fil open-file = Åbn fil
open-folder = Åbn mappe open-folder = Åbn mappe
open-in-new-tab = Åbn i ny fane open-in-new-tab = Åbn i nyt faneblad
open-in-new-window = Åbn i nyt vindue open-in-new-window = Åbn i nyt vindue
open-item-location = Åbn placering for objekt open-item-location = Åbn emne-placering
open-multiple-files = Åbn flere filer open-multiple-files = Åbn flere filer
open-multiple-folders = Åbn flere mapper open-multiple-folders = Åbn flere mapper
save = Gem save = Gem
@ -87,20 +95,20 @@ rename-folder = Omdøb mappe
## Replace Dialog ## Replace Dialog
replace = Erstat replace = Erstat
replace-title = "{ $filename }" eksisterer allerede på denne placering. replace-title = "{ $filename }" eksisterer allerede i denne placering
replace-warning = Vil du erstatte den med den du er ved at gemme? Hvis du erstatter den, overskriver du dens indhold. replace-warning = Ønsker du at erstatte den med den, du er ved at gemme? Erstatning af den vil overskrive dens indhold.
replace-warning-operation = Vil du erstatte den? Hvis du erstatter den, overskriver du dens indhold. replace-warning-operation = Ønsker du at erstatte den? Erstatning af den vil overskrive dens indhold.
original-file = Original fil original-file = Original fil
replace-with = Erstat med replace-with = Erstat med
apply-to-all = Anvend for alle apply-to-all = Anvend på alle
keep-both = Behold begge keep-both = Behold begge
skip = Spring over skip = Spring over
## Set as Executable and Launch Dialog ## Set as Executable and Launch Dialog
set-executable-and-launch = Indstil som eksekverbar fil og start set-executable-and-launch = Angiv som eksekverbar og start
set-executable-and-launch-description = Vil du indstille "{ $name }" som en eksekverbar fil og starte den? set-executable-and-launch-description = Vil du angive "{ $name }" som eksekverbar og starte den?
set-and-launch = Indstil og start set-and-launch = Angiv og start
## Metadata Dialog ## Metadata Dialog
@ -239,13 +247,13 @@ settings = Indstillinger
appearance = Udseende appearance = Udseende
theme = Tema theme = Tema
match-desktop = Match skrivebord match-desktop = Følg skrivebordets indstillinger
dark = Mørk dark = Mørkt
light = Lys light = Lyst
# Context menu # Context menu
add-to-sidebar = Tilføj til sidebjælke add-to-sidebar = Tilføj til sidebjælke
compress = Komprimér compress = Komprimer…
extract-here = Extract extract-here = Uddrag
new-file = Ny fil... new-file = Ny fil...
new-folder = Ny mappe... new-folder = Ny mappe...
open-in-terminal = Åbn i terminal open-in-terminal = Åbn i terminal
@ -280,15 +288,15 @@ quit = Afslut
edit = Redigér edit = Redigér
cut = Klip cut = Klip
copy = Kopiér copy = Kopiér
paste = Sæt ind paste = Indsæt
select-all = Vælg alt select-all = Vælg alle
## View ## View
zoom-in = Zoom ind zoom-in = Zoom ind
default-size = Standardstørrelse default-size = Standardstørrelse
zoom-out = Zoom ud zoom-out = Zoom ud
view = Visning view = Vis
grid-view = Gitter-visning grid-view = Gitter-visning
list-view = Liste-visning list-view = Liste-visning
show-hidden-files = Vis skjulte filer show-hidden-files = Vis skjulte filer
@ -306,3 +314,102 @@ sort-newest-first = Nyeste først
sort-oldest-first = Ældste først sort-oldest-first = Ældste først
sort-smallest-to-largest = Mindste til største sort-smallest-to-largest = Mindste til største
sort-largest-to-smallest = Største til mindste sort-largest-to-smallest = Største til mindste
delete = Slet
run = Kør
repository = Pakkearkiv
grid-spacing = Gitter-afstand
extract-password-required = Adgangskode påkrævet
extract-to = Udpak Til...
extract-to-title = Udpak til mappe
empty-trash-title = Tøm papirkurv?
keywords = Mappe;Håndtering;
comment = Filhåndteringsprogram til COSMIC-skrivebordet
copy-to-title = Vælg kopi destination
copy-to-button-label = Kopier
move-to-title = Vælg flytte destination
move-to-button-label = Flyt
deleted =
Slettede { $items } { $items ->
[one] element
*[other] elementer
} fra { trash }
removing-from-recents =
Fjerner { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
type-to-search-enter-path = Indtast stien til mappen eller filen
permanently-deleting =
Sletter permanent { $items } { $items ->
[one] element
*[other] elementer
}
execute-only = Kun kørsel
removed-from-recents =
Fjernede { $items } { $items ->
[one] element
*[other] elementer
} fra { recents }
permanently-deleted =
Slettede permanent { $items } { $items ->
[one] element
*[other] elementer
}
write-only = Kun skrivning
remove = Fjern
context-action = Konteksthandling
context-action-confirm-title = Kør “{ $name }”?
context-action-confirm-warning =
Dette vil køre på { $items } { $items ->
[one] item
*[other] items
}.
selected-items = De { $items } valgte elementer
mixed = Blandet
none = Ingen
write-execute = Skriv og kør
read-only = Kun læsning
read-execute = Læs og kør
read-write = Læs og skriv
read-write-execute = Læs, skriv og kør
keep = Behold
support = Support
progress-failed = { $percent } %, mislykkedes
pasted-image = Indsat billede
pasted-text = Indsat tekst
pasted-video = Indsat video
deleting =
Sletter { $items } { $items ->
[one] element
*[other] elementer
} fra { trash } ({ $progress })...
setting-permissions = Indstiller tilladelser for “{ $name }” til { $mode }
set-permissions = Sæt tilladelser for “{ $name }” til { $mode }
show-recents = Seneste-mappe i sidepanelet
type-to-search = Skriv for at søge
type-to-search-recursive = Søger i den aktuelle mappe og alle undermapper
type-to-search-select = Vælger den første matchende fil eller mappe
clear-recents-history = Ryd seneste historik
copy-to = Kopiér til…
delete-permanently = Slet permanent
eject = Skub ud
move-to = Flyt til…
remove-from-recents = Fjern fra seneste
reload-folder = Genindlæs mappe
copy-path = Kopiér sti
rename-confirm = Omdøb
calculate = Udregn
error = Fejl
checksum = { $kind }-kontrolsum
other-apps = Andre applikationer
related-apps = Relaterede applikationer
permanently-delete-question = Slet permanent?
permanently-delete-warning = { $target } vil blive slettet permanent. Denne handling kan ikke fortrydes.
open-with = Åbn med
favorite-path-error = Fejl ved åbning af sti
favorite-path-error-description =
Ude af stand til at åbne "{ $path }"
"{ $path }" Eksisterer muligvis ikke, eller du har måske ikketilladelse til at åbne den
Ønsker du at fjerne den fra sidebjælken?
single-click = Enkelt klik for at åbne

View file

@ -11,7 +11,7 @@ recents = Zuletzt verwendet
undo = Rückgängig undo = Rückgängig
today = Heute today = Heute
# Optionen für die Desktop-Ansicht # Optionen für die Desktop-Ansicht
desktop-view-options = Optionen für die Desktop-Ansicht… desktop-view-options = Optionen für die Desktop-Ansicht ...
show-on-desktop = Auf Desktop anzeigen show-on-desktop = Auf Desktop anzeigen
desktop-folder-content = Inhalt des Desktop-Ordners desktop-folder-content = Inhalt des Desktop-Ordners
mounted-drives = Eingehängte Laufwerke mounted-drives = Eingehängte Laufwerke
@ -31,12 +31,12 @@ operations-running =
{ $running } { $running -> { $running } { $running ->
[one] laufender Vorgang [one] laufender Vorgang
*[other] laufende Vorgänge *[other] laufende Vorgänge
} ({ $percent } %)... } ({ $percent } %) ...
operations-running-finished = operations-running-finished =
{ $running } { $running -> { $running } { $running ->
[one] laufender Vorgang [one] laufender Vorgang
*[other] laufende Vorgänge *[other] laufende Vorgänge
} ({ $percent } %), { $finished } abgeschlossen... } ({ $percent } %), { $finished } abgeschlossen ...
pause = Pause pause = Pause
resume = Fortsetzen resume = Fortsetzen
@ -50,7 +50,7 @@ create-archive = Archiv erstellen
## Entpacken-Dialog ## Entpacken-Dialog
extract-password-required = Passwort erforderlich extract-password-required = Passwort erforderlich
extract-to = Entpacken nach... extract-to = Entpacken nach ...
extract-to-title = In Ordner entpacken extract-to-title = In Ordner entpacken
## Dialog zum Leeren des Papierkorbs ## Dialog zum Leeren des Papierkorbs
@ -179,7 +179,7 @@ keep = Behalten
add-network-drive = Netzlaufwerk hinzufügen add-network-drive = Netzlaufwerk hinzufügen
connect = Verbinden connect = Verbinden
connect-anonymously = Anonym verbinden connect-anonymously = Anonym verbinden
connecting = Wird verbunden... connecting = Wird verbunden ...
domain = Domain domain = Domain
enter-server-address = Serveradresse eingeben enter-server-address = Serveradresse eingeben
network-drive-description = network-drive-description =
@ -218,7 +218,7 @@ compressing =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress })... } von „{ $from }“ nach „{ $to }“ komprimiert ({ $progress }) ...
compressed = compressed =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -231,7 +231,7 @@ copying =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ kopiert ({ $progress })... } von „{ $from }“ nach „{ $to }“ kopiert ({ $progress }) ...
copied = copied =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -241,19 +241,19 @@ deleting =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} aus dem { trash } gelöscht ({ $progress })... } aus dem { trash } gelöscht ({ $progress }) ...
deleted = deleted =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
*[other] Elemente wurden *[other] Elemente wurden
} aus dem { trash } gelöscht } aus dem { trash } gelöscht
emptying-trash = { trash } wird geleert ({ $progress })... emptying-trash = { trash } wird geleert ({ $progress }) ...
emptied-trash = { trash } geleert emptied-trash = { trash } geleert
extracting = extracting =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ entpackt ({ $progress })... } von „{ $from }“ nach „{ $to }“ entpackt ({ $progress }) ...
extracted = extracted =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -267,7 +267,7 @@ moving =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} von „{ $from }“ nach „{ $to }“ verschoben ({ $progress })... } von „{ $from }“ nach „{ $to }“ verschoben ({ $progress }) ...
moved = moved =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -289,7 +289,7 @@ restoring =
{ $items } { $items -> { $items } { $items ->
[one] Element wird [one] Element wird
*[other] Elemente werden *[other] Elemente werden
} aus dem { trash } wiederhergestellt ({ $progress })... } aus dem { trash } wiederhergestellt ({ $progress }) ...
restored = restored =
{ $items } { $items -> { $items } { $items ->
[one] Element wurde [one] Element wurde
@ -299,7 +299,7 @@ unknown-folder = unbekannter Ordner
## Öffnen mit ## Öffnen mit
menu-open-with = Öffnen mit... menu-open-with = Öffnen mit ...
default-app = { $name } (Standard) default-app = { $name } (Standard)
## Details anzeigen ## Details anzeigen
@ -311,7 +311,7 @@ item-size = Größe: { $size }
item-created = Erstellt: { $created } item-created = Erstellt: { $created }
item-modified = Geändert: { $modified } item-modified = Geändert: { $modified }
item-accessed = Zugegriffen: { $accessed } item-accessed = Zugegriffen: { $accessed }
calculating = Wird berechnet... calculating = Wird berechnet ...
## Einstellungen ## Einstellungen
@ -333,15 +333,15 @@ type-to-search-recursive = Durchsucht den aktuellen Ordner und alle Unterordner
type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein
# Kontextmenü # Kontextmenü
add-to-sidebar = Zur Seitenleiste hinzufügen add-to-sidebar = Zur Seitenleiste hinzufügen
compress = Komprimieren... compress = Komprimieren ...
delete-permanently = Endgültig löschen delete-permanently = Endgültig löschen
extract-here = Entpacken extract-here = Entpacken
new-file = Neue Datei... new-file = Neue Datei ...
new-folder = Neuer Ordner... new-folder = Neuer Ordner ...
open-in-terminal = Im Terminal öffnen open-in-terminal = Im Terminal öffnen
move-to-trash = In den Papierkorb verschieben move-to-trash = In den Papierkorb verschieben
restore-from-trash = Aus dem Papierkorb wiederherstellen restore-from-trash = Aus dem Papierkorb wiederherstellen
remove-from-sidebar = Von der Seitenleiste entfernen remove-from-sidebar = Aus der Seitenleiste entfernen
sort-by-name = Nach Name sortieren sort-by-name = Nach Name sortieren
sort-by-modified = Nach Änderung sortieren sort-by-modified = Nach Änderung sortieren
sort-by-size = Nach Größe sortieren sort-by-size = Nach Größe sortieren
@ -349,9 +349,9 @@ sort-by-trashed = Nach Löschzeitpunkt sortieren
## Desktop ## Desktop
change-wallpaper = Hintergrundbild ändern... change-wallpaper = Hintergrundbild ändern ...
desktop-appearance = Desktop-Aussehen... desktop-appearance = Desktop-Aussehen ...
display-settings = Anzeigeeinstellungen... display-settings = Anzeigeeinstellungen ...
# Menü # Menü
@ -362,7 +362,7 @@ file = Datei
new-tab = Neuer Tab new-tab = Neuer Tab
new-window = Neues Fenster new-window = Neues Fenster
reload-folder = Ordner neu laden reload-folder = Ordner neu laden
rename = Umbenennen... rename = Umbenennen ...
close-tab = Tab schließen close-tab = Tab schließen
quit = Beenden quit = Beenden
@ -385,8 +385,8 @@ list-view = Listenansicht
show-hidden-files = Versteckte Dateien anzeigen show-hidden-files = Versteckte Dateien anzeigen
list-directories-first = Verzeichnisse zuerst auflisten list-directories-first = Verzeichnisse zuerst auflisten
gallery-preview = Galerie-Vorschau gallery-preview = Galerie-Vorschau
menu-settings = Einstellungen... menu-settings = Einstellungen ...
menu-about = Über COSMIC Dateien... menu-about = Über COSMIC Dateien ...
## Sortieren ## Sortieren
@ -397,7 +397,7 @@ sort-newest-first = Neueste zuerst
sort-oldest-first = Älteste zuerst sort-oldest-first = Älteste zuerst
sort-smallest-to-largest = Von klein nach groß sort-smallest-to-largest = Von klein nach groß
sort-largest-to-smallest = Von groß nach klein sort-largest-to-smallest = Von groß nach klein
repository = Repository repository = Repositorium
empty-trash-title = Papierkorb leeren? empty-trash-title = Papierkorb leeren?
copy-to-title = Kopierziel auswählen copy-to-title = Kopierziel auswählen
copy-to-button-label = Kopieren copy-to-button-label = Kopieren
@ -418,7 +418,7 @@ type-to-search-select = Wählt die erste übereinstimmende Datei oder den ersten
pasted-image = Eingefügtes Bild pasted-image = Eingefügtes Bild
pasted-text = Eingefügter Text pasted-text = Eingefügter Text
pasted-video = Eingefügtes Video pasted-video = Eingefügtes Video
copy-to = Kopieren nach... copy-to = Kopieren nach ...
show-recents = Ordner zuletzt verwendeter Elemente in der Seitenleiste show-recents = Ordner zuletzt verwendeter Elemente in der Seitenleiste
clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren
comment = Dateimanager für den COSMIC Desktop comment = Dateimanager für den COSMIC Desktop
@ -426,6 +426,20 @@ keywords = Ordner;Manager;
move-to-button-label = Verschieben move-to-button-label = Verschieben
move-to-title = Verschiebeziel auswählen move-to-title = Verschiebeziel auswählen
remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen
move-to = Verschieben nach... move-to = Verschieben nach ...
copy-path = Pfad kopieren copy-path = Pfad kopieren
mixed = Gemischt 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
checksum = { $kind } Prüfsumme
calculate = Berechnen
error = Fehler
sidebar-label = Bezeichnung in der Seitenleiste
search-application = Nach App-Name suchen

View file

@ -1,15 +1,336 @@
empty-folder = Άδειος φάκελος empty-folder = Άδειος φάκελος
no-results = Δεν βρέθηκαν αποτελέσματα no-results = Δεν βρέθηκαν αποτελέσματα
trash = Κάδος Ανακύκλωσης trash = Απορρίμματα
recents = Πρόσφατα recents = Πρόσφατα
cosmic-files = COSMIC Αρχεία cosmic-files = Αρχεία COSMIC
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά αντικείμενα) empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά στοιχεία)
filesystem = Σύστημα αρχείων filesystem = Σύστημα αρχείων
home = Προσωπικός φάκελος home = Προσωπικός φάκελος
networks = Δίκτυο networks = Δίκτυα
comment = Αρχεία για το COSMIC περιβάλλον comment = Διαχείριση αρχείων για το περιβάλλον επιφάνειας εργασίας COSMIC
keywords = Φάκελος;Διαχειριστής; keywords = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager;
rename = Μετονομασία... rename = Μετονομασία...
close-tab = Κλείσιμο καρτέλας close-tab = Κλείσιμο καρτέλας
light = Φωτεινό light = Ανοιχτόχρωμο
dark = Σκοτεινό 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 = Μετονομασία
checksum = Άθροισμα ελέγχου { $kind }
calculate = Υπολογισμός
error = Σφάλμα
sidebar-label = Ετικέτα πλαϊνής στήλης
search-application = Αναζήτηση με όνομα εφαρμογής
change-sidebar-label = Αλλαγή ετικέτας πλαϊνής στήλης

View file

@ -0,0 +1,100 @@
save = Save
quit = Quit
name = Name
delete = Delete
cancel = Cancel
run = Run
new-window = New window
theme = Theme
appearance = Appearance
light = Light
repository = Repository
support = Support
dark = Dark
match-desktop = Match desktop
settings = Settings
replace = Replace
file = File
zoom-in = Zoom in
select-all = Select all
zoom-out = Zoom out
default-size = Default size
edit = Edit
copy = Copy
paste = Paste
view = View
undo = Undo
cut = Cut
icon-size-and-spacing = Icon size and spacing
cosmic-files = COSMIC Files
trash = Rubbish
empty-folder-hidden = Empty folder (has hidden items)
no-results = No results found
empty-folder = Empty folder
menu-settings = Settings...
recents = Recents
mounted-drives = Mounted drives
filesystem = Filesystem
home = Home
desktop-view-options = Desktop view options...
show-on-desktop = Show on Desktop
trash-folder-icon = Bin folder icon
desktop-folder-content = Desktop folder content
networks = Networks
today = Today
notification-in-progress = File operations are in progress
comment = File manager for the COSMIC desktop
keywords = Folder;Manager;
size = Size
dismiss = Dismiss message
trashed-on = Binned
create-archive = Create archive
pause = Pause
resume = Resume
grid-spacing = Grid spacing
details = Details
operations-running-finished =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%), { $finished } finished...
icon-size = Icon size
modified = Modified
operations-running =
{ $running } { $running ->
[one] operation
*[other] operations
} running ({ $percent }%)...
copy-to-title = Select copy destination
open-file = Open file
open-multiple-files = Open multiple files
create-new-folder = Create new folder
create = Create
other-apps = Other applications
open-multiple-folders = Open multiple folders
extract-to = Extract To...
create-new-file = Create new file
open-in-new-window = Open in new window
folder-name = Folder name
browse-store = Browse { $store }
open-item-location = Open item location
mount-error = Unable to access drive
open = Open
extract-to-title = Extract to folder
open-with-title = How do you want to open "{ $name }"?
extract-password-required = Password required
open-folder = Open folder
open-in-new-tab = Open in new tab
file-name = File name
save-file = Save file
file-already-exists = A file with that name already exists
name-hidden = Names starting with "." will be hidden
folder-already-exists = A folder with that name already exists
empty-trash = Empty bin
empty-trash-title = Empty bin?
name-no-slashes = Name cannot contain slashes
name-invalid = Name cannot be "{ $filename }"
copy-to-button-label = Copy
move-to-title = Select move destination
move-to-button-label = Move
context-action = Context action

View file

@ -96,6 +96,7 @@ save-file = Save file
## Open With Dialog ## Open With Dialog
open-with-title = How do you want to open "{$name}"? 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} browse-store = Browse {$store}
other-apps = Other applications other-apps = Other applications
related-apps = Related applications related-apps = Related applications
@ -116,6 +117,10 @@ permanently-delete-warning = {$target} will be permanently deleted. This action
## Rename Dialog ## Rename Dialog
rename-file = Rename file rename-file = Rename file
rename-folder = Rename folder rename-folder = Rename folder
rename-confirm = Rename
## Change Sidebar Label Dialog
sidebar-label = Sidebar label
## Replace Dialog ## Replace Dialog
replace = Replace replace = Replace
@ -138,6 +143,11 @@ open-with = Open with
owner = Owner owner = Owner
group = Group group = Group
other = Other 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 mixed = Mixed
### Mode 0 ### Mode 0
none = None none = None
@ -292,6 +302,7 @@ unknown-folder = unknown folder
## Open with ## Open with
menu-open-with = Open with... menu-open-with = Open with...
default-app = {$name} (default) default-app = {$name} (default)
search-application = Search by app name
## Show details ## Show details
show-details = Show details show-details = Show details
@ -302,6 +313,9 @@ item-created = Created: {$created}
item-modified = Modified: {$modified} item-modified = Modified: {$modified}
item-accessed = Accessed: {$accessed} item-accessed = Accessed: {$accessed}
calculating = Calculating... calculating = Calculating...
checksum = {$kind} checksum
calculate = Calculate
error = Error
## Settings ## Settings
settings = Settings settings = Settings
@ -332,10 +346,12 @@ extract-here = Extract
new-file = New file... new-file = New file...
new-folder = New folder... new-folder = New folder...
open-in-terminal = Open in terminal open-in-terminal = Open in terminal
edit-desktop-entry = Edit desktop entry
move-to = Move to... move-to = Move to...
move-to-trash = Move to trash move-to-trash = Move to trash
restore-from-trash = Restore from trash restore-from-trash = Restore from trash
remove-from-sidebar = Remove from sidebar remove-from-sidebar = Remove from sidebar
change-sidebar-label = Change sidebar label
sort-by-name = Sort by name sort-by-name = Sort by name
sort-by-modified = Sort by modified sort-by-modified = Sort by modified
sort-by-size = Sort by size sort-by-size = Sort by size

View file

@ -345,3 +345,4 @@ sort-newest-first = جدیدترین در ابتدا
sort-oldest-first = قدیمی‌ترین در ابتدا sort-oldest-first = قدیمی‌ترین در ابتدا
sort-smallest-to-largest = کوچک‌ترین به بزرگ‌ترین sort-smallest-to-largest = کوچک‌ترین به بزرگ‌ترین
sort-largest-to-smallest = بزرگ‌ترین به کوچک‌ترین sort-largest-to-smallest = بزرگ‌ترین به کوچک‌ترین
run = اجرا

View file

@ -225,7 +225,7 @@ settings = Asetukset
appearance = Ulkoasu appearance = Ulkoasu
theme = Teema theme = Teema
match-desktop = Sovita työpöytään match-desktop = Sama kuin työpöytä
dark = Tumma dark = Tumma
light = Vaalea light = Vaalea
@ -405,3 +405,19 @@ removed-from-recents =
[one] kohde [one] kohde
*[other] kohdetta *[other] kohdetta
} viimeaikaisista } 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
checksum = { $kind }-tarkistussumma
calculate = Laske
error = Virhe
search-application = Hae sovelluksen nimellä
sidebar-label = Sivupalkin nimike
change-sidebar-label = Muuta sivupalkin nimikettä

View file

View file

@ -80,18 +80,19 @@ cancel = Annuler
create = Créer create = Créer
open = Ouvrir open = Ouvrir
open-file = Ouvrir le fichier open-file = Ouvrir le fichier
open-folder = Ouvrir dossier open-folder = Ouvrir le dossier
open-in-new-tab = Ouvrir dans un nouvel onglet open-in-new-tab = Ouvrir dans un nouvel onglet
open-in-new-window = Ouvrir dans une nouvelle fenêtre open-in-new-window = Ouvrir dans une nouvelle fenêtre
open-item-location = Ouvrir l'emplacement de l'élément open-item-location = Ouvrir l'emplacement de l'élément
open-multiple-files = Ouvrir plusieurs fichiers open-multiple-files = Ouvrir plusieurs fichiers
open-multiple-folders = Ouvrir plusieurs dossiers open-multiple-folders = Ouvrir plusieurs dossiers
save = Enregistrer save = Enregistrer
save-file = Enregistrer fichier save-file = Enregistrer le fichier
## Open With Dialog ## Open With Dialog
open-with-title = Comment souhaitez-vous ouvrir "{ $name }" ? 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 } browse-store = Parcourir { $store }
## Permanently delete Dialog ## Permanently delete Dialog
@ -130,6 +131,11 @@ open-with = Ouvrir avec
owner = Propriétaire owner = Propriétaire
group = Groupe group = Groupe
other = Autre 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 ### Mode 0
@ -183,7 +189,7 @@ keep = Conserver
## Add Network Drive ## Add Network Drive
add-network-drive = Ajouter un lecteur réseau add-network-drive = Ajouter un lecteur réseau
connect = Connecter connect = Se connecter
connect-anonymously = Se connecter anonymement connect-anonymously = Se connecter anonymement
connecting = Connexion... connecting = Connexion...
domain = Domaine domain = Domaine
@ -200,8 +206,8 @@ network-drive-schemes =
File Transfer Protocol,ftp:// or ftps:// File Transfer Protocol,ftp:// or ftps://
Network File System,nfs:// Network File System,nfs://
Server Message Block,smb:// Server Message Block,smb://
SSH File Transfer Protocol,sftp:// or ssh:// SSH File Transfer Protocol,sftp:// ou ssh://
WebDav,dav:// or davs:// WebDAV,dav:// ou davs://
network-drive-error = Impossible d'accéder au lecteur réseau network-drive-error = Impossible d'accéder au lecteur réseau
password = Mot de passe password = Mot de passe
remember-password = Se souvenir du mot de passe remember-password = Se souvenir du mot de passe
@ -216,7 +222,7 @@ history = Historique
no-history = Aucun élément dans l'historique. no-history = Aucun élément dans l'historique.
pending = En attente pending = En attente
progress = { $percent }% progress = { $percent }%
progress-cancelled = { $percent }%, annulation progress-cancelled = { $percent }%, annulé
progress-paused = { $percent }%, en pause progress-paused = { $percent }%, en pause
failed = Échoué failed = Échoué
complete = Terminé complete = Terminé
@ -253,7 +259,7 @@ deleted =
[one] élément supprimé [one] élément supprimé
*[other] éléments supprimés *[other] éléments supprimés
} depuis { trash } } depuis { trash }
emptying-trash = { trash } en cours de nettoyage ({ $progress })... emptying-trash = Nettoyage en cours de { trash } ({ $progress })...
emptied-trash = { trash } vidée emptied-trash = { trash } vidée
extracting = extracting =
Extraction de { $items } { $items -> Extraction de { $items } { $items ->
@ -265,7 +271,7 @@ extracted =
[one] élément extrait [one] élément extrait
*[other] éléments extraits *[other] éléments extraits
} depuis "{ $from }" vers "{ $to }" } depuis "{ $from }" vers "{ $to }"
setting-executable-and-launching = Paramétrage de "{ $name }" comme exécutable et prêt à être lancé setting-executable-and-launching = Définition de "{ $name }" comme exécutable et lancement en cours
set-executable-and-launched = Défini "{ $name }" comme exécutable et lancé set-executable-and-launched = Défini "{ $name }" comme exécutable et lancé
moving = moving =
Déplacement de { $items } { $items -> Déplacement de { $items } { $items ->
@ -276,7 +282,7 @@ moved =
{ $items } { $items -> { $items } { $items ->
[one] élément déplacé [one] élément déplacé
*[other] éléments déplacés *[other] éléments déplacés
} de "{ $from }" à "{ $to }" } de "{ $from }" vers "{ $to }"
permanently-deleting = permanently-deleting =
Suppression définitive de { $items } { $items -> Suppression définitive de { $items } { $items ->
[one] élément [one] élément
@ -304,7 +310,7 @@ unknown-folder = Dossier inconnu
## Open with ## Open with
menu-open-with = Ouvrir avec... menu-open-with = Ouvrir avec...
default-app = { $name } (défaut) default-app = { $name } (par défaut)
## Show details ## Show details
@ -343,9 +349,10 @@ extract-here = Extraire
new-file = Nouveau fichier... new-file = Nouveau fichier...
new-folder = Nouveau dossier... new-folder = Nouveau dossier...
open-in-terminal = Ouvrir dans le terminal open-in-terminal = Ouvrir dans le terminal
edit-desktop-entry = Modifier le fichier .desktop
move-to-trash = Déplacer vers la corbeille move-to-trash = Déplacer vers la corbeille
restore-from-trash = Restaurer depuis la corbeille restore-from-trash = Restaurer depuis la corbeille
remove-from-sidebar = Supprimer de la barre latérale remove-from-sidebar = Retirer de la barre latérale
sort-by-name = Trier par nom sort-by-name = Trier par nom
sort-by-modified = Trier par date de modification sort-by-modified = Trier par date de modification
sort-by-size = Trier par taille sort-by-size = Trier par taille
@ -408,11 +415,11 @@ removing-from-recents =
*[other] éléments *[other] éléments
} de { recents } } de { recents }
other-apps = Autres applications other-apps = Autres applications
set-permissions = Définir les permissions pour "{ $name }" à { $mode } set-permissions = Permissions définies pour "{ $name }" à { $mode }
repository = Dépôt repository = Dépôt
support = Support support = Support
eject = Éjecter eject = Éjecter
remove-from-recents = Supprimer des récents remove-from-recents = Retirer des récents
empty-trash-title = Vider la corbeille ? empty-trash-title = Vider la corbeille ?
setting-permissions = Définition des permissions pour "{ $name }" à { $mode } setting-permissions = Définition des permissions pour "{ $name }" à { $mode }
removed-from-recents = removed-from-recents =
@ -421,9 +428,9 @@ removed-from-recents =
*[other] éléments supprimés *[other] éléments supprimés
} de { recents } } de { recents }
progress-failed = { $percent }%, échec progress-failed = { $percent }%, échec
type-to-search-select = Sélectionne le premier fichier ou dossier qui convient type-to-search-select = Sélectionne le premier fichier ou dossier correspondant
pasted-image = Image collée pasted-image = Image collée
pasted-text = Text collé pasted-text = Texte collé
pasted-video = Vidéo collée pasted-video = Vidéo collée
copy-to-title = Sélectionner la destination de la copie copy-to-title = Sélectionner la destination de la copie
copy-to-button-label = Copier copy-to-button-label = Copier
@ -437,3 +444,18 @@ show-recents = Dossier Récents dans la barre latérale
copy-path = Copier le chemin copy-path = Copier le chemin
clear-recents-history = Effacer l'historique des Récents clear-recents-history = Effacer l'historique des Récents
mixed = Mixte 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
calculate = Calculer
error = Erreur
checksum = Somme de contrôle { $kind }
sidebar-label = Libellé de la barre latérale
search-application = Chercher par nom d'appli
change-sidebar-label = Changer le libellé de la barre latérale

View file

@ -307,7 +307,7 @@ single-click = Cliceáil amháin le hoscailt
### Appearance ### Appearance
appearance = Cuma appearance = Dealramh
theme = Téama theme = Téama
match-desktop = Meaitseáil deasc match-desktop = Meaitseáil deasc
dark = Dorcha dark = Dorcha
@ -434,3 +434,15 @@ show-recents = Fillteán le déanaí sa bharra taoibh
clear-recents-history = Glan stair na n-earraí le déanaí clear-recents-history = Glan stair na n-earraí le déanaí
copy-path = Cóipeáil an chosán copy-path = Cóipeáil an chosán
mixed = Measctha 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
checksum = { $kind } suim sheiceála
calculate = Ríomh
error = Earráid

333
i18n/gl/cosmic_files.ftl Normal file
View file

@ -0,0 +1,333 @@
name = Nome
save = Gardar
cancel = Cancelar
run = Executar
delete = Eliminar
repository = Repositorio
support = Soporte
settings = Axustes
appearance = Apariencia
theme = Tema
match-desktop = Axustar ao escritorio
dark = Escuro
light = Claro
replace = Remplazar
file = Ficheiro
new-window = Nova xanela
quit = Pechar
edit = Editar
undo = Desfacer
cut = Cortar
copy = Copiar
paste = Pegar
select-all = Seleccionar todo
view = Ver
zoom-in = Aumentar Zoom
default-size = Tamaño por defecto
zoom-out = Afastar Zoom
menu-settings = Axustes...
cosmic-files = Ficheiros COSMIC
comment = Xestor de ficheiros para o escritorio COSMIC
keywords = Cartafol;Xestor;
empty-folder = Cartafol baleiro
empty-folder-hidden = Cartafol baleiro (contén elementos agochados)
no-results = Non se atoparon resultados
filesystem = Sistema de ficheiros
home = Inicio
networks = Redes
notification-in-progress = Operacións de ficheiros en progreso
trash = Lixo
recents = Recentes
today = Hoxe
desktop-view-options = Axustes da vista do escritorio…
show-on-desktop = Amosar no Escritorio
desktop-folder-content = Contido do cartafol Escritorio
mounted-drives = Unidades montadas
trash-folder-icon = Icona do cartafol do Lixo
icon-size-and-spacing = Tamaño e espazado das iconas
icon-size = Tamaño de icona
grid-spacing = Espazado da grella
modified = Modificado
trashed-on = No lixo
size = Tamaño
details = Detalles
dismiss = Descartar a mensaxe
operations-running =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%)...
operations-running-finished =
{ $running } { $executandose ->
[unha] operacion
*[outra] operacions
} executandose ({ $percent }%), { $finished } rematadas...
pause = Pausa
resume = Retomar
create-archive = Crear arquivo comprimido
copy-to-title = Seleccionar o destino da copia
copy-to-button-label = Copiar
extract-password-required = Contrasinal necesario
extract-to = Extraer en…
extract-to-title = Extraer a cartafol
empty-trash = Baleirar Lixo
empty-trash-title = Baleirar o lixo?
empty-trash-warning = Os elementos do cartafol do lixo eliminaranse de forma permanente
mount-error = Non é posible acceder a unidade
move-to-title = Seleccionar o destino do movemento
move-to-button-label = Mover
create-new-file = Crear un novo ficheiro
create-new-folder = Crear novo cartafol
file-name = Nome do ficheiro
folder-name = Nome do cartafol
file-already-exists = Existe un ficheiro co mesmo nome
folder-already-exists = Existe un cartafol co mesmo nome
name-hidden = Os nomes que comecen por "." agocharanse
name-invalid = O nome non pode ser "{ $filename }"
name-no-slashes = O nome non pode conter barras
create = Crear
open = Abrir
open-file = Abrir ficheiro
open-folder = Abrir cartafol
open-in-new-tab = Abrir nunha nova lapela
open-in-new-window = Abrir nunha nova xanela
open-item-location = Abrir ubicación do ítem
open-multiple-files = Abrir multiples ficheiros
open-multiple-folders = Abrir multiples cartafoles
save-file = Gardar ficheiro
open-with-title = Como queres abrir «{ $name }»?
browse-store = Atopar { $store }
other-apps = Outras aplicacións
related-apps = Aplicacións relacionadas
context-action = Acción contextual
context-action-confirm-title = Executar "{ $name }"?
context-action-confirm-warning =
Esto vaise executar en { $items } { $items ->
[un] item
*[outro] items
}.
selected-items = Os { $items } elementos seleccionados
permanently-delete-question = Eliminar permanentemente?
permanently-delete-warning = { $target } será eliminado permanentemente. Esta acción non se poderá desfacer.
rename-file = Renomear ficheiro
rename-folder = Renomear cartafol
rename-confirm = Renomear
replace-title = "{ $filename }" xa existe nesta ubicación
replace-warning = Queres substituílo polo que estás a gardar? Ao substituílo sobrescribirase o seu contido.
replace-warning-operation = Queres substituílo? Ao substituílo sobrescribirase o seu contido.
original-file = Ficheiro orixinal
replace-with = Remplazar con
apply-to-all = Aplicar a todo
keep-both = Manter ambos
skip = Saltar
set-executable-and-launch = Marcar como executable e lanzalo
set-executable-and-launch-description = Queres marcar «{ $name }» como executábel e lanzalo?
set-and-launch = Marcar e executar
open-with = Abrir con
owner = Propietario
group = Grupo
other = Outro
mixed = Mesturar
none = Ningún
execute-only = Só execución
write-only = Só escritura
write-execute = Escritura e execución
read-only = Só lectura
read-execute = Lectura e execución
read-write = Lectura e escritura
read-write-execute = Lectura, escritura e execución
favorite-path-error = Erro abrindo o directorio
favorite-path-error-description =
Non foi posíbel abrir «{ $path }».
É posíbel que «{ $path }» non exista ou que non teñas permisos para abrilo.
Queres retiralo da barra lateral?
remove = Eliminar
keep = Manter
add-network-drive = Engadir unidade de rede
connect = Conectar
connect-anonymously = Conectar de xeito anonimo
connecting = Conectando...
domain = Dominio
enter-server-address = Introducir a dirección do servidor
network-drive-description =
Os enderezos dos servidores inclúen un prefixo de protocolo e un enderezo.
Exemplos: ssh://192.168.0.1, ftp://[2001:db8::1]
network-drive-schemes =
Protocolos dispoñíbeis,Prefixo
AppleTalk,afp://
Protocolo de transferencia de ficheiros,ftp:// ou ftps://
Sistema de ficheiros en rede,nfs://
Server Message Block,smb://
Protocolo de transferencia de ficheiros por SSH,sftp:// ou ssh://
WebDAV,dav:// ou davs://
network-drive-error = Non foi posible acceder a unidade de rede
password = Contrasinal
remember-password = Recordar o contrasinal
try-again = Voltar a intentar
username = Nome de usuario
cancelled = Cancelado
edit-history = Editar historial
history = Historial
no-history = Non hai elementos no historial.
pending = Pendente
progress = { $percent }%
progress-cancelled = { $percent }%, cancelado
progress-failed = { $percent }%, fallou
progress-paused = { $percent }%, pausado
failed = Fallou
complete = Completado
compressing =
Comprimindo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
compressed =
Comprimidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
copy_noun = Copiar
pasted-image = Imaxe pegada
pasted-text = Texto Pegado
pasted-video = Video pegado
creating = Creando "{ $name }" en "{ $parent }"
created = Creado "{ $name }" en "{ $parent }"
copying =
Copiando { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
copied =
Copiados { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
deleted =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} do lixo ({ $progress })
emptying-trash = Baleirando { trash } ({ $progress })...
emptied-trash = Baleirado o { trash }
extracting =
Extraendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
extracted =
Extraídos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
setting-executable-and-launching = Marcando «{ $name }» como executábel e executándoo
set-executable-and-launched = «{ $name }» marcouse como executábel e executouse
setting-permissions = Establecendo os permisos de «{ $name }» en { $mode }
set-permissions = Establecéronse os permisos de «{ $name }» en { $mode }
moving =
Movendo { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }» ({ $progress })...
moved =
Movidos { $items } { $items ->
[one] elemento
*[other] elementos
} de «{ $from }» a «{ $to }»
permanently-deleting =
Eliminando permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
permanently-deleted =
Eliminados permanentemente { $items } { $items ->
[one] elemento
*[other] elementos
}
removing-from-recents =
Retirando { $items } { $items ->
[one] elemento
*[other] elementos
} dos { recents }
removed-from-recents =
Retirado { $items } { $items ->
[one] elemento
*[other] elementos
} de { recents }
renaming = Renomeando "{ $from }" a "{ $to }"
renamed = Renomeado "{ $from }" a "{ $to }"
restoring =
Restaurando { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash } ({ $progress })...
restored =
Restaurado { $items } { $items ->
[one] elemento
*[other] elementos
} de { trash }
unknown-folder = cartafol descoñecido
menu-open-with = Abrir con...
default-app = { $name } (por defecto)
show-details = Amosar detalles
type = Tipo: { $mime }
items = Elementos: { $items }
item-size = Tamaño: { $size }
item-created = Creado: { $created }
item-modified = Modificado: { $modified }
item-accessed = Accedido: { $accessed }
calculating = Calculando...
checksum = { $kind } checksum
calculate = Calculado
error = Erro
single-click = Clic único para abrir
show-recents = Cartafol de recentes no panel lateral
type-to-search = Escribe para atopar
type-to-search-recursive = Busca no cartafol actual e en todos os subcartafoles
type-to-search-enter-path = Introduce a ruta do cartafol ou ficheiro
type-to-search-select = Selecciona o primeiro ficheiro ou cartafol coincidente
add-to-sidebar = Engadir o panel lateral
clear-recents-history = Limpar historial de recentes
compress = Comprimir...
copy-to = Copiar a...
delete-permanently = Eliminar permanentemente
eject = Expulsar
extract-here = Extraer
new-file = Novo ficheiro...
new-folder = Novo cartafol...
open-in-terminal = Abrir no terminal
move-to = Mover a...
move-to-trash = Mover ao lixo
restore-from-trash = Recuperar do lixo
remove-from-sidebar = Eliminar do panel lateral
sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por modificación
sort-by-size = Ordenar por tamaño
sort-by-trashed = Ordenar pola data de eliminación
remove-from-recents = Eliminar de recentes
change-wallpaper = Cambiar fondo de pantalla...
desktop-appearance = Apariencia do Escritorio...
display-settings = Axustes de pantalla...
new-tab = Nova lapela
reload-folder = Recargar cartafol
rename = Renomear...
close-tab = Pechar lapela
copy-path = Copiar ruta
grid-view = Vista de grella
list-view = Vista de lista
show-hidden-files = Amosar ficheiros ocultos
list-directories-first = Listar primeiro os directorios
gallery-preview = Vista previa da galería
menu-about = Sobre Ficheiros COSMIC...
sort = Ordenar
sort-a-z = A-Z
sort-z-a = Z-A
sort-newest-first = Novos primeiro
sort-oldest-first = Antigos primeiro
sort-smallest-to-largest = Do máis pequeno ao máis grande
sort-largest-to-smallest = Do máis grande ao máis pequeno
deleting =
Eliminando { $items } { $items ->
[one] elemento
*[other] elementos
} da { trash } ({ $progress })...

View file

@ -5,7 +5,7 @@ no-results = कोई परिणाम नहीं
filesystem = फाइल सिस्टम filesystem = फाइल सिस्टम
home = होम home = होम
networks = नेटवर्क्स networks = नेटवर्क्स
notification-in-progress = फाइल संचालन प्रगति पर है। notification-in-progress = फ़ाइल संचालन प्रगति पर हैं
trash = कचरा trash = कचरा
recents = हाल के recents = हाल के
undo = पूर्ववत करें undo = पूर्ववत करें
@ -33,8 +33,8 @@ create-archive = संग्रह बनाएँ
## Empty Trash Dialog ## Empty Trash Dialog
empty-trash = कचरा खाली करें empty-trash = रद्दी साफ़ करें
empty-trash-warning = क्या आप वाकई कचरे में सभी आइटमों को स्थायी रूप से हटाना चाहते हैं? empty-trash-warning = रद्दी फ़ोल्डर में मौजूद आइटम स्थायी रूप से हटा दिए जाएंगे
## New File/Folder Dialog ## New File/Folder Dialog
@ -42,11 +42,11 @@ create-new-file = नई फाइल बनाएँ
create-new-folder = नया फ़ोल्डर बनाएँ create-new-folder = नया फ़ोल्डर बनाएँ
file-name = फाइल का नाम file-name = फाइल का नाम
folder-name = फ़ोल्डर का नाम folder-name = फ़ोल्डर का नाम
file-already-exists = इसी नाम की फाइल पहले से मौजूद है। file-already-exists = उस नाम की फ़ाइल पहले से मौजूद है
folder-already-exists = इसी नाम का फ़ोल्डर पहले से मौजूद है। folder-already-exists = उस नाम का फ़ोल्डर पहले से मौजूद है
name-hidden = "." से शुरू होने वाले नाम छिपे रहेंगे। name-hidden = "." से शुरू होने वाले नाम छिपे होंगे
name-invalid = नाम "{ $filename }" मान्य नहीं है। name-invalid = नाम "{ $filename }" नहीं हो सकता
name-no-slashes = नाम में स्लैश का उपयोग नहीं किया जा सकता है। name-no-slashes = नाम में स्लैश नहीं हो सकते
## Open/Save Dialog ## Open/Save Dialog
@ -75,8 +75,8 @@ rename-folder = फ़ोल्डर का नाम बदलें
## Replace Dialog ## Replace Dialog
replace = प्रतिस्थापित करें replace = बदलें
replace-title = { $filename } पहले से इस स्थान पर मौजूद है। replace-title = { $filename } पहले से इस स्थान पर मौजूद है
replace-warning = क्या आप इसे प्रतिस्थापित करना चाहते हैं? यदि प्रतिस्थापित किया गया, तो मौजूदा फ़ाइल को ओवरराइट किया जाएगा। replace-warning = क्या आप इसे प्रतिस्थापित करना चाहते हैं? यदि प्रतिस्थापित किया गया, तो मौजूदा फ़ाइल को ओवरराइट किया जाएगा।
replace-warning-operation = क्या आप इसे बदलना चाहते हैं? प्रतिस्थापित करने पर मौजूदा फ़ाइल ओवरराइट हो जाएगी। replace-warning-operation = क्या आप इसे बदलना चाहते हैं? प्रतिस्थापित करने पर मौजूदा फ़ाइल ओवरराइट हो जाएगी।
original-file = मूल फ़ाइल original-file = मूल फ़ाइल
@ -211,13 +211,13 @@ show-details = विवरण दिखाएँ
## Settings ## Settings
settings = सेटिंग settings = सेटिंग्स
### Appearance ### Appearance
appearance = रूप appearance = रुप-रंग
theme = थीम theme = प्रसंग
match-desktop = सिस्टम सेटिंग्स से मेल खाएँ match-desktop = डेस्कटॉप से मेल खाएँ
dark = डार्क dark = डार्क
light = लाइट light = लाइट
# Context menu # Context menu
@ -251,27 +251,27 @@ new-tab = नया टैब
new-window = नई विंडो new-window = नई विंडो
rename = नाम बदलें... rename = नाम बदलें...
close-tab = टैब बंद करें close-tab = टैब बंद करें
quit = बंद करें quit = बाहर जाएँ
## Edit ## Edit
edit = संपादित करें edit = संपादन
cut = कट करें cut = काटें
copy = नकल करें copy = कॉपी करें
paste = चिपकाएँ paste = चिपकाएँ
select-all = सभी चुनें select-all = सभी चुनें
## View ## View
zoom-in = ज़ूम इन करें zoom-in = बड़ा करें
default-size = डिफ़ॉल्ट आकार default-size = मूल आकार
zoom-out = ज़ूम आउट करें zoom-out = छोटा करें
view = दृश्य view = दृश्य
grid-view = ग्रिड दृश्य grid-view = ग्रिड दृश्य
list-view = सूची दृश्य list-view = सूची दृश्य
show-hidden-files = छिपी हुई फाइलें दिखाएँ show-hidden-files = छिपी हुई फाइलें दिखाएँ
list-directories-first = सबसे पहले डाइरेक्ट्री दिखाएँ list-directories-first = सबसे पहले डाइरेक्ट्री दिखाएँ
menu-settings = सेटिंग्स... menu-settings = सेटिंग्स..।
menu-about = कॉस्मिक फाइल्स के बारे में... menu-about = कॉस्मिक फाइल्स के बारे में...
## Sort ## Sort
@ -284,4 +284,98 @@ sort-oldest-first = पुराने से नए
sort-smallest-to-largest = छोटे से बड़े sort-smallest-to-largest = छोटे से बड़े
sort-largest-to-smallest = बड़े से छोटे sort-largest-to-smallest = बड़े से छोटे
repository = रिपॉजिटरी repository = रिपॉजिटरी
support = सहायता support = समर्थन
read-execute = पढ़ें और निष्पादित करें
deleted =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया गया
*[other] आइटम मिटाए गए
}
dismiss = संदेश खारिज करें
favorite-path-error = निर्देशिका खोलने में त्रुटि
progress = { $percent }%
related-apps = संबंधित ऐप्स
removing-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया जा रहा है
*[other] आइटम हटाए जा रहे हैं
}
remove = हटाएँ
read-write-execute = पढ़ें, लिखें और निष्पादित करें
other-apps = अन्य ऐप्स
pause = विराम
keep = रखें
permanently-deleting =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया जा रहा है
*[other] आइटम स्थायी रूप से मिटाए जा रहे हैं
}
read-write = पढ़ें और लिखें
none = कोई नहीं
resume = फिर से शुरू करें
grid-spacing = ग्रिड स्पेसिंग
extract-to = इस रूप में निकालें..।
delete = हटाएं
read-only = केवल पढ़ने के लिए
deleting =
{ trash } से { $items } { $items ->
[one] आइटम मिटाया जा रहा है
*[other] आइटम मिटाए जा रहे हैं
} ({ $progress })..।
execute-only = केवल निष्पादित करें
details = विवरण
mount-error = ड्राइव तक पहुँचने में असमर्थ
removed-from-recents =
{ recents } से { $items } { $items ->
[one] आइटम हटाया गया
*[other] आइटम हटाए गए
}
progress-paused = { $percent }%, रुका हुआ
cancelled = रद्द किया गया
operations-running-finished =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%), { $finished } समाप्त..।
progress-failed = { $percent }%, विफल
extract-to-title = फ़ोल्डर में निकालें
open-with = इससे खोलें
permanently-deleted =
{ $items } { $items ->
[one] आइटम स्थायी रूप से मिटाया गया
*[other] आइटम स्थायी रूप से मिटाए गए
}
write-execute = लिखें और निष्पादित करें
extract-password-required = पासवर्ड आवश्यक है
progress-cancelled = { $percent }%, रद्द किया गया
write-only = केवल लिखने के लिए
permanently-delete-warning = { $target } को स्थाई रूप से हटा दिया जाएगा। इस कार्रवाई को पूर्ववत नहीं किया जा सकता है।
favorite-path-error-description =
"{ $path }" को खोला नहीं जा सकता
"{ $path }" मौजूद नहीं हो सकता है या आपके पास इसे खोलने की अनुमति नहीं हो सकती है
क्या आप इसे साइडबार से हटाना चाहते हैं?
empty-trash-title = रद्दी साफ़ करें?
permanently-delete-question = स्थाई रूप से हटाएं?
operations-running =
{ $running } { $running ->
[one] ऑपरेशन
*[other] ऑपरेशन
} चल रहा है ({ $percent }%)..।
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] आइटम
} पर चलेगा।
run = चलाएँ
rename-confirm = नाम बदलें
mixed = मिश्रित
pasted-image = चिपकाई गई छवि
pasted-text = चिपकाया गया पाठ
pasted-video = चिपकाया गया वीडियो

View file

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

View file

@ -249,7 +249,7 @@ settings = Pengaturan
single-click = Klik sekali untuk membuka single-click = Klik sekali untuk membuka
appearance = Tampilan appearance = Tampilan
theme = Tema theme = Tema
match-desktop = Cocokkan desktop match-desktop = Cocok desktop
dark = Gelap dark = Gelap
light = Terang light = Terang
type-to-search = Ketik untuk mencari type-to-search = Ketik untuk mencari
@ -319,3 +319,18 @@ show-recents = Map terbaru di bilah sisi
clear-recents-history = Bersihkan riwayat Terbaru clear-recents-history = Bersihkan riwayat Terbaru
copy-path = Salin jalur copy-path = Salin jalur
mixed = Bercampur 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
checksum = { $kind } checksum
calculate = Hitung
error = Galat
sidebar-label = Label bilah sisi
search-application = Cari berdasarkan nama aplikasi
change-sidebar-label = Ubah label bilah sisi

View file

@ -1,5 +1,5 @@
cosmic-files = COSMIC Files cosmic-files = COSMIC Files
comment = File manager di COSMIC comment = File manager per il desktop COSMIC
keywords = File;Archivi;Cartelle;Explorer; keywords = File;Archivi;Cartelle;Explorer;
empty-folder = Cartella vuota empty-folder = Cartella vuota
empty-folder-hidden = Cartella vuota (con elementi nascosti) empty-folder-hidden = Cartella vuota (con elementi nascosti)
@ -7,7 +7,7 @@ no-results = Nessun risultato trovato
filesystem = Filesystem filesystem = Filesystem
home = Home home = Home
networks = Reti networks = Reti
notification-in-progress = Operazioni sui file in corso. notification-in-progress = Operazioni sui file in corso
trash = Cestino trash = Cestino
recents = Recenti recents = Recenti
undo = Annulla undo = Annulla
@ -322,7 +322,7 @@ single-click = Click singolo per aprire
appearance = Aspetto appearance = Aspetto
theme = Tema theme = Tema
match-desktop = Sistema match-desktop = Abbina al desktop
dark = Scuro dark = Scuro
light = Chiaro light = Chiaro
@ -422,3 +422,4 @@ removed-from-recents =
[one] elemento [one] elemento
*[other] elementi *[other] elementi
} da { recents } } da { recents }
run = Esegui

View file

@ -20,7 +20,7 @@ name = Isem
modified = Ittusnifel modified = Ittusnifel
trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman
size = Tiddi size = Tiddi
details = Talqayt details = Ifatusen
pause = Serǧu pause = Serǧu
resume = Kemmel resume = Kemmel
create-archive = Snulfu-d aɣbaṛ create-archive = Snulfu-d aɣbaṛ
@ -40,7 +40,7 @@ file-already-exists = Afaylu s yisem-agi yella yakan
folder-already-exists = Akaram s yisem-agi yella yakan folder-already-exists = Akaram s yisem-agi yella yakan
name-hidden = Ismawen ibeddun s "." ad ttwaffren name-hidden = Ismawen ibeddun s "." ad ttwaffren
name-invalid = Isem ur yezmir ara ad yili "{ $filename }" name-invalid = Isem ur yezmir ara ad yili "{ $filename }"
cancel = Sefsex cancel = Semmet
create = Snulfu-d create = Snulfu-d
open = Ldi open = Ldi
open-file = Ldi afaylu open-file = Ldi afaylu
@ -72,7 +72,7 @@ comment = Amsefrak n yifuyla i tnarit COSMIC
keywords = Akaram;Amsefrak; keywords = Akaram;Amsefrak;
delete = kkes delete = kkes
replace = Semselsi replace = Semselsi
support = Tallalt support = Tallelt
settings = Iɣewwaṛen settings = Iɣewwaṛen
appearance = Timeẓri appearance = Timeẓri
theme = Asentel theme = Asentel
@ -86,10 +86,10 @@ zoom-out = Asemẓi
view = Wali view = Wali
menu-settings = Iɣewwaṛen… menu-settings = Iɣewwaṛen…
save = Sekles save = Sekles
match-desktop = Amṣada d tnarit match-desktop = Semṣada akked tnarit
file = Afaylu file = Afaylu
new-window = Asfaylu amaynut new-window = Asfaylu amaynut
quit = Tuffɣa quit = Ffeɣ
edit = Ẓreg edit = Ẓreg
cut = Gzem cut = Gzem
copy = Nɣel copy = Nɣel

View file

@ -22,7 +22,7 @@ name = Аты
modified = Өзгертілген modified = Өзгертілген
trashed-on = Қоқыс шелегіне тасталған trashed-on = Қоқыс шелегіне тасталған
size = Өлшемі size = Өлшемі
details = Ақпараты details = Толық ақпарат
dismiss = Хабарламаны елемеу dismiss = Хабарламаны елемеу
operations-running = operations-running =
{ $running } { $running -> { $running } { $running ->
@ -246,10 +246,10 @@ item-accessed = Қол жеткізілген: { $accessed }
calculating = Есептеу... calculating = Есептеу...
settings = Баптаулар settings = Баптаулар
single-click = Ашу үшін бір рет шерту single-click = Ашу үшін бір рет шерту
appearance = Сыртқы түрі appearance = Сыртқы көрініс
theme = Тақырып theme = Тема
match-desktop = Жұмыс үстеліне сәйкес келу match-desktop = Жұмыс үстеліне сәйкес болу
dark = Күңгірт dark = Қою
light = Ашық light = Ашық
type-to-search = Іздеу үшін теру type-to-search = Іздеу үшін теру
type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді
@ -319,3 +319,15 @@ show-recents = Бүйір панеліндегі «Жуырдағы құжатт
clear-recents-history = Жуырдағылар тарихын өшіру clear-recents-history = Жуырдағылар тарихын өшіру
copy-path = Орналасқан жолын көшіру copy-path = Орналасқан жолын көшіру
mixed = Аралас mixed = Аралас
context-action = Контекст әрекеті
context-action-confirm-title = "{ $name }" орындау керек пе?
context-action-confirm-warning =
Бұл { $items } орындалады { $items ->
[one] нәрсеге
*[other] нәрсеге
}.
run = Орындау
rename-confirm = Атын өзгерту
checksum = { $kind } бақылау сомасы
calculate = Есептеу
error = Қате

View file

@ -0,0 +1,336 @@
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 } { $items ->
[one] hêman
*[other] hêman
} tê guvaştin 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 } { $items ->
[one] hêman
*[other] hêman
} hate derxistin 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 } { $items ->
[one] hêman
*[other] hêman
} hate guvaştin 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 } { $items ->
[one] hêman
*[other] hêman
} tê jêgirtin 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 } { $items ->
[one] hêman
*[other] hêman
} hate jêgirtin 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 } { $items ->
[one] hêman
*[other] hêman
} tê livandin 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 = Peldanka nû...
match-desktop = Bi sermaseyê re li hev bîne
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 } { $items ->
[one] hêman
*[other] hêman
} tê derxistin 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 } { $items ->
[one] hêman
*[other] hêman
} hate livandin 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 = Guhertin
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 = Afirandin
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 riya...
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 riya
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 = Jêbirî
mixed = Tevlihev
type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre
calculate = Bijmêre
error = Şaşetî
checksum = Tevahiya { $kind } kontrol bike
sidebar-label = Nîşanka benda kêlekê
search-application = Li gorî navê sepanê bigere
change-sidebar-label = Nîşanka benda kêlekê biguhêrîne

View file

@ -277,3 +277,13 @@ copy-to-button-label = 복사
move-to-button-label = 이동 move-to-button-label = 이동
clear-recents-history = 최근 기록 비우기 clear-recents-history = 최근 기록 비우기
copy-path = 복사 경로 copy-path = 복사 경로
move-to-title = 이동 위치 선택
copy-to-title = 복사 경로 선택
run = 실행
rename-confirm = 이름 변경
mixed = 혼합됨
pasted-image = 붙여넣은 이미지
pasted-text = 붙여넣은 텍스트
pasted-video = 붙여넣은 영상
copy-to = 복사하기...
move-to = 이동하기...

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

View file

@ -1,5 +1,5 @@
progress = { $percent }% progress = { $percent }%
cosmic-files = Cosmic Files cosmic-files = COSMIC Failai
empty-folder = Tuščias aplankas empty-folder = Tuščias aplankas
empty-folder-hidden = Tuščias aplankas (turi paslėptų failų) empty-folder-hidden = Tuščias aplankas (turi paslėptų failų)
no-results = Rezultatų nėra 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. permanently-delete-warning = { $target } bus ištrintas visam laikui. Šis veiksmas yra negrįžtamas.
rename-file = Pervadinti failą rename-file = Pervadinti failą
rename-folder = Pervadinti aplanką rename-folder = Pervadinti aplanką
replace = Pakeisti replace = Keisti
replace-title = „{ $filename }“ jau egzistuoja šioje vietoje 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 = 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. 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-modified = Modifikuota: { $modified }
item-accessed = Paskutinė prieiga: { $accessed } item-accessed = Paskutinė prieiga: { $accessed }
calculating = Skaičiuojama... calculating = Skaičiuojama...
settings = Nustatymai settings = Nuostatai
single-click = Vieno paspaudimo atidarymas single-click = Vieno paspaudimo atidarymas
appearance = Išvaizda appearance = Išvaizda
match-desktop = Pagal darbalaukio temą match-desktop = Pagal darbalaukio temą
@ -276,22 +276,22 @@ new-window = Naujas langas
reload-folder = Perkrauti aplanką reload-folder = Perkrauti aplanką
rename = Pervadinti... rename = Pervadinti...
close-tab = Uždaryti skirtuką close-tab = Uždaryti skirtuką
quit = Išeiti quit = Išjungti
edit = Redaguoti edit = Redaguoti
cut = Iškirpti cut = Iškirpti
copy = Kopijuoti copy = Kopijuoti
paste = Įklijuoti paste = Įdėti
select-all = Pažymėti viską select-all = Žymėti viską
zoom-in = Priartinti zoom-in = Artinti
default-size = Numatytas dydis default-size = Numatytas dydis
zoom-out = Nutolinti zoom-out = Tolinti
view = Rodymas view = Rodymas
grid-view = Tinklelio išdėstymas grid-view = Tinklelio išdėstymas
list-view = Sąrašo išdėstymas list-view = Sąrašo išdėstymas
show-hidden-files = Rodyti paslėptus failus show-hidden-files = Rodyti paslėptus failus
list-directories-first = Pirmiau pateikti aplankus list-directories-first = Pirmiau pateikti aplankus
gallery-preview = Galerijos peržiūra gallery-preview = Galerijos peržiūra
menu-settings = Nustatymai... menu-settings = Nuostatai...
menu-about = Apie COSMIC Files... menu-about = Apie COSMIC Files...
sort = Rikiuoti sort = Rikiuoti
sort-a-z = A-Ž 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 sort-largest-to-smallest = Nuo didžiausio iki mažiausio
dark = Tamsus dark = Tamsus
light = Šviesus light = Šviesus
comment = COSMIC desktop failų tvarkyklė comment = COSMIC aplinkos failų tvarkyklė
keywords = Aplankas;Tvarkyklė; keywords = Aplankas;Tvarkyklė;
copy-to-title = Pasirinkti kopijavimo vietą copy-to-title = Pasirinkti kopijavimo vietą
copy-to-button-label = Kopijuoti copy-to-button-label = Kopijuoti
@ -317,3 +317,10 @@ clear-recents-history = Išvalyti Neseniai naudotų istoriją
copy-to = Kopijuoti į... copy-to = Kopijuoti į...
move-to = Perkeltiį į... move-to = Perkeltiį į...
copy-path = Kopijuoti kelią copy-path = Kopijuoti kelią
theme = Stilius
context-action = Konteksto veiksmas
context-action-confirm-title = Paleisti „{ $name }“?
run = Paleisti
rename-confirm = Pervadinti
calculate = Skaičiuoti
error = Klaida

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

View file

@ -425,7 +425,7 @@ removed-from-recents =
} uit { recents } verwijderd } uit { recents } verwijderd
remove-from-recents = Uit recente verwijderen remove-from-recents = Uit recente verwijderen
type-to-search-select = Dit selecteert het eerst overeenkomende bestand of map type-to-search-select = Dit selecteert het eerst overeenkomende bestand of map
comment = Bestandsbeheerder voor COSMIC desktop comment = Bestandsbeheerder voor COSMIC
copy-to-title = Kopieerbestemming aanwijzen copy-to-title = Kopieerbestemming aanwijzen
copy-to-button-label = Kopiëren copy-to-button-label = Kopiëren
move-to-button-label = Verplaatsen move-to-button-label = Verplaatsen

View file

@ -0,0 +1,33 @@
appearance = Aparéncia
theme = Tèma
match-desktop = Assortir al burèu
dark = Escur
light = Clar
file = Fichièr
details = Detalhs
name = Nom
cancel = Anullar
repository = Depaus
settings = Configuracion
menu-settings = Configuracion...
quit = Quitar
cosmic-files = Fichièrs de COSMIC
comment = Gestionari de fichièrs pel burèu COSMIC
save-file = Enregistrar lo fichièr
open-with-title = Cossí volètz dobrir « { $name } » ?
other-apps = Autras aplicacions
context-action-confirm-title = Executar « { $name } » ?
run = Executar
delete = Suprimir
replace = Remplaçar
owner = Propietari
group = Grop
write-execute = Escritura e execucion
read-only = Lectura sola
read-execute = Lectura e execucion
read-write = Lectura e execucion
read-write-execute = Lectura, escritura e execucion
remove = Suprimir
connecting = Connexion...
domain = Domeni
password = Senhal

View file

@ -146,7 +146,7 @@ new-tab = ਨਵੀਂ ਟੈਬ
new-window = ਨਵੀਂ ਵਿੰਡੋ new-window = ਨਵੀਂ ਵਿੰਡੋ
rename = ਨਾਂ ਨੂੰ ਬਦਲੋ... rename = ਨਾਂ ਨੂੰ ਬਦਲੋ...
close-tab = ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ close-tab = ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ
quit = ਬਾਹਰ quit = ਸਮਾਪਤ
edit = ਸੋਧੋ edit = ਸੋਧੋ
cut = ਕੱਟੋ cut = ਕੱਟੋ
copy = ਕਾਪੀ ਕਰੋ copy = ਕਾਪੀ ਕਰੋ
@ -188,9 +188,9 @@ item-accessed = ਪਹੁੰਚ: { $accessed }
calculating = ਗਿਣਤੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ... calculating = ਗਿਣਤੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ...
single-click = ਇੱਕ ਵਾਰ ਕਲਿੱਕ ਕਰਕੇ ਖੋਲ੍ਹੋ single-click = ਇੱਕ ਵਾਰ ਕਲਿੱਕ ਕਰਕੇ ਖੋਲ੍ਹੋ
show-recents = ਬਾਹੀ ਵਿੱਚ ਸੱਜਰੇ ਫੋਲਡਰ show-recents = ਬਾਹੀ ਵਿੱਚ ਸੱਜਰੇ ਫੋਲਡਰ
match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਦਾ match-desktop = ਡੈਸਕਟਾਪ ਨਾਲ ਮਿਲਾਓ
dark = ਗੂੜ੍ਹਾ dark = ਗੂੜ੍ਹਾ
light = ਹਲਕਾ light = ਫਿੱਕਾ
type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ

View file

@ -1,5 +1,5 @@
cosmic-files = Pliki COSMIC cosmic-files = Pliki COSMIC
comment = Menedżer plików pulpitu COSMIC comment = Menedżer plików dla środowiska COSMIC
keywords = Katalogi;Pliki;Menedżer; keywords = Katalogi;Pliki;Menedżer;
empty-folder = Pusty katalog empty-folder = Pusty katalog
empty-folder-hidden = Pusty katalog (z ukrytymi plikami) empty-folder-hidden = Pusty katalog (z ukrytymi plikami)
@ -27,7 +27,7 @@ modified = Zmodyfikowano
trashed-on = Wyrzucono do kosza trashed-on = Wyrzucono do kosza
size = Rozmiar size = Rozmiar
# Progress footer # Progress footer
details = Detale details = Szczegóły
dismiss = Odrzuć wiadomość dismiss = Odrzuć wiadomość
operations-running = operations-running =
{ $running } bieżące { $running -> { $running } bieżące { $running ->
@ -390,7 +390,7 @@ new-window = Nowe okno
reload-folder = Odśwież katalog reload-folder = Odśwież katalog
rename = Zmień nazwę… rename = Zmień nazwę…
close-tab = Zamknij kartę close-tab = Zamknij kartę
quit = Zamknij quit = Zakończ
## Edit ## Edit
@ -438,3 +438,18 @@ show-recents = Ostatnie katalogi w panelu bocznym
clear-recents-history = Wyczyść bierzącą historię clear-recents-history = Wyczyść bierzącą historię
copy-path = Skopiuj ścieżkę copy-path = Skopiuj ścieżkę
mixed = Mieszane 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ę
checksum = { $kind } suma kontrolna
calculate = Oblicz
error = Błąd
sidebar-label = Etykieta panelu bocznego
search-application = Wyszukaj po nazwie aplikacji
change-sidebar-label = Zmień etykietę panelu bocznego

View file

@ -109,6 +109,10 @@ permanently-delete-warning = Deseja realmente excluir permanentemente { $target
rename-file = Renomear arquivo rename-file = Renomear arquivo
rename-folder = Renomear pasta rename-folder = Renomear pasta
## Change Sidebar Label Dialog
sidebar-label = Legenda da barra lateral
## Replace Dialog ## Replace Dialog
replace = Substituir replace = Substituir
@ -350,7 +354,7 @@ light = Estilo claro
### Type to Search ### Type to Search
type-to-search = Digite para pesquisar type-to-search = Digitar para pesquisar
type-to-search-recursive = Pesquisa na pasta atual e em todas as subpastas type-to-search-recursive = Pesquisa na pasta atual e em todas as subpastas
type-to-search-enter-path = Insere o caminho do diretório ou arquivo type-to-search-enter-path = Insere o caminho do diretório ou arquivo
# Context menu # Context menu
@ -365,6 +369,7 @@ open-in-terminal = Abrir no terminal
move-to-trash = Mover para a lixeira move-to-trash = Mover para a lixeira
restore-from-trash = Restaurar da lixeira restore-from-trash = Restaurar da lixeira
remove-from-sidebar = Remover da barra lateral remove-from-sidebar = Remover da barra lateral
change-sidebar-label = Alterar legenda da barra lateral
sort-by-name = Ordenar por nome sort-by-name = Ordenar por nome
sort-by-modified = Ordenar por data de modificação sort-by-modified = Ordenar por data de modificação
sort-by-size = Ordenar por tamanho sort-by-size = Ordenar por tamanho
@ -388,7 +393,7 @@ new-window = Nova janela
reload-folder = Recarregar pasta reload-folder = Recarregar pasta
rename = Renomear... rename = Renomear...
close-tab = Fechar aba close-tab = Fechar aba
quit = Sair quit = Encerrar
## Edit ## Edit
@ -437,3 +442,16 @@ show-recents = Pasta de recentes na barra lateral
clear-recents-history = Limpar histórico de recentes clear-recents-history = Limpar histórico de recentes
copy-path = Copiar caminho copy-path = Copiar caminho
mixed = Misto 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
checksum = Soma de verificação { $kind }
calculate = Calcular
error = Erro
search-application = Pesquisar por nome de aplicativo

View file

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

View file

@ -174,6 +174,7 @@ unknown-folder = неизвестная папка
menu-open-with = Открыть с помощью… menu-open-with = Открыть с помощью…
default-app = { $name } (по умолчанию) default-app = { $name } (по умолчанию)
search-application = Поиск по названию приложения
## Show details ## Show details
@ -188,7 +189,7 @@ settings = Параметры
### Appearance ### Appearance
appearance = Оформление appearance = Внешний вид
theme = Тема theme = Тема
match-desktop = Как в системе match-desktop = Как в системе
dark = Тёмная dark = Тёмная
@ -218,7 +219,7 @@ new-tab = Новая вкладка
new-window = Новое окно new-window = Новое окно
rename = Переименовать… rename = Переименовать…
close-tab = Закрыть вкладку close-tab = Закрыть вкладку
quit = Выйти quit = Завершить
## Edit ## Edit
@ -271,8 +272,8 @@ extract-to = Распаковать в…
extract-to-title = Распаковать в папку extract-to-title = Распаковать в папку
mount-error = Не удалось получить доступ к диску mount-error = Не удалось получить доступ к диску
create = Создать create = Создать
open-with-title = Как вы хотите открыть «{ $name }»? open-with-title = Как хотите открыть «{ $name }»?
browse-store = Искать в { $store } browse-store = Перейти в { $store }
other-apps = Другие приложения other-apps = Другие приложения
related-apps = Связанные приложения related-apps = Связанные приложения
selected-items = { $items } выделенных элем. selected-items = { $items } выделенных элем.
@ -320,7 +321,7 @@ type-to-search-enter-path = Ввод пути к каталогу или фай
delete-permanently = Удалить навсегда delete-permanently = Удалить навсегда
eject = Извлечь eject = Извлечь
remove-from-recents = Убрать из недавних remove-from-recents = Убрать из недавних
change-wallpaper = Изменить обои… change-wallpaper = Изменить фон...
desktop-appearance = Параметры оформления… desktop-appearance = Параметры оформления…
display-settings = Параметры экрана… display-settings = Параметры экрана…
reload-folder = Обновить папку reload-folder = Обновить папку
@ -381,3 +382,17 @@ show-recents = «Недавние документы» в бок. панели
clear-recents-history = Очистить историю недавних clear-recents-history = Очистить историю недавних
copy-path = Копировать путь copy-path = Копировать путь
mixed = Смешанные mixed = Смешанные
context-action = Контекстная команда
context-action-confirm-title = Запустить «{ $name }»?
context-action-confirm-warning =
Команда затронет { $items } { $items ->
[one] элемент
*[other] элем.
}.
run = Запустить
rename-confirm = Переименовать
checksum = Хеш-сумма { $kind }
calculate = Вычислить
error = Ошибка
sidebar-label = Подпись в боковой панели
change-sidebar-label = Изменить подпись

View file

@ -0,0 +1,3 @@
cancel = ᱵᱟᱹᱛᱤᱞ
name = ᱧᱩᱛᱩᱢ
save = ᱥᱟᱧᱪᱟᱣ

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

@ -319,7 +319,7 @@ list-view = Listvy
show-hidden-files = Visa dolda filer show-hidden-files = Visa dolda filer
list-directories-first = Lista mappar först list-directories-first = Lista mappar först
gallery-preview = Galleri förhandsvisning gallery-preview = Galleri förhandsvisning
menu-settings = Inställningar… menu-settings = Inställningar...
menu-about = Om COSMIC Filer... menu-about = Om COSMIC Filer...
## Sortera ## Sortera
@ -410,3 +410,18 @@ move-to = Flytta till...
show-recents = Mapp för senast använda filer i sidofältet show-recents = Mapp för senast använda filer i sidofältet
clear-recents-history = Töm historik för Senaste clear-recents-history = Töm historik för Senaste
copy-path = Kopiera sökväg 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
checksum = { $kind }-kontrollsumma
calculate = Beräkna
error = Fel
sidebar-label = Sidofältsetikett
search-application = Sök på programnamn
change-sidebar-label = Ändra sidofältsetikett

View file

@ -202,17 +202,9 @@ moved =
*[other] ไฟล์ *[other] ไฟล์
} จาก "{ $from }" สู่ "{ $to }" } จาก "{ $from }" สู่ "{ $to }"
renaming = กำลังเปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }" renaming = กำลังเปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }"
renamed = Renamed "{ $from }" to "{ $to }" renamed = เปลี่ยนชื่อจาก "{ $from }" เป็น "{ $to }" แล้ว
restoring = restoring = กำลังกู้คืน { $items } รายการจาก { trash } ({ $progress })...
Restoring { $items } { $items -> restored = กู้คืน { $items } รายการจาก { trash } แล้ว
[one] item
*[other] items
} from { trash } ({ $progress })...
restored =
Restored { $items } { $items ->
[one] item
*[other] items
} from { trash }
unknown-folder = แฟ้มที่ไม่รู้จัก unknown-folder = แฟ้มที่ไม่รู้จัก
## Open with ## Open with
@ -240,8 +232,8 @@ settings = การตั้งค่า
appearance = ลักษณะ appearance = ลักษณะ
theme = ธีม theme = ธีม
match-desktop = ใช้ตามธีมหน้าจอหลัก match-desktop = ใช้ตามธีมหน้าจอหลัก
dark = ธีมมืด dark = มืด
light = ธีมสว่าง light = สว่าง
# Context menu # Context menu
add-to-sidebar = เพิ่มเข้าแถบด้านข้าง add-to-sidebar = เพิ่มเข้าแถบด้านข้าง
compress = บีบอัด compress = บีบอัด
@ -306,3 +298,4 @@ sort-newest-first = ไฟล์ใหม่ก่อน
sort-oldest-first = ไฟล์เก่าก่อน sort-oldest-first = ไฟล์เก่าก่อน
sort-smallest-to-largest = ขนาดเล็กก่อน sort-smallest-to-largest = ขนาดเล็กก่อน
sort-largest-to-smallest = ขนาดใหญ่ก่อน sort-largest-to-smallest = ขนาดใหญ่ก่อน
remove = ลบ

View file

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

View file

@ -25,7 +25,7 @@ modified = 修改时间
trashed-on = 删除时间 trashed-on = 删除时间
size = 大小 size = 大小
# Progress footer # Progress footer
details = 详细信息 details = 详情
dismiss = 清除消息 dismiss = 清除消息
operations-running = operations-running =
正在进行 { $running } { $running -> 正在进行 { $running } { $running ->
@ -167,10 +167,10 @@ read-write-execute = 读取、写入和执行
## Favorite Path Error Dialog ## Favorite Path Error Dialog
favorite-path-error = 打开路径时出错 favorite-path-error = 打开目录时出错
favorite-path-error-description = favorite-path-error-description =
无法打开 "{ $path }" 。 无法打开“{ $path }” 。
"{ $path }" 可能不存在或您没有权限打开它。 “{ $path }”可能不存在或您没有权限打开它。
您想要从侧边栏中移除它吗? 您想要从侧边栏中移除它吗?
remove = 移除 remove = 移除
@ -267,10 +267,10 @@ extracted =
[one] 项目 [one] 项目
*[other] 项目 *[other] 项目
}到“{ $to }” }到“{ $to }”
setting-executable-and-launching = 设置 "{ $name }" 为可执行文件并启动 setting-executable-and-launching = 设置 “{ $name }” 为可执行文件并启动
set-executable-and-launched = 已设 "{ $name }" 为可执行文件并且启动 set-executable-and-launched = 已设 “{ $name }” 为可执行文件并且启动
setting-permissions = 正在设置 "{ $name }" 的权限为 { $mode } setting-permissions = 正在设置 “{ $name }” 的权限为 { $mode }
set-permissions = 设置 "{ $name }" 的权限为 { $mode } set-permissions = 设置 “{ $name }” 的权限为 { $mode }
moving = moving =
正在从“{ $from }”移动 { $items } { $items -> 正在从“{ $from }”移动 { $items } { $items ->
[one] 项目 [one] 项目
@ -302,7 +302,7 @@ removed-from-recents =
*[other] 项目 *[other] 项目
} }
renaming = 正在将“{ $from }”重命名为“{ $to }” renaming = 正在将“{ $from }”重命名为“{ $to }”
renamed = 已重命名"{ $from }"为"{ $to }" renamed = 已重命名 “{ $from }” 为 “{ $to }”
restoring = restoring =
正在从{ trash }中还原 { $items } { $items -> 正在从{ trash }中还原 { $items } { $items ->
[one] 项目 [one] 项目
@ -348,7 +348,7 @@ light = 亮色模式
type-to-search = 输入即可搜索 type-to-search = 输入即可搜索
type-to-search-recursive = 搜索当前文件夹及其所有子文件夹 type-to-search-recursive = 搜索当前文件夹及其所有子文件夹
type-to-search-enter-path = 输入文件夹或文件路径 type-to-search-enter-path = 输入文件夹或文件目录
# Context menu # Context menu
add-to-sidebar = 加入侧边栏 add-to-sidebar = 加入侧边栏
compress = 压缩… compress = 压缩…
@ -437,3 +437,18 @@ clear-recents-history = 清除最近访问历史
copy-path = 复制文件路径 copy-path = 复制文件路径
show-recents = 侧边栏中的最近访问 show-recents = 侧边栏中的最近访问
mixed = 混合 mixed = 混合
context-action-confirm-title = 运行 “{ $name }” ?
run = 运行
context-action-confirm-warning =
该行动将会在 { $items } { $items ->
[one] 项目
*[other] 项目
} 上运行。
context-action = 环境行动
rename-confirm = 重命名
calculate = 计算
error = 错误
checksum = { $kind } 校验和
sidebar-label = 侧边栏标签
search-application = 使用应用名称搜索
change-sidebar-label = 更改侧边栏标签

View file

@ -12,7 +12,7 @@ undo = 復原
today = 今天 today = 今天
# List view # List view
name = 名稱 name = 名稱
modified = 修改日期 modified = 修改時間
size = 大小 size = 大小
# Dialogs # Dialogs
@ -161,8 +161,8 @@ moved =
[one] 項目 [one] 項目
*[other] 項目 *[other] 項目
} 從「{ $from }」至「{ $to }」 } 從「{ $from }」至「{ $to }」
renaming = 正在重新命名「{ $from }」至「{ $to }」 renaming = 正在將「{ $from }」重新命名為「{ $to }」
renamed = 已經重新命名「{ $from }」至「{ $to }」 renamed = 已將「{ $from }」重新命名為「{ $to }」
restoring = restoring =
正在還原 { $items } 項目 { $items -> 正在還原 { $items } 項目 { $items ->
[one] 項目 [one] 項目
@ -182,7 +182,7 @@ default-app = { $name } (預設)
## Show details ## Show details
show-details = 顯示詳細資料 show-details = 顯示詳細資訊
## Settings ## Settings
@ -196,13 +196,13 @@ match-desktop = 符合桌面
dark = 深色 dark = 深色
light = 淺色 light = 淺色
# Context menu # Context menu
add-to-sidebar = 添加至側邊欄 add-to-sidebar = 加入側邊欄
compress = 壓縮… compress = 壓縮…
extract-here = 解壓縮 extract-here = 解壓縮
new-file = 新建檔案... new-file = 新增檔案...
new-folder = 新建資料夾... new-folder = 新增資料夾...
open-in-terminal = 在終端機中開啟 open-in-terminal = 在終端機中開啟
move-to-trash = 移動至垃圾桶 move-to-trash = 丟入垃圾桶
restore-from-trash = 從垃圾桶還原 restore-from-trash = 從垃圾桶還原
remove-from-sidebar = 從側邊欄移除 remove-from-sidebar = 從側邊欄移除
sort-by-name = 依名稱排序 sort-by-name = 依名稱排序
@ -215,11 +215,11 @@ sort-by-size = 依大小排序
## File ## File
file = 檔案 file = 檔案
new-tab = 新建分頁 new-tab = 新增分頁
new-window = 新建視窗 new-window = 新增視窗
rename = 重新命名... rename = 重新命名...
close-tab = 關閉分頁 close-tab = 關閉分頁
quit = 退出 quit = 結束
## Edit ## Edit
@ -258,18 +258,18 @@ deleted =
}從 { trash } }從 { trash }
permanently-deleting = permanently-deleting =
正在永久刪除 { $items } { $items -> 正在永久刪除 { $items } { $items ->
[one] 项目 [one] 項目
*[other] 项目 *[other] 項目
} }
permanently-deleted = permanently-deleted =
已經永久刪除 { $items } { $items -> 已經永久刪除 { $items } { $items ->
[one] 项目 [one] 項目
*[other] 项目 *[other] 項目
} }
removing-from-recents = removing-from-recents =
正在從 { recents } 中移除 { $items } { $items -> 正在從 { recents } 中移除 { $items } { $items ->
[one] 项目 [one] 項目
*[other] 项目 *[other] 項目
} }
deleting = deleting =
正在刪除 { $items } { $items -> 正在刪除 { $items } { $items ->
@ -278,21 +278,21 @@ deleting =
}從 { trash }({ $progress })… }從 { trash }({ $progress })…
removed-from-recents = removed-from-recents =
已經從 { recents } 中移除 { $items } { $items -> 已經從 { recents } 中移除 { $items } { $items ->
[one] 项目 [one] 項目
*[other] 项目 *[other] 項目
} }
repository = 軟體庫源 repository = 軟體庫
desktop-view-options = 桌面檢視選項... desktop-view-options = 桌面檢視選項...
show-on-desktop = 顯示在桌面 show-on-desktop = 顯示在桌面
desktop-folder-content = 桌面資料夾內容 desktop-folder-content = 桌面資料夾內容
mounted-drives = 已經掛載的磁碟機 mounted-drives = 已經掛載的磁碟機
trash-folder-icon = 垃圾桶圖示 trash-folder-icon = 垃圾桶圖示
trashed-on = 遺棄時間 trashed-on = 丟入時間
icon-size-and-spacing = 圖示大小與間距 icon-size-and-spacing = 圖示大小與間距
icon-size = 圖示大小 icon-size = 圖示大小
grid-spacing = 網格間距 grid-spacing = 網格間距
details = 詳情 details = 詳細資訊
dismiss = 撤停訊息 dismiss = 關閉訊息
delete = 刪除 delete = 刪除
remove = 移除 remove = 移除
support = 支援 support = 支援
@ -358,7 +358,7 @@ calculating = 計算中...
single-click = 點按以開啟 single-click = 點按以開啟
type-to-search = 輸入進行搜尋 type-to-search = 輸入進行搜尋
type-to-search-recursive = 搜尋目前資料夾及全部子資料夾 type-to-search-recursive = 搜尋目前資料夾及全部子資料夾
type-to-search-enter-path = 輸入目錄或檔案的路徑 type-to-search-enter-path = 輸入目錄或檔案的目錄
delete-permanently = 永久刪除 delete-permanently = 永久刪除
eject = 彈出 eject = 彈出
remove-from-recents = 從最近項目中移除 remove-from-recents = 從最近項目中移除
@ -384,3 +384,18 @@ set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟
setting-permissions = 設定「{ $name }」的權限至 { $mode } setting-permissions = 設定「{ $name }」的權限至 { $mode }
set-permissions = 設定「{ $name }」的權限至 { $mode } set-permissions = 設定「{ $name }」的權限至 { $mode }
mixed = 混合 mixed = 混合
context-action = 環境行動
context-action-confirm-title = 執行「{ $name }」嗎?
context-action-confirm-warning =
該行動將會在 { $items } { $items ->
[one] 項目
*[other] 項目
} 上執行。
run = 執行
rename-confirm = 重新命名
checksum = { $kind } 核對和
calculate = 計算
error = 錯誤
sidebar-label = 側邊欄標籤
search-application = 依應用程式名稱搜尋
change-sidebar-label = 變更側邊欄標籤

View file

@ -12,6 +12,10 @@ cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
bin-src := cargo-target-dir / 'release' / name bin-src := cargo-target-dir / 'release' / name
bin-dst := base-dir / 'bin' / name bin-dst := base-dir / 'bin' / name
thumbnailer-bin-name := name + '-thumbnailer'
thumbnailer-bin-src := cargo-target-dir / 'release' / thumbnailer-bin-name
thumbnailer-bin-dst := base-dir / 'bin' / thumbnailer-bin-name
applet-name := name + '-applet' applet-name := name + '-applet'
applet-src := cargo-target-dir / 'release' / applet-name applet-src := cargo-target-dir / 'release' / applet-name
applet-dst := base-dir / 'bin' / applet-name applet-dst := base-dir / 'bin' / applet-name
@ -24,6 +28,10 @@ metainfo := APPID + '.metainfo.xml'
metainfo-src := 'target/xdgen' / metainfo metainfo-src := 'target/xdgen' / metainfo
metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo
thumbnailer := APPID + '.thumbnailer'
thumbnailer-src := 'res' / thumbnailer
thumbnailer-dst := clean(rootdir / prefix) / 'share' / 'thumbnailers' / thumbnailer
icons-src := 'res' / 'icons' / 'hicolor' icons-src := 'res' / 'icons' / 'hicolor'
icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor' icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor'
@ -45,13 +53,10 @@ clean-dist: clean clean-vendor
build-debug *args: build-debug *args:
cargo build {{args}} cargo build {{args}}
cargo build --package {{applet-name}} {{args}} cargo build --package {{applet-name}} {{args}}
cargo build --package {{thumbnailer-bin-name}} {{args}}
# Compiles with release profile # Compiles with release profile
build-release *args: (build-debug '--release' args) build-release *args: (build-debug '--release' args)
# Compiles applet with release profile
build-release-applet *args:
cargo build --package {{applet-name}} --release {{args}}
# Compiles release profile with vendored dependencies # Compiles release profile with vendored dependencies
build-vendored *args: vendor-extract (build-release '--frozen --offline' args) build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
@ -92,9 +97,11 @@ heaptrack *args:
# Installs files # Installs files
install: install:
install -Dm0755 {{bin-src}} {{bin-dst}} install -Dm0755 {{bin-src}} {{bin-dst}}
install -Dm0755 {{thumbnailer-bin-src}} {{thumbnailer-bin-dst}}
install -Dm0755 {{applet-src}} {{applet-dst}} install -Dm0755 {{applet-src}} {{applet-dst}}
install -Dm0644 {{desktop-src}} {{desktop-dst}} install -Dm0644 {{desktop-src}} {{desktop-dst}}
install -Dm0644 {{metainfo-src}} {{metainfo-dst}} install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
install -Dm0644 {{thumbnailer-src}} {{thumbnailer-dst}}
for size in `ls {{icons-src}}`; do \ for size in `ls {{icons-src}}`; do \
install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \ install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
done done
@ -105,13 +112,13 @@ install-applet:
# Uninstalls installed files # Uninstalls installed files
uninstall: uninstall:
rm -f {{bin-dst}} {{applet-dst}} rm -f {{bin-dst}} {{thumbnailer-bin-dst}} {{applet-dst}} {{thumbnailer-dst}}
# Vendor dependencies locally # Vendor dependencies locally
vendor: vendor:
#!/usr/bin/env bash #!/usr/bin/env bash
mkdir -p .cargo 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 'directory = "vendor"' >> .cargo/config.toml
echo >> .cargo/config.toml echo >> .cargo/config.toml
echo '[env]' >> .cargo/config.toml echo '[env]' >> .cargo/config.toml

View file

@ -0,0 +1,4 @@
[Thumbnailer Entry]
TryExec=cosmic-files-thumbnailer
Exec=cosmic-files-thumbnailer %o --size %s %i
MimeType=application/vnd.microsoft.portable-executable;application/x-msdownload;

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"

1550
src/app.rs

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,14 @@
use crate::{ use crate::mime_icon::mime_for_path;
mime_icon::mime_for_path, use crate::operation::{Controller, OpReader, OperationError, OperationErrorType};
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
};
use cosmic::iced::futures; use cosmic::iced::futures;
use jiff::{Zoned, civil::DateTime, tz::TimeZone}; use jiff::Zoned;
use std::{ use jiff::civil::DateTime;
collections::HashSet, use jiff::tz::TimeZone;
fs, use std::collections::HashSet;
io::{self, Read, Write}, use std::fs;
path::{Path, PathBuf}, use std::io::{self, Read, Write};
time::SystemTime, use std::path::{Path, PathBuf};
}; use std::time::SystemTime;
use zip::result::ZipError; use zip::result::ZipError;
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[ pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
@ -47,7 +45,7 @@ pub fn extract(
new_dir: &Path, new_dir: &Path,
password: &Option<String>, password: &Option<String>,
controller: &Controller, controller: &Controller,
) -> Result<(), OperationError> { ) -> Result<(Vec<PathBuf>, HashSet<PathBuf>), OperationError> {
let mime = mime_for_path(path, None, false); let mime = mime_for_path(path, None, false);
let password = password.as_deref(); let password = password.as_deref();
match mime.essence_str() { match mime.essence_str() {
@ -57,13 +55,15 @@ pub fn extract(
.map(flate2::read::GzDecoder::new) .map(flate2::read::GzDecoder::new)
.map(tar::Archive::new) .map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir)) .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()) "application/x-tar" => OpReader::new(path, controller.clone())
.map(io::BufReader::new) .map(io::BufReader::new)
.map(tar::Archive::new) .map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir)) .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) "application/zip" => fs::File::open(path)
.map(io::BufReader::new) .map(io::BufReader::new)
.map(zip::ZipArchive::new) .map(zip::ZipArchive::new)
@ -77,7 +77,7 @@ pub fn extract(
OperationError::from_kind(OperationErrorType::PasswordRequired, controller) OperationError::from_kind(OperationErrorType::PasswordRequired, controller)
} }
_ => OperationError::from_err(e, controller), _ => OperationError::from_err(e, controller),
})?, }),
#[cfg(feature = "bzip2")] #[cfg(feature = "bzip2")]
"application/x-bzip" "application/x-bzip"
| "application/x-bzip-compressed-tar" | "application/x-bzip-compressed-tar"
@ -87,7 +87,8 @@ pub fn extract(
.map(bzip2::read::BzDecoder::new) .map(bzip2::read::BzDecoder::new)
.map(tar::Archive::new) .map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir)) .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")] #[cfg(feature = "lzma-rust2")]
"application/x-xz" | "application/x-xz-compressed-tar" => { "application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller.clone()) OpReader::new(path, controller.clone())
@ -95,14 +96,14 @@ pub fn extract(
.map(|reader| lzma_rust2::XzReader::new(reader, true)) .map(|reader| lzma_rust2::XzReader::new(reader, true))
.map(tar::Archive::new) .map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir)) .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( _ => Err(OperationError::from_err(
format!("unsupported mime type {mime:?}"), format!("unsupported mime type {mime:?}"),
controller, controller,
))?, )),
} }
Ok(())
} }
// From https://docs.rs/zip/latest/zip/read/struct.ZipArchive.html#method.extract, with cancellation and progress added // 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, directory: P,
password: Option<&str>, password: Option<&str>,
controller: Controller, controller: Controller,
) -> zip::result::ZipResult<()> { ) -> zip::result::ZipResult<(Vec<PathBuf>, HashSet<PathBuf>)> {
use std::{ffi::OsString, fs}; use std::ffi::OsString;
use std::fs;
use zip::result::ZipError; use zip::result::ZipError;
fn make_writable_dir_all<T: AsRef<Path>>( 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 Ok((written_files, target_dirs))
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
Ok(())
} }
fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option<SystemTime> { 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> // Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::{ use std::collections::VecDeque;
collections::VecDeque, use std::sync::atomic::{AtomicBool, Ordering};
sync::{ use std::sync::{Arc, Mutex};
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
};
/// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque. /// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
pub fn channel<Message>() -> (Sender<Message>, Receiver<Message>) { pub fn channel<Message>() -> (Sender<Message>, Receiver<Message>) {

View file

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

View file

@ -1,20 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only // 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::{ use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
Application, use cosmic::cosmic_config::{self, CosmicConfigEntry};
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}, use cosmic::iced::Subscription;
iced::Subscription, use cosmic::{Application, theme};
theme,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::FxOrderMap;
FxOrderMap, use crate::app::App;
app::App, use crate::tab::{HeadingOptions, Location, View};
tab::{HeadingOptions, Location, View},
};
pub use crate::context_action::{ContextActionPreset, ContextActionSelection}; pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
@ -72,6 +70,11 @@ pub enum Favorite {
name: String, name: String,
path: PathBuf, path: PathBuf,
}, },
/// A path with a custom name chosen by the user
Named {
path: PathBuf,
name: String,
},
} }
impl Favorite { impl Favorite {
@ -100,6 +103,38 @@ impl Favorite {
Self::Videos => dirs::video_dir(), Self::Videos => dirs::video_dir(),
Self::Path(path) => Some(path.clone()), Self::Path(path) => Some(path.clone()),
Self::Network { path, .. } => Some(path.clone()), Self::Network { path, .. } => Some(path.clone()),
Self::Named { path, .. } => Some(path.clone()),
}
}
/// Name shown in the sidebar, or `None` if the path has no usable file name
pub fn display_name(&self) -> Option<String> {
match self {
Self::Home => Some(crate::fl!("home")),
Self::Named { name, .. } | Self::Network { name, .. } => Some(name.clone()),
_ => self
.path_opt()?
.file_name()
.and_then(|x| x.to_str())
.map(ToString::to_string),
}
}
/// Return this favorite with a custom sidebar label chosen by the user
pub fn with_label(&self, name: &str) -> Self {
match self {
Self::Network { uri, path, .. } => Self::Network {
uri: uri.clone(),
name: name.to_string(),
path: path.clone(),
},
other => match other.path_opt() {
Some(path) => Self::Named {
path,
name: name.to_string(),
},
None => other.clone(),
},
} }
} }
} }
@ -115,6 +150,7 @@ pub enum TypeToSearch {
#[serde(default)] #[serde(default)]
pub struct State { pub struct State {
pub sort_names: FxOrderMap<String, (HeadingOptions, bool)>, pub sort_names: FxOrderMap<String, (HeadingOptions, bool)>,
pub dialog_sort_name: (HeadingOptions, bool),
} }
impl Default for State { impl Default for State {
@ -126,6 +162,7 @@ impl Default for State {
(HeadingOptions::Modified, false), (HeadingOptions::Modified, false),
) )
})), })),
dialog_sort_name: (HeadingOptions::Modified, false),
} }
} }
} }
@ -172,6 +209,11 @@ pub struct Config {
pub show_details: bool, pub show_details: bool,
pub show_recents: bool, pub show_recents: bool,
pub tab: TabConfig, 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, pub type_to_search: TypeToSearch,
} }
@ -236,11 +278,97 @@ impl Default for Config {
show_details: false, show_details: false,
show_recents: true, show_recents: true,
tab: TabConfig::default(), tab: TabConfig::default(),
toolbar: default_toolbar(),
type_to_search: TypeToSearch::Recursive, 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)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct DesktopConfig { pub struct DesktopConfig {
@ -387,3 +515,66 @@ pub const TIME_CONFIG_ID: &str = "com.system76.CosmicAppletTime";
pub struct TimeConfig { pub struct TimeConfig {
pub military_time: bool, pub military_time: bool,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn favorite_with_label_converts_path_to_named() {
let favorite = Favorite::Path(PathBuf::from("/some/dir"));
assert_eq!(
favorite.with_label("Custom"),
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_with_label_updates_network_in_place() {
let favorite = Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "example.com".to_string(),
path: PathBuf::from("/run/mount/example"),
};
assert_eq!(
favorite.with_label("Custom"),
Favorite::Network {
uri: "sftp://example.com/".to_string(),
name: "Custom".to_string(),
path: PathBuf::from("/run/mount/example"),
}
);
}
#[test]
fn favorite_with_label_converts_special_folder_to_named() {
let home = dirs::home_dir().unwrap();
assert_eq!(
Favorite::Home.with_label("Custom"),
Favorite::Named {
path: home,
name: "Custom".to_string(),
}
);
}
#[test]
fn favorite_display_name() {
assert_eq!(
Favorite::Path(PathBuf::from("/some/dir")).display_name(),
Some("dir".to_string())
);
assert_eq!(Favorite::Path(PathBuf::from("/")).display_name(), None);
assert_eq!(
Favorite::Named {
path: PathBuf::from("/some/dir"),
name: "Custom".to_string(),
}
.display_name(),
Some("Custom".to_string())
);
}
}

View file

@ -4,7 +4,8 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize}; 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)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum ContextActionSelection { pub enum ContextActionSelection {
@ -46,7 +47,7 @@ impl ContextActionPreset {
} }
for step in &self.steps { 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!( log::warn!(
"failed to parse context action {:?}: invalid Exec {:?}", "failed to parse context action {:?}: invalid Exec {:?}",
self.name, self.name,

View file

@ -1,58 +1,47 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::app::cosmic::Cosmic;
Application, ApplicationExt, Element, use cosmic::app::{Core, Task, context_drawer};
app::{Core, Task, context_drawer, cosmic::Cosmic}, use cosmic::cosmic_config::ConfigSet;
cosmic_config, cosmic_theme, executor, use cosmic::iced::core::SmolStr;
iced::core::widget::operation, use cosmic::iced::core::widget::operation;
iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper}, use cosmic::iced::futures::{self, SinkExt};
iced::widget::scrollable::AbsoluteOffset, use cosmic::iced::keyboard::key::{Named, Physical};
iced::{ use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
self, Alignment, Event, Length, Size, Subscription, use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper};
core::SmolStr, use cosmic::iced::widget::scrollable;
event, use cosmic::iced::widget::scrollable::AbsoluteOffset;
futures::{self, SinkExt}, use cosmic::iced::{
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named}, self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window,
mouse, stream,
widget::scrollable,
window,
},
theme,
widget::{
self, Operation,
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
segmented_button,
},
}; };
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 mime_guess::{Mime, mime};
use notify_debouncer_full::{ use notify_debouncer_full::notify::{self, RecommendedWatcher};
DebouncedEvent, Debouncer, RecommendedCache, new_debouncer, use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
notify::{self, RecommendedWatcher},
};
use recently_used_xbel::update_recently_used; use recently_used_xbel::update_recently_used;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::{ use std::any::TypeId;
any::TypeId, use std::collections::{HashMap, VecDeque};
collections::{HashMap, VecDeque}, use std::path::PathBuf;
env, fmt, fs, use std::time::{self, Instant};
path::PathBuf, use std::{env, fmt, fs};
time::{self, Instant},
};
use crate::{ use crate::app::{
app::{ Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
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::config::{
Config, DialogConfig, State, 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, HeadingOptions, 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)] #[derive(Clone, Debug)]
pub struct DialogMessage(cosmic::Action<Message>); pub struct DialogMessage(cosmic::Action<Message>);
@ -265,6 +254,7 @@ impl<M: Send + 'static> Dialog<M> {
crate::localize::localize(); crate::localize::localize();
let (config_handler, config) = Config::load(); let (config_handler, config) = Config::load();
let (state_handler, state) = State::load();
let mut settings = window::Settings { let mut settings = window::Settings {
decorations: false, decorations: false,
@ -299,6 +289,8 @@ impl<M: Send + 'static> Dialog<M> {
window_id, window_id,
config_handler, config_handler,
config, config,
state_handler,
state,
}; };
let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags)); let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags));
@ -453,6 +445,8 @@ struct Flags {
#[allow(dead_code)] #[allow(dead_code)]
config_handler: Option<cosmic_config::Config>, config_handler: Option<cosmic_config::Config>,
config: Config, config: Config,
state_handler: Option<cosmic_config::Config>,
state: State,
} }
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
@ -468,7 +462,7 @@ enum Message {
Escape, Escape,
Filename(String), Filename(String),
Filter(usize), Filter(usize),
Key(Modifiers, Key, Option<SmolStr>), Key(Modifiers, Key, Physical, Option<SmolStr>),
ModifiersChanged(Modifiers), ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems), MounterItems(MounterKey, MounterItems),
Mouse(window::Id, mouse::Button), Mouse(window::Id, mouse::Button),
@ -482,12 +476,12 @@ enum Message {
SearchActivate, SearchActivate,
SearchClear, SearchClear,
SearchInput(String), SearchInput(String),
Surface(cosmic::surface::Action), Surface(cosmic::surface::Action<Message>),
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
TabMessage(tab::Message), TabMessage(tab::Message),
TabRescan( TabRescan(
Location, Location,
Option<tab::Item>, Option<Box<tab::Item>>,
Vec<tab::Item>, Vec<tab::Item>,
Option<Vec<PathBuf>>, Option<Vec<PathBuf>>,
), ),
@ -515,7 +509,7 @@ impl From<AppMessage> for Message {
AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn, AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn,
AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut, AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut,
AppMessage::NewItem(_entity_opt, true) => Self::NewFolder, AppMessage::NewItem(_entity_opt, true) => Self::NewFolder,
AppMessage::Surface(action) => Self::Surface(action), AppMessage::Surface(action) => Self::Surface(action.map(Self::from)),
unsupported => { unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode"); log::warn!("{unsupported:?} not supported in dialog mode");
Self::None Self::None
@ -555,7 +549,6 @@ struct App {
title: String, title: String,
accept_label: DialogLabel, accept_label: DialogLabel,
choices: Vec<DialogChoice>, choices: Vec<DialogChoice>,
context_menu_window: Option<window::Id>,
context_page: ContextPage, context_page: ContextPage,
dialog_pages: VecDeque<DialogPage>, dialog_pages: VecDeque<DialogPage>,
dialog_text_input: widget::Id, dialog_text_input: widget::Id,
@ -587,7 +580,7 @@ impl App {
space_s, space_s,
space_l, space_l,
.. ..
} = theme::active().cosmic().spacing; } = theme::spacing();
let is_condensed = self.core().is_condensed(); let is_condensed = self.core().is_condensed();
let mut col = widget::column::with_capacity(2).spacing(space_xxs); let mut col = widget::column::with_capacity(2).spacing(space_xxs);
@ -744,11 +737,17 @@ impl App {
fn rescan_tab(&self, selection_paths: Option<Vec<PathBuf>>) -> Task<Message> { fn rescan_tab(&self, selection_paths: Option<Vec<PathBuf>>) -> Task<Message> {
let location = self.tab.location.clone(); let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes; let icon_sizes = self.tab.config.icon_sizes;
#[cfg(feature = "gvfs")]
let mounter_items = self.mounter_items.clone(); let mounter_items = self.mounter_items.clone();
Task::future(async move { Task::future(async move {
let location2 = location.clone(); let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { 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")] #[cfg(feature = "gvfs")]
{ {
let mounter_paths: Box<[_]> = mounter_items let mounter_paths: Box<[_]> = mounter_items
@ -837,9 +836,10 @@ impl App {
fn update_config(&mut self) -> Task<Message> { fn update_config(&mut self) -> Task<Message> {
self.core.window.show_context = self.flags.config.dialog.show_details; 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_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> { fn with_dialog_config<F: Fn(&mut DialogConfig)>(&mut self, f: F) -> Task<Message> {
@ -878,15 +878,6 @@ impl App {
} }
} }
fn close_context_menus(&mut self) -> Task<Message> {
self.tab.location_context_menu_index = None;
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
Task::none()
}
fn update_nav_model(&mut self) { fn update_nav_model(&mut self) {
let mut nav_model = segmented_button::ModelBuilder::default(); let mut nav_model = segmented_button::ModelBuilder::default();
@ -900,11 +891,7 @@ impl App {
for favorite in &self.flags.config.favorites { for favorite in &self.flags.config.favorites {
if let Some(path) = favorite.path_opt() { if let Some(path) = favorite.path_opt() {
let name = if matches!(favorite, Favorite::Home) { let Some(name) = favorite.display_name() else {
fl!("home")
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
continue; continue;
}; };
nav_model = nav_model.insert(move |b| { nav_model = nav_model.insert(move |b| {
@ -1054,8 +1041,7 @@ impl Application for App {
None, None,
); );
tab.mode = tab::Mode::Dialog(flags.kind.clone()); tab.mode = tab::Mode::Dialog(flags.kind.clone());
tab.sort_name = tab::HeadingOptions::Modified; (tab.sort_name, tab.sort_direction) = flags.state.dialog_sort_name;
tab.sort_direction = false;
let key_binds = key_binds(&tab.mode); let key_binds = key_binds(&tab.mode);
@ -1065,7 +1051,6 @@ impl Application for App {
title, title,
accept_label: DialogLabel::from(accept_label), accept_label: DialogLabel::from(accept_label),
choices: Vec::new(), choices: Vec::new(),
context_menu_window: None,
context_page: ContextPage::Preview(None, PreviewKind::Selected), context_page: ContextPage::Preview(None, PreviewKind::Selected),
dialog_pages: VecDeque::new(), dialog_pages: VecDeque::new(),
dialog_text_input: widget::Id::new("Dialog Text Input"), dialog_text_input: widget::Id::new("Dialog Text Input"),
@ -1125,7 +1110,7 @@ impl Application for App {
} }
fn dialog(&self) -> Option<Element<'_, Message>> { 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? //TODO: should gallery view just be a dialog?
if self.tab.gallery { if self.tab.gallery {
@ -1336,15 +1321,6 @@ impl Application for App {
return Task::none(); return Task::none();
} }
if self.tab.location_context_menu_index.is_some() {
self.tab.location_context_menu_index = None;
return Task::none();
}
if self.tab.context_menu.is_some() {
return self.update(Message::TabMessage(tab::Message::ContextMenu(None, None)));
}
if self.tab.edit_location.is_some() { if self.tab.edit_location.is_some() {
// Close location editing if enabled // Close location editing if enabled
self.tab.edit_location = None; self.tab.edit_location = None;
@ -1403,7 +1379,10 @@ impl Application for App {
Message::Config(config) => { Message::Config(config) => {
if config != self.flags.config { if config != self.flags.config {
log::info!("update 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 = config;
self.flags.config.tab.military_time = military_time;
return self.update_config(); return self.update_config();
} }
} }
@ -1456,16 +1435,16 @@ impl Application for App {
} }
return self.rescan_tab(None); 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 { 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())); return self.update(Message::from(action.message()));
} }
} }
// Check key binds from accept label // Check key binds from accept label
if let Some(key_bind) = &self.accept_label.key_bind_opt 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() { return self.update(if self.flags.kind.save() {
Message::Save(false) Message::Save(false)
@ -1576,7 +1555,7 @@ impl Application for App {
Message::Mouse(window_id, _button) => { Message::Mouse(window_id, _button) => {
// Close context menu when clicking outside. // Close context menu when clicking outside.
if self.core.main_window_id() == Some(window_id) { if self.core.main_window_id() == Some(window_id) {
return self.close_context_menus(); return Task::none();
} }
} }
Message::NewFolder => { Message::NewFolder => {
@ -1736,7 +1715,7 @@ impl Application for App {
))); )));
} }
Message::SearchActivate => { Message::SearchActivate => {
let mut tasks = vec![self.close_context_menus()]; let mut tasks = vec![];
if self.search_get().is_none() { if self.search_get().is_none() {
tasks.push(self.search_set(Some(String::new()))); tasks.push(self.search_set(Some(String::new())));
@ -1747,7 +1726,7 @@ impl Application for App {
return Task::batch(tasks); return Task::batch(tasks);
} }
Message::SearchClear => { Message::SearchClear => {
return Task::batch([self.close_context_menus(), self.search_set(None)]); return self.search_set(None);
} }
Message::SearchInput(input) => { Message::SearchInput(input) => {
return self.search_set(Some(input)); return self.search_set(Some(input));
@ -1783,77 +1762,9 @@ impl Application for App {
self.rescan_tab(selection_paths), self.rescan_tab(selection_paths),
])); ]));
} }
tab::Command::ContextMenu(point_opt, parent_id) => { tab::Command::Surface(action) => {
#[cfg(feature = "wayland")] let action = action.map(Message::TabMessage);
match point_opt { commands.push(self.update(Message::Surface(action)));
Some(point) => {
if crate::is_wayland() {
// Open context menu
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
Anchor, Gravity,
};
use cosmic::iced::runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use cosmic::iced::Rectangle;
let window_id = window::Id::unique();
self.context_menu_window = Some(window_id);
let autosize_id = widget::Id::unique();
commands.push(self.update(Message::Surface(
cosmic::surface::action::app_popup(
move |app: &mut Self| -> SctkPopupSettings {
let anchor_rect = Rectangle {
x: point.x as i32,
y: point.y as i32,
width: 1,
height: 1,
};
let positioner = SctkPositioner {
size: None,
anchor_rect,
anchor: Anchor::None,
gravity: Gravity::BottomRight,
reactive: true,
..Default::default()
};
SctkPopupSettings {
parent: parent_id
.unwrap_or(app.flags.window_id),
id: window_id,
positioner,
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
}
},
Some(Box::new(move |app: &Self| {
widget::autosize::autosize(
menu::context_menu(
&app.tab,
&app.key_binds,
&app.modifiers,
false, // Paste not used in dialogs
&app.flags.config.context_actions,
)
.map(Message::TabMessage)
.map(cosmic::Action::App),
autosize_id.clone(),
)
.into()
})),
),
)));
}
}
None => {
if let Some(window_id) = self.context_menu_window.take() {
commands.push(self.update(Message::Surface(
cosmic::surface::action::destroy_popup(window_id),
)));
}
}
}
} }
tab::Command::Iced(iced_command) => { tab::Command::Iced(iced_command) => {
commands.push(iced_command.0.map(|tab_message| { commands.push(iced_command.0.map(|tab_message| {
@ -1888,6 +1799,17 @@ impl Application for App {
self.auto_scroll_speed = None; self.auto_scroll_speed = None;
} }
} }
tab::Command::SetSort(_, heading_options, direction) => {
self.flags.state.dialog_sort_name = (heading_options, direction);
if let Some(state_handler) = self.flags.state_handler.as_ref()
&& let Err(err) = state_handler.set::<&(HeadingOptions, bool)>(
"dialog_sort_name",
&self.flags.state.dialog_sort_name,
)
{
log::warn!("Failed to save dialog sort name: {err:?}");
}
}
unsupported => { unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode"); log::warn!("{unsupported:?} not supported in dialog mode");
} }
@ -1925,11 +1847,13 @@ impl Application for App {
items.retain(|item| { items.retain(|item| {
// Directories are always shown // Directories are always shown
item.metadata.is_dir() item.metadata.is_dir()
// Check for mime type match (first because it is faster)
|| mimes.iter().any(|filter_mime| { || mimes.iter().any(|filter_mime| {
if filter_mime.subtype() == mime::STAR { if filter_mime.subtype() == mime::STAR {
filter_mime.type_() == item.mime.type_() filter_mime.type_() == item.mime.type_()
} else { } else {
*filter_mime == item.mime *filter_mime == item.mime
|| mime_icon::is_mime_subclass_of(&item.mime, filter_mime)
} }
}) })
// Check for glob match (last because it is slower) // Check for glob match (last because it is slower)
@ -2006,9 +1930,7 @@ impl Application for App {
}); });
} }
Message::Surface(action) => { Message::Surface(action) => {
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Surface(action));
cosmic::app::Action::Surface(action),
));
} }
} }
@ -2017,7 +1939,7 @@ impl Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<'_, Message> { 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); let mut col = widget::column::with_capacity(2);
@ -2056,11 +1978,14 @@ impl Application for App {
}, },
Event::Keyboard(KeyEvent::KeyPressed { Event::Keyboard(KeyEvent::KeyPressed {
key, key,
physical_key,
modifiers, modifiers,
text, text,
.. ..
}) => match status { }) => 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 => { event::Status::Captured => {
if key == Key::Named(Named::Escape) { if key == Key::Named(Named::Escape) {
Some(Message::Escape) Some(Message::Escape)

View file

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

View file

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

View file

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

View file

@ -1,11 +1,10 @@
use cosmic::iced::{core as iced_core, widget as iced_widget}; use cosmic::iced::{core as iced_core, widget as iced_widget};
use iced_core::event::Event; 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::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>( pub fn loaded_image<'a, Message: 'static, Theme>(
handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle, handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle,

View file

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

View file

@ -1,49 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::{Element, theme};
Element, use cosmic::app::Core;
app::Core, use cosmic::iced::keyboard::Modifiers;
iced::{ use cosmic::widget::menu::action::MenuAction;
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle, use cosmic::widget::menu::key_bind::KeyBind;
keyboard::Modifiers, use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
}, use cosmic::widget::{self, responsive_menu_bar};
theme,
widget::{
self, Row, button, column, container, divider,
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
responsive_menu_bar, space, text,
},
};
use i18n_embed::LanguageLoader; use i18n_embed::LanguageLoader;
use mime_guess::Mime; use mime_guess::Mime;
use std::{collections::HashMap, sync::LazyLock}; use std::collections::HashMap;
use std::sync::LazyLock;
use crate::{ use crate::app::{Action, Message};
app::{Action, Message}, use crate::config::{Config, ContextActionPreset};
config::{Config, ContextActionPreset}, use crate::fl;
fl, use crate::tab::{
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab}, self, HeadingOptions, ItemMetadata, Location, LocationMenuAction, SearchLocation, Tab,
trash::{Trash, TrashExt},
}; };
use crate::trash::{Trash, TrashExt};
static MENU_ID: LazyLock<cosmic::widget::Id> = static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu")); LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
macro_rules! menu_button {
($($x:expr),+ $(,)?) => (
button::custom(
Row::with_children(
[$(Element::from($x)),+]
)
.height(Length::Fixed(24.0))
.align_y(Alignment::Center)
)
.padding([theme::active().cosmic().spacing.space_xxs, 16])
.width(Length::Fill)
.class(theme::Button::MenuItem)
);
}
const fn menu_button_optional( const fn menu_button_optional(
label: String, label: String,
action: Action, action: Action,
@ -56,73 +35,46 @@ const fn menu_button_optional(
} }
} }
/// A menu action dispatched to the tab the menu was opened on.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TabAction(pub Action);
impl MenuAction for TabAction {
type Message = tab::Message;
fn message(&self) -> tab::Message {
tab::Message::ContextAction(self.0)
}
}
pub fn context_menu<'a>( pub fn context_menu<'a>(
tab: &Tab, tab: &Tab,
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
modifiers: &Modifiers, modifiers: &Modifiers,
clipboard_paste_available: bool, clipboard_paste_available: bool,
context_actions: &[ContextActionPreset], context_actions: &[ContextActionPreset],
) -> Element<'a, tab::Message> { ) -> Vec<menu::Tree<tab::Message>> {
let find_key = |action: &Action| -> String { let menu_item =
for (key_bind, key_action) in key_binds { |label: String, action: Action| menu::Item::Button(label, None, TabAction(action));
if action == key_action { let menu_item_disabled =
return key_bind.to_string(); |label: String, action: Action| menu::Item::ButtonDisabled(label, None, TabAction(action));
}
}
String::new()
};
fn key_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
}
}
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background.component.on;
color.alpha *= 0.5;
TextStyle {
color: Some(color.into()),
}
}
let menu_item = |label, action| {
let key = find_key(&action);
menu_button!(
text::body(label),
space::horizontal(),
text::body(key).class(theme::Text::Custom(key_style))
)
.on_press(tab::Message::ContextAction(action))
};
let menu_item_disabled = |label, action: Action| {
let key = find_key(&action);
menu_button!(
text::body(label).class(theme::Text::Custom(disabled_style)),
space::horizontal(),
text::body(key).class(theme::Text::Custom(disabled_style))
)
};
// Allow paste when clipboard has data and we're in a location that supports it // Allow paste when clipboard has data and we're in a location that supports it
let can_paste = clipboard_paste_available && tab.location.supports_paste(); let can_paste = clipboard_paste_available && tab.location.supports_paste();
let (sort_name, sort_direction, _) = tab.sort_options(); let (sort_name, sort_direction, _) = tab.sort_options();
let sort_item = |label, variant| { let sort_item = |label: String, variant| {
menu_item( let entry = menu::Entry::new(label, TabAction(Action::ToggleSort(variant)));
format!( menu::Item::Entry(if sort_name == variant {
"{} {}", let icon_name = if sort_direction {
label, "view-sort-ascending-symbolic"
match (sort_name == variant, sort_direction) { } else {
(true, true) => "\u{2B07}", "view-sort-descending-symbolic"
(true, false) => "\u{2B06}", };
_ => "", entry.icon(widget::icon::from_name(icon_name).size(14).handle())
} } else {
), entry.reserve_icon()
Action::ToggleSort(variant), })
)
.into()
}; };
let mut selected_dir = 0; let mut selected_dir = 0;
@ -131,6 +83,7 @@ pub fn context_menu<'a>(
let mut selected_desktop_entry = None; let mut selected_desktop_entry = None;
let mut selected_types: Vec<Mime> = vec![]; let mut selected_types: Vec<Mime> = vec![];
let mut selected_mount_point = 0; let mut selected_mount_point = 0;
let mut any_trash_item = false;
if let Some(items) = tab.items_opt() { if let Some(items) = tab.items_opt() {
for item in items { for item in items {
if item.selected { if item.selected {
@ -143,15 +96,17 @@ pub fn context_menu<'a>(
Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => { Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => {
selected_trash_only = true selected_trash_only = true
} }
Some(Location::Path(path)) => { Some(Location::Path(path))
if selected == 1 if selected == 1
&& path.extension().and_then(|s| s.to_str()) == Some("desktop") && path.extension().and_then(|s| s.to_str()) == Some("desktop") =>
{ {
selected_desktop_entry = Some(&**path); selected_desktop_entry = Some(&**path);
}
} }
_ => (), _ => (),
} }
if matches!(&item.metadata, ItemMetadata::Trash { .. }) {
any_trash_item = true;
}
selected_types.push(item.mime.clone()); selected_types.push(item.mime.clone());
} }
} }
@ -164,8 +119,8 @@ pub fn context_menu<'a>(
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, action)| action.matches_selection(selected, selected_dir)) .filter(|(_, action)| action.matches_selection(selected, selected_dir))
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)).into()) .map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)))
.collect::<Vec<Element<'a, tab::Message>>>() .collect::<Vec<_>>()
}; };
// Parse the desktop entry if it is the only selection // Parse the desktop entry if it is the only selection
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
@ -180,7 +135,7 @@ pub fn context_menu<'a>(
} }
}); });
let mut children: Vec<Element<_>> = Vec::new(); let mut children: Vec<menu::Item<TabAction, String>> = Vec::new();
match (&tab.mode, &tab.location) { match (&tab.mode, &tab.location) {
( (
tab::Mode::App | tab::Mode::Desktop, tab::Mode::App | tab::Mode::Desktop,
@ -192,152 +147,175 @@ pub fn context_menu<'a>(
| Location::Network(_, _, Some(_)), | Location::Network(_, _, Some(_)),
) => { ) => {
if selected_trash_only { if selected_trash_only {
children.push(menu_item(fl!("open"), Action::Open).into()); children.push(menu_item(fl!("open"), Action::Open));
if !Trash::is_empty() { if !Trash::is_empty() {
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into()); children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash));
} }
} 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()); children.push(menu_item(fl!("open"), Action::Open));
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
{ {
children.extend(entry.desktop_actions.into_iter().enumerate().map( children.extend(
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(), entry
)); .desktop_actions
.into_iter()
.enumerate()
.map(|(i, action)| menu_item(action.name, Action::ExecEntryAction(i))),
);
} }
children.push(divider::horizontal::light().into()); children.push(menu_item(fl!("edit-desktop-entry"), Action::EditDesktopEntry));
children.push(menu_item(fl!("rename"), Action::Rename).into()); children.push(menu::Item::Divider);
children.push(menu_item(fl!("cut"), Action::Cut).into()); children.push(menu_item(fl!("rename"), Action::Rename));
children.push(menu_item(fl!("cut"), Action::Cut));
if modifiers.shift() && !modifiers.control() { if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into()); children.push(menu_item(fl!("copy-path"), Action::CopyPath));
} else { } else {
children.push(menu_item(fl!("copy"), Action::Copy).into()); children.push(menu_item(fl!("copy"), Action::Copy));
} }
// Should this simply bypass trash and remove the shortcut? // Should this simply bypass trash and remove the shortcut?
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); children.push(menu_item(fl!("move-to-trash"), Action::Delete));
let action_items = context_action_items(selected, selected_dir); let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() { if !action_items.is_empty() {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.extend(action_items); children.extend(action_items);
} }
} else if selected > 0 { } else if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open).into()); children.push(menu_item(fl!("open"), Action::Open));
} }
if selected == 1 { if selected == 1 {
children.push(menu_item(fl!("menu-open-with"), Action::OpenWith).into()); children.push(menu_item(fl!("menu-open-with"), Action::OpenWith));
if selected_dir == 1 { if selected_dir == 1 {
children children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
} }
} }
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) { if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
children.push( children.push(menu_item(
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(), fl!("open-item-location"),
); Action::OpenItemLocation,
));
} }
// All selected items are directories // All selected items are directories
if selected == selected_dir && matches!(tab.mode, tab::Mode::App) { if selected == selected_dir && matches!(tab.mode, tab::Mode::App) {
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into()); children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab));
children children.push(menu_item(
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into()); fl!("open-in-new-window"),
Action::OpenInNewWindow,
));
} }
let action_items = context_action_items(selected, selected_dir); let action_items = context_action_items(selected, selected_dir);
if !action_items.is_empty() { if !action_items.is_empty() {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.extend(action_items); children.extend(action_items);
} }
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
if selected_mount_point == 0 { if selected_mount_point == 0 {
children.push(menu_item(fl!("rename"), Action::Rename).into()); children.push(menu_item(fl!("rename"), Action::Rename));
children.push(menu_item(fl!("cut"), Action::Cut).into()); children.push(menu_item(fl!("cut"), Action::Cut));
} }
if modifiers.shift() && !modifiers.control() { if modifiers.shift() && !modifiers.control() {
children.push(menu_item(fl!("copy-path"), Action::CopyPath).into()); children.push(menu_item(fl!("copy-path"), Action::CopyPath));
} else { } else {
children.push(menu_item(fl!("copy"), Action::Copy).into()); children.push(menu_item(fl!("copy"), Action::Copy));
} }
if selected_mount_point == 0 { if selected_mount_point == 0 {
children.push(menu_item(fl!("move-to"), Action::MoveTo).into()); children.push(menu_item(fl!("move-to"), Action::MoveTo));
} }
children.push(menu_item(fl!("copy-to"), Action::CopyTo).into()); children.push(menu_item(fl!("copy-to"), Action::CopyTo));
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES; let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES;
selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m)); selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m));
if selected_types.is_empty() { if selected_types.is_empty() {
children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into()); children.push(menu_item(fl!("extract-here"), Action::ExtractHere));
children.push(menu_item(fl!("extract-to"), Action::ExtractTo).into()); children.push(menu_item(fl!("extract-to"), Action::ExtractTo));
} }
children.push(menu_item(fl!("compress"), Action::Compress).into()); children.push(menu_item(fl!("compress"), Action::Compress));
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
//TODO: Print? //TODO: Print?
children.push(menu_item(fl!("show-details"), Action::Preview).into()); children.push(menu_item(fl!("show-details"), Action::Preview));
if matches!(tab.mode, tab::Mode::App) { if any_trash_item {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); children.push(menu_item(
} fl!("restore-from-trash"),
children.push(divider::horizontal::light().into()); Action::RestoreFromTrash,
if tab.location.is_recents() { ));
children.push( children.push(menu::Item::Divider);
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(), children.push(menu_item(fl!("delete-permanently"), Action::Delete));
); } else {
children.push(divider::horizontal::light().into()); if matches!(tab.mode, tab::Mode::App) {
} children.push(menu::Item::Divider);
if selected_mount_point == 0 { children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar));
if modifiers.shift() && !modifiers.control() { }
children.push( children.push(menu::Item::Divider);
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(), if tab.location.is_recents() {
); children.push(menu_item(
} else { fl!("remove-from-recents"),
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); Action::RemoveFromRecents,
));
children.push(menu::Item::Divider);
}
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));
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject));
} }
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject).into());
} }
} else { } else {
//TODO: need better designs for menu with no selection //TODO: need better designs for menu with no selection
//TODO: have things like properties but they apply to the folder? //TODO: have things like properties but they apply to the folder?
if tab.location != Location::Recents { if tab.location != Location::Recents {
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into()); children.push(menu_item(fl!("new-folder"), Action::NewFolder));
children.push(menu_item(fl!("new-file"), Action::NewFile).into()); children.push(menu_item(fl!("new-file"), Action::NewFile));
children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into()); children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal));
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
} }
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); children.push(menu_item(fl!("select-all"), Action::SelectAll));
} }
if can_paste { if can_paste {
children.push(menu_item(fl!("paste"), Action::Paste).into()); children.push(menu_item(fl!("paste"), Action::Paste));
} else { } else {
children.push(menu_item_disabled(fl!("paste"), Action::Paste).into()); children.push(menu_item_disabled(fl!("paste"), Action::Paste));
} }
//TODO: only show if cosmic-settings is found? //TODO: only show if cosmic-settings is found?
if matches!(tab.mode, tab::Mode::Desktop) { if matches!(tab.mode, tab::Mode::Desktop) {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.push( children.push(menu_item(
menu_item(fl!("change-wallpaper"), Action::CosmicSettingsWallpaper).into(), fl!("change-wallpaper"),
); Action::CosmicSettingsWallpaper,
children.push( ));
menu_item(fl!("desktop-appearance"), Action::CosmicSettingsDesktop).into(), children.push(menu_item(
); fl!("desktop-appearance"),
children.push( Action::CosmicSettingsDesktop,
menu_item(fl!("display-settings"), Action::CosmicSettingsDisplays).into(), ));
); children.push(menu_item(
fl!("display-settings"),
Action::CosmicSettingsDisplays,
));
} }
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
// TODO: Nested menu // TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size)); children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
if matches!(tab.location, Location::Desktop(..)) { if matches!(tab.location, Location::Desktop(..)) {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.push( children.push(menu_item(
menu_item(fl!("desktop-view-options"), Action::DesktopViewOptions).into(), fl!("desktop-view-options"),
); Action::DesktopViewOptions,
));
} }
} }
} }
@ -352,24 +330,25 @@ pub fn context_menu<'a>(
) => { ) => {
if selected > 0 { if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open).into()); children.push(menu_item(fl!("open"), Action::Open));
} }
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() { if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
children.push( children.push(menu_item(
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(), fl!("open-item-location"),
); Action::OpenItemLocation,
));
} }
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children.push(menu_item(fl!("show-details"), Action::Preview).into()); children.push(menu_item(fl!("show-details"), Action::Preview));
} else { } else {
if dialog_kind.save() { if dialog_kind.save() {
children.push(menu_item(fl!("new-folder"), Action::NewFolder).into()); children.push(menu_item(fl!("new-folder"), Action::NewFolder));
} }
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); children.push(menu_item(fl!("select-all"), Action::SelectAll));
} }
if !children.is_empty() { if !children.is_empty() {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
} }
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -379,14 +358,14 @@ pub fn context_menu<'a>(
(_, Location::Network(..)) => { (_, Location::Network(..)) => {
if selected > 0 { if selected > 0 {
if selected_dir == 1 && selected == 1 || selected_dir == 0 { if selected_dir == 1 && selected == 1 || selected_dir == 0 {
children.push(menu_item(fl!("open"), Action::Open).into()); children.push(menu_item(fl!("open"), Action::Open));
} }
} else { } else {
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); children.push(menu_item(fl!("select-all"), Action::SelectAll));
} }
if !children.is_empty() { if !children.is_empty() {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
} }
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified));
@ -395,18 +374,26 @@ pub fn context_menu<'a>(
} }
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => { (_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
if tab.mode.multiple() { if tab.mode.multiple() {
children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); children.push(menu_item(fl!("select-all"), Action::SelectAll));
}
if !Trash::is_empty() {
if !children.is_empty() {
children.push(menu::Item::Divider);
}
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash));
} }
if !children.is_empty() { if !children.is_empty() {
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
} }
if selected > 0 { if selected > 0 {
children.push(menu_item(fl!("show-details"), Action::Preview).into()); children.push(menu_item(fl!("show-details"), Action::Preview));
children.push(divider::horizontal::light().into()); children.push(menu::Item::Divider);
children children.push(menu_item(
.push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into()); fl!("restore-from-trash"),
children.push(divider::horizontal::light().into()); Action::RestoreFromTrash,
children.push(menu_item(fl!("delete-permanently"), Action::Delete).into()); ));
children.push(menu::Item::Divider);
children.push(menu_item(fl!("delete-permanently"), Action::Delete));
} else { } else {
// TODO: Nested menu // TODO: Nested menu
children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name));
@ -416,26 +403,11 @@ pub fn context_menu<'a>(
} }
} }
container(column::with_children(children)) let key_binds: HashMap<KeyBind, TabAction> = key_binds
.padding(1) .iter()
//TODO: move style to libcosmic .map(|(key_bind, action)| (key_bind.clone(), TabAction(*action)))
.style(|theme| { .collect();
let cosmic = theme.cosmic(); menu::items(&key_binds, children)
let component = &cosmic.background.component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
} }
pub fn dialog_menu( pub fn dialog_menu(
@ -579,7 +551,7 @@ pub fn dialog_menu(
]) ])
.item_height(ItemHeight::Dynamic(40)) .item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360)) .item_width(ItemWidth::Uniform(360))
.spacing(theme::active().cosmic().spacing.space_xxxs.into()) .spacing(theme::spacing().space_xxxs.into())
.into() .into()
} }
@ -634,7 +606,7 @@ pub fn menu_bar<'a>(
responsive_menu_bar() responsive_menu_bar()
.item_height(ItemHeight::Dynamic(40)) .item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360)) .item_width(ItemWidth::Uniform(360))
.spacing(theme::active().cosmic().spacing.space_xxxs.into()) .spacing(theme::spacing().space_xxxs.into())
.into_element( .into_element(
core, core,
key_binds, key_binds,
@ -784,50 +756,33 @@ pub fn menu_bar<'a>(
) )
} }
pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> { pub fn location_context_menu(ancestor_index: usize) -> Vec<menu::Tree<tab::Message>> {
//TODO: only add some of these when in App mode //TODO: only add some of these when in App mode
let children = [ menu::items(
menu_button!(text::body(fl!("open-in-new-tab"))) &HashMap::new(),
.on_press(tab::Message::LocationMenuAction( vec![
menu::Item::Button(
fl!("open-in-new-tab"),
None,
LocationMenuAction::OpenInNewTab(ancestor_index), LocationMenuAction::OpenInNewTab(ancestor_index),
)) ),
.into(), menu::Item::Button(
menu_button!(text::body(fl!("open-in-new-window"))) fl!("open-in-new-window"),
.on_press(tab::Message::LocationMenuAction( None,
LocationMenuAction::OpenInNewWindow(ancestor_index), LocationMenuAction::OpenInNewWindow(ancestor_index),
)) ),
.into(), menu::Item::Divider,
divider::horizontal::light().into(), menu::Item::Button(
menu_button!(text::body(fl!("show-details"))) fl!("show-details"),
.on_press(tab::Message::LocationMenuAction( None,
LocationMenuAction::Preview(ancestor_index), LocationMenuAction::Preview(ancestor_index),
)) ),
.into(), menu::Item::Divider,
divider::horizontal::light().into(), menu::Item::Button(
menu_button!(text::body(fl!("add-to-sidebar"))) fl!("add-to-sidebar"),
.on_press(tab::Message::LocationMenuAction( None,
LocationMenuAction::AddToSidebar(ancestor_index), LocationMenuAction::AddToSidebar(ancestor_index),
)) ),
.into(), ],
]; )
container(column::with_children(children))
.padding(1)
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
} }

View file

@ -1,155 +1,171 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "desktop")] use bstr::{BString, ByteSlice, ByteVec};
use cosmic::desktop;
use cosmic::widget; use cosmic::widget;
pub use mime_guess::Mime; pub use mime_guess::Mime;
use rustc_hash::FxHashMap; #[cfg(feature = "desktop")]
use std::{ use notify_debouncer_full::notify;
cmp::Ordering, use rustc_hash::{FxHashMap, FxHashSet};
ffi::OsStr, use std::ffi::OsStr;
fs, io, use std::os::unix::ffi::OsStrExt;
path::{Path, PathBuf}, use std::path::{Path, PathBuf};
process, use std::sync::atomic::AtomicBool;
time::Instant, use std::sync::{Arc, RwLock, atomic};
}; use std::time::{self, Instant};
use std::{fs, io, process};
// Supported exec key field codes #[cfg(feature = "desktop")]
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"]; pub async fn watch(mut emitter: impl FnMut() + 'static + Send) {
// Deprecated field codes. The spec advises to ignore these handlers. let watcher_result = notify_debouncer_full::new_debouncer(
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"]; 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( pub fn exec_to_command(
exec: &str, exec: &str,
entry_name: &str,
entry_path: Option<&Path>,
path_opt: &[impl AsRef<OsStr>], path_opt: &[impl AsRef<OsStr>],
) -> Option<Vec<process::Command>> { ) -> Option<Vec<process::Command>> {
let args_vec = shlex::split(exec)?; let arguments = shlex::split(exec)?;
let program = args_vec.first()?;
// Skip program to make indexing easier
let args_vec = &args_vec[1..];
// Base Command instance(s) if arguments.is_empty() {
// 1. We may need to launch multiple of the same process. tracing::error!("command does not contain any arguments");
// 2. Each of those processes will need to be passed args from exec. return None;
// 3. Each of those args may appear in any order. }
// 4. Arg order should be preserved.
// let mut commands = Vec::new();
// 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. let paths = path_opt
//
// 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
.iter() .iter()
.position(|arg| EXEC_HANDLERS.contains(&arg.as_str())); .map(AsRef::as_ref)
let args_handler = field_code_pos.and_then(|i| args_vec.get(i)); .map(Some)
// msrv // Add a single `None` if no path was given.
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}")); .chain(std::iter::repeat_n(
// Number of args before the field code. None,
// This won't be an off by one err below because take is not zero indexed. if path_opt.is_empty() { 1 } else { 0 },
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());
for path in path_opt.iter().map(AsRef::as_ref) { for path in paths {
// TODO: %f and %F need to handle non-file URLs (see spec) let mut batch_process = false;
if from_file_or_dir(path).is_none() { let mut args = Vec::with_capacity(arguments.len());
log::warn!("Desktop file expects a file path instead of a URL: {path:?}"); 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 if !new_argument.is_empty() {
} args.push(new_argument);
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:?}");
} }
// 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 let mut command = process::Command::new(&arguments[0]);
for arg in args_vec.iter().skip(field_code_pos) {
match arg.as_str() { for arg in args {
// Consume path field codes or fail on codes we don't handle yet match arg.to_os_str() {
field_code if arg.starts_with('%') => { Ok(arg) => {
if !EXEC_HANDLERS.contains(&field_code) command.arg(arg);
&& !DEPRECATED_HANDLERS.contains(&field_code) }
{ Err(_) => {
log::warn!("unsupported Exec code {field_code:?} in {exec:?}"); tracing::error!("invalid string encoding in command");
return None; return None;
} }
} }
arg => { }
for process in &mut processes {
process.arg(arg); commands.push(command);
}
} if !batch_process {
break;
} }
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
for command in &processes { for command in &commands {
log::debug!( log::debug!(
"Parsed program {} with args: {:?}", "Parsed program {} with args: {:?}",
command.get_program().to_string_lossy(), 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> { #[derive(Clone, Copy, Debug, Eq, PartialEq)]
url::Url::from_file_path(&path) pub enum MimeAppMatch {
.ok() Exact,
.or_else(|| url::Url::from_directory_path(&path).ok()) Related,
Other,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -172,14 +189,42 @@ pub struct MimeApp {
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
pub name: String, pub name: String,
pub exec: Option<String>, pub exec: Option<String>,
pub icon: widget::icon::Handle, icon_name: Box<str>,
pub is_default: bool, icon: std::sync::OnceLock<widget::icon::Handle>,
is_default: Arc<RwLock<FxHashSet<Box<str>>>>,
no_display: Arc<AtomicBool>,
} }
impl MimeApp { impl MimeApp {
//TODO: move to libcosmic, support multiple files //TODO: move to libcosmic, support multiple files
pub fn command<O: AsRef<OsStr>>(&self, path_opt: &[O]) -> Option<Vec<process::Command>> { 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 { pub struct MimeAppCache {
apps: Vec<MimeApp>, apps: Vec<Arc<MimeApp>>,
cache: FxHashMap<Mime, Vec<MimeApp>>, cache: FxHashMap<Mime, Vec<Arc<MimeApp>>>,
icons: FxHashMap<Mime, Box<[widget::icon::Handle]>>, terminals: Vec<Arc<MimeApp>>,
terminals: Vec<MimeApp>,
} }
impl MimeAppCache { impl MimeAppCache {
@ -229,154 +246,273 @@ impl MimeAppCache {
let mut mime_app_cache = Self { let mut mime_app_cache = Self {
apps: Vec::new(), apps: Vec::new(),
cache: FxHashMap::default(), cache: FxHashMap::default(),
icons: FxHashMap::default(),
terminals: Vec::new(), terminals: Vec::new(),
}; };
mime_app_cache.reload(); mime_app_cache.reload();
mime_app_cache 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"))] #[cfg(not(feature = "desktop"))]
pub fn reload(&mut self) {} 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")] #[cfg(feature = "desktop")]
pub fn reload(&mut self) { pub fn reload(&mut self) {
use crate::localize::LANGUAGE_SORTER; use crate::localize::LANGUAGE_SORTER;
use crate::mime_icon;
use cosmic::desktop::fde;
use std::borrow::Cow;
let start = Instant::now(); let start = Instant::now();
self.apps.clear(); self.apps.clear();
self.cache.clear(); self.cache.clear();
self.icons.clear();
self.terminals.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 mut list = cosmic_mime_apps::List::default();
let paths = cosmic_mime_apps::list_paths(); let paths = cosmic_mime_apps::list_paths();
list.load_from_paths(&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 for desktop_entry in desktop_entries {
.added_associations let name = desktop_entry
.iter() .name(&locales)
.chain(list.default_apps.iter()) .unwrap_or_else(|| Cow::Borrowed(desktop_entry.id()));
{
for filename in filenames { let app = Arc::new(MimeApp {
log::trace!("add {mime}={filename}"); id: desktop_entry.appid.clone(),
let apps = self path: Some(desktop_entry.path.clone()),
.cache name: name.into(),
.entry(mime.clone()) exec: desktop_entry.exec().map(String::from),
.or_insert_with(|| Vec::with_capacity(1)); icon_name: desktop_entry.icon().unwrap_or_default().into(),
if !apps.iter().any(|x| filename_eq(&x.path, filename)) { icon: std::sync::OnceLock::new(),
if let Some(app) = all_apps.iter().find(|&x| filename_eq(&x.path, filename)) { is_default: Arc::new(RwLock::default()),
apps.push(MimeApp::from(app)); no_display: Arc::new(AtomicBool::new(false)),
} else { });
log::info!(
"failed to add association for {mime:?}: application {filename:?} not found" 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() { // Remove associations
for filename in filenames { for (mut removed_mime, removed_apps) in &list.removed_associations {
log::trace!("remove {mime}={filename}"); let _unaliased;
if let Some(apps) = self.cache.get_mut(mime) { if let Some(unaliased) = shared_mime_info.unalias_mime_type(removed_mime) {
apps.retain(|x| !filename_eq(&x.path, filename)); 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() { // Fetch defaults and sort apps by their default precedence.
for filename in filenames { for (mime, mut apps) in std::mem::take(&mut self.cache).into_iter() {
log::trace!("default {mime}={filename}"); let defaults = list.default_app_for(&mime);
if let Some(apps) = self.cache.get_mut(mime) { let aliased_defaults = aliased_mimes
let mut found = false; .get(&mime)
for app in apps.iter_mut() { .and_then(|mime| list.default_app_for(mime));
if filename_eq(&app.path, filename) {
app.is_default = true; let cache = self
found = true; .cache
} else { .entry(mime.clone())
app.is_default = false; .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 { 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 let associated: rustc_hash::FxHashSet<&str> = self
self.apps .cache
.sort_by(|a, b| match (a.is_default, b.is_default) { .values()
(true, false) => Ordering::Less, .flatten()
(false, true) => Ordering::Greater, .map(|app| app.id.as_str())
_ => LANGUAGE_SORTER.compare(&a.name, &b.name), .collect();
}); for app in &self.apps {
for apps in self.cache.values_mut() { app.no_display.store(
apps.sort_by(|a, b| match (a.is_default, b.is_default) { !associated.contains(app.id.as_str()),
(true, false) => Ordering::Less, atomic::Ordering::Relaxed,
(false, true) => Ordering::Greater, );
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
} }
// 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(); 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 &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) self.cache.get(key).map_or(&[], Vec::as_slice)
} }
pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] { pub fn icons(&self, key: &Mime) -> Vec<widget::icon::Handle> {
self.icons.get(key).map_or(&[], Box::as_ref) self.cache
.get(key)
.map_or_else(Vec::new, |apps| apps.iter().map(|app| app.icon()).collect())
} }
fn get_default_terminal(&self) -> Option<String> { fn get_default_terminal(&self) -> Option<String> {
@ -394,14 +530,19 @@ impl MimeAppCache {
.map(|string| string.trim().replace(".desktop", "")) .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 //TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec
// The current approach works but might not adhere to the spec (yet) // The current approach works but might not adhere to the spec (yet)
// Look for and return preferred terminals // Look for and return preferred terminals
//TODO: fallback order beyond cosmic-term? //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() { if let Some(id) = self.get_default_terminal() {
preference_order.insert(0, id); preference_order.insert(0, id);
@ -475,11 +616,43 @@ impl Default for MimeAppCache {
mod tests { mod tests {
use super::exec_to_command; 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] #[test]
fn one_path_f_field_code() { fn one_path_f_field_code() {
let exec = "/usr/bin/foo %f"; let exec = "/usr/bin/foo %f";
let paths = ["file1"]; 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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -494,31 +667,40 @@ mod tests {
#[test] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn one_path_F_field_code() { fn one_path_F_field_code() {
let exec = "/usr/bin/bar %F"; let exec = "/usr/bin/cosmic-term -w %F";
let paths = ["cat"]; let paths = ["/home/user"];
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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap()); assert_eq!(
assert_eq!("cat", 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] #[test]
fn one_path_u_field_code() { fn one_path_u_field_code() {
let exec = "/usr/bin/foobar %u"; let exec = "/usr/bin/cosmic-term -w %u";
let paths = ["/home/josh/krumpli"]; let paths = ["/home/user"];
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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
let mut args = command.get_args();
assert_eq!("/usr/bin/foobar", command.get_program().to_str().unwrap());
assert_eq!( assert_eq!(
*paths.first().unwrap(), "/usr/bin/cosmic-term",
command.get_args().next().unwrap().to_str().unwrap() 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] #[test]
@ -526,7 +708,8 @@ mod tests {
fn one_path_U_field_code() { fn one_path_U_field_code() {
let exec = "/usr/bin/rmrfbye %U"; let exec = "/usr/bin/rmrfbye %U";
let paths = ["/"]; 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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -542,7 +725,8 @@ mod tests {
"/usr/share/games/psp/miku.iso", "/usr/share/games/psp/miku.iso",
"/usr/share/games/psp/eternia.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()); assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) { 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/hr.wad",
"/usr/share/games/doom2/hrmus.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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -584,7 +769,8 @@ mod tests {
"https://redox-os.org/", "https://redox-os.org/",
"https://system76.com/", "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()); assert_eq!(paths.len(), commands.len());
for (command, path) in commands.into_iter().zip(paths.iter()) { for (command, path) in commands.into_iter().zip(paths.iter()) {
@ -607,7 +793,8 @@ mod tests {
"frieren01.mkv", "frieren01.mkv",
"rtmp://example.org/this/video/doesnt/exist.avi", "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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -635,7 +822,8 @@ mod tests {
"@@u", "@@u",
]; ];
let paths = ["file1.rs", "file2.rs"]; 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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -658,7 +846,8 @@ mod tests {
"file:///usr/share/games/roguelike/mods/mod1", "file:///usr/share/games/roguelike/mods/mod1",
"file:///usr/share/games/roguelike/mods/mod2", "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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();
@ -691,7 +880,8 @@ mod tests {
]; ];
let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"]; let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"];
let args_trailing = ["@@"]; 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()); assert_eq!(1, commands.len());
let command = commands.first().unwrap(); let command = commands.first().unwrap();

View file

@ -3,11 +3,9 @@
use cosmic::widget::icon; use cosmic::widget::icon;
use mime_guess::Mime; use mime_guess::Mime;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{ use std::fs;
fs, use std::path::Path;
path::Path, use std::sync::{LazyLock, Mutex};
sync::{LazyLock, Mutex},
};
pub const FALLBACK_MIME_ICON: &str = "text-x-generic"; pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
@ -17,28 +15,21 @@ struct MimeIconKey {
size: u16, size: u16,
} }
struct MimeIconCache { #[derive(Default)]
pub struct MimeIconCache {
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>, cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
#[cfg(unix)] #[cfg(unix)]
shared_mime_info: xdg_mime::SharedMimeInfo, pub shared_mime_info: xdg_mime::SharedMimeInfo,
} }
impl MimeIconCache { impl MimeIconCache {
pub fn new() -> Self {
Self {
cache: FxHashMap::default(),
#[cfg(unix)]
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
}
}
#[cfg(not(unix))] #[cfg(not(unix))]
pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> { pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
None None
} }
#[cfg(unix)] #[cfg(unix)]
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> { fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
self.cache self.cache
.entry(key) .entry(key)
.or_insert_with_key(|key| { .or_insert_with_key(|key| {
@ -47,7 +38,7 @@ impl MimeIconCache {
return None; return None;
} }
let icon_name = icon_names.remove(0); 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() { if !icon_names.is_empty() {
let fallback_names = let fallback_names =
icon_names.into_iter().map(std::borrow::Cow::from).collect(); icon_names.into_iter().map(std::borrow::Cow::from).collect();
@ -58,8 +49,9 @@ impl MimeIconCache {
.clone() .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))] #[cfg(not(unix))]
pub fn mime_for_path( pub fn mime_for_path(
@ -80,6 +72,7 @@ pub fn mime_for_path(
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
// Try the shared mime info cache first // Try the shared mime info cache first
let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type(); let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
gb.zero_size(false);
if remote { if remote {
if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) { if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
gb.file_name(file_name); 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(); let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
match mime_icon_cache.get(MimeIconKey { mime, size }) { match mime_icon_cache.get(MimeIconKey { mime, size }) {
Some(handle) => handle, 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(); let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
mime_icon_cache.shared_mime_info.get_parents_aliased(mime) 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,21 +1,32 @@
use cosmic::{ use cosmic::iced::futures::SinkExt;
Task, use cosmic::iced::{Subscription, stream};
iced::{Subscription, futures::SinkExt, stream}, use cosmic::{Task, widget};
widget, use gio::glib;
}; use gio::prelude::*;
use gio::{glib, prelude::*}; use std::any::TypeId;
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc}; 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 tokio::sync::mpsc;
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage}; use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
use crate::{ use crate::config::IconSizes;
config::IconSizes, use crate::err_str;
err_str, use crate::tab::{self, ChecksumState, DirSize, ItemMetadata, ItemThumbnail, Location};
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
};
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri"; const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
// Attributes requested when listing a network directory.
const SCAN_ATTRIBUTES: &str = "standard::name,\
standard::display-name,\
standard::type,\
standard::size,\
standard::icon,\
standard::is-hidden,\
time::modified";
fn resolve_uri(uri: &str) -> (String, gio::File) { fn resolve_uri(uri: &str) -> (String, gio::File) {
let file = gio::File::for_uri(uri); let file = gio::File::for_uri(uri);
// Resolve the target-uri if it exists // Resolve the target-uri if it exists
@ -117,9 +128,27 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
Box::from([]) Box::from([])
}; };
// `filesystem::remote` belongs to the filesystem namespace, which `enumerate_children`
// never fills in, so it has to be queried once for the directory being listed.
let remote = file
.query_filesystem_info(
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
gio::Cancellable::NONE,
)
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
.unwrap_or_else(|err| {
log::warn!("failed to get GIO filesystem info for {uri}: {err}");
// Assume remote, so per-entry work is skipped rather than retried
true
});
let mut items = Vec::new(); let mut items = Vec::new();
for info_res in file for info_res in file
.enumerate_children("*", gio::FileQueryInfoFlags::NONE, gio::Cancellable::NONE) .enumerate_children(
SCAN_ATTRIBUTES,
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
)
.map_err(err_str)? .map_err(err_str)?
{ {
let info = info_res.map_err(err_str)?; let info = info_res.map_err(err_str)?;
@ -131,13 +160,17 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: what is the best way to resolve shortcuts? //TODO: what is the best way to resolve shortcuts?
let location = Location::Network(uri, display_name.clone(), file.child(&name).path()); let location = Location::Network(uri, display_name.clone(), file.child(&name).path());
let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) { let metadata = if force_dir {
ItemMetadata::SimpleDir { entries: 0 }
} else {
let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED); let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED);
let is_dir = matches!(info.file_type(), gio::FileType::Directory); let is_dir = matches!(info.file_type(), gio::FileType::Directory);
let size_opt = (!is_dir).then_some(info.size() as u64); let size_opt = (!is_dir).then_some(info.size() as u64);
let mut children_opt = None; let mut children_opt = None;
if is_dir { // Counting children costs a directory listing per entry, which is far too
// expensive on a remote filesystem
if is_dir && !remote {
if let Some(path) = file.child(&name).path() { if let Some(path) = file.child(&name).path() {
//TODO: calculate children in the background (and make it cancellable?) //TODO: calculate children in the background (and make it cancellable?)
match std::fs::read_dir(&path) { match std::fs::read_dir(&path) {
@ -157,9 +190,8 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
mtime, mtime,
size_opt, size_opt,
children_opt, children_opt,
is_dir,
} }
} else {
ItemMetadata::SimpleDir { entries: 0 }
}; };
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = { let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
@ -214,6 +246,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
//TODO: scan directory size on gvfs mounts? //TODO: scan directory size on gvfs mounts?
dir_size: DirSize::NotDirectory, dir_size: DirSize::NotDirectory,
cut: false, cut: false,
checksums: ChecksumState::default(),
}); });
} }
Ok(items) Ok(items)
@ -656,7 +689,7 @@ impl Mounter for Gvfs {
) )
} }
fn network_drive(&self, uri: String) -> Task<()> { fn network_drive(&self, uri: String) -> Task<bool> {
let command_tx = self.command_tx.clone(); let command_tx = self.command_tx.clone();
Task::perform( Task::perform(
async move { async move {
@ -665,9 +698,15 @@ impl Mounter for Gvfs {
command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap(); command_tx.send(Cmd::NetworkDrive(uri, res_tx)).unwrap();
res_rx.await res_rx.await
}, },
|x| { |result| match result {
if let Err(err) = x { Ok(Ok(())) => true,
Ok(Err(err)) => {
log::error!("{err:?}"); log::error!("{err:?}");
false
}
Err(err) => {
log::error!("{err:?}");
false
} }
}, },
) )

View file

@ -1,13 +1,13 @@
use cosmic::{Task, iced::Subscription, widget}; use cosmic::iced::Subscription;
use std::{ use cosmic::{Task, widget};
collections::BTreeMap, use std::collections::BTreeMap;
fmt, use std::fmt;
path::PathBuf, use std::path::PathBuf;
sync::{Arc, LazyLock}, use std::sync::{Arc, LazyLock};
};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::{config::IconSizes, tab}; use crate::config::IconSizes;
use crate::tab;
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
mod 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 { match self {
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
Self::Gvfs(item) => item.icon(symbolic), Self::Gvfs(item) => item.icon(_symbolic),
Self::None => unreachable!(), Self::None => unreachable!(),
} }
} }
@ -103,6 +103,7 @@ impl MounterItem {
pub type MounterItems = Vec<MounterItem>; pub type MounterItems = Vec<MounterItem>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum MounterMessage { pub enum MounterMessage {
Items(MounterItems), Items(MounterItems),
MountResult(MounterItem, Result<bool, String>), MountResult(MounterItem, Result<bool, String>),
@ -114,7 +115,7 @@ pub trait Mounter: Send + Sync {
fn items(&self, sizes: IconSizes) -> Option<MounterItems>; fn items(&self, sizes: IconSizes) -> Option<MounterItems>;
//TODO: send result //TODO: send result
fn mount(&self, item: MounterItem) -> Task<()>; fn mount(&self, item: MounterItem) -> Task<()>;
fn network_drive(&self, uri: String) -> Task<()>; fn network_drive(&self, uri: String) -> Task<bool>;
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>; fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>; fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>;
fn unmount(&self, item: MounterItem) -> Task<()>; fn unmount(&self, item: MounterItem) -> Task<()>;

View file

@ -3,21 +3,17 @@
use std::time::Instant; use std::time::Instant;
use crate::tab::DOUBLE_CLICK_DURATION; use crate::tab::DOUBLE_CLICK_DURATION;
use cosmic::{ use cosmic::iced::core::border::Border;
Element, Renderer, Theme, use cosmic::iced::core::event::Event;
iced::core::{ use cosmic::iced::core::mouse::{self, click};
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, use cosmic::iced::core::renderer::{self, Quad, Renderer as _};
border::Border, use cosmic::iced::core::widget::{Operation, Tree, tree};
event::Event, use cosmic::iced::core::{
layout, Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
mouse::{self, click}, overlay, touch,
overlay,
renderer::{self, Quad, Renderer as _},
touch,
widget::{Operation, Tree, tree},
},
widget::Id,
}; };
use cosmic::widget::Id;
use cosmic::{Element, Renderer, Theme};
/// Emit messages on mouse events. /// Emit messages on mouse events.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
@ -218,8 +214,9 @@ impl<'a, Message, F> OnAutoScroll<'a, Message> for F where F: Fn(Option<f32>) ->
pub trait OnMouseButton<'a, Message>: Fn(Option<Point>) -> Message + 'a {} pub trait OnMouseButton<'a, Message>: Fn(Option<Point>) -> Message + 'a {}
impl<'a, Message, F> OnMouseButton<'a, Message> for F where F: Fn(Option<Point>) -> Message + 'a {} impl<'a, Message, F> OnMouseButton<'a, Message> for F where F: Fn(Option<Point>) -> Message + 'a {}
pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Message + 'a {} pub trait OnDrag<'a, Message>: Fn(Option<Rectangle>) -> Option<Message> + 'a {}
impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Message + 'a {} impl<'a, Message, F> OnDrag<'a, Message> for F where F: Fn(Option<Rectangle>) -> Option<Message> + 'a
{}
pub trait OnResize<'a, Message>: Fn(Rectangle) -> Message + 'a {} pub trait OnResize<'a, Message>: Fn(Rectangle) -> Message + 'a {}
impl<'a, Message, F> OnResize<'a, Message> for F where F: Fn(Rectangle) -> Message + 'a {} impl<'a, Message, F> OnResize<'a, Message> for F where F: Fn(Rectangle) -> Message + 'a {}
@ -567,6 +564,16 @@ fn update<Message: Clone>(
state.last_position = position_in; state.last_position = position_in;
} }
if state.drag_initiated.is_some()
&& matches!(
event,
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. })
)
{
shell.request_redraw();
}
if let Event::Mouse(mouse::Event::CursorMoved { position }) = event { if let Event::Mouse(mouse::Event::CursorMoved { position }) = event {
let virtual_position = Point::new( let virtual_position = Point::new(
viewport.x - layout_bounds.x + position.x, viewport.x - layout_bounds.x + position.x,
@ -643,6 +650,7 @@ fn update<Message: Clone>(
{ {
state.drag_initiated = None; state.drag_initiated = None;
state.prev_click = None; state.prev_click = None;
shell.request_redraw();
if let Some(message) = widget.on_drag_end.as_ref() { if let Some(message) = widget.on_drag_end.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds))); shell.publish(message(cursor.position_in(layout_bounds)));
} }
@ -662,7 +670,9 @@ fn update<Message: Clone>(
state.prev_click = None; state.prev_click = None;
return; return;
} }
state.drag_initiated = None; if state.drag_initiated.take().is_some() {
shell.request_redraw();
}
if let Some(message) = widget.on_release.as_ref() { if let Some(message) = widget.on_release.as_ref() {
shell.publish(message(cursor.position_in(layout_bounds))); shell.publish(message(cursor.position_in(layout_bounds)));
@ -789,12 +799,13 @@ fn update<Message: Clone>(
} }
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) { if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
shell.publish(message(drag_rect.intersection(&layout_bounds).map( let rect_opt = drag_rect.intersection(&layout_bounds).map(|mut rect| {
|mut rect| { rect.x -= layout_bounds.x;
rect.x -= layout_bounds.x; rect.y -= layout_bounds.y;
rect.y -= layout_bounds.y; rect
rect });
}, if let Some(message) = message(rect_opt) {
))); shell.publish(message);
}
} }
} }

View file

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

View file

@ -6,15 +6,21 @@ use crate::operation::{OperationError, sync_to_disk};
use anyhow::Context as AnyhowContext; use anyhow::Context as AnyhowContext;
use compio::BufResult; use compio::BufResult;
use compio::buf::{IntoInner, IoBuf}; 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 compio::io::{AsyncReadAt, AsyncWriteAt};
use cosmic::iced::futures; use cosmic::iced::futures;
#[cfg(feature = "gvfs")]
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use std::cell::Cell;
use std::error::Error;
use std::fs;
use std::future::Future; use std::future::Future;
use std::ops::ControlFlow;
use std::path::PathBuf;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf};
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
@ -448,7 +454,7 @@ impl Op {
} }
} }
let (from_file, metadata, to_file) = cosmic::iced::futures::join!( let (from_file_open_result, metadata, to_file_open_result) = cosmic::iced::futures::join!(
async { async {
compio::fs::OpenOptions::new() compio::fs::OpenOptions::new()
.read(true) .read(true)
@ -464,21 +470,39 @@ impl Op {
.write(true) .write(true)
.open(&self.to) .open(&self.to)
.await .await
.with_context(|| format!("failed to open {} for writing", self.to.display()))
} }
); );
let from_file = from_file?; let from_file = from_file_open_result?;
let mut to_file = to_file?;
let mut to_file = match to_file_open_result {
Ok(file) => file,
#[cfg(not(feature = "gvfs"))]
Err(why) => {
_ = from_file.close().await;
return Err(why)
.with_context(|| format!("failed to open {} for writing", self.to.display()))
.map_err(Into::into);
}
#[cfg(feature = "gvfs")]
Err(_why) => {
_ = from_file.close().await;
return self
.gio_file_copy(ctx, progress)
.await
.map(|()| true)
.map_err(Into::into);
}
};
progress.total_bytes = metadata.as_ref().map(|m| m.len()); progress.total_bytes = metadata.as_ref().map(|m| m.len());
(ctx.on_progress)(self, &progress); (ctx.on_progress)(self, &progress);
if let Some(metadata) = metadata.as_ref() { if let Some(metadata) = metadata.as_ref()
if let Err(why) = to_file.set_permissions(metadata.permissions()).await { && 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) { // This error is not propagated upwards as some filesystems do not support setting permissions
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),); if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
} tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
} }
} }

1331
src/tab.rs

File diff suppressed because it is too large Load diff

View file

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

View file

@ -5,12 +5,12 @@
use cosmic::desktop::fde::GenericEntry; use cosmic::desktop::fde::GenericEntry;
use mime_guess::Mime; use mime_guess::Mime;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
#[cfg(feature = "desktop")]
use std::{fs, time::Instant};
use std::{ use std::{
fs,
path::Path, path::Path,
process, process,
sync::{LazyLock, Mutex}, sync::{LazyLock, Mutex},
time::Instant,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -175,3 +175,25 @@ pub fn thumbnailer(mime: &Mime) -> Vec<Thumbnailer> {
let thumbnailer_cache = THUMBNAILER_CACHE.lock().unwrap(); let thumbnailer_cache = THUMBNAILER_CACHE.lock().unwrap();
thumbnailer_cache.get(mime) thumbnailer_cache.get(mime)
} }
#[cfg(all(test, feature = "desktop"))]
mod tests {
use super::Thumbnailer;
use std::path::Path;
#[test]
fn command_expands_thumbnailer_arguments() {
let thumbnailer = Thumbnailer {
exec: "cosmic-files-thumbnailer %o --size %s %i".to_string(),
};
let command = thumbnailer
.command(Path::new("input.exe"), Path::new("output.png"), 128)
.expect("valid thumbnailer command");
assert_eq!(command.get_program(), "cosmic-files-thumbnailer");
assert_eq!(
command.get_args().collect::<Vec<_>>(),
["output.png", "--size", "128", "input.exe"]
);
}
}

View file

@ -1,11 +1,30 @@
use cosmic::widget; use cosmic::widget;
use regex::Regex; use regex::Regex;
use std::{collections::HashSet, path::PathBuf}; use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use crate::{ use crate::config::IconSizes;
config::IconSizes, use crate::tab::{Item, SearchItem};
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 { pub trait TrashExt {
fn is_empty() -> bool { fn is_empty() -> bool {
@ -27,7 +46,7 @@ pub trait TrashExt {
Vec::new() 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 { fn icon(icon_size: u16) -> widget::icon::Handle {
widget::icon::from_name(if Self::is_empty() { widget::icon::from_name(if Self::is_empty() {
@ -50,6 +69,67 @@ 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 = trash_files_dir(p)?;
let root = files.parent()?;
let top = p.strip_prefix(files).ok()?.components().next()?;
let mut trashinfo_name = top.as_os_str().to_os_string();
trashinfo_name.push(".trashinfo");
let info = std::fs::read_to_string(root.join("info").join(trashinfo_name)).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)
}
static TRASH_FOLDERS: LazyLock<HashSet<PathBuf>> = LazyLock::new(|| {
Trash::folders().unwrap_or_else(|e| {
log::warn!("failed to list trash folders: {}", e);
HashSet::new()
})
});
fn is_trash_root(root: &Path) -> bool {
if TRASH_FOLDERS.is_empty() {
root.join("info").is_dir()
} else {
TRASH_FOLDERS.contains(root)
}
}
fn trash_files_dir(path: &Path) -> Option<&Path> {
path.ancestors()
.find(|a| a.ends_with("files") && a.parent().is_some_and(is_trash_root))
}
/// Check whether a path is inside any trash `files/` directory.
pub fn is_trash_path(path: &Path) -> bool {
trash_files_dir(path).is_some()
}
pub struct Trash; pub struct Trash;
// This config statement is from trash::os_limited // This config statement is from trash::os_limited
@ -81,7 +161,8 @@ impl TrashExt for Trash {
} }
fn scan(sizes: IconSizes) -> Vec<Item> { 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; use std::cmp::Ordering;
let entries = match trash::os_limited::list() { let entries = match trash::os_limited::list() {
@ -142,4 +223,12 @@ impl TrashExt for Trash {
not(target_os = "android") 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 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 DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap();
static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap(); static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap();