Merge branch 'master' into master
This commit is contained in:
commit
eb1218a0db
26 changed files with 2278 additions and 1660 deletions
972
Cargo.lock
generated
972
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
46
Cargo.toml
46
Cargo.toml
|
|
@ -1,10 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "1.0.0"
|
version = "1.0.5"
|
||||||
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.85"
|
rust-version = "1.90"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
@ -14,8 +14,6 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c
|
||||||
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"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
freedesktop_entry_parser = "1.3"
|
|
||||||
futures = "0.3.31"
|
|
||||||
gio = { version = "0.21", optional = true }
|
gio = { version = "0.21", optional = true }
|
||||||
glib = { version = "0.21", optional = true }
|
glib = { version = "0.21", optional = true }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
|
@ -24,9 +22,9 @@ image = "0.25"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime_guess = "2"
|
mime_guess = "2"
|
||||||
notify-debouncer-full = "0.6"
|
notify-debouncer-full = "0.7"
|
||||||
notify-rust = { version = "4", optional = true }
|
notify-rust = { version = "4", optional = true }
|
||||||
open = "5.3.2"
|
open = "5.3.3"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
rustc-hash = "2.1"
|
rustc-hash = "2.1"
|
||||||
|
|
@ -38,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.11", optional = true }
|
wayland-client = { version = "0.31.12", optional = true }
|
||||||
xdg = { version = "3.0", optional = true }
|
xdg = { version = "3.0", optional = true }
|
||||||
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
|
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
|
||||||
# Compression
|
# Compression
|
||||||
bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate
|
bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate
|
||||||
flate2 = "1.1"
|
flate2 = "1.1"
|
||||||
tar = "0.4.44"
|
tar = "0.4.44"
|
||||||
lzma-rust2 = { version = "0.15.4", optional = true }
|
lzma-rust2 = { version = "0.15.7", optional = true }
|
||||||
ordermap = { version = "1.0.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",
|
||||||
|
|
@ -54,10 +52,10 @@ i18n-embed = { version = "0.16", features = [
|
||||||
] }
|
] }
|
||||||
i18n-embed-fl = "0.10"
|
i18n-embed-fl = "0.10"
|
||||||
rust-embed = "8"
|
rust-embed = "8"
|
||||||
slotmap = "1.0.7"
|
slotmap = "1.1.1"
|
||||||
recently-used-xbel = { git = "https://github.com/pop-os/recently-used-xbel.git" }
|
recently-used-xbel = { git = "https://github.com/pop-os/recently-used-xbel.git" }
|
||||||
zip = "7"
|
zip = "7"
|
||||||
uzers = "0.12.1"
|
uzers = "0.12.2"
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
png = "0.18"
|
png = "0.18"
|
||||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||||
|
|
@ -65,22 +63,23 @@ num_cpus = "1.17.0"
|
||||||
|
|
||||||
# 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]
|
||||||
# Patched to fix mtime: https://github.com/compio-rs/compio/pull/625
|
version = "0.18"
|
||||||
# version = "0.17.0"
|
|
||||||
git = "https://github.com/jackpot51/compio.git"
|
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["fs", "io", "macros", "polling", "runtime"]
|
features = ["fs", "io", "macros", "polling", "runtime"]
|
||||||
|
|
||||||
[dependencies.io-uring]
|
|
||||||
version = "0.7.11"
|
|
||||||
default-features = false
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic.git"
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
default-features = false
|
default-features = false
|
||||||
#TODO: a11y feature crashes
|
#TODO: a11y feature crashes
|
||||||
features = ["about", "autosize", "multi-window", "tokio", "winit", "surface-message"]
|
features = [
|
||||||
|
"about",
|
||||||
|
"autosize",
|
||||||
|
"desktop",
|
||||||
|
"multi-window",
|
||||||
|
"tokio",
|
||||||
|
"winit",
|
||||||
|
"surface-message",
|
||||||
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "gio-list"
|
name = "gio-list"
|
||||||
|
|
@ -107,11 +106,10 @@ default = [
|
||||||
"wayland",
|
"wayland",
|
||||||
]
|
]
|
||||||
dbus-config = ["libcosmic/dbus-config"]
|
dbus-config = ["libcosmic/dbus-config"]
|
||||||
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
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", "dep:io-uring"]
|
io-uring = ["compio/io-uring"]
|
||||||
io-uring-bindgen = ["io-uring?/bindgen"]
|
|
||||||
jemalloc = ["dep:tikv-jemallocator"]
|
jemalloc = ["dep:tikv-jemallocator"]
|
||||||
notify = ["dep:notify-rust"]
|
notify = ["dep:notify-rust"]
|
||||||
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
|
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files-applet"
|
name = "cosmic-files-applet"
|
||||||
version = "0.1.0"
|
version = "1.0.5"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
12
debian/changelog
vendored
12
debian/changelog
vendored
|
|
@ -1,3 +1,15 @@
|
||||||
|
cosmic-files (1.0.5) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.0.5 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Fri, 30 Jan 2026 17:16:28 -0700
|
||||||
|
|
||||||
|
cosmic-files (1.0.4) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.0.4 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Wed, 21 Jan 2026 10:16:11 -0700
|
||||||
|
|
||||||
cosmic-files (1.0.0) jammy; urgency=medium
|
cosmic-files (1.0.0) jammy; urgency=medium
|
||||||
|
|
||||||
* Stable release.
|
* Stable release.
|
||||||
|
|
|
||||||
|
|
@ -389,9 +389,9 @@ type-to-search-enter-path = Zadává cestu ke složce nebo souboru
|
||||||
compress = Komprimovat
|
compress = Komprimovat
|
||||||
eject = Vysunout
|
eject = Vysunout
|
||||||
extract-here = Extrahovat
|
extract-here = Extrahovat
|
||||||
change-wallpaper = Změnit pozadí...
|
change-wallpaper = Změnit tapetu...
|
||||||
desktop-appearance = Vzhled plochy...
|
desktop-appearance = Vzhled plochy...
|
||||||
display-settings = Nastavení displeje...
|
display-settings = Nastavení obrazovky...
|
||||||
reload-folder = Znovu načíst složku
|
reload-folder = Znovu načíst složku
|
||||||
sort-z-a = Z-A
|
sort-z-a = Z-A
|
||||||
sort-newest-first = Nejnovější první
|
sort-newest-first = Nejnovější první
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
empty-folder = Kosongkan map
|
||||||
|
empty-folder-hidden = Kosongkan map (memiliki item tersembunyi)
|
||||||
|
no-results = Tidak ada hasil yang ditemukan
|
||||||
|
filesystem = Sistem berkas
|
||||||
|
cosmic-files = Berkas COSMIC
|
||||||
|
home = Beranda
|
||||||
|
networks = Jaringan
|
||||||
|
notification-in-progress = Operasi berkas sedang berlangsung
|
||||||
|
trash = Sampah
|
||||||
|
recents = Terbaru
|
||||||
|
undo = Batalkan
|
||||||
|
today = Hari ini
|
||||||
|
desktop-view-options = Opsi tampilan desktop...
|
||||||
|
show-on-desktop = Tampilkan di Desktop
|
||||||
|
desktop-folder-content = Konten map desktop
|
||||||
|
mounted-drives = Drive terpasang
|
||||||
|
trash-folder-icon = Ikon map sampah
|
||||||
|
icon-size-and-spacing = Ukuran dan jarak ikon
|
||||||
|
icon-size = Ukuran ikon
|
||||||
|
name = Nama
|
||||||
|
grid-spacing = Jarak antar kisi
|
||||||
|
modified = Dimodifikasi
|
||||||
|
trashed-on = Dibuang
|
||||||
|
size = Ukuran
|
||||||
|
details = Rincian
|
||||||
|
dismiss = Abaikan pesan
|
||||||
|
operations-running =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] operasi
|
||||||
|
*[other] operasi
|
||||||
|
} berjalan ({ $percent }%)...
|
||||||
|
operations-running-finished =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] operasi
|
||||||
|
*[other] operasi
|
||||||
|
} berjalan ({ $percent }%), { $finished } selesai...
|
||||||
|
pause = Jeda
|
||||||
|
resume = Lanjutkan
|
||||||
|
create-archive = Buat arsip
|
||||||
|
extract-password-required = Kata sandi diperlukan
|
||||||
|
extract-to = Ekstrak ke...
|
||||||
|
extract-to-title = Ekstrak ke map
|
||||||
|
empty-trash = Kosongkan sampah
|
||||||
|
empty-trash-title = Kosongkan sampah?
|
||||||
|
empty-trash-warning = Item di map Sampah akan dihapus permanen
|
||||||
|
emptying-trash = Mengosongkan { trash } ({ $progress })...
|
||||||
|
mount-error = Tidak dapat mengakses drive
|
||||||
|
create-new-file = Buat berkas baru
|
||||||
|
create-new-folder = Buat map baru
|
||||||
|
permanently-delete-question = Hapus secara permanen?
|
||||||
|
delete = Hapus
|
||||||
|
sort-by-trashed = Urutkan berdasarkan waktu penghapusan
|
||||||
|
delete-permanently = Hapus secara permanen
|
||||||
|
permanently-delete-warning = { $target } akan dihapus secara permanen. Tindakan ini tidak dapat dibatalkan.
|
||||||
|
deleted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dihapus dari { trash }
|
||||||
|
permanently-deleted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dihapus secara permanen
|
||||||
|
file-name = Nama berkas
|
||||||
|
folder-name = Nama map
|
||||||
|
file-already-exists = Berkas dengan nama tersebut sudah ada
|
||||||
|
folder-already-exists = Map dengan nama tersebut sudah ada
|
||||||
|
name-hidden = Nama yang diawali dengan "." akan disembunyikan
|
||||||
|
name-invalid = Nama tidak boleh "{ $filename }"
|
||||||
|
name-no-slashes = Nama tidak boleh berisi garis miring
|
||||||
|
cancel = Batalkan
|
||||||
|
create = Buat
|
||||||
|
open = Buka
|
||||||
|
open-file = Buka berkas
|
||||||
|
open-folder = Buka map
|
||||||
|
open-in-new-tab = Buka di tab baru
|
||||||
|
open-in-new-window = Buka di jendela baru
|
||||||
|
open-item-location = Buka lokasi item
|
||||||
|
open-multiple-files = Buka beberapa berkas
|
||||||
|
open-multiple-folders = Buka beberapa map
|
||||||
|
save = Simpan
|
||||||
|
save-file = Simpan berkas
|
||||||
|
open-with-title = Bagaimana anda ingin membuka "{ $name }"?
|
||||||
|
browse-store = Telusuri { $store }
|
||||||
|
other-apps = Aplikasi lainnya
|
||||||
|
related-apps = Aplikasi terkait
|
||||||
|
rename-file = Ganti nama berkas
|
||||||
|
rename-folder = Ganti nama map
|
||||||
|
replace = Ganti
|
||||||
|
replace-title = "{ $filename }" sudah ada di lokasi ini
|
||||||
|
replace-warning-operation = Apakah anda ingin menggantinya? Menggantinya akan menimpa konten tersebut.
|
||||||
|
original-file = Berkas asli
|
||||||
|
replace-with = Ganti dengan
|
||||||
|
apply-to-all = Terapkan ke semua
|
||||||
|
replace-warning = Apakah anda ingin menggantinya dengan yang sedang anda simpan? Menggantinya akan menimpa konten tersebut.
|
||||||
|
keep-both = Pertahankan keduanya
|
||||||
|
skip = Lewati
|
||||||
|
set-executable-and-launch = Atur sebagai dijalankan dan luncurkan
|
||||||
|
set-and-launch = Atur dan luncurkan
|
||||||
|
set-executable-and-launch-description = Apakah anda ingin mengatur "{ $name }" sebagai dijalankan dan luncurkan?
|
||||||
|
open-with = Buka dengan
|
||||||
|
owner = Pemilik
|
||||||
|
group = Grup
|
||||||
|
other = Lainnya
|
||||||
|
none = Tidak ada
|
||||||
|
execute-only = Hanya jalankan
|
||||||
|
write-only = Hanya tulis
|
||||||
|
write-execute = Tulis dan jalankan
|
||||||
|
read-only = Hanya baca
|
||||||
|
read-execute = Baca dan jalankan
|
||||||
|
read-write = Baca dan tulis
|
||||||
|
read-write-execute = Baca, tulis, dan jalankan
|
||||||
|
favorite-path-error = Galat membuka direktori
|
||||||
|
remove = Hapus
|
||||||
|
keep = Pertahankan
|
||||||
|
repository = Repositori
|
||||||
|
favorite-path-error-description =
|
||||||
|
Tidak dapat membuka "{ $path }"
|
||||||
|
"{ $path }" mungkin tidak ada atau anda mungkin tidak memiliki izin untuk membuka
|
||||||
|
|
||||||
|
Apakah anda ingin menghapus dari bilah sisi?
|
||||||
|
support = Dukungan
|
||||||
|
add-network-drive = Tambahkan drive jaringan
|
||||||
|
connect = Sambungkan
|
||||||
|
connect-anonymously = Sambungkan secara anonim
|
||||||
|
connecting = Menyambungkan...
|
||||||
|
domain = Domain
|
||||||
|
enter-server-address = Masukkan alamat server
|
||||||
|
network-drive-description =
|
||||||
|
Alamat server mencakup awalan protokol dan alamat.
|
||||||
|
Contoh: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||||
|
network-drive-schemes =
|
||||||
|
Protokol yang tersedia,Awalan
|
||||||
|
AppleTalk,afp://
|
||||||
|
File Transfer Protocol,ftp:// atau ftps://
|
||||||
|
Network File System,nfs://
|
||||||
|
Server Message Block,smb://
|
||||||
|
SSH File Transfer Protocol,sftp:// atau ssh://
|
||||||
|
WebDAV,dav:// atau davs://
|
||||||
|
network-drive-error = Tidak dapat mengakses drive jaringan
|
||||||
|
password = Kata sandi
|
||||||
|
remember-password = Ingat kata sandi
|
||||||
|
try-again = Coba lagi
|
||||||
|
username = Nama pengguna
|
||||||
|
cancelled = Dibatalkan
|
||||||
|
edit-history = Sunting riwayat
|
||||||
|
history = Riwayat
|
||||||
|
no-history = Tidak ada item dalam riwayat.
|
||||||
|
pending = Menunggu
|
||||||
|
progress = { $percent }%
|
||||||
|
progress-cancelled = { $percent }%, dibatalkan
|
||||||
|
progress-failed = { $percent }%, gagal
|
||||||
|
progress-paused = { $percent }%, dijeda
|
||||||
|
failed = Gagal
|
||||||
|
complete = Selesai
|
||||||
|
copy_noun = Salin
|
||||||
|
creating = Membuat "{ $name }" di "{ $parent }"
|
||||||
|
created = "{ $name }" dibuat di "{ $parent }"
|
||||||
|
compressing =
|
||||||
|
Mengompres { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||||
|
compressed =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dikompres dari "{ $from }" ke "{ $to }"
|
||||||
|
copied =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} disalin dari "{ $from }" ke "{ $to }"
|
||||||
|
copying =
|
||||||
|
Menyalin { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||||
|
deleting =
|
||||||
|
Menghapus { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari { trash } ({ $progress })...
|
||||||
|
emptied-trash = { trash } telah dikosongkan
|
||||||
|
extracting =
|
||||||
|
Mengekstrak { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||||
|
extracted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} diekstrak dari "{ $from }" ke "{ $to }"
|
||||||
|
setting-executable-and-launching = Mengatur "{ $name }" sebagai dijalankan dan meluncurkan
|
||||||
|
set-executable-and-launched = Atur "{ $name }" sebagai dijalankan dan diluncurkan
|
||||||
|
setting-permissions = Mengatur izin untuk "{ $name }" ke { $mode }
|
||||||
|
set-permissions = Atur izin untuk "{ $name }" ke { $mode }
|
||||||
|
menu-open-with = Buka dengan...
|
||||||
|
unknown-folder = map yang tidak diketahui
|
||||||
|
default-app = { $name } (bawaan)
|
||||||
|
show-details = Tampilkan rincian
|
||||||
|
type = Jenis: { $mime }
|
||||||
|
items = Item: { $items }
|
||||||
|
item-size = Ukuran: { $size }
|
||||||
|
moving =
|
||||||
|
Memindahkan { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||||
|
moved =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dipindahkan dari "{ $from }" ke "{ $to }"
|
||||||
|
permanently-deleting =
|
||||||
|
Menghapus { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} secara permanen
|
||||||
|
removing-from-recents =
|
||||||
|
Menghapus { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari { recents }
|
||||||
|
removed-from-recents =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dihapus dari { recents }
|
||||||
|
renaming = Mengganti nama "{ $from }" ke "{ $to }"
|
||||||
|
renamed = Nama diganti "{ $from }" ke "{ $to }"
|
||||||
|
restoring =
|
||||||
|
Memulihkan { $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dari { trash } ({ $progress })...
|
||||||
|
restored =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] item
|
||||||
|
*[other] item
|
||||||
|
} dipulihkan dari { trash }
|
||||||
|
item-created = Dibuat: { $created }
|
||||||
|
item-modified = Dimodifikasi: { $modified }
|
||||||
|
item-accessed = Diakses: { $accessed }
|
||||||
|
calculating = Menghitung...
|
||||||
|
settings = Pengaturan
|
||||||
|
single-click = Klik sekali untuk membuka
|
||||||
|
appearance = Tampilan
|
||||||
|
theme = Tema
|
||||||
|
match-desktop = Cocokkan desktop
|
||||||
|
dark = Gelap
|
||||||
|
light = Terang
|
||||||
|
type-to-search = Ketik untuk mencari
|
||||||
|
type-to-search-recursive = Mencari di map saat ini dan semua submap
|
||||||
|
type-to-search-enter-path = Memasukkan jalur ke direktori atau berkas
|
||||||
|
add-to-sidebar = Tambahkan ke bilah sisi
|
||||||
|
compress = Kompres
|
||||||
|
eject = Keluarkan
|
||||||
|
extract-here = Ekstrak
|
||||||
|
new-file = Berkas baru...
|
||||||
|
new-folder = Map baru...
|
||||||
|
open-in-terminal = Buka di terminal
|
||||||
|
move-to-trash = Pindahkan ke sampah
|
||||||
|
restore-from-trash = Pulihkan dari sampah
|
||||||
|
remove-from-sidebar = Hapus dari bilah sisi
|
||||||
|
sort-by-name = Urutkan berdasarkan nama
|
||||||
|
sort-by-modified = Urutkan berdasarkan dimodifikasi
|
||||||
|
sort-by-size = Urutkan berdasarkan ukuran
|
||||||
|
remove-from-recents = Hapus dari terbaru
|
||||||
|
change-wallpaper = Ubah wallpaper...
|
||||||
|
desktop-appearance = Tampilan desktop...
|
||||||
|
display-settings = Pengaturan layar...
|
||||||
|
file = Berkas
|
||||||
|
new-tab = Tab baru
|
||||||
|
new-window = Jendela baru
|
||||||
|
reload-folder = Muat ulang map
|
||||||
|
rename = Ganti nama...
|
||||||
|
close-tab = Tutup tab
|
||||||
|
quit = Keluar
|
||||||
|
edit = Sunting
|
||||||
|
cut = Potong
|
||||||
|
copy = Salin
|
||||||
|
paste = Tempel
|
||||||
|
select-all = Pilih semua
|
||||||
|
zoom-in = Perbesar
|
||||||
|
default-size = Ukuran bawaan
|
||||||
|
zoom-out = Perkecil
|
||||||
|
view = Tampilan
|
||||||
|
grid-view = Tampilan kisi
|
||||||
|
list-view = Tampilan daftar
|
||||||
|
gallery-preview = Tampilan galeri
|
||||||
|
show-hidden-files = Tampilkan berkas tersembunyi
|
||||||
|
list-directories-first = Daftar direktori terlebih dahulu
|
||||||
|
menu-settings = Pengaturan...
|
||||||
|
menu-about = Tentang Berkas COSMIC...
|
||||||
|
sort = Urutkan
|
||||||
|
sort-a-z = A-Z
|
||||||
|
sort-z-a = Z-A
|
||||||
|
sort-newest-first = Terbaru terlebih dahulu
|
||||||
|
sort-oldest-first = Tertua terlebih dahulu
|
||||||
|
sort-smallest-to-largest = Terkecil hingga terbesar
|
||||||
|
sort-largest-to-smallest = Terbesar hingga terkecil
|
||||||
|
selected-items = { $items } item yang dipilih
|
||||||
|
type-to-search-select = Memilih berkas atau map pertama yang cocok
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
cosmic-files = COSMIC файлдары
|
||||||
|
empty-folder = Бос бума
|
||||||
|
empty-folder-hidden = Бос бума (жасырын элементтері бар)
|
||||||
|
no-results = Нәтижелер табылмады
|
||||||
|
filesystem = Файлдық жүйе
|
||||||
|
home = Үй
|
||||||
|
networks = Желілер
|
||||||
|
notification-in-progress = Файлдармен әрекеттер орындалуда
|
||||||
|
trash = Қоқыс шелегі
|
||||||
|
recents = Соңғылар
|
||||||
|
undo = Болдырмау
|
||||||
|
today = Бүгін
|
||||||
|
desktop-view-options = Жұмыс үстелінің көрініс опциялары...
|
||||||
|
show-on-desktop = Жұмыс үстелінде көрсету
|
||||||
|
desktop-folder-content = Жұмыс үстелі бумасының мазмұны
|
||||||
|
mounted-drives = Тіркелген дискілер
|
||||||
|
trash-folder-icon = Қоқыс шелегі бумасының таңбашасы
|
||||||
|
icon-size-and-spacing = Таңбаша өлшемі мен аралықтары
|
||||||
|
icon-size = Таңбаша өлшемі
|
||||||
|
grid-spacing = Тор аралықтары
|
||||||
|
name = Аты
|
||||||
|
modified = Өзгертілген
|
||||||
|
trashed-on = Қоқыс шелегіне тасталған
|
||||||
|
size = Өлшемі
|
||||||
|
details = Ақпараты
|
||||||
|
dismiss = Хабарламаны елемеу
|
||||||
|
operations-running =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] әрекет
|
||||||
|
*[other] әрекет
|
||||||
|
} орындалуда ({ $percent }%)...
|
||||||
|
operations-running-finished =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] әрекет
|
||||||
|
*[other] әрекет
|
||||||
|
} орындалуда ({ $percent }%), { $finished } аяқталды...
|
||||||
|
pause = Аялдату
|
||||||
|
resume = Жалғастыру
|
||||||
|
create-archive = Архив жасау
|
||||||
|
extract-password-required = Пароль керек
|
||||||
|
extract-to = Шығару...
|
||||||
|
extract-to-title = Бумаға шығару
|
||||||
|
empty-trash = Себетті тазарту
|
||||||
|
empty-trash-title = Себетті тазарту керек пе?
|
||||||
|
empty-trash-warning = Себет бумасындағы элементтер біржола өшіріледі
|
||||||
|
mount-error = Дискіге қол жеткізу мүмкін емес
|
||||||
|
create-new-file = Жаңа файл жасау
|
||||||
|
create-new-folder = Жаңа бума жасау
|
||||||
|
file-name = Файл аты
|
||||||
|
folder-name = Бума аты
|
||||||
|
file-already-exists = Ондай аты бар файл бұрыннан бар
|
||||||
|
folder-already-exists = Ондай аты бар бума бұрыннан бар
|
||||||
|
name-hidden = "." таңбасынан басталатын атаулар жасырын болады
|
||||||
|
name-invalid = Аты "{ $filename }" болуы мүмкін емес
|
||||||
|
name-no-slashes = Атауда қиғаш сызықтар болмауы тиіс
|
||||||
|
cancel = Бас тарту
|
||||||
|
create = Жасау
|
||||||
|
open = Ашу
|
||||||
|
open-file = Файлды ашу
|
||||||
|
open-folder = Буманы ашу
|
||||||
|
open-in-new-tab = Жаңа бетте ашу
|
||||||
|
open-in-new-window = Жаңа терезеде ашу
|
||||||
|
open-item-location = Нысанның орнын ашу
|
||||||
|
open-multiple-files = Бірнеше файлды ашу
|
||||||
|
open-multiple-folders = Бірнеше буманы ашу
|
||||||
|
save = Сақтау
|
||||||
|
save-file = Файлды сақтау
|
||||||
|
open-with-title = "{ $name }" қалай ашқыңыз келеді?
|
||||||
|
browse-store = { $store } шолу
|
||||||
|
other-apps = Басқа қолданбалар
|
||||||
|
related-apps = Қатысты қолданбалар
|
||||||
|
selected-items = Таңдалған { $items } нысан
|
||||||
|
permanently-delete-question = Біржола өшіру керек пе?
|
||||||
|
delete = Өшіру
|
||||||
|
permanently-delete-warning = { $target } біржола өшіріледі. Бұл әрекетті болдырмау мүмкін емес.
|
||||||
|
rename-file = Файлдың атын өзгерту
|
||||||
|
rename-folder = Буманың атын өзгерту
|
||||||
|
replace = Алмастыру
|
||||||
|
replace-title = Бұл жерде "{ $filename }" бұрыннан бар
|
||||||
|
replace-warning = Оны сақталып жатқан файлмен алмастыруды қалайсыз ба? Алмастыру кезінде оның мазмұны қайта жазылады.
|
||||||
|
replace-warning-operation = Оны алмастыруды қалайсыз ба? Алмастыру кезінде оның мазмұны үстінен жазылады.
|
||||||
|
original-file = Түпнұсқа файл
|
||||||
|
replace-with = Келесімен алмастыру
|
||||||
|
apply-to-all = Барлығына іске асыру
|
||||||
|
keep-both = Екеуін де қалдыру
|
||||||
|
skip = Өткізіп жіберу
|
||||||
|
set-executable-and-launch = Орындалатын файл ретінде орнату және жөнелту
|
||||||
|
set-executable-and-launch-description = "{ $name }" нысанын орындалатын файл ретінде орнатып, оны жөнелтуді қалайсыз ба?
|
||||||
|
set-and-launch = Орнату және жөнелту
|
||||||
|
open-with = Көмегімен ашу
|
||||||
|
owner = Иесі
|
||||||
|
group = Топ
|
||||||
|
other = Басқа
|
||||||
|
none = Ештеңе
|
||||||
|
execute-only = Тек орындау
|
||||||
|
write-only = Тек жазу
|
||||||
|
write-execute = Жазу және орындау
|
||||||
|
read-only = Тек оқу
|
||||||
|
read-execute = Оқу және орындау
|
||||||
|
read-write = Оқу және жазу
|
||||||
|
read-write-execute = Оқу, жазу және орындау
|
||||||
|
favorite-path-error = Буманы ашу қатесі
|
||||||
|
favorite-path-error-description =
|
||||||
|
"{ $path }" ашу мүмкін емес
|
||||||
|
"{ $path }" жоқ болуы мүмкін немесе оны ашуға құқығыңыз жоқ
|
||||||
|
|
||||||
|
Оны бүйірлік панельден өшіруді қалайсыз ба?
|
||||||
|
remove = Өшіру
|
||||||
|
keep = Қалдыру
|
||||||
|
repository = Репозиторий
|
||||||
|
support = Қолдау
|
||||||
|
add-network-drive = Желілік дискіні қосу
|
||||||
|
connect = Байланысу
|
||||||
|
connect-anonymously = Анонимді түрде байланысу
|
||||||
|
connecting = Байланысуда...
|
||||||
|
domain = Домен
|
||||||
|
enter-server-address = Сервер адресін енгізіңіз
|
||||||
|
network-drive-description =
|
||||||
|
Сервер адрестері хаттама префиксі мен адрестен тұрады.
|
||||||
|
Мысалдар: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||||
|
network-drive-schemes =
|
||||||
|
Қолжетімді хаттамалар,Префикс
|
||||||
|
AppleTalk,afp://
|
||||||
|
Файлды тасымалдау хаттамасы,ftp:// немесе ftps://
|
||||||
|
Желілік файлдық жүйе,nfs://
|
||||||
|
Сервер хабарламаларының блогы,smb://
|
||||||
|
SSH файлды тасымалдау хаттамасы,sftp:// немесе ssh://
|
||||||
|
WebDAV,dav:// немесе davs://
|
||||||
|
network-drive-error = Желілік дискіге қол жеткізу мүмкін емес
|
||||||
|
password = Пароль
|
||||||
|
remember-password = Парольді есте сақтау
|
||||||
|
try-again = Қайтадан көру
|
||||||
|
username = Пайдаланушы аты
|
||||||
|
cancelled = Бас тартылды
|
||||||
|
edit-history = Тарихты түзету
|
||||||
|
history = Тарихы
|
||||||
|
no-history = Тарихта ешқандай элемент жоқ.
|
||||||
|
pending = Күтілуде
|
||||||
|
progress = { $percent }%
|
||||||
|
progress-cancelled = { $percent }%, бас тартылды
|
||||||
|
progress-failed = { $percent }%, сәтсіз аяқталды
|
||||||
|
progress-paused = { $percent }%, аялдатылды
|
||||||
|
failed = Сәтсіз аяқталды
|
||||||
|
complete = Аяқталды
|
||||||
|
compressing =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне сығу ({ $progress })...
|
||||||
|
compressed =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне сығылды
|
||||||
|
copy_noun = Көшіріп алу
|
||||||
|
creating = "{ $parent }" ішінде "{ $name }" жасау
|
||||||
|
created = "{ $parent }" ішінде "{ $name }" жасалды
|
||||||
|
copying =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне көшіру ({ $progress })...
|
||||||
|
copied =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне көшірілді
|
||||||
|
deleting =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} { trash } ішінен өшіру ({ $progress })...
|
||||||
|
deleted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} { trash } ішінен өшірілді
|
||||||
|
emptying-trash = { trash } тазартылуда ({ $progress })...
|
||||||
|
emptied-trash = { trash } тазартылды
|
||||||
|
extracting =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне тарқату ({ $progress })...
|
||||||
|
extracted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне тарқатылды
|
||||||
|
setting-executable-and-launching = "{ $name }" орындалатын файл ретінде орнату және іске қосу
|
||||||
|
set-executable-and-launched = "{ $name }" орындалатын файл ретінде орнатылды және іске қосылды
|
||||||
|
setting-permissions = "{ $name }" үшін рұқсаттарды { $mode } мәніне орнату
|
||||||
|
set-permissions = "{ $name }" үшін рұқсаттар { $mode } мәніне орнатылды
|
||||||
|
moving =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне жылжыту ({ $progress })...
|
||||||
|
moved =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} "{ $from }" ішінен "{ $to }" ішіне жылжытылды
|
||||||
|
permanently-deleting =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} біржола өшіру
|
||||||
|
permanently-deleted =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} біржола өшірілді
|
||||||
|
removing-from-recents =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} { recents } тізімінен өшіру
|
||||||
|
removed-from-recents =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} { recents } тізімінен өшірілді
|
||||||
|
renaming = "{ $from }" атын "{ $to }" деп өзгерту
|
||||||
|
renamed = "{ $from }" аты "{ $to }" деп өзгертілді
|
||||||
|
restoring =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсені
|
||||||
|
*[other] нәрсені
|
||||||
|
} { trash } ішінен қалпына келтіру ({ $progress })...
|
||||||
|
restored =
|
||||||
|
{ $items } { $items ->
|
||||||
|
[one] нәрсе
|
||||||
|
*[other] нәрсе
|
||||||
|
} { trash } ішінен қалпына келтірілді
|
||||||
|
unknown-folder = белгісіз бума
|
||||||
|
menu-open-with = Көмегімен ашу...
|
||||||
|
default-app = { $name } (әдепкі)
|
||||||
|
show-details = Мәліметтерді көрсету
|
||||||
|
type = Түрі: { $mime }
|
||||||
|
items = Элементтер: { $items }
|
||||||
|
item-size = Өлшемі: { $size }
|
||||||
|
item-created = Жасалған: { $created }
|
||||||
|
item-modified = Өзгертілген: { $modified }
|
||||||
|
item-accessed = Қол жеткізілген: { $accessed }
|
||||||
|
calculating = Есептеу...
|
||||||
|
settings = Баптаулар
|
||||||
|
single-click = Ашу үшін бір рет шерту
|
||||||
|
appearance = Сыртқы түрі
|
||||||
|
theme = Тақырып
|
||||||
|
match-desktop = Жұмыс үстеліне сәйкес келу
|
||||||
|
dark = Күңгірт
|
||||||
|
light = Ашық
|
||||||
|
type-to-search = Іздеу үшін теру
|
||||||
|
type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді
|
||||||
|
type-to-search-enter-path = Бумаға немесе файлға жолды енгізеді
|
||||||
|
type-to-search-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
|
||||||
|
add-to-sidebar = Бүйірлік панельге қосу
|
||||||
|
compress = Сығу
|
||||||
|
delete-permanently = Біржолата өшіру
|
||||||
|
eject = Шығару
|
||||||
|
extract-here = Тарқату
|
||||||
|
new-file = Жаңа файл...
|
||||||
|
new-folder = Жаңа бума...
|
||||||
|
open-in-terminal = Терминалда ашу
|
||||||
|
move-to-trash = Қоқыс жәшігіне тастау
|
||||||
|
restore-from-trash = Қоқыс жәшігінен қалпына келтіру
|
||||||
|
remove-from-sidebar = Бүйірлік панельден өшіру
|
||||||
|
sort-by-name = Аты бойынша сұрыптау
|
||||||
|
sort-by-modified = Өзгертілген уақыты бойынша сұрыптау
|
||||||
|
sort-by-size = Өлшемі бойынша сұрыптау
|
||||||
|
sort-by-trashed = Өшірілген уақыты бойынша сұрыптау
|
||||||
|
remove-from-recents = Соңғылардан өшіру
|
||||||
|
change-wallpaper = Тұсқағазды өзгерту...
|
||||||
|
desktop-appearance = Жұмыс үстелінің сыртқы түрі...
|
||||||
|
display-settings = Көрсету баптаулары...
|
||||||
|
file = Файл
|
||||||
|
new-tab = Жаңа бет
|
||||||
|
new-window = Жаңа терезе
|
||||||
|
reload-folder = Буманы қайта жүктеу
|
||||||
|
rename = Атын өзгерту...
|
||||||
|
close-tab = Бетті жабу
|
||||||
|
quit = Шығу
|
||||||
|
edit = Түзету
|
||||||
|
cut = Қиып алу
|
||||||
|
copy = Көшіру
|
||||||
|
paste = Кірістіру
|
||||||
|
select-all = Барлығын таңдау
|
||||||
|
zoom-in = Үлкейту
|
||||||
|
default-size = Әдепкі өлшем
|
||||||
|
zoom-out = Кішірейту
|
||||||
|
view = Көрініс
|
||||||
|
grid-view = Тор көрінісі
|
||||||
|
list-view = Тізім көрінісі
|
||||||
|
show-hidden-files = Жасырын файлдарды көрсету
|
||||||
|
list-directories-first = Алдымен бумаларды тізімдеу
|
||||||
|
gallery-preview = Галереяны алдын ала қарау
|
||||||
|
menu-settings = Баптаулар...
|
||||||
|
menu-about = COSMIC файлдар туралы...
|
||||||
|
sort = Сұрыптау
|
||||||
|
sort-a-z = А-Я
|
||||||
|
sort-z-a = Я-А
|
||||||
|
sort-newest-first = Алдымен жаңалары
|
||||||
|
sort-oldest-first = Алдымен ескілері
|
||||||
|
sort-smallest-to-largest = Кішісінен үлкеніне
|
||||||
|
sort-largest-to-smallest = Үлкенінен кішісіне
|
||||||
|
|
@ -68,7 +68,7 @@ dark = 다크
|
||||||
light = 라이트
|
light = 라이트
|
||||||
# Context menu
|
# Context menu
|
||||||
new-file = 새 파일...
|
new-file = 새 파일...
|
||||||
new-folder = 새 폴더
|
new-folder = 새 폴더...
|
||||||
open-in-terminal = 터미널에서 열기
|
open-in-terminal = 터미널에서 열기
|
||||||
move-to-trash = 휴지통으로 이동
|
move-to-trash = 휴지통으로 이동
|
||||||
restore-from-trash = 휴지통에서 복구
|
restore-from-trash = 휴지통에서 복구
|
||||||
|
|
@ -84,7 +84,7 @@ sort-by-size = 크기 순으로 정렬
|
||||||
file = 파일
|
file = 파일
|
||||||
new-tab = 새 탭
|
new-tab = 새 탭
|
||||||
new-window = 새 창
|
new-window = 새 창
|
||||||
rename = 이름 바꾸기
|
rename = 이름 바꾸기...
|
||||||
close-tab = 탭 닫기
|
close-tab = 탭 닫기
|
||||||
quit = 종료
|
quit = 종료
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ view = 보기
|
||||||
grid-view = 그리드 보기
|
grid-view = 그리드 보기
|
||||||
list-view = 목록 보기
|
list-view = 목록 보기
|
||||||
menu-settings = 설정...
|
menu-settings = 설정...
|
||||||
menu-about = 코스믹 파일에 대하여...
|
menu-about = COSMIC 파일 정보...
|
||||||
connect = 연결
|
connect = 연결
|
||||||
read-execute = 읽기 및 실행
|
read-execute = 읽기 및 실행
|
||||||
item-modified = 마지막 수정 일자: { $modified }
|
item-modified = 마지막 수정 일자: { $modified }
|
||||||
|
|
@ -171,7 +171,7 @@ details = 세부 정보
|
||||||
mounted-drives = 마운트된 드라이브
|
mounted-drives = 마운트된 드라이브
|
||||||
mount-error = 드라이브에 접근할 수 없음
|
mount-error = 드라이브에 접근할 수 없음
|
||||||
extract-here = 압축 해제
|
extract-here = 압축 해제
|
||||||
removed-from-recents = { recents }에서 { $items } 항목 제거됨
|
removed-from-recents = { recents } 에서 { $items }개의 항목을 제거했습니다
|
||||||
add-to-sidebar = 사이드 바에 추가
|
add-to-sidebar = 사이드 바에 추가
|
||||||
item-created = 생성 일자: { $created }
|
item-created = 생성 일자: { $created }
|
||||||
type-to-search-recursive = 현재 폴더와 하위 폴더 탐색
|
type-to-search-recursive = 현재 폴더와 하위 폴더 탐색
|
||||||
|
|
@ -202,7 +202,7 @@ delete-permanently = 완전히 삭제
|
||||||
networks = 네트워크
|
networks = 네트워크
|
||||||
write-only = 쓰기 전용
|
write-only = 쓰기 전용
|
||||||
today = 오늘
|
today = 오늘
|
||||||
permanently-delete-warning = { $target }이(가) 완전히 삭제됩니다. 이 행동은 되돌릴 수 없습니다.
|
permanently-delete-warning = { $target } 이(가) 완전히 삭제됩니다. 이 행동은 되돌릴 수 없습니다.
|
||||||
empty-trash-warning = 휴지통의 항목이 완전히 삭제됩니다
|
empty-trash-warning = 휴지통의 항목이 완전히 삭제됩니다
|
||||||
empty-trash = 휴지통 비우기
|
empty-trash = 휴지통 비우기
|
||||||
empty-trash-title = 휴지통을 비울까요?
|
empty-trash-title = 휴지통을 비울까요?
|
||||||
|
|
@ -210,3 +210,64 @@ type-to-search = 입력하여 검색
|
||||||
notification-in-progress = 파일 작업이 진행 중입니다
|
notification-in-progress = 파일 작업이 진행 중입니다
|
||||||
permanently-delete-question = 완전히 삭제할까요?
|
permanently-delete-question = 완전히 삭제할까요?
|
||||||
selected-items = { $items }개 항목 선택됨
|
selected-items = { $items }개 항목 선택됨
|
||||||
|
sort-newest-first = 새 항목 우선
|
||||||
|
renamed = "{ $from }" 에서 "{ $to }" 로 이름 변경됨
|
||||||
|
deleted = { trash } 에서 { $items }개의 항목을 제거했습니다
|
||||||
|
reload-folder = 폴더 새로고침
|
||||||
|
favorite-path-error = 디렉터리를 여는 중 오류가 발생했습니다
|
||||||
|
remove-from-sidebar = 사이드 바에서 제거
|
||||||
|
restoring = { trash } 에서 { $items }개의 항목을 복구 중 ({ $progress })...
|
||||||
|
gallery-preview = 갤러리 미리보기
|
||||||
|
sort-smallest-to-largest = 작은 항목부터 큰 항목
|
||||||
|
zoom-in = 확대
|
||||||
|
removing-from-recents = { recents } 에서 { $items }개의 항목을 제거 중
|
||||||
|
zoom-out = 축소
|
||||||
|
compressing = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 중({ $progress })...
|
||||||
|
setting-executable-and-launching = "{ $name }"를 실행 가능으로 설정 및 실행 중
|
||||||
|
default-size = 기본 크기
|
||||||
|
extracted = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 해제했습니다
|
||||||
|
permanently-deleting = { $items }개의 항목을 영구적으로 제거 중
|
||||||
|
compressed = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축했습니다
|
||||||
|
grid-spacing = 그리드 간격
|
||||||
|
copying = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 복사 중({ $progress })...
|
||||||
|
sort-oldest-first = 오래된 항목 우선
|
||||||
|
sort-by-trashed = 삭제된 시간 순으로 정렬
|
||||||
|
copied = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 복사했습니다
|
||||||
|
list-directories-first = 폴더 우선 나열
|
||||||
|
remove-from-recents = 최근 항목에서 제거
|
||||||
|
moving = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 이동 중 ({ $progress })...
|
||||||
|
change-wallpaper = 배경화면 변경...
|
||||||
|
deleting = { trash } 에서 { $items }개의 항목을 제거 중({ $progress })...
|
||||||
|
set-executable-and-launched = "{ $name }"를 실행 가능으로 설정 및 실행됨
|
||||||
|
sort-a-z = A-Z
|
||||||
|
set-and-launch = 설정 후 실행
|
||||||
|
set-executable-and-launch = 실행 가능으로 설정 후 실행
|
||||||
|
restored = { trash } 에서 { $items }개의 항목을 복구했습니다
|
||||||
|
sort-z-a = Z-A
|
||||||
|
operations-running-finished = { $running }개의 작업 진행 중 ({ $percent }%), { $finished } 완료됨...
|
||||||
|
sort = 정렬
|
||||||
|
show-hidden-files = 숨긴 파일 표시
|
||||||
|
trash-folder-icon = 휴지통 아이콘
|
||||||
|
extracting = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 해제 중 ({ $progress })...
|
||||||
|
permanently-deleted = { $items }개의 항목을 영구적으로 제거했습니다
|
||||||
|
renaming = "{ $from }" 에서 "{ $to }" 로 이름 변경 중
|
||||||
|
set-executable-and-launch-description = "{ $name }"을 실행 가능으로 설정하고 실행할까요?
|
||||||
|
sort-largest-to-smallest = 큰 항목부터 작은 항목
|
||||||
|
moved = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 이동했습니다
|
||||||
|
display-settings = 화면 설정...
|
||||||
|
desktop-appearance = 데스크톱 외관...
|
||||||
|
favorite-path-error-description =
|
||||||
|
"{ $path }"을(를) 열 수 없습니다
|
||||||
|
"{ $path }"이(가) 존재하지 않거나 열기 권한이 없을 수 있습니다
|
||||||
|
|
||||||
|
사이드바에서 제거하시겠습니까?
|
||||||
|
operations-running = { $running }개의 작업 진행 중 ({ $percent }%)...
|
||||||
|
network-drive-schemes =
|
||||||
|
지원 프로토콜,접두사(Prefix)
|
||||||
|
AppleTalk,afp://
|
||||||
|
파일 전송 프로토콜 (FTP),ftp:// 또는 ftps://
|
||||||
|
네트워크 파일 시스템 (NFS),nfs://
|
||||||
|
서버 메시지 블록 (SMB),smb://
|
||||||
|
SSH 파일 전송 프로토콜 (SFTP),sftp:// 또는 ssh://
|
||||||
|
WebDAV,dav:// 또는 davs://
|
||||||
|
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
|
||||||
|
|
|
||||||
0
i18n/ms/cosmic_files.ftl
Normal file
0
i18n/ms/cosmic_files.ftl
Normal file
|
|
@ -3,8 +3,8 @@ empty-folder = Lege map
|
||||||
empty-folder-hidden = Lege map (met verborgen bestanden)
|
empty-folder-hidden = Lege map (met verborgen bestanden)
|
||||||
no-results = Geen resultaten gevonden
|
no-results = Geen resultaten gevonden
|
||||||
filesystem = Bestandssysteem
|
filesystem = Bestandssysteem
|
||||||
home = Home
|
home = Persoonlijke map
|
||||||
networks = Netwerk
|
networks = Netwerken
|
||||||
notification-in-progress = Bestanden worden nog bewerkt
|
notification-in-progress = Bestanden worden nog bewerkt
|
||||||
trash = Prullenbak
|
trash = Prullenbak
|
||||||
recents = Recente bestanden
|
recents = Recente bestanden
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ size = Розмір
|
||||||
|
|
||||||
## Empty Trash Dialog
|
## Empty Trash Dialog
|
||||||
|
|
||||||
empty-trash = Спорожнити Смітник
|
empty-trash = Спорожнити смітник
|
||||||
empty-trash-warning = Ви впевнені, що хочете остаточно видалити всі елементи зі Смітника?
|
empty-trash-warning = Елементи зі смітника будуть остаточно видалені
|
||||||
|
|
||||||
## New File/Folder Dialog
|
## New File/Folder Dialog
|
||||||
|
|
||||||
|
|
@ -25,11 +25,11 @@ create-new-file = Створити новий файл
|
||||||
create-new-folder = Створити нову теку
|
create-new-folder = Створити нову теку
|
||||||
file-name = Назва файлу
|
file-name = Назва файлу
|
||||||
folder-name = Назва теки
|
folder-name = Назва теки
|
||||||
file-already-exists = Файл з такою назвою вже існує.
|
file-already-exists = Файл з такою назвою вже існує
|
||||||
folder-already-exists = Тека з такою назвою вже існує.
|
folder-already-exists = Тека з такою назвою вже існує
|
||||||
name-hidden = Назви, що починаються з ".", будуть приховані.
|
name-hidden = Назви, що починаються з ".", будуть приховані
|
||||||
name-invalid = Назва не може бути "{ $filename }".
|
name-invalid = Назва не може бути "{ $filename }"
|
||||||
name-no-slashes = Назва не може містити скісні риски.
|
name-no-slashes = Назва не може містити скісні риски
|
||||||
|
|
||||||
## Open/Save Dialog
|
## Open/Save Dialog
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ rename-folder = Перейменувати теку
|
||||||
## Replace Dialog
|
## Replace Dialog
|
||||||
|
|
||||||
replace = Замінити
|
replace = Замінити
|
||||||
replace-title = " { $filename }" вже існує в цьому місці.
|
replace-title = " { $filename }" вже існує в цьому місці
|
||||||
replace-warning = Бажаєте замінити його тим, що зберігаєте? Замінювання перезапише його вміст.
|
replace-warning = Бажаєте замінити його тим, що зберігаєте? Замінювання перезапише його вміст.
|
||||||
replace-warning-operation = Бажаєте замінити його? Замінювання перезапише його вміст.
|
replace-warning-operation = Бажаєте замінити його? Замінювання перезапише його вміст.
|
||||||
original-file = Початковий файл
|
original-file = Початковий файл
|
||||||
|
|
@ -185,7 +185,7 @@ remove = Вилучити
|
||||||
cancelled = Скасовані
|
cancelled = Скасовані
|
||||||
no-results = Нічого не знайдено
|
no-results = Нічого не знайдено
|
||||||
networks = Мережі
|
networks = Мережі
|
||||||
notification-in-progress = Виконуються операції з файлами.
|
notification-in-progress = Триває обробка файлів
|
||||||
today = Сьогодні
|
today = Сьогодні
|
||||||
desktop-view-options = Параметри вигляду стільниці...
|
desktop-view-options = Параметри вигляду стільниці...
|
||||||
show-on-desktop = Показувати на стільниці
|
show-on-desktop = Показувати на стільниці
|
||||||
|
|
@ -219,9 +219,9 @@ open-with-title = Як ви бажаєте відкрити "{ $name }"?
|
||||||
browse-store = Переглянути { $store }
|
browse-store = Переглянути { $store }
|
||||||
other-apps = Інші застосунки
|
other-apps = Інші застосунки
|
||||||
related-apps = Пов'язані застосунки
|
related-apps = Пов'язані застосунки
|
||||||
permanently-delete-question = Вилучити остаточно
|
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 = Зробити і запустити
|
||||||
|
|
@ -239,8 +239,8 @@ read-write = Перегляд і запис
|
||||||
read-write-execute = Перегляд, запис і виконання
|
read-write-execute = Перегляд, запис і виконання
|
||||||
favorite-path-error = Помилка при відкритті каталогу
|
favorite-path-error = Помилка при відкритті каталогу
|
||||||
favorite-path-error-description =
|
favorite-path-error-description =
|
||||||
Неможливо відкрити "{ $path }".
|
Неможливо відкрити "{ $path }"
|
||||||
Можливо, його не існує або у вас немає прав на відкриття.
|
"{ $path }" можливо, його не існує або у вас немає прав на відкриття
|
||||||
|
|
||||||
Вилучити з бічної панелі?
|
Вилучити з бічної панелі?
|
||||||
keep = Залишити
|
keep = Залишити
|
||||||
|
|
@ -302,7 +302,7 @@ extracted =
|
||||||
} з "{ $from }" до "{ $to }"
|
} з "{ $from }" до "{ $to }"
|
||||||
setting-executable-and-launching = Встановлення "{ $name }" виконуваним і запуск
|
setting-executable-and-launching = Встановлення "{ $name }" виконуваним і запуск
|
||||||
set-executable-and-launched = Встановлено "{ $name }" виконуваним і запущено
|
set-executable-and-launched = Встановлено "{ $name }" виконуваним і запущено
|
||||||
selected-items = { $items } обраних елементів
|
selected-items = Вибрані { $items } елементи
|
||||||
setting-permissions = Встановлення дозволів { $mode } для "{ $name }"
|
setting-permissions = Встановлення дозволів { $mode } для "{ $name }"
|
||||||
set-permissions = Встановлено дозволи { $mode } для "{ $name }"
|
set-permissions = Встановлено дозволи { $mode } для "{ $name }"
|
||||||
show-details = Показати деталі
|
show-details = Показати деталі
|
||||||
|
|
@ -355,3 +355,5 @@ removed-from-recents =
|
||||||
[one] елемент
|
[one] елемент
|
||||||
*[other] елементи
|
*[other] елементи
|
||||||
} з { recents }
|
} з { recents }
|
||||||
|
empty-trash-title = Спорожити смітник?
|
||||||
|
type-to-search-select = Вибирає перший відповідний файл або папку
|
||||||
|
|
|
||||||
0
i18n/uz/cosmic_files.ftl
Normal file
0
i18n/uz/cosmic_files.ftl
Normal file
794
src/app.rs
794
src/app.rs
File diff suppressed because it is too large
Load diff
109
src/archive.rs
109
src/archive.rs
|
|
@ -1,15 +1,15 @@
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
fs,
|
|
||||||
io::{self, Read, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
use zip::result::ZipError;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
mime_icon::mime_for_path,
|
mime_icon::mime_for_path,
|
||||||
operation::{Controller, OpReader, OperationError, OperationErrorType},
|
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||||
};
|
};
|
||||||
|
use cosmic::iced::futures;
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
fs,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use zip::result::ZipError;
|
||||||
|
|
||||||
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
||||||
"application/gzip",
|
"application/gzip",
|
||||||
|
|
@ -113,27 +113,36 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
use std::{ffi::OsString, fs};
|
use std::{ffi::OsString, fs};
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
|
|
||||||
fn make_writable_dir_all<T: AsRef<Path>>(outpath: T) -> Result<(), ZipError> {
|
fn make_writable_dir_all<T: AsRef<Path>>(
|
||||||
fs::create_dir_all(outpath.as_ref())?;
|
outpath: T,
|
||||||
|
target_dirs: &mut HashSet<PathBuf>,
|
||||||
|
) -> Result<(), ZipError> {
|
||||||
|
let path = outpath.as_ref();
|
||||||
|
if !path.exists() {
|
||||||
|
fs::create_dir_all(path)?;
|
||||||
|
}
|
||||||
|
if !target_dirs.contains(path) {
|
||||||
|
target_dirs.insert(path.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
// Dirs must be writable until all normal files are extracted
|
// Dirs must be writable until all normal files are extracted
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
std::fs::set_permissions(
|
fs::set_permissions(
|
||||||
outpath.as_ref(),
|
path,
|
||||||
std::fs::Permissions::from_mode(
|
fs::Permissions::from_mode(0o700 | fs::metadata(path)?.permissions().mode()),
|
||||||
0o700 | std::fs::metadata(outpath.as_ref())?.permissions().mode(),
|
|
||||||
),
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
let mut files_by_unix_mode = Vec::new();
|
|
||||||
let mut buffer = vec![0; 4 * 1024 * 1024];
|
let mut buffer = vec![0; 4 * 1024 * 1024];
|
||||||
let total_files = archive.len();
|
let total_files = archive.len();
|
||||||
let mut pending_directory_creates = VecDeque::new();
|
let mut written_files = Vec::with_capacity(total_files);
|
||||||
|
let mut target_dirs = HashSet::new();
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut files_by_unix_mode = Vec::with_capacity(total_files);
|
||||||
|
|
||||||
for i in 0..total_files {
|
for i in 0..total_files {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
|
|
@ -143,7 +152,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
|
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / total_files as f32);
|
controller.set_progress(i as f32 / total_files as f32);
|
||||||
|
|
||||||
let mut file = match password {
|
let mut file = match password {
|
||||||
None => archive.by_index(i),
|
None => archive.by_index(i),
|
||||||
|
|
@ -156,26 +165,22 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
let outpath = directory.as_ref().join(filepath);
|
let outpath = directory.as_ref().join(filepath);
|
||||||
|
|
||||||
if file.is_dir() {
|
if file.is_dir() {
|
||||||
pending_directory_creates.push_back(outpath.clone());
|
make_writable_dir_all(&outpath, &mut target_dirs)?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(mode) = file.unix_mode() {
|
||||||
|
files_by_unix_mode.push((outpath, mode));
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let symlink_target = if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
|
||||||
|
if let Some(parent) = outpath.parent() {
|
||||||
|
make_writable_dir_all(parent, &mut target_dirs)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
||||||
let mut target = Vec::with_capacity(file.size() as usize);
|
let mut target = Vec::with_capacity(file.size() as usize);
|
||||||
file.read_to_end(&mut target)?;
|
file.read_to_end(&mut target)?;
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
drop(file);
|
|
||||||
if let Some(target) = symlink_target {
|
|
||||||
// create all pending dirs
|
|
||||||
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
|
||||||
make_writable_dir_all(pending_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p) = outpath.parent() {
|
|
||||||
make_writable_dir_all(p)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
|
@ -205,21 +210,10 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
std::os::windows::fs::symlink_file(target_path, outpath.as_path())?;
|
std::os::windows::fs::symlink_file(target_path, outpath.as_path())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
written_files.push(outpath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut file = match password {
|
|
||||||
None => archive.by_index(i),
|
|
||||||
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
// create all pending dirs
|
|
||||||
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
|
||||||
make_writable_dir_all(pending_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p) = outpath.parent() {
|
|
||||||
make_writable_dir_all(p)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let total = file.size();
|
let total = file.size();
|
||||||
let mut outfile = fs::File::create(&outpath)?;
|
let mut outfile = fs::File::create(&outpath)?;
|
||||||
|
|
@ -245,13 +239,14 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
controller.set_progress(total_progress);
|
controller.set_progress(total_progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for real permissions, which we'll set in a second pass
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
if let Some(mode) = file.unix_mode() {
|
||||||
// Check for real permissions, which we'll set in a second pass
|
files_by_unix_mode.push((outpath.clone(), mode));
|
||||||
if let Some(mode) = file.unix_mode() {
|
|
||||||
files_by_unix_mode.push((outpath.clone(), mode));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
written_files.push(outpath);
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
|
@ -260,11 +255,15 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
|
|
||||||
if files_by_unix_mode.len() > 1 {
|
if files_by_unix_mode.len() > 1 {
|
||||||
// Ensure we update children's permissions before making a parent unwritable
|
// Ensure we update children's permissions before making a parent unwritable
|
||||||
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone()));
|
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.components().count()));
|
||||||
}
|
}
|
||||||
for (path, mode) in files_by_unix_mode {
|
for (path, mode) in files_by_unix_mode {
|
||||||
fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
|
fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush files to disk
|
||||||
|
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
296
src/dialog.rs
296
src/dialog.rs
|
|
@ -179,11 +179,11 @@ impl<T: AsRef<str>> From<T> for DialogLabel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(span) = spans.last_mut() {
|
if let Some(span) = spans.last_mut()
|
||||||
if underline == span.underline {
|
&& underline == span.underline
|
||||||
span.text.push(c);
|
{
|
||||||
continue;
|
span.text.push(c);
|
||||||
}
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
spans.push(DialogLabelSpan {
|
spans.push(DialogLabelSpan {
|
||||||
|
|
@ -718,10 +718,10 @@ impl App {
|
||||||
children.push(preview);
|
children.push(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
if children.is_empty() {
|
if children.is_empty()
|
||||||
if let Some(item) = &self.tab.parent_item_opt {
|
&& let Some(item) = &self.tab.parent_item_opt
|
||||||
children.push(item.preview_view(None, military_time));
|
{
|
||||||
}
|
children.push(item.preview_view(None, military_time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1279,12 +1279,12 @@ impl Application for App {
|
||||||
return self.update(message);
|
return self.update(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = self.nav_model.data::<MounterData>(entity) {
|
if let Some(data) = self.nav_model.data::<MounterData>(entity)
|
||||||
if let Some(mounter) = MOUNTERS.get(&data.0) {
|
&& let Some(mounter) = MOUNTERS.get(&data.0)
|
||||||
return mounter
|
{
|
||||||
.mount(data.1.clone())
|
return mounter
|
||||||
.map(|()| cosmic::action::none());
|
.mount(data.1.clone())
|
||||||
}
|
.map(|()| cosmic::action::none());
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -1322,10 +1322,10 @@ impl Application for App {
|
||||||
|
|
||||||
// Close the dialog if the focused widget is the dialog's main text input instead of
|
// Close the dialog if the focused widget is the dialog's main text input instead of
|
||||||
// unfocussing the widget.
|
// unfocussing the widget.
|
||||||
if let operation::Outcome::Some(focused) = operation::focusable::find_focused().finish() {
|
if let operation::Outcome::Some(focused) = operation::focusable::find_focused().finish()
|
||||||
if self.dialog_text_input == focused {
|
&& self.dialog_text_input == focused
|
||||||
return self.update(Message::Cancel);
|
{
|
||||||
}
|
return self.update(Message::Cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update(Message::Cancel)
|
self.update(Message::Cancel)
|
||||||
|
|
@ -1419,14 +1419,14 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
if key_bind.matches(modifiers, &key) {
|
&& key_bind.matches(modifiers, &key)
|
||||||
return self.update(if self.flags.kind.save() {
|
{
|
||||||
Message::Save(false)
|
return self.update(if self.flags.kind.save() {
|
||||||
} else {
|
Message::Save(false)
|
||||||
Message::Open
|
} else {
|
||||||
});
|
Message::Open
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncaptured keys with only shift modifiers go to the search or location box
|
// Uncaptured keys with only shift modifiers go to the search or location box
|
||||||
|
|
@ -1434,45 +1434,44 @@ impl Application for App {
|
||||||
&& !modifiers.control()
|
&& !modifiers.control()
|
||||||
&& !modifiers.alt()
|
&& !modifiers.alt()
|
||||||
&& matches!(key, Key::Character(_))
|
&& matches!(key, Key::Character(_))
|
||||||
|
&& let Some(text) = text
|
||||||
{
|
{
|
||||||
if let Some(text) = text {
|
match self.flags.config.type_to_search {
|
||||||
match self.flags.config.type_to_search {
|
TypeToSearch::Recursive => {
|
||||||
TypeToSearch::Recursive => {
|
let mut term = self.search_get().unwrap_or_default().to_string();
|
||||||
let mut term = self.search_get().unwrap_or_default().to_string();
|
term.push_str(&text);
|
||||||
term.push_str(&text);
|
return self.search_set(Some(term));
|
||||||
return self.search_set(Some(term));
|
}
|
||||||
|
TypeToSearch::EnterPath => {
|
||||||
|
let location = (self.tab.edit_location)
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| &self.tab.location, |x| &x.location);
|
||||||
|
// Try to add text to end of location
|
||||||
|
if let Some(path) = location.path_opt() {
|
||||||
|
let mut path_string = path.to_string_lossy().to_string();
|
||||||
|
path_string.push_str(&text);
|
||||||
|
self.tab.edit_location =
|
||||||
|
Some(location.with_path(PathBuf::from(path_string)).into());
|
||||||
}
|
}
|
||||||
TypeToSearch::EnterPath => {
|
}
|
||||||
let location = (self.tab.edit_location)
|
TypeToSearch::SelectByPrefix => {
|
||||||
.as_ref()
|
// Reset buffer if timeout elapsed
|
||||||
.map_or_else(|| &self.tab.location, |x| &x.location);
|
if let Some(last_key) = self.type_select_last_key
|
||||||
// Try to add text to end of location
|
&& last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT
|
||||||
if let Some(path) = location.path_opt() {
|
{
|
||||||
let mut path_string = path.to_string_lossy().to_string();
|
self.type_select_prefix.clear();
|
||||||
path_string.push_str(&text);
|
|
||||||
self.tab.edit_location =
|
|
||||||
Some(location.with_path(PathBuf::from(path_string)).into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TypeToSearch::SelectByPrefix => {
|
|
||||||
// Reset buffer if timeout elapsed
|
|
||||||
if let Some(last_key) = self.type_select_last_key {
|
|
||||||
if last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT {
|
|
||||||
self.type_select_prefix.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate character and select
|
// Accumulate character and select
|
||||||
self.type_select_prefix.push_str(&text.to_lowercase());
|
self.type_select_prefix.push_str(&text.to_lowercase());
|
||||||
self.type_select_last_key = Some(Instant::now());
|
self.type_select_last_key = Some(Instant::now());
|
||||||
|
|
||||||
self.tab.select_by_prefix(&self.type_select_prefix);
|
self.tab.select_by_prefix(&self.type_select_prefix);
|
||||||
if let Some(offset) = self.tab.select_focus_scroll() {
|
if let Some(offset) = self.tab.select_focus_scroll() {
|
||||||
return scrollable::scroll_to(
|
return scrollable::scroll_to(
|
||||||
self.tab.scrollable_id.clone(),
|
self.tab.scrollable_id.clone(),
|
||||||
offset,
|
offset,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1486,21 +1485,22 @@ impl Application for App {
|
||||||
let mut unmounted = Vec::new();
|
let mut unmounted = Vec::new();
|
||||||
if let Some(old_items) = self.mounter_items.get(&mounter_key) {
|
if let Some(old_items) = self.mounter_items.get(&mounter_key) {
|
||||||
for old_item in old_items {
|
for old_item in old_items {
|
||||||
if let Some(old_path) = old_item.path() {
|
if let Some(old_path) = old_item.path()
|
||||||
if old_item.is_mounted() {
|
&& old_item.is_mounted()
|
||||||
let mut still_mounted = false;
|
{
|
||||||
for item in &mounter_items {
|
let mut still_mounted = false;
|
||||||
if let Some(path) = item.path() {
|
for item in &mounter_items {
|
||||||
if path == old_path && item.is_mounted() {
|
if let Some(path) = item.path()
|
||||||
still_mounted = true;
|
&& path == old_path
|
||||||
break;
|
&& item.is_mounted()
|
||||||
}
|
{
|
||||||
}
|
still_mounted = true;
|
||||||
}
|
break;
|
||||||
if !still_mounted {
|
|
||||||
unmounted.push(Location::Path(old_path));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !still_mounted {
|
||||||
|
unmounted.push(Location::Path(old_path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1601,16 +1601,16 @@ impl Application for App {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
if let Some(items) = self.tab.items_opt() {
|
if let Some(items) = self.tab.items_opt() {
|
||||||
for item in items {
|
for item in items {
|
||||||
if item.selected {
|
if item.selected
|
||||||
if let Some(path) = item.path_opt() {
|
&& let Some(path) = item.path_opt()
|
||||||
paths.push(path.clone());
|
{
|
||||||
let _ = update_recently_used(
|
paths.push(path.clone());
|
||||||
path,
|
let _ = update_recently_used(
|
||||||
Self::APP_ID.to_string(),
|
path,
|
||||||
"cosmic-files".to_string(),
|
Self::APP_ID.to_string(),
|
||||||
None,
|
"cosmic-files".to_string(),
|
||||||
);
|
None,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1640,11 +1640,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are in directory mode, return the current directory
|
// If we are in directory mode, return the current directory
|
||||||
if self.flags.kind.is_dir() {
|
if self.flags.kind.is_dir()
|
||||||
if let Location::Path(tab_path) = &self.tab.location {
|
&& let Location::Path(tab_path) = &self.tab.location
|
||||||
self.result_opt = Some(DialogResult::Open(vec![tab_path.clone()]));
|
{
|
||||||
return window::close(self.flags.window_id);
|
self.result_opt = Some(DialogResult::Open(vec![tab_path.clone()]));
|
||||||
}
|
return window::close(self.flags.window_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Preview => {
|
Message::Preview => {
|
||||||
|
|
@ -1654,26 +1654,24 @@ impl Application for App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::Save(replace) => {
|
Message::Save(replace) => {
|
||||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
if let DialogKind::SaveFile { filename } = &self.flags.kind
|
||||||
if !filename.is_empty() {
|
&& !filename.is_empty()
|
||||||
if let Some(tab_path) = self.tab.location.path_opt() {
|
&& let Some(tab_path) = self.tab.location.path_opt()
|
||||||
let path = tab_path.join(filename);
|
{
|
||||||
if path.is_dir() {
|
let path = tab_path.join(filename);
|
||||||
// cd to directory
|
if path.is_dir() {
|
||||||
let message = Message::TabMessage(tab::Message::Location(
|
// cd to directory
|
||||||
Location::Path(path),
|
let message =
|
||||||
));
|
Message::TabMessage(tab::Message::Location(Location::Path(path)));
|
||||||
return self.update(message);
|
return self.update(message);
|
||||||
} else if !replace && path.exists() {
|
} else if !replace && path.exists() {
|
||||||
self.dialog_pages.push_back(DialogPage::Replace {
|
self.dialog_pages.push_back(DialogPage::Replace {
|
||||||
filename: filename.clone(),
|
filename: filename.clone(),
|
||||||
});
|
});
|
||||||
return widget::button::focus(REPLACE_BUTTON_ID.clone());
|
return widget::button::focus(REPLACE_BUTTON_ID.clone());
|
||||||
}
|
|
||||||
self.result_opt = Some(DialogResult::Open(vec![path]));
|
|
||||||
return window::close(self.flags.window_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.result_opt = Some(DialogResult::Open(vec![path]));
|
||||||
|
return window::close(self.flags.window_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ScrollTab(scroll_speed) => {
|
Message::ScrollTab(scroll_speed) => {
|
||||||
|
|
@ -1703,16 +1701,14 @@ impl Application for App {
|
||||||
let tab_commands = self.tab.update(tab_message, self.modifiers);
|
let tab_commands = self.tab.update(tab_message, self.modifiers);
|
||||||
|
|
||||||
// Update filename box when anything is selected
|
// Update filename box when anything is selected
|
||||||
if let DialogKind::SaveFile { filename } = &mut self.flags.kind {
|
if let DialogKind::SaveFile { filename } = &mut self.flags.kind
|
||||||
if let Some(click_i) = click_i_opt {
|
&& let Some(click_i) = click_i_opt
|
||||||
if let Some(items) = self.tab.items_opt() {
|
&& let Some(items) = self.tab.items_opt()
|
||||||
if let Some(item) = items.get(click_i) {
|
&& let Some(item) = items.get(click_i)
|
||||||
if item.selected && !item.metadata.is_dir() {
|
&& item.selected
|
||||||
filename.clone_from(&item.name);
|
&& !item.metadata.is_dir()
|
||||||
}
|
{
|
||||||
}
|
filename.clone_from(&item.name);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
|
|
@ -1840,34 +1836,34 @@ impl Application for App {
|
||||||
Message::TabRescan(location, parent_item_opt, mut items, selection_paths) => {
|
Message::TabRescan(location, parent_item_opt, mut items, selection_paths) => {
|
||||||
if location == self.tab.location {
|
if location == self.tab.location {
|
||||||
// Filter
|
// Filter
|
||||||
if let Some(filter_i) = self.filter_selected {
|
if let Some(filter_i) = self.filter_selected
|
||||||
if let Some(filter) = self.filters.get(filter_i) {
|
&& let Some(filter) = self.filters.get(filter_i)
|
||||||
// Parse globs (Mime implements PartialEq with &str, so no need to parse)
|
{
|
||||||
let mut parsed_globs = Vec::new();
|
// Parse globs (Mime implements PartialEq with &str, so no need to parse)
|
||||||
let mut mimes = Vec::new();
|
let mut parsed_globs = Vec::new();
|
||||||
for pattern in &filter.patterns {
|
let mut mimes = Vec::new();
|
||||||
match pattern {
|
for pattern in &filter.patterns {
|
||||||
DialogFilterPattern::Glob(value) => {
|
match pattern {
|
||||||
match glob::Pattern::new(value) {
|
DialogFilterPattern::Glob(value) => {
|
||||||
Ok(glob) => parsed_globs.push(glob),
|
match glob::Pattern::new(value) {
|
||||||
Err(err) => {
|
Ok(glob) => parsed_globs.push(glob),
|
||||||
log::warn!("failed to parse glob {value:?}: {err}");
|
Err(err) => {
|
||||||
}
|
log::warn!("failed to parse glob {value:?}: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DialogFilterPattern::Mime(value) => mimes.push(value.as_str()),
|
|
||||||
}
|
}
|
||||||
|
DialogFilterPattern::Mime(value) => mimes.push(value.as_str()),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items.retain(|item| {
|
items.retain(|item| {
|
||||||
// Directories are always shown
|
// Directories are always shown
|
||||||
item.metadata.is_dir()
|
item.metadata.is_dir()
|
||||||
// Check for mime type match (first because it is faster)
|
// Check for mime type match (first because it is faster)
|
||||||
|| mimes.iter().copied().any(|mime| mime == item.mime)
|
|| mimes.iter().copied().any(|mime| mime == item.mime)
|
||||||
// Check for glob match (last because it is slower)
|
// Check for glob match (last because it is slower)
|
||||||
|| parsed_globs.iter().any(|glob| glob.matches(&item.name))
|
|| parsed_globs.iter().any(|glob| glob.matches(&item.name))
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select based on filename
|
// Select based on filename
|
||||||
|
|
@ -1944,19 +1940,19 @@ impl Application for App {
|
||||||
|
|
||||||
let mut col = widget::column::with_capacity(2);
|
let mut col = widget::column::with_capacity(2);
|
||||||
|
|
||||||
if self.core.is_condensed() {
|
if self.core.is_condensed()
|
||||||
if let Some(term) = self.search_get() {
|
&& let Some(term) = self.search_get()
|
||||||
col = col.push(
|
{
|
||||||
widget::container(
|
col = col.push(
|
||||||
widget::text_input::search_input("", term)
|
widget::container(
|
||||||
.width(Length::Fill)
|
widget::text_input::search_input("", term)
|
||||||
.id(self.search_id.clone())
|
.width(Length::Fill)
|
||||||
.on_clear(Message::SearchClear)
|
.id(self.search_id.clone())
|
||||||
.on_input(Message::SearchInput),
|
.on_clear(Message::SearchClear)
|
||||||
)
|
.on_input(Message::SearchInput),
|
||||||
.padding(space_xxs),
|
)
|
||||||
);
|
.padding(space_xxs),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
col = col.push(
|
col = col.push(
|
||||||
|
|
|
||||||
|
|
@ -392,16 +392,16 @@ impl LargeImageManager {
|
||||||
generation: u64,
|
generation: u64,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Check if this decode is still current (not superseded by a newer one)
|
// Check if this decode is still current (not superseded by a newer one)
|
||||||
if let Some(¤t_gen) = self.decode_generations.get(&path) {
|
if let Some(¤t_gen) = self.decode_generations.get(&path)
|
||||||
if generation != current_gen {
|
&& generation != current_gen
|
||||||
log::info!(
|
{
|
||||||
"Discarding outdated decode for {} (generation {} != current {})",
|
log::info!(
|
||||||
path.display(),
|
"Discarding outdated decode for {} (generation {} != current {})",
|
||||||
generation,
|
path.display(),
|
||||||
current_gen
|
generation,
|
||||||
);
|
current_gen
|
||||||
return false;
|
);
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -556,7 +556,7 @@ impl LargeImageManager {
|
||||||
|
|
||||||
/// Check if sufficient memory is available, clearing cache if needed.
|
/// Check if sufficient memory is available, clearing cache if needed.
|
||||||
/// Returns true if memory is available, false otherwise.
|
/// Returns true if memory is available, false otherwise.
|
||||||
fn ensure_memory_available(&mut self, path: &PathBuf, width: u32, height: u32) -> bool {
|
fn ensure_memory_available(&mut self, path: &Path, width: u32, height: u32) -> bool {
|
||||||
let (has_memory, error_opt) = check_memory_available(width, height);
|
let (has_memory, error_opt) = check_memory_available(width, height);
|
||||||
|
|
||||||
if has_memory {
|
if has_memory {
|
||||||
|
|
@ -565,7 +565,7 @@ impl LargeImageManager {
|
||||||
|
|
||||||
if self.cache_is_empty() {
|
if self.cache_is_empty() {
|
||||||
if let Some(error_msg) = error_opt {
|
if let Some(error_msg) = error_opt {
|
||||||
self.store_error(path.clone(), error_msg);
|
self.store_error(path.to_path_buf(), error_msg);
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Cannot load {}: insufficient memory and cache is empty",
|
"Cannot load {}: insufficient memory and cache is empty",
|
||||||
path.display()
|
path.display()
|
||||||
|
|
@ -588,7 +588,7 @@ impl LargeImageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error_msg) = error_opt_after {
|
if let Some(error_msg) = error_opt_after {
|
||||||
self.store_error(path.clone(), error_msg);
|
self.store_error(path.to_path_buf(), error_msg);
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Cannot load {}: insufficient memory even after cache clear",
|
"Cannot load {}: insufficient memory even after cache clear",
|
||||||
path.display()
|
path.display()
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,10 @@ pub static LOCALE: LazyLock<Locale> = LazyLock::new(|| {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try language-only fallback (e.g., "en" from "en-US")
|
// Try language-only fallback (e.g., "en" from "en-US")
|
||||||
if let Some(lang) = cleaned_locale.split('-').next() {
|
if let Some(lang) = cleaned_locale.split('-').next()
|
||||||
if let Ok(locale) = Locale::try_from_str(lang) {
|
&& let Ok(locale) = Locale::try_from_str(lang)
|
||||||
return locale;
|
{
|
||||||
}
|
return locale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ impl MimeAppCache {
|
||||||
for (mime, filenames) in list.removed_associations.iter() {
|
for (mime, filenames) in list.removed_associations.iter() {
|
||||||
for filename in filenames {
|
for filename in filenames {
|
||||||
log::trace!("remove {mime}={filename}");
|
log::trace!("remove {mime}={filename}");
|
||||||
if let Some(apps) = self.cache.get_mut(&mime) {
|
if let Some(apps) = self.cache.get_mut(mime) {
|
||||||
apps.retain(|x| !filename_eq(&x.path, filename));
|
apps.retain(|x| !filename_eq(&x.path, filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +319,7 @@ impl MimeAppCache {
|
||||||
for (mime, filenames) in list.default_apps.iter() {
|
for (mime, filenames) in list.default_apps.iter() {
|
||||||
for filename in filenames {
|
for filename in filenames {
|
||||||
log::trace!("default {mime}={filename}");
|
log::trace!("default {mime}={filename}");
|
||||||
if let Some(apps) = self.cache.get_mut(&mime) {
|
if let Some(apps) = self.cache.get_mut(mime) {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for app in apps.iter_mut() {
|
for app in apps.iter_mut() {
|
||||||
if filename_eq(&app.path, filename) {
|
if filename_eq(&app.path, filename) {
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,11 @@ fn resolve_uri(uri: &str) -> (String, gio::File) {
|
||||||
TARGET_URI_ATTRIBUTE,
|
TARGET_URI_ATTRIBUTE,
|
||||||
gio::FileQueryInfoFlags::NONE,
|
gio::FileQueryInfoFlags::NONE,
|
||||||
gio::Cancellable::NONE,
|
gio::Cancellable::NONE,
|
||||||
) {
|
) && let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE)
|
||||||
if let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE) {
|
{
|
||||||
let resolved_uri = String::from(resolved_uri);
|
let resolved_uri = String::from(resolved_uri);
|
||||||
let file = gio::File::for_uri(&resolved_uri);
|
let file = gio::File::for_uri(&resolved_uri);
|
||||||
return (resolved_uri, file);
|
return (resolved_uri, file);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(uri.to_string(), file)
|
(uri.to_string(), file)
|
||||||
|
|
@ -60,7 +59,7 @@ fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems {
|
||||||
gio::Cancellable::NONE,
|
gio::Cancellable::NONE,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|info| Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE)))
|
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||||
.unwrap_or(true); // Default to remote if query fails
|
.unwrap_or(true); // Default to remote if query fails
|
||||||
|
|
||||||
MounterItem::Gvfs(Item {
|
MounterItem::Gvfs(Item {
|
||||||
|
|
@ -457,9 +456,9 @@ impl Gvfs {
|
||||||
log::info!("mount {name}: result {res:?}");
|
log::info!("mount {name}: result {res:?}");
|
||||||
// Update the mounter_item with mount information after successful mount
|
// Update the mounter_item with mount information after successful mount
|
||||||
let mut updated_item = mounter_item.clone();
|
let mut updated_item = mounter_item.clone();
|
||||||
if res.is_ok() {
|
if res.is_ok()
|
||||||
if let MounterItem::Gvfs(ref mut item) = updated_item {
|
&& let MounterItem::Gvfs(ref mut item) = updated_item
|
||||||
if let Some(mount) = volume_for_callback.get_mount() {
|
&& let Some(mount) = volume_for_callback.get_mount() {
|
||||||
let root = MountExt::root(&mount);
|
let root = MountExt::root(&mount);
|
||||||
item.path_opt = root.path();
|
item.path_opt = root.path();
|
||||||
item.is_mounted = true;
|
item.is_mounted = true;
|
||||||
|
|
@ -469,14 +468,9 @@ impl Gvfs {
|
||||||
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
||||||
gio::Cancellable::NONE,
|
gio::Cancellable::NONE,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||||
.and_then(|info| {
|
|
||||||
Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
|
||||||
})
|
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
event_tx.send(Event::MountResult(updated_item, match res {
|
event_tx.send(Event::MountResult(updated_item, match res {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
_ = complete_tx.send(Ok(()));
|
_ = complete_tx.send(Ok(()));
|
||||||
|
|
|
||||||
|
|
@ -246,19 +246,18 @@ struct State {
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn drag_rect(&self, cursor: mouse::Cursor) -> Option<Rectangle> {
|
fn drag_rect(&self, cursor: mouse::Cursor) -> Option<Rectangle> {
|
||||||
if let Some(drag_source) = self.drag_initiated {
|
if let Some(drag_source) = self.drag_initiated
|
||||||
if let Some(position) = cursor.position().or(self.last_virtual_position) {
|
&& let Some(position) = cursor.position().or(self.last_virtual_position)
|
||||||
if position.distance(drag_source) > 1.0 {
|
&& position.distance(drag_source) > 1.0
|
||||||
let min_x = drag_source.x.min(position.x);
|
{
|
||||||
let max_x = drag_source.x.max(position.x);
|
let min_x = drag_source.x.min(position.x);
|
||||||
let min_y = drag_source.y.min(position.y);
|
let max_x = drag_source.x.max(position.x);
|
||||||
let max_y = drag_source.y.max(position.y);
|
let min_y = drag_source.y.min(position.y);
|
||||||
return Some(Rectangle::new(
|
let max_y = drag_source.y.max(position.y);
|
||||||
Point::new(min_x, min_y),
|
return Some(Rectangle::new(
|
||||||
Size::new(max_x - min_x, max_y - min_y),
|
Point::new(min_x, min_y),
|
||||||
));
|
Size::new(max_x - min_x, max_y - min_y),
|
||||||
}
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -527,12 +526,12 @@ fn update<Message: Clone>(
|
||||||
let offset = layout.virtual_offset();
|
let offset = layout.virtual_offset();
|
||||||
let layout_bounds = layout.bounds();
|
let layout_bounds = layout.bounds();
|
||||||
|
|
||||||
let viewport_changed = state.viewport.map_or(true, |v| v != *viewport);
|
let viewport_changed = state.viewport != Some(*viewport);
|
||||||
|
|
||||||
if let Some(message) = widget.on_resize.as_ref() {
|
if let Some(message) = widget.on_resize.as_ref()
|
||||||
if viewport_changed {
|
&& viewport_changed
|
||||||
shell.publish(message(*viewport));
|
{
|
||||||
}
|
shell.publish(message(*viewport));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.viewport = Some(*viewport);
|
state.viewport = Some(*viewport);
|
||||||
|
|
@ -664,113 +663,112 @@ fn update<Message: Clone>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_right_press.as_ref() {
|
if let Some(message) = widget.on_right_press.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right))
|
||||||
) {
|
)
|
||||||
let point_opt = if widget.on_right_press_window_position {
|
{
|
||||||
cursor.position_over(layout_bounds).map(|mut p| {
|
let point_opt = if widget.on_right_press_window_position {
|
||||||
p.x -= offset.x;
|
cursor.position_over(layout_bounds).map(|mut p| {
|
||||||
p.y -= offset.y;
|
p.x -= offset.x;
|
||||||
p
|
p.y -= offset.y;
|
||||||
})
|
p
|
||||||
} else {
|
})
|
||||||
cursor.position_in(layout_bounds)
|
} else {
|
||||||
};
|
cursor.position_in(layout_bounds)
|
||||||
shell.publish(message(point_opt));
|
};
|
||||||
|
shell.publish(message(point_opt));
|
||||||
|
|
||||||
if widget.on_right_press_no_capture {
|
if widget.on_right_press_no_capture {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
}
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_right_release.as_ref() {
|
if let Some(message) = widget.on_right_release.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_middle_press.as_ref() {
|
if let Some(message) = widget.on_middle_press.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_middle_release.as_ref() {
|
if let Some(message) = widget.on_middle_release.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_back_press.as_ref() {
|
if let Some(message) = widget.on_back_press.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_back_release.as_ref() {
|
if let Some(message) = widget.on_back_release.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Back))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Back))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_forward_press.as_ref() {
|
if let Some(message) = widget.on_forward_press.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = widget.on_forward_release.as_ref() {
|
if let Some(message) = widget.on_forward_release.as_ref()
|
||||||
if matches!(
|
&& matches!(
|
||||||
event,
|
event,
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Forward))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Forward))
|
||||||
) {
|
)
|
||||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
{
|
||||||
|
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(on_scroll) = widget.on_scroll.as_ref() {
|
if let Some(on_scroll) = widget.on_scroll.as_ref()
|
||||||
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
|
&& let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event
|
||||||
if let Some(message) = on_scroll(*delta) {
|
&& let Some(message) = on_scroll(*delta)
|
||||||
shell.publish(message);
|
{
|
||||||
return event::Status::Captured;
|
shell.publish(message);
|
||||||
}
|
return event::Status::Captured;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
|
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
spawn_detached::spawn_detached,
|
spawn_detached::spawn_detached,
|
||||||
tab,
|
tab,
|
||||||
};
|
};
|
||||||
use cosmic::iced::futures::{SinkExt, channel::mpsc::Sender};
|
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
fmt::Formatter,
|
fmt::Formatter,
|
||||||
|
|
@ -196,6 +196,31 @@ async fn copy_or_move(
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn sync_to_disk(
|
||||||
|
written_files: Vec<PathBuf>,
|
||||||
|
target_dirs: std::collections::HashSet<PathBuf>,
|
||||||
|
) {
|
||||||
|
// Sync files to disk
|
||||||
|
stream::iter(written_files.into_iter().map(|path| async move {
|
||||||
|
if let Ok(file) = compio::fs::OpenOptions::new().write(true).open(&path).await {
|
||||||
|
let _ = file.sync_all().await;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.buffer_unordered(32)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Sync directories to disk
|
||||||
|
stream::iter(target_dirs.into_iter().map(|path| async move {
|
||||||
|
if let Ok(dir) = compio::fs::OpenOptions::new().read(true).open(&path).await {
|
||||||
|
let _ = dir.sync_all().await;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.buffer_unordered(16)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
||||||
// List of compound extensions to check
|
// List of compound extensions to check
|
||||||
const COMPOUND_EXTENSIONS: &[&str] = &[
|
const COMPOUND_EXTENSIONS: &[&str] = &[
|
||||||
|
|
@ -934,10 +959,10 @@ impl Operation {
|
||||||
let dir_name = get_directory_name(file_name);
|
let dir_name = get_directory_name(file_name);
|
||||||
let mut new_dir = to.join(dir_name);
|
let mut new_dir = to.join(dir_name);
|
||||||
|
|
||||||
if new_dir.exists() {
|
if new_dir.exists()
|
||||||
if let Some(new_dir_parent) = new_dir.parent() {
|
&& let Some(new_dir_parent) = new_dir.parent()
|
||||||
new_dir = copy_unique_path(&new_dir, new_dir_parent);
|
{
|
||||||
}
|
new_dir = copy_unique_path(&new_dir, new_dir_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
op_sel.ignored.push(path.clone());
|
op_sel.ignored.push(path.clone());
|
||||||
|
|
@ -1185,7 +1210,7 @@ mod tests {
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use cosmic::iced::futures::{StreamExt, channel::mpsc};
|
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
use tokio::sync;
|
use tokio::sync;
|
||||||
|
|
@ -1239,7 +1264,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
futures::future::join(handle_messages, handle_copy).await.1
|
future::join(handle_messages, handle_copy).await.1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test(compio::test)]
|
#[test(compio::test)]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl OpReader {
|
||||||
|
|
||||||
impl io::Read for OpReader {
|
impl io::Read for OpReader {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
futures::executor::block_on(async {
|
cosmic::iced::futures::executor::block_on(async {
|
||||||
self.controller
|
self.controller
|
||||||
.check()
|
.check()
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::time::Instant;
|
||||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::operation::OperationError;
|
use crate::operation::{OperationError, sync_to_disk};
|
||||||
|
|
||||||
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||||
|
|
||||||
|
|
@ -57,6 +57,8 @@ impl Context {
|
||||||
) -> Result<bool, OperationError> {
|
) -> Result<bool, OperationError> {
|
||||||
let mut ops = Vec::new();
|
let mut ops = Vec::new();
|
||||||
let mut cleanup_ops = Vec::new();
|
let mut cleanup_ops = Vec::new();
|
||||||
|
let mut written_files = Vec::new();
|
||||||
|
let mut target_dirs = std::collections::HashSet::new();
|
||||||
for (from_parent, to_parent) in from_to_pairs {
|
for (from_parent, to_parent) in from_to_pairs {
|
||||||
self.controller
|
self.controller
|
||||||
.check()
|
.check()
|
||||||
|
|
@ -136,10 +138,13 @@ impl Context {
|
||||||
}),
|
}),
|
||||||
is_cleanup: false,
|
is_cleanup: false,
|
||||||
};
|
};
|
||||||
if matches!(method, Method::Move { .. }) {
|
if matches!(method, Method::Move { .. })
|
||||||
if let Some(cleanup_op) = op.move_cleanup_op() {
|
&& let Some(cleanup_op) = op.move_cleanup_op()
|
||||||
cleanup_ops.push(cleanup_op);
|
{
|
||||||
}
|
cleanup_ops.push(cleanup_op);
|
||||||
|
}
|
||||||
|
if let Some(parent) = op.to.parent() {
|
||||||
|
target_dirs.insert(parent.to_path_buf());
|
||||||
}
|
}
|
||||||
ops.push(op);
|
ops.push(op);
|
||||||
}
|
}
|
||||||
|
|
@ -177,10 +182,19 @@ impl Context {
|
||||||
&self.controller,
|
&self.controller,
|
||||||
)
|
)
|
||||||
})? {
|
})? {
|
||||||
|
if matches!(
|
||||||
|
op.kind,
|
||||||
|
OpKind::Copy
|
||||||
|
| OpKind::Move {
|
||||||
|
cross_device_copy: true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
written_files.push(op.to.clone());
|
||||||
|
}
|
||||||
// The from path is ignored in the operation selection if it is a top level item
|
// The from path is ignored in the operation selection if it is a top level item
|
||||||
if self.op_sel.ignored.contains(&op.from) {
|
if self.op_sel.ignored.contains(&op.from) {
|
||||||
// So add the to path to the selection
|
// So add the to path to the selection
|
||||||
self.op_sel.selected.push(op.to.clone());
|
self.op_sel.selected.push(op.to);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Cancelled
|
// Cancelled
|
||||||
|
|
@ -188,6 +202,9 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush files to disk
|
||||||
|
sync_to_disk(written_files, target_dirs).await;
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,7 +322,7 @@ impl Op {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (from_file, metadata, mut to_file) = futures::try_join!(
|
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
|
||||||
async {
|
async {
|
||||||
compio::fs::OpenOptions::new()
|
compio::fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
|
@ -411,8 +428,6 @@ impl Op {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
to_file.sync_all().await?;
|
|
||||||
}
|
}
|
||||||
OpKind::Move { cross_device_copy } => {
|
OpKind::Move { cross_device_copy } => {
|
||||||
// Remove `to` if overwriting and it is an existing file
|
// Remove `to` if overwriting and it is an existing file
|
||||||
|
|
|
||||||
659
src/tab.rs
659
src/tab.rs
|
|
@ -1,5 +1,8 @@
|
||||||
|
use chrono::{Datelike, Timelike, Utc};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, cosmic_theme, font,
|
Apply, Element, cosmic_theme,
|
||||||
|
desktop::fde::{DesktopEntry, get_languages_from_env},
|
||||||
|
font,
|
||||||
iced::{
|
iced::{
|
||||||
Alignment,
|
Alignment,
|
||||||
Border,
|
Border,
|
||||||
|
|
@ -37,8 +40,6 @@ use cosmic::{
|
||||||
menu::{action::MenuAction, key_bind::KeyBind},
|
menu::{action::MenuAction, key_bind::KeyBind},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{Datelike, Timelike, Utc};
|
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use icu::{
|
use icu::{
|
||||||
datetime::{
|
datetime::{
|
||||||
|
|
@ -615,7 +616,8 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
let locales = get_languages_from_env();
|
||||||
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||||
|
|
@ -623,14 +625,11 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
entry
|
entry.name(&locales).map(|s| s.into_owned())
|
||||||
.section("Desktop Entry")
|
|
||||||
.attr("Name")
|
|
||||||
.map(str::to_string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||||
|
|
@ -638,10 +637,7 @@ fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
entry
|
entry.icon().map(str::to_string)
|
||||||
.section("Desktop Entry")
|
|
||||||
.attr("Icon")
|
|
||||||
.map(str::to_string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an icon handle from a desktop file's Icon field value.
|
/// Creates an icon handle from a desktop file's Icon field value.
|
||||||
|
|
@ -656,17 +652,17 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
let locales = get_languages_from_env();
|
||||||
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||||
return (None, None);
|
return (None, None);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let section = entry.section("Desktop Entry");
|
|
||||||
(
|
(
|
||||||
section.attr("Name").map(str::to_string),
|
entry.name(&locales).map(|s| s.into_owned()),
|
||||||
section.attr("Icon").map(str::to_string),
|
entry.icon().map(str::to_string),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -934,43 +930,43 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
#[cfg(feature = "gvfs")]
|
||||||
{
|
{
|
||||||
if let Ok(path_meta) = fs::metadata(tab_path) {
|
if let Ok(path_meta) = fs::metadata(tab_path)
|
||||||
if fs_kind(&path_meta) == FsKind::Gvfs {
|
&& fs_kind(&path_meta) == FsKind::Gvfs
|
||||||
let file = gio::File::for_path(tab_path);
|
{
|
||||||
|
let file = gio::File::for_path(tab_path);
|
||||||
|
|
||||||
// gio crate expects a comma delimited string
|
// gio crate expects a comma delimited string
|
||||||
let attr_string = [
|
let attr_string = [
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME.as_str(),
|
gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME.as_str(),
|
||||||
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE.as_str(),
|
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE.as_str(),
|
||||||
gio::FILE_ATTRIBUTE_TIME_MODIFIED.as_str(),
|
gio::FILE_ATTRIBUTE_TIME_MODIFIED.as_str(),
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_SIZE.as_str(),
|
gio::FILE_ATTRIBUTE_STANDARD_SIZE.as_str(),
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_TYPE.as_str(),
|
gio::FILE_ATTRIBUTE_STANDARD_TYPE.as_str(),
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_NAME.as_str(),
|
gio::FILE_ATTRIBUTE_STANDARD_NAME.as_str(),
|
||||||
]
|
]
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
match gio::prelude::FileExt::enumerate_children(
|
match gio::prelude::FileExt::enumerate_children(
|
||||||
&file,
|
&file,
|
||||||
attr_string.as_str(),
|
attr_string.as_str(),
|
||||||
gio::FileQueryInfoFlags::NONE,
|
gio::FileQueryInfoFlags::NONE,
|
||||||
gio::Cancellable::NONE,
|
gio::Cancellable::NONE,
|
||||||
) {
|
) {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
remote_scannable = true;
|
remote_scannable = true;
|
||||||
items = res
|
items = res
|
||||||
.filter_map(|file| {
|
.filter_map(|file| {
|
||||||
let file = file.ok()?;
|
let file = file.ok()?;
|
||||||
Some(item_from_gvfs_info(tab_path.join(file.name()), file, sizes))
|
Some(item_from_gvfs_info(tab_path.join(file.name()), file, sizes))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"could not enumerate {} via gio: {}",
|
"could not enumerate {} via gio: {}",
|
||||||
tab_path.display(),
|
tab_path.display(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1410,11 +1406,11 @@ impl EditLocation {
|
||||||
self.selected = Some(selected);
|
self.selected = Some(selected);
|
||||||
|
|
||||||
// Automatically resolve if there is only one completion
|
// Automatically resolve if there is only one completion
|
||||||
if completions.len() == 1 {
|
if completions.len() == 1
|
||||||
if let Some(resolved) = self.resolve() {
|
&& let Some(resolved) = self.resolve()
|
||||||
self.location = resolved;
|
{
|
||||||
self.selected = None;
|
self.location = resolved;
|
||||||
}
|
self.selected = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2033,10 +2029,10 @@ impl ItemThumbnail {
|
||||||
if let Some((item_thumbnail, temp_file)) =
|
if let Some((item_thumbnail, temp_file)) =
|
||||||
Self::generate_thumbnail_external(path, &mime, thumbnail_size, thumbnail_dir)
|
Self::generate_thumbnail_external(path, &mime, thumbnail_size, thumbnail_dir)
|
||||||
{
|
{
|
||||||
if let Ok(cache) = thumbnail_cacher {
|
if let Ok(cache) = thumbnail_cacher
|
||||||
if let Err(err) = cache.update_with_temp_file(temp_file) {
|
&& let Err(err) = cache.update_with_temp_file(temp_file)
|
||||||
log::warn!("failed to update cache for {}: {}", path.display(), err);
|
{
|
||||||
}
|
log::warn!("failed to update cache for {}: {}", path.display(), err);
|
||||||
}
|
}
|
||||||
return item_thumbnail;
|
return item_thumbnail;
|
||||||
}
|
}
|
||||||
|
|
@ -2076,16 +2072,15 @@ impl ItemThumbnail {
|
||||||
// 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
|
||||||
// been able to, create a fail marker so that it isn't tried the
|
// been able to, create a fail marker so that it isn't tried the
|
||||||
// next time.
|
// next time.
|
||||||
if let Ok(cacher) = thumbnail_cacher {
|
if let Ok(cacher) = thumbnail_cacher
|
||||||
if tried_supported_file {
|
&& tried_supported_file
|
||||||
if let Err(err) = cacher.create_fail_marker() {
|
&& let Err(err) = cacher.create_fail_marker()
|
||||||
log::warn!(
|
{
|
||||||
"failed to create thumbnail fail marker for {}: {}",
|
log::warn!(
|
||||||
path.display(),
|
"failed to create thumbnail fail marker for {}: {}",
|
||||||
err
|
path.display(),
|
||||||
);
|
err
|
||||||
}
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::NotImage
|
Self::NotImage
|
||||||
|
|
@ -2273,13 +2268,13 @@ impl Item {
|
||||||
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
||||||
.on_press(Message::ItemRight),
|
.on_press(Message::ItemRight),
|
||||||
);
|
);
|
||||||
if self.can_gallery() {
|
if self.can_gallery()
|
||||||
if let Some(_path) = self.path_opt() {
|
&& let Some(_path) = self.path_opt()
|
||||||
row = row.push(
|
{
|
||||||
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
row = row.push(
|
||||||
.on_press(Message::Gallery(true)),
|
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
||||||
);
|
.on_press(Message::Gallery(true)),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
row.into()
|
row.into()
|
||||||
}
|
}
|
||||||
|
|
@ -2445,18 +2440,20 @@ impl Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) = self.path_opt() {
|
if let Some(path) = self.path_opt()
|
||||||
if let Ok(img) = image::image_dimensions(path) {
|
&& let Ok(img) = image::image_dimensions(path)
|
||||||
let (width, height) = img;
|
{
|
||||||
details = details.push(widget::text::body(format!("{width}x{height}")));
|
let (width, height) = img;
|
||||||
}
|
details = details.push(widget::text::body(format!("{width}x{height}")));
|
||||||
}
|
}
|
||||||
column = column.push(details);
|
column = column.push(details);
|
||||||
|
|
||||||
if let Some(path) = self.path_opt() {
|
if let Some(path) = self.path_opt() {
|
||||||
column = column.push(
|
if self.selected {
|
||||||
widget::button::standard(fl!("open")).on_press(Message::Open(Some(path.clone()))),
|
column = column.push(
|
||||||
);
|
widget::button::standard(fl!("open")).on_press(Message::Open(Some(path.clone()))),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !settings.is_empty() {
|
if !settings.is_empty() {
|
||||||
|
|
@ -2635,12 +2632,11 @@ async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64,
|
||||||
.map_err(|s| OperationError::from_state(s, &controller))?;
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
//TODO: report more errors?
|
//TODO: report more errors?
|
||||||
if let Ok(entry) = entry_res {
|
if let Ok(entry) = entry_res
|
||||||
if let Ok(metadata) = entry.metadata() {
|
&& let Ok(metadata) = entry.metadata()
|
||||||
if metadata.is_file() {
|
&& metadata.is_file()
|
||||||
total += metadata.len();
|
{
|
||||||
}
|
total += metadata.len();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield in case this process takes a while.
|
// Yield in case this process takes a while.
|
||||||
|
|
@ -2766,10 +2762,10 @@ impl Tab {
|
||||||
let selected = self.selected_locations();
|
let selected = self.selected_locations();
|
||||||
for item in &mut items {
|
for item in &mut items {
|
||||||
item.selected = false;
|
item.selected = false;
|
||||||
if let Some(location) = &item.location_opt {
|
if let Some(location) = &item.location_opt
|
||||||
if selected.contains(location) {
|
&& selected.contains(location)
|
||||||
item.selected = true;
|
{
|
||||||
}
|
item.selected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.items_opt = Some(items);
|
self.items_opt = Some(items);
|
||||||
|
|
@ -2788,10 +2784,9 @@ impl Tab {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
item.cut = false;
|
item.cut = false;
|
||||||
if let Some(location_path) = item.location_opt.as_ref().and_then(Location::path_opt)
|
if let Some(location_path) = item.location_opt.as_ref().and_then(Location::path_opt)
|
||||||
|
&& locations.contains(location_path)
|
||||||
{
|
{
|
||||||
if locations.contains(location_path) {
|
item.cut = true;
|
||||||
item.cut = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2881,11 +2876,11 @@ impl Tab {
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for (i, item) in items.iter_mut().enumerate() {
|
for (i, item) in items.iter_mut().enumerate() {
|
||||||
item.selected = false;
|
item.selected = false;
|
||||||
if let Some(path) = item.path_opt() {
|
if let Some(path) = item.path_opt()
|
||||||
if paths.contains(path) {
|
&& paths.contains(path)
|
||||||
item.selected = true;
|
{
|
||||||
self.select_focus = Some(i);
|
item.selected = true;
|
||||||
}
|
self.select_focus = Some(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3085,10 +3080,10 @@ impl Tab {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((w, h)) = original_dims {
|
if let Some((w, h)) = original_dims
|
||||||
if !should_use_tiling(*w, *h) {
|
&& !should_use_tiling(*w, *h)
|
||||||
return Vec::new();
|
{
|
||||||
}
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(path) = item.path_opt() else {
|
let Some(path) = item.path_opt() else {
|
||||||
|
|
@ -3389,15 +3384,13 @@ impl Tab {
|
||||||
self.date_time_formatter = date_time_formatter(self.config.military_time);
|
self.date_time_formatter = date_time_formatter(self.config.military_time);
|
||||||
self.time_formatter = time_formatter(self.config.military_time);
|
self.time_formatter = time_formatter(self.config.military_time);
|
||||||
}
|
}
|
||||||
if show_hidden_changed {
|
if show_hidden_changed && let Location::Search(path, term, ..) = &self.location {
|
||||||
if let Location::Search(path, term, ..) = &self.location {
|
cd = Some(Location::Search(
|
||||||
cd = Some(Location::Search(
|
path.clone(),
|
||||||
path.clone(),
|
term.clone(),
|
||||||
term.clone(),
|
self.config.show_hidden,
|
||||||
self.config.show_hidden,
|
Instant::now(),
|
||||||
Instant::now(),
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Unhighlight all items when config changes
|
// Unhighlight all items when config changes
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
|
|
@ -3418,11 +3411,12 @@ impl Tab {
|
||||||
self.location_context_menu_index = None;
|
self.location_context_menu_index = None;
|
||||||
|
|
||||||
//TODO: hack for clearing selecting when right clicking empty space
|
//TODO: hack for clearing selecting when right clicking empty space
|
||||||
if self.context_menu.is_some() && self.last_right_click.take().is_none() {
|
if self.context_menu.is_some()
|
||||||
if let Some(ref mut items) = self.items_opt {
|
&& self.last_right_click.take().is_none()
|
||||||
for item in items.iter_mut() {
|
&& let Some(ref mut items) = self.items_opt
|
||||||
item.selected = false;
|
{
|
||||||
}
|
for item in items.iter_mut() {
|
||||||
|
item.selected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3510,11 +3504,11 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::EditLocationComplete(selected) => {
|
Message::EditLocationComplete(selected) => {
|
||||||
if let Some(mut edit_location) = self.edit_location.take() {
|
if let Some(mut edit_location) = self.edit_location.take()
|
||||||
if !matches!(edit_location.location, Location::Network(..)) {
|
&& !matches!(edit_location.location, Location::Network(..))
|
||||||
edit_location.selected = Some(selected);
|
{
|
||||||
cd = edit_location.resolve();
|
edit_location.selected = Some(selected);
|
||||||
}
|
cd = edit_location.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::EditLocationEnable => {
|
Message::EditLocationEnable => {
|
||||||
|
|
@ -3530,11 +3524,11 @@ impl Tab {
|
||||||
&& edit_location
|
&& edit_location
|
||||||
.completions
|
.completions
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |completions| !completions.is_empty())
|
.is_some_and(|completions| !completions.is_empty())
|
||||||
&& edit_location
|
&& edit_location
|
||||||
.location
|
.location
|
||||||
.path_opt()
|
.path_opt()
|
||||||
.map_or(false, |path| !path.exists())
|
.is_some_and(|path| !path.exists())
|
||||||
{
|
{
|
||||||
edit_location.selected = Some(0);
|
edit_location.selected = Some(0);
|
||||||
}
|
}
|
||||||
|
|
@ -3632,19 +3626,19 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::GoNext => {
|
Message::GoNext => {
|
||||||
if let Some(history_i) = self.history_i.checked_add(1) {
|
if let Some(history_i) = self.history_i.checked_add(1)
|
||||||
if let Some(location) = self.history.get(history_i) {
|
&& let Some(location) = self.history.get(history_i)
|
||||||
cd = Some(location.clone());
|
{
|
||||||
history_i_opt = Some(history_i);
|
cd = Some(location.clone());
|
||||||
}
|
history_i_opt = Some(history_i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::GoPrevious => {
|
Message::GoPrevious => {
|
||||||
if let Some(history_i) = self.history_i.checked_sub(1) {
|
if let Some(history_i) = self.history_i.checked_sub(1)
|
||||||
if let Some(location) = self.history.get(history_i) {
|
&& let Some(location) = self.history.get(history_i)
|
||||||
cd = Some(location.clone());
|
{
|
||||||
history_i_opt = Some(history_i);
|
cd = Some(location.clone());
|
||||||
}
|
history_i_opt = Some(history_i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ItemDown => {
|
Message::ItemDown => {
|
||||||
|
|
@ -3823,10 +3817,10 @@ impl Tab {
|
||||||
Message::LocationUp => {
|
Message::LocationUp => {
|
||||||
// Sets location to the path's parent
|
// Sets location to the path's parent
|
||||||
// Does nothing if path is root or location is Trash
|
// Does nothing if path is root or location is Trash
|
||||||
if let Location::Path(ref path) = self.location {
|
if let Location::Path(ref path) = self.location
|
||||||
if let Some(parent) = path.parent() {
|
&& let Some(parent) = path.parent()
|
||||||
cd = Some(Location::Path(parent.to_owned()));
|
{
|
||||||
}
|
cd = Some(Location::Path(parent.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Open(path_opt) => {
|
Message::Open(path_opt) => {
|
||||||
|
|
@ -3868,27 +3862,25 @@ impl Tab {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::App => {
|
Mode::App => {
|
||||||
if is_only_one_selected {
|
if is_only_one_selected {
|
||||||
return ResolveResult::Cd(location.clone());
|
ResolveResult::Cd(location.clone())
|
||||||
} else {
|
} else {
|
||||||
return ResolveResult::OpenInTab(path_opt.cloned());
|
ResolveResult::OpenInTab(path_opt.cloned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::Desktop => {
|
Mode::Desktop => match location {
|
||||||
return match location {
|
Location::Trash => ResolveResult::OpenTrash,
|
||||||
Location::Trash => ResolveResult::OpenTrash,
|
_ => ResolveResult::Open(path_opt.cloned()),
|
||||||
_ => ResolveResult::Open(path_opt.cloned()),
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
Mode::Dialog(_) => {
|
Mode::Dialog(_) => {
|
||||||
if is_only_one_selected {
|
if is_only_one_selected {
|
||||||
return ResolveResult::Cd(location.clone());
|
ResolveResult::Cd(location.clone())
|
||||||
} else {
|
} else {
|
||||||
return ResolveResult::Skip;
|
ResolveResult::Skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return ResolveResult::Open(path_opt.cloned());
|
ResolveResult::Open(path_opt.cloned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut open_files = Vec::new();
|
let mut open_files = Vec::new();
|
||||||
|
|
@ -3933,14 +3925,13 @@ impl Tab {
|
||||||
if mod_ctrl || mod_shift {
|
if mod_ctrl || mod_shift {
|
||||||
self.update(Message::Click(click_i_opt), modifiers);
|
self.update(Message::Click(click_i_opt), modifiers);
|
||||||
}
|
}
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt
|
||||||
if !click_i_opt
|
&& !click_i_opt
|
||||||
.is_some_and(|click_i| items.get(click_i).is_some_and(|x| x.selected))
|
.is_some_and(|click_i| items.get(click_i).is_some_and(|x| x.selected))
|
||||||
{
|
{
|
||||||
// If item not selected, clear selection on other items
|
// If item not selected, clear selection on other items
|
||||||
for (i, item) in items.iter_mut().enumerate() {
|
for (i, item) in items.iter_mut().enumerate() {
|
||||||
item.selected = Some(i) == click_i_opt;
|
item.selected = Some(i) == click_i_opt;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: hack for clearing selecting when right clicking empty space
|
//TODO: hack for clearing selecting when right clicking empty space
|
||||||
|
|
@ -3988,12 +3979,12 @@ impl Tab {
|
||||||
}
|
}
|
||||||
Message::Resize(viewport) => {
|
Message::Resize(viewport) => {
|
||||||
// Scroll to ensure focused item still in view
|
// Scroll to ensure focused item still in view
|
||||||
if self.viewport_opt.map(|v| v.size()) != Some(viewport.size()) {
|
if self.viewport_opt.map(|v| v.size()) != Some(viewport.size())
|
||||||
if let Some(offset) = self.select_focus_scroll() {
|
&& let Some(offset) = self.select_focus_scroll()
|
||||||
commands.push(Command::Iced(
|
{
|
||||||
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
|
commands.push(Command::Iced(
|
||||||
));
|
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewport_opt = Some(viewport);
|
self.viewport_opt = Some(viewport);
|
||||||
|
|
@ -4097,20 +4088,17 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::SelectLast => {
|
Message::SelectLast => {
|
||||||
if let Some(ref items) = self.items_opt {
|
if let Some(ref items) = self.items_opt
|
||||||
if let Some(last_pos) = items.iter().filter_map(|item| item.pos_opt.get()).max()
|
&& let Some(last_pos) = items.iter().filter_map(|item| item.pos_opt.get()).max()
|
||||||
{
|
&& self.select_position(last_pos.0, last_pos.1, mod_shift)
|
||||||
if self.select_position(last_pos.0, last_pos.1, mod_shift) {
|
{
|
||||||
if let Some(offset) = self.select_focus_scroll() {
|
if let Some(offset) = self.select_focus_scroll() {
|
||||||
commands.push(Command::Iced(
|
commands.push(Command::Iced(
|
||||||
scrollable::scroll_to(self.scrollable_id.clone(), offset)
|
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
|
||||||
.into(),
|
));
|
||||||
));
|
}
|
||||||
}
|
if let Some(id) = self.select_focus_id() {
|
||||||
if let Some(id) = self.select_focus_id() {
|
commands.push(Command::Iced(widget::button::focus(id).into()));
|
||||||
commands.push(Command::Iced(widget::button::focus(id).into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4134,13 +4122,13 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TabComplete(path, completions) => {
|
Message::TabComplete(path, completions) => {
|
||||||
if let Some(edit_location) = &mut self.edit_location {
|
if let Some(edit_location) = &mut self.edit_location
|
||||||
if edit_location.location.path_opt() == Some(&path) {
|
&& edit_location.location.path_opt() == Some(&path)
|
||||||
edit_location.completions = Some(completions);
|
{
|
||||||
commands.push(Command::Iced(
|
edit_location.completions = Some(completions);
|
||||||
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
commands.push(Command::Iced(
|
||||||
));
|
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Thumbnail(path, thumbnail) => {
|
Message::Thumbnail(path, thumbnail) => {
|
||||||
|
|
@ -4274,10 +4262,10 @@ impl Tab {
|
||||||
}
|
}
|
||||||
Message::DirectorySize(path, dir_size) => {
|
Message::DirectorySize(path, dir_size) => {
|
||||||
let location = Location::Path(path);
|
let location = Location::Path(path);
|
||||||
if let Some(ref mut item) = self.parent_item_opt {
|
if let Some(ref mut item) = self.parent_item_opt
|
||||||
if item.location_opt.as_ref() == Some(&location) {
|
&& item.location_opt.as_ref() == Some(&location)
|
||||||
item.dir_size.clone_from(&dir_size);
|
{
|
||||||
}
|
item.dir_size.clone_from(&dir_size);
|
||||||
}
|
}
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
|
|
@ -4315,13 +4303,12 @@ impl Tab {
|
||||||
} else {
|
} else {
|
||||||
// Select parent if location is not directory
|
// Select parent if location is not directory
|
||||||
let mut selected_paths = None;
|
let mut selected_paths = None;
|
||||||
if let Some(path) = location.path_opt() {
|
if let Some(path) = location.path_opt()
|
||||||
if !path.is_dir() {
|
&& !path.is_dir()
|
||||||
if let Some(parent) = path.parent() {
|
&& let Some(parent) = path.parent()
|
||||||
selected_paths = Some(vec![path.clone()]);
|
{
|
||||||
location = location.with_path(parent.to_path_buf());
|
selected_paths = Some(vec![path.clone()]);
|
||||||
}
|
location = location.with_path(parent.to_path_buf());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if location != self.location || selected_paths.is_some() {
|
if location != self.location || selected_paths.is_some() {
|
||||||
if location.path_opt().is_none_or(|path| path.is_dir()) {
|
if location.path_opt().is_none_or(|path| path.is_dir()) {
|
||||||
|
|
@ -4529,99 +4516,93 @@ impl Tab {
|
||||||
//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;
|
||||||
let mut element_opt: Option<Element<Message>> = None;
|
let mut element_opt: Option<Element<Message>> = None;
|
||||||
if let Some(index) = self.select_focus {
|
if let Some(index) = self.select_focus
|
||||||
if let Some(items) = &self.items_opt {
|
&& let Some(items) = &self.items_opt
|
||||||
if let Some(item) = items.get(index) {
|
&& let Some(item) = items.get(index)
|
||||||
name_opt = Some(widget::text::heading(&item.display_name));
|
{
|
||||||
match item
|
name_opt = Some(widget::text::heading(&item.display_name));
|
||||||
.thumbnail_opt
|
match item
|
||||||
.as_ref()
|
.thumbnail_opt
|
||||||
.unwrap_or(&ItemThumbnail::NotImage)
|
.as_ref()
|
||||||
{
|
.unwrap_or(&ItemThumbnail::NotImage)
|
||||||
ItemThumbnail::NotImage => {}
|
{
|
||||||
ItemThumbnail::Image(handle, original_dims) => {
|
ItemThumbnail::NotImage => {}
|
||||||
// Determine which image to show based on async decode state
|
ItemThumbnail::Image(handle, original_dims) => {
|
||||||
let mut is_loading = false;
|
// Determine which image to show based on async decode state
|
||||||
let mut error_msg_opt = None;
|
let mut is_loading = false;
|
||||||
let image_handle = if let Some(path) = item.path_opt() {
|
let mut error_msg_opt = None;
|
||||||
if let Some(error_msg) = self.large_image_manager.get_error(path) {
|
let image_handle = if let Some(path) = item.path_opt() {
|
||||||
error_msg_opt = Some(error_msg.clone());
|
if let Some(error_msg) = self.large_image_manager.get_error(path) {
|
||||||
handle.clone()
|
error_msg_opt = Some(error_msg.clone());
|
||||||
} else if self.large_image_manager.is_decoding(path) {
|
handle.clone()
|
||||||
// Currently decoding (initial or re-decode) --> show cached/thumbnail with loading indicator
|
} else if self.large_image_manager.is_decoding(path) {
|
||||||
is_loading = true;
|
// Currently decoding (initial or re-decode) --> show cached/thumbnail with loading indicator
|
||||||
// Use decoded handle if available (re-decode), otherwise thumbnail (initial decode)
|
is_loading = true;
|
||||||
self.large_image_manager
|
// Use decoded handle if available (re-decode), otherwise thumbnail (initial decode)
|
||||||
.get_decoded(path)
|
self.large_image_manager
|
||||||
.cloned()
|
.get_decoded(path)
|
||||||
.unwrap_or_else(|| handle.clone())
|
.cloned()
|
||||||
} else if let Some(decoded_handle) =
|
.unwrap_or_else(|| handle.clone())
|
||||||
self.large_image_manager.get_decoded(path)
|
} else if let Some(decoded_handle) =
|
||||||
{
|
self.large_image_manager.get_decoded(path)
|
||||||
// Decoded and not currently decoding --> use it
|
{
|
||||||
decoded_handle.clone()
|
// Decoded and not currently decoding --> use it
|
||||||
} else if let Some((w, h)) = original_dims {
|
decoded_handle.clone()
|
||||||
// Check if image needs tiling
|
} else if let Some((w, h)) = original_dims {
|
||||||
if should_use_tiling(*w, *h) {
|
// Check if image needs tiling
|
||||||
// Large image --> show thumbnail only
|
if should_use_tiling(*w, *h) {
|
||||||
handle.clone()
|
// Large image --> show thumbnail only
|
||||||
} else {
|
|
||||||
// Normal-sized image --> load full resolution directly
|
|
||||||
widget::image::Handle::from_path(path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No dimensions available --> show thumbnail
|
|
||||||
handle.clone()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handle.clone()
|
handle.clone()
|
||||||
};
|
} else {
|
||||||
|
// Normal-sized image --> load full resolution directly
|
||||||
|
widget::image::Handle::from_path(path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No dimensions available --> show thumbnail
|
||||||
|
handle.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handle.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let content: cosmic::Element<'_, Message> =
|
let content: cosmic::Element<'_, Message> =
|
||||||
if let Some(error_msg) = error_msg_opt {
|
if let Some(error_msg) = error_msg_opt {
|
||||||
widget::column()
|
widget::column()
|
||||||
.push(widget::image(image_handle))
|
.push(widget::image(image_handle))
|
||||||
.push(widget::text(format!("⚠ {}", error_msg)).size(13))
|
.push(widget::text(format!("⚠ {}", error_msg)).size(13))
|
||||||
.padding(space_xs)
|
.padding(space_xs)
|
||||||
.align_x(cosmic::iced::Alignment::Center)
|
.align_x(cosmic::iced::Alignment::Center)
|
||||||
.into()
|
.into()
|
||||||
} else if is_loading {
|
} else if is_loading {
|
||||||
widget::column()
|
widget::column()
|
||||||
.push(widget::image(image_handle))
|
.push(widget::image(image_handle))
|
||||||
.push(widget::text("Loading higher resolution...").size(14))
|
.push(widget::text("Loading higher resolution...").size(14))
|
||||||
.padding(space_xs)
|
.padding(space_xs)
|
||||||
.align_x(cosmic::iced::Alignment::Center)
|
.align_x(cosmic::iced::Alignment::Center)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
//TODO: use widget::image::viewer, when its zoom can be reset
|
//TODO: use widget::image::viewer, when its zoom can be reset
|
||||||
widget::image(image_handle).into()
|
widget::image(image_handle).into()
|
||||||
};
|
};
|
||||||
|
|
||||||
element_opt =
|
element_opt = Some(widget::container(content).center(Length::Fill).into());
|
||||||
Some(widget::container(content).center(Length::Fill).into());
|
}
|
||||||
}
|
ItemThumbnail::Svg(handle) => {
|
||||||
ItemThumbnail::Svg(handle) => {
|
element_opt = Some(
|
||||||
element_opt = Some(
|
widget::svg(handle.clone())
|
||||||
widget::svg(handle.clone())
|
.width(Length::Fill)
|
||||||
.width(Length::Fill)
|
.height(Length::Fill)
|
||||||
.height(Length::Fill)
|
.into(),
|
||||||
.into(),
|
);
|
||||||
);
|
}
|
||||||
}
|
ItemThumbnail::Text(text) => {
|
||||||
ItemThumbnail::Text(text) => {
|
element_opt = Some(
|
||||||
element_opt = Some(
|
widget::container(widget::text_editor(text).padding(space_xxs).class(
|
||||||
widget::container(
|
cosmic::theme::iced::TextEditor::Custom(Box::new(text_editor_class)),
|
||||||
widget::text_editor(text).padding(space_xxs).class(
|
))
|
||||||
cosmic::theme::iced::TextEditor::Custom(Box::new(
|
.center(Length::Fill)
|
||||||
text_editor_class,
|
.into(),
|
||||||
)),
|
);
|
||||||
),
|
|
||||||
)
|
|
||||||
.center(Length::Fill)
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4860,32 +4841,32 @@ impl Tab {
|
||||||
);
|
);
|
||||||
let mut popover =
|
let mut popover =
|
||||||
widget::popover(text_input).position(widget::popover::Position::Bottom);
|
widget::popover(text_input).position(widget::popover::Position::Bottom);
|
||||||
if let Some(completions) = &edit_location.completions {
|
if let Some(completions) = &edit_location.completions
|
||||||
if !completions.is_empty() {
|
&& !completions.is_empty()
|
||||||
let mut column =
|
{
|
||||||
widget::column::with_capacity(completions.len()).padding(space_xxs);
|
let mut column =
|
||||||
for (i, (name, _path)) in completions.iter().enumerate() {
|
widget::column::with_capacity(completions.len()).padding(space_xxs);
|
||||||
let selected = edit_location.selected == Some(i);
|
for (i, (name, _path)) in completions.iter().enumerate() {
|
||||||
column = column.push(
|
let selected = edit_location.selected == Some(i);
|
||||||
widget::button::custom(widget::text::body(name))
|
column = column.push(
|
||||||
//TODO: match to design
|
widget::button::custom(widget::text::body(name))
|
||||||
.class(if selected {
|
//TODO: match to design
|
||||||
theme::Button::Standard
|
.class(if selected {
|
||||||
} else {
|
theme::Button::Standard
|
||||||
theme::Button::HeaderBar
|
} else {
|
||||||
})
|
theme::Button::HeaderBar
|
||||||
.on_press(Message::EditLocationComplete(i))
|
})
|
||||||
.padding(space_xxs)
|
.on_press(Message::EditLocationComplete(i))
|
||||||
.width(Length::Fill),
|
.padding(space_xxs)
|
||||||
);
|
.width(Length::Fill),
|
||||||
}
|
|
||||||
popover = popover.popup(
|
|
||||||
widget::container(column)
|
|
||||||
.class(theme::Container::Dropdown)
|
|
||||||
//TODO: This is a hack to get the popover to be the right width
|
|
||||||
.max_width(size.width - 140.0),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
popover = popover.popup(
|
||||||
|
widget::container(column)
|
||||||
|
.class(theme::Container::Dropdown)
|
||||||
|
//TODO: This is a hack to get the popover to be the right width
|
||||||
|
.max_width(size.width - 140.0),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
row = row.push(popover);
|
row = row.push(popover);
|
||||||
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
|
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
|
||||||
|
|
@ -5911,13 +5892,13 @@ impl Tab {
|
||||||
.wayland_on_right_press_window_position();
|
.wayland_on_right_press_window_position();
|
||||||
|
|
||||||
let mut popover = widget::popover(mouse_area);
|
let mut popover = widget::popover(mouse_area);
|
||||||
if let Some(point) = self.context_menu {
|
if let Some(point) = self.context_menu
|
||||||
if !cfg!(feature = "wayland") || !crate::is_wayland() {
|
&& (!cfg!(feature = "wayland") || !crate::is_wayland())
|
||||||
let context_menu = menu::context_menu(self, key_binds, &modifiers);
|
{
|
||||||
popover = popover
|
let context_menu = menu::context_menu(self, key_binds, modifiers);
|
||||||
.popup(context_menu)
|
popover = popover
|
||||||
.position(widget::popover::Position::Point(point));
|
.popup(context_menu)
|
||||||
}
|
.position(widget::popover::Position::Point(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tab_column = widget::column::with_capacity(3);
|
let mut tab_column = widget::column::with_capacity(3);
|
||||||
|
|
@ -5937,21 +5918,21 @@ impl Tab {
|
||||||
}
|
}
|
||||||
match &self.location {
|
match &self.location {
|
||||||
Location::Trash => {
|
Location::Trash => {
|
||||||
if let Some(items) = self.items_opt() {
|
if let Some(items) = self.items_opt()
|
||||||
if !items.is_empty() {
|
&& !items.is_empty()
|
||||||
tab_column = tab_column.push(
|
{
|
||||||
widget::layer_container(widget::row::with_children([
|
tab_column = tab_column.push(
|
||||||
widget::horizontal_space().into(),
|
widget::layer_container(widget::row::with_children([
|
||||||
widget::button::standard(fl!("empty-trash"))
|
widget::horizontal_space().into(),
|
||||||
.on_press(Message::EmptyTrash)
|
widget::button::standard(fl!("empty-trash"))
|
||||||
.into(),
|
.on_press(Message::EmptyTrash)
|
||||||
]))
|
.into(),
|
||||||
.padding([space_xxs, space_xs])
|
]))
|
||||||
.layer(cosmic_theme::Layer::Primary)
|
.padding([space_xxs, space_xs])
|
||||||
.apply(widget::container)
|
.layer(cosmic_theme::Layer::Primary)
|
||||||
.padding([0, 0, 7, 0]),
|
.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:///" => {
|
||||||
|
|
@ -6248,10 +6229,10 @@ impl Tab {
|
||||||
let mut selected_items: Vec<&Item> =
|
let mut selected_items: Vec<&Item> =
|
||||||
items.iter().filter(|item| item.selected).collect();
|
items.iter().filter(|item| item.selected).collect();
|
||||||
|
|
||||||
if selected_items.is_empty() {
|
if selected_items.is_empty()
|
||||||
if let Some(p) = self.parent_item_opt.as_ref() {
|
&& let Some(p) = self.parent_item_opt.as_ref()
|
||||||
selected_items.push(p)
|
{
|
||||||
}
|
selected_items.push(p)
|
||||||
}
|
}
|
||||||
for item in selected_items {
|
for item in selected_items {
|
||||||
// Item must have a path
|
// Item must have a path
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,10 @@ impl ThumbnailCacher {
|
||||||
if let (Some(cache_base_dir), Ok(metadata)) = (
|
if let (Some(cache_base_dir), Ok(metadata)) = (
|
||||||
THUMBNAIL_CACHE_BASE_DIR.as_ref(),
|
THUMBNAIL_CACHE_BASE_DIR.as_ref(),
|
||||||
std::fs::metadata(&self.file_path),
|
std::fs::metadata(&self.file_path),
|
||||||
) {
|
) && metadata.is_file()
|
||||||
if metadata.is_file() && self.file_path.starts_with(cache_base_dir) {
|
&& self.file_path.starts_with(cache_base_dir)
|
||||||
return CachedThumbnail::Valid((self.file_path.clone(), None));
|
{
|
||||||
}
|
return CachedThumbnail::Valid((self.file_path.clone(), None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use cached thumbnail if it is valid.
|
// Use cached thumbnail if it is valid.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// 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::desktop::fde::GenericEntry;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -115,7 +116,7 @@ impl ThumbnailerCache {
|
||||||
|
|
||||||
//TODO: handle directory specific behavior
|
//TODO: handle directory specific behavior
|
||||||
for path in thumbnailer_paths {
|
for path in thumbnailer_paths {
|
||||||
let entry = match freedesktop_entry_parser::parse_entry(&path) {
|
let entry = match GenericEntry::from_path(&path) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||||
|
|
@ -124,12 +125,18 @@ impl ThumbnailerCache {
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: use TryExec?
|
//TODO: use TryExec?
|
||||||
let section = entry.section("Thumbnailer Entry");
|
let Some(section) = entry.group("Thumbnailer Entry") else {
|
||||||
let Some(exec) = section.attr("Exec") else {
|
log::warn!(
|
||||||
|
"missing Thumbnailer Entry section for thumbnailer {}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(exec) = section.entry("Exec") else {
|
||||||
log::warn!("missing Exec attribute for thumbnailer {}", path.display());
|
log::warn!("missing Exec attribute for thumbnailer {}", path.display());
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(mime_types) = section.attr("MimeType") else {
|
let Some(mime_types) = section.entry("MimeType") else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"missing MimeType attribute for thumbnailer {}",
|
"missing MimeType attribute for thumbnailer {}",
|
||||||
path.display()
|
path.display()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue