Compare commits
1 commit
local/libc
...
backup/pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bcfe7a1f5 |
62 changed files with 1349 additions and 2871 deletions
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"format_on_save": "on",
|
|
||||||
"lsp": {
|
|
||||||
"rust-analyzer": {
|
|
||||||
"initialization_options": {
|
|
||||||
"check": {
|
|
||||||
"command": "clippy",
|
|
||||||
},
|
|
||||||
"rustfmt": {
|
|
||||||
"extraArgs": ["+nightly"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
818
Cargo.lock
generated
818
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
51
Cargo.toml
51
Cargo.toml
|
|
@ -1,17 +1,17 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "1.0.14"
|
version = "1.0.11"
|
||||||
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.93"
|
rust-version = "1.90"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
jiff = "0.2"
|
jiff = "0.2"
|
||||||
jiff-icu = "0.2"
|
jiff-icu = "0.2"
|
||||||
icu = { version = "2.2.0", features = ["compiled_data"] }
|
icu = { version = "2.1.1", features = ["compiled_data"] }
|
||||||
cctk = { path = "../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", 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 }
|
||||||
|
|
@ -24,7 +24,7 @@ log = "0.4"
|
||||||
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.4"
|
open = "5.3.3"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
rustc-hash = "2.1"
|
rustc-hash = "2.1"
|
||||||
|
|
@ -36,15 +36,15 @@ tokio = { version = "1", features = ["process", "sync"] }
|
||||||
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
|
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
wayland-client = { version = "0.31.14", optional = true }
|
wayland-client = { version = "0.31.13", 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.45"
|
tar = "0.4.44"
|
||||||
lzma-rust2 = { version = "0.16", optional = true }
|
lzma-rust2 = { version = "0.16", optional = true }
|
||||||
ordermap = { version = "1.2.0", features = ["serde"] }
|
ordermap = { version = "1.1.0", features = ["serde"] }
|
||||||
# Internationalization
|
# Internationalization
|
||||||
i18n-embed = { version = "0.16", features = [
|
i18n-embed = { version = "0.16", features = [
|
||||||
"fluent-system",
|
"fluent-system",
|
||||||
|
|
@ -61,11 +61,10 @@ 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.23", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.22", 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]
|
||||||
|
|
@ -73,9 +72,8 @@ version = "0.18"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["fs", "io", "macros", "polling", "runtime"]
|
features = ["fs", "io", "macros", "polling", "runtime"]
|
||||||
|
|
||||||
# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch).
|
[dependencies.libcosmic]
|
||||||
[dependencies.libcosmic-yoda]
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
path = "../libcosmic"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
#TODO: a11y feature crashes
|
#TODO: a11y feature crashes
|
||||||
features = [
|
features = [
|
||||||
|
|
@ -84,7 +82,7 @@ features = [
|
||||||
"autosize",
|
"autosize",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
"tokio",
|
"tokio",
|
||||||
"wayland",
|
"winit",
|
||||||
"surface-message",
|
"surface-message",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -112,15 +110,15 @@ default = [
|
||||||
"wayland",
|
"wayland",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
dbus-config = ["libcosmic-yoda/dbus-config"]
|
dbus-config = ["libcosmic/dbus-config"]
|
||||||
desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
desktop = ["libcosmic/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-yoda/wayland", "dep:cctk", "dep:wayland-client"]
|
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
|
||||||
wgpu = ["libcosmic-yoda/wgpu"]
|
wgpu = ["libcosmic/wgpu"]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
@ -146,12 +144,19 @@ fastrand = "2"
|
||||||
test-log = "0.2"
|
test-log = "0.2"
|
||||||
tokio = { version = "1", features = ["rt", "macros"] }
|
tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
|
|
||||||
# Yoda fork — libcosmic dep is now a direct path dep (libcosmic-yoda above),
|
# [patch.'https://github.com/pop-os/cosmic-text']
|
||||||
# no [patch] block needed anymore. Keeping the block below would be a no-op
|
# cosmic-text = { path = "../cosmic-text" }
|
||||||
# since nothing in the dep graph still asks for pop-os/libcosmic.git.
|
|
||||||
|
|
||||||
[patch.'https://github.com/pop-os/cosmic-text.git']
|
[patch.'https://github.com/pop-os/libcosmic']
|
||||||
cosmic-text = { path = "../cosmic-text" }
|
libcosmic = { path = "../libcosmic" }
|
||||||
|
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
|
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||||
|
iced_futures = { path = "../libcosmic/iced/futures" }
|
||||||
|
iced_winit = { path = "../libcosmic/iced/winit" }
|
||||||
|
|
||||||
|
|
||||||
|
# [patch.'https://github.com/pop-os/smithay-clipboard']
|
||||||
|
# smithay-clipboard = { path = "../smithay-clipboard" }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["cosmic-files-applet"]
|
members = ["cosmic-files-applet"]
|
||||||
|
|
|
||||||
3
build.rs
3
build.rs
|
|
@ -1,5 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::{env, fs, path::PathBuf};
|
||||||
use std::{env, fs};
|
|
||||||
use xdgen::{App, Context, FluentString};
|
use xdgen::{App, Context, FluentString};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files-applet"
|
name = "cosmic-files-applet"
|
||||||
version = "1.0.14"
|
version = "1.0.11"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
18
debian/changelog
vendored
18
debian/changelog
vendored
|
|
@ -1,21 +1,3 @@
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
# 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`.
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use cosmic_files::operation::recursive::{Context, Method};
|
use cosmic_files::operation::recursive::Method;
|
||||||
use cosmic_files::operation::{Controller, ReplaceResult};
|
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
|
||||||
use std::error::Error;
|
use std::{error::Error, io, path::PathBuf};
|
||||||
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>> {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use cosmic::app::{self, Core, Settings, Task};
|
use cosmic::{
|
||||||
use cosmic::iced::{Subscription, window};
|
Application, Element,
|
||||||
use cosmic::{Application, Element, executor, widget};
|
app::{self, Core, Settings, Task},
|
||||||
|
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;
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -372,14 +372,3 @@ 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 = شغِّل
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ name-no-slashes = El nom no pot contenir barres.
|
||||||
|
|
||||||
## Open/Save Dialog
|
## Open/Save Dialog
|
||||||
|
|
||||||
cancel = Cancel·lar
|
cancel = Cancel·la
|
||||||
create = Crea
|
create = Crea
|
||||||
open = Obre
|
open = Obre
|
||||||
open-file = Obre el fixer
|
open-file = Obre el fixer
|
||||||
|
|
@ -352,4 +352,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ size = Velikost
|
||||||
|
|
||||||
## Empty Trash Dialog
|
## Empty Trash Dialog
|
||||||
|
|
||||||
empty-trash = Vyprázdnit koš
|
empty-trash = Vysypat 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 = Vyprazdňování koše ({ $progress })...
|
emptying-trash = Vysypávání koše ({ $progress })...
|
||||||
emptied-trash = Koš vyprázdněn
|
emptied-trash = Koš vysypá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 = Vyprázdnit koš?
|
empty-trash-title = Vysypat 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,13 +418,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -429,12 +429,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -1,330 +1,15 @@
|
||||||
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 = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager;
|
keywords = Φάκελος;Διαχειριστής;
|
||||||
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 = Μετονομασία
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ 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
|
||||||
|
|
@ -117,7 +116,6 @@ 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
|
|
||||||
|
|
||||||
## Replace Dialog
|
## Replace Dialog
|
||||||
replace = Replace
|
replace = Replace
|
||||||
|
|
@ -140,11 +138,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -405,13 +405,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ save-file = Enregistrer 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
|
||||||
|
|
@ -131,11 +130,6 @@ 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
|
||||||
|
|
||||||
|
|
@ -443,12 +437,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -434,12 +434,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -47,13 +47,13 @@ resume = Folytatás
|
||||||
|
|
||||||
## Compress Dialog
|
## Compress Dialog
|
||||||
|
|
||||||
create-archive = Archívum létrehozása
|
create-archive = Tömörített fájl 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ási cél kiválasztása
|
extract-to-title = Kibontás mappába
|
||||||
|
|
||||||
## Empty Trash Dialog
|
## Empty Trash 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(z) „{ $name }” fájlt?
|
open-with-title = Hogyan szeretnéd megnyitni a következőt: „{ $name }”?
|
||||||
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
|
||||||
|
|
@ -113,7 +113,7 @@ rename-folder = Mappa átnevezése
|
||||||
## Replace Dialog
|
## Replace Dialog
|
||||||
|
|
||||||
replace = Csere
|
replace = Csere
|
||||||
replace-title = „{ $filename }” már létezik ezen a helyen
|
replace-title = „{ $filename }” már létezik
|
||||||
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 = Mások
|
other = Többi
|
||||||
|
|
||||||
### 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 }”
|
||||||
A(z) „{ $path }” útvonal lehet, hogy nem létezik, vagy nincs jogosultságod a megnyitásához
|
„{ $path }” lehet 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 = Kapcsolódás
|
connect = Csatlakozás
|
||||||
connect-anonymously = Kapcsolódás névtelenül
|
connect-anonymously = Csatlakozás névtelenül
|
||||||
connecting = Kapcsolódás…
|
connecting = Csatlakozá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 = Próbáld újra
|
try-again = Ú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
|
||||||
|
|
@ -330,7 +330,7 @@ items = Elemek: { $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 = Utolsó hozzáférés: { $accessed }
|
item-accessed = Hozzáférve: { $accessed }
|
||||||
calculating = Számítás…
|
calculating = Számítás…
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
@ -350,7 +350,7 @@ light = Világos
|
||||||
|
|
||||||
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 almappákban
|
||||||
type-to-search-enter-path = Elérési út megnyitása
|
type-to-search-enter-path = Elérési út megadá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…
|
||||||
|
|
@ -373,7 +373,7 @@ remove-from-recents = Eltávolítás a legutóbbiak közül
|
||||||
|
|
||||||
change-wallpaper = Háttérkép cseréje…
|
change-wallpaper = Háttérkép cseréje…
|
||||||
desktop-appearance = Asztali megjelenés…
|
desktop-appearance = Asztali megjelenés…
|
||||||
display-settings = Kijelzőbeállítások…
|
display-settings = Képernyő-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,8 +415,8 @@ 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öl
|
sort-newest-first = Legújabb előre
|
||||||
sort-oldest-first = Legrégibb elöl
|
sort-oldest-first = Legrégibb előre
|
||||||
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ó
|
||||||
|
|
@ -437,12 +437,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -319,11 +319,3 @@ 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
|
|
||||||
}.
|
|
||||||
|
|
|
||||||
|
|
@ -319,11 +319,3 @@ 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 = Орындау
|
|
||||||
|
|
|
||||||
|
|
@ -277,4 +277,3 @@ 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 = 이동 위치 선택
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
progress = { $percent }%
|
progress = { $percent }%
|
||||||
cosmic-files = COSMIC Failai
|
cosmic-files = Cosmic Files
|
||||||
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 = Keisti
|
replace = Pakeisti
|
||||||
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 = Nuostatos
|
settings = Nustatymai
|
||||||
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ą
|
||||||
|
|
@ -280,18 +280,18 @@ quit = Išeiti
|
||||||
edit = Redaguoti
|
edit = Redaguoti
|
||||||
cut = Iškirpti
|
cut = Iškirpti
|
||||||
copy = Kopijuoti
|
copy = Kopijuoti
|
||||||
paste = Įdėti
|
paste = Įklijuoti
|
||||||
select-all = Žymėti viską
|
select-all = Pažymėti viską
|
||||||
zoom-in = Artinti
|
zoom-in = Priartinti
|
||||||
default-size = Numatytas dydis
|
default-size = Numatytas dydis
|
||||||
zoom-out = Tolinti
|
zoom-out = Nutolinti
|
||||||
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 = Nuostatos...
|
menu-settings = Nustatymai...
|
||||||
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 aplinkos failų tvarkyklė
|
comment = COSMIC desktop 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,4 +317,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
Klarte ikke åpne «{ $path }».
|
Kunne ikke åpne "{ $path }".
|
||||||
«{ $path }» finnes ikke eller så har du ikke tilgang til å åpne den.
|
Den kan ikke eksistere 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 tjeneradresse
|
enter-server-address = Angi serveradresse
|
||||||
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]
|
||||||
|
|
|
||||||
|
|
@ -438,12 +438,3 @@ 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ę
|
|
||||||
|
|
|
||||||
|
|
@ -437,12 +437,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -381,12 +381,3 @@ 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 = Переименовать
|
|
||||||
|
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
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 = Умножи путању
|
|
||||||
|
|
@ -410,12 +410,3 @@ 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
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ filesystem = Файлова система
|
||||||
home = Домівка
|
home = Домівка
|
||||||
trash = Смітник
|
trash = Смітник
|
||||||
recents = Нещодавні
|
recents = Нещодавні
|
||||||
undo = Скасувати
|
undo = Відмінити
|
||||||
# List view
|
# List view
|
||||||
name = Назва
|
name = Назва
|
||||||
modified = Змінено
|
modified = Змінено
|
||||||
|
|
@ -82,11 +82,10 @@ 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 })...
|
||||||
|
|
@ -95,12 +94,11 @@ moving =
|
||||||
Переміщення { $items } { $items ->
|
Переміщення { $items } { $items ->
|
||||||
[one] елемента
|
[one] елемента
|
||||||
*[other] елементів
|
*[other] елементів
|
||||||
} з «{ $from }» до «{ $to }» ({ $progress })...
|
} з «{ $from }» в «{ $to }» ({ $progress })...
|
||||||
moved =
|
moved =
|
||||||
Переміщено { $items } { $items ->
|
Переміщено { $items } { $items ->
|
||||||
[one] елемент
|
[one] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[other] елементів
|
|
||||||
} з «{ $from }» в «{ $to }»
|
} з «{ $from }» в «{ $to }»
|
||||||
renaming = Перейменування «{ $from }» на «{ $to }»
|
renaming = Перейменування «{ $from }» на «{ $to }»
|
||||||
renamed = Перейменовано «{ $from }» на «{ $to }»
|
renamed = Перейменовано «{ $from }» на «{ $to }»
|
||||||
|
|
@ -112,8 +110,7 @@ restoring =
|
||||||
restored =
|
restored =
|
||||||
Відновлено { $items } { $items ->
|
Відновлено { $items } { $items ->
|
||||||
[one] елемент
|
[one] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[other] елементів
|
|
||||||
} з { trash }
|
} з { trash }
|
||||||
unknown-folder = невідома тека
|
unknown-folder = невідома тека
|
||||||
|
|
||||||
|
|
@ -226,7 +223,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 = Власник
|
||||||
|
|
@ -248,7 +245,7 @@ favorite-path-error-description =
|
||||||
Вилучити з бічної панелі?
|
Вилучити з бічної панелі?
|
||||||
keep = Залишити
|
keep = Залишити
|
||||||
add-network-drive = Додати мережевий диск
|
add-network-drive = Додати мережевий диск
|
||||||
connect = З’єднати
|
connect = З'єднати
|
||||||
connect-anonymously = З'єднатись анонімно
|
connect-anonymously = З'єднатись анонімно
|
||||||
connecting = З'єднання…
|
connecting = З'єднання…
|
||||||
domain = Домен
|
domain = Домен
|
||||||
|
|
@ -277,13 +274,12 @@ compressing =
|
||||||
Стиснення { $items } { $items ->
|
Стиснення { $items } { $items ->
|
||||||
[one] елемента
|
[one] елемента
|
||||||
*[other] елементів
|
*[other] елементів
|
||||||
} з «{ $from }» до «{ $to }» ({ $progress })...
|
} з "{ $from }" до "{ $to }" ({ $progress })...
|
||||||
compressed =
|
compressed =
|
||||||
Стиснуто { $items } { $items ->
|
Стиснуто { $items } { $items ->
|
||||||
[one] елемент
|
[one] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[other] елементів
|
} з "{ $from }" до "{ $to }"
|
||||||
} з «{ $from }» до «{ $to }»
|
|
||||||
deleting =
|
deleting =
|
||||||
Видалення { $items } { $items ->
|
Видалення { $items } { $items ->
|
||||||
[one] елемента
|
[one] елемента
|
||||||
|
|
@ -292,19 +288,17 @@ 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] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[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 }» надано права на виконання і відкрито
|
||||||
|
|
@ -349,8 +343,7 @@ permanently-deleting =
|
||||||
permanently-deleted =
|
permanently-deleted =
|
||||||
Остаточно вилучено { $items } { $items ->
|
Остаточно вилучено { $items } { $items ->
|
||||||
[one] елемент
|
[one] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[other] елементів
|
|
||||||
}
|
}
|
||||||
removing-from-recents =
|
removing-from-recents =
|
||||||
Вилучення { $items } { $items ->
|
Вилучення { $items } { $items ->
|
||||||
|
|
@ -360,14 +353,13 @@ removing-from-recents =
|
||||||
removed-from-recents =
|
removed-from-recents =
|
||||||
Вилучено { $items } { $items ->
|
Вилучено { $items } { $items ->
|
||||||
[one] елемент
|
[one] елемент
|
||||||
[few] елементи
|
*[other] елементи
|
||||||
*[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 = Копіювати до…
|
||||||
|
|
@ -379,12 +371,3 @@ 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 = Перейменувати
|
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ 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 }" 可能不存在或您没有权限打开它。
|
||||||
|
|
@ -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,12 +437,3 @@ 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 = 重命名
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ name-no-slashes = 名稱不能包含斜線
|
||||||
## Open/Save Dialog
|
## Open/Save Dialog
|
||||||
|
|
||||||
cancel = 取消
|
cancel = 取消
|
||||||
create = 建立
|
create = 創作
|
||||||
open = 開啟
|
open = 開啟
|
||||||
open-file = 開啟檔案
|
open-file = 開啟檔案
|
||||||
open-folder = 開啟資料夾
|
open-folder = 開啟資料夾
|
||||||
|
|
@ -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,12 +384,3 @@ 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 = 重新命名
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "1.93.0"
|
|
||||||
components = ["clippy", "rustfmt"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
imports_granularity = "Module"
|
|
||||||
845
src/app.rs
845
src/app.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::mime_icon::mime_for_path;
|
use crate::{
|
||||||
use crate::operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk};
|
mime_icon::mime_for_path,
|
||||||
|
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||||
|
};
|
||||||
use cosmic::iced::futures;
|
use cosmic::iced::futures;
|
||||||
use jiff::Zoned;
|
use jiff::{Zoned, civil::DateTime, tz::TimeZone};
|
||||||
use jiff::civil::DateTime;
|
use std::{
|
||||||
use jiff::tz::TimeZone;
|
collections::HashSet,
|
||||||
use std::collections::HashSet;
|
fs,
|
||||||
use std::fs;
|
io::{self, Read, Write},
|
||||||
use std::io::{self, Read, Write};
|
path::{Path, PathBuf},
|
||||||
use std::path::{Path, PathBuf};
|
time::SystemTime,
|
||||||
use std::time::SystemTime;
|
};
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
|
|
||||||
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
||||||
|
|
@ -110,8 +112,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
) -> zip::result::ZipResult<()> {
|
) -> zip::result::ZipResult<()> {
|
||||||
use std::ffi::OsString;
|
use std::{ffi::OsString, fs};
|
||||||
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>>(
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
// 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::collections::VecDeque;
|
use std::{
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
collections::VecDeque,
|
||||||
use std::sync::{Arc, Mutex};
|
sync::{
|
||||||
|
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>) {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
// 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::borrow::Cow;
|
use std::{
|
||||||
use std::error::Error;
|
borrow::Cow,
|
||||||
use std::path::{Path, PathBuf};
|
error::Error,
|
||||||
use std::str;
|
path::{Path, PathBuf},
|
||||||
|
str,
|
||||||
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
@ -130,11 +132,9 @@ 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().filter(|line| {
|
for line in text.lines() {
|
||||||
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),
|
||||||
|
|
|
||||||
113
src/config.rs
113
src/config.rs
|
|
@ -1,18 +1,20 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
|
||||||
use std::num::NonZeroU16;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
use cosmic::{
|
||||||
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
Application,
|
||||||
use cosmic::iced::Subscription;
|
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
||||||
use cosmic::{Application, theme};
|
iced::Subscription,
|
||||||
|
theme,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::FxOrderMap;
|
use crate::{
|
||||||
use crate::app::App;
|
FxOrderMap,
|
||||||
use crate::tab::{HeadingOptions, Location, View};
|
app::App,
|
||||||
|
tab::{HeadingOptions, Location, View},
|
||||||
|
};
|
||||||
|
|
||||||
pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
|
pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
|
||||||
|
|
||||||
|
|
@ -170,11 +172,6 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,97 +236,11 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::mime_app;
|
use crate::{mime_app, spawn_detached::spawn_detached};
|
||||||
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 {
|
||||||
|
|
@ -47,7 +46,7 @@ impl ContextActionPreset {
|
||||||
}
|
}
|
||||||
|
|
||||||
for step in &self.steps {
|
for step in &self.steps {
|
||||||
let Some(commands) = mime_app::exec_to_command(step, &self.name, None, paths) else {
|
let Some(commands) = mime_app::exec_to_command(step, paths) else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"failed to parse context action {:?}: invalid Exec {:?}",
|
"failed to parse context action {:?}: invalid Exec {:?}",
|
||||||
self.name,
|
self.name,
|
||||||
|
|
|
||||||
119
src/dialog.rs
119
src/dialog.rs
|
|
@ -1,46 +1,58 @@
|
||||||
// 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::cosmic::Cosmic;
|
use cosmic::{
|
||||||
use cosmic::app::{Core, Task, context_drawer};
|
Application, ApplicationExt, Element,
|
||||||
use cosmic::iced::core::SmolStr;
|
app::{Core, Task, context_drawer, cosmic::Cosmic},
|
||||||
use cosmic::iced::core::widget::operation;
|
cosmic_config, cosmic_theme, executor,
|
||||||
use cosmic::iced::futures::{self, SinkExt};
|
iced::core::widget::operation,
|
||||||
use cosmic::iced::keyboard::key::{Named, Physical};
|
iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper},
|
||||||
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
|
iced::widget::scrollable::AbsoluteOffset,
|
||||||
use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper};
|
iced::{
|
||||||
use cosmic::iced::widget::scrollable;
|
self, Alignment, Event, Length, Size, Subscription,
|
||||||
use cosmic::iced::widget::scrollable::AbsoluteOffset;
|
core::SmolStr,
|
||||||
use cosmic::iced::{
|
event,
|
||||||
self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window,
|
futures::{self, SinkExt},
|
||||||
|
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named},
|
||||||
|
mouse, stream,
|
||||||
|
widget::scrollable,
|
||||||
|
window,
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
widget::{
|
||||||
|
self, Operation,
|
||||||
|
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
|
||||||
|
segmented_button,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use cosmic::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::notify::{self, RecommendedWatcher};
|
use notify_debouncer_full::{
|
||||||
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
|
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::any::TypeId;
|
use std::{
|
||||||
use std::collections::{HashMap, VecDeque};
|
any::TypeId,
|
||||||
use std::path::PathBuf;
|
collections::{HashMap, VecDeque},
|
||||||
use std::time::{self, Instant};
|
env, fmt, fs,
|
||||||
use std::{env, fmt, fs};
|
path::PathBuf,
|
||||||
|
time::{self, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::{
|
||||||
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
|
app::{
|
||||||
|
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
|
||||||
|
},
|
||||||
|
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch},
|
||||||
|
fl, home_dir,
|
||||||
|
key_bind::key_binds,
|
||||||
|
localize::LANGUAGE_SORTER,
|
||||||
|
menu,
|
||||||
|
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||||
|
tab::{self, ItemMetadata, Location, SearchLocation, Tab},
|
||||||
|
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||||
};
|
};
|
||||||
use crate::config::{
|
|
||||||
Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch,
|
|
||||||
};
|
|
||||||
use crate::key_bind::key_binds;
|
|
||||||
use crate::localize::LANGUAGE_SORTER;
|
|
||||||
use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage};
|
|
||||||
use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab};
|
|
||||||
use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default};
|
|
||||||
use crate::{fl, home_dir, menu};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DialogMessage(cosmic::Action<Message>);
|
pub struct DialogMessage(cosmic::Action<Message>);
|
||||||
|
|
@ -456,7 +468,7 @@ enum Message {
|
||||||
Escape,
|
Escape,
|
||||||
Filename(String),
|
Filename(String),
|
||||||
Filter(usize),
|
Filter(usize),
|
||||||
Key(Modifiers, Key, Physical, Option<SmolStr>),
|
Key(Modifiers, Key, Option<SmolStr>),
|
||||||
ModifiersChanged(Modifiers),
|
ModifiersChanged(Modifiers),
|
||||||
MounterItems(MounterKey, MounterItems),
|
MounterItems(MounterKey, MounterItems),
|
||||||
Mouse(window::Id, mouse::Button),
|
Mouse(window::Id, mouse::Button),
|
||||||
|
|
@ -475,7 +487,7 @@ enum Message {
|
||||||
TabMessage(tab::Message),
|
TabMessage(tab::Message),
|
||||||
TabRescan(
|
TabRescan(
|
||||||
Location,
|
Location,
|
||||||
Option<Box<tab::Item>>,
|
Option<tab::Item>,
|
||||||
Vec<tab::Item>,
|
Vec<tab::Item>,
|
||||||
Option<Vec<PathBuf>>,
|
Option<Vec<PathBuf>>,
|
||||||
),
|
),
|
||||||
|
|
@ -575,7 +587,7 @@ impl App {
|
||||||
space_s,
|
space_s,
|
||||||
space_l,
|
space_l,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().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);
|
||||||
|
|
@ -732,17 +744,11 @@ 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, items)) => {
|
Ok((parent_item_opt, mut 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
|
||||||
|
|
@ -831,10 +837,9 @@ 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;
|
||||||
let config = self.flags.config.dialog_tab();
|
self.tab.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(config)))
|
self.update(Message::TabMessage(tab::Message::Config(self.tab.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> {
|
||||||
|
|
@ -897,8 +902,6 @@ impl App {
|
||||||
if let Some(path) = favorite.path_opt() {
|
if let Some(path) = favorite.path_opt() {
|
||||||
let name = if matches!(favorite, Favorite::Home) {
|
let name = if matches!(favorite, Favorite::Home) {
|
||||||
fl!("home")
|
fl!("home")
|
||||||
} else if let Favorite::Network { name, .. } = favorite {
|
|
||||||
name.clone()
|
|
||||||
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
|
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
|
||||||
file_name.to_string()
|
file_name.to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1122,7 +1125,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().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 {
|
||||||
|
|
@ -1400,10 +1403,7 @@ 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 +1456,16 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
return self.rescan_tab(None);
|
return self.rescan_tab(None);
|
||||||
}
|
}
|
||||||
Message::Key(modifiers, key, physical_key, text) => {
|
Message::Key(modifiers, key, text) => {
|
||||||
for (key_bind, action) in &self.key_binds {
|
for (key_bind, action) in &self.key_binds {
|
||||||
if key_bind.matches(modifiers, &key, None) {
|
if key_bind.matches(modifiers, &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, None)
|
&& key_bind.matches(modifiers, &key)
|
||||||
{
|
{
|
||||||
return self.update(if self.flags.kind.save() {
|
return self.update(if self.flags.kind.save() {
|
||||||
Message::Save(false)
|
Message::Save(false)
|
||||||
|
|
@ -2017,7 +2017,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::spacing();
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut col = widget::column::with_capacity(2);
|
let mut col = widget::column::with_capacity(2);
|
||||||
|
|
||||||
|
|
@ -2056,14 +2056,11 @@ 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 => {
|
event::Status::Ignored => Some(Message::Key(modifiers, key, text)),
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use cosmic::iced::core::keyboard::key::Named;
|
use cosmic::{
|
||||||
use cosmic::iced::keyboard::Key;
|
iced::core::keyboard::key::Named,
|
||||||
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
|
iced::keyboard::Key,
|
||||||
|
widget::menu::key_bind::{KeyBind, Modifier},
|
||||||
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::app::Action;
|
use crate::{app::Action, tab};
|
||||||
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> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use cosmic::widget;
|
use cosmic::widget;
|
||||||
use image::ImageReader;
|
use image::ImageReader;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::{
|
||||||
use std::path::{Path, PathBuf};
|
collections::{HashMap, HashSet},
|
||||||
|
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;
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -1,16 +1,15 @@
|
||||||
// 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;
|
use cosmic::{app::Settings, iced::Limits};
|
||||||
use cosmic::iced::Limits;
|
use std::{env, fs, path::PathBuf, process};
|
||||||
use std::path::PathBuf;
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
use std::{env, fs, process};
|
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
|
||||||
|
|
||||||
use crate::app::{App, Flags};
|
use crate::{
|
||||||
use crate::config::{Config, State};
|
app::{App, Flags},
|
||||||
use crate::tab::Location;
|
config::{Config, State},
|
||||||
|
tab::Location,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
mod archive;
|
mod archive;
|
||||||
|
|
@ -38,7 +37,6 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
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::{
|
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
|
use i18n_embed::{
|
||||||
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
|
DefaultLocalizer, LanguageLoader, Localizer,
|
||||||
use icu::collator::options::CollatorOptions;
|
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||||
use icu::collator::preferences::CollationNumericOrdering;
|
};
|
||||||
use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences};
|
use icu::collator::{
|
||||||
|
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;
|
||||||
|
|
|
||||||
59
src/menu.rs
59
src/menu.rs
|
|
@ -1,26 +1,30 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::app::Core;
|
use cosmic::{
|
||||||
use cosmic::iced::advanced::widget::text::Style as TextStyle;
|
Element,
|
||||||
use cosmic::iced::keyboard::Modifiers;
|
app::Core,
|
||||||
use cosmic::iced::{Alignment, Background, Border, Length};
|
iced::{
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
|
||||||
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
|
keyboard::Modifiers,
|
||||||
use cosmic::widget::{
|
},
|
||||||
self, Row, button, column, container, divider, responsive_menu_bar, space, text,
|
theme,
|
||||||
|
widget::{
|
||||||
|
self, Row, button, column, container, divider,
|
||||||
|
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||||
|
responsive_menu_bar, space, text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use cosmic::{Element, theme};
|
|
||||||
#[cfg(feature = "desktop")]
|
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::LazyLock};
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use crate::app::{Action, Message};
|
use crate::{
|
||||||
use crate::config::{Config, ContextActionPreset};
|
app::{Action, Message},
|
||||||
use crate::fl;
|
config::{Config, ContextActionPreset},
|
||||||
use crate::tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab};
|
fl,
|
||||||
use crate::trash::{Trash, TrashExt};
|
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||||
|
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"));
|
||||||
|
|
@ -34,7 +38,7 @@ macro_rules! menu_button {
|
||||||
.height(Length::Fixed(24.0))
|
.height(Length::Fixed(24.0))
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
)
|
)
|
||||||
.padding([theme::spacing().space_xxs, 16])
|
.padding([theme::active().cosmic().spacing.space_xxs, 16])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.class(theme::Button::MenuItem)
|
.class(theme::Button::MenuItem)
|
||||||
);
|
);
|
||||||
|
|
@ -72,7 +76,6 @@ pub fn context_menu<'a>(
|
||||||
color.alpha *= 0.75;
|
color.alpha *= 0.75;
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: Some(color.into()),
|
color: Some(color.into()),
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
|
fn disabled_style(theme: &cosmic::Theme) -> TextStyle {
|
||||||
|
|
@ -80,7 +83,6 @@ pub fn context_menu<'a>(
|
||||||
color.alpha *= 0.5;
|
color.alpha *= 0.5;
|
||||||
TextStyle {
|
TextStyle {
|
||||||
color: Some(color.into()),
|
color: Some(color.into()),
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,11 +143,12 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
@ -193,11 +196,11 @@ pub fn context_menu<'a>(
|
||||||
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).into());
|
||||||
}
|
}
|
||||||
} else if let Some(_entry) = selected_desktop_entry {
|
} else if let Some(entry) = selected_desktop_entry {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
{
|
{
|
||||||
children.extend(_entry.desktop_actions.into_iter().enumerate().map(
|
children.extend(entry.desktop_actions.into_iter().enumerate().map(
|
||||||
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
|
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +579,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::spacing().space_xxxs.into())
|
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -631,7 +634,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::spacing().space_xxxs.into())
|
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
|
||||||
.into_element(
|
.into_element(
|
||||||
core,
|
core,
|
||||||
key_binds,
|
key_binds,
|
||||||
|
|
|
||||||
317
src/mime_app.rs
317
src/mime_app.rs
|
|
@ -1,123 +1,155 @@
|
||||||
// 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 bstr::{BString, ByteSlice, ByteVec};
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
use cosmic::desktop;
|
use cosmic::desktop;
|
||||||
use cosmic::widget;
|
use cosmic::widget;
|
||||||
pub use mime_guess::Mime;
|
pub use mime_guess::Mime;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
#[cfg(feature = "desktop")]
|
|
||||||
use std::{cmp::Ordering, fs, io, time::Instant};
|
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
os::unix::ffi::OsStrExt,
|
fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process,
|
process,
|
||||||
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Supported exec key field codes
|
||||||
|
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"];
|
||||||
|
// Deprecated field codes. The spec advises to ignore these handlers.
|
||||||
|
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"];
|
||||||
|
|
||||||
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 arguments = shlex::split(exec)?;
|
let args_vec = shlex::split(exec)?;
|
||||||
|
let program = args_vec.first()?;
|
||||||
|
// Skip program to make indexing easier
|
||||||
|
let args_vec = &args_vec[1..];
|
||||||
|
|
||||||
if arguments.is_empty() {
|
// Base Command instance(s)
|
||||||
tracing::error!("command does not contain any arguments");
|
// 1. We may need to launch multiple of the same process.
|
||||||
return None;
|
// 2. Each of those processes will need to be passed args from exec.
|
||||||
}
|
// 3. Each of those args may appear in any order.
|
||||||
|
// 4. Arg order should be preserved.
|
||||||
let mut commands = Vec::new();
|
//
|
||||||
|
// So, we'll go through exec in two passes. The first pass handles paths (%f etc) and args up
|
||||||
let paths = path_opt
|
// to the field code followed by the second which passes extra, non-% args to each processes.
|
||||||
|
//
|
||||||
|
// While it'd be marginally faster to process everything in one pass, that's problematic:
|
||||||
|
// 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck
|
||||||
|
// doesn't know we'll only use it once)
|
||||||
|
// 2. We have to keep track of which modifier (%f etc) we've used/seen already
|
||||||
|
// 3. We have to keep track of which processes received non-modifier args which gets messy fast
|
||||||
|
// 4. `exec` is likely small so looping over it twice is not a big deal
|
||||||
|
let field_code_pos = args_vec
|
||||||
.iter()
|
.iter()
|
||||||
.map(AsRef::as_ref)
|
.position(|arg| EXEC_HANDLERS.contains(&arg.as_str()));
|
||||||
.map(Some)
|
let args_handler = field_code_pos.and_then(|i| args_vec.get(i));
|
||||||
// Add a single `None` if no path was given.
|
// msrv
|
||||||
.chain(std::iter::repeat_n(
|
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}"));
|
||||||
None,
|
// Number of args before the field code.
|
||||||
if path_opt.is_empty() { 1 } else { 0 },
|
// This won't be an off by one err below because take is not zero indexed.
|
||||||
));
|
let field_code_pos = field_code_pos.unwrap_or_default();
|
||||||
|
let mut processes = match args_handler.map(String::as_str) {
|
||||||
|
Some("%f") => {
|
||||||
|
let mut processes = Vec::with_capacity(path_opt.len());
|
||||||
|
|
||||||
for path in paths {
|
for path in path_opt.iter().map(AsRef::as_ref) {
|
||||||
let mut batch_process = false;
|
// TODO: %f and %F need to handle non-file URLs (see spec)
|
||||||
let mut args = Vec::with_capacity(arguments.len());
|
if from_file_or_dir(path).is_none() {
|
||||||
let mut field_code_used = false;
|
log::warn!("Desktop file expects a file path instead of a URL: {path:?}");
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !new_argument.is_empty() {
|
processes
|
||||||
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:?}");
|
||||||
|
}
|
||||||
|
|
||||||
let mut command = process::Command::new(&arguments[0]);
|
// 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)),
|
||||||
|
);
|
||||||
|
|
||||||
for arg in args {
|
vec![process]
|
||||||
match arg.to_os_str() {
|
}
|
||||||
Ok(arg) => {
|
Some("%u") => path_opt
|
||||||
command.arg(arg);
|
.iter()
|
||||||
}
|
.map(|path| {
|
||||||
Err(_) => {
|
let mut process = process::Command::new(program);
|
||||||
tracing::error!("invalid string encoding in command");
|
process.args(
|
||||||
|
args_vec
|
||||||
|
.iter()
|
||||||
|
.map(OsStr::new)
|
||||||
|
.take(field_code_pos)
|
||||||
|
.chain(std::iter::once(path.as_ref())),
|
||||||
|
);
|
||||||
|
process
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Some("%U") => {
|
||||||
|
let mut process = process::Command::new(program);
|
||||||
|
process.args(
|
||||||
|
args_vec
|
||||||
|
.iter()
|
||||||
|
.map(OsStr::new)
|
||||||
|
.take(field_code_pos)
|
||||||
|
.chain(path_opt.iter().map(AsRef::as_ref)),
|
||||||
|
);
|
||||||
|
vec![process]
|
||||||
|
}
|
||||||
|
Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"),
|
||||||
|
None => vec![process::Command::new(program)],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pass 2: Add remaining arguments that are not % to each process
|
||||||
|
for arg in args_vec.iter().skip(field_code_pos) {
|
||||||
|
match arg.as_str() {
|
||||||
|
// Consume path field codes or fail on codes we don't handle yet
|
||||||
|
field_code if arg.starts_with('%') => {
|
||||||
|
if !EXEC_HANDLERS.contains(&field_code)
|
||||||
|
&& !DEPRECATED_HANDLERS.contains(&field_code)
|
||||||
|
{
|
||||||
|
log::warn!("unsupported Exec code {field_code:?} in {exec:?}");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
arg => {
|
||||||
|
for process in &mut processes {
|
||||||
commands.push(command);
|
process.arg(arg);
|
||||||
|
}
|
||||||
if !batch_process {
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
for command in &commands {
|
for command in &processes {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Parsed program {} with args: {:?}",
|
"Parsed program {} with args: {:?}",
|
||||||
command.get_program().to_string_lossy(),
|
command.get_program().to_string_lossy(),
|
||||||
|
|
@ -125,7 +157,13 @@ pub fn exec_to_command(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(commands)
|
Some(processes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_file_or_dir(path: impl AsRef<Path>) -> Option<url::Url> {
|
||||||
|
url::Url::from_file_path(&path)
|
||||||
|
.ok()
|
||||||
|
.or_else(|| url::Url::from_directory_path(&path).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -141,12 +179,7 @@ pub struct MimeApp {
|
||||||
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(
|
exec_to_command(self.exec.as_deref()?, path_opt)
|
||||||
self.exec.as_deref()?,
|
|
||||||
&self.name,
|
|
||||||
self.path.as_deref(),
|
|
||||||
path_opt,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,12 +399,9 @@ impl MimeAppCache {
|
||||||
// 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
|
||||||
// Yoda: cosmic-yoterm (our fork) wins over upstream cosmic-term if both
|
//TODO: fallback order beyond cosmic-term?
|
||||||
// are installed — useful when xdg-mime default is not set.
|
|
||||||
let mut preference_order = vec![
|
let mut preference_order = vec!["com.system76.CosmicTerm".to_string()];
|
||||||
"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);
|
||||||
|
|
@ -445,43 +475,11 @@ 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, "one_path_f_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -496,40 +494,31 @@ 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/cosmic-term -w %F";
|
let exec = "/usr/bin/bar %F";
|
||||||
let paths = ["/home/user"];
|
let paths = ["cat"];
|
||||||
let commands = exec_to_command(exec, "one_path_F_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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!(
|
assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap());
|
||||||
"/usr/bin/cosmic-term",
|
assert_eq!("cat", 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]
|
||||||
fn one_path_u_field_code() {
|
fn one_path_u_field_code() {
|
||||||
let exec = "/usr/bin/cosmic-term -w %u";
|
let exec = "/usr/bin/foobar %u";
|
||||||
let paths = ["/home/user"];
|
let paths = ["/home/josh/krumpli"];
|
||||||
let commands = exec_to_command(exec, "one_path_u_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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!(
|
||||||
"/usr/bin/cosmic-term",
|
*paths.first().unwrap(),
|
||||||
command.get_program().to_str().unwrap()
|
command.get_args().next().unwrap().to_str().unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
|
|
||||||
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -537,8 +526,7 @@ 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, "one_path_U_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -554,8 +542,7 @@ 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, "mult_path_f_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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()) {
|
||||||
|
|
@ -575,8 +562,7 @@ 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, "mult_path_F_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -598,8 +584,7 @@ mod tests {
|
||||||
"https://redox-os.org/",
|
"https://redox-os.org/",
|
||||||
"https://system76.com/",
|
"https://system76.com/",
|
||||||
];
|
];
|
||||||
let commands = exec_to_command(exec, "mult_path_u_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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()) {
|
||||||
|
|
@ -622,8 +607,7 @@ 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, "mult_path_U_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -651,8 +635,7 @@ mod tests {
|
||||||
"@@u",
|
"@@u",
|
||||||
];
|
];
|
||||||
let paths = ["file1.rs", "file2.rs"];
|
let paths = ["file1.rs", "file2.rs"];
|
||||||
let commands = exec_to_command(exec, "flatpak_style_exec", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -675,8 +658,7 @@ 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, "multiple_field_codes", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
@ -709,8 +691,7 @@ 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, "sandwiched_field_code", None, &paths)
|
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||||
.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();
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
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::fs;
|
use std::{
|
||||||
use std::path::Path;
|
fs,
|
||||||
use std::sync::{LazyLock, Mutex};
|
path::Path,
|
||||||
|
sync::{LazyLock, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
|
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
|
||||||
|
|
||||||
|
|
@ -45,7 +47,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).prefer_svg(true).size(key.size);
|
let mut named = icon::from_name(icon_name).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();
|
||||||
|
|
@ -112,10 +114,7 @@ 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)
|
None => icon::from_name(FALLBACK_MIME_ICON).size(size).handle(),
|
||||||
.prefer_svg(true)
|
|
||||||
.size(size)
|
|
||||||
.handle(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
use cosmic::iced::futures::SinkExt;
|
use cosmic::{
|
||||||
use cosmic::iced::{Subscription, stream};
|
Task,
|
||||||
use cosmic::{Task, widget};
|
iced::{Subscription, futures::SinkExt, stream},
|
||||||
use gio::glib;
|
widget,
|
||||||
use gio::prelude::*;
|
};
|
||||||
use std::any::TypeId;
|
use gio::{glib, prelude::*};
|
||||||
use std::cell::Cell;
|
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
||||||
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::config::IconSizes;
|
use crate::{
|
||||||
use crate::err_str;
|
config::IconSizes,
|
||||||
use crate::tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location};
|
err_str,
|
||||||
|
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
|
||||||
|
};
|
||||||
|
|
||||||
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
|
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use cosmic::iced::Subscription;
|
use cosmic::{Task, iced::Subscription, widget};
|
||||||
use cosmic::{Task, widget};
|
use std::{
|
||||||
use std::collections::BTreeMap;
|
collections::BTreeMap,
|
||||||
use std::fmt;
|
fmt,
|
||||||
use std::path::PathBuf;
|
path::PathBuf,
|
||||||
use std::sync::{Arc, LazyLock};
|
sync::{Arc, LazyLock},
|
||||||
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::config::IconSizes;
|
use crate::{config::IconSizes, tab};
|
||||||
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,7 +103,6 @@ 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>),
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,21 @@
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::tab::DOUBLE_CLICK_DURATION;
|
use crate::tab::DOUBLE_CLICK_DURATION;
|
||||||
use cosmic::iced::core::border::Border;
|
use cosmic::{
|
||||||
use cosmic::iced::core::event::Event;
|
Element, Renderer, Theme,
|
||||||
use cosmic::iced::core::mouse::{self, click};
|
iced::core::{
|
||||||
use cosmic::iced::core::renderer::{self, Quad, Renderer as _};
|
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||||
use cosmic::iced::core::widget::{Operation, Tree, tree};
|
border::Border,
|
||||||
use cosmic::iced::core::{
|
event::Event,
|
||||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
|
layout,
|
||||||
overlay, touch,
|
mouse::{self, click},
|
||||||
|
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)]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
use crate::app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID};
|
use crate::{
|
||||||
use crate::config::IconSizes;
|
app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID},
|
||||||
use crate::spawn_detached::spawn_detached;
|
archive,
|
||||||
use crate::{archive, fl, tab};
|
config::IconSizes,
|
||||||
use cosmic::iced::futures::channel::mpsc::Sender;
|
fl,
|
||||||
use cosmic::iced::futures::{self, SinkExt, StreamExt, stream};
|
spawn_detached::spawn_detached,
|
||||||
use std::borrow::Cow;
|
tab,
|
||||||
use std::fmt::Formatter;
|
};
|
||||||
use std::fs;
|
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
|
||||||
use std::io::{self, Read, Write};
|
use std::{
|
||||||
use std::path::{Path, PathBuf};
|
borrow::Cow,
|
||||||
use std::sync::Arc;
|
fmt::Formatter,
|
||||||
|
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;
|
||||||
|
|
@ -34,7 +39,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) => Box::new(ok),
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("{err}");
|
log::warn!("{err}");
|
||||||
return ReplaceResult::Cancel;
|
return ReplaceResult::Cancel;
|
||||||
|
|
@ -42,7 +47,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) => Box::new(ok),
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("{err}");
|
log::warn!("{err}");
|
||||||
return ReplaceResult::Cancel;
|
return ReplaceResult::Cancel;
|
||||||
|
|
@ -1238,23 +1243,28 @@ fn wrap_compio_spawn_error(err: Box<dyn std::any::Any + Send>) -> OperationError
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs::{self, File};
|
use std::{
|
||||||
use std::io;
|
fs::{self, File},
|
||||||
use std::path::PathBuf;
|
io,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use cosmic::iced::futures::channel::mpsc;
|
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
|
||||||
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::app::test_utils::{
|
use crate::{
|
||||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, filter_files,
|
app::{
|
||||||
simple_fs,
|
DialogPage, Message,
|
||||||
|
test_utils::{
|
||||||
|
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
|
||||||
|
filter_files, simple_fs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fl,
|
||||||
};
|
};
|
||||||
use crate::app::{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(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::path::Path;
|
use std::{fs, io, path::Path};
|
||||||
use std::{fs, io};
|
|
||||||
|
|
||||||
use crate::operation::OperationError;
|
use crate::operation::OperationError;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,15 @@ 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;
|
use compio::driver::{ToSharedFd, op::AsyncifyFd};
|
||||||
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")]
|
||||||
|
|
@ -479,12 +473,12 @@ impl Op {
|
||||||
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() {
|
||||||
&& let Err(why) = to_file.set_permissions(metadata.permissions()).await
|
if let Err(why) = to_file.set_permissions(metadata.permissions()).await {
|
||||||
{
|
// This error is not propagated upwards as some filesystems do not support setting permissions
|
||||||
// This error is not propagated upwards as some filesystems do not support setting permissions
|
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
||||||
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
|
||||||
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
481
src/tab.rs
481
src/tab.rs
|
|
@ -1,72 +1,91 @@
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env};
|
use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env};
|
||||||
use cosmic::iced::advanced::graphics;
|
use cosmic::{
|
||||||
use cosmic::iced::advanced::text::{self, Paragraph};
|
Apply, Element, cosmic_theme, font,
|
||||||
use cosmic::iced::alignment::Vertical;
|
iced::core::{mouse::ScrollDelta, widget::tree},
|
||||||
use cosmic::iced::clipboard::dnd::DndAction;
|
iced::{
|
||||||
use cosmic::iced::core::mouse::ScrollDelta;
|
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
||||||
use cosmic::iced::core::widget::tree;
|
advanced::{
|
||||||
use cosmic::iced::futures::{self, SinkExt};
|
graphics,
|
||||||
use cosmic::iced::keyboard::Modifiers;
|
text::{self, Paragraph},
|
||||||
use cosmic::iced::widget::scrollable::{self, AbsoluteOffset, Viewport};
|
},
|
||||||
use cosmic::iced::widget::{rule, stack};
|
alignment::Vertical,
|
||||||
use cosmic::iced::{
|
clipboard::dnd::DndAction,
|
||||||
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
futures::{self, SinkExt},
|
||||||
padding, stream, window,
|
keyboard::Modifiers,
|
||||||
|
padding, stream,
|
||||||
|
widget::{
|
||||||
|
rule,
|
||||||
|
scrollable::{self, AbsoluteOffset, Viewport},
|
||||||
|
stack,
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
widget::{
|
||||||
|
self, DndDestination, DndSource, Id, RcElementWrapper, Widget,
|
||||||
|
menu::{action::MenuAction, key_bind::KeyBind},
|
||||||
|
space,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use cosmic::widget::menu::action::MenuAction;
|
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
|
||||||
use cosmic::widget::{self, DndDestination, DndSource, Id, RcElementWrapper, Widget, space};
|
|
||||||
use cosmic::{Apply, Element, cosmic_theme, font, theme};
|
|
||||||
#[cfg(feature = "desktop")]
|
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use icu::datetime::input::DateTime;
|
use icu::{
|
||||||
use icu::datetime::options::TimePrecision;
|
datetime::{
|
||||||
use icu::datetime::{DateTimeFormatter, DateTimeFormatterPreferences, fieldsets};
|
DateTimeFormatter, DateTimeFormatterPreferences, fieldsets, input::DateTime,
|
||||||
use icu::locale::preferences::extensions::unicode::keywords::HourCycle;
|
options::TimePrecision,
|
||||||
|
},
|
||||||
|
locale::preferences::extensions::unicode::keywords::HourCycle,
|
||||||
|
};
|
||||||
use image::{DynamicImage, ImageReader};
|
use image::{DynamicImage, ImageReader};
|
||||||
use jiff_icu::ConvertFrom;
|
use jiff_icu::ConvertFrom;
|
||||||
use mime_guess::{Mime, mime};
|
use mime_guess::{Mime, mime};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::cmp::{Ordering, Reverse};
|
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::fs::{self, File, Metadata};
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::io::{BufRead, BufReader, Read};
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::{self, Path, PathBuf};
|
use std::{
|
||||||
use std::sync::{Arc, LazyLock, RwLock, atomic};
|
borrow::Cow,
|
||||||
use std::time::{Duration, Instant, SystemTime};
|
cell::Cell,
|
||||||
|
cmp::{Ordering, Reverse},
|
||||||
|
collections::{BTreeMap, BTreeSet, HashMap},
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Display},
|
||||||
|
fs::{self, File, Metadata},
|
||||||
|
hash::Hash,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::{self, Path, PathBuf},
|
||||||
|
sync::{Arc, LazyLock, RwLock, atomic},
|
||||||
|
time::{Duration, Instant, SystemTime},
|
||||||
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use trash::{TrashItem, TrashItemMetadata, TrashItemSize};
|
use trash::{TrashItem, TrashItemMetadata, TrashItemSize};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::app::{Action, PreviewItem, PreviewKind};
|
use crate::{
|
||||||
use crate::clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste};
|
FxOrderMap,
|
||||||
use crate::config::{
|
app::{Action, PreviewItem, PreviewKind},
|
||||||
ContextActionPreset, DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig,
|
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||||
ThumbCfg,
|
config::{
|
||||||
|
ContextActionPreset, DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig,
|
||||||
|
ThumbCfg,
|
||||||
|
},
|
||||||
|
dialog::DialogKind,
|
||||||
|
fl,
|
||||||
|
large_image::{
|
||||||
|
LargeImageManager, decode_large_image, exceeds_memory_limit, should_use_dedicated_worker,
|
||||||
|
should_use_tiling,
|
||||||
|
},
|
||||||
|
localize::{LANGUAGE_SORTER, LOCALE},
|
||||||
|
menu, mime_app,
|
||||||
|
mime_icon::{mime_for_path, mime_icon},
|
||||||
|
mounter::MOUNTERS,
|
||||||
|
mouse_area,
|
||||||
|
operation::{Controller, OperationError},
|
||||||
|
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||||
|
thumbnailer::thumbnailer,
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
};
|
};
|
||||||
use crate::dialog::DialogKind;
|
|
||||||
use crate::large_image::{
|
|
||||||
LargeImageManager, decode_large_image, exceeds_memory_limit, should_use_dedicated_worker,
|
|
||||||
should_use_tiling,
|
|
||||||
};
|
|
||||||
use crate::localize::{LANGUAGE_SORTER, LOCALE};
|
|
||||||
use crate::mime_icon::{mime_for_path, mime_icon};
|
|
||||||
use crate::mounter::MOUNTERS;
|
|
||||||
use crate::operation::{Controller, OperationError};
|
|
||||||
use crate::thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize};
|
|
||||||
use crate::thumbnailer::thumbnailer;
|
|
||||||
use crate::trash::{Trash, TrashExt};
|
|
||||||
use crate::{FxOrderMap, fl, menu, mime_app, mouse_area};
|
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
||||||
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
||||||
|
|
@ -76,11 +95,6 @@ const MAX_SEARCH_LATENCY: Duration = Duration::from_millis(20);
|
||||||
const MAX_SEARCH_RESULTS: usize = 200;
|
const MAX_SEARCH_RESULTS: usize = 200;
|
||||||
//TODO: configurable thumbnail size?
|
//TODO: configurable thumbnail size?
|
||||||
const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32);
|
const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32);
|
||||||
/// Maximum bytes of text to pass to the editor for preview; caps shaping work to avoid blocking.
|
|
||||||
/// Files larger than this get a truncated preview (first N bytes only).
|
|
||||||
const TEXT_PREVIEW_MAX_BYTES: usize = 256 * 1024; // 256 KiB
|
|
||||||
/// Maximum file size (bytes) to attempt text preview; files larger than this are skipped entirely.
|
|
||||||
const TEXT_PREVIEW_MAX_FILE_BYTES: u64 = 8 * 1000 * 1000; // 8 MiB
|
|
||||||
|
|
||||||
// Thumbnail generation semaphore - limits parallel thumbnail workers
|
// Thumbnail generation semaphore - limits parallel thumbnail workers
|
||||||
// Uses 4 workers for balanced throughput and memory usage
|
// Uses 4 workers for balanced throughput and memory usage
|
||||||
|
|
@ -275,7 +289,6 @@ fn button_style(
|
||||||
|
|
||||||
pub fn folder_icon(path: &PathBuf, icon_size: u16) -> widget::icon::Handle {
|
pub fn folder_icon(path: &PathBuf, icon_size: u16) -> widget::icon::Handle {
|
||||||
widget::icon::from_name(SPECIAL_DIRS.get(path).map_or("folder", |x| *x))
|
widget::icon::from_name(SPECIAL_DIRS.get(path).map_or("folder", |x| *x))
|
||||||
.prefer_svg(true)
|
|
||||||
.size(icon_size)
|
.size(icon_size)
|
||||||
.handle()
|
.handle()
|
||||||
}
|
}
|
||||||
|
|
@ -289,26 +302,6 @@ pub fn folder_icon_symbolic(path: &PathBuf, icon_size: u16) -> widget::icon::Han
|
||||||
.handle()
|
.handle()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generic_file_icons(
|
|
||||||
sizes: IconSizes,
|
|
||||||
) -> (
|
|
||||||
widget::icon::Handle,
|
|
||||||
widget::icon::Handle,
|
|
||||||
widget::icon::Handle,
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
widget::icon::from_name("text-x-generic")
|
|
||||||
.size(sizes.grid())
|
|
||||||
.handle(),
|
|
||||||
widget::icon::from_name("text-x-generic")
|
|
||||||
.size(sizes.list())
|
|
||||||
.handle(),
|
|
||||||
widget::icon::from_name("text-x-generic")
|
|
||||||
.size(sizes.list_condensed())
|
|
||||||
.handle(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: replace with Path::has_trailing_sep when stable
|
//TODO: replace with Path::has_trailing_sep when stable
|
||||||
fn has_trailing_sep(path: &Path) -> bool {
|
fn has_trailing_sep(path: &Path) -> bool {
|
||||||
path.as_os_str()
|
path.as_os_str()
|
||||||
|
|
@ -565,7 +558,7 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "desktop"))]
|
#[cfg(not(feature = "desktop"))]
|
||||||
fn get_desktop_file_display_name(_path: &Path) -> Option<String> {
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -584,7 +577,7 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "desktop"))]
|
#[cfg(not(feature = "desktop"))]
|
||||||
fn get_desktop_file_icon(_path: &Path) -> Option<String> {
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -608,10 +601,7 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle {
|
||||||
if icon_path.is_absolute() && icon_path.exists() {
|
if icon_path.is_absolute() && icon_path.exists() {
|
||||||
widget::icon::from_path(icon_path.to_path_buf())
|
widget::icon::from_path(icon_path.to_path_buf())
|
||||||
} else {
|
} else {
|
||||||
widget::icon::from_name(icon)
|
widget::icon::from_name(icon).size(size).handle()
|
||||||
.prefer_svg(true)
|
|
||||||
.size(size)
|
|
||||||
.handle()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -675,9 +665,9 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
|
||||||
folder_icon(&path, sizes.list_condensed()),
|
folder_icon(&path, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Keep the initial directory scan cheap. Opening files still
|
// ALWAYS assume we're remote for mime guessing here, since gvfs reading can be expensive
|
||||||
// recalculates MIME from the real path before launching apps.
|
// @todo - expose this as a config option?
|
||||||
let mime = mime_guess::from_path(&path).first_or_octet_stream();
|
let mime = mime_for_path(&path, None, true);
|
||||||
|
|
||||||
//TODO: clean this up, implement for trash
|
//TODO: clean this up, implement for trash
|
||||||
let icon_name_opt = if mime == "application/x-desktop" {
|
let icon_name_opt = if mime == "application/x-desktop" {
|
||||||
|
|
@ -694,21 +684,28 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
|
||||||
desktop_icon_handle(&icon_name, sizes.list_condensed()),
|
desktop_icon_handle(&icon_name, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) =
|
|
||||||
generic_file_icons(sizes);
|
|
||||||
(
|
(
|
||||||
mime,
|
mime.clone(),
|
||||||
icon_handle_grid,
|
mime_icon(mime.clone(), sizes.grid()),
|
||||||
icon_handle_list,
|
mime_icon(mime.clone(), sizes.list()),
|
||||||
icon_handle_list_condensed,
|
mime_icon(mime, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let children_opt = None;
|
let mut children_opt = None;
|
||||||
let mut dir_size = DirSize::NotDirectory;
|
let mut dir_size = DirSize::NotDirectory;
|
||||||
if is_dir && !remote {
|
if is_dir && !remote {
|
||||||
dir_size = DirSize::Calculating(Controller::default());
|
dir_size = DirSize::Calculating(Controller::default());
|
||||||
|
//TODO: calculate children in the background (and make it cancellable?)
|
||||||
|
match fs::read_dir(&path) {
|
||||||
|
Ok(entries) => {
|
||||||
|
children_opt = Some(entries.count());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to read directory {}: {}", path.display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let display_name = display_name_for_file(&path, &file_info.display_name(), false, is_desktop);
|
let display_name = display_name_for_file(&path, &file_info.display_name(), false, is_desktop);
|
||||||
|
|
@ -762,10 +759,7 @@ pub fn item_from_entry(
|
||||||
sizes: IconSizes,
|
sizes: IconSizes,
|
||||||
) -> Item {
|
) -> Item {
|
||||||
let mut is_desktop = false;
|
let mut is_desktop = false;
|
||||||
#[cfg(feature = "gvfs")]
|
|
||||||
let mut is_gvfs = false;
|
let mut is_gvfs = false;
|
||||||
#[cfg(not(feature = "gvfs"))]
|
|
||||||
let is_gvfs = false;
|
|
||||||
|
|
||||||
let hidden = name.starts_with('.') || hidden_attribute(&metadata);
|
let hidden = name.starts_with('.') || hidden_attribute(&metadata);
|
||||||
|
|
||||||
|
|
@ -813,9 +807,7 @@ pub fn item_from_entry(
|
||||||
folder_icon(&path, sizes.list_condensed()),
|
folder_icon(&path, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Keep the initial directory scan cheap. Opening files still
|
let mime = mime_for_path(&path, Some(&metadata), remote);
|
||||||
// recalculates MIME from the real path before launching apps.
|
|
||||||
let mime = mime_guess::from_path(&path).first_or_octet_stream();
|
|
||||||
//TODO: clean this up, implement for trash
|
//TODO: clean this up, implement for trash
|
||||||
let icon_name_opt = if mime == "application/x-desktop" {
|
let icon_name_opt = if mime == "application/x-desktop" {
|
||||||
is_desktop = true;
|
is_desktop = true;
|
||||||
|
|
@ -831,21 +823,28 @@ pub fn item_from_entry(
|
||||||
desktop_icon_handle(&icon_name, sizes.list_condensed()),
|
desktop_icon_handle(&icon_name, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) =
|
|
||||||
generic_file_icons(sizes);
|
|
||||||
(
|
(
|
||||||
mime,
|
mime.clone(),
|
||||||
icon_handle_grid,
|
mime_icon(mime.clone(), sizes.grid()),
|
||||||
icon_handle_list,
|
mime_icon(mime.clone(), sizes.list()),
|
||||||
icon_handle_list_condensed,
|
mime_icon(mime, sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let children_opt = None;
|
let mut children_opt = None;
|
||||||
let mut dir_size = DirSize::NotDirectory;
|
let mut dir_size = DirSize::NotDirectory;
|
||||||
if metadata.is_dir() && !remote {
|
if metadata.is_dir() && !remote {
|
||||||
dir_size = DirSize::Calculating(Controller::default());
|
dir_size = DirSize::Calculating(Controller::default());
|
||||||
|
//TODO: calculate children in the background (and make it cancellable?)
|
||||||
|
match fs::read_dir(&path) {
|
||||||
|
Ok(entries) => {
|
||||||
|
children_opt = Some(entries.count());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to read directory {}: {}", path.display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let display_name = display_name_for_file(&path, &name, is_gvfs, is_desktop);
|
let display_name = display_name_for_file(&path, &name, is_gvfs, is_desktop);
|
||||||
|
|
@ -959,10 +958,7 @@ pub fn item_from_path<P: Into<PathBuf>>(path: P, sizes: IconSizes) -> Result<Ite
|
||||||
pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let mut hidden_files = Box::from([]);
|
let mut hidden_files = Box::from([]);
|
||||||
#[cfg(feature = "gvfs")]
|
|
||||||
let mut remote_scannable = false;
|
let mut remote_scannable = false;
|
||||||
#[cfg(not(feature = "gvfs"))]
|
|
||||||
let remote_scannable = false;
|
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
#[cfg(feature = "gvfs")]
|
||||||
{
|
{
|
||||||
|
|
@ -1562,7 +1558,7 @@ impl Location {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan(&self, sizes: IconSizes) -> (Option<Box<Item>>, Vec<Item>) {
|
pub fn scan(&self, sizes: IconSizes) -> (Option<Item>, Vec<Item>) {
|
||||||
let items = match self {
|
let items = match self {
|
||||||
Self::Desktop(path, display, desktop_config) => {
|
Self::Desktop(path, display, desktop_config) => {
|
||||||
scan_desktop(path, display, *desktop_config, sizes)
|
scan_desktop(path, display, *desktop_config, sizes)
|
||||||
|
|
@ -1578,7 +1574,7 @@ impl Location {
|
||||||
};
|
};
|
||||||
let parent_item_opt = match self.path_opt() {
|
let parent_item_opt = match self.path_opt() {
|
||||||
Some(path) => match item_from_path(path, sizes) {
|
Some(path) => match item_from_path(path, sizes) {
|
||||||
Ok(item) => Some(Box::new(item)),
|
Ok(item) => Some(item),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to get item for {}: {}", path.display(), err);
|
log::warn!("failed to get item for {}: {}", path.display(), err);
|
||||||
None
|
None
|
||||||
|
|
@ -1687,7 +1683,6 @@ pub enum Command {
|
||||||
ContextMenu(Option<Point>, Option<window::Id>),
|
ContextMenu(Option<Point>, Option<window::Id>),
|
||||||
Delete(Vec<PathBuf>),
|
Delete(Vec<PathBuf>),
|
||||||
DropFiles(PathBuf, ClipboardPaste),
|
DropFiles(PathBuf, ClipboardPaste),
|
||||||
ClearRecents,
|
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
ExecEntryAction(cosmic::desktop::DesktopEntryData, usize),
|
ExecEntryAction(cosmic::desktop::DesktopEntryData, usize),
|
||||||
|
|
@ -1727,7 +1722,6 @@ pub enum Message {
|
||||||
EditLocationSubmit,
|
EditLocationSubmit,
|
||||||
EditLocationTab,
|
EditLocationTab,
|
||||||
OpenInNewTab(PathBuf),
|
OpenInNewTab(PathBuf),
|
||||||
ClearRecents,
|
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
ExecEntryAction(Option<PathBuf>, usize),
|
ExecEntryAction(Option<PathBuf>, usize),
|
||||||
|
|
@ -2090,37 +2084,17 @@ impl ItemThumbnail {
|
||||||
log::warn!("failed to read {}: {}", path.display(), err);
|
log::warn!("failed to read {}: {}", path.display(), err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if mime.type_() == mime::TEXT && check_size("text", TEXT_PREVIEW_MAX_FILE_BYTES) {
|
} else if mime.type_() == mime::TEXT && check_size("text", 8 * 1000 * 1000) {
|
||||||
tried_supported_file = true;
|
/*TODO: fix performance issues, widget::text_editor::Content::with_text forces all text to shape, which blocks rendering
|
||||||
if size > 0 {
|
match fs::read_to_string(&path) {
|
||||||
// Reuse size from metadata above; cap allocation and read
|
Ok(data) => {
|
||||||
let read_cap = (size.min(TEXT_PREVIEW_MAX_BYTES as u64)) as usize;
|
return ItemThumbnail::Text(widget::text_editor::Content::with_text(&data));
|
||||||
let mut buf = vec![0u8; read_cap];
|
}
|
||||||
match File::open(path).and_then(|f| {
|
Err(err) => {
|
||||||
let n = Read::read(&mut f.take(read_cap as u64), &mut buf)?;
|
log::warn!("failed to read {}: {}", path.display(), err);
|
||||||
buf.truncate(n);
|
|
||||||
Ok(())
|
|
||||||
}) {
|
|
||||||
Ok(()) => {
|
|
||||||
let text = match std::str::from_utf8(&buf) {
|
|
||||||
Ok(s) => s.to_string(),
|
|
||||||
Err(e) => {
|
|
||||||
// Use only the valid UTF-8 prefix (slice is guaranteed valid by valid_up_to())
|
|
||||||
std::str::from_utf8(&buf[..e.valid_up_to()])
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !text.is_empty() {
|
|
||||||
return Self::Text(widget::text_editor::Content::with_text(&text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to read {}: {}", path.display(), err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// size == 0: empty file or unknown size; skip read and allocation
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we weren't able to create a thumbnail, but we should have
|
// If we weren't able to create a thumbnail, but we should have
|
||||||
|
|
@ -2304,7 +2278,7 @@ impl Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview(&self) -> Element<'_, Message> {
|
fn preview(&self) -> Element<'_, Message> {
|
||||||
let spacing = cosmic::theme::spacing();
|
let spacing = cosmic::theme::active().cosmic().spacing;
|
||||||
// This loads the image only if thumbnailing worked
|
// This loads the image only if thumbnailing worked
|
||||||
let icon = widget::icon::icon(self.icon_handle_grid.clone())
|
let icon = widget::icon::icon(self.icon_handle_grid.clone())
|
||||||
.content_fit(ContentFit::Contain)
|
.content_fit(ContentFit::Contain)
|
||||||
|
|
@ -2365,7 +2339,7 @@ impl Item {
|
||||||
space_xxxs,
|
space_xxxs,
|
||||||
space_m,
|
space_m,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut column = widget::column::with_capacity(4).spacing(space_m);
|
let mut column = widget::column::with_capacity(4).spacing(space_m);
|
||||||
|
|
||||||
|
|
@ -2543,7 +2517,7 @@ impl Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_view(&self, heading: String, military_time: bool) -> Element<'_, Message> {
|
pub fn replace_view(&self, heading: String, military_time: bool) -> Element<'_, Message> {
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing();
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut row = widget::row::with_capacity(2).spacing(space_xxxs);
|
let mut row = widget::row::with_capacity(2).spacing(space_xxxs);
|
||||||
row = row.push(self.preview());
|
row = row.push(self.preview());
|
||||||
|
|
@ -2683,7 +2657,7 @@ pub struct Tab {
|
||||||
pub sort_name: HeadingOptions,
|
pub sort_name: HeadingOptions,
|
||||||
pub sort_direction: bool,
|
pub sort_direction: bool,
|
||||||
pub gallery: bool,
|
pub gallery: bool,
|
||||||
pub(crate) parent_item_opt: Option<Box<Item>>,
|
pub(crate) parent_item_opt: Option<Item>,
|
||||||
pub(crate) items_opt: Option<Vec<Item>>,
|
pub(crate) items_opt: Option<Vec<Item>>,
|
||||||
pub dnd_hovered: Option<(Location, Instant)>,
|
pub dnd_hovered: Option<(Location, Instant)>,
|
||||||
pub(crate) scrollable_id: widget::Id,
|
pub(crate) scrollable_id: widget::Id,
|
||||||
|
|
@ -3017,10 +2991,10 @@ impl Tab {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let search_items = after
|
let search_items = after
|
||||||
.iter_mut()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, item)| (i + start, item))
|
.map(|(i, item)| (i + start, item))
|
||||||
.chain(until.iter_mut().enumerate());
|
.chain(until.into_iter().enumerate());
|
||||||
|
|
||||||
if forward {
|
if forward {
|
||||||
Self::select_first_prefix_match(prefix_lower, search_items)
|
Self::select_first_prefix_match(prefix_lower, search_items)
|
||||||
|
|
@ -3037,7 +3011,7 @@ impl Tab {
|
||||||
items: impl Iterator<Item = (usize, &'a mut Item)>,
|
items: impl Iterator<Item = (usize, &'a mut Item)>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
for (i, item) in items {
|
for (i, item) in items {
|
||||||
if item.name.to_lowercase().starts_with(prefix) {
|
if item.name.to_lowercase().starts_with(&prefix) {
|
||||||
item.selected = true;
|
item.selected = true;
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
|
|
@ -3633,7 +3607,7 @@ impl Tab {
|
||||||
match item_from_path(&path, IconSizes::default()) {
|
match item_from_path(&path, IconSizes::default()) {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
commands.push(Command::Preview(PreviewKind::Custom(
|
commands.push(Command::Preview(PreviewKind::Custom(
|
||||||
PreviewItem(Box::new(item)),
|
PreviewItem(item),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -3723,9 +3697,6 @@ impl Tab {
|
||||||
Message::OpenInNewTab(path) => {
|
Message::OpenInNewTab(path) => {
|
||||||
commands.push(Command::OpenInNewTab(path));
|
commands.push(Command::OpenInNewTab(path));
|
||||||
}
|
}
|
||||||
Message::ClearRecents => {
|
|
||||||
commands.push(Command::ClearRecents);
|
|
||||||
}
|
|
||||||
Message::EmptyTrash => {
|
Message::EmptyTrash => {
|
||||||
commands.push(Command::EmptyTrash);
|
commands.push(Command::EmptyTrash);
|
||||||
}
|
}
|
||||||
|
|
@ -4361,7 +4332,7 @@ impl Tab {
|
||||||
Message::ShiftPermissions(path_mode_opt, shift, bits) => match path_mode_opt {
|
Message::ShiftPermissions(path_mode_opt, shift, bits) => match path_mode_opt {
|
||||||
Some((path, mode)) => commands.push(Command::SetPermissions(
|
Some((path, mode)) => commands.push(Command::SetPermissions(
|
||||||
path,
|
path,
|
||||||
set_mode_part(mode, shift, bits),
|
set_mode_part(mode, shift, bits.try_into().unwrap()),
|
||||||
)),
|
)),
|
||||||
// Shift permissions on all selected items
|
// Shift permissions on all selected items
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -4372,9 +4343,13 @@ impl Tab {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let (Some(path), Some(mode)) = (
|
if let (Some(path), Some(mode)) = (
|
||||||
item.path_opt(),
|
item.path_opt(),
|
||||||
item.file_metadata().map(|metadata| metadata.mode()),
|
item.file_metadata()
|
||||||
|
.and_then(|metadata| Some(metadata.mode())),
|
||||||
) {
|
) {
|
||||||
permissions.push((path.clone(), set_mode_part(mode, shift, bits)));
|
permissions.push((
|
||||||
|
path.clone(),
|
||||||
|
set_mode_part(mode, shift, bits.try_into().unwrap()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commands.push(Command::SetMultiplePermissions(permissions));
|
commands.push(Command::SetMultiplePermissions(permissions));
|
||||||
|
|
@ -4790,7 +4765,7 @@ impl Tab {
|
||||||
space_xs,
|
space_xs,
|
||||||
space_m,
|
space_m,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
//TODO: display error messages when image not found?
|
//TODO: display error messages when image not found?
|
||||||
let mut name_opt = None;
|
let mut name_opt = None;
|
||||||
|
|
@ -4988,7 +4963,7 @@ impl Tab {
|
||||||
space_s,
|
space_s,
|
||||||
space_m,
|
space_m,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let size = self.size_opt.get().unwrap_or(Size::new(0.0, 0.0));
|
let size = self.size_opt.get().unwrap_or(Size::new(0.0, 0.0));
|
||||||
|
|
||||||
|
|
@ -5323,7 +5298,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> {
|
pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> {
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
mouse_area::MouseArea::new(widget::column::with_children([widget::container(
|
mouse_area::MouseArea::new(widget::column::with_children([widget::container(
|
||||||
match self.mode {
|
match self.mode {
|
||||||
|
|
@ -5363,7 +5338,7 @@ impl Tab {
|
||||||
space_xxs,
|
space_xxs,
|
||||||
space_xxxs,
|
space_xxxs,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let TabConfig {
|
let TabConfig {
|
||||||
show_hidden,
|
show_hidden,
|
||||||
|
|
@ -5473,7 +5448,8 @@ impl Tab {
|
||||||
widget::button::custom(
|
widget::button::custom(
|
||||||
widget::icon::icon(item.icon_handle_grid.clone())
|
widget::icon::icon(item.icon_handle_grid.clone())
|
||||||
.content_fit(ContentFit::Contain)
|
.content_fit(ContentFit::Contain)
|
||||||
.size(icon_sizes.grid()),
|
.size(icon_sizes.grid())
|
||||||
|
.width(Length::Shrink),
|
||||||
)
|
)
|
||||||
.padding(space_xxxs)
|
.padding(space_xxxs)
|
||||||
.class(button_style(
|
.class(button_style(
|
||||||
|
|
@ -5708,7 +5684,7 @@ impl Tab {
|
||||||
) {
|
) {
|
||||||
let cosmic_theme::Spacing {
|
let cosmic_theme::Spacing {
|
||||||
space_s, space_xxs, ..
|
space_s, space_xxs, ..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let TabConfig {
|
let TabConfig {
|
||||||
show_hidden,
|
show_hidden,
|
||||||
|
|
@ -6110,7 +6086,7 @@ impl Tab {
|
||||||
space_xxs,
|
space_xxs,
|
||||||
space_xs,
|
space_xs,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let location_view_opt = if matches!(self.mode, Mode::Desktop) {
|
let location_view_opt = if matches!(self.mode, Mode::Desktop) {
|
||||||
None
|
None
|
||||||
|
|
@ -6240,24 +6216,6 @@ impl Tab {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Location::Recents | Location::Search(SearchLocation::Recents, ..) => {
|
|
||||||
if let Some(items) = self.items_opt()
|
|
||||||
&& !items.is_empty()
|
|
||||||
{
|
|
||||||
tab_column = tab_column.push(
|
|
||||||
widget::layer_container(widget::row::with_children([
|
|
||||||
widget::space::horizontal().into(),
|
|
||||||
widget::button::standard(fl!("clear-recents-history"))
|
|
||||||
.on_press(Message::ClearRecents)
|
|
||||||
.into(),
|
|
||||||
]))
|
|
||||||
.padding([space_xxs, space_xs])
|
|
||||||
.layer(cosmic_theme::Layer::Primary)
|
|
||||||
.apply(widget::container)
|
|
||||||
.padding([0, 0, 7, 0]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Location::Network(uri, _display_name, _path) if uri == "network:///" => {
|
Location::Network(uri, _display_name, _path) if uri == "network:///" => {
|
||||||
tab_column = tab_column.push(
|
tab_column = tab_column.push(
|
||||||
widget::layer_container(widget::row::with_children([
|
widget::layer_container(widget::row::with_children([
|
||||||
|
|
@ -6324,7 +6282,7 @@ impl Tab {
|
||||||
space_xxxs,
|
space_xxxs,
|
||||||
space_m,
|
space_m,
|
||||||
..
|
..
|
||||||
} = theme::spacing();
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut column = widget::column::with_capacity(4).spacing(space_m);
|
let mut column = widget::column::with_capacity(4).spacing(space_m);
|
||||||
|
|
||||||
|
|
@ -6457,31 +6415,33 @@ impl Tab {
|
||||||
|
|
||||||
let mut settings = Vec::new();
|
let mut settings = Vec::new();
|
||||||
// Only allow modifying open-with if all mime types are the same
|
// Only allow modifying open-with if all mime types are the same
|
||||||
if mime_types.len() == 1
|
if mime_types.len() == 1 {
|
||||||
&& let Some(mime) = mime_types
|
if let Some(mime) = mime_types
|
||||||
.first()
|
.get(0)
|
||||||
.and_then(|(mime, _)| mime.parse::<Mime>().ok())
|
.and_then(|(mime, _)| mime.parse::<Mime>().ok())
|
||||||
&& let Some(mime_app_cache) = mime_app_cache_opt
|
{
|
||||||
{
|
if let Some(mime_app_cache) = mime_app_cache_opt {
|
||||||
let mime_apps = mime_app_cache.get(&mime);
|
let mime_apps = mime_app_cache.get(&mime);
|
||||||
if !mime_apps.is_empty() {
|
if !mime_apps.is_empty() {
|
||||||
let mime_closure = mime.clone();
|
let mime_closure = mime.clone();
|
||||||
settings.push(
|
settings.push(
|
||||||
widget::settings::item::builder(fl!("open-with")).control(
|
widget::settings::item::builder(fl!("open-with")).control(
|
||||||
Element::from(
|
Element::from(
|
||||||
widget::dropdown(
|
widget::dropdown(
|
||||||
mime_apps,
|
mime_apps,
|
||||||
mime_apps.iter().position(|x| x.is_default),
|
mime_apps.iter().position(|x| x.is_default),
|
||||||
move |index| (index, mime_closure.clone()),
|
move |index| (index, mime_closure.clone()),
|
||||||
)
|
)
|
||||||
.icons(Cow::Borrowed(mime_app_cache.icons(&mime))),
|
.icons(Cow::Borrowed(mime_app_cache.icons(&mime))),
|
||||||
)
|
)
|
||||||
.map(|(index, mime)| {
|
.map(|(index, mime)| {
|
||||||
let mime_app = &mime_apps[index];
|
let mime_app = &mime_apps[index];
|
||||||
Message::SetOpenWith(mime, mime_app.id.clone())
|
Message::SetOpenWith(mime, mime_app.id.clone())
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7086,25 +7046,21 @@ fn text_editor_class(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::PathBuf;
|
use std::{fs, io, path::PathBuf};
|
||||||
use std::{fs, io};
|
|
||||||
|
|
||||||
use cosmic::iced::mouse::ScrollDelta;
|
use cosmic::{iced::mouse::ScrollDelta, iced::runtime::keyboard::Modifiers, widget};
|
||||||
use cosmic::iced::runtime::keyboard::Modifiers;
|
|
||||||
use cosmic::widget;
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use mime_guess::mime;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
|
||||||
use super::{
|
use super::{Location, Message, Tab, respond_to_scroll_direction, scan_path};
|
||||||
ItemMetadata, ItemThumbnail, Location, Message, Tab, respond_to_scroll_direction, scan_path,
|
use crate::{
|
||||||
|
app::test_utils::{
|
||||||
|
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs,
|
||||||
|
eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new,
|
||||||
|
},
|
||||||
|
config::{IconSizes, TabConfig, ThumbCfg},
|
||||||
};
|
};
|
||||||
use crate::app::test_utils::{
|
|
||||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs,
|
|
||||||
eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new,
|
|
||||||
};
|
|
||||||
use crate::config::{IconSizes, TabConfig, ThumbCfg};
|
|
||||||
|
|
||||||
// Boilerplate for tab tests. Checks if simulated clicks selected items.
|
// Boilerplate for tab tests. Checks if simulated clicks selected items.
|
||||||
fn tab_selects_item(
|
fn tab_selects_item(
|
||||||
|
|
@ -7517,89 +7473,4 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn item_thumbnail_text_preview_small_utf8_returns_text() -> io::Result<()> {
|
|
||||||
let dir = TempDir::new()?;
|
|
||||||
let path = dir.path().join("preview.txt");
|
|
||||||
fs::write(&path, "Hello, world!")?;
|
|
||||||
let metadata = fs::metadata(&path)?;
|
|
||||||
let item_metadata = ItemMetadata::Path {
|
|
||||||
metadata,
|
|
||||||
children_opt: None,
|
|
||||||
};
|
|
||||||
let thumb = ItemThumbnail::new(
|
|
||||||
&path,
|
|
||||||
item_metadata,
|
|
||||||
mime::TEXT_PLAIN,
|
|
||||||
128,
|
|
||||||
100 * 1024 * 1024,
|
|
||||||
1,
|
|
||||||
8,
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(thumb, ItemThumbnail::Text(_)),
|
|
||||||
"small text file should produce Text thumbnail"
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn item_thumbnail_text_preview_empty_file_returns_not_image() -> io::Result<()> {
|
|
||||||
let dir = TempDir::new()?;
|
|
||||||
let path = dir.path().join("empty.txt");
|
|
||||||
fs::File::create(&path)?;
|
|
||||||
let metadata = fs::metadata(&path)?;
|
|
||||||
let item_metadata = ItemMetadata::Path {
|
|
||||||
metadata,
|
|
||||||
children_opt: None,
|
|
||||||
};
|
|
||||||
let thumb = ItemThumbnail::new(
|
|
||||||
&path,
|
|
||||||
item_metadata,
|
|
||||||
mime::TEXT_PLAIN,
|
|
||||||
128,
|
|
||||||
100 * 1024 * 1024,
|
|
||||||
1,
|
|
||||||
8,
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(thumb, ItemThumbnail::NotImage),
|
|
||||||
"empty text file should produce NotImage (no read)"
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn item_thumbnail_text_preview_invalid_utf8_uses_valid_prefix() -> io::Result<()> {
|
|
||||||
let dir = TempDir::new()?;
|
|
||||||
let path = dir.path().join("invalid_utf8.txt");
|
|
||||||
// Valid UTF-8 "ab" then invalid byte sequence then "c"
|
|
||||||
fs::write(&path, b"ab\xff\xfe\xfdc")?;
|
|
||||||
let metadata = fs::metadata(&path)?;
|
|
||||||
let item_metadata = ItemMetadata::Path {
|
|
||||||
metadata,
|
|
||||||
children_opt: None,
|
|
||||||
};
|
|
||||||
let thumb = ItemThumbnail::new(
|
|
||||||
&path,
|
|
||||||
item_metadata,
|
|
||||||
mime::TEXT_PLAIN,
|
|
||||||
128,
|
|
||||||
100 * 1024 * 1024,
|
|
||||||
1,
|
|
||||||
8,
|
|
||||||
);
|
|
||||||
match &thumb {
|
|
||||||
ItemThumbnail::Text(content) => {
|
|
||||||
// Text editor content may add a trailing newline
|
|
||||||
assert_eq!(content.text().trim_end(), "ab");
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"expected Text thumbnail with valid prefix only, got {:?}",
|
|
||||||
thumb
|
|
||||||
),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
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::path::{Path, PathBuf};
|
use std::{
|
||||||
use std::sync::LazyLock;
|
error::Error,
|
||||||
use std::time::UNIX_EPOCH;
|
fs::{self, File},
|
||||||
|
io::{self, BufReader, BufWriter},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::LazyLock,
|
||||||
|
time::UNIX_EPOCH,
|
||||||
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
24
src/trash.rs
24
src/trash.rs
|
|
@ -1,10 +1,11 @@
|
||||||
use cosmic::widget;
|
use cosmic::widget;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::config::IconSizes;
|
use crate::{
|
||||||
use crate::tab::{Item, SearchItem};
|
config::IconSizes,
|
||||||
|
tab::{Item, SearchItem},
|
||||||
|
};
|
||||||
|
|
||||||
pub trait TrashExt {
|
pub trait TrashExt {
|
||||||
fn is_empty() -> bool {
|
fn is_empty() -> bool {
|
||||||
|
|
@ -26,7 +27,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() {
|
||||||
|
|
@ -80,8 +81,7 @@ impl TrashExt for Trash {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan(sizes: IconSizes) -> Vec<Item> {
|
fn scan(sizes: IconSizes) -> Vec<Item> {
|
||||||
use crate::localize::LANGUAGE_SORTER;
|
use crate::{localize::LANGUAGE_SORTER, tab::item_from_trash_entry};
|
||||||
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,12 +142,4 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
|
|
||||||
use crate::config::IconSizes;
|
use crate::{config::IconSizes, tab::View};
|
||||||
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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue