commit
09eb076372
16 changed files with 1074 additions and 1146 deletions
190
Cargo.lock
generated
190
Cargo.lock
generated
|
|
@ -976,9 +976,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
|
|
@ -1457,8 +1457,6 @@ dependencies = [
|
||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fork",
|
"fork",
|
||||||
"freedesktop_entry_parser",
|
|
||||||
"futures",
|
|
||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"glob",
|
"glob",
|
||||||
|
|
@ -1575,8 +1573,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
source = "git+https://github.com/pop-os/cosmic-text.git#ee702e50901d90cd842dbd88154687bd2512b52c"
|
source = "git+https://github.com/pop-os/cosmic-text.git#bdd6657fd7268dc8882214f540a6150eb78cfae0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"fontdb 0.23.0",
|
"fontdb 0.23.0",
|
||||||
|
|
@ -1584,10 +1582,10 @@ dependencies = [
|
||||||
"linebender_resource_handle",
|
"linebender_resource_handle",
|
||||||
"log",
|
"log",
|
||||||
"rangemap",
|
"rangemap",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 2.1.1",
|
||||||
"self_cell",
|
"self_cell",
|
||||||
"skrifa 0.39.0",
|
"skrifa 0.36.0",
|
||||||
"smol_str",
|
"smol_str 0.3.5",
|
||||||
"swash",
|
"swash",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
|
|
@ -1736,38 +1734,14 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling"
|
|
||||||
version = "0.20.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
|
||||||
dependencies = [
|
|
||||||
"darling_core 0.20.11",
|
|
||||||
"darling_macro 0.20.11",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.21.3",
|
"darling_core",
|
||||||
"darling_macro 0.21.3",
|
"darling_macro",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling_core"
|
|
||||||
version = "0.20.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
|
||||||
dependencies = [
|
|
||||||
"fnv",
|
|
||||||
"ident_case",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"strsim",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1784,24 +1758,13 @@ dependencies = [
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling_macro"
|
|
||||||
version = "0.20.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
|
||||||
dependencies = [
|
|
||||||
"darling_core 0.20.11",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.21.3",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
@ -1841,11 +1804,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_setters"
|
name = "derive_setters"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9"
|
checksum = "b7e6f6fa1f03c14ae082120b84b3c7fbd7b8588d924cf2d7c3daf9afd49df8b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.20.11",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
|
|
@ -2302,9 +2265,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixed_decimal"
|
name = "fixed_decimal"
|
||||||
|
|
@ -2406,6 +2369,15 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "font-types"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -2527,16 +2499,6 @@ dependencies = [
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "freedesktop_entry_parser"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4"
|
|
||||||
dependencies = [
|
|
||||||
"nom 7.1.3",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
|
|
@ -2972,14 +2934,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "harfrust"
|
name = "harfrust"
|
||||||
version = "0.4.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0caaee032384c10dd597af4579c67dee16650d862a9ccbe1233ff1a379abc07"
|
checksum = "75a4c970f1a00edc1626f1e3cc039492b15b73df28b9fff70f95404a571b4fae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"core_maths",
|
"core_maths",
|
||||||
"read-fonts 0.36.0",
|
"read-fonts 0.34.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3200,7 +3162,7 @@ dependencies = [
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"smol_str",
|
"smol_str 0.2.2",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"web-time",
|
"web-time",
|
||||||
"window_clipboard",
|
"window_clipboard",
|
||||||
|
|
@ -3835,7 +3797,7 @@ dependencies = [
|
||||||
"rgb",
|
"rgb",
|
||||||
"tiff",
|
"tiff",
|
||||||
"zune-core 0.5.1",
|
"zune-core 0.5.1",
|
||||||
"zune-jpeg 0.5.11",
|
"zune-jpeg 0.5.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4947,9 +4909,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify-debouncer-full"
|
name = "notify-debouncer-full"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078"
|
checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"file-id",
|
"file-id",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -5002,9 +4964,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
|
|
@ -6155,6 +6117,17 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "read-fonts"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8941f8e9d5f8ad3aebea330d01ac68c0167600eb31a86ecd86e97be4d13b51f5"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"core_maths",
|
||||||
|
"font-types 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "read-fonts"
|
name = "read-fonts"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
|
|
@ -6162,18 +6135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"
|
checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"font-types",
|
"font-types 0.10.1",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "read-fonts"
|
|
||||||
version = "0.36.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"core_maths",
|
|
||||||
"font-types",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6638,7 +6600,7 @@ version = "3.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.21.3",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
|
|
@ -6721,6 +6683,16 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "skrifa"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37004372610e83ee2a4c69c7d896b41f33da6a3dc1a4fe07dd9b2629a549b1dc"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"read-fonts 0.34.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "skrifa"
|
name = "skrifa"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
|
|
@ -6731,16 +6703,6 @@ dependencies = [
|
||||||
"read-fonts 0.35.0",
|
"read-fonts 0.35.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "skrifa"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"read-fonts 0.36.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
|
@ -6837,6 +6799,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smol_str"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
|
|
@ -7211,9 +7179,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.45"
|
version = "0.3.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
|
@ -7227,15 +7195,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.25"
|
version = "0.2.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
|
@ -7791,9 +7759,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.1+wasi-0.2.4"
|
version = "1.0.2+wasi-0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
@ -8811,7 +8779,7 @@ dependencies = [
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"sctk-adwaita",
|
"sctk-adwaita",
|
||||||
"smithay-client-toolkit 0.19.2",
|
"smithay-client-toolkit 0.19.2",
|
||||||
"smol_str",
|
"smol_str 0.2.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
@ -8848,9 +8816,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.46.0"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
|
|
@ -9245,18 +9213,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.35"
|
version = "0.8.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572"
|
checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.35"
|
version = "0.8.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22"
|
checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -9450,9 +9418,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.5.11"
|
version = "0.5.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2"
|
checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core 0.5.1",
|
"zune-core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
21
Cargo.toml
21
Cargo.toml
|
|
@ -4,7 +4,7 @@ version = "1.0.4"
|
||||||
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"] }
|
||||||
|
|
@ -76,6 +74,7 @@ default-features = false
|
||||||
features = [
|
features = [
|
||||||
"about",
|
"about",
|
||||||
"autosize",
|
"autosize",
|
||||||
|
"desktop",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
"tokio",
|
"tokio",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -107,7 +106,7 @@ 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"]
|
io-uring = ["compio/io-uring"]
|
||||||
|
|
|
||||||
764
src/app.rs
764
src/app.rs
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
mime_icon::mime_for_path,
|
mime_icon::mime_for_path,
|
||||||
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||||
};
|
};
|
||||||
|
use cosmic::iced::futures;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
|
|
|
||||||
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,
|
||||||
|
|
@ -200,23 +200,25 @@ pub async fn sync_to_disk(
|
||||||
written_files: Vec<PathBuf>,
|
written_files: Vec<PathBuf>,
|
||||||
target_dirs: std::collections::HashSet<PathBuf>,
|
target_dirs: std::collections::HashSet<PathBuf>,
|
||||||
) {
|
) {
|
||||||
use futures::{StreamExt, stream};
|
|
||||||
|
|
||||||
// Sync files to disk
|
// Sync files to disk
|
||||||
let file_stream = stream::iter(written_files.into_iter().map(|path| async move {
|
stream::iter(written_files.into_iter().map(|path| async move {
|
||||||
if let Ok(file) = compio::fs::OpenOptions::new().write(true).open(&path).await {
|
if let Ok(file) = compio::fs::OpenOptions::new().write(true).open(&path).await {
|
||||||
let _ = file.sync_all().await;
|
let _ = file.sync_all().await;
|
||||||
}
|
}
|
||||||
}));
|
}))
|
||||||
file_stream.buffer_unordered(32).collect::<Vec<_>>().await;
|
.buffer_unordered(32)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
|
||||||
// Sync directories to disk
|
// Sync directories to disk
|
||||||
let dir_stream = stream::iter(target_dirs.into_iter().map(|path| async move {
|
stream::iter(target_dirs.into_iter().map(|path| async move {
|
||||||
if let Ok(dir) = compio::fs::OpenOptions::new().read(true).open(&path).await {
|
if let Ok(dir) = compio::fs::OpenOptions::new().read(true).open(&path).await {
|
||||||
let _ = dir.sync_all().await;
|
let _ = dir.sync_all().await;
|
||||||
}
|
}
|
||||||
}));
|
}))
|
||||||
dir_stream.buffer_unordered(16).collect::<Vec<_>>().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 {
|
||||||
|
|
@ -957,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());
|
||||||
|
|
@ -1208,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;
|
||||||
|
|
@ -1262,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
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,10 @@ 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() {
|
if let Some(parent) = op.to.parent() {
|
||||||
target_dirs.insert(parent.to_path_buf());
|
target_dirs.insert(parent.to_path_buf());
|
||||||
|
|
@ -322,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)
|
||||||
|
|
|
||||||
651
src/tab.rs
651
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,11 +2440,11 @@ 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);
|
||||||
|
|
||||||
|
|
@ -2637,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.
|
||||||
|
|
@ -2768,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);
|
||||||
|
|
@ -2790,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2883,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3087,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 {
|
||||||
|
|
@ -3391,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 {
|
||||||
|
|
@ -3420,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3512,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 => {
|
||||||
|
|
@ -3532,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);
|
||||||
}
|
}
|
||||||
|
|
@ -3634,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 => {
|
||||||
|
|
@ -3825,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) => {
|
||||||
|
|
@ -3870,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();
|
||||||
|
|
@ -3935,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
|
||||||
|
|
@ -3990,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);
|
||||||
|
|
@ -4099,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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4136,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) => {
|
||||||
|
|
@ -4276,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() {
|
||||||
|
|
@ -4317,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()) {
|
||||||
|
|
@ -4531,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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4862,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]);
|
||||||
|
|
@ -5913,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);
|
||||||
|
|
@ -5939,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:///" => {
|
||||||
|
|
@ -6250,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