Merge branch 'pop-os:master' into master

This commit is contained in:
David Carvalho 2024-09-20 17:58:04 -03:00 committed by GitHub
commit 38435562ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 671 additions and 339 deletions

139
Cargo.lock generated
View file

@ -254,9 +254,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.88"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "apply"
@ -290,9 +290,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arrayref"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
@ -519,9 +519,9 @@ dependencies = [
[[package]]
name = "async-process"
version = "2.2.4"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374"
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
dependencies = [
"async-channel",
"async-io 2.3.4",
@ -534,7 +534,6 @@ dependencies = [
"futures-lite 2.3.0",
"rustix 0.38.37",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
@ -835,9 +834,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "bzip2"
@ -914,9 +913,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.18"
version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
dependencies = [
"jobserver",
"libc",
@ -1213,7 +1212,7 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
@ -1232,7 +1231,7 @@ dependencies = [
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"quote",
"syn 1.0.109",
@ -1242,6 +1241,7 @@ dependencies = [
name = "cosmic-files"
version = "0.1.0"
dependencies = [
"bzip2",
"chrono",
"dirs 5.0.1",
"env_logger",
@ -1261,6 +1261,7 @@ dependencies = [
"image",
"libc",
"libcosmic",
"liblzma",
"log",
"mime_guess",
"notify-debouncer-full",
@ -1339,7 +1340,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"almost",
"cosmic-config",
@ -2073,7 +2074,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
dependencies = [
"fontconfig-parser",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"slotmap",
"tinyvec",
"ttf-parser 0.20.0",
@ -2758,9 +2759,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.60"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -2782,7 +2783,7 @@ dependencies = [
[[package]]
name = "iced"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"dnd",
"iced_accessibility",
@ -2801,7 +2802,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"accesskit",
"accesskit_unix",
@ -2811,7 +2812,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"bitflags 2.6.0",
"dnd",
@ -2833,7 +2834,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"futures",
"iced_core",
@ -2846,7 +2847,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"bitflags 2.6.0",
"bytemuck",
@ -2870,7 +2871,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -2882,7 +2883,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"dnd",
"iced_accessibility",
@ -2896,7 +2897,7 @@ dependencies = [
[[package]]
name = "iced_sctk"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"enum-repr",
"float-cmp",
@ -2923,7 +2924,7 @@ dependencies = [
[[package]]
name = "iced_style"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"iced_core",
"once_cell",
@ -2933,7 +2934,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"bytemuck",
"cosmic-text",
@ -2950,7 +2951,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"as-raw-xcb-connection",
"bitflags 2.6.0",
@ -2979,7 +2980,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"dnd",
"iced_accessibility",
@ -2997,7 +2998,7 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.12.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"dnd",
"iced_accessibility",
@ -3513,7 +3514,7 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733"
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
dependencies = [
"apply",
"ashpd 0.9.1",
@ -3576,6 +3577,26 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "liblzma"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7c45fc6fcf5b527d3cf89c1dee8c327943984b0dc8bfcf6e100473b00969e63"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63117d31458acdb7b406f6c60090aa8e1e7cd6e283f8ee02ce585ed68c53fe39"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.8"
@ -3782,9 +3803,9 @@ dependencies = [
[[package]]
name = "memmap2"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
@ -4633,7 +4654,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
dependencies = [
"toml_edit 0.22.20",
"toml_edit 0.22.21",
]
[[package]]
@ -4701,9 +4722,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.36.1"
version = "0.36.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
dependencies = [
"memchr",
"serde",
@ -4812,7 +4833,7 @@ dependencies = [
"dirs 5.0.1",
"infer",
"mime_guess",
"quick-xml 0.36.1",
"quick-xml 0.36.2",
"serde",
"thiserror",
"url",
@ -5145,7 +5166,7 @@ checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7"
dependencies = [
"ab_glyph",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"smithay-client-toolkit 0.18.1",
"tiny-skia",
]
@ -5331,7 +5352,7 @@ dependencies = [
"cursor-icon",
"libc",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"rustix 0.38.37",
"thiserror",
"wayland-backend",
@ -5357,7 +5378,7 @@ dependencies = [
"cursor-icon",
"libc",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"pkg-config",
"rustix 0.38.37",
"thiserror",
@ -5427,7 +5448,7 @@ dependencies = [
"foreign-types",
"js-sys",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"objc",
"raw-window-handle",
"redox_syscall 0.4.1",
@ -5875,7 +5896,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.20",
"toml_edit 0.22.21",
]
[[package]]
@ -5900,9 +5921,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.20"
version = "0.22.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [
"indexmap",
"serde",
@ -5973,8 +5994,8 @@ dependencies = [
[[package]]
name = "trash"
version = "5.0.0"
source = "git+https://github.com/jackpot51/trash-rs.git?branch=delete-info#e9fd256298bf9873a794dfe60a2261d1ed41674c"
version = "5.1.1"
source = "git+https://github.com/jackpot51/trash-rs.git?branch=cosmic#483f83908beef9166f30dfe7b57568ab01c4e140"
dependencies = [
"chrono",
"libc",
@ -6103,9 +6124,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
@ -6118,15 +6139,15 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
[[package]]
name = "unicode-script"
version = "0.5.6"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-vo"
@ -6136,15 +6157,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-xid"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unix_permissions_ext"
@ -6519,7 +6540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
dependencies = [
"proc-macro2",
"quick-xml 0.36.1",
"quick-xml 0.36.2",
"quote",
]
@ -7076,7 +7097,7 @@ dependencies = [
"js-sys",
"libc",
"log",
"memmap2 0.9.4",
"memmap2 0.9.5",
"ndk",
"ndk-sys",
"objc2 0.4.1",
@ -7346,7 +7367,7 @@ dependencies = [
"async-fs 2.1.2",
"async-io 2.3.4",
"async-lock 3.4.0",
"async-process 2.2.4",
"async-process 2.3.0",
"async-recursion",
"async-task",
"async-trait",

View file

@ -14,7 +14,6 @@ chrono = { version = "0.4", features = ["unstable-locales"] }
dirs = "5.0.1"
env_logger = "0.11"
freedesktop_entry_parser = "1.3"
flate2 = "1.0"
fs_extra = { git = "https://github.com/pop-os/fs_extra.git" }
gio = { version = "0.20", optional = true }
glib = { version = "0.20", optional = true }
@ -35,14 +34,18 @@ rayon = "1"
regex = "1"
serde = { version = "1", features = ["serde_derive"] }
shlex = { version = "1.3" }
tar = "0.4.41"
tokio = { version = "1", features = ["sync"] }
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" }
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
url = "2.5"
walkdir = "2.5.0"
wayland-client = { version = "0.31.5", optional = true }
xdg = { version = "2.5.2", optional = true }
xdg-mime = "0.3"
# Compression
bzip2 = { version = "0.4", optional = true } #TODO: replace with pure Rust crate
flate2 = "1.0"
liblzma = { version = "0.3", optional = true } #TODO: replace with pure Rust crate
tar = "0.4.41"
# Internationalization
i18n-embed = { version = "0.14", features = [
"fluent-system",
@ -66,7 +69,7 @@ version = "0.2.1"
features = ["serde"]
[features]
default = ["desktop", "gvfs", "notify", "winit", "wgpu"]
default = ["bzip2", "desktop", "gvfs", "liblzma", "notify", "winit", "wgpu"]
desktop = ["libcosmic/desktop", "dep:xdg"]
gvfs = ["dep:gio", "dep:glib"]
notify = ["dep:notify-rust"]

View file

@ -1,9 +1,14 @@
cosmic-files = Файлы COSMIC
empty-folder = Пустая папка
empty-folder-hidden = Пустая папка (з захаванымі элементамі)
no-results = Нічога не знойдзена
filesystem = Файлавая сістэма
home = Хатняя папка
networks = Сеткі
notification-in-progress = Ідзе аперацыя з файламі.
trash = Сметніца
recents = Нядаўняе
undo = Адрабіць
# List view
name = Назва
@ -12,6 +17,9 @@ size = Памер
# Dialogs
## Compress Dialog
create-archive = Стварыць архіў
## Empty Trash Dialog
empty-trash = Ачысціць сметніцу
empty-trash-warning = Вы сапраўды хочаце назаўсёды выдаліць усе элементы з сметніцы?
@ -29,11 +37,13 @@ name-no-slashes = Назва не можа ўтрымліваць касыя р
# Open/Save Dialog
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 = Захаваць
@ -47,22 +57,106 @@ rename-folder = Перайменаваць папку
replace = Замяніць
replace-title = {$filename} ужо існуе ў гэтым месцы.
replace-warning = Вы сапраўды хочыце замяніць яго на той, які вы захоўваеце? Пры замене яго змесціва будзе перапісана.
replace-warning-operation = Вы хочаце замяніць яго? Пры замене яго змесціва будзе перазапісана.
original-file = Зыходны файл
replace-with = Замяніць на
apply-to-all = Прымяніць на ўсіх
keep-both = Захаваць абодва
skip = Прапусціць
# List view
name = Назва
modified = Зменена
size = Памер
## Metadata Dialog
owner = Уладальнік
group = Група
other = Іншыя
read = Чытаць
write = Пісаць
execute = Выконваць
# Context Pages
## About
git-description = Git каміт {$hash} ад {$date}
## Add Network Drive
add-network-drive = Дадаць сеткавы дыск
connect = Падлучыць
connect-anonymously = Падлучыць ананімна
connecting = Падлучэнне...
domain = Дамен
enter-server-address = Увядзіце адрас серверу
network-drive-description =
Адрасы сервераў ўключаюць у сябе прэфікс пратаколу і адрас.
Прыклад: ssh://192.168.0.1, ftp://[2001:db8::1]
### Make sure to keep the comma which separates the columns
network-drive-schemes =
Даступная пратаколы,Прэфікс
AppleTalk,afp://
File Transfer Protocol,ftp:// або ftps://
Network File System,nfs://
Server Message Block,smb://
SSH File Transfer Protocol,sftp:// або ssh://
WebDav,dav:// або davs://
network-drive-error = Немагчыма атрымаць доступ да сеткавага дыска
password = Пароль
remember-password = Запомніць пароль
try-again = Паўтарыць спробу
username = Імя карыстальніка
## Operations
operations = Аперацыі
edit-history = Гісторыя рэдагавання
history = Гісторыя
no-history = Няма элементаў у гісторыі
pending = У чаканні
failed = Няўдала
complete = Завершана
compressing = Сцісканне {$items} {$items ->
[one] элементу
*[other] элементаў
} з {$from} у {$to}
compressed = Сціснута {$items} {$items ->
[one] элемент
*[other] элементаў
} з {$from} у {$to}
copy_noun = Капіяваць
creating = Стварэнне {$name} у {$parent}
created = Створана {$name} у {$parent}
copying = Капіяванне {$items} {$items ->
[one] элементу
*[other] элементаў
} з {$from} у {$to}
copied = Скапіявана {$items} {$items ->
[one] элемент
*[other] элементаў
} з {$from} у {$to}
emptying-trash = Emptying {trash}
emptied-trash = Emptied {trash}
extracting = Выманне {$items} {$items ->
[one] элементу
*[other] элементаў
} з {$from} у {$to}
extracted = Вынята {$items} {$items ->
[one] элемент
*[other] элементаў
} з {$from} у {$to}
moving = Перамяшчэнне {$items} {$items ->
[one] элементу
*[other] элементаў
} з {$from} у {$to}
moved = Перанесена {$items} {$items ->
[one] элемент
*[other] элементаў
} з {$from} у {$to}
renaming = Перайменаванне {$from} у {$to}
renamed = Перайменавана {$from} у {$to}
restoring = Аднаўленне {$items} {$items ->
[one] элементу
*[other] элементаў
} з {trash}
restored = Адноўлена {$items} {$items ->
[one] элемент
*[other] элементаў
} з {trash}
unknown-folder = невядомая папка
## Open with
open-with = Адкрыць з дапамогай
@ -71,10 +165,14 @@ default-app = {$name} (па змаўчанні)
## Properties
properties = Уласцівасці
## Show details
show-details = Паказаць дэталі
## Settings
settings = Налады
settings-tab = Укладка
settings-show-hidden = Паказаць схаваныя файлы
default-view = Выгляд па змаўчанні
icon-size-list = Памер значка (спіс)
icon-size-grid = Памер значка (сетка)
sorting-name = Сартаваць
@ -91,6 +189,8 @@ light = Светлая
# Context menu
add-to-sidebar = Дадаць на бакавую панэль
compress = Сціснуць
extract-here = Выняць
new-file = Новы файл
new-folder = Новая папка
open-in-terminal = Адкрыць у кансолі
@ -108,6 +208,7 @@ file = Файл
new-tab = Новая ўкладка
new-window = Новае акно
rename = Перайменаваць
menu-show-details = Паказаць уласцівасці...
close-tab = Закрыць укладку
quit = Выйсці
@ -119,8 +220,22 @@ paste = Уставіць
select-all = Вылучыць усё
## View
zoom-in = Павялічыць
default-size = Памер па змаўчанні
zoom-out = Паменшыць
view = Выгляд
grid-view = Рэжым сеткі
list-view = Рэжым спіса
show-hidden-files = Паказваць схаваныя файлы
list-directories-first = Размяшчаць папкі перад файламі
menu-settings = Налады...
menu-about = Пра Файлы COSMIC...
## Sort
sort = Сартаванне
sort-a-z = А
sort-z-a = Я-А
sort-newest-first = Спачатку новыя
sort-oldest-first = Спачатку старыя
sort-smallest-to-largest = Ад меншага да найбольшага
sort-largest-to-smallest = Ад вялікага да найменшага

View file

@ -9,6 +9,7 @@ notification-in-progress = Operazioni sui file in corso ...
trash = Cestino
recents = Recenti
undo = Annulla ultima operazione
today = Oggi
# List view
name = Nome

View file

@ -112,7 +112,7 @@ pub enum Action {
OpenTerminal,
OpenWith,
Paste,
Properties,
Preview,
Rename,
RestoreFromTrash,
SearchActivate,
@ -164,7 +164,9 @@ impl Action {
Action::OpenTerminal => Message::OpenTerminal(entity_opt),
Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith),
Action::Paste => Message::Paste(entity_opt),
Action::Properties => Message::ToggleContextPage(ContextPage::Properties(None)),
Action::Preview => {
Message::ToggleContextPage(ContextPage::Preview(entity_opt, PreviewKind::Selected))
}
Action::Rename => Message::Rename(entity_opt),
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
Action::SearchActivate => Message::SearchActivate,
@ -210,18 +212,29 @@ impl MenuAction for Action {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ContextItem {
NavBar(segmented_button::Entity),
TabBar(segmented_button::Entity),
BreadCrumbs(usize),
#[derive(Clone, Debug)]
pub struct PreviewItem(pub tab::Item);
impl PartialEq for PreviewItem {
fn eq(&self, other: &Self) -> bool {
self.0.location_opt == other.0.location_opt
}
}
impl Eq for PreviewItem {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PreviewKind {
Custom(PreviewItem),
Location(Location),
Selected,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum NavMenuAction {
OpenInNewTab(segmented_button::Entity),
OpenInNewWindow(segmented_button::Entity),
Properties(segmented_button::Entity),
Preview(segmented_button::Entity),
RemoveFromSidebar(segmented_button::Entity),
EmptyTrash,
}
@ -279,6 +292,7 @@ pub enum Message {
PendingComplete(u64),
PendingError(u64, String),
PendingProgress(u64, f32),
Preview(Entity, PreviewKind, time::Duration),
RescanTrash,
Rename(Option<Entity>),
ReplaceResult(ReplaceResult),
@ -317,13 +331,13 @@ pub enum Message {
None,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ContextPage {
About,
EditHistory,
NetworkDrive,
OpenWith,
Properties(Option<ContextItem>),
Preview(Option<Entity>, PreviewKind),
Settings,
}
@ -334,7 +348,7 @@ impl ContextPage {
Self::EditHistory => fl!("edit-history"),
Self::NetworkDrive => fl!("add-network-drive"),
Self::OpenWith => fl!("open-with"),
Self::Properties(..) => String::default(),
Self::Preview(..) => String::default(),
Self::Settings => fl!("settings"),
}
}
@ -461,6 +475,7 @@ pub struct App {
pending_operations: BTreeMap<u64, (Operation, f32)>,
complete_operations: BTreeMap<u64, Operation>,
failed_operations: BTreeMap<u64, (Operation, String)>,
preview_opt: Option<(Entity, PreviewKind, time::Instant)>,
search_active: bool,
search_id: widget::Id,
search_input: String,
@ -487,7 +502,10 @@ impl App {
let mut tab = Tab::new(location.clone(), self.config.tab);
tab.mode = match self.mode {
Mode::App => tab::Mode::App,
Mode::Desktop => tab::Mode::Desktop,
Mode::Desktop => {
tab.config.view = tab::View::Grid;
tab::Mode::Desktop
}
};
let entity = self
.tab_model
@ -668,19 +686,21 @@ impl App {
.divider_above()
});
nav_model = nav_model.insert(|b| {
b.text(fl!("networks"))
.icon(widget::icon::icon(
widget::icon::from_name("network-workgroup-symbolic")
.size(16)
.handle(),
))
.data(Location::Network(
"network:///".to_string(),
fl!("networks"),
))
.divider_above()
});
if !self.mounters.is_empty() {
nav_model = nav_model.insert(|b| {
b.text(fl!("networks"))
.icon(widget::icon::icon(
widget::icon::from_name("network-workgroup-symbolic")
.size(16)
.handle(),
))
.data(Location::Network(
"network:///".to_string(),
fl!("networks"),
))
.divider_above()
});
}
// Collect all mounter items
let mut nav_items = Vec::new();
@ -944,59 +964,42 @@ impl App {
widget::settings::view_column(children).into()
}
fn properties(&self, entity: Option<ContextItem>) -> Element<Message> {
match entity {
None => self.tab_properties(self.tab_model.active()),
Some(ContextItem::TabBar(entity)) => self.tab_properties(entity),
Some(ContextItem::NavBar(item)) => {
let mut children = Vec::with_capacity(1);
if let Some(location) = self.nav_model.data::<Location>(item) {
if let Location::Path(path) = location {
//TODO: this should be done once, not when generating the view!
if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) {
children.push(item.property_view(IconSizes::default()));
fn preview(&self, entity_opt: &Option<Entity>, kind: &PreviewKind) -> Element<Message> {
let mut children = Vec::with_capacity(1);
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
match kind {
PreviewKind::Custom(PreviewItem(item)) => {
children.push(item.property_view(IconSizes::default()));
}
PreviewKind::Location(location) => {
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
if let Some(items) = tab.items_opt() {
for item in items.iter() {
if item.location_opt.as_ref() == Some(location) {
children.push(item.property_view(tab.config.icon_sizes));
// Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files
break;
}
}
}
}
widget::settings::view_column(children).into()
}
Some(ContextItem::BreadCrumbs(index)) => {
let mut children = Vec::with_capacity(1);
if let Some(tab) = self.tab_model.active_data::<Tab>() {
let path_opt = tab
.location
.path_opt()
.and_then(|path| path.ancestors().nth(index))
.map(|path| path.to_path_buf());
if let Some(ref path) = path_opt {
//TODO: this should be done once, not when generating the view!
if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) {
children.push(item.property_view(IconSizes::default()));
PreviewKind::Selected => {
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
if let Some(items) = tab.items_opt() {
for item in items.iter() {
if item.selected {
children.push(item.property_view(tab.config.icon_sizes));
// Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files
break;
}
}
};
}
widget::settings::view_column(children).into()
}
}
}
fn tab_properties(&self, entity: segmented_button::Entity) -> Element<Message> {
let mut children = Vec::new();
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
if let Some(items) = tab.items_opt() {
for item in items.iter() {
if item.selected {
children.push(item.property_view(tab.config.icon_sizes));
// Only show one property view to avoid issues like hangs when generating
// preview images on thousands of files
break;
}
}
}
}
widget::settings::view_column(children).into()
}
@ -1157,6 +1160,7 @@ impl Application for App {
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
core.window.context_is_overlay = false;
match flags.mode {
Mode::App => {}
Mode::Desktop => {
@ -1199,6 +1203,7 @@ impl Application for App {
pending_operations: BTreeMap::new(),
complete_operations: BTreeMap::new(),
failed_operations: BTreeMap::new(),
preview_opt: None,
search_active: false,
search_id: widget::Id::unique(),
search_input: String::new(),
@ -1301,10 +1306,7 @@ impl Application for App {
NavMenuAction::OpenInNewWindow(id),
),
cosmic::widget::menu::Item::Divider,
cosmic::widget::menu::Item::Button(
fl!("show-details"),
NavMenuAction::Properties(id),
),
cosmic::widget::menu::Item::Button(fl!("show-details"), NavMenuAction::Preview(id)),
cosmic::widget::menu::Item::Divider,
if is_context_trash {
cosmic::widget::menu::Item::Button(
@ -1363,7 +1365,7 @@ impl Application for App {
// Usually, the Escape key (for example) closes menus and panes one by one instead
// of closing everything on one press
if self.core.window.show_context {
self.core.window.show_context = false;
self.set_show_context(false);
return Command::none();
}
if self.search_active {
@ -1708,7 +1710,7 @@ impl Application for App {
Ok(true) => {
log::info!("connected to {:?}", uri);
if matches!(self.context_page, ContextPage::NetworkDrive) {
self.core.window.show_context = false;
self.set_show_context(false);
}
}
Ok(false) => {
@ -1877,7 +1879,7 @@ impl Application for App {
}
// Close Open With context view
self.core.window.show_context = false;
self.set_show_context(false);
}
Message::OpenInNewTab(entity_opt) => {
return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map(
@ -1947,10 +1949,34 @@ impl Application for App {
});
}
ClipboardKind::Cut => {
self.operation(Operation::Move {
paths: contents.paths,
to,
});
//TODO: determine ability to move on non-Unix systems
let mut can_move = true;
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
//TODO: better error handling, fall back to not moving?
if let Ok(to_meta) = fs::metadata(&to) {
for path in contents.paths.iter() {
if let Ok(meta) = fs::metadata(path) {
if meta.dev() != to_meta.dev() {
can_move = false;
}
}
}
}
}
if can_move {
self.operation(Operation::Move {
paths: contents.paths,
to,
});
} else {
self.operation(Operation::Copy {
paths: contents.paths,
to,
});
}
}
}
}
@ -1973,9 +1999,8 @@ impl Application for App {
.map(cosmic::app::Message::App),
);
}
self.complete_operations.insert(id, op);
}
self.complete_operations.insert(id, op);
}
// Potentially show a notification
commands.push(self.update_notification());
@ -2002,6 +2027,17 @@ impl Application for App {
}
return self.update_notification();
}
Message::Preview(entity, kind, timeout) => {
if self
.preview_opt
.as_ref()
.is_some_and(|(e, k, i)| *e == entity && *k == kind && i.elapsed() > timeout)
{
self.context_page = ContextPage::Preview(Some(entity), kind);
self.set_show_context(true);
self.set_context_title(self.context_page.title());
}
}
Message::RescanTrash => {
// Update trash icon if empty/full
let maybe_entity = self.nav_model.iter().find(|&entity| {
@ -2229,7 +2265,7 @@ impl Application for App {
//TODO: move to Command?
if let tab::Message::ContextMenu(_point_opt) = tab_message {
// Disable side context page
self.core.window.show_context = false;
self.set_show_context(false);
}
let tab_commands = match self.tab_model.data_mut::<Tab>(entity) {
@ -2244,10 +2280,9 @@ impl Application for App {
commands.push(self.update(action.message(Some(entity))));
}
tab::Command::AddNetworkDrive => {
let context_page = ContextPage::NetworkDrive;
self.context_page = context_page;
self.core.window.show_context = true;
self.set_context_title(context_page.title());
self.context_page = ContextPage::NetworkDrive;
self.set_show_context(true);
self.set_context_title(self.context_page.title());
}
tab::Command::ChangeLocation(tab_title, tab_path, selection_path) => {
self.activate_nav_model_location(&tab_path);
@ -2270,12 +2305,6 @@ impl Application for App {
message::app(Message::TabMessage(Some(entity), tab_message))
}));
}
tab::Command::LocationProperties(index) => {
self.context_page =
ContextPage::Properties(Some(ContextItem::BreadCrumbs(index)));
self.core.window.show_context = true;
self.set_context_title(self.context_page.title());
}
tab::Command::MoveToTrash(paths) => {
self.operation(Operation::Delete { paths });
}
@ -2346,6 +2375,23 @@ impl Application for App {
log::error!("failed to get current executable path: {}", err);
}
},
tab::Command::Preview(kind, mut timeout) => {
self.preview_opt = Some((entity, kind.clone(), Instant::now()));
if self.core.window.show_context {
// If the context window is already open, immediately show the preview
timeout = time::Duration::new(0, 0)
};
commands.push(Command::perform(
async move {
tokio::time::sleep(timeout).await;
message::app(Message::Preview(entity, kind, timeout))
},
|x| x,
));
}
tab::Command::PreviewCancel => {
self.preview_opt = None;
}
}
}
return Command::batch(commands);
@ -2374,12 +2420,12 @@ impl Application for App {
Message::ToggleContextPage(context_page) => {
//TODO: ensure context menus are closed
if self.context_page == context_page {
self.core.window.show_context = !self.core.window.show_context;
self.set_show_context(!self.core.window.show_context);
} else {
self.context_page = context_page;
self.core.window.show_context = true;
self.set_show_context(true);
}
self.set_context_title(context_page.title());
self.context_page = context_page;
self.set_context_title(self.context_page.title());
}
Message::Undo(id) => {
// TODO;
@ -2595,10 +2641,22 @@ impl Application for App {
}
}
NavMenuAction::Properties(entity) => {
self.context_page = ContextPage::Properties(Some(ContextItem::NavBar(entity)));
self.core.window.show_context = true;
self.set_context_title(self.context_page.title());
NavMenuAction::Preview(entity) => {
if let Some(Location::Path(path)) = self.nav_model.data::<Location>(entity) {
match tab::item_from_path(path, IconSizes::default()) {
Ok(item) => {
self.context_page = ContextPage::Preview(
None,
PreviewKind::Custom(PreviewItem(item)),
);
self.set_show_context(true);
self.set_context_title(self.context_page.title());
}
Err(err) => {
log::warn!("failed to get item from path {:?}: {}", path, err);
}
}
}
}
NavMenuAction::RemoveFromSidebar(entity) => {
@ -2703,12 +2761,12 @@ impl Application for App {
return None;
}
Some(match self.context_page {
Some(match &self.context_page {
ContextPage::About => self.about(),
ContextPage::EditHistory => self.edit_history(),
ContextPage::NetworkDrive => self.network_drive(),
ContextPage::OpenWith => self.open_with(),
ContextPage::Properties(entity) => self.properties(entity),
ContextPage::Preview(entity_opt, kind) => self.preview(entity_opt, kind),
ContextPage::Settings => self.settings(),
})
}
@ -3182,7 +3240,7 @@ impl Application for App {
space_xxs, space_s, ..
} = theme::active().cosmic().spacing;
let mut tab_column = widget::column::with_capacity(1);
let mut tab_column = widget::column::with_capacity(3);
if self.tab_model.iter().count() > 1 {
tab_column = tab_column.push(
@ -3218,7 +3276,13 @@ impl Application for App {
}
}
let content: Element<_> = widget::toaster(&self.toasts, tab_column).into();
// The toaster is added on top of an empty element to ensure that it does not override context menus
tab_column = tab_column.push(widget::toaster(
&self.toasts,
widget::horizontal_space(Length::Fill),
));
let content: Element<_> = tab_column.into();
// Uncomment to debug layout:
//content.explain(cosmic::iced::Color::WHITE)

View file

@ -168,7 +168,7 @@ pub struct TabConfig {
impl Default for TabConfig {
fn default() -> Self {
Self {
view: View::Grid,
view: View::List,
folders_first: true,
show_hidden: false,
sort_name: HeadingOptions::Name,

View file

@ -574,6 +574,7 @@ impl Application for App {
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Message>) {
core.window.context_is_overlay = false;
core.window.show_close = false;
core.window.show_maximize = false;
core.window.show_minimize = false;

View file

@ -46,7 +46,7 @@ pub fn key_binds() -> HashMap<KeyBind, Action> {
bind!([Ctrl], Key::Named(Named::Enter), OpenInNewTab);
bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow);
bind!([Ctrl], Key::Character("v".into()), Paste);
bind!([], Key::Named(Named::Space), Properties);
bind!([], Key::Named(Named::Space), Preview);
bind!([], Key::Named(Named::F2), Rename);
bind!([Ctrl], Key::Character("f".into()), SearchActivate);
bind!([Ctrl], Key::Character("a".into()), SelectAll);

View file

@ -22,7 +22,7 @@ use crate::{
macro_rules! menu_button {
($($x:expr),+ $(,)?) => (
button(
button::custom(
Row::with_children(
vec![$(Element::from($x)),+]
)
@ -131,9 +131,18 @@ pub fn context_menu<'a>(
children.push(divider::horizontal::light().into());
let supported_archive_types = [
"application/gzip",
"application/x-compressed-tar",
"application/x-tar",
"application/zip",
#[cfg(feature = "bzip2")]
"application/x-bzip",
#[cfg(feature = "bzip2")]
"application/x-bzip-compressed-tar",
#[cfg(feature = "liblzma")]
"application/x-xz",
#[cfg(feature = "liblzma")]
"application/x-xz-compressed-tar",
]
.iter()
.filter_map(|mime_type| mime_type.parse::<Mime>().ok())
@ -146,7 +155,7 @@ pub fn context_menu<'a>(
children.push(divider::horizontal::light().into());
//TODO: Print?
children.push(menu_item(fl!("show-details"), Action::Properties).into());
children.push(menu_item(fl!("show-details"), Action::Preview).into());
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
children.push(divider::horizontal::light().into());
@ -222,7 +231,7 @@ pub fn context_menu<'a>(
children.push(divider::horizontal::light().into());
}
if selected > 0 {
children.push(menu_item(fl!("show-details"), Action::Properties).into());
children.push(menu_item(fl!("show-details"), Action::Preview).into());
children.push(divider::horizontal::light().into());
children
.push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into());
@ -362,7 +371,7 @@ pub fn menu_bar<'a>(
menu::Item::Divider,
menu::Item::Button(fl!("rename"), Action::Rename),
menu::Item::Divider,
menu::Item::Button(fl!("menu-show-details"), Action::Properties),
menu::Item::Button(fl!("menu-show-details"), Action::Preview),
menu::Item::Divider,
menu::Item::Button(fl!("add-to-sidebar"), Action::AddToSidebar),
menu::Item::Divider,
@ -477,7 +486,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess
divider::horizontal::light().into(),
menu_button!(text::body(fl!("show-details")))
.on_press(tab::Message::LocationMenuAction(
LocationMenuAction::Properties(ancestor_index),
LocationMenuAction::Preview(ancestor_index),
))
.into(),
];

View file

@ -36,6 +36,7 @@ pub struct MouseArea<'a, Message> {
on_press: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_drag_end: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_release: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_resize: Option<Box<dyn Fn(Size) -> Message + 'a>>,
on_right_press: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_right_press_no_capture: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
on_right_release: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
@ -85,6 +86,13 @@ impl<'a, Message> MouseArea<'a, Message> {
self
}
/// The message to emit on resizing.
#[must_use]
pub fn on_resize(mut self, message: impl Fn(Size) -> Message + 'a) -> Self {
self.on_resize = Some(Box::new(message));
self
}
/// The message to emit on a right button press.
#[must_use]
pub fn on_right_press(mut self, message: impl Fn(Option<Point>) -> Message + 'a) -> Self {
@ -182,6 +190,7 @@ struct State {
drag_initiated: Option<Point>,
modifiers: Modifiers,
prev_click: Option<(mouse::Click, Instant)>,
size: Option<Size>,
}
impl State {
@ -235,6 +244,7 @@ impl<'a, Message> MouseArea<'a, Message> {
on_double_click: None,
on_press: None,
on_release: None,
on_resize: None,
on_right_press: None,
on_right_press_no_capture: None,
on_right_release: None,
@ -449,6 +459,14 @@ fn update<Message: Clone>(
) -> event::Status {
let layout_bounds = layout.bounds();
if let Some(message) = widget.on_resize.as_ref() {
let size = layout_bounds.size();
if state.size != Some(size) {
state.size = Some(size);
shell.publish(message(size));
}
}
if state.drag_initiated.is_none() && !cursor.is_over(layout_bounds) {
return event::Status::Ignored;
}

View file

@ -635,22 +635,26 @@ impl Operation {
if let Some(file_stem) = path.file_stem() {
let mut new_dir = to.join(file_stem);
// Make sure all extension parts are removed (file_stem may still contain them)
while new_dir.extension().is_some() {
new_dir.set_extension("");
}
if new_dir.exists() {
let mut extensionless_path = path.to_owned();
extensionless_path.set_extension("");
if let Some(new_dir_parent) = new_dir.parent() {
new_dir = copy_unique_path(&extensionless_path, new_dir_parent);
new_dir = copy_unique_path(&new_dir, new_dir_parent);
}
}
let mime = mime_for_path(&path);
match mime.essence_str() {
"application/x-compressed-tar" => fs::File::open(path)
.map(io::BufReader::new)
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)?,
"application/gzip" | "application/x-compressed-tar" => {
fs::File::open(path)
.map(io::BufReader::new)
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)?
}
"application/x-tar" => fs::File::open(path)
.map(io::BufReader::new)
.map(tar::Archive::new)
@ -662,6 +666,24 @@ impl Operation {
.map_err(err_str)?
.and_then(|mut archive| archive.extract(new_dir))
.map_err(err_str)?,
#[cfg(feature = "bzip2")]
"application/x-bzip" | "application/x-bzip-compressed-tar" => {
fs::File::open(path)
.map(io::BufReader::new)
.map(bzip2::read::BzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)?
}
#[cfg(feature = "liblzma")]
"application/x-xz" | "application/x-xz-compressed-tar" => {
fs::File::open(path)
.map(io::BufReader::new)
.map(liblzma::read::XzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)?
}
_ => Err(format!("unsupported mime type {:?}", mime))?,
}
}

View file

@ -55,7 +55,7 @@ use std::{
};
use crate::{
app::{self, Action},
app::{self, Action, PreviewItem, PreviewKind},
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
config::{IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID},
dialog::DialogKind,
@ -687,11 +687,9 @@ fn uri_to_path(uri: String) -> Option<PathBuf> {
}
pub fn scan_recents(sizes: IconSizes) -> Vec<Item> {
let mut recent_files = recently_used_xbel::parse_file();
let mut recents = Vec::new();
match recent_files {
match recently_used_xbel::parse_file() {
Ok(recent_files) => {
for bookmark in recent_files.bookmarks {
let uri = bookmark.href;
@ -814,11 +812,12 @@ pub enum Command {
DropFiles(PathBuf, ClipboardPaste),
EmptyTrash,
Iced(cosmic::Command<Message>),
LocationProperties(usize),
MoveToTrash(Vec<PathBuf>),
OpenFile(PathBuf),
OpenInNewTab(PathBuf),
OpenInNewWindow(PathBuf),
Preview(PreviewKind, Duration),
PreviewCancel,
}
#[derive(Clone, Debug)]
@ -850,6 +849,7 @@ pub enum Message {
RightClick(Option<usize>),
MiddleClick(usize),
Scroll(Viewport),
ScrollToFocus,
SelectAll,
SetSort(HeadingOptions, bool),
Thumbnail(PathBuf, ItemThumbnail),
@ -870,7 +870,7 @@ pub enum Message {
pub enum LocationMenuAction {
OpenInNewTab(usize),
OpenInNewWindow(usize),
Properties(usize),
Preview(usize),
}
impl MenuAction for LocationMenuAction {
@ -1003,7 +1003,7 @@ impl Item {
if let Some(Location::Path(path)) = &self.location_opt {
for app in self.open_with.iter() {
column = column.push(
widget::button(
widget::button::custom(
widget::row::with_children(vec![
widget::icon(app.icon.clone()).into(),
if app.is_default {
@ -1230,6 +1230,7 @@ pub struct Tab {
cached_selected: RefCell<Option<bool>>,
clicked: Option<usize>,
selected_clicked: bool,
last_right_click: Option<usize>,
}
fn folder_name<P: AsRef<Path>>(path: P) -> (String, bool) {
@ -1276,6 +1277,7 @@ impl Tab {
clicked: None,
dnd_hovered: None,
selected_clicked: false,
last_right_click: None,
}
}
@ -1551,6 +1553,7 @@ impl Tab {
self.location = location.clone();
self.items_opt = None;
self.select_focus = None;
self.context_menu = None;
self.edit_location = None;
if let Some(history_i) = history_i_opt {
// Navigating in history
@ -1571,6 +1574,7 @@ impl Tab {
let mut history_i_opt = None;
let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple();
let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple();
let last_select_focus = self.select_focus;
match message {
Message::AddNetworkDrive => {
commands.push(Command::AddNetworkDrive);
@ -1624,6 +1628,9 @@ impl Tab {
} else {
log::warn!("no item for click index {:?}", click_i_opt);
}
// Cancel any preview timers
commands.push(Command::PreviewCancel);
}
Message::Click(click_i_opt) => {
self.selected_clicked = false;
@ -1739,7 +1746,7 @@ impl Tab {
item.selected = true;
self.select_range = Some((i, i));
}
self.select_focus = click_i_opt;
self.selected_clicked = true;
} else if !dont_unset && item.selected {
self.clicked = click_i_opt;
@ -1747,10 +1754,6 @@ impl Tab {
}
}
}
if self.select_focus.take().is_some() {
// Unfocus currently focused button
commands.push(Command::Iced(widget::button::focus(widget::Id::unique())));
}
}
}
Message::Config(config) => {
@ -1765,6 +1768,14 @@ impl Tab {
Message::ContextMenu(point_opt) => {
if point_opt.is_none() || !mod_shift {
self.context_menu = point_opt;
//TODO: hack for clearing selecting when right clicking empty space
if self.context_menu.is_some() && self.last_right_click.take().is_none() {
if let Some(ref mut items) = self.items_opt {
for item in items.iter_mut() {
item.selected = false;
}
}
}
}
}
Message::LocationContextMenuPoint(point_opt) => {
@ -1795,8 +1806,22 @@ impl Tab {
commands.push(Command::OpenInNewWindow(path));
}
}
LocationMenuAction::Properties(ancestor_index) => {
commands.push(Command::LocationProperties(ancestor_index));
LocationMenuAction::Preview(ancestor_index) => {
if let Some(path) = path_for_index(ancestor_index) {
//TODO: blocking code, run in command
match item_from_path(&path, IconSizes::default()) {
Ok(item) => {
// Show preview instantly
commands.push(Command::Preview(
PreviewKind::Custom(PreviewItem(item)),
Duration::new(0, 0),
));
}
Err(err) => {
log::warn!("failed to get item from path {:?}: {}", path, err);
}
}
}
}
}
}
@ -2039,6 +2064,8 @@ impl Tab {
}
}
}
//TODO: hack for clearing selecting when right clicking empty space
self.last_right_click = click_i_opt;
}
Message::MiddleClick(click_i) => {
self.update(Message::Click(Some(click_i)), modifiers);
@ -2070,6 +2097,14 @@ impl Tab {
Message::Scroll(viewport) => {
self.scroll_opt = Some(viewport.absolute_offset());
}
Message::ScrollToFocus => {
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
}
}
Message::SelectAll => {
self.select_all();
if self.select_focus.take().is_some() {
@ -2094,7 +2129,8 @@ impl Tab {
rgba.as_raw().clone(),
);
item.icon_handle_grid = handle.clone();
item.icon_handle_list = handle;
item.icon_handle_list = handle.clone();
item.icon_handle_list_condensed = handle;
}
item.thumbnail_opt = Some(thumbnail);
break;
@ -2146,9 +2182,11 @@ impl Tab {
self.dnd_hovered = None;
}
Message::DndHover(loc) => {
if self.dnd_hovered.as_ref().is_some_and(|(l, i)| {
*l == loc && Instant::now().duration_since(*i) > HOVER_DURATION
}) {
if self
.dnd_hovered
.as_ref()
.is_some_and(|(l, i)| *l == loc && i.elapsed() > HOVER_DURATION)
{
cd = Some(loc);
}
}
@ -2163,6 +2201,9 @@ impl Tab {
|x| x,
)));
}
// Clear preview timer
commands.push(Command::PreviewCancel);
}
Message::DndLeave(loc) => {
if Some(&loc) == self.dnd_hovered.as_ref().map(|(l, _)| l) {
@ -2212,6 +2253,26 @@ impl Tab {
}
}
}
// Update preview timer
//TODO: make this configurable
if last_select_focus != self.select_focus {
if let Some(index) = self.select_focus {
if let Some(ref items) = self.items_opt {
if let Some(item) = items.get(index) {
if let Some(location) = item.location_opt.clone() {
// Show preview after double click timeout
commands.push(Command::Preview(
PreviewKind::Location(location),
DOUBLE_CLICK_DURATION,
));
}
}
}
}
}
// Change directory if requested
if let Some(location) = cd {
if matches!(self.mode, Mode::Desktop) {
match location {
@ -2238,6 +2299,7 @@ impl Tab {
}
}
}
commands
}
@ -2322,6 +2384,58 @@ impl Tab {
Some(items)
}
fn dnd_dest<'a>(
&self,
location: &Location,
element: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
let location1 = location.clone();
let location2 = location.clone();
let location3 = location.clone();
let is_dnd_hovered = self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&location);
widget::container(
DndDestination::for_data::<ClipboardPaste>(element, move |data, action| {
if let Some(mut data) = data {
if action == DndAction::Copy {
Message::Drop(Some((location1.clone(), data)))
} else if action == DndAction::Move {
data.kind = ClipboardKind::Cut;
Message::Drop(Some((location1.clone(), data)))
} else {
log::warn!("unsupported action: {:?}", action);
Message::Drop(None)
}
} else {
Message::Drop(None)
}
})
.on_enter(move |_, _, _| Message::DndEnter(location2.clone()))
.on_leave(move || Message::DndLeave(location3.clone())),
)
.style(if is_dnd_hovered {
theme::Container::custom(|t| {
let mut a = cosmic::iced_style::container::StyleSheet::appearance(
t,
&theme::Container::default(),
);
let t = t.cosmic();
// todo use theme drop target color
let mut bg = t.accent_color();
bg.alpha = 0.2;
a.background = Some(Color::from(bg).into());
a.border = Border {
color: t.accent_color().into(),
width: 1.0,
radius: t.radius_s().into(),
};
a
})
} else {
theme::Container::default()
})
.into()
}
pub fn location_view(&self) -> Element<Message> {
//TODO: responsiveness is done in a hacky way, potentially move this to a custom widget?
fn text_width<'a>(
@ -2375,7 +2489,7 @@ impl Tab {
let mut w = 0.0;
let mut prev_button =
widget::button(widget::icon::from_name("go-previous-symbolic").size(16))
widget::button::custom(widget::icon::from_name("go-previous-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon);
if self.history_i > 0 && !self.history.is_empty() {
@ -2384,9 +2498,10 @@ impl Tab {
row = row.push(prev_button);
w += 16.0 + 2.0 * space_xxs as f32;
let mut next_button = widget::button(widget::icon::from_name("go-next-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon);
let mut next_button =
widget::button::custom(widget::icon::from_name("go-next-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon);
if self.history_i + 1 < self.history.len() {
next_button = next_button.on_press(Message::GoNext);
}
@ -2442,10 +2557,12 @@ impl Tab {
match location {
Location::Path(path) => {
row = row.push(
widget::button(widget::icon::from_name("window-close-symbolic").size(16))
.on_press(Message::EditLocation(None))
.padding(space_xxs)
.style(theme::Button::Icon),
widget::button::custom(
widget::icon::from_name("window-close-symbolic").size(16),
)
.on_press(Message::EditLocation(None))
.padding(space_xxs)
.style(theme::Button::Icon),
);
row = row.push(
widget::text_input("", path.to_string_lossy())
@ -2479,7 +2596,7 @@ impl Tab {
} else if let Location::Path(path) = &self.location {
row = row.push(
crate::mouse_area::MouseArea::new(
widget::button(widget::icon::from_name("edit-symbolic").size(16))
widget::button::custom(widget::icon::from_name("edit-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon)
.on_press(Message::EditLocation(Some(self.location.clone()))),
@ -2489,7 +2606,7 @@ impl Tab {
w += 16.0 + 2.0 * space_xxs as f32;
} else if let Location::Search(_, term) = &self.location {
row = row.push(
widget::button(
widget::button::custom(
widget::row::with_children(vec![
widget::icon::from_name("system-search-symbolic")
.size(16)
@ -2547,17 +2664,19 @@ impl Tab {
w += name_width;
}
let location = match &self.location {
Location::Path(_) => Location::Path(ancestor.to_path_buf()),
Location::Search(_, term) => {
Location::Search(ancestor.to_path_buf(), term.clone())
}
other => other.clone(),
};
let mut mouse_area = crate::mouse_area::MouseArea::new(
widget::button(row)
widget::button::custom(row)
.padding(space_xxxs)
.style(theme::Button::Link)
.on_press(Message::Location(match &self.location {
Location::Path(_) => Location::Path(ancestor.to_path_buf()),
Location::Search(_, term) => {
Location::Search(ancestor.to_path_buf(), term.clone())
}
other => other.clone(),
})),
.on_press(Message::Location(location.clone())),
);
if self.location_context_menu_index.is_some() {
@ -2577,7 +2696,7 @@ impl Tab {
mouse_area
};
children.push(mouse_area.into());
children.push(self.dnd_dest(&location, mouse_area));
if found_home || overflow {
break;
@ -2587,7 +2706,7 @@ impl Tab {
}
Location::Trash => {
children.push(
widget::button(widget::text::heading(fl!("trash")))
widget::button::custom(widget::text::heading(fl!("trash")))
.padding(space_xxxs)
.on_press(Message::Location(Location::Trash))
.style(theme::Button::Text)
@ -2596,7 +2715,7 @@ impl Tab {
}
Location::Recents => {
children.push(
widget::button(widget::text::heading(fl!("recents")))
widget::button::custom(widget::text::heading(fl!("recents")))
.padding(space_xxxs)
.on_press(Message::Location(Location::Recents))
.style(theme::Button::Text)
@ -2605,7 +2724,7 @@ impl Tab {
}
Location::Network(uri, display_name) => {
children.push(
widget::button(widget::text::heading(display_name))
widget::button::custom(widget::text::heading(display_name))
.padding(space_xxxs)
.on_press(Message::Location(Location::Network(
uri.clone(),
@ -2776,14 +2895,14 @@ impl Tab {
//TODO: one focus group per grid item (needs custom widget)
let buttons = vec![
widget::button(
widget::button::custom(
widget::icon::icon(item.icon_handle_grid.clone())
.content_fit(ContentFit::Contain)
.size(icon_sizes.grid()),
)
.padding(space_xxxs)
.style(button_style(item.selected, false, false, false)),
widget::button(widget::text::body(&item.display_name))
widget::button::custom(widget::text::body(&item.display_name))
.id(item.button_id.clone())
.padding([0, space_xxxs])
.style(button_style(
@ -2810,58 +2929,12 @@ impl Tab {
}
}
let column: Element<Message> = if item.metadata.is_dir()
&& item.location_opt.is_some()
{
let tab_location = item.location_opt.clone().unwrap();
let tab_location_enter = tab_location.clone();
let tab_location_leave = tab_location.clone();
let is_dnd_hovered =
self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location);
cosmic::widget::container(
DndDestination::for_data::<ClipboardPaste>(column, move |data, action| {
if let Some(mut data) = data {
if action == DndAction::Copy {
Message::Drop(Some((tab_location.clone(), data)))
} else if action == DndAction::Move {
data.kind = ClipboardKind::Cut;
Message::Drop(Some((tab_location.clone(), data)))
} else {
log::warn!("unsupported action: {:?}", action);
Message::Drop(None)
}
} else {
Message::Drop(None)
}
})
.on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone()))
.on_leave(move || Message::DndLeave(tab_location_leave.clone())),
)
.style(if is_dnd_hovered {
theme::Container::custom(|t| {
let mut a = cosmic::iced_style::container::StyleSheet::appearance(
t,
&theme::Container::default(),
);
let t = t.cosmic();
// todo use theme drop target color
let mut bg = t.accent_color();
bg.alpha = 0.2;
a.background = Some(Color::from(bg).into());
a.border = Border {
color: t.accent_color().into(),
width: 1.0,
radius: t.radius_s().into(),
};
a
})
let column: Element<Message> =
if item.metadata.is_dir() && item.location_opt.is_some() {
self.dnd_dest(&item.location_opt.clone().unwrap(), column)
} else {
theme::Container::default()
})
.into()
} else {
column.into()
};
column.into()
};
if item.selected {
dnd_items.push((i, (row, col), item));
@ -2962,7 +3035,7 @@ impl Tab {
};
if *row == r && *col == c {
let buttons = vec![
widget::button(
widget::button::custom(
widget::icon::icon(item.icon_handle_grid.clone())
.content_fit(ContentFit::Contain)
.size(icon_sizes.grid()),
@ -2975,7 +3048,7 @@ impl Tab {
false,
false,
)),
widget::button(widget::text(item.display_name.clone()))
widget::button::custom(widget::text(item.display_name.clone()))
.id(item.button_id.clone())
.on_press(Message::Click(Some(*i)))
.padding([0, space_xxxs])
@ -3043,7 +3116,8 @@ impl Tab {
let modified_width = 200.0;
let size_width = 100.0;
let condensed = size.width < (name_width + modified_width + size_width);
let icon_size = if condensed {
let is_search = matches!(self.location, Location::Search(_, _));
let icon_size = if condensed || is_search {
icon_sizes.list_condensed()
} else {
icon_sizes.list()
@ -3140,6 +3214,32 @@ impl Tab {
.height(Length::Fixed(row_height as f32))
.align_items(Alignment::Center)
.spacing(space_xxs)
} else if is_search {
widget::row::with_children(vec![
widget::icon::icon(item.icon_handle_list_condensed.clone())
.content_fit(ContentFit::Contain)
.size(icon_size)
.into(),
widget::column::with_children(vec![
widget::text(item.display_name.clone()).into(),
widget::text::caption(match item.path_opt() {
Some(path) => path.display().to_string(),
None => String::new(),
})
.into(),
])
.width(Length::Fill)
.into(),
widget::text(modified_text.clone())
.width(Length::Fixed(modified_width))
.into(),
widget::text(size_text.clone())
.width(Length::Fixed(size_width))
.into(),
])
.height(Length::Fixed(row_height as f32))
.align_items(Alignment::Center)
.spacing(space_xxs)
} else {
widget::row::with_children(vec![
widget::icon::icon(item.icon_handle_list.clone())
@ -3163,7 +3263,7 @@ impl Tab {
let button = |row| {
let mouse_area = crate::mouse_area::MouseArea::new(
widget::button(row)
widget::button::custom(row)
.width(Length::Fill)
.id(item.button_id.clone())
.padding([0, space_xxs])
@ -3186,54 +3286,7 @@ impl Tab {
let button_row = button(row.into());
let button_row: Element<_> =
if item.metadata.is_dir() && item.location_opt.is_some() {
let tab_location = item.location_opt.clone().unwrap();
let tab_location_enter = tab_location.clone();
let tab_location_leave = tab_location.clone();
let is_dnd_hovered =
self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location);
cosmic::widget::container(
DndDestination::for_data(button_row, move |data, action| {
if let Some(mut data) = data {
if action == DndAction::Copy {
Message::Drop(Some((tab_location.clone(), data)))
} else if action == DndAction::Move {
data.kind = ClipboardKind::Cut;
Message::Drop(Some((tab_location.clone(), data)))
} else {
log::warn!("unsupported action: {:?}", action);
Message::Drop(None)
}
} else {
log::warn!("No data for drop.");
Message::Drop(None)
}
})
.on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone()))
.on_leave(move || Message::DndLeave(tab_location_leave.clone())),
)
// todo refactor into the dnd destination wrapper
.style(if is_dnd_hovered {
theme::Container::custom(|t| {
let mut a = cosmic::iced_style::container::StyleSheet::appearance(
t,
&theme::Container::default(),
);
let t = t.cosmic();
// todo use theme drop target color
let mut bg = t.accent_color();
bg.alpha = 0.2;
a.background = Some(Color::from(bg).into());
a.border = Border {
color: t.accent_color().into(),
width: 1.0,
radius: t.radius_s().into(),
};
a
})
} else {
theme::Container::default()
})
.into()
self.dnd_dest(item.location_opt.as_ref().unwrap(), button_row)
} else {
button_row.into()
};
@ -3257,6 +3310,32 @@ impl Tab {
.align_items(Alignment::Center)
.spacing(space_xxs)
.into()
} else if is_search {
widget::row::with_children(vec![
widget::icon::icon(item.icon_handle_list_condensed.clone())
.content_fit(ContentFit::Contain)
.size(icon_size)
.into(),
widget::column::with_children(vec![
widget::text(item.display_name.clone()).into(),
widget::text::caption(match item.path_opt() {
Some(path) => path.display().to_string(),
None => String::new(),
})
.into(),
])
.width(Length::Fill)
.into(),
widget::text(modified_text.clone())
.width(Length::Fixed(modified_width))
.into(),
widget::text(size_text.clone())
.width(Length::Fixed(size_width))
.into(),
])
.align_items(Alignment::Center)
.spacing(space_xxs)
.into()
} else {
widget::row::with_children(vec![
widget::icon::icon(item.icon_handle_list.clone())
@ -3299,7 +3378,7 @@ impl Tab {
}
//TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that
{
let top_deduct = (if condensed { 6 } else { 9 }) * space_xxs;
let top_deduct = (if condensed || is_search { 6 } else { 9 }) * space_xxs;
self.item_view_size_opt
.set(self.size_opt.get().map(|s| Size {
@ -3394,6 +3473,8 @@ impl Tab {
let mut mouse_area = mouse_area::MouseArea::new(item_view)
.on_press(move |_point_opt| Message::Click(None))
.on_release(|_| Message::ClickRelease(None))
//TODO: better way to keep focused item in view
.on_resize(|_| Message::ScrollToFocus)
.on_back_press(move |_point_opt| Message::GoPrevious)
.on_forward_press(move |_point_opt| Message::GoNext)
.on_scroll(respond_to_scroll_direction);
@ -3591,10 +3672,7 @@ impl Tab {
}
}
//TODO: how to properly kill this task?
loop {
tokio::time::sleep(std::time::Duration::new(1, 0)).await;
}
std::future::pending().await
},
));
}