diff --git a/Cargo.lock b/Cargo.lock index 56ebce6..2936ed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ "accesskit_consumer", "atspi-common", "serde", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -72,7 +72,7 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -109,9 +109,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ "cipher", "cpubits", @@ -172,9 +172,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -198,7 +198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.11.1", + "bitflags 2.13.0", "cc", "jni", "libc", @@ -277,9 +277,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "apply" @@ -304,9 +304,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -330,15 +330,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "as-slice" @@ -373,7 +367,7 @@ dependencies = [ "serde_repr", "tokio", "url", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -393,7 +387,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -579,11 +573,11 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zbus 5.15.0", + "zbus 5.16.0", "zbus-lockstep", "zbus-lockstep-macros", "zbus_names 4.3.2", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -594,14 +588,14 @@ checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" dependencies = [ "atspi-common", "serde", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] name = "auto_enums" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03" +checksum = "2e4487600931c9a89f8db7ffbdf3fbdd45bb7bd85e26861f659a463cd0dff966" dependencies = [ "derive_utils", "proc-macro2", @@ -611,9 +605,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "av-scenechange" @@ -702,9 +696,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -735,9 +729,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", "zeroize", @@ -776,9 +770,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "bytes", "cfg_aliases", @@ -786,9 +780,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -805,13 +799,13 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -825,15 +819,15 @@ dependencies = [ [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "by_address" @@ -875,9 +869,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bzip2" @@ -904,7 +898,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "polling", "rustix 1.1.4", "slab", @@ -925,14 +919,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -948,9 +942,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ "smallvec", "target-lexicon", @@ -970,9 +964,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -984,11 +978,11 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -1004,7 +998,6 @@ dependencies = [ [[package]] name = "clipboard_macos" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "objc", "objc-foundation", @@ -1014,27 +1007,17 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "dnd", "mime 0.1.0", "smithay-clipboard", ] -[[package]] -name = "clipboard_x11" -version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" -dependencies = [ - "thiserror 1.0.69", - "x11rb", -] - [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "cocoa" @@ -1116,9 +1099,9 @@ dependencies = [ [[package]] name = "compio-buf" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00d719dbd8c602ab0d25d219cbc6b517008858de7a8d6c51b4dc95aefff4dce" +checksum = "b51a2c35873865376ed4cdb6cfeb602b6cf569815f017ddd7bd8f86ad49b34c7" dependencies = [ "arrayvec", "bytes", @@ -1238,9 +1221,9 @@ dependencies = [ [[package]] name = "configparser" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" +checksum = "b46dec724fd22199ebde05033a0cbae453bc3b1ecff11eb6a6bb3eec4b90c6a4" [[package]] name = "const-oid" @@ -1310,7 +1293,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "libc", ] @@ -1338,10 +1321,22 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=160b086#160b086abe03cd34a8a375d7fbe47b24308d1f38" dependencies = [ - "bitflags 2.11.1", - "cosmic-protocols", + "bitflags 2.13.0", + "cosmic-protocols 0.2.0", + "libc", + "smithay-client-toolkit", + "wayland-client", + "wayland-protocols", +] + +[[package]] +name = "cosmic-client-toolkit" +version = "0.2.0" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=c253ec1#c253ec1d6804afbcdf250f5cc37ae1194bba7bd2" +dependencies = [ + "bitflags 2.13.0", + "cosmic-protocols 0.2.0 (git+https://github.com/pop-os/cosmic-protocols?rev=c253ec1)", "libc", "smithay-client-toolkit", "wayland-client", @@ -1351,7 +1346,6 @@ dependencies = [ [[package]] name = "cosmic-config" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1361,18 +1355,17 @@ dependencies = [ "iced_futures", "known-folders", "notify", - "ron 0.12.1", + "ron 0.12.2", "serde", "tokio", "tracing", "xdg", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] name = "cosmic-config-derive" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "quote", "syn", @@ -1380,14 +1373,14 @@ dependencies = [ [[package]] name = "cosmic-files" -version = "1.0.13" +version = "1.2.0" dependencies = [ "anyhow", "atomic_float", "bstr", "bzip2", "compio", - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0 (git+https://github.com/pop-os/cosmic-protocols?rev=c253ec1)", "cosmic-mime-apps", "dirs 6.0.0", "fastrand", @@ -1406,7 +1399,7 @@ dependencies = [ "jiff-icu", "jxl-oxide", "libc", - "libcosmic", + "libcosmic-yoda", "log", "lzma-rust2", "md-5", @@ -1425,7 +1418,8 @@ dependencies = [ "rust-embed", "rustc-hash 2.1.2", "serde", - "shlex", + "sha2 0.10.9", + "shlex 1.3.0", "slotmap", "tar", "tempfile", @@ -1448,7 +1442,7 @@ dependencies = [ [[package]] name = "cosmic-files-applet" -version = "1.0.13" +version = "1.2.0" dependencies = [ "cosmic-files", "log", @@ -1458,12 +1452,11 @@ dependencies = [ [[package]] name = "cosmic-freedesktop-icons" version = "0.4.0" -source = "git+https://github.com/pop-os/freedesktop-icons#9c562fe3ecf03241a46a60c0078cd6ea10bd75ce" dependencies = [ "bstr", "btoi", "memchr", - "memmap2 0.9.10", + "memmap2 0.9.11", "thiserror 2.0.18", "tracing", "xdg", @@ -1471,8 +1464,8 @@ dependencies = [ [[package]] name = "cosmic-mime-apps" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-mime-apps.git#673f3758920a7b20d307d356d586deef714df6b0" +version = "0.2.0" +source = "git+https://github.com/pop-os/cosmic-mime-apps.git#8fc81e8857e46c018ea21ab985aaf30ac853c007" dependencies = [ "freedesktop-desktop-entry", "mime 0.3.17", @@ -1483,9 +1476,22 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=160b086#160b086abe03cd34a8a375d7fbe47b24308d1f38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "cosmic-protocols" +version = "0.2.0" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=c253ec1#c253ec1d6804afbcdf250f5cc37ae1194bba7bd2" +dependencies = [ + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1497,7 +1503,6 @@ dependencies = [ [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#716da6d6af0b252e2f78aba2ad72ee19ae0241e0" dependencies = [ "cosmic-config", "ron 0.11.0", @@ -1509,19 +1514,16 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon" -version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#507e342c21d3ce6ae41b1d4f3fa2f0ad5ee23e75" +version = "0.1.1-yoda.1" dependencies = [ - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] name = "cosmic-text" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "fontdb", "harfrust", "linebender_resource_handle", @@ -1529,7 +1531,7 @@ dependencies = [ "rangemap", "rustc-hash 2.1.2", "self_cell", - "skrifa", + "skrifa 0.40.0", "smol_str", "swash", "sys-locale", @@ -1537,20 +1539,21 @@ dependencies = [ "unicode-linebreak", "unicode-script", "unicode-segmentation", + "unicode-width", ] [[package]] name = "cosmic-theme" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "almost", "configparser", "cosmic-config", "csscolorparser", "dirs 6.0.0", + "hex_color", "palette", - "ron 0.12.1", + "ron 0.12.2", "serde", "serde_json", "thiserror 2.0.18", @@ -1632,7 +1635,6 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryoglyph" version = "0.1.0" -source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a" dependencies = [ "cosmic-text", "etagere", @@ -1653,9 +1655,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -1678,15 +1680,6 @@ dependencies = [ "uncased", ] -[[package]] -name = "ctor" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" -dependencies = [ - "dtor", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -1783,13 +1776,44 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -1832,9 +1856,9 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", "zeroize", ] @@ -1908,7 +1932,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -1916,9 +1940,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1937,9 +1961,8 @@ dependencies = [ [[package]] name = "dnd" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "mime 0.1.0", "raw-window-handle", "smithay-client-toolkit", @@ -1964,52 +1987,6 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" version = "0.1.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" - -[[package]] -name = "drm" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" -dependencies = [ - "bitflags 2.11.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" -dependencies = [ - "drm-sys", - "rustix 0.38.44", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - -[[package]] -name = "dtor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" [[package]] name = "dyn-clone" @@ -2019,9 +1996,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "endi" @@ -2052,18 +2029,19 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -2206,9 +2184,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5b2eef6fafbf69f877e55509ce5b11a760690ac9700a2921be067aa6afaef6" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", @@ -2370,7 +2348,7 @@ checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.10", + "memmap2 0.9.11", "slotmap", "tinyvec", "ttf-parser", @@ -2558,9 +2536,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -2581,16 +2559,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix 1.1.4", - "windows-link 0.2.1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -2616,16 +2584,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", "wasm-bindgen", ] @@ -2722,7 +2688,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -2831,7 +2797,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -2842,7 +2808,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -2879,10 +2845,10 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "core_maths", - "read-fonts", + "read-fonts 0.37.0", "smallvec", ] @@ -2942,6 +2908,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0" +dependencies = [ + "arrayvec", + "rand 0.8.6", + "serde", +] + [[package]] name = "hexf-parse" version = "0.2.1" @@ -2959,9 +2936,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -3060,7 +3037,6 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "dnd", "iced_accessibility", @@ -3081,7 +3057,6 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "accesskit", "accesskit_winit", @@ -3090,11 +3065,10 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytes", - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "dnd", "glam", "lilt", @@ -3107,6 +3081,7 @@ dependencies = [ "serde", "smol_str", "thiserror 2.0.18", + "unicode-segmentation", "web-time", "window_clipboard", ] @@ -3114,7 +3089,6 @@ dependencies = [ [[package]] name = "iced_debug" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "iced_core", "iced_futures", @@ -3124,7 +3098,6 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "futures", "iced_core", @@ -3138,9 +3111,8 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "cosmic-text", "half", @@ -3159,7 +3131,6 @@ dependencies = [ [[package]] name = "iced_program" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "iced_graphics", "iced_runtime", @@ -3168,7 +3139,6 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -3180,10 +3150,9 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "bytes", - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "dnd", "iced_core", "iced_futures", @@ -3195,7 +3164,6 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ "bytemuck", "cosmic-text", @@ -3212,12 +3180,10 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ - "as-raw-xcb-connection", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "cryoglyph", "futures", "glam", @@ -3231,21 +3197,18 @@ dependencies = [ "rustc-hash 2.1.2", "rustix 0.38.44", "thiserror 2.0.18", - "tiny-xlib", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-sys", "wgpu", - "x11rb", ] [[package]] name = "iced_widget" version = "0.14.2" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "dnd", "iced_renderer", "iced_runtime", @@ -3261,9 +3224,8 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" dependencies = [ - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "cursor-icon", "dnd", "iced_debug", @@ -3362,9 +3324,9 @@ checksum = "846b0857ca091204be3c874bc93daaf89d4777e8d2d20b0d3ffe8f671d98014b" [[package]] name = "icu_collator" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b521b92a2666061ddda902769d8a4cf730b5c9529a845cc1b69770b12a6c9a71" +checksum = "8bbdf98e5e0aa827770acee171ebb568aaab975a36b56afdb5fd0e2f525750bb" dependencies = [ "icu_collator_data", "icu_collections", @@ -3689,12 +3651,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f2f8aeca682d874a5247084aa4fb7d1cef9ba45d889c21209a8818dcaaa0ec9" -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -3724,9 +3680,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" dependencies = [ "crossbeam-deque", "globset", @@ -3789,9 +3745,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" [[package]] name = "indexmap" @@ -3827,11 +3783,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "inotify-sys", "libc", ] @@ -3886,11 +3842,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62" +checksum = "9080b15e63775b9a2ac7dca720f7050a8b955e092ea0f6020a4a80f69998cdc0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "libc", ] @@ -3954,17 +3910,18 @@ checksum = "2ceaf4c6c48465bead8cb6a0b7c4ee0c86ecbb31239032b9c66ab9a08d2f3ee1" [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link 0.2.1", ] [[package]] @@ -3980,9 +3937,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -4074,13 +4031,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -4137,9 +4093,9 @@ dependencies = [ [[package]] name = "jxl-grid" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e0ef92d5d60e76bf41098e57e985f523185e08fad54268da448637feca6989" +checksum = "01671307879a033bfa52e6e8784b941aca770b3f3a7d33830b455b6844f793fb" dependencies = [ "tracing", ] @@ -4176,9 +4132,9 @@ dependencies = [ [[package]] name = "jxl-modular" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da758b2f989aafd9eeb39489fe43d7be5a3a0d2ad61cf1bad705eb6990a6053c" +checksum = "2a2f045b24c738dd91d482be385512b512721ae08a671bd4b27bf1c47f215235" dependencies = [ "jxl-bitstream", "jxl-coding", @@ -4190,9 +4146,9 @@ dependencies = [ [[package]] name = "jxl-oxide" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8ecd2678ed70c1eda42b811ccb2e25ab836edeb18e7f1178c1f917ed36b772" +checksum = "d36c662923f47586880211f3bc7c0d83fb3a9b410d278c7bde93450748abeef3" dependencies = [ "brotli-decompressor", "bytemuck", @@ -4220,9 +4176,9 @@ dependencies = [ [[package]] name = "jxl-render" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa0c3100918bd3c41bb0f8ce1f4f1664e48f3032ff8eeab0d6a2cfc3276f462d" +checksum = "d34386bfdb6a19b5a30cc9beb4d475d537422c31ae8c39bb69640fcce3fcaf19" dependencies = [ "bytemuck", "jxl-bitstream", @@ -4279,7 +4235,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "serde", ] @@ -4311,9 +4267,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -4321,11 +4277,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285efcf12ef41bec907b3000d5ffaeb54191d4d9d83c0d6157e6cbc2db255e64" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", ] @@ -4356,12 +4312,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lebe" version = "0.5.3" @@ -4370,9 +4320,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libbz2-rs-sys" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fc329e1457d97a9d58a4e2ca49e3be572431a7e096008efc2e3a3c19d428f4" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" @@ -4381,14 +4331,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "libcosmic" -version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic.git#4fab6c777dbd1a023440f08fb2b729c86492366c" +name = "libcosmic-yoda" +version = "0.1.0-yoda.2" dependencies = [ "apply", "ashpd 0.12.3", "auto_enums", - "cosmic-client-toolkit", + "cosmic-client-toolkit 0.2.0", "cosmic-config", "cosmic-freedesktop-icons", "cosmic-settings-config", @@ -4396,6 +4345,7 @@ dependencies = [ "cosmic-theme", "css-color", "derive_setters", + "enumflags2", "float-cmp 0.10.0", "freedesktop-desktop-entry", "futures", @@ -4422,7 +4372,7 @@ dependencies = [ "rust-embed", "rustix 1.1.4", "serde", - "shlex", + "shlex 1.3.0", "slotmap", "taffy", "thiserror 2.0.18", @@ -4430,14 +4380,14 @@ dependencies = [ "tracing", "unicode-segmentation", "url", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] name = "libfuzzer-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ "arbitrary", "cc", @@ -4461,14 +4411,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.7.5", + "redox_syscall 0.8.1", ] [[package]] @@ -4492,12 +4442,6 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4540,9 +4484,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loom" @@ -4627,23 +4571,25 @@ dependencies = [ [[package]] name = "lzma-rust2" -version = "0.16.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +checksum = "ce716bf1a316f47a280fc76295f6495b5bea4752bca01c3b3885e101b1c23c02" dependencies = [ - "sha2", + "sha2 0.11.0", ] [[package]] name = "mac-notification-sys" -version = "0.6.12" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a16783dd1a47849b8c8133c9cd3eb2112cfbc6901670af3dba47c8bbfb07d3" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" dependencies = [ "cc", + "log", "objc2 0.6.4", "objc2-foundation 0.3.2", "time", + "uuid", ] [[package]] @@ -4686,9 +4632,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -4701,9 +4647,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -4723,7 +4669,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block", "core-graphics-types 0.2.0", "foreign-types", @@ -4735,7 +4681,6 @@ dependencies = [ [[package]] name = "mime" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "smithay-clipboard", ] @@ -4774,9 +4719,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -4808,7 +4753,7 @@ checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "codespan-reporting", @@ -4832,7 +4777,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys", @@ -4868,7 +4813,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -4915,7 +4860,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "fsevent-sys", "inotify", "kqueue", @@ -4942,16 +4887,16 @@ dependencies = [ [[package]] name = "notify-rust" -version = "4.17.0" +version = "4.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ff2e74231b72c832d82982193b417f230945be6bdb5575b251d941d31adb00" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" dependencies = [ "futures-lite", "log", "mac-notification-sys", "serde", "tauri-winrt-notification", - "zbus 5.15.0", + "zbus 5.16.0", ] [[package]] @@ -4960,7 +4905,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -4984,9 +4929,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -5112,7 +5057,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -5128,7 +5073,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", @@ -5141,7 +5086,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5153,7 +5098,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "dispatch2", "objc2 0.6.4", @@ -5165,7 +5110,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "objc2-core-foundation", ] @@ -5188,7 +5133,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2-core-foundation", "objc2-core-graphics", ] @@ -5205,7 +5150,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -5217,7 +5162,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -5230,7 +5175,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5242,7 +5187,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5255,7 +5200,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -5284,13 +5229,12 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.5" +version = "5.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a" dependencies = [ "is-wsl", "libc", - "pathdiff", ] [[package]] @@ -5301,9 +5245,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orbclient" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" dependencies = [ "libc", "libredox", @@ -5448,12 +5392,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "pbkdf2" version = "0.13.0" @@ -5565,18 +5503,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -5637,7 +5575,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -5717,16 +5655,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -5786,7 +5714,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "chrono", "flate2", "procfs-core", @@ -5799,7 +5727,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "chrono", "hex", ] @@ -5884,9 +5812,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -6061,6 +5989,16 @@ dependencies = [ "font-types", ] +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "recently-used-xbel" version = "1.2.0" @@ -6077,22 +6015,32 @@ dependencies = [ "url", ] +[[package]] +name = "redox_event" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c07d0d6d291e3a951bd847b1cd4af32fc6243d64116cf7702838c02797688b7" +dependencies = [ + "bitflags 2.13.0", + "libredox", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "redox_syscall" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -6139,9 +6087,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -6162,9 +6110,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "renderdoc-sys" @@ -6229,7 +6177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", - "bitflags 2.11.1", + "bitflags 2.13.0", "serde", "serde_derive", "unicode-ident", @@ -6237,11 +6185,11 @@ dependencies = [ [[package]] name = "ron" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" +checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "once_cell", "serde", "serde_derive", @@ -6285,7 +6233,7 @@ version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ - "sha2", + "sha2 0.10.9", "walkdir", ] @@ -6316,7 +6264,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -6329,7 +6277,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -6348,7 +6296,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "core_maths", "log", @@ -6413,7 +6361,7 @@ checksum = "1dd3accc0f3f4bbaf2c9e1957a030dc582028130c67660d44c0a0345a22ca69b" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.10", + "memmap2 0.9.11", "smithay-client-toolkit", "tiny-skia", ] @@ -6462,9 +6410,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap 2.14.0", "itoa", @@ -6496,9 +6444,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64", "bs58", @@ -6516,9 +6464,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -6559,6 +6507,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -6574,6 +6533,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -6637,7 +6602,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" +dependencies = [ + "bytemuck", + "read-fonts 0.39.2", ] [[package]] @@ -6657,9 +6632,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smithay-client-toolkit" @@ -6667,14 +6642,14 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", - "memmap2 0.9.10", + "memmap2 0.9.11", "pkg-config", "rustix 1.1.4", "thiserror 2.0.18", @@ -6694,7 +6669,6 @@ dependencies = [ [[package]] name = "smithay-clipboard" version = "0.8.0" -source = "git+https://github.com/pop-os/smithay-clipboard?tag=sctk-0.20#859b02c88f45c554049a67c6ddeec1692ce0e20b" dependencies = [ "libc", "raw-window-handle", @@ -6714,9 +6688,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -6725,31 +6699,26 @@ dependencies = [ [[package]] name = "softbuffer" version = "0.4.1" -source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#c2b2c19ddb38ff17495643699f97cb1f2064a1be" dependencies = [ - "as-raw-xcb-connection", "bytemuck", "cfg_aliases", "cocoa", "core-graphics", - "drm", "fastrand", "foreign-types", "js-sys", "log", - "memmap2 0.9.10", + "memmap2 0.9.11", "objc", "raw-window-handle", "redox_syscall 0.5.18", "rustix 0.38.44", - "tiny-xlib", "wasm-bindgen", "wayland-backend", "wayland-client", "wayland-sys", "web-sys", "windows-sys 0.52.0", - "x11rb", ] [[package]] @@ -6767,7 +6736,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -6815,20 +6784,20 @@ dependencies = [ [[package]] name = "swash" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +checksum = "0811b01ca2c4e8718760713911feaf4675c24f94e50530a015ec646cfb622f7c" dependencies = [ - "skrifa", + "skrifa 0.42.1", "yazi", "zeno", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -6892,9 +6861,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -6903,9 +6872,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tauri-winrt-notification" @@ -6932,7 +6901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -6949,9 +6918,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f46bf474f0a4afebf92f076d54fd5e63423d9438b8c278a3d2ccb0f47f7cdb3" +checksum = "9b9c218384242b5c89b68303ab6f6fc53a312d923f0c14dc6bb860c6aeee40f1" dependencies = [ "env_logger", "test-log-macros", @@ -6960,9 +6929,9 @@ dependencies = [ [[package]] name = "test-log-core" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d4d41320b48bc4a211a9021678fcc0c99569b594ea31c93735b8e517102b4c" +checksum = "c26ef8b00e4d382e59f6a8ddb3cd790b3a5bb29f21a358a9a69ea2f29f13f27b" dependencies = [ "proc-macro2", "quote", @@ -6971,9 +6940,9 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9beb9249a81e430dffd42400a49019bcf548444f1968ff23080a625de0d4d320" +checksum = "944ad38adcbb71eaa682c56bceeb079e4ca82b4b3edc2a0fde5cb297b77dac8d" dependencies = [ "syn", "test-log-core", @@ -7070,12 +7039,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "0e48db7b415311b615f910b3dcaa4557bcd4bf1982379c95c223fd8c2a20e210" dependencies = [ "deranged", - "itoa", "js-sys", "num-conv", "powerfmt", @@ -7086,15 +7054,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -7126,19 +7094,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tiny-xlib" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" -dependencies = [ - "as-raw-xcb-connection", - "ctor", - "libloading", - "pkg-config", - "tracing", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -7239,9 +7194,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime", @@ -7328,14 +7283,15 @@ dependencies = [ [[package]] name = "trash" -version = "5.2.2" -source = "git+https://github.com/jackpot51/trash-rs.git?branch=cosmic#a225f753a88e722aeeb27bb2fb00144739911035" +version = "5.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7602e0c7d66ec2d92a8c917219fbc7894039efa2063b9064260110828a356f46" dependencies = [ "chrono", "libc", "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", "once_cell", "percent-encoding", "scopeguard", @@ -7375,9 +7331,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "uds_windows" @@ -7468,9 +7424,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-vo" @@ -7484,12 +7440,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "url" version = "2.5.8" @@ -7556,10 +7506,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -7622,27 +7573,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -7653,9 +7595,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -7663,9 +7605,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7673,9 +7615,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -7686,47 +7628,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "wasmtimer" version = "0.4.3" @@ -7761,7 +7669,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "rustix 1.1.4", "wayland-backend", "wayland-scanner", @@ -7773,7 +7681,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cursor-icon", "wayland-backend", ] @@ -7791,11 +7699,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.12" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -7808,7 +7716,7 @@ version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7821,7 +7729,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7834,7 +7742,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7847,7 +7755,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7872,7 +7780,7 @@ version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc1846eb04c49182e04f4a099e2a830a2b745610bbc1d61246e206f29c7000a0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "downcast-rs", "rustix 1.1.4", "wayland-backend", @@ -7893,9 +7801,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -7924,7 +7832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" dependencies = [ "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "cfg-if", "cfg_aliases", @@ -7956,7 +7864,7 @@ dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "cfg_aliases", "document-features", @@ -8016,7 +7924,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.11.1", + "bitflags 2.13.0", "block", "bytemuck", "cfg-if", @@ -8060,7 +7968,7 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytemuck", "js-sys", "log", @@ -8107,12 +8015,10 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c" dependencies = [ "clipboard-win", "clipboard_macos", "clipboard_wayland", - "clipboard_x11", "dnd", "mime 0.1.0", "raw-window-handle", @@ -8614,9 +8520,8 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg_aliases", "cursor-icon", "dpi", @@ -8634,16 +8539,14 @@ dependencies = [ "winit-wayland", "winit-web", "winit-win32", - "winit-x11", ] [[package]] name = "winit-android" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "android-activity", - "bitflags 2.11.1", + "bitflags 2.13.0", "dpi", "ndk", "raw-window-handle", @@ -8655,9 +8558,8 @@ dependencies = [ [[package]] name = "winit-appkit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "dispatch2", "dpi", @@ -8677,24 +8579,21 @@ dependencies = [ [[package]] name = "winit-common" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "memmap2 0.9.10", + "memmap2 0.9.11", "objc2 0.6.4", "objc2-core-foundation", "smol_str", "tracing", "winit-core", - "x11-dl", "xkbcommon-dl", ] [[package]] name = "winit-core" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cursor-icon", "dpi", "keyboard-types", @@ -8706,14 +8605,13 @@ dependencies = [ [[package]] name = "winit-orbital" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dpi", "libredox", "orbclient", "raw-window-handle", - "redox_syscall 0.7.5", + "redox_event", "smol_str", "tracing", "winit-core", @@ -8722,9 +8620,8 @@ dependencies = [ [[package]] name = "winit-uikit" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block2 0.6.2", "dispatch2", "dpi", @@ -8742,15 +8639,14 @@ dependencies = [ [[package]] name = "winit-wayland" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "ahash", - "bitflags 2.11.1", + "bitflags 2.13.0", "calloop", "cursor-icon", "dpi", "libc", - "memmap2 0.9.10", + "memmap2 0.9.11", "raw-window-handle", "rustix 1.1.4", "sctk-adwaita", @@ -8768,10 +8664,9 @@ dependencies = [ [[package]] name = "winit-web" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ "atomic-waker", - "bitflags 2.11.1", + "bitflags 2.13.0", "concurrent-queue", "cursor-icon", "dpi", @@ -8790,9 +8685,8 @@ dependencies = [ [[package]] name = "winit-win32" version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cursor-icon", "dpi", "raw-window-handle", @@ -8803,132 +8697,21 @@ dependencies = [ "winit-core", ] -[[package]] -name = "winit-x11" -version = "0.31.0-beta.2" -source = "git+https://github.com/pop-os/winit.git?tag=cosmic-0.14#261cda54017f98a12dc55569c864430fe6770366" -dependencies = [ - "bitflags 2.11.1", - "bytemuck", - "calloop", - "cursor-icon", - "dpi", - "libc", - "percent-encoding", - "raw-window-handle", - "rustix 1.1.4", - "smol_str", - "tracing", - "winit-common", - "winit-core", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "write16" version = "1.0.0" @@ -8944,39 +8727,6 @@ dependencies = [ "either", ] -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 1.1.4", - "x11rb-protocol", - "xcursor", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - [[package]] name = "xattr" version = "1.6.1" @@ -9051,7 +8801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" dependencies = [ "libc", - "memmap2 0.9.10", + "memmap2 0.9.11", "xkeysym", ] @@ -9062,7 +8812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" dependencies = [ "libc", - "memmap2 0.9.10", + "memmap2 0.9.11", "xkeysym", ] @@ -9072,7 +8822,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dlib", "log", "once_cell", @@ -9135,9 +8885,9 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -9196,9 +8946,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -9225,9 +8975,9 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "winnow", - "zbus_macros 5.15.0", + "zbus_macros 5.16.0", "zbus_names 4.3.2", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -9237,7 +8987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" dependencies = [ "zbus_xml", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -9251,7 +9001,7 @@ dependencies = [ "syn", "zbus-lockstep", "zbus_xml", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -9269,17 +9019,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "zbus_names 4.3.2", - "zvariant 5.11.0", - "zvariant_utils 3.3.1", + "zvariant 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -9301,7 +9051,7 @@ checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", "winnow", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -9313,7 +9063,7 @@ dependencies = [ "quick-xml 0.39.4", "serde", "zbus_names 4.3.2", - "zvariant 5.11.0", + "zvariant 5.12.0", ] [[package]] @@ -9324,18 +9074,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -9365,9 +9115,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -9416,7 +9166,7 @@ dependencies = [ "crc32fast", "deflate64", "flate2", - "getrandom 0.4.2", + "getrandom 0.4.3", "hmac", "indexmap 2.14.0", "lzma-rust2", @@ -9433,9 +9183,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" [[package]] name = "zmij" @@ -9537,17 +9287,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", "url", "winnow", - "zvariant_derive 5.11.0", - "zvariant_utils 3.3.1", + "zvariant_derive 5.12.0", + "zvariant_utils 3.4.0", ] [[package]] @@ -9565,15 +9315,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", - "zvariant_utils 3.3.1", + "zvariant_utils 3.4.0", ] [[package]] @@ -9589,9 +9339,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", @@ -9599,3 +9349,22 @@ dependencies = [ "syn", "winnow", ] + +[[patch.unused]] +name = "dnd" +version = "0.1.0" +source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#af8e6a872d8ac4956e044b9cb05a615a48261a57" + +[[patch.unused]] +name = "mime" +version = "0.1.0" +source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#af8e6a872d8ac4956e044b9cb05a615a48261a57" + +[[patch.unused]] +name = "window_clipboard" +version = "0.4.1" +source = "git+https://forge.aditua.com/leyoda/window_clipboard.git?branch=yoda-x11-optional#af8e6a872d8ac4956e044b9cb05a615a48261a57" + +[[patch.unused]] +name = "winit-x11" +version = "0.31.0-beta.2" diff --git a/Cargo.toml b/Cargo.toml index 9059e13..a6b6dde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-files" -version = "1.0.13" +version = "1.2.0" authors = ["Jeremy Soller "] edition = "2024" license = "GPL-3.0-only" @@ -11,7 +11,7 @@ anyhow = "1" jiff = "0.2" jiff-icu = "0.2" icu = { version = "2.2.0", features = ["compiled_data"] } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true } cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true } dirs = "6.0.0" gio = { version = "0.21", optional = true } @@ -33,7 +33,7 @@ shlex = { version = "1.3" } tempfile = "3" tikv-jemallocator = { version = "0.6", optional = true } tokio = { version = "1", features = ["process", "sync"] } -trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" } +trash = "5.2.6" url = "2.5" walkdir = "2.5.0" wayland-client = { version = "0.31.14", optional = true } @@ -56,6 +56,7 @@ slotmap = "1.1.1" recently-used-xbel = "1.2.0" zip = "8" md-5 = "0.10.6" +sha2 = "0.10" png = "0.18" jxl-oxide = { version = "0.12.5", features = ["image"] } num_cpus = "1.17.0" @@ -73,8 +74,9 @@ version = "0.18" default-features = false features = ["fs", "io", "macros", "polling", "runtime"] -[dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic.git" +# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch). +[dependencies.libcosmic-yoda] +path = "../libcosmic" default-features = false #TODO: a11y feature crashes features = [ @@ -83,7 +85,7 @@ features = [ "autosize", "multi-window", "tokio", - "winit", + "wayland", "surface-message", ] @@ -111,15 +113,15 @@ default = [ "wayland", "wgpu", ] -dbus-config = ["libcosmic/dbus-config"] -desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"] +dbus-config = ["libcosmic-yoda/dbus-config"] +desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"] desktop-applet = [] gvfs = ["dep:gio", "dep:glib"] io-uring = ["compio/io-uring"] jemalloc = ["dep:tikv-jemallocator"] notify = ["dep:notify-rust"] -wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"] -wgpu = ["libcosmic/wgpu"] +wayland = ["libcosmic-yoda/wayland", "dep:cctk", "dep:wayland-client"] +wgpu = ["libcosmic-yoda/wgpu"] [profile.dev] opt-level = 1 @@ -145,20 +147,23 @@ fastrand = "2" test-log = "0.2" tokio = { version = "1", features = ["rt", "macros"] } -# [patch.'https://github.com/pop-os/cosmic-text'] -# cosmic-text = { path = "../cosmic-text" } +# Yoda fork — libcosmic dep is now a direct path dep (libcosmic-yoda above), +# no [patch] block needed anymore. Keeping the block below would be a no-op +# since nothing in the dep graph still asks for pop-os/libcosmic.git. -# [patch.'https://github.com/pop-os/libcosmic'] -# libcosmic = { path = "../libcosmic" } -# cosmic-config = { path = "../libcosmic/cosmic-config" } -# cosmic-theme = { path = "../libcosmic/cosmic-theme" } -# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" } -# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" } -# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" } +# Yoda wayland cut: redirect window_clipboard (x11 gated behind opt-in +# feature) to our public Forgejo fork and cosmic-text to the local checkout +# carrying PR#503 plus the EAW monospace width fix. The window_clipboard patch +# is needed to consolidate the +# upstream pop-os/libcosmic chain (pulled by cosmic-settings-daemon) onto +# the same fork iced uses, otherwise cargo compiles two versions. +[patch.'https://github.com/pop-os/window_clipboard.git'] +window_clipboard = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } +dnd = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } +mime = { git = "https://forge.aditua.com/leyoda/window_clipboard.git", branch = "yoda-x11-optional" } - -# [patch.'https://github.com/pop-os/smithay-clipboard'] -# smithay-clipboard = { path = "../smithay-clipboard" } +[patch.'https://github.com/pop-os/cosmic-text.git'] +cosmic-text = { path = "../cosmic-text" } [workspace] members = ["cosmic-files-applet"] diff --git a/cosmic-files-applet/Cargo.toml b/cosmic-files-applet/Cargo.toml index 8fcf2bd..b4f5bcc 100644 --- a/cosmic-files-applet/Cargo.toml +++ b/cosmic-files-applet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-files-applet" -version = "1.0.13" +version = "1.2.0" edition = "2024" [dependencies] diff --git a/debian/changelog b/debian/changelog index e4697d2..0dbce82 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,33 @@ +cosmic-files (1.2.0) noble; urgency=medium + + * Epoch 1.2.0 version update + + -- Jeremy Soller Tue, 30 Jun 2026 12:21:07 -0600 + +cosmic-files (1.1.0) noble; urgency=medium + + * Epoch 1.1.0 version update + + -- Jeremy Soller Tue, 23 Jun 2026 08:47:18 -0600 + +cosmic-files (1.0.16) noble; urgency=medium + + * Epoch 1.0.16 version update + + -- Jeremy Soller Tue, 09 Jun 2026 10:26:05 -0600 + +cosmic-files (1.0.15) noble; urgency=medium + + * Epoch 1.0.15 version update + + -- Jeremy Soller Tue, 02 Jun 2026 11:02:29 -0600 + +cosmic-files (1.0.14) noble; urgency=medium + + * Epoch 1.0.14 version update + + -- Jeremy Soller Tue, 26 May 2026 08:30:59 -0600 + cosmic-files (1.0.13) noble; urgency=medium * Epoch 1.0.13 version update diff --git a/docs/local-performance-and-portal-notes.md b/docs/local-performance-and-portal-notes.md new file mode 100644 index 0000000..a3dabbe --- /dev/null +++ b/docs/local-performance-and-portal-notes.md @@ -0,0 +1,78 @@ +# Local performance and portal notes + +Date: 2026-05-05 + +This repository carries a local patch aimed at improving initial directory +display latency in large folders, plus a system-level workaround for a COSMIC +portal FileChooser crash observed with Firefox and Chromium. + +## Directory listing latency + +The slow path was the initial construction of `Item` values in `src/tab.rs`. +On a test folder with about 2000 entries, raw filesystem enumeration and stat +calls completed in a few milliseconds, while COSMIC Files took multiple seconds +before showing the directory. + +The local patch keeps initial item construction cheap: + +- directory child counts are no longer computed synchronously in + `item_from_entry` and `item_from_gvfs_info`; +- initial MIME detection uses extension-based `mime_guess`; +- regular files use a generic file icon during the initial scan instead of + resolving the full MIME icon immediately. + +This keeps folders and `.desktop` files special-cased, while avoiding expensive +per-file work for ordinary files during first paint. + +Measured locally during investigation: + +- before the MIME/icon changes: about 3.1 seconds for `~/Téléchargements`; +- after avoiding full MIME icon resolution during scan: below the temporary + 100 ms perf-log threshold for the same folder. + +## File chooser portal workaround + +Firefox and Chromium were failing to open `Save As` on the first attempt because +`xdg-desktop-portal-cosmic` crashed while handling `FileChooser`. + +Logs showed: + +```text +Backend call failed: Remote peer disconnected +xdg-desktop-portal-cosmic ... status=11/SEGV +``` + +The working local system workaround is to remove +`org.freedesktop.impl.portal.FileChooser` from: + +```text +/usr/share/xdg-desktop-portal/portals/cosmic.portal +``` + +so the file chooser falls back to GTK. The resulting local `cosmic.portal` +keeps COSMIC for the other interfaces: + +```ini +[portal] +DBusName=org.freedesktop.impl.portal.desktop.cosmic +Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast +UseIn=COSMIC +``` + +Backups created locally: + +```text +/usr/share/xdg-desktop-portal/portals/cosmic.portal.bak-codex-20260505-filechooser +/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505 +/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505-2 +``` + +After editing portal files, restart: + +```sh +systemctl --user restart xdg-desktop-portal.service xdg-desktop-portal-gtk.service +``` + +Package updates may overwrite `/usr/share/xdg-desktop-portal/portals/cosmic.portal`. +If `Save As` starts needing two attempts again, re-check that `FileChooser` has +not been reintroduced in `cosmic.portal`. diff --git a/i18n/ca/cosmic_files.ftl b/i18n/ca/cosmic_files.ftl index 48e7033..f37cc18 100644 --- a/i18n/ca/cosmic_files.ftl +++ b/i18n/ca/cosmic_files.ftl @@ -1,11 +1,11 @@ cosmic-files = Fitxers del COSMIC empty-folder = Carpeta buida -empty-folder-hidden = Carpeta buida (té elements ocults) +empty-folder-hidden = Carpeta buida (conté elements ocults) no-results = No s'ha trobat cap resultat filesystem = Sistema de fitxers home = Inici networks = Xarxes -notification-in-progress = Hi ha operacions amb fitxers en curs. +notification-in-progress = Operacions de fitxers en curs trash = Paperera recents = Recents undo = Desfés @@ -29,14 +29,14 @@ details = Detalls dismiss = Descarta el missatge operations-running = { $running } { $running -> - [one] operacion + [one] operació *[other] operacions } en curs ({ $percent }%)... operations-running-finished = { $running }{ $running -> - [one] operacion + [one] operació *[other] operacions - } en curs ({ $percent }%), { $finished } acabat... + } en curs ({ $percent }%), { $finished } acabada/es... pause = Pausa resume = Reprèn @@ -54,7 +54,7 @@ extract-password-required = Cal una contrasenya ## Empty Trash Dialog empty-trash = Buida la paperera -empty-trash-warning = Voleu suprimir permanentment tots els fitxers de la paperera? +empty-trash-warning = Els elements dins la Paperera s'eliminaran permanentment ## Mount Error Dialog @@ -64,12 +64,12 @@ mount-error = No es pot accedir a la unitat create-new-file = Crea un nou fitxer create-new-folder = Crea una nova carpeta -file-name = Nom del fixer +file-name = Nom del fitxer folder-name = Nom de la carpeta -file-already-exists = Ja existeix un fitxer amb aquest nom. -folder-already-exists = Ja existeix una carpeta amb aquest nom. -name-hidden = Els noms que comencin amb "." seran ocults. -name-invalid = El nom no pot ser "{ $filename }". +file-already-exists = Ja existeix un fitxer amb aquest nom +folder-already-exists = Ja existeix una carpeta amb aquest nom +name-hidden = Els noms que comencin amb "." s'ocultaran. +name-invalid = El nom no pot ser "{ $filename }" name-no-slashes = El nom no pot contenir barres. ## Open/Save Dialog @@ -353,3 +353,13 @@ sort-oldest-first = Primer més antics sort-smallest-to-largest = De petit a gran sort-largest-to-smallest = De gran a petit run = Executa +extract-to-title = Extreu a una carpeta +empty-trash-title = Voleu buidar la paperera? +extract-to = Extreu a... +delete = Suprimeix +comment = Gestor de fitxers de l'escriptori COSMIC +copy-to-title = Escolliu el destí de la còpia +copy-to-button-label = Copia +move-to-title = Escolliu el destí del trasllat +move-to-button-label = Mou +keywords = Carpeta;Fitxers;Arxius;Gestor;Explorador; diff --git a/i18n/cs/cosmic_files.ftl b/i18n/cs/cosmic_files.ftl index 29814ba..fac1794 100644 --- a/i18n/cs/cosmic_files.ftl +++ b/i18n/cs/cosmic_files.ftl @@ -427,3 +427,4 @@ context-action-confirm-warning = *[other] položek }. run = Spustit +rename-confirm = Přejmenovat diff --git a/i18n/de/cosmic_files.ftl b/i18n/de/cosmic_files.ftl index 993af2d..65a5ebf 100644 --- a/i18n/de/cosmic_files.ftl +++ b/i18n/de/cosmic_files.ftl @@ -437,3 +437,4 @@ context-action-confirm-warning = *[other] Elementen }. run = Ausführen +rename-confirm = Umbenennen diff --git a/i18n/el/cosmic_files.ftl b/i18n/el/cosmic_files.ftl index 7471630..c75e419 100644 --- a/i18n/el/cosmic_files.ftl +++ b/i18n/el/cosmic_files.ftl @@ -327,3 +327,4 @@ context-action-confirm-warning = [one] στοιχείο *[other] στοιχεία }. +rename-confirm = Μετονομασία diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 45965ef..129e6f9 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -96,6 +96,7 @@ save-file = Save file ## Open With Dialog open-with-title = How do you want to open "{$name}"? +open-with-set-default = Always use this app for this file type browse-store = Browse {$store} other-apps = Other applications related-apps = Related applications @@ -139,6 +140,11 @@ open-with = Open with owner = Owner group = Group other = Other +toolbar = Toolbar +toolbar-available = Available +toolbar-empty-hint = No buttons. Drag or add from below. +toolbar-reset = Reset to defaults +parent-directory = Parent directory mixed = Mixed ### Mode 0 none = None @@ -303,6 +309,9 @@ item-created = Created: {$created} item-modified = Modified: {$modified} item-accessed = Accessed: {$accessed} calculating = Calculating... +checksum = {$kind} checksum +calculate = Calculate +error = Error ## Settings settings = Settings diff --git a/i18n/fi/cosmic_files.ftl b/i18n/fi/cosmic_files.ftl index f1c3c58..6000bc7 100644 --- a/i18n/fi/cosmic_files.ftl +++ b/i18n/fi/cosmic_files.ftl @@ -414,3 +414,4 @@ context-action-confirm-warning = *[other] kohdetta }. run = Suorita +rename-confirm = Nimeä uudelleen diff --git a/i18n/fr/cosmic_files.ftl b/i18n/fr/cosmic_files.ftl index f1454d2..e85a1f6 100644 --- a/i18n/fr/cosmic_files.ftl +++ b/i18n/fr/cosmic_files.ftl @@ -92,6 +92,7 @@ save-file = Enregistrer fichier ## Open With Dialog open-with-title = Comment souhaitez-vous ouvrir "{ $name }" ? +open-with-set-default = Toujours utiliser cette application pour ce type de fichier browse-store = Parcourir { $store } ## Permanently delete Dialog @@ -130,6 +131,11 @@ open-with = Ouvrir avec owner = Propriétaire group = Groupe other = Autre +toolbar = Barre d'outils +toolbar-available = Disponibles +toolbar-empty-hint = Aucun bouton. Glisser-déposer ou ajouter depuis la liste ci-dessous. +toolbar-reset = Rétablir par défaut +parent-directory = Dossier parent ### Mode 0 @@ -445,3 +451,4 @@ context-action-confirm-warning = }. run = Exécuter context-action = Action contextuelle +rename-confirm = Renommer diff --git a/i18n/ga/cosmic_files.ftl b/i18n/ga/cosmic_files.ftl index 04d546f..773c2fa 100644 --- a/i18n/ga/cosmic_files.ftl +++ b/i18n/ga/cosmic_files.ftl @@ -442,3 +442,4 @@ context-action-confirm-warning = *[other] míreanna }. run = Rith +rename-confirm = Athainmnigh diff --git a/i18n/hu/cosmic_files.ftl b/i18n/hu/cosmic_files.ftl index 2cf31e6..dfdbf44 100644 --- a/i18n/hu/cosmic_files.ftl +++ b/i18n/hu/cosmic_files.ftl @@ -2,7 +2,7 @@ cosmic-files = COSMIC Fájlok comment = Fájlkezelő a COSMIC asztali környezethez keywords = mappa;fájl;kezelő; empty-folder = Üres mappa -empty-folder-hidden = Üres mappa (Rejtett elemeket tartalmaz) +empty-folder-hidden = Üres mappa (rejtett elemeket tartalmaz) no-results = Nincs találat filesystem = Fájlrendszer home = Saját mappa @@ -17,7 +17,7 @@ desktop-view-options = Asztali nézet beállításai… show-on-desktop = Megjelenítés az asztalon desktop-folder-content = Asztal mappa tartalma mounted-drives = Csatolt meghajtók -trash-folder-icon = Kuka ikonja +trash-folder-icon = Kuka ikon icon-size-and-spacing = Ikonméret és távolság icon-size = Ikonméret grid-spacing = Rácsköz @@ -47,18 +47,18 @@ resume = Folytatás ## Compress Dialog -create-archive = Tömörített fájl létrehozása +create-archive = Archívum létrehozása ## Extract Dialog extract-password-required = Jelszó szükséges extract-to = Kibontás ide… -extract-to-title = Kibontás mappába +extract-to-title = Kibontási cél kiválasztása ## Empty Trash Dialog -empty-trash = Kuka ürítése -empty-trash-warning = A kukában lévő összes elem véglegesen törölve lesz +empty-trash = A Kuka ürítése +empty-trash-warning = A Kukában lévő összes elem véglegesen törölve lesz ## Mount Error Dialog @@ -93,7 +93,7 @@ save-file = Fájl mentése ## Open With Dialog -open-with-title = Hogyan szeretnéd megnyitni a következőt: „{ $name }”? +open-with-title = Hogyan szeretnéd megnyitni a(z) „{ $name }” fájlt? browse-store = { $store } böngészése other-apps = Egyéb alkalmazások related-apps = Hasonló alkalmazások @@ -103,7 +103,7 @@ related-apps = Hasonló alkalmazások selected-items = A(z) { $items } kijelölt elem permanently-delete-question = Végleges törlés? delete = Törlés -permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem visszavonható. +permanently-delete-warning = { $target } véglegesen törölve lesz. A művelet nem vonható vissza. ## Rename Dialog @@ -113,7 +113,7 @@ rename-folder = Mappa átnevezése ## Replace Dialog replace = Csere -replace-title = „{ $filename }” már létezik +replace-title = „{ $filename }” már létezik ezen a helyen replace-warning = Szeretnéd lecserélni a meglévő fájlt a mentendő fájllal? A cserével felülírod annak tartalmát. replace-warning-operation = Szeretnéd lecserélni? A csere felülírja annak tartalmát. original-file = Eredeti fájl @@ -133,7 +133,7 @@ set-and-launch = Beállítás és indítás open-with = Megnyitás ezzel owner = Tulajdonos group = Csoport -other = Többi +other = Mások ### Mode 0 @@ -172,7 +172,7 @@ read-write-execute = Olvasás, írás és végrehajtás favorite-path-error = Hiba a könyvtár megnyitásakor favorite-path-error-description = Nem sikerült megnyitni ezt: „{ $path }” - „{ $path }” lehet nem létezik, vagy nincs jogosultságod a megnyitásához + A(z) „{ $path }” útvonal lehet, hogy nem létezik, vagy nincs jogosultságod a megnyitásához Szeretnéd eltávolítani az oldalsávról? remove = Eltávolítás @@ -187,9 +187,9 @@ keep = Megtartás ## Add Network Drive add-network-drive = Hálózati meghajtó hozzáadása -connect = Csatlakozás -connect-anonymously = Csatlakozás névtelenül -connecting = Csatlakozás… +connect = Kapcsolódás +connect-anonymously = Kapcsolódás névtelenül +connecting = Kapcsolódás… domain = Tartomány enter-server-address = Add meg a kiszolgáló címét network-drive-description = @@ -209,7 +209,7 @@ network-drive-schemes = network-drive-error = Nem érhető el a hálózati meghajtó password = Jelszó remember-password = Jelszó megjegyzése -try-again = Újra +try-again = Próbáld újra username = Felhasználónév ## Operations @@ -217,7 +217,7 @@ username = Felhasználónév cancelled = Megszakítva edit-history = Fájlműveleti előzmények history = Előzmények -no-history = Nem találhatók elemek az előzményekben +no-history = Nem találhatók elemek az előzményekben. pending = Függőben progress = { $percent }% progress-cancelled = { $percent }%, megszakítva @@ -251,13 +251,13 @@ deleting = { $items } { $items -> [one] elem *[other] elem - } törlése a kukából ({ $progress })… + } törlése a Kukából ({ $progress })… deleted = { $items } { $items -> [one] elem *[other] elem - } törölve a kukából -emptying-trash = { trash } kiürítése ({ $progress })… + } törölve a Kukából +emptying-trash = { trash } ürítése ({ $progress })… emptied-trash = { trash } kiürítve extracting = { $items } { $items -> @@ -309,12 +309,12 @@ restoring = { $items } { $items -> [one] elem *[other] elem - } visszaállítása a kukából ({ $progress })… + } visszaállítása a Kukából ({ $progress })… restored = { $items } { $items -> [one] elem *[other] elem - } visszaállítva a kukából + } visszaállítva a Kukából unknown-folder = ismeretlen mappa ## Open with @@ -330,7 +330,7 @@ items = Elemek: { $items } item-size = Méret: { $size } item-created = Létrehozva: { $created } item-modified = Módosítva: { $modified } -item-accessed = Hozzáférve: { $accessed } +item-accessed = Utolsó hozzáférés: { $accessed } calculating = Számítás… ## Settings @@ -349,8 +349,8 @@ light = Világos ### Type to Search type-to-search = Gépeléssel keresés -type-to-search-recursive = Keresés a jelenlegi mappában és almappákban -type-to-search-enter-path = Elérési út megadása +type-to-search-recursive = Keresés a jelenlegi mappában és az almappákban +type-to-search-enter-path = Elérési út megnyitása # Context menu add-to-sidebar = Hozzáadás az oldalsávhoz compress = Tömörítés… @@ -360,8 +360,8 @@ extract-here = Kibontás new-file = Új fájl… new-folder = Új mappa… open-in-terminal = Megnyitás a terminálban -move-to-trash = Kukába helyezés -restore-from-trash = Visszaállítás a kukából +move-to-trash = Áthelyezés a Kukába +restore-from-trash = Visszaállítás a Kukából remove-from-sidebar = Eltávolítás az oldalsávról sort-by-name = Név szerinti rendezés sort-by-modified = Módosítás szerinti rendezés @@ -372,8 +372,8 @@ remove-from-recents = Eltávolítás a legutóbbiak közül ## Desktop change-wallpaper = Háttérkép cseréje… -desktop-appearance = Asztali megjelenés… -display-settings = Képernyő-beállítások… +desktop-appearance = Asztal megjelenése… +display-settings = Kijelzőbeállítások… # Menu @@ -406,7 +406,7 @@ grid-view = Rácsnézet list-view = Listanézet show-hidden-files = Rejtett fájlok megjelenítése list-directories-first = Könyvtárak listázása először -gallery-preview = Galéria előnézet +gallery-preview = Galéria-előnézet menu-settings = Beállítások… menu-about = A COSMIC Fájlok névjegye… @@ -415,14 +415,14 @@ menu-about = A COSMIC Fájlok névjegye… sort = Rendezés sort-a-z = A-Z sort-z-a = Z-A -sort-newest-first = Legújabb előre -sort-oldest-first = Legrégibb előre +sort-newest-first = Legújabb elöl +sort-oldest-first = Legrégibb elöl sort-smallest-to-largest = Legkisebbtől a legnagyobbig sort-largest-to-smallest = Legnagyobbtól a legkisebbig repository = Tároló support = Támogatás progress-failed = { $percent }%, sikertelen -empty-trash-title = Kuka ürítése? +empty-trash-title = Kiüríted a Kukát? type-to-search-select = Kijelöli az első egyező fájlt vagy mappát pasted-image = Beillesztett kép pasted-text = Beillesztett szöveg @@ -445,3 +445,4 @@ context-action-confirm-warning = *[other] elemen } fog lefutni. run = Futtatás +rename-confirm = Átnevezés diff --git a/i18n/id/cosmic_files.ftl b/i18n/id/cosmic_files.ftl index aefff98..3300d25 100644 --- a/i18n/id/cosmic_files.ftl +++ b/i18n/id/cosmic_files.ftl @@ -327,3 +327,4 @@ context-action-confirm-warning = [one] item *[other] item }. +rename-confirm = Ganti nama diff --git a/i18n/kk/cosmic_files.ftl b/i18n/kk/cosmic_files.ftl index 8aabf01..0ee3ffe 100644 --- a/i18n/kk/cosmic_files.ftl +++ b/i18n/kk/cosmic_files.ftl @@ -327,3 +327,4 @@ context-action-confirm-warning = *[other] нәрсеге }. run = Орындау +rename-confirm = Атын өзгерту diff --git a/i18n/kmr/cosmic_files.ftl b/i18n/kmr/cosmic_files.ftl index e69de29..6c08bd0 100644 --- a/i18n/kmr/cosmic_files.ftl +++ b/i18n/kmr/cosmic_files.ftl @@ -0,0 +1,330 @@ +sort-newest-first = Pêşî ya herî nû +connect = Girê bide +deleted = + { $items } hat jêbirin{ $items -> + [one] hêman + *[other] hêman + } ji { trash } +dismiss = Peyamê paşguh bike +list-view = Dîtina lîsteyê +copy_noun = Jê bigire +favorite-path-error = Şaşetiya vekirina rêgehê +progress = { $percent }% +open-file = Pelê veke +remove-from-sidebar = Ji benda kêlekê rake +related-apps = Bernameyên têkildar +network-drive-error = Nikaribû bigihêje ajokarê torê +gallery-preview = Pêşdîtina galeriyê +sort-smallest-to-largest = Biçûktir bo mezintir +zoom-in = Nêzîk bike +select-all = Hemûyan hilbijêre +removing-from-recents = + { $items } tê rakirin { $items -> + [one] hêman + *[other] hêman + } ji { recents } +password = Borînpeyv +cosmic-files = Pelên COSMIC +emptying-trash = { trash } tê valakirin({ $progress })... +new-window = Çarçoveya nû +remove = Rake +zoom-out = Dûr bike +compressing = + { $items } tê guvaştin { $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" ({ $progress })... +move-to-trash = Bilivîne bo jêbirdankê +menu-about = Derbarê Pelên COSMIC... +file-already-exists = Pelek bi heman navî jixwe heye +setting-executable-and-launching = "{ $name }" wekî pelê xebitandinê tê sazkirin û xebitandin +open-multiple-files = Gelek pelan veke +default-size = Mezinahiya berdest +name-hidden = Navên ku bi "." tê destpêkirin wê werin veşartin +extracted = + { $items } hate derxistin { $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" +create-new-folder = Peldanka nû biafrîne +folder-already-exists = Peldankek bi heman navî jixwe heye +create-archive = Erşîvê biafrîne +permanently-delete-warning = { $target } wê bi mayînde were jêbirin û nayê vegerandin. +favorite-path-error-description = + Nikaribû "{ $path }" veke. + "{ $path }" dibe ku tune be yan jî mafê te yê + vekirina wê tune be. + Tu dixwazî wê ji benda kêlekê rakî? +empty-trash-warning = Hêmanên di jêbirdankê de wê bi mayînde werin jêbirin +other-apps = Bernameyên din +set-permissions = Maf hatine sazkirin ji bo "{ $name }" bo { $mode } +quit = Biqedîne +sort-by-size = Li gorî mezinahiyê rêz bike +rename = Navê biguhêrîne... +empty-folder-hidden = Peldanka vala (tiştên veşartî tê de hene) +keep = Bihêle +operations-running = + { $running } { $running -> + [one] pêvajo + *[other] pêvajo + } dixebite ({ $percent }%)... +permanently-deleting = + { $items } bi mayînde tê jêbirin { $items -> + [one] hêman + *[other] hêman + } +edit = Biguhêrîne +copy = Jê bigire +none = Ne yek +no-results = Tu encam nehatin dîtin +theme = Rûkar +compressed = + { $items } hate guvaştin{ $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" +appearance = Xuyang +rename-folder = Navê peldankê biguherîne +new-file = Pela nû... +close-tab = Rûgerê bigire +open-in-terminal = Di termînalê de veke +open-multiple-folders = Gelek peldankan veke +remember-password = Borînpeyvê bi bîr bîne +username = Navê bikarhêner +light = Ronî +extract-to = Derxîne bo... +add-network-drive = Ajokarê torê tevlî bike +copying = + { $items } tê jêgirtin{ $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" ({ $progress })... +create-new-file = Pelê nû biafrîne +sort-by-trashed = Li gorî demê rêz bike +support = Piştgirî +try-again = Dîsa hewl bide +eject = Biavêje +copied = + { $items } hate jêgirtin { $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" +open-in-new-window = Di çarçoveyeke nû de veke +empty-folder = Peldanka vala +empty-trash = Jêbirdankê vala bike +sort-by-modified = Li gorî guhertinê rêz bike +list-directories-first = Pêşî rêgehan lîste bike +folder-name = Navê peldankê +browse-store = { $store } bigere +remove-from-recents = Ji dawiyê rake +paste = Pêve bike +empty-trash-title = Jêbirdankê vala bike? +menu-settings = Sazkarî... +moving = + { $items } tê livandin{ $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" ({ $progress })... +change-wallpaper = Wêneyê dîwêr biguhêrîne... +network-drive-description = + Di nav navnîşanên rajekarê de pêşgira protokolekê û navnîşanek heye. + Mînak: ssh://192.168.0.1, ftp://[2001:db8::1] +deleting = + { $items } tê jêbirin { $items -> + [one] hêman + *[other] hêman + } ji { trash } ({ $progress })... +single-click = Tikandina yekane bo vekriinê +view = Dîtin +setting-permissions = Maf tên sazkirin ji bo "{ $name }" bo { $mode } +creating = "{ $name }" tê afirandin di "{ $parent }" de +execute-only = Tenê xebitandin +open-item-location = Cihê hêmanê veke +type-to-search = Bo lêgerînê binivîse +set-executable-and-launched = "{ $name }" wekî pelê xebitandinê hat sazkirin û xebitandin +mounted-drives = Ajokarên siwarkirî +sort-a-z = A-Z +mount-error = Nikare bigihêje ajokerê +extract-here = Derxîne +grid-view = Dîtina torê +add-to-sidebar = Tevlî benda kêlekê bike +dark = Tarî +notification-in-progress = Pêvajoyên pelê didome +home = Rûpela sereke +sort-z-a = Z-A +type-to-search-recursive = Di peldank û binpeldankên heyî de digere +history = Dîrok +progress-paused = { $percent }%, hate rawestandin +desktop-view-options = Vebijêrkên dîtina sermaseyê... +show-on-desktop = Li ser sermaseyê nîşan bide +cancelled = Têkbirî +new-folder = Peldankan nû... +match-desktop = Li gorî sermaseyê +name-no-slashes = Nav nabe ku "/" tê de hebe +operations-running-finished = + { $running } { $running -> + [one] pêvajo + *[other] pêvajo + } dixebite ({ $percent }%), { $finished } qediya... +sort-by-name = Li gorî navê rêz bike +edit-history = Dîrokê biguhêrînê +show-hidden-files = Pelên veşartî nîşan bide +progress-failed = { $percent }%, têk çû +trash-folder-icon = Îkona peldanka jêbirdankê +extract-to-title = Derxîne bo peldankê +icon-size = Mezinahiya îkonê +open-with-title = Tu çawa dixwazî "{ $name }" vekî? +extracting = + { $items } tê derxistin { $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo"{ $to }" ({ $progress })... +settings = Sazkarî +complete = Qediya +permanently-delete-question = Bi mayînde jê bibe? +extract-password-required = Borînpeyv pêdivî ye +pending = Li bendê ye +desktop-folder-content = Naveroka peldanka sermaseyê +no-history = Tu hêman di dîrok de tune ne. +open-folder = Peldankê veke +emptied-trash = { trash } hate valakirin +rename-file = Navê pelê biguherîne +sort-largest-to-smallest = Mezintir bo biçûktir +restore-from-trash = Ji jêbirdankê vegerîne +cut = Jê bike +moved = + { $items } hate livandin { $items -> + [one] hêman + *[other] hêman + } ji "{ $from }" bo "{ $to }" +progress-cancelled = { $percent }%, hat têkbirin +open-in-new-tab = Di rûgerê nû de veke +file = Pel +file-name = Navê pelê +save-file = Pelê tomar bike +created = "{ $name }" hate afirandin di "{ $parent }" de +delete-permanently = Bi mayînde jê bibe +display-settings = Sazkariyên dîmenderê... +new-tab = Rûgera nû +failed = Têk çû +name-invalid = Nav nabe ku \"{ $filename }\" be +desktop-appearance = Xuyanga sermaseyê... +comment = Rêveberê pelê ji bo sermaseya COSMIC +keywords = Rêveber;Peldank; +filesystem = Pergala pelê +networks = Tor +recents = Dawî +open = Veke +cancel = Têk bibe +trash = Jêbirdank +undo = Vegerîne +today = Îro +name = Nav +modified = Guhertî +size = Mezinahî +details = Hûrgilî +pause = Rawestîne +resume = Bidomîne +copy-to-title = Rêgeha jêgirtinê hilbijêre +copy-to-button-label = Jê bigire +move-to-title = Rêgeha livandinê hilbijêre +move-to-button-label = Bilivîne +create = Biafrîne +save = Tomar bike +context-action = Çalakiya naverokê +context-action-confirm-title = "{ $name }" bixebtîne? +context-action-confirm-warning = + Ev ê li ser { $items } { $items -> + [one] hêman + *[other] hêman + } bixebite. +run = Bixebitîne +selected-items = { $items } hêmanên hilbijartî +delete = Jê bibe +rename-confirm = Navê biguherîne +pasted-image = Wêneya pêvekirî +pasted-text = Nivîsa pêvekirî +pasted-video = Vîdiyoya pêvekirî +copy-path = Rêgehê jê bigire +show-recents = Peldanka dawî di benda kêlekê de +clear-recents-history = Dîroka dawî pak bike +compress = Biguvşîne... +copy-to = Jê bigire bo... +move-to = Bilivîne bo... +default-app = { $name } (berdest) +renamed = Nav hate guhertin ji "{ $from }" bo "{ $to }" +read-execute = Xwendin û xebitandin +item-modified = Guhertin: { $modified } +reload-folder = Peldankê ji nû ve bar bike +restoring = + { $items } tê vegerandin { $items -> + [one] hêman + *[other] hêman + } ji { trash } ({ $progress })... +icon-size-and-spacing = Mezinahiya îkonan û valahiya di navbera wan de +type-to-search-enter-path = Riya rêgehê yan jî pelê têdixîne +menu-open-with = Veke bi... +original-file = Pelê resen +read-write-execute = Xwendin, nivîsandin û xebitandin +calculating = Tê jimartin... +item-size = Mezinahî: { $size } +connecting = Tê girêdan... +read-write = Xwendin û nivîsandin +items = Hêman: { $items } +type = Cûre: { $mime } +replace-warning = Tu dixwazî bi ya ku tu niha tomar dikî biguhêrînî? Naveroka berê wê were jêbirin. +show-details = Hûrgiliyan nîşan bide +grid-spacing = Valahiya torê +sort-oldest-first = Pêşî ya herî kevn +repository = Embar +replace-warning-operation = Tu dixwazî biguherînî? Naveroka berê wê were jêbirin. +other = Ên din +read-only = Tenê xwendin +enter-server-address = Navnîşana rajekarê têxîne +connect-anonymously = Bi awayekî anonîm girê bide +group = Kom +apply-to-all = Bo hemûyan bisepîne +skip = Derbas bike +replace-with = Biguhêrîne bi +owner = Xwedî +set-and-launch = Saz bike û dest pê bike +removed-from-recents = + { $items } hate rakirin { $items -> + [one] hêman + *[other] hêman + } ji { recents } +item-created = Afirandin:{ $created } +network-drive-schemes = + Protokolên berdest,pêşgirê + AppleTalk,afp:// + Protokola şandina pelê,ftp:// an ftps:// + Pergala pelê torê,nfs:// + Astengiya peyama rajekarê,smb:// + SSH protokola şandina pelê,sftp:// an ssh:// + WebDAV, dav:// an davs:// +set-executable-and-launch = Wekî pelê xebitandinê saz bike û bixebitîne +restored = + { $items } hate vegerandin { $items -> + [one] hêman + *[other] hêman + } ji { trash } +domain = Navper +sort = Rêz bike +item-accessed = Gihîştin: { $accessed } +open-with = Veke bi +keep-both = Herduyan bihêle +permanently-deleted = + { $items } bi mayînde hate jêbirin{ $items -> + [one] hêman + *[other] hêman + } +write-execute = Nivîsandin û xebitandin +renaming = Navê ji "{ $from }" biguhêrîne bo "{ $to }" +set-executable-and-launch-description = Tu dixwazî "{ $name }" wekî pelê xebitandinê saz bikî û bixebtînî? +replace = Biguhêrîne +replace-title = "{ $filename }" jixwe di vê cihê de heye +unknown-folder = Peldanka nenas +write-only = Tenê nivîsandin +trashed-on = Li Jêbirdankê hatiye avêtin +mixed = Tevlihev +type-to-search-select = Pel an peldanka yekem a lihevhatî hildibijêre diff --git a/i18n/nb-NO/cosmic_files.ftl b/i18n/nb-NO/cosmic_files.ftl index e0fb82c..cae2b5b 100644 --- a/i18n/nb-NO/cosmic_files.ftl +++ b/i18n/nb-NO/cosmic_files.ftl @@ -98,7 +98,7 @@ permanently-delete-question = Slett permanent permanently-delete-warning = Er du sukker på om du vil permanent slette { $target }? Dette kan ikke angres. rename-file = Endre filnavn rename-folder = Endre mappenavn -replace-title = "{ $filename }" finnes allerede på denne plasseringen. +replace-title = «{ $filename }» finnes allerede på denne plasseringen replace-warning = Vil du bytte den ut med filen du lagrer? Dette vil overskrive innholdet. replace-warning-operation = Vil du erstatte den? Dette vil overskrive innholdet. original-file = Orginalfil @@ -122,8 +122,8 @@ read-write = Kun skrive- og lesbar read-write-execute = Lesbar, skrivbar og kjørbar favorite-path-error = Feil ved åpning av mappen favorite-path-error-description = - Kunne ikke åpne "{ $path }". - Den kan ikke eksistere eller så har du ikke tilgang til å åpne den. + Klarte ikke åpne «{ $path }». + «{ $path }» finnes ikke eller så har du ikke tilgang til å åpne den. Vil du fjerne den fra sidepanelet? keep = Behold @@ -140,7 +140,7 @@ zoom-out = Zoom ut cut = Klipp ut set-and-launch = Still inn og start domain = Domene -enter-server-address = Angi serveradresse +enter-server-address = Angi tjeneradresse network-drive-description = Serveradresser inkluderer ett protokollprefiks og en addresse. Examples: ssh://192.168.0.1, ftp://[2001:db8::1] diff --git a/i18n/pl/cosmic_files.ftl b/i18n/pl/cosmic_files.ftl index 9af6027..2f58d64 100644 --- a/i18n/pl/cosmic_files.ftl +++ b/i18n/pl/cosmic_files.ftl @@ -446,3 +446,4 @@ context-action-confirm-warning = *[other] elementów }. run = Uruchom +rename-confirm = Zmień nazwę diff --git a/i18n/pt-BR/cosmic_files.ftl b/i18n/pt-BR/cosmic_files.ftl index 6d33279..6309e46 100644 --- a/i18n/pt-BR/cosmic_files.ftl +++ b/i18n/pt-BR/cosmic_files.ftl @@ -445,3 +445,4 @@ context-action-confirm-warning = *[other] itens }. run = Executar +rename-confirm = Renomear diff --git a/i18n/pt/cosmic_files.ftl b/i18n/pt/cosmic_files.ftl index 211b094..b0c7e30 100644 --- a/i18n/pt/cosmic_files.ftl +++ b/i18n/pt/cosmic_files.ftl @@ -1,12 +1,12 @@ cosmic-files = Ficheiros COSMIC comment = Gerenciador de arquivos do COSMIC -keywords = Pastas;Gerenciador;Arquivos;Gestor;Explorer; +keywords = Pasta;Gestor; empty-folder = Pasta vazia empty-folder-hidden = Pasta vazia (tem ficheiros ocultos) no-results = Nenhum resultado encontrado filesystem = Sistema de ficheiros home = Pasta Pessoal -notification-in-progress = Operações em curso. +notification-in-progress = Operações em curso trash = Lixo undo = Anular # List view @@ -204,7 +204,7 @@ desktop-view-options = Opções de visualização da área de trabalho... show-on-desktop = Mostrar na área de trabalho desktop-folder-content = Conteúdo da pasta da área de trabalho mounted-drives = Dispositivos montados -trash-folder-icon = Ícone do lixo +trash-folder-icon = Ícone da pasta lixo icon-size-and-spacing = Tamanho e espaçamento do ícone icon-size = Tamanho do ícone grid-spacing = Espaçamento entre ícones @@ -354,3 +354,4 @@ sort-smallest-to-largest = Do menor para o maior sort-largest-to-smallest = Do maior para o menor context-action-confirm-title = Executar "{ $name }"? run = Executar +rename-confirm = Renomear diff --git a/i18n/ru/cosmic_files.ftl b/i18n/ru/cosmic_files.ftl index f8e76c1..f34a377 100644 --- a/i18n/ru/cosmic_files.ftl +++ b/i18n/ru/cosmic_files.ftl @@ -389,3 +389,4 @@ context-action-confirm-warning = *[other] элем. }. run = Выполнить +rename-confirm = Переименовать diff --git a/i18n/sr/cosmic_files.ftl b/i18n/sr/cosmic_files.ftl index b71afe5..98a9e35 100644 --- a/i18n/sr/cosmic_files.ftl +++ b/i18n/sr/cosmic_files.ftl @@ -327,3 +327,4 @@ compress = Сажми... copy-to = Умножи у... move-to = Помери у... copy-path = Умножи путању +rename-confirm = Преименуј diff --git a/i18n/sv/cosmic_files.ftl b/i18n/sv/cosmic_files.ftl index 974c7dc..506be0b 100644 --- a/i18n/sv/cosmic_files.ftl +++ b/i18n/sv/cosmic_files.ftl @@ -418,3 +418,4 @@ context-action-confirm-warning = *[other] objekt }. run = Kör +rename-confirm = Byt namn diff --git a/i18n/uk/cosmic_files.ftl b/i18n/uk/cosmic_files.ftl index 7299a7d..31b4103 100644 --- a/i18n/uk/cosmic_files.ftl +++ b/i18n/uk/cosmic_files.ftl @@ -82,7 +82,7 @@ copying = Копіювання { $items } { $items -> [one] елемента *[other] елементів - } з «{ $from }» в «{ $to }» ({ $progress })... + } з «{ $from }» до «{ $to }» ({ $progress })... copied = Скопійовано { $items } { $items -> [one] елемент @@ -95,7 +95,7 @@ moving = Переміщення { $items } { $items -> [one] елемента *[other] елементів - } з «{ $from }» в «{ $to }» ({ $progress })... + } з «{ $from }» до «{ $to }» ({ $progress })... moved = Переміщено { $items } { $items -> [one] елемент @@ -299,7 +299,7 @@ extracting = Видобування { $items } { $items -> [one] елемента *[other] елементів - } з «{ $from }» в «{ $to }» ({ $progress })... + } з «{ $from }» до «{ $to }» ({ $progress })... extracted = Видобуто { $items } { $items -> [one] елемент @@ -364,10 +364,10 @@ removed-from-recents = *[other] елементів } з { recents } empty-trash-title = Спорожити смітник? -type-to-search-select = Вибирає перший відповідний файл або папку +type-to-search-select = Вибирає перший відповідний файл або теку pasted-image = Вставлене зображення pasted-text = Вставлений текст -pasted-video = Вставлене відео +pasted-video = Вставлене видиво copy-to-button-label = Копіювати move-to-button-label = Перемістити copy-to = Копіювати до… @@ -387,3 +387,5 @@ context-action-confirm-warning = *[other] елементів }. context-action = Контекстна дія +rename-confirm = Перейменувати +mixed = Змішано diff --git a/i18n/zh-CN/cosmic_files.ftl b/i18n/zh-CN/cosmic_files.ftl index abc8fda..4807c4a 100644 --- a/i18n/zh-CN/cosmic_files.ftl +++ b/i18n/zh-CN/cosmic_files.ftl @@ -445,3 +445,4 @@ context-action-confirm-warning = *[other] 项目 } 上运行。 context-action = 环境行动 +rename-confirm = 重命名 diff --git a/i18n/zh-TW/cosmic_files.ftl b/i18n/zh-TW/cosmic_files.ftl index b356db3..294ed2a 100644 --- a/i18n/zh-TW/cosmic_files.ftl +++ b/i18n/zh-TW/cosmic_files.ftl @@ -392,3 +392,4 @@ context-action-confirm-warning = *[other] 項目 } 上執行。 run = 執行 +rename-confirm = 重新命名 diff --git a/justfile b/justfile index 9522e98..5e8d024 100644 --- a/justfile +++ b/justfile @@ -48,7 +48,7 @@ build-debug *args: # Compiles with release profile build-release *args: (build-debug '--release' args) - + # Compiles applet with release profile build-release-applet *args: cargo build --package {{applet-name}} --release {{args}} @@ -111,7 +111,7 @@ uninstall: vendor: #!/usr/bin/env bash mkdir -p .cargo - cargo vendor --sync Cargo.toml | head -n -1 > .cargo/config.toml + cargo vendor --locked --sync Cargo.toml | head -n -1 > .cargo/config.toml echo 'directory = "vendor"' >> .cargo/config.toml echo >> .cargo/config.toml echo '[env]' >> .cargo/config.toml diff --git a/src/app.rs b/src/app.rs index cc3e5c0..0e572a0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,6 +7,7 @@ use cosmic::iced::clipboard::dnd::DndAction; use cosmic::iced::core::SmolStr; use cosmic::iced::core::widget::operation::focusable::unfocus; use cosmic::iced::futures::{self, SinkExt}; +use cosmic::iced::keyboard::key::Physical; use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers}; #[cfg(all(feature = "wayland", feature = "desktop-applet"))] use cosmic::iced::platform_specific::shell::wayland::commands::overlap_notify::overlap_notify; @@ -41,15 +42,21 @@ use notify_debouncer_full::notify::{self, RecommendedWatcher}; use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer}; use rustc_hash::{FxHashMap, FxHashSet}; use slotmap::Key as SlotMapKey; -use std::any::TypeId; -use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; -use std::future::Future; -use std::num::NonZeroU16; -use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::sync::{Arc, LazyLock, Mutex}; -use std::time::{self, Duration, Instant}; -use std::{env, fmt, fs, io, process}; +#[cfg(feature = "notify")] +use std::sync::Mutex; +use std::{ + any::TypeId, + collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, + env, fmt, fs, + future::Future, + io, + num::NonZeroU16, + path::{Path, PathBuf}, + pin::Pin, + process, + sync::{Arc, LazyLock}, + time::{self, Duration, Instant}, +}; use tokio::sync::mpsc; use trash::TrashItem; #[cfg(all(feature = "wayland", feature = "desktop-applet"))] @@ -61,12 +68,12 @@ use crate::clipboard::{ }; use crate::config::{ AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig, - TimeConfig, TypeToSearch, + TimeConfig, ToolbarAction, TypeToSearch, default_toolbar, }; use crate::dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings}; use crate::key_bind::key_binds; use crate::localize::LANGUAGE_SORTER; -use crate::mime_app::{self, MimeApp, MimeAppCache}; +use crate::mime_app::{self, MimeApp, MimeAppCache, MimeAppMatch}; use crate::mounter::{ MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, }; @@ -127,6 +134,105 @@ pub struct Flags { pub uris: Vec, } +/// Yoda phase 3: MIME for the DnD payload carried when a user drags a +/// toolbar row in the Settings editor. A single byte = ToolbarAction +/// discriminant (see `ToolbarAction::to_u8`). +const TOOLBAR_MIME: &str = "application/x-cosmic-files-toolbar-action"; + +/// Yoda phase 3: DnD payload wrapping a ToolbarAction discriminant. +#[derive(Clone, Debug)] +pub struct ToolbarActionPayload(pub u8); + +impl cosmic::iced::clipboard::mime::AsMimeTypes for ToolbarActionPayload { + fn available(&self) -> std::borrow::Cow<'static, [String]> { + std::borrow::Cow::Owned(vec![TOOLBAR_MIME.to_owned()]) + } + fn as_bytes(&self, mime_type: &str) -> Option> { + if mime_type == TOOLBAR_MIME { + Some(std::borrow::Cow::Owned(vec![self.0])) + } else { + None + } + } +} + +impl cosmic::iced::clipboard::mime::AllowedMimeTypes for ToolbarActionPayload { + fn allowed() -> std::borrow::Cow<'static, [String]> { + std::borrow::Cow::Owned(vec![TOOLBAR_MIME.to_owned()]) + } +} + +impl TryFrom<(Vec, String)> for ToolbarActionPayload { + type Error = (); + fn try_from((data, _mime): (Vec, String)) -> Result { + if data.len() == 1 { + Ok(Self(data[0])) + } else { + Err(()) + } + } +} + +/// Yoda phase 3 helper: map a ToolbarAction to its button UI (icon name, +/// localized label, app Message). Shared by the toolbar renderer in +/// `view()` and by the Settings page row renderer so the two stay in +/// sync. +fn toolbar_action_ui(a: ToolbarAction) -> (&'static str, String, Message) { + match a { + ToolbarAction::LocationUp => ( + "go-up-symbolic", + fl!("parent-directory"), + Action::LocationUp.message(None), + ), + ToolbarAction::Reload => ( + "view-refresh-symbolic", + fl!("reload-folder"), + Action::Reload.message(None), + ), + ToolbarAction::NewFolder => ( + "folder-new-symbolic", + fl!("new-folder"), + Action::NewFolder.message(None), + ), + ToolbarAction::NewFile => ( + "document-new-symbolic", + fl!("new-file"), + Action::NewFile.message(None), + ), + ToolbarAction::Rename => ( + "pencil-symbolic", + fl!("rename"), + Action::Rename.message(None), + ), + ToolbarAction::Delete => ( + "edit-delete-symbolic", + fl!("delete"), + Action::Delete.message(None), + ), + ToolbarAction::Cut => ("edit-cut-symbolic", fl!("cut"), Action::Cut.message(None)), + ToolbarAction::Copy => ( + "edit-copy-symbolic", + fl!("copy"), + Action::Copy.message(None), + ), + ToolbarAction::Paste => ( + "edit-paste-symbolic", + fl!("paste"), + Action::Paste.message(None), + ), + ToolbarAction::ToggleShowHidden => ( + "view-reveal-symbolic", + fl!("show-hidden-files"), + Action::ToggleShowHidden.message(None), + ), + ToolbarAction::OpenTerminal => ( + "utilities-terminal-symbolic", + fl!("open-in-terminal"), + Action::OpenTerminal.message(None), + ), + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { About, @@ -154,6 +260,8 @@ pub enum Action { HistoryPrevious, ItemDown, ItemLeft, + ItemPageDown, + ItemPageUp, ItemRight, ItemUp, LocationUp, @@ -227,6 +335,8 @@ impl Action { Self::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), Self::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown), Self::ItemLeft => Message::TabMessage(entity_opt, tab::Message::ItemLeft), + Self::ItemPageDown => Message::TabMessage(entity_opt, tab::Message::ItemPageDown), + Self::ItemPageUp => Message::TabMessage(entity_opt, tab::Message::ItemPageUp), Self::ItemRight => Message::TabMessage(entity_opt, tab::Message::ItemRight), Self::ItemUp => Message::TabMessage(entity_opt, tab::Message::ItemUp), Self::LocationUp => Message::TabMessage(entity_opt, tab::Message::LocationUp), @@ -355,7 +465,7 @@ pub enum Message { ExtractToResult(DialogResult), #[cfg(all(feature = "wayland", feature = "desktop-applet"))] Focused(window::Id), - Key(window::Id, Modifiers, Key, Option), + Key(window::Id, Modifiers, Key, Physical, Option), LaunchUrl(String), MaybeExit, ModifiersChanged(window::Id, Modifiers), @@ -389,6 +499,7 @@ pub enum Message { OpenWithBrowse, OpenWithDialog(Option), OpenWithSelection(usize), + OpenWithToggleDefault(bool), #[cfg(all(feature = "wayland", feature = "desktop-applet"))] Overlap(window::Id, OverlapNotifyEvent), Paste(Option), @@ -415,6 +526,7 @@ pub enum Message { PendingPauseAll(bool), PermanentlyDelete(Option), Preview(Option), + ReloadMimeAppCache, ReorderTab(ReorderEvent), RescanRecents, RescanTrash, @@ -429,6 +541,22 @@ pub enum Message { SearchInput(String), SetShowDetails(bool), SetShowRecents(bool), + /// Yoda phase 3 — toolbar editing messages. + ToolbarAdd(ToolbarAction), + ToolbarRemove(ToolbarAction), + ToolbarReorder { + src: ToolbarAction, + target: ToolbarAction, + }, + /// Move one step up (toward index 0) inside the enabled toolbar list. + ToolbarMoveUp(ToolbarAction), + /// Move one step down (toward the end) inside the enabled toolbar list. + ToolbarMoveDown(ToolbarAction), + ToolbarReset, + /// Click on a toolbar button (via segmented_button activation). + ToolbarTabActivate(segmented_button::Entity), + /// Drag-reorder inside the toolbar (via segmented_button drag). + ToolbarTabReorder(segmented_button::ReorderEvent), SetTypeToSearch(TypeToSearch), SystemThemeModeChange, Size(window::Id, Size), @@ -558,7 +686,10 @@ pub enum DialogPage { path: PathBuf, mime: mime_guess::Mime, selected: usize, - store_opt: Option, + store_opt: Option>, + /// When true, the chosen app is written to mimeapps.list as the + /// default handler for `mime` after it spawns. + set_default: bool, }, PermanentlyDelete { paths: Box<[PathBuf]>, @@ -654,13 +785,6 @@ impl DialogPages { pub struct FavoriteIndex(usize); -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum MimeAppMatch { - Exact, - Related, - Other, -} - pub struct MounterData(MounterKey, MounterItem); #[derive(Clone, Debug)] @@ -716,6 +840,11 @@ pub struct App { nav_bar_context_id: segmented_button::Entity, nav_model: segmented_button::SingleSelectModel, tab_model: segmented_button::Model, + /// Yoda phase 3: segmented_button model mirroring config.toolbar so the + /// toolbar row gets free drag-reorder + click activation (same widget + /// that powers the tab bar, its reorder is proven to work in this + /// setup unlike the generic dnd_source/dnd_destination wrappers). + toolbar_model: segmented_button::Model, config_handler: Option, state_handler: Option, config: Config, @@ -1010,6 +1139,12 @@ impl App { .and_then(|first| first.as_ref().parent()) .map(Path::to_path_buf) { + let mut tasks = Vec::new(); + if let Some(old_dialog) = self.file_dialog_opt.take() { + let old_id = old_dialog.window_id(); + self.windows.remove(&old_id); + tasks.push(window::close(old_id)); + } let (mut dialog, dialog_task) = Dialog::new( DialogSettings::new() .kind(DialogKind::OpenFolder) @@ -1026,7 +1161,9 @@ impl App { ))), ); self.file_dialog_opt = Some(dialog); - Task::batch([set_title_task, dialog_task]) + tasks.push(set_title_task); + tasks.push(dialog_task); + Task::batch(tasks) } else { Task::none() } @@ -1071,7 +1208,7 @@ impl App { .sort_by(|a, b| (b.1.width * b.1.height).total_cmp(&(a.1.width * b.1.height))); for (w_id, overlap) in sorted_overlaps { - let Some((bl, br, tl, tr, mut size)) = self.layer_sizes.get(w_id).map(|s| { + let Some((bl, br, tl, tr, size)) = self.layer_sizes.get(w_id).map(|s| { ( Rectangle::new( Point::new(0., s.height / 2.), @@ -1121,30 +1258,18 @@ impl App { if tl && !(tr || bl) { *top += min_dim.1; *left += min_dim.0; - - size.height -= min_dim.1; - size.width -= min_dim.0; } if tr && !(tl || br) { *top += min_dim.1; *right += min_dim.0; - - size.height -= min_dim.1; - size.width -= min_dim.0; } if bl && !(br || tl) { *bottom += min_dim.1; *left += min_dim.0; - - size.height -= min_dim.1; - size.width -= min_dim.0; } if br && !(bl || tr) { *bottom += min_dim.1; *right += min_dim.0; - - size.height -= min_dim.1; - size.width -= min_dim.0; } } self.margin = overlaps; @@ -1503,12 +1628,18 @@ impl App { ) -> Task { log::info!("rescan_tab {entity:?} {location:?} {selection_paths:?}"); let icon_sizes = self.config.tab.icon_sizes; + #[cfg(feature = "gvfs")] let mounter_items = self.mounter_items.clone(); Task::future(async move { let location2 = location.clone(); match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { - Ok((parent_item_opt, mut items)) => { + Ok((parent_item_opt, items)) => { + #[cfg(feature = "gvfs")] + let mut items = items; + #[cfg(not(feature = "gvfs"))] + let items = items; + #[cfg(feature = "gvfs")] { let mounter_paths: Box<[_]> = mounter_items @@ -1678,6 +1809,7 @@ impl App { fn update_config(&mut self) -> Task { self.update_nav_model(); + self.rebuild_toolbar_model(); // Tabs are collected first to placate the borrowck let tabs: Box<[_]> = self.tab_model.iter().collect(); // Update main conf and each tab with the new config @@ -1691,6 +1823,52 @@ impl App { Task::batch(commands) } + /// Yoda phase 3: rebuild `toolbar_model` so it matches `config.toolbar`. + /// Called on init and on every config update. Each entity carries the + /// associated `ToolbarAction` as data so click/reorder handlers can + /// round-trip Entity → ToolbarAction without maintaining a side map. + /// + /// We insert ONLY the icon (no `.text()`) so the toolbar renders as a + /// clean icon row — user-visible labels stay in Settings/tooltips on + /// other surfaces. + fn rebuild_toolbar_model(&mut self) { + self.toolbar_model.clear(); + for action in self.config.toolbar.iter().copied() { + let (icon_name, _label, _msg) = toolbar_action_ui(action); + self.toolbar_model + .insert() + .icon(widget::icon::from_name(icon_name).size(16).icon()) + .data::(action); + } + } + + /// Yoda phase 3: after a drag-reorder, sync `config.toolbar` with the + /// new entity order in `toolbar_model`. Inlines what `config_set!` + /// would do (the macro lives inside update()). + fn sync_toolbar_config_from_model(&mut self) -> Task { + let new_toolbar: Vec = self + .toolbar_model + .iter() + .filter_map(|e| self.toolbar_model.data::(e).copied()) + .collect(); + if new_toolbar == self.config.toolbar { + return Task::none(); + } + match self.config_handler.as_ref() { + Some(h) => { + if let Err(err) = self.config.set_toolbar(h, new_toolbar) { + log::warn!("failed to save toolbar order: {err}"); + } + } + None => self.config.toolbar = new_toolbar, + } + // Don't call update_config() — that would rebuild the + // toolbar_model from config and undo the reorder the user just + // made. The model already has the new order; config is just + // catching up for persistence. + Task::none() + } + fn update_desktop(&mut self) -> Task { let needs_reload: Box<[_]> = (self.tab_model.iter()) .filter_map(|entity| { @@ -2268,47 +2446,132 @@ impl App { .toggler(self.config.show_recents, Message::SetShowRecents) }) .into(), + // Yoda phase 3: toolbar editor. Two stacked lists: + // - top: enabled buttons in their current order (drag to reorder) + // - bottom: available (not-yet-enabled) buttons + // Each row's toggle adds/removes; enabled rows are also + // drag sources + drop targets. + self.toolbar_settings_section(), ]) .into() } - fn get_apps_for_mime(&self, mime_type: &Mime) -> Vec<(&MimeApp, MimeAppMatch)> { - let mut results = Vec::new(); + /// Yoda phase 3: build the Toolbar settings section. + fn toolbar_settings_section(&self) -> Element<'_, Message> { + use iced::clipboard::dnd::DndAction; + let enabled = &self.config.toolbar; + let disabled: Vec = ToolbarAction::ALL + .iter() + .copied() + .filter(|a| !enabled.contains(a)) + .collect(); - let mut dedupe = FxHashSet::default(); + let space_xxs = theme::active().cosmic().spacing.space_xxs; - // start with exact matches - results.extend( - self.mime_app_cache - .get(mime_type) - .iter() - .filter(|&mime_app| dedupe.insert(&mime_app.id)) - .map(|mime_app| (mime_app, MimeAppMatch::Exact)), - ); + let drag_icon = |size: u16| -> Element<'static, Message> { + widget::icon::from_name("list-drag-handle-symbolic") + .size(size) + .into() + }; - // grab matches based off of subclass / parent mime type - if let Some(parent_types) = mime_icon::parent_mime_types(mime_type) { - for parent_type in parent_types { - results.extend( - self.mime_app_cache - .get(&parent_type) - .iter() - .filter(|&mime_app| dedupe.insert(&mime_app.id)) - .map(|mime_app| (mime_app, MimeAppMatch::Related)), - ); + let row_enabled = + |action: ToolbarAction, pos: usize, last: usize| -> Element<'_, Message> { + let (icon, label, _msg) = toolbar_action_ui(action); + let up_btn = + widget::button::icon(widget::icon::from_name("go-up-symbolic").size(14)); + let up_btn = if pos > 0 { + up_btn.on_press(Message::ToolbarMoveUp(action)) + } else { + up_btn + }; + let down_btn = + widget::button::icon(widget::icon::from_name("go-down-symbolic").size(14)); + let down_btn = if pos < last { + down_btn.on_press(Message::ToolbarMoveDown(action)) + } else { + down_btn + }; + + let row_content: Element<_> = widget::row::with_children(vec![ + drag_icon(14), + widget::icon::from_name(icon).size(16).into(), + widget::text::body(label).width(Length::Fill).into(), + up_btn.into(), + down_btn.into(), + widget::button::icon(widget::icon::from_name("list-remove-symbolic").size(14)) + .on_press(Message::ToolbarRemove(action)) + .into(), + ]) + .spacing(space_xxs) + .align_y(Alignment::Center) + .into(); + + let row_container = widget::container(row_content) + .width(Length::Fill) + .padding(space_xxs); + + // Wrap as DnD source (drags itself) + DnD destination (accepts + // drops from other enabled rows; on drop, move the src before + // this row). + let source = widget::dnd_source::(row_container) + .drag_content(move || ToolbarActionPayload(action.to_u8())); + widget::dnd_destination(source, vec![std::borrow::Cow::Borrowed(TOOLBAR_MIME)]) + .data_received_for::( + move |payload: Option| { + match payload.and_then(|p| ToolbarAction::from_u8(p.0)) { + Some(src) if src != action => Message::ToolbarReorder { + src, + target: action, + }, + // No-op if payload missing / malformed / same row. + _ => Message::ToolbarReorder { + src: action, + target: action, + }, + } + }, + ) + .action(DndAction::Move) + .into() + }; + + let row_disabled = |action: ToolbarAction| -> Element<'_, Message> { + let (icon, label, _msg) = toolbar_action_ui(action); + widget::row::with_children(vec![ + widget::icon::from_name(icon).size(16).into(), + widget::text::body(label).width(Length::Fill).into(), + widget::button::icon(widget::icon::from_name("list-add-symbolic").size(14)) + .on_press(Message::ToolbarAdd(action)) + .into(), + ]) + .spacing(space_xxs) + .align_y(Alignment::Center) + .padding(space_xxs) + .into() + }; + + let mut section = widget::settings::section().title(fl!("toolbar")); + if enabled.is_empty() { + section = section.add(widget::text::body(fl!("toolbar-empty-hint"))); + } else { + let last = enabled.len() - 1; + for (pos, a) in enabled.iter().copied().enumerate() { + section = section.add(row_enabled(a, pos, last)); } } - // Add other apps - results.extend( - self.mime_app_cache - .apps() - .iter() - .filter(|&mime_app| dedupe.insert(&mime_app.id)) - .map(|mime_app| (mime_app, MimeAppMatch::Other)), - ); - - results + let mut col = widget::column::with_capacity(3).spacing(space_xxs); + col = col.push(section); + if !disabled.is_empty() { + let mut avail = widget::settings::section().title(fl!("toolbar-available")); + for a in disabled { + avail = avail.add(row_disabled(a)); + } + col = col.push(avail); + } + col = col + .push(widget::button::standard(fl!("toolbar-reset")).on_press(Message::ToolbarReset)); + col.into() } // Update favorites based on renaming or moving dirs. @@ -2443,6 +2706,7 @@ impl Application for App { nav_bar_context_id: segmented_button::Entity::null(), nav_model: segmented_button::ModelBuilder::default().build(), tab_model: segmented_button::ModelBuilder::default().build(), + toolbar_model: segmented_button::ModelBuilder::default().build(), config_handler: flags.config_handler, state_handler: flags.state_handler, config: flags.config, @@ -2553,7 +2817,7 @@ impl Application for App { .on_middle_press(|entity| { cosmic::Action::App(Message::NavMenuAction(NavMenuAction::OpenInNewTab(entity))) }) - .context_menu(self.nav_context_menu(self.nav_bar_context_id)) + .context_menu(self.nav_context_menu()) .close_icon(icon::from_name("media-eject-symbolic").size(16).icon()) .into_container(); @@ -2568,8 +2832,8 @@ impl Application for App { fn nav_context_menu( &self, - entity: widget::nav_bar::Id, ) -> Option>>> { + let entity = self.nav_bar_context_id; let favorite_index_opt = self.nav_model.data::(entity); let location_opt = self.nav_model.data::(entity); @@ -3222,9 +3486,10 @@ impl Application for App { path, mime, selected, + set_default, .. } => { - let available_apps = self.get_apps_for_mime(&mime); + let available_apps = self.mime_app_cache.get_apps_for_mime(&mime, true); if let Some((app, _)) = available_apps.get(selected) { if let Some(mut command) = @@ -3240,6 +3505,11 @@ impl Application for App { None, ); } + // Yoda: persist as default if the user asked for it. + if set_default { + self.mime_app_cache + .set_default(mime.clone(), app.id.clone()); + } } Err(err) => { log::warn!( @@ -3351,7 +3621,7 @@ impl Application for App { return dialog.update(dialog_message); } } - Message::Key(window_id, modifiers, key, text) => { + Message::Key(window_id, modifiers, key, physical_key, text) => { #[cfg(all(feature = "wayland", feature = "desktop-applet"))] let in_surface_ids = self.surface_ids.values().any(|id| *id == window_id); #[cfg(not(all(feature = "wayland", feature = "desktop-applet")))] @@ -3359,7 +3629,7 @@ impl Application for App { if self.core.main_window_id() == Some(window_id) || in_surface_ids { let entity = self.tab_model.active(); for (key_bind, action) in &self.key_binds { - if key_bind.matches(modifiers, &key) { + if key_bind.matches(modifiers, &key, Some(&physical_key)) { return self.update(action.message(Some(entity))); } } @@ -3862,6 +4132,7 @@ impl Application for App { .and_then(|mime| { self.mime_app_cache.get(&mime).first().cloned() }), + set_default: false, }, Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()), ); @@ -3873,6 +4144,13 @@ impl Application for App { *selected = index; } } + Message::OpenWithToggleDefault(enabled) => { + if let Some(DialogPage::OpenWith { set_default, .. }) = + self.dialog_pages.front_mut() + { + *set_default = enabled; + } + } Message::Paste(entity_opt) => { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data_mut::(entity) @@ -4219,6 +4497,9 @@ impl Application for App { let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); return self.operation(Operation::RemoveFromRecents { paths }); } + Message::ReloadMimeAppCache => { + self.mime_app_cache.reload(); + } Message::RescanRecents => { return self.rescan_recents(); } @@ -4349,6 +4630,86 @@ impl Application for App { config_set!(show_recents, show_recents); return self.update_config(); } + Message::ToolbarAdd(action) => { + let mut tb = self.config.toolbar.clone(); + if !tb.contains(&action) { + tb.push(action); + } + config_set!(toolbar, tb); + return self.update_config(); + } + Message::ToolbarRemove(action) => { + let mut tb = self.config.toolbar.clone(); + tb.retain(|a| a != &action); + config_set!(toolbar, tb); + return self.update_config(); + } + Message::ToolbarReorder { src, target } => { + let mut tb = self.config.toolbar.clone(); + if let (Some(src_idx), Some(tgt_idx)) = ( + tb.iter().position(|a| a == &src), + tb.iter().position(|a| a == &target), + ) && src_idx != tgt_idx + { + // Pull src out, then insert before the target's new position. + let item = tb.remove(src_idx); + let new_tgt = if src_idx < tgt_idx { + tgt_idx - 1 + } else { + tgt_idx + }; + tb.insert(new_tgt, item); + config_set!(toolbar, tb); + return self.update_config(); + } + return Task::none(); + } + Message::ToolbarMoveUp(action) => { + let mut tb = self.config.toolbar.clone(); + if let Some(i) = tb.iter().position(|a| a == &action) + && i > 0 + { + tb.swap(i, i - 1); + config_set!(toolbar, tb); + return self.update_config(); + } + return Task::none(); + } + Message::ToolbarMoveDown(action) => { + let mut tb = self.config.toolbar.clone(); + if let Some(i) = tb.iter().position(|a| a == &action) + && i + 1 < tb.len() + { + tb.swap(i, i + 1); + config_set!(toolbar, tb); + return self.update_config(); + } + return Task::none(); + } + Message::ToolbarReset => { + config_set!(toolbar, default_toolbar()); + return self.update_config(); + } + Message::ToolbarTabActivate(entity) => { + // Dispatch the stored ToolbarAction's message, then clear + // the "active" selection so the button doesn't stay + // highlighted after a click (we use segmented_button for + // layout/drag but toolbar buttons are action-firing, not + // a mutual-exclusive choice). + let action = self.toolbar_model.data::(entity).copied(); + self.toolbar_model.deactivate(); + if let Some(action) = action { + let (_, _, msg) = toolbar_action_ui(action); + return self.update(msg); + } + return Task::none(); + } + Message::ToolbarTabReorder(event) => { + let _ = self + .toolbar_model + .reorder(event.dragged, event.target, event.position); + return self.sync_toolbar_config_from_model(); + } Message::SetTypeToSearch(type_to_search) => { config_set!(type_to_search, type_to_search); return self.update_config(); @@ -4524,6 +4885,7 @@ impl Application for App { commands.push(self.update(Message::CheckClipboard)); commands.push(self.update(Message::Surface( cosmic::surface::action::app_popup( + |_: &Self| cosmic::surface::action::LiveSettings::default(), move |app: &mut Self| -> SctkPopupSettings { let anchor_rect = Rectangle { x: point.x as i32, @@ -5108,6 +5470,7 @@ impl Application for App { .and_then(|mime| { self.mime_app_cache.get(&mime).first().cloned() }), + set_default: false, }, None, ); @@ -5951,7 +6314,7 @@ impl Application for App { mime, selected, store_opt, - .. + set_default, } => { let name = match path.file_name() { Some(file_name) => file_name.to_str(), @@ -5959,7 +6322,7 @@ impl Application for App { }; let mut column = widget::list_column(); - let available_apps = self.get_apps_for_mime(mime); + let available_apps = self.mime_app_cache.get_apps_for_mime(mime, true); let item_height = 32.0; let mut displayed_default = false; let mut last_kind = MimeAppMatch::Exact; @@ -5980,8 +6343,8 @@ impl Application for App { widget::mouse_area( widget::button::custom( widget::row::with_children([ - icon(app.icon.clone()).size(32).into(), - if app.is_default && !displayed_default { + icon(app.icon()).size(32).into(), + if app.is_default(mime) && !displayed_default { displayed_default = true; widget::text::body(fl!( "default-app", @@ -6036,7 +6399,21 @@ impl Application for App { } else { Length::Shrink } - })); + })) + // Yoda: let the user make this choice stick. A plain row + // instead of settings::item::builder because the latter + // returns a section Item, not an Element usable in .control(). + .control( + widget::row::with_children([ + widget::text::body(fl!("open-with-set-default")).into(), + widget::space::horizontal().into(), + widget::toggler(*set_default) + .on_toggle(Message::OpenWithToggleDefault) + .into(), + ]) + .spacing(space_s) + .align_y(Alignment::Center), + ); if let Some(app) = store_opt { dialog = dialog.tertiary_action( @@ -6442,7 +6819,10 @@ impl Application for App { /// Creates a view after each update. fn view(&self) -> Element<'_, Self::Message> { let cosmic_theme::Spacing { - space_xxs, space_s, .. + space_xxs, + space_xs, + space_s, + .. } = theme::spacing(); let mut tab_column = widget::column::with_capacity(4); @@ -6486,6 +6866,36 @@ impl Application for App { ); } + // Yoda phase 3: Dolphin-style quick actions toolbar via + // segmented_button::horizontal — the same widget that powers the + // tab bar, so its built-in drag reorder works reliably (unlike the + // generic dnd_source+dnd_destination pairing we tried earlier). + // Short click = action (ToolbarTabActivate → dispatch the stored + // ToolbarAction's message). Drag past threshold = reorder + // (ToolbarTabReorder → model.reorder + sync to config). + if !self.config.toolbar.is_empty() { + // Use Control style (no TabBar underline, no bottom border) + // and let each button shrink to its icon. Spacing = space_xs + // keeps the buttons visually separated so it looks like an + // icon toolbar rather than a conjoined segmented control. + let toolbar = widget::segmented_button::horizontal(&self.toolbar_model) + .style(theme::SegmentedButton::Control) + .button_height(32) + .button_spacing(space_xs) + .button_alignment(Alignment::Center) + .minimum_button_width(32) + .maximum_button_width(32) + .enable_tab_drag(String::from("x-cosmic-files/toolbar-dnd")) + .on_reorder(Message::ToolbarTabReorder) + .tab_drag_threshold(8.) + .on_activate(Message::ToolbarTabActivate); + tab_column = tab_column.push( + widget::container(toolbar) + .width(Length::Shrink) + .padding([space_xxs, space_s]), + ); + } + let entity = self.tab_model.active(); if let Some(tab) = self.tab_model.data::(entity) { let tab_view = tab @@ -6633,11 +7043,14 @@ impl Application for App { }, Event::Keyboard(KeyEvent::KeyPressed { key, + physical_key, modifiers, text, .. }) => match status { - event::Status::Ignored => Some(Message::Key(window_id, modifiers, key, text)), + event::Status::Ignored => { + Some(Message::Key(window_id, modifiers, key, physical_key, text)) + } event::Status::Captured => None, }, Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { @@ -6769,6 +7182,22 @@ impl Application for App { }, ) }), + #[cfg(feature = "desktop")] + Subscription::run(|| { + stream::channel( + 1, + |mut output: futures::channel::mpsc::Sender| async move { + mime_app::watch(move || { + futures::executor::block_on(async { + _ = output.send(Message::ReloadMimeAppCache).await; + }) + }) + .await; + + std::future::pending().await + }, + ) + }), Subscription::run_with(TypeId::of::(), |_| { stream::channel( 1, diff --git a/src/archive.rs b/src/archive.rs index cff0c1e..d51b01b 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,5 +1,5 @@ use crate::mime_icon::mime_for_path; -use crate::operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk}; +use crate::operation::{Controller, OpReader, OperationError, OperationErrorType}; use cosmic::iced::futures; use jiff::Zoned; use jiff::civil::DateTime; @@ -45,7 +45,7 @@ pub fn extract( new_dir: &Path, password: &Option, controller: &Controller, -) -> Result<(), OperationError> { +) -> Result<(Vec, HashSet), OperationError> { let mime = mime_for_path(path, None, false); let password = password.as_deref(); match mime.essence_str() { @@ -55,13 +55,15 @@ pub fn extract( .map(flate2::read::GzDecoder::new) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))?; + .map_err(|e| OperationError::from_err(e, controller)) + .map(|_| Default::default()) } "application/x-tar" => OpReader::new(path, controller.clone()) .map(io::BufReader::new) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))?, + .map_err(|e| OperationError::from_err(e, controller)) + .map(|_| Default::default()), "application/zip" => fs::File::open(path) .map(io::BufReader::new) .map(zip::ZipArchive::new) @@ -75,7 +77,7 @@ pub fn extract( OperationError::from_kind(OperationErrorType::PasswordRequired, controller) } _ => OperationError::from_err(e, controller), - })?, + }), #[cfg(feature = "bzip2")] "application/x-bzip" | "application/x-bzip-compressed-tar" @@ -85,7 +87,8 @@ pub fn extract( .map(bzip2::read::BzDecoder::new) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))?, + .map_err(|e| OperationError::from_err(e, controller)) + .map(|_| Default::default()), #[cfg(feature = "lzma-rust2")] "application/x-xz" | "application/x-xz-compressed-tar" => { OpReader::new(path, controller.clone()) @@ -93,14 +96,14 @@ pub fn extract( .map(|reader| lzma_rust2::XzReader::new(reader, true)) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))?; + .map_err(|e| OperationError::from_err(e, controller)) + .map(|_| Default::default()) } _ => Err(OperationError::from_err( format!("unsupported mime type {mime:?}"), controller, - ))?, + )), } - Ok(()) } // From https://docs.rs/zip/latest/zip/read/struct.ZipArchive.html#method.extract, with cancellation and progress added @@ -109,7 +112,7 @@ fn zip_extract>( directory: P, password: Option<&str>, controller: Controller, -) -> zip::result::ZipResult<()> { +) -> zip::result::ZipResult<(Vec, HashSet)> { use std::ffi::OsString; use std::fs; use zip::result::ZipError; @@ -282,10 +285,7 @@ fn zip_extract>( } } - // Flush files to disk - futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await }); - - Ok(()) + Ok((written_files, target_dirs)) } fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option { diff --git a/src/clipboard.rs b/src/clipboard.rs index 7af2f9f..7d06be5 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -130,9 +130,11 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { match mime.as_str() { "text/uri-list" => { let text = str::from_utf8(&data)?; - let _lines = text.lines(); - for line in text.lines() { + for line in text.lines().filter(|line| { + let line = line.trim(); + !line.is_empty() && !line.starts_with('#') + }) { let url = Url::parse(line)?; match url.to_file_path() { Ok(path) => paths.push(path), diff --git a/src/config.rs b/src/config.rs index 2138d83..8f493c0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -170,6 +170,11 @@ pub struct Config { pub show_details: bool, pub show_recents: bool, pub tab: TabConfig, + /// Yoda phase 3: Dolphin-style quick actions toolbar. An ordered list + /// of enabled buttons — position in the vec drives the toolbar order. + /// Reorder in Settings via drag-drop; items not in the vec are + /// hidden. Default = the minimal-6 set from phase 1. + pub toolbar: Vec, pub type_to_search: TypeToSearch, } @@ -234,11 +239,97 @@ impl Default for Config { show_details: false, show_recents: true, tab: TabConfig::default(), + toolbar: default_toolbar(), type_to_search: TypeToSearch::Recursive, } } } +/// Yoda phase 3: ordered enum of quick-action toolbar buttons. +/// The Config stores `Vec` so the user can pick BOTH +/// visibility (just include/exclude the variant) AND order (position in +/// the vec). Drag-drop reorder in the Settings page moves items around. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum ToolbarAction { + LocationUp, + Reload, + NewFolder, + NewFile, + Rename, + Delete, + Cut, + Copy, + Paste, + ToggleShowHidden, + OpenTerminal, +} + +impl ToolbarAction { + /// Stable list of every supported action. Ordered roughly by logical + /// grouping (location → create/edit → clipboard → view/misc) so that + /// the default enabled set follows a sensible shape and the Settings + /// row for a not-yet-enabled action lands in a predictable spot. + pub const ALL: &'static [Self] = &[ + Self::LocationUp, + Self::Reload, + Self::NewFolder, + Self::NewFile, + Self::Rename, + Self::Delete, + Self::Cut, + Self::Copy, + Self::Paste, + Self::ToggleShowHidden, + Self::OpenTerminal, + ]; + + /// u8 discriminant used to carry the action over a DnD mime payload. + pub const fn to_u8(self) -> u8 { + match self { + Self::LocationUp => 0, + Self::Reload => 1, + Self::NewFolder => 2, + Self::NewFile => 3, + Self::Rename => 4, + Self::Delete => 5, + Self::Cut => 6, + Self::Copy => 7, + Self::Paste => 8, + Self::ToggleShowHidden => 9, + Self::OpenTerminal => 10, + } + } + + pub const fn from_u8(v: u8) -> Option { + match v { + 0 => Some(Self::LocationUp), + 1 => Some(Self::Reload), + 2 => Some(Self::NewFolder), + 3 => Some(Self::NewFile), + 4 => Some(Self::Rename), + 5 => Some(Self::Delete), + 6 => Some(Self::Cut), + 7 => Some(Self::Copy), + 8 => Some(Self::Paste), + 9 => Some(Self::ToggleShowHidden), + 10 => Some(Self::OpenTerminal), + _ => None, + } + } +} + +/// Default set shown on a fresh install — same "minimal 6" as phase 1/2. +pub fn default_toolbar() -> Vec { + vec![ + ToolbarAction::NewFolder, + ToolbarAction::Rename, + ToolbarAction::Delete, + ToolbarAction::Cut, + ToolbarAction::Copy, + ToolbarAction::Paste, + ] +} + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)] #[serde(default)] pub struct DesktopConfig { diff --git a/src/dialog.rs b/src/dialog.rs index 69714f9..c760472 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -6,7 +6,7 @@ use cosmic::app::{Core, Task, context_drawer}; use cosmic::iced::core::SmolStr; use cosmic::iced::core::widget::operation; use cosmic::iced::futures::{self, SinkExt}; -use cosmic::iced::keyboard::key::Named; +use cosmic::iced::keyboard::key::{Named, Physical}; use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers}; use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper}; use cosmic::iced::widget::scrollable; @@ -40,7 +40,7 @@ use crate::localize::LANGUAGE_SORTER; use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage}; use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab}; use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default}; -use crate::{fl, home_dir, menu}; +use crate::{fl, home_dir, menu, mime_icon}; #[derive(Clone, Debug)] pub struct DialogMessage(cosmic::Action); @@ -456,7 +456,7 @@ enum Message { Escape, Filename(String), Filter(usize), - Key(Modifiers, Key, Option), + Key(Modifiers, Key, Physical, Option), ModifiersChanged(Modifiers), MounterItems(MounterKey, MounterItems), Mouse(window::Id, mouse::Button), @@ -732,11 +732,17 @@ impl App { fn rescan_tab(&self, selection_paths: Option>) -> Task { let location = self.tab.location.clone(); let icon_sizes = self.tab.config.icon_sizes; + #[cfg(feature = "gvfs")] let mounter_items = self.mounter_items.clone(); Task::future(async move { let location2 = location.clone(); match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { - Ok((parent_item_opt, mut items)) => { + Ok((parent_item_opt, items)) => { + #[cfg(feature = "gvfs")] + let mut items = items; + #[cfg(not(feature = "gvfs"))] + let items = items; + #[cfg(feature = "gvfs")] { let mounter_paths: Box<[_]> = mounter_items @@ -1450,16 +1456,16 @@ impl Application for App { } return self.rescan_tab(None); } - Message::Key(modifiers, key, text) => { + Message::Key(modifiers, key, physical_key, text) => { for (key_bind, action) in &self.key_binds { - if key_bind.matches(modifiers, &key) { + if key_bind.matches(modifiers, &key, Some(&physical_key)) { return self.update(Message::from(action.message())); } } // Check key binds from accept label if let Some(key_bind) = &self.accept_label.key_bind_opt - && key_bind.matches(modifiers, &key) + && key_bind.matches(modifiers, &key, Some(&physical_key)) { return self.update(if self.flags.kind.save() { Message::Save(false) @@ -1795,6 +1801,7 @@ impl Application for App { let autosize_id = widget::Id::unique(); commands.push(self.update(Message::Surface( cosmic::surface::action::app_popup( + |_: &Self| cosmic::surface::action::LiveSettings::default(), move |app: &mut Self| -> SctkPopupSettings { let anchor_rect = Rectangle { x: point.x as i32, @@ -1919,11 +1926,13 @@ impl Application for App { items.retain(|item| { // Directories are always shown item.metadata.is_dir() + // Check for mime type match (first because it is faster) || mimes.iter().any(|filter_mime| { if filter_mime.subtype() == mime::STAR { filter_mime.type_() == item.mime.type_() } else { *filter_mime == item.mime + || mime_icon::is_mime_subclass_of(&item.mime, filter_mime) } }) // Check for glob match (last because it is slower) @@ -2050,11 +2059,14 @@ impl Application for App { }, Event::Keyboard(KeyEvent::KeyPressed { key, + physical_key, modifiers, text, .. }) => match status { - event::Status::Ignored => Some(Message::Key(modifiers, key, text)), + event::Status::Ignored => { + Some(Message::Key(modifiers, key, physical_key, text)) + } event::Status::Captured => { if key == Key::Named(Named::Escape) { Some(Message::Escape) diff --git a/src/key_bind.rs b/src/key_bind.rs index f47d403..d3ddd59 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -30,12 +30,16 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap { bind!([], Key::Named(Named::F5), Reload); bind!([], Key::Named(Named::Home), SelectFirst); bind!([], Key::Named(Named::End), SelectLast); + bind!([], Key::Named(Named::PageDown), ItemPageDown); + bind!([], Key::Named(Named::PageUp), ItemPageUp); bind!([Shift], Key::Named(Named::ArrowDown), ItemDown); bind!([Shift], Key::Named(Named::ArrowLeft), ItemLeft); bind!([Shift], Key::Named(Named::ArrowRight), ItemRight); bind!([Shift], Key::Named(Named::ArrowUp), ItemUp); bind!([Shift], Key::Named(Named::Home), SelectFirst); bind!([Shift], Key::Named(Named::End), SelectLast); + bind!([Shift], Key::Named(Named::PageDown), ItemPageDown); + bind!([Shift], Key::Named(Named::PageUp), ItemPageUp); bind!([Ctrl, Shift], Key::Character("n".into()), NewFolder); bind!([], Key::Named(Named::Enter), Open); bind!([Ctrl], Key::Character(" ".into()), Preview); diff --git a/src/lib.rs b/src/lib.rs index f2d04d7..44b87f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ mod zoom; pub(crate) type FxOrderMap = ordermap::OrderMap; +#[cfg(feature = "gvfs")] pub(crate) fn err_str(err: T) -> String { err.to_string() } diff --git a/src/menu.rs b/src/menu.rs index 4132fbf..0937595 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -10,6 +10,7 @@ use cosmic::widget::{ self, Row, button, column, container, divider, responsive_menu_bar, space, text, }; use cosmic::{Element, theme}; +#[cfg(feature = "desktop")] use i18n_embed::LanguageLoader; use mime_guess::Mime; use std::collections::HashMap; @@ -18,7 +19,9 @@ use std::sync::LazyLock; use crate::app::{Action, Message}; use crate::config::{Config, ContextActionPreset}; use crate::fl; -use crate::tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab}; +use crate::tab::{ + self, HeadingOptions, ItemMetadata, Location, LocationMenuAction, SearchLocation, Tab, +}; use crate::trash::{Trash, TrashExt}; static MENU_ID: LazyLock = @@ -67,17 +70,19 @@ pub fn context_menu<'a>( String::new() }; fn key_style(theme: &cosmic::Theme) -> TextStyle { - let mut color = theme.cosmic().background.component.on; + let mut color = theme.cosmic().background(false).component.on; color.alpha *= 0.75; TextStyle { color: Some(color.into()), + ..Default::default() } } fn disabled_style(theme: &cosmic::Theme) -> TextStyle { - let mut color = theme.cosmic().background.component.on; + let mut color = theme.cosmic().background(false).component.on; color.alpha *= 0.5; TextStyle { color: Some(color.into()), + ..Default::default() } } @@ -105,18 +110,25 @@ pub fn context_menu<'a>( let (sort_name, sort_direction, _) = tab.sort_options(); let sort_item = |label, variant| { - menu_item( - format!( - "{} {}", - label, - match (sort_name == variant, sort_direction) { - (true, true) => "\u{2B07}", - (true, false) => "\u{2B06}", - _ => "", - } - ), - Action::ToggleSort(variant), + let key = find_key(&Action::ToggleSort(variant)); + let leading: Element<'a, tab::Message> = if sort_name == variant { + let icon_name = if sort_direction { + "view-sort-ascending-symbolic" + } else { + "view-sort-descending-symbolic" + }; + widget::icon::from_name(icon_name).size(14).into() + } else { + space::horizontal().width(Length::Fixed(14.0)).into() + }; + menu_button!( + leading, + space::horizontal().width(Length::Fixed(theme::spacing().space_xxs.into())), + text::body(label), + space::horizontal(), + text::body(key).class(theme::Text::Custom(key_style)) ) + .on_press(tab::Message::ContextAction(Action::ToggleSort(variant))) .into() }; @@ -126,6 +138,7 @@ pub fn context_menu<'a>( let mut selected_desktop_entry = None; let mut selected_types: Vec = vec![]; let mut selected_mount_point = 0; + let mut any_trash_item = false; if let Some(items) = tab.items_opt() { for item in items { if item.selected { @@ -146,6 +159,9 @@ pub fn context_menu<'a>( } _ => (), } + if matches!(&item.metadata, ItemMetadata::Trash { .. }) { + any_trash_item = true; + } selected_types.push(item.mime.clone()); } } @@ -190,11 +206,11 @@ pub fn context_menu<'a>( if !Trash::is_empty() { children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into()); } - } else if let Some(entry) = selected_desktop_entry { + } else if let Some(_entry) = selected_desktop_entry { children.push(menu_item(fl!("open"), Action::Open).into()); #[cfg(feature = "desktop")] { - children.extend(entry.desktop_actions.into_iter().enumerate().map( + children.extend(_entry.desktop_actions.into_iter().enumerate().map( |(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(), )); } @@ -267,27 +283,38 @@ pub fn context_menu<'a>( //TODO: Print? children.push(menu_item(fl!("show-details"), Action::Preview).into()); - if matches!(tab.mode, tab::Mode::App) { + if any_trash_item { children.push(divider::horizontal::light().into()); - children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); - } - children.push(divider::horizontal::light().into()); - if tab.location.is_recents() { children.push( - menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(), + menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into(), ); children.push(divider::horizontal::light().into()); - } - if selected_mount_point == 0 { - if modifiers.shift() && !modifiers.control() { - children.push( - menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(), - ); - } else { - children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); + children.push(menu_item(fl!("delete-permanently"), Action::Delete).into()); + } else { + if matches!(tab.mode, tab::Mode::App) { + children.push(divider::horizontal::light().into()); + children + .push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); + } + children.push(divider::horizontal::light().into()); + if tab.location.is_recents() { + children.push( + menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(), + ); + children.push(divider::horizontal::light().into()); + } + if selected_mount_point == 0 { + if modifiers.shift() && !modifiers.control() { + children.push( + menu_item(fl!("delete-permanently"), Action::PermanentlyDelete) + .into(), + ); + } else { + children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); + } + } else if selected == 1 { + children.push(menu_item(fl!("eject"), Action::Eject).into()); } - } else if selected == 1 { - children.push(menu_item(fl!("eject"), Action::Eject).into()); } } else { //TODO: need better designs for menu with no selection @@ -415,7 +442,7 @@ pub fn context_menu<'a>( //TODO: move style to libcosmic .style(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background.component; + let component = &cosmic.background(false).component; container::Style { icon_color: Some(component.on.into()), text_color: Some(component.on.into()), @@ -809,7 +836,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess .padding(1) .style(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background.component; + let component = &cosmic.background(false).component; container::Style { icon_color: Some(component.on.into()), text_color: Some(component.on.into()), diff --git a/src/mime_app.rs b/src/mime_app.rs index 8695d79..8fd8a60 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -2,18 +2,68 @@ // SPDX-License-Identifier: GPL-3.0-only use bstr::{BString, ByteSlice, ByteVec}; -#[cfg(feature = "desktop")] -use cosmic::desktop; use cosmic::widget; pub use mime_guess::Mime; -use rustc_hash::FxHashMap; -use std::cmp::Ordering; +#[cfg(feature = "desktop")] +use notify_debouncer_full::notify; +use rustc_hash::{FxHashMap, FxHashSet}; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -use std::time::Instant; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, RwLock, atomic}; +use std::time::{self, Instant}; use std::{fs, io, process}; +#[cfg(feature = "desktop")] +pub async fn watch(mut emitter: impl FnMut() + 'static + Send) { + let watcher_result = notify_debouncer_full::new_debouncer( + time::Duration::from_millis(250), + Some(time::Duration::from_millis(250)), + move |event_res: notify_debouncer_full::DebounceEventResult| { + let Ok(events) = event_res else { + return; + }; + + if events.iter().any(|event| { + event.kind.is_create() || event.kind.is_modify() || event.kind.is_remove() + }) { + emitter(); + } + }, + ); + + if let Ok(mut watcher) = watcher_result { + let system_paths = cosmic_mime_apps::list_paths(); + let local_paths = (|| { + let base_dirs = xdg::BaseDirectories::new(); + let Some(home) = base_dirs.get_config_home() else { + return Err(std::io::Error::other("XDG config home not set")); + }; + + let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") else { + return Err(std::io::Error::other("XDG_CURRENT_DESKTOP unset")); + }; + + let default_mimeapps = home.join("mimeapps.list"); + let desktop_mimeapps = + home.join([&desktop.to_ascii_lowercase(), "-mimeapps.list"].concat()); + + Ok([desktop_mimeapps, default_mimeapps]) + })() + .ok(); + + for path in system_paths + .iter() + .chain(local_paths.as_ref().into_iter().flatten()) + { + _ = watcher.watch(path.as_path(), notify::RecursiveMode::NonRecursive); + } + + std::future::pending().await + } +} + pub fn exec_to_command( exec: &str, entry_name: &str, @@ -126,14 +176,23 @@ pub fn exec_to_command( Some(commands) } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MimeAppMatch { + Exact, + Related, + Other, +} + #[derive(Clone, Debug)] pub struct MimeApp { pub id: String, pub path: Option, pub name: String, pub exec: Option, - pub icon: widget::icon::Handle, - pub is_default: bool, + icon_name: Box, + icon: std::sync::OnceLock, + is_default: Arc>>>, + no_display: Arc, } impl MimeApp { @@ -146,6 +205,27 @@ impl MimeApp { path_opt, ) } + + pub fn is_default(&self, mime: &Mime) -> bool { + self.is_default.read().unwrap().contains(mime.essence_str()) + } + + pub fn no_display(&self) -> bool { + self.no_display.load(atomic::Ordering::Relaxed) + } + + pub fn icon(&self) -> widget::icon::Handle { + self.icon + .get_or_init(|| { + let name = &*self.icon_name; + if name.starts_with('/') { + cosmic::widget::icon::from_path(PathBuf::from(name)) + } else { + cosmic::widget::icon::from_name(name).size(32).handle() + } + }) + .clone() + } } // This allows usage of MimeApp in a dropdown @@ -155,38 +235,10 @@ impl AsRef for MimeApp { } } -#[cfg(feature = "desktop")] -impl From<&desktop::DesktopEntryData> for MimeApp { - fn from(app: &desktop::DesktopEntryData) -> Self { - Self { - id: app.id.clone(), - path: app.path.clone(), - name: app.name.clone(), - exec: app.exec.clone(), - icon: match &app.icon { - desktop::fde::IconSource::Name(name) => { - widget::icon::from_name(name.as_str()).size(32).handle() - } - desktop::fde::IconSource::Path(path) => widget::icon::from_path(path.clone()), - }, - is_default: false, - } - } -} - -#[cfg(feature = "desktop")] -fn filename_eq(path_opt: &Option, filename: &str) -> bool { - path_opt - .as_ref() - .and_then(|path| path.file_name()) - .is_some_and(|x| x == filename) -} - pub struct MimeAppCache { - apps: Vec, - cache: FxHashMap>, - icons: FxHashMap>, - terminals: Vec, + apps: Vec>, + cache: FxHashMap>>, + terminals: Vec>, } impl MimeAppCache { @@ -194,154 +246,273 @@ impl MimeAppCache { let mut mime_app_cache = Self { apps: Vec::new(), cache: FxHashMap::default(), - icons: FxHashMap::default(), terminals: Vec::new(), }; mime_app_cache.reload(); mime_app_cache } + pub fn get_apps_for_mime( + &self, + mime_type: &Mime, + include_other: bool, + ) -> Vec<(&Arc, MimeAppMatch)> { + let mut results = Vec::new(); + let mut dedupe = FxHashSet::default(); + + // start with exact matches + results.extend( + self.get(mime_type) + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Exact)), + ); + + let include_mime = match mime_type.type_().as_str() { + "audio" => Some("video/mp4".parse::().expect("video/mp4 mime")), + "text" => Some(mime_guess::mime::TEXT_PLAIN), + _ => None, + }; + + if let Some(mime) = include_mime { + results.extend( + self.get(&mime) + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Exact)), + ); + } + + // grab matches based off of subclass / parent mime type + if let Some(parent_types) = crate::mime_icon::parent_mime_types(mime_type) { + for parent_type in parent_types { + results.extend( + self.get(&parent_type) + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Related)), + ); + } + } + + if include_other { + results.extend({ + let mut apps = self + .apps() + .iter() + .filter(|mime_app| !mime_app.no_display()) + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Other)) + .collect::>(); + apps.sort_by(|(a, _), (b, _)| { + crate::localize::LANGUAGE_SORTER.compare(&a.name, &b.name) + }); + apps + }); + } + + results + } + #[cfg(not(feature = "desktop"))] pub fn reload(&mut self) {} - // Only available when using desktop feature of libcosmic, which only works on Unix-likes + /// Reload mime types and their known app associations and defaults. #[cfg(feature = "desktop")] pub fn reload(&mut self) { use crate::localize::LANGUAGE_SORTER; + use crate::mime_icon; + use cosmic::desktop::fde; + use std::borrow::Cow; let start = Instant::now(); self.apps.clear(); self.cache.clear(); - self.icons.clear(); self.terminals.clear(); - //TODO: get proper locale? - let locale = &[]; - - // Load desktop applications by supported mime types - //TODO: hashmap for all apps by id? - let all_apps: Box<[_]> = desktop::load_applications(locale, false, None).collect(); - for app in &all_apps { - //TODO: just collect apps that can be executed with a file argument? - if !app.mime_types.is_empty() { - self.apps.push(MimeApp::from(app)); - } - for mime in &app.mime_types { - let apps = self - .cache - .entry(mime.clone()) - .or_insert_with(|| Vec::with_capacity(1)); - if !apps.iter().any(|x| x.id == app.id) { - apps.push(MimeApp::from(app)); - } - } - for category in &app.categories { - if category == "TerminalEmulator" { - self.terminals.push(MimeApp::from(app)); - break; - } - } - } - let mut list = cosmic_mime_apps::List::default(); - let paths = cosmic_mime_apps::list_paths(); list.load_from_paths(&paths); + let locales = fde::get_languages_from_env(); + let desktop_entries = fde::Iter::new(fde::default_paths()).entries(Some(&locales)); + let mime_icon_cache = mime_icon::MIME_ICON_CACHE.lock().unwrap(); + let shared_mime_info = &mime_icon_cache.shared_mime_info; + let mut aliased_mimes = FxHashMap::default(); - for (mime, filenames) in list - .added_associations - .iter() - .chain(list.default_apps.iter()) - { - for filename in filenames { - log::trace!("add {mime}={filename}"); - let apps = self - .cache - .entry(mime.clone()) - .or_insert_with(|| Vec::with_capacity(1)); - if !apps.iter().any(|x| filename_eq(&x.path, filename)) { - if let Some(app) = all_apps.iter().find(|&x| filename_eq(&x.path, filename)) { - apps.push(MimeApp::from(app)); - } else { - log::info!( - "failed to add association for {mime:?}: application {filename:?} not found" - ); + for desktop_entry in desktop_entries { + let name = desktop_entry + .name(&locales) + .unwrap_or_else(|| Cow::Borrowed(desktop_entry.id())); + + let app = Arc::new(MimeApp { + id: desktop_entry.appid.clone(), + path: Some(desktop_entry.path.clone()), + name: name.into(), + exec: desktop_entry.exec().map(String::from), + icon_name: desktop_entry.icon().unwrap_or_default().into(), + icon: std::sync::OnceLock::new(), + is_default: Arc::new(RwLock::default()), + no_display: Arc::new(AtomicBool::new(false)), + }); + + tracing::info!(target: "mime-apps", id = app.id, "detected desktop entry"); + + self.apps.push(app.clone()); + + if desktop_entry + .categories() + .into_iter() + .flatten() + .any(|c| c == "TerminalEmulator") + { + self.terminals.push(app.clone()); + } + + // Cache associations defined by the desktop entry. + let mime_types = desktop_entry.mime_type().unwrap_or_else(Vec::new); + let associated_mime_types = mime_types.iter().filter_map(|m| { + m.parse::().ok().map(|mime| { + if let Some(unaliased) = shared_mime_info.unalias_mime_type(&mime) { + aliased_mimes.insert(unaliased.clone(), mime); + return unaliased; + } + + mime + }) + }); + + for mime in associated_mime_types { + let apps = self.cache.entry(mime.clone()).or_default(); + if apps.iter().all(|cached_app| cached_app.id != app.id) { + apps.push(app.clone()); + } + } + } + + // Cache added associations from mimeapps lists. + for (mut added_mime, added_apps) in &list.added_associations { + let _unaliased; + if let Some(unaliased) = shared_mime_info.unalias_mime_type(added_mime) { + aliased_mimes.insert(unaliased.clone(), added_mime.clone()); + _unaliased = unaliased; + added_mime = &_unaliased; + } + + for added_app in added_apps { + if let Some(app) = self + .apps + .iter() + .find(|cached| cached.id.as_str() == added_app.as_ref()) + { + let apps = self.cache.entry(added_mime.clone()).or_default(); + if apps.iter().all(|cached_app| cached_app.id != app.id) { + apps.push(app.clone()); } } } } - for (mime, filenames) in list.removed_associations.iter() { - for filename in filenames { - log::trace!("remove {mime}={filename}"); - if let Some(apps) = self.cache.get_mut(mime) { - apps.retain(|x| !filename_eq(&x.path, filename)); + // Remove associations + for (mut removed_mime, removed_apps) in &list.removed_associations { + let _unaliased; + if let Some(unaliased) = shared_mime_info.unalias_mime_type(removed_mime) { + aliased_mimes.insert(unaliased.clone(), removed_mime.clone()); + _unaliased = unaliased; + removed_mime = &_unaliased; + } + + for removed_app in removed_apps { + if let Some(app) = self + .apps + .iter() + .find(|cached| cached.id.as_str() == removed_app.as_ref()) + && let Some(apps) = self.cache.get_mut(removed_mime) + { + apps.retain(|cached_app| cached_app.id != app.id); } } } - for (mime, filenames) in list.default_apps.iter() { - for filename in filenames { - log::trace!("default {mime}={filename}"); - if let Some(apps) = self.cache.get_mut(mime) { - let mut found = false; - for app in apps.iter_mut() { - if filename_eq(&app.path, filename) { - app.is_default = true; - found = true; - } else { - app.is_default = false; - } - } + // Fetch defaults and sort apps by their default precedence. + for (mime, mut apps) in std::mem::take(&mut self.cache).into_iter() { + let defaults = list.default_app_for(&mime); + let aliased_defaults = aliased_mimes + .get(&mime) + .and_then(|mime| list.default_app_for(mime)); + + let cache = self + .cache + .entry(mime.clone()) + .or_insert_with(|| Vec::with_capacity(apps.len())); + + // Sort cached apps for this mime by default precedence. + for default in defaults + .into_iter() + .flatten() + .chain(aliased_defaults.into_iter().flatten()) + { + let default = default.strip_suffix(".desktop").unwrap_or(default.as_ref()); + let mut found_any = false; + apps.retain(|app| { + let found = app.id.as_str() == default; if found { - break; + app.is_default + .write() + .unwrap() + .insert(mime.essence_str().into()); + cache.push(app.clone()); + found_any = true; } - log::debug!( - "failed to set default for {mime:?}: application {filename:?} not found" - ); + + !found + }); + + if !found_any && let Some(app) = self.apps.iter().find(|app| app.id == default) { + app.is_default + .write() + .unwrap() + .insert(mime.essence_str().into()); + cache.push(app.clone()); } } + + // Sort remaining apps by name + apps.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.name, &b.name)); + cache.extend_from_slice(&apps); + + tracing::debug!(target: "mime-apps", mime = mime.essence_str(), apps = ?(cache.iter().map(|app| &*app.id).collect::>()), "mime defaults found") } - // Sort apps by name - self.apps - .sort_by(|a, b| match (a.is_default, b.is_default) { - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - _ => LANGUAGE_SORTER.compare(&a.name, &b.name), - }); - for apps in self.cache.values_mut() { - apps.sort_by(|a, b| match (a.is_default, b.is_default) { - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - _ => LANGUAGE_SORTER.compare(&a.name, &b.name), - }); + let associated: rustc_hash::FxHashSet<&str> = self + .cache + .values() + .flatten() + .map(|app| app.id.as_str()) + .collect(); + for app in &self.apps { + app.no_display.store( + !associated.contains(app.id.as_str()), + atomic::Ordering::Relaxed, + ); } - // Copy icons to special cache - //TODO: adjust dropdown API so this is no longer needed - self.icons.extend(self.cache.iter().map(|(mime, apps)| { - ( - mime.clone(), - apps.iter().map(|app| app.icon.clone()).collect(), - ) - })); - let elapsed = start.elapsed(); - log::info!("loaded mime app cache in {elapsed:?}"); + tracing::info!(target: "mime-apps", "loaded mime app cache in {elapsed:?}"); } - pub fn apps(&self) -> &[MimeApp] { + pub fn apps(&self) -> &[Arc] { &self.apps } - pub fn get(&self, key: &Mime) -> &[MimeApp] { + pub fn get(&self, key: &Mime) -> &[Arc] { self.cache.get(key).map_or(&[], Vec::as_slice) } - pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] { - self.icons.get(key).map_or(&[], Box::as_ref) + pub fn icons(&self, key: &Mime) -> Vec { + self.cache + .get(key) + .map_or_else(Vec::new, |apps| apps.iter().map(|app| app.icon()).collect()) } fn get_default_terminal(&self) -> Option { @@ -359,14 +530,19 @@ impl MimeAppCache { .map(|string| string.trim().replace(".desktop", "")) } - pub fn terminal(&self) -> Option<&MimeApp> { + pub fn terminal(&self) -> Option<&Arc> { //TODO: consider rules in https://github.com/Vladimir-csp/xdg-terminal-exec // The current approach works but might not adhere to the spec (yet) // Look for and return preferred terminals //TODO: fallback order beyond cosmic-term? - let mut preference_order = vec!["com.system76.CosmicTerm".to_string()]; + // Yoda: cosmic-yoterm (our fork) wins over upstream cosmic-term if both + // are installed — useful when xdg-mime default is not set. + let mut preference_order = vec![ + "com.aditua.CosmicYoterm".to_string(), + "com.system76.CosmicTerm".to_string(), + ]; if let Some(id) = self.get_default_terminal() { preference_order.insert(0, id); diff --git a/src/mime_icon.rs b/src/mime_icon.rs index 64f007f..26d899d 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -15,28 +15,21 @@ struct MimeIconKey { size: u16, } -struct MimeIconCache { +#[derive(Default)] +pub struct MimeIconCache { cache: FxHashMap>, #[cfg(unix)] - shared_mime_info: xdg_mime::SharedMimeInfo, + pub shared_mime_info: xdg_mime::SharedMimeInfo, } impl MimeIconCache { - pub fn new() -> Self { - Self { - cache: FxHashMap::default(), - #[cfg(unix)] - shared_mime_info: xdg_mime::SharedMimeInfo::new(), - } - } - #[cfg(not(unix))] pub fn get(&mut self, _key: MimeIconKey) -> Option { None } #[cfg(unix)] - pub fn get(&mut self, key: MimeIconKey) -> Option { + fn get(&mut self, key: MimeIconKey) -> Option { self.cache .entry(key) .or_insert_with_key(|key| { @@ -56,8 +49,9 @@ impl MimeIconCache { .clone() } } -static MIME_ICON_CACHE: LazyLock> = - LazyLock::new(|| Mutex::new(MimeIconCache::new())); + +pub static MIME_ICON_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(MimeIconCache::default())); #[cfg(not(unix))] pub fn mime_for_path( @@ -78,6 +72,7 @@ pub fn mime_for_path( let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); // Try the shared mime info cache first let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type(); + gb.zero_size(false); if remote { if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) { gb.file_name(file_name); @@ -129,3 +124,11 @@ pub fn parent_mime_types(mime: &Mime) -> Option> { let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); mime_icon_cache.shared_mime_info.get_parents_aliased(mime) } + +pub fn is_mime_subclass_of(mime_type: &Mime, base: &Mime) -> bool { + let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); + + mime_icon_cache + .shared_mime_info + .mime_type_subclass(mime_type, base) +} diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index 7c28668..e2c6ba1 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -14,7 +14,7 @@ use tokio::sync::mpsc; use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage}; use crate::config::IconSizes; use crate::err_str; -use crate::tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location}; +use crate::tab::{self, ChecksumState, DirSize, ItemMetadata, ItemThumbnail, Location}; const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri"; @@ -216,6 +216,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { //TODO: scan directory size on gvfs mounts? dir_size: DirSize::NotDirectory, cut: false, + checksums: ChecksumState::default(), }); } Ok(items) diff --git a/src/mounter/mod.rs b/src/mounter/mod.rs index 3171c0f..a5ee75a 100644 --- a/src/mounter/mod.rs +++ b/src/mounter/mod.rs @@ -75,10 +75,10 @@ impl MounterItem { } } - pub fn icon(&self, symbolic: bool) -> Option { + pub fn icon(&self, _symbolic: bool) -> Option { match self { #[cfg(feature = "gvfs")] - Self::Gvfs(item) => item.icon(symbolic), + Self::Gvfs(item) => item.icon(_symbolic), Self::None => unreachable!(), } } @@ -103,6 +103,7 @@ impl MounterItem { pub type MounterItems = Vec; #[derive(Clone, Debug)] +#[allow(dead_code)] pub enum MounterMessage { Items(MounterItems), MountResult(MounterItem, Result), diff --git a/src/operation/mod.rs b/src/operation/mod.rs index c490655..62dbba6 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -967,11 +967,13 @@ impl Operation { password, } => { let controller_clone = controller.clone(); - compio::runtime::spawn_blocking( - move || -> Result { + compio::runtime::spawn(async move { + let extracted = compio::runtime::spawn_blocking(move || { let controller = controller_clone; let total_paths = paths.len(); let mut op_sel = OperationSelection::default(); + let mut written_files = Vec::new(); + let mut target_dirs = std::collections::HashSet::new(); for (i, path) in paths.iter().enumerate() { futures::executor::block_on(async { controller @@ -995,16 +997,32 @@ impl Operation { op_sel.ignored.push(path.clone()); op_sel.selected.push(new_dir.clone()); - crate::archive::extract(path, &new_dir, &password, &controller)?; + let (files, dirs) = crate::archive::extract( + path, + &new_dir, + &password, + &controller, + )?; + written_files.extend(files); + target_dirs.extend(dirs); } } - Ok(op_sel) - }, - ) + Ok::<_, OperationError>((op_sel, written_files, target_dirs)) + }) + .await + .map_err(wrap_compio_spawn_error)??; + + let (op_sel, written_files, target_dirs) = extracted; + if !written_files.is_empty() || !target_dirs.is_empty() { + sync_to_disk(written_files, target_dirs).await; + } + + Ok::<_, OperationError>(op_sel) + }) + .await + .map_err(wrap_compio_spawn_error)? } - .await - .map_err(wrap_compio_spawn_error)?, Self::Move { paths, to, @@ -1138,10 +1156,26 @@ impl Operation { paths.push(item.original_path()); - compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item])) - .await - .map_err(wrap_compio_spawn_error)? - .map_err(|e| OperationError::from_err(e, &controller))?; + // Items with .trashinfo id use standard restore; sub-items use manual move + if item + .id + .to_str() + .map_or(false, |s| s.ends_with(".trashinfo")) + { + compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item])) + .await + .map_err(wrap_compio_spawn_error)? + .map_err(|e| OperationError::from_err(e, &controller))?; + } else { + let from = PathBuf::from(&item.id); + let to = item.original_path(); + if let Some(parent) = to.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| OperationError::from_err(e, &controller))?; + } + std::fs::rename(&from, &to) + .map_err(|e| OperationError::from_err(e, &controller))?; + } } Ok(OperationSelection { ignored: Vec::new(), diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index a3bd2c4..0f09bd6 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -10,6 +10,7 @@ use compio::driver::ToSharedFd; use compio::driver::op::AsyncifyFd; use compio::io::{AsyncReadAt, AsyncWriteAt}; use cosmic::iced::futures; +#[cfg(feature = "gvfs")] use futures::{FutureExt, StreamExt}; use std::cell::Cell; use std::error::Error; diff --git a/src/tab.rs b/src/tab.rs index 25d271b..dc3d925 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -18,6 +18,7 @@ use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::{self, DndDestination, DndSource, Id, RcElementWrapper, Widget, space}; use cosmic::{Apply, Element, cosmic_theme, font, theme}; +#[cfg(feature = "desktop")] use i18n_embed::LanguageLoader; use icu::datetime::input::DateTime; use icu::datetime::options::TimePrecision; @@ -28,6 +29,7 @@ use jiff_icu::ConvertFrom; use mime_guess::{Mime, mime}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::borrow::Cow; use std::cell::Cell; use std::cmp::{Ordering, Reverse}; @@ -179,7 +181,7 @@ fn button_appearance( appearance.icon_color = Some(Color::from(cosmic.on_bg_component_color())); appearance.text_color = Some(Color::from(cosmic.on_bg_component_color())); if cut { - appearance.text_color = Some(Color::from(cosmic.background.component.on_disabled)); + appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled)); } else { appearance.text_color = Some(Color::from(cosmic.on_bg_component_color())); } @@ -190,12 +192,12 @@ fn button_appearance( appearance.background = Some(Color::from(cosmic.bg_color()).into()); appearance.icon_color = Some(Color::from(cosmic.on_bg_color())); if cut { - appearance.text_color = Some(Color::from(cosmic.background.component.disabled)); + appearance.text_color = Some(Color::from(cosmic.background(false).component.disabled)); } else { appearance.text_color = Some(Color::from(cosmic.on_bg_color())); } } else if cut { - appearance.text_color = Some(Color::from(cosmic.background.component.on_disabled)); + appearance.text_color = Some(Color::from(cosmic.background(false).component.on_disabled)); } if focused && accent { appearance.outline_width = 1.0; @@ -288,6 +290,26 @@ pub fn folder_icon_symbolic(path: &PathBuf, icon_size: u16) -> widget::icon::Han .handle() } +fn generic_file_icons( + sizes: IconSizes, +) -> ( + widget::icon::Handle, + widget::icon::Handle, + widget::icon::Handle, +) { + ( + widget::icon::from_name("text-x-generic") + .size(sizes.grid()) + .handle(), + widget::icon::from_name("text-x-generic") + .size(sizes.list()) + .handle(), + widget::icon::from_name("text-x-generic") + .size(sizes.list_condensed()) + .handle(), + ) +} + //TODO: replace with Path::has_trailing_sep when stable fn has_trailing_sep(path: &Path) -> bool { path.as_os_str() @@ -544,7 +566,7 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind { } #[cfg(not(feature = "desktop"))] -fn get_desktop_file_display_name(path: &Path) -> Option { +fn get_desktop_file_display_name(_path: &Path) -> Option { None } @@ -563,7 +585,7 @@ fn get_desktop_file_display_name(path: &Path) -> Option { } #[cfg(not(feature = "desktop"))] -fn get_desktop_file_icon(path: &Path) -> Option { +fn get_desktop_file_icon(_path: &Path) -> Option { None } @@ -654,9 +676,9 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS folder_icon(&path, sizes.list_condensed()), ) } else { - // ALWAYS assume we're remote for mime guessing here, since gvfs reading can be expensive - // @todo - expose this as a config option? - let mime = mime_for_path(&path, None, true); + // Keep the initial directory scan cheap. Opening files still + // recalculates MIME from the real path before launching apps. + let mime = mime_guess::from_path(&path).first_or_octet_stream(); //TODO: clean this up, implement for trash let icon_name_opt = if mime == "application/x-desktop" { @@ -673,28 +695,21 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS desktop_icon_handle(&icon_name, sizes.list_condensed()), ) } else { + let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = + generic_file_icons(sizes); ( - mime.clone(), - mime_icon(mime.clone(), sizes.grid()), - mime_icon(mime.clone(), sizes.list()), - mime_icon(mime, sizes.list_condensed()), + mime, + icon_handle_grid, + icon_handle_list, + icon_handle_list_condensed, ) } }; - let mut children_opt = None; + let children_opt = None; let mut dir_size = DirSize::NotDirectory; if is_dir && !remote { dir_size = DirSize::Calculating(Controller::default()); - //TODO: calculate children in the background (and make it cancellable?) - match fs::read_dir(&path) { - Ok(entries) => { - children_opt = Some(entries.count()); - } - Err(err) => { - log::warn!("failed to read directory {}: {}", path.display(), err); - } - } } let display_name = display_name_for_file(&path, &file_info.display_name(), false, is_desktop); @@ -731,6 +746,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS overlaps_drag_rect: false, dir_size, cut: false, + checksums: ChecksumState::default(), } } @@ -748,7 +764,10 @@ pub fn item_from_entry( sizes: IconSizes, ) -> Item { let mut is_desktop = false; + #[cfg(feature = "gvfs")] let mut is_gvfs = false; + #[cfg(not(feature = "gvfs"))] + let is_gvfs = false; let hidden = name.starts_with('.') || hidden_attribute(&metadata); @@ -796,7 +815,9 @@ pub fn item_from_entry( folder_icon(&path, sizes.list_condensed()), ) } else { - let mime = mime_for_path(&path, Some(&metadata), remote); + // Keep the initial directory scan cheap. Opening files still + // recalculates MIME from the real path before launching apps. + let mime = mime_guess::from_path(&path).first_or_octet_stream(); //TODO: clean this up, implement for trash let icon_name_opt = if mime == "application/x-desktop" { is_desktop = true; @@ -812,28 +833,21 @@ pub fn item_from_entry( desktop_icon_handle(&icon_name, sizes.list_condensed()), ) } else { + let (icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = + generic_file_icons(sizes); ( - mime.clone(), - mime_icon(mime.clone(), sizes.grid()), - mime_icon(mime.clone(), sizes.list()), - mime_icon(mime, sizes.list_condensed()), + mime, + icon_handle_grid, + icon_handle_list, + icon_handle_list_condensed, ) } }; - let mut children_opt = None; + let children_opt = None; let mut dir_size = DirSize::NotDirectory; if metadata.is_dir() && !remote { dir_size = DirSize::Calculating(Controller::default()); - //TODO: calculate children in the background (and make it cancellable?) - match fs::read_dir(&path) { - Ok(entries) => { - children_opt = Some(entries.count()); - } - Err(err) => { - log::warn!("failed to read directory {}: {}", path.display(), err); - } - } } let display_name = display_name_for_file(&path, &name, is_gvfs, is_desktop); @@ -862,6 +876,7 @@ pub fn item_from_entry( overlaps_drag_rect: false, dir_size, cut: false, + checksums: ChecksumState::default(), } } @@ -874,6 +889,8 @@ pub fn item_from_trash_entry( let name = entry.name.to_string_lossy().into_owned(); let display_name = Item::display_name(&name); + let location = crate::trash::trash_item_path(&entry).map(Location::Path); + let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = match metadata.size { trash::TrashItemSize::Entries(_) => ( @@ -901,7 +918,7 @@ pub fn item_from_trash_entry( is_mount_point: false, metadata: ItemMetadata::Trash { metadata, entry }, hidden: false, - location_opt: None, + location_opt: location, image_dimensions: (mime.type_() == mime::IMAGE) .then(|| image::image_dimensions(&original_path).ok()) .flatten(), @@ -918,9 +935,35 @@ pub fn item_from_trash_entry( overlaps_drag_rect: false, dir_size: DirSize::NotDirectory, cut: false, + checksums: ChecksumState::default(), } } +fn item_from_trash_child( + path: PathBuf, + name: String, + metadata: fs::Metadata, + sizes: IconSizes, +) -> Option { + let original_path = crate::trash::original_path_for_trash_child(&path)?; + let entry = trash::TrashItem { + id: path.as_os_str().to_os_string(), + name: std::ffi::OsString::from(&name), + original_parent: original_path.parent()?.to_path_buf(), + time_deleted: 0, + }; + let size = if metadata.is_dir() { + trash::TrashItemSize::Entries(0) + } else { + trash::TrashItemSize::Bytes(metadata.len()) + }; + Some(item_from_trash_entry( + entry, + trash::TrashItemMetadata { size }, + sizes, + )) +} + fn get_filename_from_path(path: &Path) -> Result { Ok(match path.file_name() { Some(name_os) => name_os @@ -947,7 +990,10 @@ pub fn item_from_path>(path: P, sizes: IconSizes) -> Result Vec { let mut items = Vec::new(); let mut hidden_files = Box::from([]); + #[cfg(feature = "gvfs")] let mut remote_scannable = false; + #[cfg(not(feature = "gvfs"))] + let remote_scannable = false; #[cfg(feature = "gvfs")] { @@ -1036,7 +1082,12 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { }) .ok()?; - Some(item_from_entry(path, name, metadata, sizes)) + let trash = crate::trash::is_trash_path(tab_path); + if trash { + item_from_trash_child(path, name, metadata, sizes) + } else { + Some(item_from_entry(path, name, metadata, sizes)) + } }) .collect(); } @@ -1341,6 +1392,7 @@ pub fn scan_desktop( overlaps_drag_rect: false, dir_size: DirSize::NotDirectory, cut: false, + checksums: ChecksumState::default(), }); } @@ -1724,6 +1776,8 @@ pub enum Message { GoPrevious, ItemDown, ItemLeft, + ItemPageDown, + ItemPageUp, ItemRight, ItemUp, Location(Location), @@ -1760,6 +1814,9 @@ pub enum Message { HighlightDeactivate(usize), HighlightActivate(usize), DirectorySize(PathBuf, DirSize), + Checksums(PathBuf, ChecksumState), + CalculateChecksums(PathBuf), + CopyChecksum(String), ImageDecoded(PathBuf, u32, u32, Vec, Option<(u32, u32)>, u64), // path, width, height, pixels, display_size, generation } @@ -1787,6 +1844,23 @@ pub enum DirSize { Error(String), } +/// Checksums computed for a file. Only SHA256 is currently exposed; see +/// [`calculate_checksums`] for how to add more. +#[derive(Clone, Debug, Default)] +pub struct FileChecksums { + pub sha256: String, +} + +/// State of checksum computation for a file. +#[derive(Clone, Debug, Default)] +pub enum ChecksumState { + #[default] + NotCalculated, + Calculating, + Calculated(FileChecksums), + Error(String), +} + #[derive(Clone, Debug)] pub enum ItemMetadata { Path { @@ -2238,6 +2312,7 @@ pub struct Item { pub cut: bool, pub overlaps_drag_rect: bool, pub dir_size: DirSize, + pub checksums: ChecksumState, } impl Item { @@ -2368,20 +2443,24 @@ impl Item { ))); let mut settings = Vec::new(); if let Some(mime_app_cache) = mime_app_cache_opt { - let mime_apps = mime_app_cache.get(&self.mime); + let mime_apps = mime_app_cache.get_apps_for_mime(&self.mime, false); if !mime_apps.is_empty() { + let (names, icons) = mime_apps + .iter() + .map(|(app, _)| (Cow::Owned(app.name.clone()), app.icon())) + .collect::<(Vec<_>, Vec<_>)>(); settings.push( widget::settings::item::builder(fl!("open-with")).control( Element::from( widget::dropdown( - mime_apps, - mime_apps.iter().position(|x| x.is_default), + names, + mime_apps.iter().position(|(x, _)| x.is_default(&self.mime)), move |index| index, ) - .icons(Cow::Borrowed(mime_app_cache.icons(&self.mime))), + .icons(Cow::Owned(icons)), ) - .map(|index| { - let mime_app = &mime_apps[index]; + .map(move |index| { + let mime_app = &mime_apps[index].0; Message::SetOpenWith(self.mime.clone(), mime_app.id.clone()) }), ), @@ -2510,6 +2589,55 @@ impl Item { } column = column.push(details); + if let Some(metadata) = self.file_metadata() + && !metadata.is_dir() + && let Some(path) = self.path_opt() + { + let control: Element<'_, Message> = match &self.checksums { + ChecksumState::NotCalculated => widget::button::standard(fl!("calculate")) + .on_press(Message::CalculateChecksums(path.clone())) + .into(), + ChecksumState::Calculating => widget::row::with_capacity(2) + .align_y(Alignment::Center) + .spacing(space_xxxs) + .push(widget::indeterminate_circular().size(16.0)) + .push(widget::text::body(fl!("calculating"))) + .into(), + ChecksumState::Calculated(checksums) => { + let value = checksums.sha256.clone(); + // Middle-ellipsize the digest to fit, full value on hover. + let value_text = widget::tooltip( + widget::text::body(value.clone()) + .font(cosmic::font::mono()) + .width(Length::Fill) + .wrapping(text::Wrapping::None) + .ellipsize(text::Ellipsize::Middle(text::EllipsizeHeightLimit::Lines( + 1, + ))), + widget::text::body(value.clone()), + widget::tooltip::Position::Bottom, + ); + let copy_button = widget::button::icon( + widget::icon::from_name("edit-copy-symbolic").size(16), + ) + .on_press(Message::CopyChecksum(value.clone())) + .tooltip(fl!("copy")); + widget::row::with_capacity(2) + .align_y(Alignment::Center) + .spacing(space_xxxs) + .push(value_text) + .push(copy_button) + .into() + } + ChecksumState::Error(err) => { + widget::text::body(format!("{}: {}", fl!("error"), err)).into() + } + }; + settings.push( + widget::settings::item::builder(fl!("checksum", kind = "SHA256")).control(control), + ); + } + if let Some(path) = self.path_opt() && self.selected { @@ -2707,6 +2835,32 @@ async fn calculate_dir_size(path: &Path, controller: Controller) -> Result Result { + let path = path.to_path_buf(); + tokio::task::spawn_blocking(move || { + let mut file = File::open(&path).map_err(|e| e.to_string())?; + let mut sha256_hasher = Sha256::new(); + + let mut buffer = [0u8; 8192]; + loop { + let bytes_read = file.read(&mut buffer).map_err(|e| e.to_string())?; + if bytes_read == 0 { + break; + } + sha256_hasher.update(&buffer[..bytes_read]); + } + + Ok(FileChecksums { + sha256: format!("{:x}", sha256_hasher.finalize()), + }) + }) + .await + .map_err(|e| e.to_string())? +} + fn folder_name>(path: P) -> (String, bool) { let path = path.as_ref(); let mut found_home = false; @@ -3126,6 +3280,14 @@ impl Tab { item.pos_opt.get() } + fn dehighlight_all(&mut self) { + if let Some(items) = self.items_opt.as_mut() { + for item in items.iter_mut() { + item.highlighted = false; + } + } + } + pub(crate) fn select_focus_scroll(&mut self) -> Option { let items = self.items_opt.as_ref()?; let item = items.get(self.select_focus?)?; @@ -3161,6 +3323,20 @@ impl Tab { } } + fn rows_per_page(&self) -> usize { + let viewport_height = self.item_view_size_opt.get().map_or(0.0, |s| s.height); + let row_height = self + .select_focus + .and_then(|i| self.items_opt.as_ref()?.get(i)) + .and_then(|item| item.rect_opt.get()) + .map_or(0.0, |r| r.height); + if row_height > 0.0 && viewport_height > 0.0 { + (viewport_height / row_height).floor() as usize + } else { + 1 + } + } + fn select_range_start_pos_opt(&self) -> Option<(usize, usize)> { let items = self.items_opt.as_ref()?; let item = items.get(self.select_range.map(|r| r.0)?)?; @@ -3816,6 +3992,7 @@ impl Tab { } } Message::ItemDown => { + self.dehighlight_all(); if let Some(edit_location) = &mut self.edit_location { edit_location.select(true); } else if self.gallery { @@ -3857,7 +4034,95 @@ impl Tab { } } } + Message::ItemPageDown => { + self.dehighlight_all(); + if let Some((row, col)) = self.select_focus_pos_opt().or(self.select_last_pos_opt()) + { + if self.select_focus.is_none() { + self.select_position(row, col, mod_shift); + } + + let rows_per_page = self.rows_per_page().max(1); + let target_row = row.saturating_add(rows_per_page); + + if !self.select_position(target_row, col, mod_shift) { + // Fall back to the last item at or before target_row + let best = self.items_opt.as_ref().and_then(|items| { + items + .iter() + .filter_map(|item| item.pos_opt.get()) + .filter(|(r, _)| *r <= target_row) + .max() + }); + if let Some((best_row, best_col)) = best { + self.select_position(best_row, best_col, mod_shift); + } + } + } else { + self.select_position(0, 0, mod_shift); + } + if let Some(offset) = self.select_focus_scroll() { + commands.push(Command::Iced( + scrollable::scroll_to( + self.scrollable_id.clone(), + AbsoluteOffset { + x: Some(offset.x), + y: Some(offset.y), + }, + ) + .into(), + )); + } + if let Some(id) = self.select_focus_id() { + commands.push(Command::Iced(widget::button::focus(id).into())); + } + } + Message::ItemPageUp => { + self.dehighlight_all(); + if let Some((row, col)) = + self.select_focus_pos_opt().or(self.select_first_pos_opt()) + { + if self.select_focus.is_none() { + self.select_position(row, col, mod_shift); + } + + let rows_per_page = self.rows_per_page().max(1); + let target_row = row.saturating_sub(rows_per_page); + + if !self.select_position(target_row, col, mod_shift) { + // Fall back to the first item at or after target_row + let best = self.items_opt.as_ref().and_then(|items| { + items + .iter() + .filter_map(|item| item.pos_opt.get()) + .filter(|(r, _)| *r >= target_row) + .min() + }); + if let Some((best_row, best_col)) = best { + self.select_position(best_row, best_col, mod_shift); + } + } + } else { + self.select_position(0, 0, mod_shift); + } + if let Some(offset) = self.select_focus_scroll() { + commands.push(Command::Iced( + scrollable::scroll_to( + self.scrollable_id.clone(), + AbsoluteOffset { + x: Some(offset.x), + y: Some(offset.y), + }, + ) + .into(), + )); + } + if let Some(id) = self.select_focus_id() { + commands.push(Command::Iced(widget::button::focus(id).into())); + } + } Message::ItemLeft => { + self.dehighlight_all(); if self.gallery { commands.append(&mut self.update(Message::GalleryPrevious, modifiers)); } else { @@ -3916,6 +4181,7 @@ impl Tab { } } Message::ItemRight => { + self.dehighlight_all(); if self.gallery { commands.append(&mut self.update(Message::GalleryNext, modifiers)); } else { @@ -3957,6 +4223,7 @@ impl Tab { } } Message::ItemUp => { + self.dehighlight_all(); if let Some(edit_location) = &mut self.edit_location { edit_location.select(false); } else if self.gallery { @@ -4533,6 +4800,52 @@ impl Tab { } } } + Message::Checksums(path, checksum_state) => { + let location = Location::Path(path); + if let Some(ref mut item) = self.parent_item_opt + && item.location_opt.as_ref() == Some(&location) + { + item.checksums = checksum_state.clone(); + } + if let Some(ref mut items) = self.items_opt { + for item in items.iter_mut() { + if item.location_opt.as_ref() == Some(&location) { + item.checksums = checksum_state; + break; + } + } + } + } + Message::CalculateChecksums(path) => { + let location = Location::Path(path.clone()); + if let Some(ref mut item) = self.parent_item_opt + && item.location_opt.as_ref() == Some(&location) + { + item.checksums = ChecksumState::Calculating; + } + if let Some(ref mut items) = self.items_opt { + for item in items.iter_mut() { + if item.location_opt.as_ref() == Some(&location) { + item.checksums = ChecksumState::Calculating; + break; + } + } + } + commands.push(Command::Iced( + cosmic::Task::future(async move { + match calculate_checksums(&path).await { + Ok(checksums) => { + Message::Checksums(path, ChecksumState::Calculated(checksums)) + } + Err(err) => Message::Checksums(path, ChecksumState::Error(err)), + } + }) + .into(), + )); + } + Message::CopyChecksum(value) => { + commands.push(Command::Iced(cosmic::iced::clipboard::write(value).into())); + } } // Scroll to top if needed @@ -6448,21 +6761,25 @@ impl Tab { .and_then(|(mime, _)| mime.parse::().ok()) && let Some(mime_app_cache) = mime_app_cache_opt { - let mime_apps = mime_app_cache.get(&mime); + let mime_apps = mime_app_cache.get_apps_for_mime(&mime, false); if !mime_apps.is_empty() { let mime_closure = mime.clone(); + let (names, icons) = mime_apps + .iter() + .map(|(app, _)| (Cow::Owned(app.name.clone()), app.icon())) + .collect::<(Vec<_>, Vec<_>)>(); settings.push( widget::settings::item::builder(fl!("open-with")).control( Element::from( widget::dropdown( - mime_apps, - mime_apps.iter().position(|x| x.is_default), + names, + mime_apps.iter().position(|(x, _)| x.is_default(&mime)), move |index| (index, mime_closure.clone()), ) - .icons(Cow::Borrowed(mime_app_cache.icons(&mime))), + .icons(Cow::Owned(icons)), ) - .map(|(index, mime)| { - let mime_app = &mime_apps[index]; + .map(move |(index, mime)| { + let mime_app = &mime_apps[index].0; Message::SetOpenWith(mime, mime_app.id.clone()) }), ), @@ -7580,7 +7897,10 @@ mod tests { // Text editor content may add a trailing newline assert_eq!(content.text().trim_end(), "ab"); } - _ => panic!("expected Text thumbnail with valid prefix only, got {:?}", thumb), + _ => panic!( + "expected Text thumbnail with valid prefix only, got {:?}", + thumb + ), } Ok(()) } diff --git a/src/thumbnailer.rs b/src/thumbnailer.rs index b9de821..8e61447 100644 --- a/src/thumbnailer.rs +++ b/src/thumbnailer.rs @@ -5,10 +5,13 @@ use cosmic::desktop::fde::GenericEntry; use mime_guess::Mime; use rustc_hash::FxHashMap; -use std::path::Path; -use std::sync::{LazyLock, Mutex}; -use std::time::Instant; -use std::{fs, process}; +#[cfg(feature = "desktop")] +use std::{fs, time::Instant}; +use std::{ + path::Path, + process, + sync::{LazyLock, Mutex}, +}; #[derive(Clone, Debug)] pub struct Thumbnailer { diff --git a/src/trash.rs b/src/trash.rs index 30d140f..c0b3548 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -1,11 +1,30 @@ use cosmic::widget; use regex::Regex; use std::collections::HashSet; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::config::IconSizes; use crate::tab::{Item, SearchItem}; +fn percent_decode(s: &str) -> Option { + let (mut r, mut b) = (String::with_capacity(s.len()), s.bytes()); + while let Some(c) = b.next() { + if c == b'%' { + let (hi, lo) = (b.next()?, b.next()?); + let h = |x: u8| match x { + b'0'..=b'9' => Some(x - b'0'), + b'a'..=b'f' => Some(x - b'a' + 10), + b'A'..=b'F' => Some(x - b'A' + 10), + _ => None, + }; + r.push((h(hi)? << 4 | h(lo)?) as char); + } else { + r.push(c as char); + } + } + Some(r) +} + pub trait TrashExt { fn is_empty() -> bool { true @@ -49,6 +68,46 @@ pub trait TrashExt { } } +/// Derive the actual filesystem path of a trashed item from its .trashinfo path +/// (or return the id directly if it's already a filesystem path). +pub fn trash_item_path(item: &trash::TrashItem) -> Option { + let id_path = Path::new(&item.id); + if id_path.extension().map_or(false, |e| e == "trashinfo") { + let trash_root = id_path.parent()?.parent()?; + let file_name = id_path.file_stem()?; + Some(trash_root.join("files").join(file_name)) + } else { + Some(PathBuf::from(&item.id)) + } +} + +/// For a path inside a trash `files/` directory, reconstruct the original path +/// by reading the parent `.trashinfo` file. +/// +/// Given `~/.local/share/Trash/files/folder/sub/file.txt`: +/// - The top-level trashed item is `folder` +/// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path +/// - Compute: `/sub/file.txt` +pub fn original_path_for_trash_child(p: &Path) -> Option { + let files = p.ancestors().find(|a| a.ends_with("files"))?; + let root = files.parent()?; + let top = p.strip_prefix(files).ok()?.components().next()?; + let info = + std::fs::read_to_string(root.join("info").join(top).with_extension("trashinfo")).ok()?; + let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?; + let rel = p.strip_prefix(files.join(top)).ok()?; + let mut result = PathBuf::from(&orig); + if !rel.as_os_str().is_empty() { + result.push(rel); + } + Some(result) +} + +/// Check whether a path is inside any trash `files/` directory. +pub fn is_trash_path(path: &Path) -> bool { + path.ancestors().any(|a| a.ends_with("files")) +} + pub struct Trash; // This config statement is from trash::os_limited @@ -142,4 +201,12 @@ impl TrashExt for Trash { not(target_os = "android") ) )))] -impl TrashExt for Trash {} +impl TrashExt for Trash { + fn scan_search bool + Sync>(callback: F, regex: &Regex) { + log::warn!( + "searching trash not supported on this platform for pattern {:?}", + regex.as_str() + ); + drop(callback); + } +}