diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c493025..754b859e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,11 @@ jobs: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.85.1 + toolchain: 1.90.0 components: clippy - name: install dependencies run: sudo apt update && sudo apt install -y libxkbcommon-dev libwayland-dev libdbus-1-dev libpulse-dev libpipewire-0.3-dev libinput-dev - uses: actions-rs-plus/clippy-check@v2 with: - toolchain: 1.85.1 + toolchain: 1.90.0 args: --all --all-targets --all-features diff --git a/Cargo.lock b/Cargo.lock index d7e3b558..7f9ca381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -119,9 +130,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -145,7 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.4", + "bitflags 2.10.0", "cc", "cesu8", "jni", @@ -176,14 +187,20 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.9.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ - "unicode-width", - "yansi-term", + "anstyle", + "unicode-width 0.2.2", ] +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + [[package]] name = "anyhow" version = "1.0.100" @@ -207,9 +224,12 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "arrayref" @@ -240,9 +260,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" dependencies = [ "enumflags2", "futures-channel", @@ -256,14 +276,14 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "ashpd" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" +checksum = "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce" dependencies = [ "enumflags2", "futures-channel", @@ -276,7 +296,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] @@ -329,12 +349,13 @@ dependencies = [ [[package]] name = "async-fn-stream" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e71711442f1016c768c259bec59300a10efe753bc3e686ec19e2c6a54a97c29b" +checksum = "d4ba0c4baf81a0d8ab31618ffa3ae29ceeb970a6d0d82f76130753462e39d0ea" dependencies = [ "futures-util", "pin-project-lite", + "smallvec", ] [[package]] @@ -370,7 +391,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling 3.11.0", - "rustix 1.1.2", + "rustix 1.1.3", "slab", "windows-sys 0.61.2", ] @@ -386,9 +407,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -420,14 +441,14 @@ checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.4.1", "futures-lite 2.6.1", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -438,7 +459,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -448,12 +469,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -473,7 +494,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -549,7 +570,7 @@ dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -575,23 +596,21 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "annotate-snippets", - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", + "itertools 0.13.0", "proc-macro2", "quote", "regex", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "shlex", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -617,11 +636,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -639,6 +658,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -701,9 +729,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -711,10 +739,19 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.19.0" +name = "btoi" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "by_address" @@ -739,7 +776,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -756,15 +793,15 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "calendrical_calculations" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c5d386a9f2c8b97e1a036420bcf937db4e5c9df33eb0232025008ced6104c0" +checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7" dependencies = [ "core_maths", "displaydoc", @@ -776,7 +813,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "log", "polling 3.11.0", "rustix 0.38.44", @@ -790,9 +827,9 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "polling 3.11.0", - "rustix 1.1.2", + "rustix 1.1.3", "slab", "tracing", ] @@ -816,16 +853,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ "calloop 0.14.3", - "rustix 1.1.2", + "rustix 1.1.3", "wayland-backend", "wayland-client", ] [[package]] -name = "cc" -version = "1.2.41" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -845,14 +891,14 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] name = "cfg-expr" -version = "0.15.8" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" dependencies = [ "smallvec", "target-lexicon", @@ -878,9 +924,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -900,6 +946,16 @@ dependencies = [ "phf 0.12.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -986,7 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1047,9 +1103,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] @@ -1059,9 +1115,6 @@ name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] [[package]] name = "core-foundation" @@ -1114,7 +1167,7 @@ dependencies = [ [[package]] name = "cosmic-app-list" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", "cosmic-app-list-config", @@ -1125,24 +1178,23 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "image", - "itertools 0.14.0", "libcosmic", - "memmap2 0.9.8", + "memmap2 0.9.9", "rust-embed", "rustc-hash 2.1.1", - "rustix 1.1.2", + "rustix 1.1.3", "switcheroo-control", "tokio", "tracing", "tracing-log", "tracing-subscriber", "url", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-app-list-config" -version = "0.1.0" +version = "1.0.2" dependencies = [ "libcosmic", "serde", @@ -1150,12 +1202,13 @@ dependencies = [ [[package]] name = "cosmic-applet-a11y" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", "cosmic-client-toolkit", "cosmic-protocols", - "cosmic-settings-subscriptions", + "cosmic-settings-a11y-manager-subscription", + "cosmic-settings-accessibility-subscription", "cosmic-time", "i18n-embed", "i18n-embed-fl", @@ -1169,14 +1222,13 @@ dependencies = [ [[package]] name = "cosmic-applet-audio" -version = "0.1.1" +version = "1.0.2" dependencies = [ - "cosmic-settings-subscriptions", + "cosmic-settings-sound-subscription", "cosmic-time", "i18n-embed", "i18n-embed-fl", "libcosmic", - "libpulse-binding", "mpris2-zbus", "rust-embed", "serde", @@ -1186,15 +1238,16 @@ dependencies = [ "tracing-subscriber", "url", "urlencoding", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-battery" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", - "cosmic-settings-subscriptions", + "cosmic-settings-daemon-subscription", + "cosmic-settings-upower-subscription", "cosmic-time", "drm 0.14.1", "futures", @@ -1208,12 +1261,12 @@ dependencies = [ "tracing-log", "tracing-subscriber", "udev", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-bluetooth" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", "bluer", @@ -1233,7 +1286,7 @@ dependencies = [ [[package]] name = "cosmic-applet-input-sources" -version = "0.1.0" +version = "1.0.2" dependencies = [ "cosmic-comp-config", "i18n-embed", @@ -1249,16 +1302,16 @@ dependencies = [ [[package]] name = "cosmic-applet-minimize" -version = "0.1.1" +version = "1.0.2" dependencies = [ "anyhow", "i18n-embed", "i18n-embed-fl", "image", "libcosmic", - "memmap2 0.9.8", + "memmap2 0.9.9", "rust-embed", - "rustix 1.1.2", + "rustix 1.1.3", "tokio", "tracing", "tracing-log", @@ -1267,29 +1320,35 @@ dependencies = [ [[package]] name = "cosmic-applet-network" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", + "async-fn-stream", "cosmic-dbus-networkmanager", + "cosmic-settings-airplane-mode-subscription", + "cosmic-settings-network-manager-subscription", "cosmic-time", "futures", "futures-util", "i18n-embed", "i18n-embed-fl", - "itertools 0.14.0", + "indexmap 2.13.0", "libcosmic", + "nm-secret-agent-manager", "rust-embed", "rustc-hash 2.1.1", + "secure-string", "tokio", "tracing", "tracing-log", "tracing-subscriber", - "zbus 5.12.0", + "uuid", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-notifications" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", "cosmic-notifications-config", @@ -1305,29 +1364,29 @@ dependencies = [ "tracing-log", "tracing-subscriber", "url", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-power" -version = "0.1.0" +version = "1.0.2" dependencies = [ "i18n-embed", "i18n-embed-fl", "libcosmic", "logind-zbus", "rust-embed", - "rustix 1.1.2", + "rustix 1.1.3", "tokio", "tracing", "tracing-log", "tracing-subscriber", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-status-area" -version = "0.1.0" +version = "1.0.2" dependencies = [ "futures", "libcosmic", @@ -1337,12 +1396,12 @@ dependencies = [ "tracing", "tracing-log", "tracing-subscriber", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-tiling" -version = "0.1.0" +version = "1.0.2" dependencies = [ "anyhow", "cosmic-client-toolkit", @@ -1362,7 +1421,7 @@ dependencies = [ [[package]] name = "cosmic-applet-time" -version = "0.1.0" +version = "1.0.2" dependencies = [ "chrono", "chrono-tz", @@ -1378,12 +1437,12 @@ dependencies = [ "tracing", "tracing-log", "tracing-subscriber", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-applet-workspaces" -version = "0.1.1" +version = "1.0.2" dependencies = [ "anyhow", "cosmic-client-toolkit", @@ -1400,7 +1459,7 @@ dependencies = [ [[package]] name = "cosmic-applets" -version = "0.1.1" +version = "1.0.2" dependencies = [ "cosmic-app-list", "cosmic-applet-a11y", @@ -1425,7 +1484,7 @@ dependencies = [ [[package]] name = "cosmic-applets-config" -version = "0.1.0" +version = "1.0.2" dependencies = [ "cosmic-config", "serde", @@ -1433,10 +1492,10 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" +version = "0.2.0" +source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cosmic-protocols", "libc", "smithay-client-toolkit 0.20.0", @@ -1457,12 +1516,12 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "atomicwrites", "cosmic-config-derive", "cosmic-settings-daemon", - "dirs 6.0.0", + "dirs", "futures-util", "iced_futures", "known-folders", @@ -1471,57 +1530,58 @@ dependencies = [ "serde", "tokio", "tracing", - "xdg 3.0.0", - "zbus 5.12.0", + "xdg", + "zbus 5.13.2", ] [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "cosmic-dbus-a11y" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-dbus-networkmanager" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "derive_builder", "procfs", "time", - "zbus 5.12.0", - "zvariant 5.8.0", + "zbus 5.13.2", + "zvariant 5.9.2", ] [[package]] name = "cosmic-freedesktop-icons" -version = "0.3.0" -source = "git+https://github.com/pop-os/freedesktop-icons#8a05c322c482ff3c69cf34bacfee98907ac45307" +version = "0.4.0" +source = "git+https://github.com/pop-os/freedesktop-icons#7a61a704f6d1ec41f71cbe766e3cc484858523fa" dependencies = [ - "dirs 5.0.1", - "ini_core", - "memmap2 0.9.8", - "thiserror 2.0.17", + "bstr", + "btoi", + "memchr", + "memmap2 0.9.9", + "thiserror 2.0.18", "tracing", - "xdg 2.5.2", + "xdg", ] [[package]] name = "cosmic-notifications-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-notifications#07d40a7fdd89e59d6f102afa08343c0c5c70039d" +source = "git+https://github.com/pop-os/cosmic-notifications#23e39eca29cf7f7c59f8ec6b80e0f847e080c240" dependencies = [ "cosmic-config", "serde", @@ -1530,19 +1590,19 @@ dependencies = [ [[package]] name = "cosmic-notifications-util" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-notifications#07d40a7fdd89e59d6f102afa08343c0c5c70039d" +source = "git+https://github.com/pop-os/cosmic-notifications#23e39eca29cf7f7c59f8ec6b80e0f847e080c240" dependencies = [ "fast_image_resize", "libcosmic", "serde", "tracing", "url", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-panel-button" -version = "0.1.0" +version = "1.0.2" dependencies = [ "cosmic-config", "libcosmic", @@ -1556,7 +1616,7 @@ dependencies = [ [[package]] name = "cosmic-panel-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#f1a947605243a79c4a4a0813fa234fa21440c859" +source = "git+https://github.com/pop-os/cosmic-panel#8eb8a1b6305213ec7402cb2ec24bef6b501b978a" dependencies = [ "anyhow", "cosmic-config", @@ -1568,11 +1628,25 @@ dependencies = [ ] [[package]] -name = "cosmic-protocols" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#d0e95be25e423cfe523b11111a3666ed7aaf0dc4" +name = "cosmic-pipewire" +version = "1.0.0" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" dependencies = [ - "bitflags 2.9.4", + "intmap", + "libspa", + "libspa-sys", + "pipewire", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "cosmic-protocols" +version = "0.2.0" +source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" +dependencies = [ + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1581,10 +1655,49 @@ dependencies = [ "wayland-server", ] +[[package]] +name = "cosmic-settings-a11y-manager-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "cosmic-protocols", + "iced_futures", + "num-derive", + "num-traits", + "smithay-client-toolkit 0.20.0", + "tokio", + "tracing", +] + +[[package]] +name = "cosmic-settings-accessibility-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "cosmic-dbus-a11y", + "futures", + "iced_futures", + "tokio", + "tracing", + "zbus 5.13.2", +] + +[[package]] +name = "cosmic-settings-airplane-mode-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "futures", + "iced_futures", + "log", + "rustix 1.1.3", + "tokio", +] + [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#68331387e4336c37ce2300bea8638257feab449a" +source = "git+https://github.com/pop-os/cosmic-settings-daemon#ef024bfd06bf9fbd57246a25c91d1fdd28153d05" dependencies = [ "cosmic-config", "ron", @@ -1597,44 +1710,78 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] -name = "cosmic-settings-subscriptions" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-subscriptions#f858ca0b6416a2b75d5f7fa513bc6fc43647d3f8" +name = "cosmic-settings-daemon-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" dependencies = [ - "async-fn-stream", - "cosmic-dbus-a11y", - "cosmic-protocols", + "futures", + "iced_futures", + "log", + "tokio", + "tokio-stream", + "zbus 5.13.2", +] + +[[package]] +name = "cosmic-settings-network-manager-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "bitflags 2.10.0", + "cosmic-dbus-networkmanager", "futures", "iced_futures", "itertools 0.14.0", + "nm-secret-agent-manager", + "secret-service", + "secure-string", + "thiserror 2.0.18", + "tokio", + "tracing", + "zbus 5.13.2", +] + +[[package]] +name = "cosmic-settings-sound-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "cosmic-pipewire", + "futures", + "intmap", "libcosmic", - "libpulse-binding", + "numtoa", + "rustix 1.1.3", + "tokio", + "tracing", +] + +[[package]] +name = "cosmic-settings-upower-subscription" +version = "1.0.2" +source = "git+https://github.com/pop-os/cosmic-settings#6b4a3d565cd58ee69d777c19255f8276520d4f55" +dependencies = [ + "futures", + "iced_futures", "log", - "num-derive", - "num-traits", - "pipewire", - "rustix 1.1.2", - "smithay-client-toolkit 0.20.0", - "thiserror 2.0.17", "tokio", "tokio-stream", - "tracing", "upower_dbus", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "cosmic-text" -version = "0.14.2" -source = "git+https://github.com/pop-os/cosmic-text.git#cffdea2b334e7830a5fd6f95bf5e1784014442a8" +version = "0.16.0" +source = "git+https://github.com/pop-os/cosmic-text.git#ee702e50901d90cd842dbd88154687bd2512b52c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "fontdb 0.23.0", "harfrust", "linebender_resource_handle", @@ -1642,7 +1789,7 @@ dependencies = [ "rangemap", "rustc-hash 1.1.0", "self_cell", - "skrifa", + "skrifa 0.39.0", "smol_str", "swash", "sys-locale", @@ -1655,17 +1802,17 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "almost", "cosmic-config", "csscolorparser", - "dirs 6.0.0", + "dirs", "palette", "ron", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1709,9 +1856,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1763,7 +1910,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] @@ -1773,7 +1920,7 @@ version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libloading", "winapi", ] @@ -1809,7 +1956,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1823,7 +1970,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1834,7 +1981,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1845,7 +1992,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1856,9 +2003,9 @@ checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "dbus" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" dependencies = [ "futures-channel", "futures-util", @@ -1869,9 +2016,9 @@ dependencies = [ [[package]] name = "dbus-crossroads" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" +checksum = "64bff0bd181fba667660276c6b7ebdc50cff37ce593e7adf9e734f89c8f444e8" dependencies = [ "dbus", ] @@ -1889,9 +2036,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -1926,7 +2073,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1936,7 +2083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1948,7 +2095,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1959,7 +2106,7 @@ checksum = "ccfae181bab5ab6c5478b2ccb69e4c68a02f8c3ec72f6616bfec9dbc599d2ee0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1970,15 +2117,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", + "subtle", ] [[package]] @@ -1987,19 +2126,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -2010,7 +2137,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.2", + "redox_users", "windows-sys 0.61.2", ] @@ -2026,7 +2153,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.6.2", "libc", "objc2 0.6.3", @@ -2040,7 +2167,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2057,7 +2184,7 @@ name = "dnd" version = "0.1.0" source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "mime 0.1.0", "raw-window-handle", "smithay-client-toolkit 0.19.2", @@ -2066,9 +2193,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -2082,7 +2209,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" version = "0.1.1" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#8dfaba290f9a00d3e13be71f1e6f438889cf5546" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#0c4adf468b8397e5b1dc9183418f56b972916e42" [[package]] name = "drm" @@ -2090,7 +2217,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "drm-ffi 0.7.1", "drm-fourcc", @@ -2103,7 +2230,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "drm-ffi 0.9.0", "drm-fourcc", @@ -2171,9 +2298,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "endi" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] name = "enumflags2" @@ -2193,7 +2320,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2230,9 +2357,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" dependencies = [ "num-traits", ] @@ -2283,14 +2410,14 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fast_image_resize" -version = "5.3.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd1eda71e8af93f8b00e189404235d82f4de77ea4a0d182b44a7f03994d647c" +checksum = "fbc7fe45cf92b43817ff62a3723e862b85bd1d06288f63007f7645d1d2f7a060" dependencies = [ "cfg-if", "document-features", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2328,15 +2455,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fixed_decimal" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35943d22b2f19c0cb198ecf915910a8158e94541c89dcc63300d7799d46c2c5e" +checksum = "35eabf480f94d69182677e37571d3be065822acfafd12f2f085db44fbbcc8e57" dependencies = [ "displaydoc", "smallvec", @@ -2345,9 +2472,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -2396,9 +2523,9 @@ dependencies = [ [[package]] name = "fluent-langneg" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" dependencies = [ "unic-langid", ] @@ -2410,7 +2537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2427,9 +2554,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "font-types" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511e2c18a516c666d27867d2f9821f76e7d591f762e9fc41dd6cc5c90fe54b0b" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" dependencies = [ "bytemuck", ] @@ -2451,7 +2578,7 @@ checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "slotmap", "tinyvec", "ttf-parser 0.21.1", @@ -2465,7 +2592,7 @@ checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "slotmap", "tinyvec", "ttf-parser 0.25.1", @@ -2489,7 +2616,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2509,17 +2636,17 @@ dependencies = [ [[package]] name = "freedesktop-desktop-entry" -version = "0.7.19" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528df05c8ed0bfd569c7018914ba1995be2a133ba9ead17628ddb0ff94b86331" +checksum = "28273c5c6b97a5f07724f6652f064c0c7f637f9aa5e7c09c83bc3bc4ad4ea245" dependencies = [ "bstr", "gettext-rs", "log", "memchr", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicase", - "xdg 3.0.0", + "xdg", ] [[package]] @@ -2616,7 +2743,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -2651,9 +2778,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2665,15 +2792,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "windows-link", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -2772,7 +2899,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "gpu-alloc-types", ] @@ -2782,7 +2909,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -2804,7 +2931,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -2815,14 +2942,14 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] name = "grid" -version = "0.18.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12101ecc8225ea6d675bc70263074eab6169079621c2186fe0c66590b2df9681" +checksum = "f9e2d4c0a8296178d8802098410ca05d86b17a10bb5ab559b3fb404c1f948220" [[package]] name = "guillotiere" @@ -2847,14 +2974,14 @@ dependencies = [ [[package]] name = "harfrust" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +checksum = "e0caaee032384c10dd597af4579c67dee16650d862a9ccbe1233ff1a379abc07" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "core_maths", - "read-fonts", + "read-fonts 0.36.0", "smallvec", ] @@ -2875,9 +3002,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hassle-rs" @@ -2885,7 +3012,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "com", "libc", "libloading", @@ -2930,6 +3057,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "i18n-config" version = "0.4.8" @@ -2980,7 +3125,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", "unic-langid", ] @@ -2994,7 +3139,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -3024,7 +3169,7 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "dnd", "iced_accessibility", @@ -3042,7 +3187,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "accesskit", "accesskit_winit", @@ -3051,9 +3196,9 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "cosmic-client-toolkit", "dnd", @@ -3075,7 +3220,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "futures", "iced_core", @@ -3101,9 +3246,9 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "cosmic-text", "half", @@ -3123,7 +3268,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -3135,7 +3280,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "bytes", "cosmic-client-toolkit", @@ -3150,7 +3295,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "bytemuck", "cosmic-text", @@ -3166,10 +3311,10 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "as-raw-xcb-connection", - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "cosmic-client-toolkit", "futures", @@ -3197,7 +3342,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -3216,7 +3361,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -3244,9 +3389,9 @@ dependencies = [ [[package]] name = "icu" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab13fe39da5da564b88228e9f08815c9d0efbe9ec244e72b149d9994e10f1054" +checksum = "67ab713dd86fa032cb5487f9ac3a85d47b5dcf4c7b8c7dd00210b3cadd6a6551" dependencies = [ "icu_calendar", "icu_casemap", @@ -3268,9 +3413,9 @@ dependencies = [ [[package]] name = "icu_calendar" -version = "2.0.5" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6c40ba6481ed7ddd358437af0f000eb9f661345845977d9d1b38e606374e1f" +checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e" dependencies = [ "calendrical_calculations", "displaydoc", @@ -3279,24 +3424,23 @@ dependencies = [ "icu_locale_core", "icu_provider", "ixdtf", + "serde", "tinystr", - "writeable", "zerovec", ] [[package]] name = "icu_calendar_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219c8639ab936713a87b571eed2bc2615aa9137e8af6eb221446ee5644acc18" +checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d" [[package]] name = "icu_casemap" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dc5e74b3c9d7b63e0d7c5fd54ee8c135705df2ea2aa558082dd555dc9747a97" +checksum = "d4ca9983e8bf51223c2f89014fa4eaa9e9b336c47f3af0d000538f86f841fba1" dependencies = [ - "displaydoc", "icu_casemap_data", "icu_collections", "icu_locale_core", @@ -3309,17 +3453,16 @@ dependencies = [ [[package]] name = "icu_casemap_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7584067558ab4c60c95d1ac2abd1588689cb4bcd4e099507f62dae86ae8d2c0" +checksum = "98d4663d0f99b301033a19e0acf94e9d2fa4b107638580165e5a6ccc49ad1450" [[package]] name = "icu_collator" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ad4c6a556938dfd31f75a8c54141079e8821dc697ffb799cfe0f0fa11f2edc" +checksum = "32eed11a5572f1088b63fa21dc2e70d4a865e5739fc2d10abc05be93bae97019" dependencies = [ - "displaydoc", "icu_collator_data", "icu_collections", "icu_locale", @@ -3335,18 +3478,19 @@ dependencies = [ [[package]] name = "icu_collator_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d880b8e680799eabd90c054e1b95526cd48db16c95269f3c89fb3117e1ac92c5" +checksum = "5ab06f0e83a613efddba3e4913e00e43ed4001fae651cb7d40fc7e66b83b6fb9" [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", + "serde", "yoke", "zerofrom", "zerovec", @@ -3354,12 +3498,11 @@ dependencies = [ [[package]] name = "icu_datetime" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0790c15e3d6ae3303365fa2337b4f6469de257916141110d14dcaf73f1d31ac5" +checksum = "1b9d49f41ded8e63761b6b4c3120dfdc289415a1ed10107db6198eb311057ca5" dependencies = [ "displaydoc", - "either", "fixed_decimal", "icu_calendar", "icu_datetime_data", @@ -3371,7 +3514,6 @@ dependencies = [ "icu_provider", "icu_time", "potential_utf", - "smallvec", "tinystr", "writeable", "zerovec", @@ -3379,39 +3521,37 @@ dependencies = [ [[package]] name = "icu_datetime_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83791ac10bb7b774f130bb81fa89c4059de710dcef53caa0b86e645212d6d54c" +checksum = "46597233625417b7c8052a63d916e4fdc73df21614ac0b679492a5d6e3b01aeb" [[package]] name = "icu_decimal" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec61c43fdc4e368a9f450272833123a8ef0d7083a44597660ce94d791b8a2e2" +checksum = "a38c52231bc348f9b982c1868a2af3195199623007ba2c7650f432038f5b3e8e" dependencies = [ - "displaydoc", "fixed_decimal", "icu_decimal_data", "icu_locale", "icu_locale_core", "icu_provider", "serde", - "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_decimal_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70963bc35f9bdf1bc66a5c1f458f4991c1dc71760e00fa06016b2c76b2738d5" +checksum = "2905b4044eab2dd848fe84199f9195567b63ab3a93094711501363f63546fef7" [[package]] name = "icu_experimental" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe3d7e64892a434b08d5a58b53127e47a095ff780305f563c8c01798a1051b0" +checksum = "f4ffa4d60b9cb8b024082afaf9e94d853184e483ec69322c74dc437bf8a882a5" dependencies = [ "displaydoc", "either", @@ -3436,24 +3576,22 @@ dependencies = [ "smallvec", "tinystr", "writeable", - "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "icu_experimental_data" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60d32ba5610adfc2083f5a759f55d9a9082ebf72750f126cb1630844eea1acf" +checksum = "a0bce39e12480e91c7ddb748218050c459e241f491d130ea6ee92c3e5cd254f7" [[package]] name = "icu_list" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e26f94ec776bb8b28cedc7dcf91033b822c5cb4c1783cf7a3f796fc168aa0c8b" +checksum = "d3a0b7b126e2fc42777d3c348611553d540bd3683caa39b387c5dd1036bb21a8" dependencies = [ - "displaydoc", "icu_list_data", "icu_locale", "icu_provider", @@ -3465,17 +3603,16 @@ dependencies = [ [[package]] name = "icu_list_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a456a2412458ca45e181d9d51c5090ef8cd90f5692e11d34bafab3b3be1c76b" +checksum = "51044c242fe2a882cc0a464314bbdb9f441556a1cb238fb527fc47355ec2827b" [[package]] name = "icu_locale" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ae5921528335e91da1b6c695dbf1ec37df5ac13faa3f91e5640be93aa2fbefd" +checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_locale_data", @@ -3487,12 +3624,13 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", @@ -3500,17 +3638,16 @@ dependencies = [ [[package]] name = "icu_locale_data" -version = "2.0.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdef0c124749d06a743c69e938350816554eb63ac979166590e2b4ee4252765" +checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831" [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3524,18 +3661,19 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_pattern" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983825f401e6bc4a13c45d552ffd9ad6f3f6b6bc0ec03f31d6835a90a46deb1f" +checksum = "7a7ff8c0ff6f61cdce299dcb54f557b0a251adbc78f6f0c35a21332c452b4a1b" dependencies = [ "displaydoc", "either", + "serde", "writeable", "yoke", "zerovec", @@ -3543,11 +3681,10 @@ dependencies = [ [[package]] name = "icu_plurals" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd83a65f58b6f28e1f3da8c6ada6b415ee3ad5cb480b75bdb669f34d72dd179" +checksum = "4f9cfe49f5b1d1163cc58db451562339916a9ca5cbcaae83924d41a0bf839474" dependencies = [ - "displaydoc", "fixed_decimal", "icu_locale", "icu_plurals_data", @@ -3557,42 +3694,41 @@ dependencies = [ [[package]] name = "icu_plurals_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec552d761eaf4a1c39ad28936e0af77a41bf01ff756ea54be4f8bfc21c265d7" +checksum = "f018a98dccf7f0eb02ba06ac0ff67d102d8ded80734724305e924de304e12ff0" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", + "serde", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", + "serde", "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -3602,15 +3738,13 @@ dependencies = [ [[package]] name = "icu_segmenter" -version = "2.0.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e185fc13b6401c138cf40db12b863b35f5edf31b88192a545857b41aeaf7d3d3" +checksum = "a807a7488f3f758629ae86d99d9d30dce24da2fb2945d74c80a4f4a62c71db73" dependencies = [ "core_maths", - "displaydoc", "icu_collections", "icu_locale", - "icu_locale_core", "icu_provider", "icu_segmenter_data", "potential_utf", @@ -3620,15 +3754,15 @@ dependencies = [ [[package]] name = "icu_segmenter_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5360a2fbe97f617c4f8b944356dedb36d423f7da7f13c070995cf89e59f01220" +checksum = "6ebbb7321d9e21d25f5660366cb6c08201d0175898a3a6f7a41ee9685af21c80" [[package]] name = "icu_time" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d01a4a2dcbc5e5180ef113920e7461d0e9caaddb3567d81c4eca262efe55c0" +checksum = "8242b00da3b3b6678f731437a11c8833a43c821ae081eca60ba1b7579d45b6d8" dependencies = [ "calendrical_calculations", "displaydoc", @@ -3638,17 +3772,15 @@ dependencies = [ "icu_time_data", "ixdtf", "serde", - "tinystr", - "writeable", "zerotrie", "zerovec", ] [[package]] name = "icu_time_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8472be4410d26a03d7208cae3a76c798dd6766e8226ab977cd8b2d349a6dbf08" +checksum = "3e10b0e5e87a2c84bd5fa407705732052edebe69291d347d0c3033785470edbf" [[package]] name = "ident_case" @@ -3679,9 +3811,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -3720,32 +3852,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] -[[package]] -name = "ini_core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a467a31a9f439b5262fa99c17084537bff57f24703d5a09a2b5c9657ec73a61" -dependencies = [ - "cfg-if", -] - [[package]] name = "inotify" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "inotify-sys", "libc", ] @@ -3759,13 +3882,23 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "input" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbdc09524a91f9cacd26f16734ff63d7dc650daffadd2b6f84d17a285bd875a9" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "input-sys", "libc", "log", @@ -3806,6 +3939,12 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "intmap" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e611826a1868311677fdcdfbec9e8621d104c732d080f546a854530232f0ee" + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -3819,9 +3958,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -3837,18 +3976,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "ixdtf" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8289f7f711a1a51f80e2e368355d023042ca55d8d554fd5e953f01464c15842d" -dependencies = [ - "displaydoc", -] +checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992" [[package]] name = "jni" @@ -3890,9 +4026,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -3980,25 +4116,19 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.177" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#6204784f202c19b675c5a0c8a56865e3f1d9bd69" +source = "git+https://github.com/pop-os/libcosmic#927035809f1564674434c27cbecdc67e199db28e" dependencies = [ "apply", - "ashpd 0.12.0", + "ashpd 0.12.1", "auto_enums", "chrono", "cosmic-client-toolkit", @@ -4024,30 +4154,32 @@ dependencies = [ "iced_winit", "image", "libc", + "log", "mime 0.3.17", "palette", + "phf 0.13.1", "raw-window-handle", "rfd", "ron", "rust-embed", - "rustix 1.1.2", + "rustix 1.1.3", "serde", "shlex", "slotmap", "taffy", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "unicode-segmentation", "url", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "libdbus-sys" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" dependencies = [ "pkg-config", ] @@ -4064,70 +4196,43 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libpulse-binding" -version = "2.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397" -dependencies = [ - "bitflags 2.9.4", - "libc", - "libpulse-sys", - "num-derive", - "num-traits", - "winapi", -] - -[[package]] -name = "libpulse-sys" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74371848b22e989f829cc1621d2ebd74960711557d8b45cfe740f60d0a05e61" -dependencies = [ - "libc", - "num-derive", - "num-traits", - "pkg-config", - "winapi", -] +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", - "redox_syscall 0.5.18", + "redox_syscall 0.7.0", ] [[package]] name = "libspa" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" +checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", - "nix 0.27.1", - "nom", + "nix 0.30.1", + "nom 8.0.0", "system-deps", ] [[package]] name = "libspa-sys" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" +checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" dependencies = [ "bindgen", "cc", @@ -4176,15 +4281,15 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "locale_config" @@ -4210,9 +4315,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logind-zbus" @@ -4221,7 +4326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469c962578b549a82f3d0cc72d0f77d1123780fa7121e2b03d78b0780f6ccac6" dependencies = [ "serde", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] @@ -4252,9 +4357,9 @@ dependencies = [ [[package]] name = "lyon_geom" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e16770d760c7848b0c1c2d209101e408207a65168109509f8483837a36cf2e7" +checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" dependencies = [ "arrayvec", "euclid", @@ -4323,9 +4428,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -4354,7 +4459,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block", "core-graphics-types", "foreign-types", @@ -4395,21 +4500,21 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "moxcms" -version = "0.7.7" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ "num-traits", "pxfm", @@ -4418,14 +4523,14 @@ dependencies = [ [[package]] name = "mpris2-zbus" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ "futures-util", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", - "zbus 5.12.0", - "zvariant 5.8.0", + "zbus 5.13.2", + "zvariant 5.9.2", ] [[package]] @@ -4442,11 +4547,11 @@ checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.11.4", + "indexmap 2.13.0", "log", "rustc-hash 1.1.0", "spirv", @@ -4461,7 +4566,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -4506,24 +4611,13 @@ dependencies = [ "memoffset 0.7.1", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -4535,11 +4629,18 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases 0.2.1", "libc", - "memoffset 0.9.1", +] + +[[package]] +name = "nm-secret-agent-manager" +version = "0.1.0" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" +dependencies = [ + "zbus 5.13.2", ] [[package]] @@ -4552,13 +4653,22 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "fsevent-sys", "inotify", "kqueue", @@ -4572,9 +4682,12 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.10.0", +] [[package]] name = "nu-ansi-term" @@ -4585,6 +4698,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4596,10 +4723,19 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "num-complex" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -4609,7 +4745,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -4621,6 +4757,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -4654,9 +4801,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -4664,16 +4811,22 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] +[[package]] +name = "numtoa" +version = "1.0.0-alpha1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3f98606e662e333dada0fa9fb6723a3c363fb4a66b51e47ce964cfaf58833d2" + [[package]] name = "objc" version = "0.2.7" @@ -4725,7 +4878,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -4741,7 +4894,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.6.2", "objc2 0.6.3", "objc2-foundation 0.3.2", @@ -4753,7 +4906,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -4777,7 +4930,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4789,7 +4942,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "dispatch2", "objc2 0.6.3", ] @@ -4830,7 +4983,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "dispatch", "libc", @@ -4843,7 +4996,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "objc2 0.6.3", "objc2-core-foundation", ] @@ -4866,7 +5019,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4878,7 +5031,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4901,7 +5054,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -4933,7 +5086,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -4963,10 +5116,11 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orbclient" -version = "0.3.48" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" dependencies = [ + "libc", "libredox", ] @@ -5001,7 +5155,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5035,7 +5189,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5110,7 +5264,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros", + "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -5123,6 +5277,17 @@ dependencies = [ "phf_shared 0.12.1", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + [[package]] name = "phf_generator" version = "0.11.3" @@ -5133,17 +5298,40 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand 2.3.0", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator", + "phf_generator 0.11.3", "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -5164,6 +5352,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pico-args" version = "0.5.0" @@ -5187,7 +5384,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5215,26 +5412,26 @@ dependencies = [ [[package]] name = "pipewire" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" +checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" dependencies = [ "anyhow", - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "libspa", "libspa-sys", - "nix 0.27.1", + "nix 0.30.1", "once_cell", "pipewire-sys", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "pipewire-sys" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" +checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" dependencies = [ "bindgen", "libspa-sys", @@ -5266,7 +5463,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "crc32fast", "fdeflate", "flate2", @@ -5299,7 +5496,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -5311,11 +5508,12 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "serde", + "serde_core", + "writeable", "zerovec", ] @@ -5356,7 +5554,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -5378,14 +5576,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -5398,7 +5596,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "version_check", "yansi", ] @@ -5409,9 +5607,9 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "procfs-core", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -5420,7 +5618,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "hex", ] @@ -5432,27 +5630,27 @@ checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] name = "pxfm" -version = "0.1.25" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" dependencies = [ "num-traits", ] [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -5481,7 +5679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -5501,7 +5699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -5510,14 +5708,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -5530,9 +5728,9 @@ checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "rangemap" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" [[package]] name = "raw-window-handle" @@ -5545,6 +5743,16 @@ name = "read-fonts" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "read-fonts" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345" dependencies = [ "bytemuck", "core_maths", @@ -5566,18 +5774,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] -name = "redox_users" -version = "0.4.6" +name = "redox_syscall" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", + "bitflags 2.10.0", ] [[package]] @@ -5586,9 +5792,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5608,7 +5814,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -5668,7 +5874,7 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ - "ashpd 0.11.0", + "ashpd 0.11.1", "block2 0.6.2", "dispatch2", "js-sys", @@ -5702,7 +5908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", - "bitflags 2.9.4", + "bitflags 2.10.0", "serde", "serde_derive", "unicode-ident", @@ -5716,9 +5922,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -5727,22 +5933,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.106", + "syn 2.0.114", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ "sha2", "walkdir", @@ -5780,7 +5986,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5789,11 +5995,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -5812,7 +6018,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "smallvec", "ttf-parser 0.21.1", @@ -5822,12 +6028,6 @@ dependencies = [ "unicode-script", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -5851,9 +6051,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -5881,16 +6081,45 @@ checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "smithay-client-toolkit 0.19.2", "tiny-skia", ] [[package]] -name = "self_cell" -version = "1.2.0" +name = "secret-service" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "getrandom 0.2.17", + "hkdf", + "num", + "once_cell", + "serde", + "sha2", + "zbus 5.13.2", +] + +[[package]] +name = "secure-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548ba8c9ff631f7bb3a64de1e8ad73fe20f6d04090724f2b496ed45314ad7488" +dependencies = [ + "libc", + "zeroize", +] + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" [[package]] name = "serde" @@ -5931,21 +6160,21 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -5956,31 +6185,31 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "serde_with" -version = "3.15.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -5989,14 +6218,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6038,18 +6267,19 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simplecss" @@ -6073,7 +6303,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.35.0", +] + +[[package]] +name = "skrifa" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e" +dependencies = [ + "bytemuck", + "read-fonts 0.36.0", ] [[package]] @@ -6084,9 +6324,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" -version = "1.0.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ "version_check", ] @@ -6103,13 +6343,13 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "calloop 0.13.0", "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", @@ -6128,17 +6368,17 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "calloop 0.14.3", "calloop-wayland-source 0.4.1", "cursor-icon", "libc", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "pkg-config", - "rustix 1.1.2", - "thiserror 2.0.17", + "rustix 1.1.3", + "thiserror 2.0.18", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -6184,9 +6424,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -6207,7 +6447,7 @@ dependencies = [ "foreign-types", "js-sys", "log", - "memmap2 0.9.8", + "memmap2 0.9.9", "objc", "raw-window-handle", "redox_syscall 0.5.18", @@ -6228,7 +6468,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.9.4", + "bitflags 2.10.0", ] [[package]] @@ -6277,9 +6517,15 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.106", + "syn 2.0.114", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.5" @@ -6302,7 +6548,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" dependencies = [ - "skrifa", + "skrifa 0.37.0", "yazi", "zeno", ] @@ -6310,9 +6556,9 @@ dependencies = [ [[package]] name = "switcheroo-control" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] @@ -6328,9 +6574,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -6345,7 +6591,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6359,22 +6605,22 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.2" +version = "7.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.23", + "toml 0.9.11+spec-1.1.0", "version-compare", ] [[package]] name = "taffy" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25026fb8cc9ab51ab9fdabe5d11706796966f6d1c78e19871ef63be2b8f0644" +checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b" dependencies = [ "arrayvec", "grid", @@ -6384,9 +6630,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "temp-dir" @@ -6396,14 +6642,14 @@ checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -6427,11 +6673,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -6442,18 +6688,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -6467,30 +6713,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -6499,9 +6745,9 @@ dependencies = [ [[package]] name = "timedate-zbus" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] @@ -6545,11 +6791,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -6570,9 +6817,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -6580,7 +6827,7 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -6594,14 +6841,14 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -6619,14 +6866,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "serde", + "indexmap 2.13.0", + "serde_core", "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", ] [[package]] @@ -6634,15 +6884,12 @@ name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -6653,50 +6900,43 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "toml_datetime 0.6.11", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned", - "toml_datetime 0.6.11", - "winnow 0.7.13", -] - -[[package]] -name = "toml_edit" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.7.3", + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] -name = "tracing" -version = "0.1.41" +name = "toml_writer" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6706,20 +6946,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6738,9 +6978,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -6828,9 +7068,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -6852,9 +7092,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -6864,15 +7104,15 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-script" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" @@ -6892,6 +7132,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -6901,23 +7147,24 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "upower_dbus" version = "0.3.2" -source = "git+https://github.com/pop-os/dbus-settings-bindings#b2337437d70b3db7a56211a43aa1632306711b2d" +source = "git+https://github.com/pop-os/dbus-settings-bindings#87c3c35666b926a24a1e8045fd70be2db1145e34" dependencies = [ "serde", "serde_repr", - "zbus 5.12.0", + "zbus 5.13.2", ] [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -6967,13 +7214,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -6985,9 +7232,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version-compare" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "version_check" @@ -7019,18 +7266,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -7039,27 +7286,14 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -7068,9 +7302,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7078,22 +7312,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -7115,13 +7349,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.2", + "rustix 1.1.3", "scoped-tls", "smallvec", "wayland-sys", @@ -7129,12 +7363,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.11" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ - "bitflags 2.9.4", - "rustix 1.1.2", + "bitflags 2.10.0", + "rustix 1.1.3", "wayland-backend", "wayland-scanner", ] @@ -7145,29 +7379,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.11" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" +checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.9" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -7180,7 +7414,7 @@ version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7189,11 +7423,11 @@ dependencies = [ [[package]] name = "wayland-protocols-misc" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +checksum = "791c58fdeec5406aa37169dd815327d1e47f334219b523444bc26d70ceb4c34e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7202,11 +7436,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7215,11 +7449,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7229,9 +7463,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", "quick-xml", @@ -7240,22 +7474,22 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbd4f3aba6c9fba70445ad2a484c0ef0356c1a9459b1e8e435bedc1971a6222" +checksum = "9297ab90f8d1f597711d36455c5b1b2290eca59b8134485e377a296b80b118c9" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "downcast-rs", - "rustix 1.1.2", + "rustix 1.1.3", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ "dlib", "log", @@ -7265,9 +7499,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -7285,9 +7519,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" @@ -7322,10 +7556,10 @@ checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg_aliases 0.1.1", "document-features", - "indexmap 2.11.4", + "indexmap 2.13.0", "log", "naga", "once_cell", @@ -7349,7 +7583,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.4", + "bitflags 2.10.0", "block", "cfg_aliases 0.1.1", "core-graphics-types", @@ -7390,7 +7624,7 @@ version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "js-sys", "web-sys", ] @@ -7509,7 +7743,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7520,7 +7754,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7531,7 +7765,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7542,7 +7776,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -7878,12 +8112,12 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" version = "0.30.5" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#8dfaba290f9a00d3e13be71f1e6f438889cf5546" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#0c4adf468b8397e5b1dc9183418f56b972916e42" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.4", + "bitflags 2.10.0", "block2 0.5.1", "bytemuck", "calloop 0.13.0", @@ -7895,7 +8129,8 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2 0.9.8", + "libredox", + "memmap2 0.9.9", "ndk", "objc2 0.5.2", "objc2-app-kit 0.2.2", @@ -7905,7 +8140,7 @@ dependencies = [ "percent-encoding", "pin-project", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall 0.7.0", "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit 0.19.2", @@ -7937,18 +8172,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "write16" @@ -7958,9 +8193,9 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" dependencies = [ "either", ] @@ -7987,7 +8222,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "x11rb-protocol", "xcursor", ] @@ -8004,12 +8239,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" -[[package]] -name = "xdg" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" - [[package]] name = "xdg" version = "3.0.0" @@ -8029,7 +8258,7 @@ dependencies = [ [[package]] name = "xdg-shell-wrapper-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#f1a947605243a79c4a4a0813fa234fa21440c859" +source = "git+https://github.com/pop-os/cosmic-panel#8eb8a1b6305213ec7402cb2ec24bef6b501b978a" dependencies = [ "serde", "wayland-protocols-wlr", @@ -8037,9 +8266,9 @@ dependencies = [ [[package]] name = "xkb-data" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddbaf7dc9e1a13a8a6dd4beb794d463ae16d150879092c757f4aea2d17b81305" +checksum = "08bde286fb1aad6e18459bdf90da78b611bb8a1993605de5d1ddacca4995c10a" dependencies = [ "serde", "serde-xml-rs", @@ -8063,7 +8292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" dependencies = [ "libc", - "memmap2 0.9.8", + "memmap2 0.9.9", "xkeysym", ] @@ -8074,7 +8303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" dependencies = [ "libc", - "memmap2 0.9.8", + "memmap2 0.9.9", "xkeysym", ] @@ -8084,7 +8313,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "dlib", "log", "once_cell", @@ -8102,9 +8331,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "xmlwriter" @@ -8118,15 +8347,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yansi-term" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" -dependencies = [ - "winapi", -] - [[package]] name = "yazi" version = "0.2.1" @@ -8135,11 +8355,10 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -8147,13 +8366,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] @@ -8195,14 +8414,14 @@ dependencies = [ [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" dependencies = [ "async-broadcast 0.7.2", "async-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "async-process 2.5.0", "async-recursion", "async-task", @@ -8213,8 +8432,9 @@ dependencies = [ "futures-core", "futures-lite 2.6.1", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix 1.1.3", "serde", "serde_repr", "tokio", @@ -8222,10 +8442,10 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.13", - "zbus_macros 5.12.0", - "zbus_names 4.2.0", - "zvariant 5.8.0", + "winnow 0.7.14", + "zbus_macros 5.13.2", + "zbus_names 4.3.1", + "zvariant 5.9.2", ] [[package]] @@ -8244,17 +8464,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.106", - "zbus_names 4.2.0", - "zvariant 5.8.0", - "zvariant_utils 3.2.1", + "syn 2.0.114", + "zbus_names 4.3.1", + "zvariant 5.9.2", + "zvariant_utils 3.3.0", ] [[package]] @@ -8270,14 +8490,13 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", - "winnow 0.7.13", - "zvariant 5.8.0", + "winnow 0.7.14", + "zvariant 5.9.2", ] [[package]] @@ -8288,22 +8507,22 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -8323,15 +8542,21 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", "synstructure", ] [[package]] -name = "zerotrie" -version = "0.2.2" +name = "zeroize" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -8340,10 +8565,11 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -8351,26 +8577,32 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] -name = "zune-core" -version = "0.4.12" +name = "zmij" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.4.21" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2" dependencies = [ "zune-core", ] @@ -8391,17 +8623,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" dependencies = [ "endi", "enumflags2", "serde", "url", - "winnow 0.7.13", - "zvariant_derive 5.8.0", - "zvariant_utils 3.2.1", + "winnow 0.7.14", + "zvariant_derive 5.9.2", + "zvariant_utils 3.3.0", ] [[package]] @@ -8419,15 +8651,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.106", - "zvariant_utils 3.2.1", + "syn 2.0.114", + "zvariant_utils 3.3.0", ] [[package]] @@ -8443,13 +8675,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", - "winnow 0.7.13", + "syn 2.0.114", + "winnow 0.7.14", ] diff --git a/Cargo.toml b/Cargo.toml index a8c35094..a4ead361 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,6 @@ cosmic-applets-config = { path = "cosmic-applets-config" } cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = [ "client", ], rev = "d0e95be" } -cosmic-settings-subscriptions = { git = "https://github.com/pop-os/cosmic-settings-subscriptions" } - cosmic-time = { git = "https://github.com/pop-os/cosmic-time", default-features = false } # cosmic-time = { path = "../cosmic-time", default-features = false ] } @@ -52,15 +50,15 @@ libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = fa "desktop-systemd-scope", "winit", ] } -rust-embed = "8.7.2" -rust-embed-utils = "8.7.2" +rust-embed = "8.9.0" +rust-embed-utils = "8.9.0" rustc-hash = "2.1" rustix = { version = "1.1", features = ["fs", "process"] } -zbus = { version = "5.11.0", default-features = false, features = ["tokio"] } +zbus = { version = "5.13.1", default-features = false, features = ["tokio"] } tracing = "0.1" -tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } tracing-log = "0.2.0" -tokio = { version = "1.47.1", features = ["full"] } +tokio = { version = "1.49.0", features = ["full"] } cosmic-config = { git = "https://github.com/pop-os/libcosmic" } serde = { version = "1.0.228", features = ["derive"] } @@ -73,6 +71,9 @@ lto = "thin" ignored = ["libcosmic"] # [patch."https://github.com/pop-os/libcosmic"] +# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "" } +# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "" } +# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "" } # cosmic-config = { path = "../libcosmic/cosmic-config" } # libcosmic = { path = "../libcosmic" } # iced_futures = { path = "../libcosmic/iced/futures" } @@ -87,3 +88,8 @@ sctk = { package = "smithay-client-toolkit", version = "0.20.0" } [patch."https://github.com/pop-os/cosmic-protocols"] cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" } + +# [patch.'https://github.com/pop-os/dbus-settings-bindings'] +# cosmic-dbus-networkmanager = { path = "../dbus-settings-bindings/networkmanager" } +# upower_dbus = { path = "../dbus-settings-bindings/upower" } +# nm-secret-agent-manager = { path = "../dbus-settings-bindings/nm-secret-agent-manager" } diff --git a/cosmic-app-list/Cargo.toml b/cosmic-app-list/Cargo.toml index 17474d99..86abedd8 100644 --- a/cosmic-app-list/Cargo.toml +++ b/cosmic-app-list/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-app-list" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" @@ -12,10 +12,9 @@ cosmic-protocols.workspace = true futures.workspace = true i18n-embed.workspace = true i18n-embed-fl.workspace = true -image = { version = "0.25.8", default-features = false } -itertools = "0.14.0" +image = { version = "0.25.9", default-features = false } libcosmic.workspace = true -memmap2 = "0.9.8" +memmap2 = "0.9.9" fastrand = "2.3.0" rust-embed.workspace = true rustix.workspace = true @@ -25,5 +24,5 @@ tokio.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true -url = "2.5.7" +url = "2.5.8" zbus.workspace = true diff --git a/cosmic-app-list/cosmic-app-list-config/Cargo.toml b/cosmic-app-list/cosmic-app-list-config/Cargo.toml index aed985cc..08d87f2e 100644 --- a/cosmic-app-list/cosmic-app-list-config/Cargo.toml +++ b/cosmic-app-list/cosmic-app-list-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-app-list-config" -version = "0.1.0" +version = "1.0.2" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/cosmic-app-list/cosmic-app-list-config/src/lib.rs b/cosmic-app-list/cosmic-app-list-config/src/lib.rs index f4968f05..30fb0b14 100644 --- a/cosmic-app-list/cosmic-app-list-config/src/lib.rs +++ b/cosmic-app-list/cosmic-app-list-config/src/lib.rs @@ -9,7 +9,7 @@ use std::fmt::Debug; pub const APP_ID: &str = "com.system76.CosmicAppList"; #[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)] -pub enum TopLevelFilter { +pub enum ToplevelFilter { #[default] ActiveWorkspace, ConfiguredOutput, @@ -18,7 +18,7 @@ pub enum TopLevelFilter { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CosmicConfigEntry)] #[version = 1] pub struct AppListConfig { - pub filter_top_levels: Option, + pub filter_top_levels: Option, pub favorites: Vec, pub enable_drag_source: bool, } diff --git a/cosmic-app-list/data/com.system76.CosmicAppList.desktop b/cosmic-app-list/data/com.system76.CosmicAppList.desktop index 3eb97df1..c8560e2b 100644 --- a/cosmic-app-list/data/com.system76.CosmicAppList.desktop +++ b/cosmic-app-list/data/com.system76.CosmicAppList.desktop @@ -12,6 +12,7 @@ Name[nl]=App-Tray Name[sk]=Panel aplikácií Name[sv]=Programfält Name[es]=Bandeja de aplicaciones +Name[it]=Area applicazioni Type=Application Exec=cosmic-app-list Terminal=false diff --git a/cosmic-app-list/i18n/ar/cosmic_app_list.ftl b/cosmic-app-list/i18n/ar/cosmic_app_list.ftl index bc404410..b6d025a5 100644 --- a/cosmic-app-list/i18n/ar/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/ar/cosmic_app_list.ftl @@ -1,8 +1,8 @@ cosmic-app-list = درج التطبيقات -pin = ثبت في درج التطبيقات -quit = إنهاء -quit-all = إنهاء الكل +pin = ثبِّت في درج التطبيقات +quit = أنهِ +quit-all = أنهِ الكل new-window = نافذة جديدة -run = تشغيل -run-on = تشغيل على {$gpu} -run-on-default = (الافتراضي) +run = شغِّل +run-on = شغِّل على { $gpu } +run-on-default = (المبدئي) diff --git a/cosmic-app-list/i18n/bg/cosmic_app_list.ftl b/cosmic-app-list/i18n/bg/cosmic_app_list.ftl index b7f3cfbb..7fd6416e 100644 --- a/cosmic-app-list/i18n/bg/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/bg/cosmic_app_list.ftl @@ -1,5 +1,5 @@ -cosmic-app-list = Тава за програми -pin = Добавяне в тавата за програми +cosmic-app-list = Меню за програми +pin = Добавяне в менюто за програми quit = Спиране на програмата quit-all = Спиране на всички програми new-window = Нов прозорец diff --git a/cosmic-app-list/i18n/bn/cosmic_app_list.ftl b/cosmic-app-list/i18n/bn/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/fr/cosmic_app_list.ftl b/cosmic-app-list/i18n/fr/cosmic_app_list.ftl index 07a10697..fc5faaf2 100644 --- a/cosmic-app-list/i18n/fr/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/fr/cosmic_app_list.ftl @@ -1,8 +1,8 @@ cosmic-app-list = Barre des applications -pin = Fixer à la barre +pin = Épingler à la barre d'applis quit = Quitter quit-all = Tout quitter new-window = Nouvelle fenêtre run = Lancer -run-on = Lancer avec {$gpu} +run-on = Lancer avec { $gpu } run-on-default = (Défaut) diff --git a/cosmic-app-list/i18n/ga/cosmic_app_list.ftl b/cosmic-app-list/i18n/ga/cosmic_app_list.ftl index dcf81394..2132b2cd 100644 --- a/cosmic-app-list/i18n/ga/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/ga/cosmic_app_list.ftl @@ -1,8 +1,8 @@ -cosmic-app-list = Tráidire Aipeanna +cosmic-app-list = Tráidire aipeanna pin = Bioráin go tráidire aipeanna quit = Scoir -quit-all = Scoir Uile -new-window = Fuinneog Nua +quit-all = Scoir uile +new-window = Fuinneog nua run = Rith -run-on = Rith ar {$gpu} +run-on = Rith ar { $gpu } run-on-default = (Réamhshocrú) diff --git a/cosmic-app-list/i18n/hu/cosmic_app_list.ftl b/cosmic-app-list/i18n/hu/cosmic_app_list.ftl index c836c685..26c1e6e6 100644 --- a/cosmic-app-list/i18n/hu/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/hu/cosmic_app_list.ftl @@ -4,5 +4,5 @@ quit = Kilépés quit-all = Kilépés az összesből new-window = Új ablak run = Futtatás -run-on = Futtatás ezen: {$gpu} +run-on = Futtatás ezen: { $gpu } run-on-default = (Alapértelmezett) diff --git a/cosmic-app-list/i18n/ka/cosmic_app_list.ftl b/cosmic-app-list/i18n/ka/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/kab/cosmic_app_list.ftl b/cosmic-app-list/i18n/kab/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/kk/cosmic_app_list.ftl b/cosmic-app-list/i18n/kk/cosmic_app_list.ftl new file mode 100644 index 00000000..c0e5a033 --- /dev/null +++ b/cosmic-app-list/i18n/kk/cosmic_app_list.ftl @@ -0,0 +1,8 @@ +quit = Шығу +run = Іске қосу +run-on = { $gpu } арқылы іске қосу +run-on-default = (Әдепкі) +cosmic-app-list = Қолданбалар сөресі +pin = Таңдамалыларға қосу +quit-all = Барлығының жұмысын аяқтау +new-window = Жаңа терезе diff --git a/cosmic-app-list/i18n/kmr/cosmic_app_list.ftl b/cosmic-app-list/i18n/kmr/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/ko/cosmic_app_list.ftl b/cosmic-app-list/i18n/ko/cosmic_app_list.ftl index 4f95ab6c..a0ffad07 100644 --- a/cosmic-app-list/i18n/ko/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/ko/cosmic_app_list.ftl @@ -1,5 +1,8 @@ -cosmic-app-list = 코스믹 독 앱 목록 +cosmic-app-list = 앱 트레이 pin = 즐겨찾기 -quit = 끝내기 +quit = 종료 quit-all = 모두 끝내기 new-window = 새 창 +run = 실행 +run-on = { $gpu } 에서 실행 +run-on-default = (기본값) diff --git a/cosmic-app-list/i18n/lt/cosmic_app_list.ftl b/cosmic-app-list/i18n/lt/cosmic_app_list.ftl index e69de29b..0180d804 100644 --- a/cosmic-app-list/i18n/lt/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/lt/cosmic_app_list.ftl @@ -0,0 +1,7 @@ +new-window = Naujas Langas +quit = Išeiti +run = Paleisti +run-on = Paleisti naudojantis { $gpu } +run-on-default = (Numatytas) +cosmic-app-list = Aplikacijų Dėtuvė +pin = Prisegti prie aplikacijų dėtuvės diff --git a/cosmic-app-list/i18n/nl/cosmic_app_list.ftl b/cosmic-app-list/i18n/nl/cosmic_app_list.ftl index 7beba328..1f13a115 100644 --- a/cosmic-app-list/i18n/nl/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/nl/cosmic_app_list.ftl @@ -1,8 +1,8 @@ cosmic-app-list = Toepassingsbalk pin = Aan de toepassingsbalk vastmaken -quit = Toepassing sluiten -quit-all = Alles sluiten +quit = Beëindig +quit-all = Sluit alles new-window = Nieuw venster -run = Uitvoeren -run-on = Op {$gpu} uitvoeren +run = Voer uit +run-on = Voer uit op { $gpu } run-on-default = (Standaard) diff --git a/cosmic-app-list/i18n/pa/cosmic_app_list.ftl b/cosmic-app-list/i18n/pa/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/pt-BR/cosmic_app_list.ftl b/cosmic-app-list/i18n/pt-BR/cosmic_app_list.ftl index b08fc316..bc74e22d 100644 --- a/cosmic-app-list/i18n/pt-BR/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/pt-BR/cosmic_app_list.ftl @@ -1,5 +1,5 @@ cosmic-app-list = Bandeja de Aplicativos -pin = Fixar em aplicativos favoritos +pin = Fixar no painel quit = Sair quit-all = Sair de Todos new-window = Nova Janela diff --git a/cosmic-app-list/i18n/ti/cosmic_app_list.ftl b/cosmic-app-list/i18n/ti/cosmic_app_list.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-app-list/i18n/uk/cosmic_app_list.ftl b/cosmic-app-list/i18n/uk/cosmic_app_list.ftl index 20b7901f..b4ad1095 100644 --- a/cosmic-app-list/i18n/uk/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/uk/cosmic_app_list.ftl @@ -1,8 +1,8 @@ -cosmic-app-list = Лоток застосунків -pin = Пришпилити до лотка застосунків +cosmic-app-list = Панель застосунків +pin = Пришпилити до панелі застосунків quit = Вийти quit-all = Закрити всі new-window = Нове вікно run = Запустити run-on = Запустити на { $gpu } -run-on-default = (звичайній) +run-on-default = (звичайний) diff --git a/cosmic-app-list/i18n/zh-CN/cosmic_app_list.ftl b/cosmic-app-list/i18n/zh-CN/cosmic_app_list.ftl index d8cd683d..b7ef6fda 100644 --- a/cosmic-app-list/i18n/zh-CN/cosmic_app_list.ftl +++ b/cosmic-app-list/i18n/zh-CN/cosmic_app_list.ftl @@ -1,8 +1,8 @@ cosmic-app-list = 应用托盘 -pin = 固定 +pin = 固定到应用托盘 quit = 退出 quit-all = 全部退出 -new-window = 新窗口 +new-window = 新建窗口 run = 运行 -run-on = 在 {$gpu} 上运行 -run-on-default = (Default) +run-on = 在 { $gpu } 上运行 +run-on-default = (默认) diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs index 266e10be..04bdc910 100755 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -19,8 +19,7 @@ use cctk::{ workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1, }, }; -use cosmic::desktop::fde::unicase::Ascii; -use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env}; +use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env, unicase::Ascii}; use cosmic::{ Apply, Element, Task, app, applet::{ @@ -30,14 +29,16 @@ use cosmic::{ cosmic_config::{Config, CosmicConfigEntry}, desktop::IconSourceExt, iced::{ - self, Limits, Subscription, + self, Alignment, Background, Border, Length, Limits, Padding, Subscription, clipboard::mime::{AllowedMimeTypes, AsMimeTypes}, event::listen_with, platform_specific::shell::commands::popup::{destroy_popup, get_popup}, - widget::{Column, Row, column, mouse_area, row, vertical_rule, vertical_space}, + widget::{ + Column, Row, column, mouse_area, row, stack, text::Wrapping, vertical_rule, + vertical_space, + }, window, }, - iced_core::{Border, Padding}, iced_runtime::{core::event, dnd::peek_dnd}, surface, theme::{self, Button, Container}, @@ -52,7 +53,6 @@ use cosmic::{ use cosmic_app_list_config::{APP_ID, AppListConfig}; use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State; use futures::future::pending; -use iced::{Alignment, Background, Length}; use rustc_hash::FxHashMap; use std::{borrow::Cow, path::PathBuf, rc::Rc, str::FromStr, time::Duration}; use switcheroo_control::Gpu; @@ -79,8 +79,13 @@ static DND_FAVORITES: u64 = u64::MAX; impl AppletIconData { fn new(applet: &Context) -> Self { let icon_size = applet.suggested_size(false).0; - let padding = applet.suggested_padding(false); - let icon_spacing = 4.0; + let (major_padding, cross_padding) = applet.suggested_padding(false); + let (h_padding, v_padding) = if applet.is_horizontal() { + (major_padding as f32, cross_padding as f32) + } else { + (cross_padding as f32, major_padding as f32) + }; + let icon_spacing = applet.spacing as f32; let (dot_radius, bar_size) = match applet.size { Size::Hardcoded(_) => (2.0, 8.0), @@ -98,14 +103,31 @@ impl AppletIconData { } } }; - - let padding = padding as f32; - let padding = match applet.anchor { - PanelAnchor::Top => [padding - (dot_radius * 2. + 1.), padding, padding, padding], - PanelAnchor::Bottom => [padding, padding, padding - (dot_radius * 2. + 1.), padding], - PanelAnchor::Left => [padding, padding, padding, padding - (dot_radius * 2. + 1.)], - PanelAnchor::Right => [padding, padding - (dot_radius * 2. + 1.), padding, padding], + PanelAnchor::Top => [ + v_padding - (dot_radius * 2. + 1.), + h_padding, + v_padding, + h_padding, + ], + PanelAnchor::Bottom => [ + v_padding, + h_padding, + v_padding - (dot_radius * 2. + 1.), + h_padding, + ], + PanelAnchor::Left => [ + v_padding, + h_padding, + v_padding, + h_padding - (dot_radius * 2. + 1.), + ], + PanelAnchor::Right => [ + v_padding, + h_padding - (dot_radius * 2. + 1.), + v_padding, + h_padding, + ], }; AppletIconData { icon_size, @@ -170,12 +192,13 @@ impl DockItem { let app_icon = AppletIconData::new(applet); - let cosmic_icon = fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default()) - .as_cosmic_icon() - // sets the preferred icon size variant - .size(128) - .width(app_icon.icon_size.into()) - .height(app_icon.icon_size.into()); + let cosmic_icon = cosmic::widget::icon( + fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default()).as_cosmic_icon(), + ) + // sets the preferred icon size variant + .size(128) + .width(app_icon.icon_size.into()) + .height(app_icon.icon_size.into()); let indicator = { let container = if toplevels.len() <= 1 { @@ -257,7 +280,7 @@ impl DockItem { .first() .map(|t| Message::Toggle(t.0.foreign_toplevel.clone())) } else { - Some(Message::TopLevelListPopup(*id, window_id)) + Some(Message::ToplevelListPopup(*id, window_id)) }) .width(Length::Shrink) .height(Length::Shrink), @@ -335,6 +358,7 @@ struct CosmicAppList { active_workspaces: Vec, output_list: FxHashMap, locales: Vec, + hovered_toplevel: Option, overflow_favorites_popup: Option, overflow_active_popup: Option, } @@ -342,7 +366,7 @@ struct CosmicAppList { #[derive(Debug, Clone, PartialEq)] pub enum PopupType { RightClickMenu, - TopLevelList, + ToplevelList, } #[derive(Debug, Clone)] @@ -352,13 +376,15 @@ enum Message { UnpinApp(u32), Popup(u32, window::Id), Pressed(window::Id), - TopLevelListPopup(u32, window::Id), + ToplevelListPopup(u32, window::Id), + ToplevelHoverChanged(ExtForeignToplevelHandleV1, bool), GpuRequest(Option>), CloseRequested(window::Id), ClosePopup, Activate(ExtForeignToplevelHandleV1), Toggle(ExtForeignToplevelHandleV1), Exec(String, Option, bool), + CloseToplevel(ExtForeignToplevelHandleV1), Quit(String), NewSeat(WlSeat), RemovedSeat, @@ -435,58 +461,76 @@ async fn try_get_gpus() -> Option> { Some(gpus) } -const TOPLEVEL_BUTTON_WIDTH: f32 = 160.0; -const TOPLEVEL_BUTTON_HEIGHT: f32 = 130.0; +const TOPLEVEL_BUTTON_WIDTH: f32 = 192.0; +const TOPLEVEL_BUTTON_HEIGHT: f32 = 156.0; -pub fn toplevel_button<'a, Msg>( +fn toplevel_button<'a>( img: Option, - on_press: Msg, title: String, + handle: ExtForeignToplevelHandleV1, is_focused: bool, -) -> cosmic::widget::Button<'a, Msg> -where - Msg: 'static + Clone, -{ + is_hovered: bool, +) -> Element<'a, Message> { + let title = if title.len() > 22 { + format!("{:.20}...", title) + } else { + title + }; let border = 1.0; - button::custom( - container( - column![ - container(if let Some(img) = img { - Element::from(Image::new(Handle::from_rgba( - img.width, - img.height, - img.img.clone(), - ))) - } else { - Image::new(Handle::from_rgba(1, 1, [0u8, 0u8, 0u8, 255u8].as_slice())).into() - }) - .class(Container::Custom(Box::new(move |theme| { - container::Style { - border: Border { - color: theme.cosmic().bg_divider().into(), - width: border, - radius: 0.0.into(), - }, - ..Default::default() - } - }))) - .padding(border as u16) - .height(Length::Shrink) - .width(Length::Shrink) - .apply(container) - .center_y(Length::Fixed(90.0)), - text::body(title), - ] - .spacing(4) - .align_x(Alignment::Center), - ) + let preview = column![ + container(if let Some(img) = img { + Element::from(Image::new(Handle::from_rgba( + img.width, + img.height, + img.img.clone(), + ))) + } else { + Image::new(Handle::from_rgba(1, 1, [0u8, 0u8, 0u8, 255u8].as_slice())).into() + }) + .class(Container::custom(move |theme| container::Style { + border: Border { + color: theme.cosmic().bg_divider().into(), + width: border, + radius: 1.0.into(), + }, + ..Default::default() + })) + .padding(border as u16) + .apply(container) .center(Length::Fill), - ) - .on_press(on_press) - .class(window_menu_style(is_focused)) - .width(Length::Fixed(TOPLEVEL_BUTTON_WIDTH)) - .height(Length::Fixed(TOPLEVEL_BUTTON_HEIGHT)) - .selected(is_focused) + text::body(title) + .wrapping(Wrapping::None) + .width(Length::Fill) + .center() + ] + .spacing(4) + .padding([4, 4, 0, 4]); + let close_button_overlay = if is_hovered { + row![ + horizontal_space(), + button::custom(icon::from_name("window-close-symbolic").size(16)) + .class(Button::Destructive) + .on_press(Message::CloseToplevel(handle.clone())) + .padding(4) + ] + } else { + row![] + } + .width(Length::Fill) + .height(Length::Fill); + + stack![preview, close_button_overlay] + .apply(button::custom) + .on_press(Message::Toggle(handle.clone())) + .class(window_menu_style(is_focused)) + .width(Length::Fixed(TOPLEVEL_BUTTON_WIDTH)) + .height(Length::Fixed(TOPLEVEL_BUTTON_HEIGHT)) + .padding(4) + .selected(is_focused) + .apply(mouse_area) + .on_enter(Message::ToplevelHoverChanged(handle.clone(), true)) + .on_exit(Message::ToplevelHoverChanged(handle, false)) + .apply(Element::from) } fn window_menu_style(selected: bool) -> cosmic::theme::Button { @@ -567,7 +611,7 @@ fn app_list_icon_style(selected: bool) -> cosmic::theme::Button { #[inline] pub fn menu_control_padding() -> Padding { - let spacing = cosmic::theme::spacing(); + let spacing = theme::spacing(); [spacing.space_xxs, spacing.space_s].into() } @@ -605,6 +649,158 @@ impl CosmicAppList { }) .collect(); } + + /// Close any open popups. + fn close_popups(&mut self) -> Task> { + let mut commands = Vec::new(); + if let Some(popup) = self.popup.take() { + commands.push(destroy_popup(popup.id)); + } + if let Some(popup) = self.overflow_active_popup.take() { + commands.push(destroy_popup(popup)); + } + if let Some(popup) = self.overflow_favorites_popup.take() { + commands.push(destroy_popup(popup)); + } + Task::batch(commands) + } + + /// Returns the length of the group in the favorite list after which items are displayed in a popup. + /// Shrink the favorite list until it only has active windows, or until it fits in the length provided. + fn panel_overflow_lengths(&self) -> (Option, Option) { + let mut favorite_index; + let mut active_index = None; + let Some(mut max_major_axis_len) = self.core.applet.suggested_bounds.as_ref().map(|c| { + // if we have a configure for width and height, we're in a overflow popup + match self.core.applet.anchor { + PanelAnchor::Top | PanelAnchor::Bottom => c.width as u32, + PanelAnchor::Left | PanelAnchor::Right => c.height as u32, + } + }) else { + return (None, active_index); + }; + // tracing::error!("{} {}", max_major_axis_len, self.pinned_list.len()); + // subtract the divider width + max_major_axis_len -= 1; + let applet_icon = AppletIconData::new(&self.core.applet); + + let button_total_size = self.core.applet.suggested_size(true).0 + + self.core.applet.suggested_padding(true).0 * 2 + + applet_icon.icon_spacing as u16; + + let favorite_active_cnt = self + .pinned_list + .iter() + .filter(|t| !t.toplevels.is_empty()) + .count(); + + // initial calculation of favorite_index + let btn_count = max_major_axis_len / button_total_size as u32; + if btn_count >= self.pinned_list.len() as u32 + self.active_list.len() as u32 { + return (None, active_index); + } else { + favorite_index = (btn_count as usize).min(favorite_active_cnt).max(2); + } + + // calculation of active_index based on favorite_index if there is still not enough space + let active_index_max = (btn_count as i32) + - (self.pinned_list.len() as i32).saturating_sub(favorite_index as i32); + if active_index_max >= self.active_list.len() as i32 { + active_index = Some(self.active_list.len()); + } else { + active_index = Some((active_index_max.max(2) as usize).min(self.active_list.len())); + } + + // final calculation of favorite_index if there is still not enough space + if let Some(active_index) = active_index { + let favorite_index_max = (btn_count as i32) - active_index as i32; + favorite_index = favorite_index_max.max(2) as usize; + } else { + favorite_index = (btn_count as usize).min(self.pinned_list.len()); + } + // tracing::error!("{} {} {:?}", btn_count, favorite_index, active_index); + (Some(favorite_index), active_index) + } + + fn currently_active_toplevel(&self) -> Vec { + if self.active_workspaces.is_empty() { + return Vec::new(); + } + let current_output = &self.core.applet.output_name; + let mut focused_toplevels: Vec = Vec::new(); + let active_workspaces = &self.active_workspaces; + for toplevel_list in self.active_list.iter().chain(self.pinned_list.iter()) { + for (t_info, _) in &toplevel_list.toplevels { + if t_info.state.contains(&State::Activated) + && active_workspaces + .iter() + .any(|workspace| t_info.workspace.contains(workspace)) + && t_info.output.iter().any(|x| { + self.output_list.get(x).is_some_and(|val| { + val.name.as_ref().is_some_and(|n| n == current_output) + }) + }) + { + focused_toplevels.push(t_info.foreign_toplevel.clone()); + } + } + } + focused_toplevels + } + + fn find_desktop_entry_for_toplevel( + &mut self, + info: &ToplevelInfo, + unicase_appid: Ascii<&str>, + ) -> DesktopEntry { + if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { + appid.clone() + } else { + // Update desktop entries in case it was not found. + self.update_desktop_entries(); + if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { + appid.clone() + } else { + tracing::error!(id = info.app_id, "could not find desktop entry for app"); + let mut fallback_entry = fde::DesktopEntry::from_appid(info.app_id.clone()); + // proton opens games as steam_app_X, where X is either + // the steam appid or "default". games with a steam appid + // can have a desktop entry generated elsewhere; this + // specifically handles non-steam games opened + // under proton + // in addition, try to match WINE entries who have its + // appid = the full name of the executable (incl. .exe) + let is_proton_game = info.app_id == "steam_app_default"; + if is_proton_game || info.app_id.ends_with(".exe") { + for entry in &self.desktop_entries { + let localised_name = entry.name(&self.locales).unwrap_or_default(); + if localised_name == info.title { + // if this is a proton game, we only want + // to look for game entries + if is_proton_game + && !entry.categories().unwrap_or_default().contains(&"Game") + { + continue; + } + fallback_entry = entry.clone(); + break; + } + } + } + fallback_entry + } + } + } + + // Check if a specific toplevel is focused + fn is_focused(&self, handle: &ExtForeignToplevelHandleV1) -> bool { + self.currently_active_toplevel().contains(handle) + } + + // Check if a specific toplevel button is currently hovered + fn is_hovered(&self, handle: &ExtForeignToplevelHandleV1) -> bool { + self.hovered_toplevel.as_ref() == Some(handle) + } } impl cosmic::Application for CosmicAppList { @@ -709,7 +905,7 @@ impl cosmic::Application for CosmicAppList { return Task::batch([gpu_update, get_popup(popup_settings)]); } } - Message::TopLevelListPopup(id, parent_window_id) => { + Message::ToplevelListPopup(id, parent_window_id) => { if let Some(Popup { parent, id: popup_id, @@ -746,7 +942,7 @@ impl cosmic::Application for CosmicAppList { parent: parent_window_id, id: new_id, dock_item: toplevel_group.clone(), - popup_type: PopupType::TopLevelList, + popup_type: PopupType::ToplevelList, }); let mut popup_settings = self.core.applet.get_popup_settings( @@ -792,6 +988,14 @@ impl cosmic::Application for CosmicAppList { return get_popup(popup_settings); } } + Message::ToplevelHoverChanged(handle, entering) => { + match (entering, &self.hovered_toplevel) { + (true, _) => self.hovered_toplevel = Some(handle), + // prevents race condition + (false, Some(h)) if h == &handle => self.hovered_toplevel = None, + _ => {} + } + } Message::PinApp(id) => { if let Some(i) = self.active_list.iter().position(|t| t.id == id) { let entry = self.active_list.remove(i); @@ -833,18 +1037,21 @@ impl cosmic::Application for CosmicAppList { } Message::Toggle(handle) => { if let Some(tx) = self.wayland_sender.as_ref() { - let _ = tx.send(WaylandRequest::Toplevel( - if self.currently_active_toplevel().contains(&handle) { - ToplevelRequest::Minimize(handle) - } else { - ToplevelRequest::Activate(handle) - }, - )); + let _ = tx.send(WaylandRequest::Toplevel(if self.is_focused(&handle) { + ToplevelRequest::Minimize(handle) + } else { + ToplevelRequest::Activate(handle) + })); } if let Some(p) = self.popup.take() { return destroy_popup(p.id); } } + Message::CloseToplevel(handle) => { + if let Some(tx) = self.wayland_sender.as_ref() { + let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Quit(handle))); + } + } Message::Quit(id) => { if let Some(toplevel_group) = self .active_list @@ -920,7 +1127,7 @@ impl cosmic::Application for CosmicAppList { } Message::DndEnter(x, y) => { let item_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).0; let pos_in_list = match self.core.applet.anchor { PanelAnchor::Top | PanelAnchor::Bottom => x as f32, PanelAnchor::Left | PanelAnchor::Right => y as f32, @@ -942,7 +1149,7 @@ impl cosmic::Application for CosmicAppList { } Message::DndMotion(x, y) => { let item_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).0; let pos_in_list = match self.core.applet.anchor { PanelAnchor::Top | PanelAnchor::Bottom => x as f32, PanelAnchor::Left | PanelAnchor::Right => y as f32, @@ -1122,6 +1329,21 @@ impl cosmic::Application for CosmicAppList { .retain(|(info, _)| info.foreign_toplevel != handle); } self.active_list.retain(|t| !t.toplevels.is_empty()); + + if let Some(popup) = &mut self.popup + && popup.popup_type == PopupType::ToplevelList + { + popup + .dock_item + .toplevels + .retain(|(info, _)| info.foreign_toplevel != handle); + + if popup.dock_item.toplevels.is_empty() { + let id = popup.id; + self.popup = None; + return destroy_popup(id); + } + } } ToplevelUpdate::Update(info) => { // TODO probably want to make sure it is removed @@ -1350,7 +1572,7 @@ impl cosmic::Application for CosmicAppList { }; } let applet_suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).0; let (_favorite_popup_cutoff, active_popup_cutoff) = self.panel_overflow_lengths(); let popup_applet_count = @@ -1407,7 +1629,7 @@ impl cosmic::Application for CosmicAppList { }; } let applet_suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).0; let (favorite_popup_cutoff, _active_popup_cutoff) = self.panel_overflow_lengths(); let popup_applet_count = @@ -1573,7 +1795,7 @@ impl cosmic::Application for CosmicAppList { icon::from_name("starred-symbolic.symbolic") .size(self.core.applet.suggested_size(false).0), ) - .padding(self.core.applet.suggested_padding(false)) + .padding(self.core.applet.suggested_padding(false).1) // TODO .into(), ); } @@ -1643,13 +1865,13 @@ impl cosmic::Application for CosmicAppList { let window_size = self.core.applet.suggested_bounds.as_ref(); let max_num = if self.core.applet.is_horizontal() { let suggested_width = self.core.applet.suggested_size(false).0 - + self.core.applet.suggested_padding(false) * 2; + + self.core.applet.suggested_padding(false).0 * 2; window_size .map(|w| w.width) .map_or(u32::MAX, |b| (b / suggested_width as f32) as u32) as usize } else { let suggested_height = self.core.applet.suggested_size(false).1 - + self.core.applet.suggested_padding(false) * 2; + + self.core.applet.suggested_padding(false).0 * 2; window_size .map(|w| w.height) .map_or(u32::MAX, |b| (b / suggested_height as f32) as u32) as usize @@ -1756,10 +1978,12 @@ impl cosmic::Application for CosmicAppList { let theme = self.core.system_theme(); if let Some((_, item, _, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) { - fde::IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default()) - .as_cosmic_icon() - .size(self.core.applet.suggested_size(false).0) - .into() + cosmic::widget::icon( + fde::IconSource::from_unknown(item.desktop_info.icon().unwrap_or_default()) + .as_cosmic_icon(), + ) + .size(self.core.applet.suggested_size(false).0) + .into() } else if let Some(Popup { dock_item: DockItem { id, .. }, popup_type, @@ -1787,7 +2011,7 @@ impl cosmic::Application for CosmicAppList { content: impl Into>, ) -> cosmic::widget::Button<'a, Message> { button::custom(content) - .height(20 + 2 * theme::active().cosmic().space_xxs()) + .height(20 + 2 * theme::spacing().space_xxs) .class(Button::MenuItem) .padding(menu_control_padding()) .width(Length::Fill) @@ -1935,22 +2159,17 @@ impl cosmic::Application for CosmicAppList { ) .into() } - PopupType::TopLevelList => match self.core.applet.anchor { + PopupType::ToplevelList => match self.core.applet.anchor { PanelAnchor::Left | PanelAnchor::Right => { let mut content = column![].padding(8).align_x(Alignment::Center).spacing(8); for (info, img) in toplevels { - let title = if info.title.len() > 18 { - format!("{:.16}...", &info.title) - } else { - info.title.clone() - }; content = content.push(toplevel_button( img.clone(), - Message::Toggle(info.foreign_toplevel.clone()), - title, - self.currently_active_toplevel() - .contains(&info.foreign_toplevel), + info.title.clone(), + info.foreign_toplevel.clone(), + self.is_focused(&info.foreign_toplevel), + self.is_hovered(&info.foreign_toplevel), )); } self.core @@ -1962,17 +2181,12 @@ impl cosmic::Application for CosmicAppList { PanelAnchor::Bottom | PanelAnchor::Top => { let mut content = row![].padding(8).align_y(Alignment::Center).spacing(8); for (info, img) in toplevels { - let title = if info.title.len() > 18 { - format!("{:.16}...", &info.title) - } else { - info.title.clone() - }; content = content.push(toplevel_button( img.clone(), - Message::Toggle(info.foreign_toplevel.clone()), - title, - self.currently_active_toplevel() - .contains(&info.foreign_toplevel), + info.title.clone(), + info.foreign_toplevel.clone(), + self.is_focused(&info.foreign_toplevel), + self.is_hovered(&info.foreign_toplevel), )); } self.core @@ -1993,44 +2207,46 @@ impl cosmic::Application for CosmicAppList { let focused_item = self.currently_active_toplevel(); let dot_radius = theme.cosmic().radius_xs(); // show the overflow popup for active list - let active: Vec<_> = - self.active_list[..active_popup_cutoff.map_or(self.active_list.len(), |n| { + let active: Vec<_> = self + .active_list + .iter() + .rev() + .take(active_popup_cutoff.map_or(self.active_list.len(), |n| { if n < self.active_list.len() { - n.saturating_sub(1) + self.active_list.len() - n + 1 } else { - n - 1 + 0 } - })] - .iter() - .map(|dock_item| { - self.core - .applet - .applet_tooltip( - dock_item.as_icon( - &self.core.applet, - self.rectangle_tracker.as_ref(), - self.popup.is_none(), - self.config.enable_drag_source, - self.gpus.as_deref(), - dock_item - .toplevels - .iter() - .any(|y| focused_item.contains(&y.0.foreign_toplevel)), - dot_radius, - self.core.main_window_id().unwrap(), - ), + })) + .map(|dock_item| { + self.core + .applet + .applet_tooltip( + dock_item.as_icon( + &self.core.applet, + self.rectangle_tracker.as_ref(), + self.popup.is_none(), + self.config.enable_drag_source, + self.gpus.as_deref(), dock_item - .desktop_info - .full_name(&self.locales) - .unwrap_or_default() - .into_owned(), - self.popup.is_some(), - Message::Surface, - None, - ) - .into() - }) - .collect(); + .toplevels + .iter() + .any(|y| focused_item.contains(&y.0.foreign_toplevel)), + dot_radius, + id, + ), + dock_item + .desktop_info + .full_name(&self.locales) + .unwrap_or_default() + .into_owned(), + self.popup.is_some(), + Message::Surface, + Some(id), + ) + .into() + }) + .collect(); let content = match &self.core.applet.anchor { PanelAnchor::Left | PanelAnchor::Right => container( Column::with_children(active) @@ -2212,155 +2428,6 @@ impl cosmic::Application for CosmicAppList { } } -impl CosmicAppList { - /// Close any open popups. - fn close_popups(&mut self) -> Task> { - let mut commands = Vec::new(); - if let Some(popup) = self.popup.take() { - commands.push(destroy_popup(popup.id)); - } - if let Some(popup) = self.overflow_active_popup.take() { - commands.push(destroy_popup(popup)); - } - if let Some(popup) = self.overflow_favorites_popup.take() { - commands.push(destroy_popup(popup)); - } - Task::batch(commands) - } - /// Returns the length of the group in the favorite list after which items are displayed in a popup. - /// Shrink the favorite list until it only has active windows, or until it fits in the length provided. - fn panel_overflow_lengths(&self) -> (Option, Option) { - let mut favorite_index; - let mut active_index = None; - let Some(mut max_major_axis_len) = self.core.applet.suggested_bounds.as_ref().map(|c| { - // if we have a configure for width and height, we're in a overflow popup - match self.core.applet.anchor { - PanelAnchor::Top | PanelAnchor::Bottom => c.width as u32, - PanelAnchor::Left | PanelAnchor::Right => c.height as u32, - } - }) else { - return (None, active_index); - }; - // tracing::error!("{} {}", max_major_axis_len, self.pinned_list.len()); - // subtract the divider width - max_major_axis_len -= 1; - let applet_icon = AppletIconData::new(&self.core.applet); - - let button_total_size = self.core.applet.suggested_size(true).0 - + self.core.applet.suggested_padding(true) * 2 - + applet_icon.icon_spacing as u16; - - let favorite_active_cnt = self - .pinned_list - .iter() - .filter(|t| !t.toplevels.is_empty()) - .count(); - - // initial calculation of favorite_index - let btn_count = max_major_axis_len / button_total_size as u32; - if btn_count >= self.pinned_list.len() as u32 + self.active_list.len() as u32 { - return (None, active_index); - } else { - favorite_index = (btn_count as usize).min(favorite_active_cnt).max(2); - } - - // calculation of active_index based on favorite_index if there is still not enough space - let active_index_max = (btn_count as i32) - - (self.pinned_list.len() as i32).saturating_sub(favorite_index as i32); - if active_index_max >= self.active_list.len() as i32 { - active_index = Some(self.active_list.len()); - } else { - active_index = Some((active_index_max.max(2) as usize).min(self.active_list.len())); - } - - // final calculation of favorite_index if there is still not enough space - if let Some(active_index) = active_index { - let favorite_index_max = (btn_count as i32) - active_index as i32; - favorite_index = favorite_index_max.max(2) as usize; - } else { - favorite_index = (btn_count as usize).min(self.pinned_list.len()); - } - // tracing::error!("{} {} {:?}", btn_count, favorite_index, active_index); - (Some(favorite_index), active_index) - } - - fn currently_active_toplevel(&self) -> Vec { - if self.active_workspaces.is_empty() { - return Vec::new(); - } - let current_output = &self.core.applet.output_name; - let mut focused_toplevels: Vec = Vec::new(); - let active_workspaces = &self.active_workspaces; - for toplevel_list in self.active_list.iter().chain(self.pinned_list.iter()) { - for (t_info, _) in &toplevel_list.toplevels { - if t_info.state.contains(&State::Activated) - && active_workspaces - .iter() - .any(|workspace| t_info.workspace.contains(workspace)) - && t_info.output.iter().any(|x| { - self.output_list.get(x).is_some_and(|val| { - val.name.as_ref().is_some_and(|n| n == current_output) - }) - }) - { - focused_toplevels.push(t_info.foreign_toplevel.clone()); - } - } - } - focused_toplevels - } - - fn find_desktop_entry_for_toplevel( - &mut self, - info: &ToplevelInfo, - unicase_appid: Ascii<&str>, - ) -> DesktopEntry { - if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { - appid.clone() - } else { - // Update desktop entries in case it was not found. - - self.update_desktop_entries(); - if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { - appid.clone() - } else { - tracing::error!(id = info.app_id, "could not find desktop entry for app"); - - let mut fallback_entry = fde::DesktopEntry::from_appid(info.app_id.clone()); - - // proton opens games as steam_app_X, where X is either - // the steam appid or "default". games with a steam appid - // can have a desktop entry generated elsewhere; this - // specifically handles non-steam games opened - // under proton - // in addition, try to match WINE entries who have its - // appid = the full name of the executable (incl. .exe) - let is_proton_game = info.app_id == "steam_app_default"; - if is_proton_game || info.app_id.ends_with(".exe") { - for entry in &self.desktop_entries { - let localised_name = entry.name(&self.locales).unwrap_or_default(); - - if localised_name == info.title { - // if this is a proton game, we only want - // to look for game entries - if is_proton_game - && !entry.categories().unwrap_or_default().contains(&"Game") - { - continue; - } - - fallback_entry = entry.clone(); - break; - } - } - } - - fallback_entry - } - } - } -} - fn launch_on_preferred_gpu(desktop_info: &DesktopEntry, gpus: Option<&[Gpu]>) -> Option { let exec = desktop_info.exec()?; diff --git a/cosmic-app-list/src/wayland_handler.rs b/cosmic-app-list/src/wayland_handler.rs index 248ec3b6..03c16973 100644 --- a/cosmic-app-list/src/wayland_handler.rs +++ b/cosmic-app-list/src/wayland_handler.rs @@ -506,9 +506,9 @@ impl AppData { return; }; - // resize to 128x128 + // resize to 256x256 let max = img.width().max(img.height()); - let ratio = max as f32 / 128.0; + let ratio = max as f32 / 256.0; let img = if ratio > 1.0 { let new_width = (img.width() as f32 / ratio).round(); diff --git a/cosmic-applet-a11y/Cargo.toml b/cosmic-applet-a11y/Cargo.toml index 48945b5b..f34d9434 100644 --- a/cosmic-applet-a11y/Cargo.toml +++ b/cosmic-applet-a11y/Cargo.toml @@ -1,14 +1,9 @@ [package] name = "cosmic-applet-a11y" -version = "0.1.0" +version = "1.0.2" edition = "2024" [dependencies] -# cosmic-dbus-a11y = { git = "https://github.com/pop-os/dbus-settings-bindings" } -cosmic-settings-subscriptions = { workspace = true, features = [ - "accessibility", - "cosmic_a11y_manager", -] } anyhow.workspace = true cctk.workspace = true cosmic-protocols.workspace = true @@ -21,3 +16,9 @@ tokio.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true + +[dependencies.cosmic-settings-a11y-manager-subscription] +git = "https://github.com/pop-os/cosmic-settings" + +[dependencies.cosmic-settings-accessibility-subscription] +git = "https://github.com/pop-os/cosmic-settings" diff --git a/cosmic-applet-a11y/data/com.system76.CosmicAppletA11y.desktop b/cosmic-applet-a11y/data/com.system76.CosmicAppletA11y.desktop index 6f37f04d..b7413930 100644 --- a/cosmic-applet-a11y/data/com.system76.CosmicAppletA11y.desktop +++ b/cosmic-applet-a11y/data/com.system76.CosmicAppletA11y.desktop @@ -12,6 +12,7 @@ Name[de]= Zugänglichkeit Name[sk]=Zjednodušenie ovládania Name[sv]=Tillgänglighet Name[es]=Accesibilidad +Name[it]=Accessibilità Type=Application Exec=cosmic-applet-a11y Terminal=false @@ -22,6 +23,7 @@ Icon=preferences-desktop-accessibility-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true # Indicates that the auto-hover click should go to the "end" of the hover popup X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-a11y/i18n/bn/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/bn/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/fr/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/fr/cosmic_applet_a11y.ftl index e69de29b..f646e355 100644 --- a/cosmic-applet-a11y/i18n/fr/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/fr/cosmic_applet_a11y.ftl @@ -0,0 +1,6 @@ +screen-reader = Lecteur d'écran +invert-colors = Inverser les couleurs +high-contrast = Contraste élevé +filter-colors = Filtre de couleurs +settings = Paramètres d'accessibilité... +magnifier = Loupe diff --git a/cosmic-applet-a11y/i18n/ga/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/ga/cosmic_applet_a11y.ftl index ec3219bd..1aeedb8a 100644 --- a/cosmic-applet-a11y/i18n/ga/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/ga/cosmic_applet_a11y.ftl @@ -3,4 +3,4 @@ magnifier = Formhéadaitheoir invert-colors = Inbhéartaigh dathanna settings = Socruithe inrochtaineachta... filter-colors = Dathanna scagaire -high-contrast = Ardchodarsnacht +high-contrast = Ard chodarsnacht diff --git a/cosmic-applet-a11y/i18n/hu/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/hu/cosmic_applet_a11y.ftl index 46bafb0e..361def69 100644 --- a/cosmic-applet-a11y/i18n/hu/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/hu/cosmic_applet_a11y.ftl @@ -1,6 +1,6 @@ screen-reader = Képernyőolvasó magnifier = Nagyító invert-colors = Színek invertálása -settings = Akadálymentességi beállítások... +settings = Akadálymentességi beállítások… filter-colors = Színek szűrése high-contrast = Magas kontraszt diff --git a/cosmic-applet-a11y/i18n/ka/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/ka/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/kab/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/kab/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/kk/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/kk/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e42a2856 --- /dev/null +++ b/cosmic-applet-a11y/i18n/kk/cosmic_applet_a11y.ftl @@ -0,0 +1,6 @@ +screen-reader = Экраннан оқу қолданбасы +magnifier = Экрандық ұлғайтқыш +invert-colors = Түстерді терістеу +settings = Қолжетімділік баптаулары... +filter-colors = Түстерді сүзгілеу +high-contrast = Жоғары контраст diff --git a/cosmic-applet-a11y/i18n/kmr/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/kmr/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/ko/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/ko/cosmic_applet_a11y.ftl index e69de29b..7f804e9e 100644 --- a/cosmic-applet-a11y/i18n/ko/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/ko/cosmic_applet_a11y.ftl @@ -0,0 +1,6 @@ +screen-reader = 스크린 리더 +invert-colors = 색상 반전 +high-contrast = 고대비 +filter-colors = 색상 필터 +settings = 접근성 설정... +magnifier = 돋보기 diff --git a/cosmic-applet-a11y/i18n/lt/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/lt/cosmic_applet_a11y.ftl index e69de29b..8010b373 100644 --- a/cosmic-applet-a11y/i18n/lt/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/lt/cosmic_applet_a11y.ftl @@ -0,0 +1,6 @@ +screen-reader = Ekrano skaitytuvas +invert-colors = Invertuoti spalvas +high-contrast = Didelis kontrastas +filter-colors = Spalvų filtras +settings = Pritaikomumo nustatymai... +magnifier = Didinamasis Stiklas diff --git a/cosmic-applet-a11y/i18n/nl/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/nl/cosmic_applet_a11y.ftl index 8ee1edf9..5d747801 100644 --- a/cosmic-applet-a11y/i18n/nl/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/nl/cosmic_applet_a11y.ftl @@ -1,6 +1,6 @@ -screen-reader = Schermverteller +screen-reader = Schermlezer magnifier = Vergrootglas -invert-colors = Kleuren omkeren +invert-colors = Keer kleuren om settings = Toegankelijkheidsinstellingen... filter-colors = filterkleuren high-contrast = Hoog contrast diff --git a/cosmic-applet-a11y/i18n/pa/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/pa/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/ti/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/ti/cosmic_applet_a11y.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-a11y/i18n/uk/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/uk/cosmic_applet_a11y.ftl index 10fecdf6..babffa73 100644 --- a/cosmic-applet-a11y/i18n/uk/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/uk/cosmic_applet_a11y.ftl @@ -2,5 +2,5 @@ screen-reader = Читач екрана magnifier = Лупа invert-colors = Інверсія кольорів settings = Налаштування доступності... -filter-colors = Виправлення кольорової сліпоти +filter-colors = Фільтрувати кольори high-contrast = Висока контрастність diff --git a/cosmic-applet-a11y/i18n/zh-CN/cosmic_applet_a11y.ftl b/cosmic-applet-a11y/i18n/zh-CN/cosmic_applet_a11y.ftl index 9a6418b0..c56bd4af 100644 --- a/cosmic-applet-a11y/i18n/zh-CN/cosmic_applet_a11y.ftl +++ b/cosmic-applet-a11y/i18n/zh-CN/cosmic_applet_a11y.ftl @@ -1,6 +1,6 @@ screen-reader = 屏幕阅读器 magnifier = 放大镜 -invert-colors = 反转颜色 +invert-colors = 颜色反转 settings = 无障碍设置... -filter-colors = 滤镜颜色 -high-contrast = 高对比度 +filter-colors = 颜色滤镜 +high-contrast = 高对比度模式 diff --git a/cosmic-applet-a11y/src/app.rs b/cosmic-applet-a11y/src/app.rs index e79f96fc..27c2dba3 100644 --- a/cosmic-applet-a11y/src/app.rs +++ b/cosmic-applet-a11y/src/app.rs @@ -24,10 +24,11 @@ use cosmic::{ theme::{self, CosmicTheme}, widget::{Column, divider, text}, }; -use cosmic_settings_subscriptions::{ - accessibility::{self, DBusRequest, DBusUpdate}, - cosmic_a11y_manager::{AccessibilityEvent, AccessibilityRequest, ColorFilter}, + +use cosmic_settings_a11y_manager_subscription::{ + self as cosmic_a11y_manager, AccessibilityEvent, AccessibilityRequest, ColorFilter, }; +use cosmic_settings_accessibility_subscription::{self as accessibility}; use cosmic_time::{Instant, Timeline, anim, chain, id}; use std::sync::LazyLock; use tokio::sync::mpsc::UnboundedSender; @@ -50,7 +51,7 @@ struct CosmicA11yApplet { magnifier_enabled: bool, inverted_colors_enabled: bool, popup: Option, - dbus_sender: Option>, + dbus_sender: Option>, wayland_sender: Option>, wayland_protocol_version: Option, timeline: Timeline, @@ -70,7 +71,7 @@ enum Message { Frame(Instant), Token(TokenUpdate), OpenSettings, - DBusUpdate(DBusUpdate), + DBusUpdate(accessibility::Response), WaylandUpdate(WaylandUpdate), Surface(surface::Action), } @@ -108,7 +109,7 @@ impl cosmic::Application for CosmicA11yApplet { if let Some(tx) = &self.dbus_sender { self.timeline.set_chain(chain).start(); self.reader_enabled = enabled; - let _ = tx.send(DBusRequest::Status(enabled)); + let _ = tx.send(accessibility::Request::ScreenReader(enabled)); } else { self.reader_enabled = false; } @@ -255,18 +256,19 @@ impl cosmic::Application for CosmicA11yApplet { } }, Message::DBusUpdate(update) => match update { - DBusUpdate::Error(err) => { + accessibility::Response::Error(err) => { tracing::error!("{err}"); let _ = self.dbus_sender.take(); self.reader_enabled = false; } - DBusUpdate::Status(enabled) => { + accessibility::Response::ScreenReader(enabled) => { self.reader_enabled = enabled; } - DBusUpdate::Init(enabled, tx) => { + accessibility::Response::Init(enabled, tx) => { self.reader_enabled = enabled; self.dbus_sender = Some(tx); } + _ => (), }, Message::WaylandUpdate(update) => match update { WaylandUpdate::Errored => { diff --git a/cosmic-applet-a11y/src/backend/wayland/mod.rs b/cosmic-applet-a11y/src/backend/wayland/mod.rs index 67b48a96..f66cbb2d 100644 --- a/cosmic-applet-a11y/src/backend/wayland/mod.rs +++ b/cosmic-applet-a11y/src/backend/wayland/mod.rs @@ -9,7 +9,7 @@ use cosmic::iced::{ stream, }; use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1::Filter; -use cosmic_settings_subscriptions::cosmic_a11y_manager::{ +use cosmic_settings_a11y_manager_subscription::{ self as thread, AccessibilityEvent, AccessibilityRequest, }; use std::sync::LazyLock; diff --git a/cosmic-applet-audio/Cargo.toml b/cosmic-applet-audio/Cargo.toml index 0ef5e6e8..2b4d3624 100644 --- a/cosmic-applet-audio/Cargo.toml +++ b/cosmic-applet-audio/Cargo.toml @@ -1,17 +1,16 @@ [package] name = "cosmic-applet-audio" -version = "0.1.1" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" [dependencies] -cosmic-settings-subscriptions.workspace = true cosmic-time.workspace = true i18n-embed-fl.workspace = true i18n-embed.workspace = true libcosmic.workspace = true -libpulse-binding = "2.30.1" mpris2-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" } +# mpris2-zbus = { path = "../../dbus-settings-bindings/mpris2" } rust-embed.workspace = true serde.workspace = true tokio.workspace = true @@ -21,3 +20,6 @@ tracing.workspace = true url = "2" urlencoding = "2.1.3" zbus.workspace = true + +[dependencies.cosmic-settings-sound-subscription] +git = "https://github.com/pop-os/cosmic-settings" diff --git a/cosmic-applet-audio/data/com.system76.CosmicAppletAudio.desktop b/cosmic-applet-audio/data/com.system76.CosmicAppletAudio.desktop index 3eea645b..13ddfaf4 100644 --- a/cosmic-applet-audio/data/com.system76.CosmicAppletAudio.desktop +++ b/cosmic-applet-audio/data/com.system76.CosmicAppletAudio.desktop @@ -13,6 +13,7 @@ Name[de]=Klang Name[sk]=Zvuk Name[sv]=Ljud Name[es]=Sonido +Name[it]=Audio Type=Application Exec=cosmic-applet-audio Terminal=false @@ -23,6 +24,7 @@ Icon=com.system76.CosmicAppletAudio-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true # Indicates that the auto-hover click should go to the "end" of the hover popup X-CosmicHoverPopup=End X-OverflowPriority=10 diff --git a/cosmic-applet-audio/i18n/ar/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/ar/cosmic_applet_audio.ftl index ec3eba16..3683cc35 100644 --- a/cosmic-applet-audio/i18n/ar/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/ar/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = الإخراج input = الإدخال -show-media-controls = إظهار عناصر التحكم بالوسائط على اللوحة +show-media-controls = أظهِر عناصر التحكم بالوسائط على اللوحة sound-settings = إعدادات الصوت... disconnected = غير متصل ببولس أوديو -no-device = لم يتم اختيار جهاز -unknown-artist = فنان غير معروف +no-device = لا جهاز محدّد +unknown-artist = مجهول diff --git a/cosmic-applet-audio/i18n/bn/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/bn/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/fr/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/fr/cosmic_applet_audio.ftl index d969ab67..5198c4fc 100644 --- a/cosmic-applet-audio/i18n/fr/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/fr/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = Sortie input = Entrée -show-media-controls = Afficher les contrôles média sur le panel supérieur -sound-settings = Paramètres sonores... +show-media-controls = Afficher les contrôles média sur le panneau +sound-settings = Paramètres audio... disconnected = PulseAudio est déconnecté -no-device = Pas de périphérique sélectionné +no-device = Aucun périphérique sélectionné unknown-artist = Inconnu(e) diff --git a/cosmic-applet-audio/i18n/ga/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/ga/cosmic_applet_audio.ftl index 84bad8e7..049d7396 100644 --- a/cosmic-applet-audio/i18n/ga/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/ga/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = Aschur input = Ionchur show-media-controls = Taispeáin rialuithe meán ar an bpanéal -sound-settings = Socruithe Fuaime... -disconnected = Dícheangailte le PulseAudio +sound-settings = Socruithe fuaime... +disconnected = PulseAudio dícheangailte no-device = Níl aon ghléas roghnaithe unknown-artist = Anaithnid diff --git a/cosmic-applet-audio/i18n/hu/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/hu/cosmic_applet_audio.ftl index bb845655..c26f9fb0 100644 --- a/cosmic-applet-audio/i18n/hu/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/hu/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = Kimenet input = Bemenet show-media-controls = Médiavezérlők megjelenítése a panelen -sound-settings = Hangbeállítások... -disconnected = PulseAudio lecsatlakozott +sound-settings = Hangbeállítások… +disconnected = PulseAudio nincs csatlakozva no-device = Nincs kiválasztott eszköz unknown-artist = Ismeretlen diff --git a/cosmic-applet-audio/i18n/ka/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/ka/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/kab/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/kab/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/kk/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/kk/cosmic_applet_audio.ftl new file mode 100644 index 00000000..82fc0bcd --- /dev/null +++ b/cosmic-applet-audio/i18n/kk/cosmic_applet_audio.ftl @@ -0,0 +1,7 @@ +output = Шығару +input = Енгізу +show-media-controls = Панельде медиа басқару элементтерін көрсету +sound-settings = Дыбыс баптаулары... +disconnected = PulseAudio ажыратылған +no-device = Құрылғы таңдалмаған +unknown-artist = Белгісіз diff --git a/cosmic-applet-audio/i18n/kmr/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/kmr/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/ko/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/ko/cosmic_applet_audio.ftl index c4902532..ba8263d1 100644 --- a/cosmic-applet-audio/i18n/ko/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/ko/cosmic_applet_audio.ftl @@ -1,6 +1,7 @@ output = 출력 input = 입력 -show-media-controls = 상단 패널에 미디어 제어기 표시 +show-media-controls = 패널에 미디어 제어기 표시 sound-settings = 소리 설정... -disconnected = PulseAudio 연결 끊김 +disconnected = PulseAudio 연결끊김 no-device = 선택된 장치가 없습니다 +unknown-artist = 알 수 없음 diff --git a/cosmic-applet-audio/i18n/lt/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/lt/cosmic_applet_audio.ftl index e69de29b..4e96ec80 100644 --- a/cosmic-applet-audio/i18n/lt/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/lt/cosmic_applet_audio.ftl @@ -0,0 +1,7 @@ +show-media-controls = Rodyti medijos valdymo mygtukus skydelyje +disconnected = PulseAudio Atjungtas +no-device = Nepasirinktas įrenginys +input = Įvestis +output = Išvestis +unknown-artist = Nežinomas atlikėjas +sound-settings = Garso Nustatymai... diff --git a/cosmic-applet-audio/i18n/nl/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/nl/cosmic_applet_audio.ftl index e6729949..16726a21 100644 --- a/cosmic-applet-audio/i18n/nl/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/nl/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = Uitvoer input = Invoer -show-media-controls = Media-applet aan paneel vastmaken -sound-settings = Geluidsinstellingen... +show-media-controls = Toon mediabeheerder op het paneel +sound-settings = Geluid instellingen… disconnected = PulseAudio niet meer verbonden no-device = Geen apparaat geselecteerd unknown-artist = Onbekend diff --git a/cosmic-applet-audio/i18n/pa/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/pa/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/sv/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/sv/cosmic_applet_audio.ftl index a52c5d75..7fb09c6d 100644 --- a/cosmic-applet-audio/i18n/sv/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/sv/cosmic_applet_audio.ftl @@ -1,7 +1,7 @@ output = Utgång input = Ingång show-media-controls = Visa mediakontroller på panelen -sound-settings = Ljudinställningar… +sound-settings = Ljudinställningar... disconnected = PulseAudio frånkopplad no-device = Ingen enhet vald unknown-artist = Okänd diff --git a/cosmic-applet-audio/i18n/ti/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/ti/cosmic_applet_audio.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-audio/i18n/uk/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/uk/cosmic_applet_audio.ftl index be6988d1..af79f37c 100644 --- a/cosmic-applet-audio/i18n/uk/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/uk/cosmic_applet_audio.ftl @@ -4,4 +4,4 @@ show-media-controls = Показувати елементи керування sound-settings = Налаштування звуку... disconnected = PulseAudio відʼєднано no-device = Не вибрано жодного пристрою -unknown-artist = Невідоме +unknown-artist = Невідомо diff --git a/cosmic-applet-audio/i18n/zh-CN/cosmic_applet_audio.ftl b/cosmic-applet-audio/i18n/zh-CN/cosmic_applet_audio.ftl index ed8f7612..14ea630c 100644 --- a/cosmic-applet-audio/i18n/zh-CN/cosmic_applet_audio.ftl +++ b/cosmic-applet-audio/i18n/zh-CN/cosmic_applet_audio.ftl @@ -1,6 +1,6 @@ output = 输出 input = 输入 -show-media-controls = 在顶栏显示媒体控制 +show-media-controls = 在面板上显示媒体控制 sound-settings = 声音设置... disconnected = PulseAudio 服务器已断开连接 no-device = 未选择设备 diff --git a/cosmic-applet-audio/src/lib.rs b/cosmic-applet-audio/src/lib.rs index d84102ea..598cb7ba 100644 --- a/cosmic-applet-audio/src/lib.rs +++ b/cosmic-applet-audio/src/lib.rs @@ -4,16 +4,14 @@ mod localize; mod mouse_area; -use std::sync::LazyLock; -use std::time::Duration; - -use crate::{localize::localize, pulse::DeviceInfo}; +use crate::localize::localize; use config::{AudioAppletConfig, amplification_sink, amplification_source}; use cosmic::{ Element, Renderer, Task, Theme, app, applet::{ + column as applet_column, cosmic_panel_config::PanelAnchor, - menu_button, menu_control_padding, padded_control, + menu_button, menu_control_padding, padded_control, row as applet_row, token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, }, cctk::sctk::reexports::calloop, @@ -21,28 +19,22 @@ use cosmic::{ cosmic_theme::Spacing, iced::{ self, Alignment, Length, Subscription, + futures::StreamExt, widget::{self, column, row, slider}, window, }, surface, theme, - widget::{Column, Row, button, container, divider, horizontal_space, icon, text}, + widget::{Row, button, container, divider, horizontal_space, icon, text}, }; -use cosmic_settings_subscriptions::pulse as sub_pulse; +use cosmic_settings_sound_subscription as css; use cosmic_time::{Instant, Timeline, anim, chain, id}; use iced::platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}; -use libpulse_binding::volume::Volume; use mpris_subscription::{MprisRequest, MprisUpdate}; use mpris2_zbus::player::PlaybackStatus; +use std::sync::LazyLock; mod config; mod mpris_subscription; -mod pulse; - -// Full, in this case, means 100%. -static FULL_VOLUME: f64 = Volume::NORMAL.0 as f64; - -// Max volume is 150% volume. -static MAX_VOLUME: f64 = FULL_VOLUME + (FULL_VOLUME * 0.5); static SHOW_MEDIA_CONTROLS: LazyLock = LazyLock::new(id::Toggler::unique); @@ -58,72 +50,57 @@ pub fn run() -> cosmic::iced::Result { #[derive(Default)] pub struct Audio { + /// For interfacing with libcosmic. core: cosmic::app::Core, - is_open: IsOpen, - output_volume: f64, - output_volume_debounce: bool, - output_volume_text: String, - output_amplification: bool, - input_volume: f64, - input_volume_debounce: bool, - input_volume_text: String, - input_amplification: bool, - current_output: Option, - current_input: Option, - outputs: Vec, - inputs: Vec, - pulse_state: PulseState, + /// Track the applet's popup window. popup: Option, + /// The model from cosmic-settings for managing pipewire devices. + model: css::Model, + /// Whether to expand the revealer of a source or sink device. + is_open: IsOpen, + /// Max slider volume for the sink device, as determined by the amplification property. + max_sink_volume: u32, + /// Max slider volume for the source device, as determined by the amplification property. + max_source_volume: u32, + /// Breakpoints for the sink volume slider. + sink_breakpoints: &'static [u32], + /// Breakpoitns for the source volume slider. + source_breakpoints: &'static [u32], + /// Track animations used by the revealers. timeline: Timeline, + /// Config file specific to this applet. config: AudioAppletConfig, + /// mpris player status player_status: Option, + /// Used to request an activation token for opening cosmic-settings. token_tx: Option>, - channels: Option, } impl Audio { - fn update_output(&mut self, output: Option) { - self.current_output = output; - - if let Some(device) = self.current_output.as_ref() { - self.output_volume = volume_to_percent(device.volume.avg()); - self.output_volume_text = format!("{}%", self.output_volume.round()); - } - } - fn output_icon_name(&self) -> &'static str { - let volume = self.output_volume; - let mute = self.current_output_mute(); - if mute || volume == 0. { + let volume = self.model.sink_volume; + let mute = self.model.sink_mute; + if mute || volume == 0 { "audio-volume-muted-symbolic" - } else if volume < 33. { + } else if volume < 33 { "audio-volume-low-symbolic" - } else if volume < 66. { + } else if volume < 66 { "audio-volume-medium-symbolic" - } else if volume <= 100. { + } else if volume <= 100 { "audio-volume-high-symbolic" } else { "audio-volume-overamplified-symbolic" } } - fn update_input(&mut self, input: Option) { - self.current_input = input; - - if let Some(device) = self.current_input.as_ref() { - self.input_volume = volume_to_percent(device.volume.avg()); - self.input_volume_text = format!("{}%", self.input_volume.round()); - } - } - fn input_icon_name(&self) -> &'static str { - let volume = self.input_volume; - let mute = self.current_input_mute(); - if mute || volume == 0. { + let volume = self.model.source_volume; + let mute = self.model.source_mute; + if mute || volume == 0 { "microphone-sensitivity-muted-symbolic" - } else if volume < 33. { + } else if volume < 33 { "microphone-sensitivity-low-symbolic" - } else if volume < 66. { + } else if volume < 66 { "microphone-sensitivity-medium-symbolic" } else { "microphone-sensitivity-high-symbolic" @@ -142,17 +119,14 @@ enum IsOpen { #[derive(Debug, Clone)] pub enum Message { Ignore, - ApplyOutputVolume, - ApplyInputVolume, - SetOutputVolume(f64), - SetInputVolume(f64), - SetOutputMute(bool), - SetInputMute(bool), + SetSinkVolume(u32), + SetSourceVolume(u32), + ToggleSinkMute, + ToggleSourceMute, + SetDefaultSink(usize), + SetDefaultSource(usize), OutputToggle, InputToggle, - OutputChanged(String), - InputChanged(String), - Pulse(pulse::Event), TogglePopup, CloseRequested(window::Id), ToggleMediaControlsInTopPanel(chain::Toggler, bool), @@ -162,14 +136,20 @@ pub enum Message { MprisRequest(MprisRequest), Token(TokenUpdate), OpenSettings, - PulseSub(sub_pulse::Event), + Subscription(css::Message), Surface(surface::Action), } +// TODO +// mouse area with on enter and a stack widget for all buttons +// most recently entered button is on top +// position is a multiple of button size +// on leave of applet, popup button is on top again + impl Audio { - fn playback_buttons(&self) -> Option> { + fn playback_buttons(&self) -> Vec> { + let mut elements: Vec> = Vec::new(); if self.player_status.is_some() && self.config.show_media_controls_in_top_panel { - let mut elements = Vec::with_capacity(3); if self .player_status .as_ref() @@ -205,18 +185,8 @@ impl Audio { .into(), ) } - - Some(match self.core.applet.anchor { - PanelAnchor::Left | PanelAnchor::Right => Column::with_children(elements) - .align_x(Alignment::Center) - .into(), - PanelAnchor::Top | PanelAnchor::Bottom => Row::with_children(elements) - .align_y(Alignment::Center) - .into(), - }) - } else { - None } + elements } fn go_previous(&self, icon_size: u16) -> Option> { @@ -270,14 +240,6 @@ impl Audio { } }) } - - fn current_output_mute(&self) -> bool { - self.current_output.as_ref().is_some_and(|o| o.mute) - } - - fn current_input_mute(&self) -> bool { - self.current_input.as_ref().is_some_and(|o| o.mute) - } } impl cosmic::Application for Audio { @@ -287,9 +249,15 @@ impl cosmic::Application for Audio { const APP_ID: &'static str = "com.system76.CosmicAppletAudio"; fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task) { + let mut model = css::Model::default(); + model.unplugged_text = "Unplugged".into(); + model.hd_audio_text = "HD Audio".into(); + model.usb_audio_text = "USB Audio".into(); + ( Self { core, + model, ..Default::default() }, Task::none(), @@ -316,15 +284,21 @@ impl cosmic::Application for Audio { if let Some(p) = self.popup.take() { return destroy_popup(p); } else { - if let Some(conn) = self.pulse_state.connection() { - conn.send(pulse::Message::UpdateConnection); - } let new_id = window::Id::unique(); self.popup.replace(new_id); self.timeline = Timeline::new(); - self.output_amplification = amplification_sink(); - self.input_amplification = amplification_source(); + (self.max_sink_volume, self.sink_breakpoints) = if amplification_sink() { + (150, &[100][..]) + } else { + (100, &[][..]) + }; + + (self.max_source_volume, self.source_breakpoints) = if amplification_source() { + (150, &[100][..]) + } else { + (100, &[][..]) + }; let popup_settings = self.core.applet.get_popup_settings( self.core.main_window_id().unwrap(), @@ -334,130 +308,14 @@ impl cosmic::Application for Audio { None, ); - if let Some(conn) = self.pulse_state.connection() { - conn.send(pulse::Message::GetDefaultSink); - conn.send(pulse::Message::GetDefaultSource); - conn.send(pulse::Message::GetSinks); - conn.send(pulse::Message::GetSources); - } - return get_popup(popup_settings); } } - Message::SetOutputVolume(vol) => { - if self.output_volume == vol { - return Task::none(); - } - self.output_volume = vol; - self.output_volume_text = format!("{}%", self.output_volume.round()); - - if self.output_volume_debounce { - return Task::none(); - } - - self.output_volume_debounce = true; - - return cosmic::task::future(async move { - tokio::time::sleep(Duration::from_millis(64)).await; - Message::ApplyOutputVolume - }); - } - Message::SetInputVolume(vol) => { - if self.input_volume == vol { - return Task::none(); - } - - self.input_volume = vol; - self.input_volume_text = format!("{}%", self.input_volume.round()); - - if self.input_volume_debounce { - return Task::none(); - } - - self.input_volume_debounce = true; - - return cosmic::task::future(async move { - tokio::time::sleep(Duration::from_millis(64)).await; - Message::ApplyInputVolume - }); - } - Message::ApplyOutputVolume => { - self.output_volume_debounce = false; - - if let Some(channel) = self.channels.as_mut() { - channel.set_volume(self.output_volume as f32 / 100.); - } - } - Message::ApplyInputVolume => { - self.input_volume_debounce = false; - - self.current_input.as_mut().map(|i| { - i.volume - .set(i.volume.len(), percent_to_volume(self.input_volume)) - }); - - if let PulseState::Connected(connection) = &mut self.pulse_state { - if let Some(device) = &self.current_input { - if let Some(name) = &device.name { - tracing::info!("increasing volume of {}", name); - connection.send(pulse::Message::SetSourceVolumeByName( - name.clone(), - device.volume, - )); - } - } - } - } - Message::SetOutputMute(mute) => { - if let Some(output) = self.current_output.as_mut() { - output.mute = mute; - } - if let PulseState::Connected(connection) = &mut self.pulse_state { - if let Some(device) = &self.current_output { - if let Some(name) = &device.name { - connection - .send(pulse::Message::SetSinkMuteByName(name.clone(), device.mute)); - } - } - } - } - Message::SetInputMute(mute) => { - if let Some(input) = self.current_input.as_mut() { - input.mute = mute; - } - if let PulseState::Connected(connection) = &mut self.pulse_state { - if let Some(device) = &self.current_input { - if let Some(name) = &device.name { - connection.send(pulse::Message::SetSourceMuteByName( - name.clone(), - device.mute, - )) - } - } - } - } - Message::OutputChanged(val) => { - if let Some(conn) = self.pulse_state.connection() { - if let Some(val) = self.outputs.iter().find(|o| o.name.as_ref() == Some(&val)) { - conn.send(pulse::Message::SetDefaultSink(val.clone())); - } - } - } - Message::InputChanged(val) => { - if let Some(conn) = self.pulse_state.connection() { - if let Some(val) = self.inputs.iter().find(|i| i.name.as_ref() == Some(&val)) { - conn.send(pulse::Message::SetDefaultSource(val.clone())); - } - } - } Message::OutputToggle => { self.is_open = if self.is_open == IsOpen::Output { IsOpen::None } else { - if let Some(conn) = self.pulse_state.connection() { - conn.send(pulse::Message::GetSinks); - } IsOpen::Output } } @@ -465,61 +323,48 @@ impl cosmic::Application for Audio { self.is_open = if self.is_open == IsOpen::Input { IsOpen::None } else { - if let Some(conn) = self.pulse_state.connection() { - conn.send(pulse::Message::GetSources); - } IsOpen::Input } } - Message::Pulse(event) => match event { - pulse::Event::Init(mut conn) => { - conn.send(pulse::Message::UpdateConnection); - self.pulse_state = PulseState::Disconnected(conn); - } - pulse::Event::Connected => { - self.pulse_state.connected(); + Message::Subscription(message) => { + return self + .model + .update(message) + .map(|message| Message::Subscription(message).into()); + } + + Message::SetDefaultSink(pos) => { + return self + .model + .set_default_sink(pos) + .map(|message| Message::Subscription(message).into()); + } + + Message::SetDefaultSource(pos) => { + return self + .model + .set_default_source(pos) + .map(|message| Message::Subscription(message).into()); + } + + Message::ToggleSinkMute => self.model.toggle_sink_mute(), + + Message::ToggleSourceMute => self.model.toggle_source_mute(), + + Message::SetSinkVolume(volume) => { + return self + .model + .set_sink_volume(volume) + .map(|message| Message::Subscription(message).into()); + } + + Message::SetSourceVolume(volume) => { + return self + .model + .set_source_volume(volume) + .map(|message| Message::Subscription(message).into()); + } - if let Some(conn) = self.pulse_state.connection() { - conn.send(pulse::Message::GetSinks); - conn.send(pulse::Message::GetSources); - conn.send(pulse::Message::GetDefaultSink); - conn.send(pulse::Message::GetDefaultSource); - } - } - pulse::Event::MessageReceived(msg) => { - match msg { - // This is where we match messages from the subscription to app state - pulse::Message::SetSinks(sinks) => self.outputs = sinks, - pulse::Message::SetSources(mut sources) => { - sources.retain(|source| { - !source.name.as_ref().is_some_and(|n| n.contains("monitor")) - }); - self.inputs = sources; - } - pulse::Message::SetDefaultSink(sink) => { - self.update_output(Some(sink)); - } - pulse::Message::SetDefaultSource(source) => { - self.update_input(Some(source)); - } - pulse::Message::Disconnected => { - panic!("Subscription error handling is bad. This should never happen.") - } - _ => { - tracing::trace!("Received misc message") - } - } - } - pulse::Event::Disconnected => { - self.pulse_state.disconnected(); - if let Some(mut conn) = self.pulse_state.connection().cloned() { - _ = tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - conn.send(pulse::Message::UpdateConnection); - }); - } - } - }, Message::ToggleMediaControlsInTopPanel(chain, enabled) => { self.timeline.set_chain(chain).start(); self.config.show_media_controls_in_top_panel = enabled; @@ -621,45 +466,6 @@ impl cosmic::Application for Audio { tokio::spawn(cosmic::process::spawn(cmd)); } }, - Message::PulseSub(event) => match event { - sub_pulse::Event::SinkVolume(value) => { - self.current_output.as_mut().map(|output| { - output - .volume - .set(output.volume.len(), percent_to_volume(value as f64)) - }); - - self.output_volume = value as f64; - self.output_volume_text = format!("{}%", self.output_volume.round()); - } - sub_pulse::Event::SinkMute(value) => { - if let Some(output) = self.current_output.as_mut() { - output.mute = value; - } - } - sub_pulse::Event::SourceVolume(value) => { - self.current_input.as_mut().map(|input| { - input - .volume - .set(input.volume.len(), percent_to_volume(value as f64)) - }); - - self.input_volume = value as f64; - self.input_volume_text = format!("{}%", self.input_volume.round()); - } - sub_pulse::Event::SourceMute(value) => { - if let Some(input) = self.current_input.as_mut() { - input.mute = value; - } - } - sub_pulse::Event::Channels(c) => { - self.channels = Some(c); - } - sub_pulse::Event::DefaultSink(_) => {} - sub_pulse::Event::DefaultSource(_) => {} - sub_pulse::Event::CardInfo(_) => {} - sub_pulse::Event::Balance(_) => {} - }, Message::Surface(a) => { return cosmic::task::message(cosmic::Action::Cosmic( cosmic::app::Action::Surface(a), @@ -672,7 +478,6 @@ impl cosmic::Application for Audio { fn subscription(&self) -> Subscription { Subscription::batch([ - pulse::connect().map(Message::Pulse), self.timeline .as_subscription() .map(|(_, now)| Message::Frame(now)), @@ -684,7 +489,7 @@ impl cosmic::Application for Audio { }), mpris_subscription::mpris_subscription(0).map(Message::Mpris), activation_token_subscription(0).map(Message::Token), - sub_pulse::subscription().map(Message::PulseSub), + Subscription::run(|| css::watch().map(Message::Subscription)), ]) } @@ -705,15 +510,9 @@ impl cosmic::Application for Audio { return Message::Ignore; } - let new_volume = (self.output_volume + (scroll_vector as f64)).clamp( - 0.0, - if self.output_amplification { - 150.0 - } else { - 100.0 - }, - ); - Message::SetOutputVolume(new_volume) + let new_volume = (self.model.sink_volume as f64 + (scroll_vector as f64)) + .clamp(0.0, self.max_sink_volume as f64); + Message::SetSinkVolume(new_volume as u32) }); let playback_buttons = (!self.core.applet.suggested_bounds.as_ref().is_some_and(|c| { @@ -724,21 +523,37 @@ impl cosmic::Application for Audio { self.core .applet - .autosize_window(if let Some(Some(playback_buttons)) = playback_buttons { - match self.core.applet.anchor { - PanelAnchor::Left | PanelAnchor::Right => Element::from( - Column::with_children([playback_buttons, btn.into()]) - .align_x(Alignment::Center), - ), - PanelAnchor::Top | PanelAnchor::Bottom => { - Row::with_children([playback_buttons, btn.into()]) - .align_y(Alignment::Center) - .into() + .autosize_window( + if let Some(playback_buttons) = playback_buttons + && !playback_buttons.is_empty() + { + match self.core.applet.anchor { + PanelAnchor::Left | PanelAnchor::Right => Element::from( + applet_column::Column::with_children(playback_buttons) + .push(btn) + .align_x(Alignment::Center) + // TODO configurable variable from the panel? + .spacing( + -(self.core.applet.suggested_padding(true).0 as f32) + * self.core.applet.padding_overlap, + ), + ), + PanelAnchor::Top | PanelAnchor::Bottom => { + applet_row::Row::with_children(playback_buttons) + .push(btn) + .align_y(Alignment::Center) + // TODO configurable variable from the panel? + .spacing( + -(self.core.applet.suggested_padding(true).0 as f32) + * self.core.applet.padding_overlap, + ) + .into() + } } - } - } else { - btn.into() - }) + } else { + btn.into() + }, + ) .into() } @@ -747,33 +562,31 @@ impl cosmic::Application for Audio { space_xxs, space_s, .. } = theme::active().cosmic().spacing; - let audio_disabled = matches!(self.pulse_state, PulseState::Disconnected(_)); - let out_mute = self.current_output_mute(); - let in_mute = self.current_input_mute(); + let sink = self + .model + .active_sink() + .and_then(|pos| self.model.sinks().get(pos)); + let source = self + .model + .active_source() + .and_then(|pos| self.model.sources().get(pos)); - let mut audio_content = if audio_disabled { - column![padded_control( - text::title3(fl!("disconnected")) - .width(Length::Fill) - .align_x(Alignment::Center) - )] - } else { - let output_slider = if self.output_amplification { - slider(0.0..=150.0, self.output_volume, Message::SetOutputVolume) - .width(Length::FillPortion(5)) - .breakpoints(&[100.]) - } else { - slider(0.0..=100.0, self.output_volume, Message::SetOutputVolume) - .width(Length::FillPortion(5)) - }; - let input_slider = if self.input_amplification { - slider(0.0..=150.0, self.input_volume, Message::SetInputVolume) - .width(Length::FillPortion(5)) - .breakpoints(&[100.]) - } else { - slider(0.0..=100.0, self.input_volume, Message::SetInputVolume) - .width(Length::FillPortion(5)) - }; + let mut audio_content = { + let output_slider = slider( + 0..=self.max_sink_volume, + self.model.sink_volume, + Message::SetSinkVolume, + ) + .width(Length::FillPortion(5)) + .breakpoints(self.sink_breakpoints); + + let input_slider = slider( + 0..=self.max_source_volume, + self.model.source_volume, + Message::SetSourceVolume, + ) + .width(Length::FillPortion(5)) + .breakpoints(self.source_breakpoints); column![ padded_control( @@ -786,9 +599,9 @@ impl cosmic::Application for Audio { .class(cosmic::theme::Button::Icon) .icon_size(24) .line_height(24) - .on_press(Message::SetOutputMute(!out_mute)), + .on_press(Message::ToggleSinkMute), output_slider, - container(text(&self.output_volume_text).size(16)) + container(text(&self.model.sink_volume_text).size(16)) .width(Length::FillPortion(1)) .align_x(Alignment::End) ] @@ -805,9 +618,9 @@ impl cosmic::Application for Audio { .class(cosmic::theme::Button::Icon) .icon_size(24) .line_height(24) - .on_press(Message::SetInputMute(!in_mute)), + .on_press(Message::ToggleSourceMute), input_slider, - container(text(&self.input_volume_text).size(16)) + container(text(&self.model.source_volume_text).size(16)) .width(Length::FillPortion(1)) .align_x(Alignment::End) ] @@ -818,24 +631,24 @@ impl cosmic::Application for Audio { revealer( self.is_open == IsOpen::Output, fl!("output"), - match &self.current_output { - Some(output) => pretty_name(output.description.clone()), - None => String::from("No device selected"), + match sink { + Some(sink) => sink.to_owned(), + None => fl!("no-device"), }, - self.outputs.as_slice(), + self.model.sinks(), Message::OutputToggle, - Message::OutputChanged, + Message::SetDefaultSink, ), revealer( self.is_open == IsOpen::Input, fl!("input"), - match &self.current_input { - Some(input) => pretty_name(input.description.clone()), + match source { + Some(source) => source.to_owned(), None => fl!("no-device"), }, - self.inputs.as_slice(), + self.model.sources(), Message::InputToggle, - Message::InputChanged, + Message::SetDefaultSource, ) ] .align_x(Alignment::Start) @@ -961,18 +774,12 @@ fn revealer( open: bool, title: String, selected: String, - device_info: &[DeviceInfo], + devices: &[String], toggle: Message, - mut change: impl FnMut(String) -> Message + 'static, + mut change: impl FnMut(usize) -> Message + 'static, ) -> widget::Column<'static, Message, crate::Theme, Renderer> { if open { - let options = device_info.iter().map(|device| { - ( - device.name.clone().unwrap_or_default(), - pretty_name(device.description.clone()), - ) - }); - options.fold( + devices.iter().cloned().enumerate().fold( column![revealer_head(open, title, selected, toggle)].width(Length::Fill), |col, (id, name)| { col.push( @@ -1000,48 +807,3 @@ fn revealer_head( ]) .on_press(toggle) } - -fn pretty_name(name: Option) -> String { - match name { - Some(n) => n, - None => String::from("Generic"), - } -} - -#[derive(Default)] -enum PulseState { - #[default] - Init, - Disconnected(pulse::Connection), - Connected(pulse::Connection), -} - -impl PulseState { - fn connection(&mut self) -> Option<&mut pulse::Connection> { - match self { - Self::Disconnected(c) => Some(c), - Self::Connected(c) => Some(c), - Self::Init => None, - } - } - - fn connected(&mut self) { - if let Self::Disconnected(c) = self { - *self = Self::Connected(c.clone()); - } - } - - fn disconnected(&mut self) { - if let Self::Connected(c) = self { - *self = Self::Disconnected(c.clone()); - } - } -} - -fn volume_to_percent(volume: Volume) -> f64 { - volume.0 as f64 * 100. / FULL_VOLUME -} - -fn percent_to_volume(percent: f64) -> Volume { - Volume((percent / 100. * FULL_VOLUME).clamp(0., MAX_VOLUME).round() as u32) -} diff --git a/cosmic-applet-audio/src/pulse.rs b/cosmic-applet-audio/src/pulse.rs deleted file mode 100644 index 97d45666..00000000 --- a/cosmic-applet-audio/src/pulse.rs +++ /dev/null @@ -1,840 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use std::{cell::RefCell, mem, rc::Rc, sync::LazyLock, thread, time::Duration}; - -extern crate libpulse_binding as pulse; - -use cosmic::{ - iced::{self, Subscription, stream}, - iced_futures::futures::{self, SinkExt}, -}; - -use libpulse_binding::{ - callbacks::ListResult, - context::{ - Context, - introspect::{Introspector, SinkInfo, SourceInfo}, - }, - error::PAErr, - mainloop::standard::{IterateResult, Mainloop}, - proplist::Proplist, - volume::ChannelVolumes, -}; - -use tokio::sync::{Mutex, mpsc}; - -pub static FROM_PULSE: LazyLock, mpsc::Sender)>>> = - LazyLock::new(|| Mutex::new(None)); - -pub fn connect() -> iced::Subscription { - struct SomeWorker; - - Subscription::run_with_id( - std::any::TypeId::of::(), - stream::channel(50, move |mut output| async move { - let mut state = State::Connecting(0); - - loop { - state = start_listening(state, &mut output).await; - } - }), - ) -} - -async fn start_listening( - state: State, - output: &mut futures::channel::mpsc::Sender, -) -> State { - match state { - // Waiting for Connection to succeed - State::Connecting(mut disconnect_count) => { - let mut guard = FROM_PULSE.lock().await; - let (from_pulse, to_pulse) = { - if guard.is_none() { - let PulseHandle { - to_pulse, - from_pulse, - } = PulseHandle::new(); - _ = output.send(Event::Init(Connection(to_pulse.clone()))).await; - - *guard = Some((from_pulse, to_pulse)); - } - guard.as_mut().unwrap() - }; - to_pulse - .send(Message::UpdateConnection) - .await - .expect("Failed to request connection update"); - - match from_pulse.recv().await { - Some(Message::Connected) => { - disconnect_count = 0; - _ = output.send(Event::Connected).await; - State::Connected - } - Some(Message::Disconnected) => { - disconnect_count += 1; - _ = output.send(Event::Disconnected).await; - tokio::time::sleep(Duration::from_millis( - 2_usize - .saturating_pow(disconnect_count.try_into().unwrap_or(u32::MAX)) - .try_into() - .unwrap_or(u64::MAX), - )) - .await; - State::Connecting(1) - } - Some(m) => { - tracing::error!("Unexpected message: {:?}", m); - State::Connecting(1) - } - None => { - panic!("Pulse Sender dropped, something has gone wrong!"); - } - } - } - State::Connected => { - let mut guard = FROM_PULSE.lock().await; - let Some((from_pulse, _)) = guard.as_mut() else { - return State::Connecting(1); - }; - // This is where we match messages from the pulse server to pass to the gui - match from_pulse.recv().await { - Some(Message::SetSinks(sinks)) => { - _ = output - .send(Event::MessageReceived(Message::SetSinks(sinks))) - .await; - - State::Connected - } - Some(Message::SetSources(sources)) => { - _ = output - .send(Event::MessageReceived(Message::SetSources(sources))) - .await; - State::Connected - } - Some(Message::SetDefaultSink(sink)) => { - _ = output - .send(Event::MessageReceived(Message::SetDefaultSink(sink))) - .await; - State::Connected - } - Some(Message::SetDefaultSource(source)) => { - _ = output - .send(Event::MessageReceived(Message::SetDefaultSource(source))) - .await; - State::Connected - } - Some(Message::Disconnected) => { - _ = output.send(Event::Disconnected).await; - State::Connecting(1) - } - None => { - _ = output.send(Event::Disconnected).await; - State::Connecting(1) - } - _ => State::Connected, - } - } - } -} - -// #[derive(Debug)] -enum State { - Connecting(usize), - Connected, -} - -#[derive(Debug, Clone)] -pub enum Event { - Init(Connection), - Connected, - Disconnected, - MessageReceived(Message), -} - -#[derive(Debug, Clone)] -pub struct Connection(mpsc::Sender); - -impl Connection { - pub fn send(&mut self, message: Message) { - if let Err(e) = self.0.try_send(message) { - match e { - mpsc::error::TrySendError::Closed(_) => { - tracing::error!( - "Failed to send message: PulseAudio server communication closed" - ); - panic!(); - } - mpsc::error::TrySendError::Full(_) => { - tracing::warn!("Failed to send message to PulseAudio server: channel is full"); - } - } - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Message { - Connected, - Disconnected, - GetSinks, - GetSources, - UpdateConnection, - SetSinks(Vec), - SetSources(Vec), - GetDefaultSink, - GetDefaultSource, - SetDefaultSink(DeviceInfo), - SetDefaultSource(DeviceInfo), - SetSinkVolumeByName(String, ChannelVolumes), - SetSourceVolumeByName(String, ChannelVolumes), - SetSinkMuteByName(String, bool), - SetSourceMuteByName(String, bool), -} - -struct PulseHandle { - to_pulse: tokio::sync::mpsc::Sender, - from_pulse: tokio::sync::mpsc::Receiver, -} - -impl PulseHandle { - // Create pulse server thread, and bidirectional comms - pub fn new() -> Self { - let (to_pulse, mut to_pulse_recv) = tokio::sync::mpsc::channel(50); - let (from_pulse_send, from_pulse) = tokio::sync::mpsc::channel(50); - - // this thread should complete by pushing a completed message, - // or fail message. This should never complete/fail without pushing - // a message. This lets the iced subscription go to sleep while init - // finishes. TLDR: be very careful with error handling - thread::spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - // take `PulseServer` and handle reciver into async context - // to listen for messages that need to be passed to the pulseserver - // this lets us put the thread to sleep, but keep hold a single - // thread, because pulse audio's API is not multithreaded... at all - rt.block_on(async { - let mut server: Option = None; - - let mut msgs = Vec::new(); - - loop { - if let Some(msg) = to_pulse_recv.recv().await { - msgs.push(msg); - } - - // Consume any additional messages in the channel. - while let Ok(msg) = to_pulse_recv.try_recv() { - // Deduplicate volume change messages. - if matches!( - msg, - Message::SetSinkVolumeByName(..) | Message::SetSourceVolumeByName(..) - ) { - let last_msg = msgs.last_mut().unwrap(); // - if mem::discriminant(last_msg) == mem::discriminant(&msg) { - *last_msg = msg; - continue; - } - } - - msgs.push(msg); - } - - for msg in msgs.drain(..) { - match msg { - Message::GetDefaultSink => { - let Some(server) = server.as_mut() else { - continue; - }; - match server.get_default_sink() { - Ok(sink) => { - if let Err(err) = from_pulse_send - .send(Message::SetDefaultSink(sink)) - .await - { - tracing::error!("ERROR! {}", err); - } - } - Err(_) => Self::send_disconnected(&from_pulse_send).await, - } - } - Message::GetDefaultSource => { - let Some(server) = server.as_mut() else { - continue; - }; - match server.get_default_source() { - Ok(source) => { - if let Err(err) = from_pulse_send - .send(Message::SetDefaultSource(source)) - .await - { - tracing::error!("ERROR! {}", err); - } - } - Err(e) => { - tracing::error!("ERROR! {:?}", e); - Self::send_disconnected(&from_pulse_send).await; - } - } - } - Message::GetSinks => { - let Some(server) = server.as_mut() else { - continue; - }; - match server.get_sinks() { - Ok(sinks) => { - if let Err(err) = - from_pulse_send.send(Message::SetSinks(sinks)).await - { - tracing::error!("ERROR! {}", err); - } - } - Err(_) => Self::send_disconnected(&from_pulse_send).await, - } - } - Message::GetSources => { - let Some(server) = server.as_mut() else { - continue; - }; - match server.get_sources() { - Ok(sinks) => { - if let Err(err) = - from_pulse_send.send(Message::SetSources(sinks)).await - { - tracing::error!("ERROR! {}", err); - } - } - Err(_) => Self::send_disconnected(&from_pulse_send).await, - } - } - Message::SetSinkVolumeByName(name, channel_volumes) => { - let Some(server) = server.as_mut() else { - continue; - }; - server.set_sink_volume_by_name(&name, &channel_volumes); - } - Message::SetSourceVolumeByName(name, channel_volumes) => { - let Some(server) = server.as_mut() else { - continue; - }; - server.set_source_volume_by_name(&name, &channel_volumes); - } - Message::SetSinkMuteByName(name, mute) => { - let Some(server) = server.as_mut() else { - continue; - }; - - let op = - server.introspector.set_sink_mute_by_name(&name, mute, None); - server.wait_for_result(op).ok(); - } - Message::SetSourceMuteByName(name, mute) => { - let Some(server) = server.as_mut() else { - continue; - }; - - let op = server - .introspector - .set_source_mute_by_name(&name, mute, None); - server.wait_for_result(op).ok(); - } - Message::UpdateConnection => { - tracing::info!( - "Updating Connection, server exists: {:?}", - server.is_some() - ); - if let Some(mut cur_server) = server.take() { - if cur_server.get_server_info().is_err() { - tracing::warn!("got error, server must be disconnected..."); - Self::send_disconnected(&from_pulse_send).await; - } else { - tracing::info!("got server info, still connected..."); - server = Some(cur_server); - Self::send_connected(&from_pulse_send).await; - } - } else { - match PulseServer::connect().and_then(PulseServer::init) { - Ok(new_server) => { - tracing::info!("Connected to server"); - Self::send_connected(&from_pulse_send).await; - server = Some(new_server); - } - Err(err) => { - tracing::error!( - "Failed to connect to server: {:?}", - err - ); - Self::send_disconnected(&from_pulse_send).await; - } - } - } - } - Message::SetDefaultSink(device) => { - let Some(server) = server.as_mut() else { - continue; - }; - let Ok(default_sink) = server.get_default_sink() else { - continue; - }; - let to_move = server.get_sink_inputs(default_sink.index); - if let Some(name) = device.name.as_ref() { - if server.set_default_sink(name, to_move) { - if let Err(err) = from_pulse_send - .send(Message::SetDefaultSink(device)) - .await - { - tracing::error!("ERROR! {:?}", err); - }; - } - } - } - Message::SetDefaultSource(device) => { - let Some(server) = server.as_mut() else { - continue; - }; - let Ok(default_source) = server.get_default_source() else { - continue; - }; - let to_move = server.get_source_outputs(default_source.index); - if let Some(name) = device.name.as_ref() { - if server.set_default_source(name, to_move) { - if let Err(err) = from_pulse_send - .send(Message::SetDefaultSource(device)) - .await - { - tracing::error!("ERROR! {:?}", err); - } - } - } - } - _ => { - tracing::warn!("message doesn't match"); - } - } - } - } - }); - }); - Self { - to_pulse, - from_pulse, - } - } - - async fn send_disconnected(sender: &tokio::sync::mpsc::Sender) { - sender.send(Message::Disconnected).await.unwrap(); - } - - #[allow(dead_code)] - async fn send_connected(sender: &tokio::sync::mpsc::Sender) { - sender.send(Message::Connected).await.unwrap(); - } -} - -struct PulseServer { - mainloop: Rc>, - context: Rc>, - introspector: Introspector, -} - -#[derive(Clone, Debug)] -enum PulseServerError<'a> { - IterateErr(IterateResult), - ContextErr(pulse::context::State), - OperationErr(pulse::operation::State), - PAErr(PAErr), - Connect, - Misc(&'a str), -} - -// `PulseServer` code is heavily inspired by Dave Patrick Caberto's pulsectl-rs (SeaDve) -// https://crates.io/crates/pulsectl-rs -impl PulseServer { - // connect() requires init() to be run after - pub fn connect() -> Result> { - // TODO: fix app name, should be variable - let mut proplist = Proplist::new().unwrap(); - proplist - .set_str( - pulse::proplist::properties::APPLICATION_NAME, - "com.system76", - ) - .or(Err(PulseServerError::Connect))?; - - let mainloop = Rc::new(RefCell::new( - pulse::mainloop::standard::Mainloop::new().ok_or(PulseServerError::Connect)?, - )); - - let context = Rc::new(RefCell::new( - Context::new_with_proplist(&*mainloop.borrow(), "MainConn", &proplist) - .ok_or(PulseServerError::Connect)?, - )); - - let introspector = context.borrow_mut().introspect(); - - context - .borrow_mut() - .connect(None, pulse::context::FlagSet::NOFLAGS, None) - .map_err(PulseServerError::PAErr)?; - - Ok(Self { - mainloop, - context, - introspector, - }) - } - - // Wait for pulse audio connection to complete - pub fn init(self) -> Result> { - loop { - match self.mainloop.borrow_mut().iterate(false) { - IterateResult::Success(_) => {} - IterateResult::Err(e) => { - return Err(PulseServerError::IterateErr(IterateResult::Err(e))); - } - IterateResult::Quit(e) => { - return Err(PulseServerError::IterateErr(IterateResult::Quit(e))); - } - } - - match self.context.borrow().get_state() { - pulse::context::State::Ready => break, - pulse::context::State::Failed => { - return Err(PulseServerError::ContextErr(pulse::context::State::Failed)); - } - pulse::context::State::Terminated => { - return Err(PulseServerError::ContextErr( - pulse::context::State::Terminated, - )); - } - _ => {} - } - } - Ok(self) - } - - // Get a list of output devices - pub fn get_sinks(&self) -> Result, PulseServerError<'_>> { - let list: Rc>>> = Rc::new(RefCell::new(Some(Vec::new()))); - let list_ref = list.clone(); - - let operation = self.introspector.get_sink_info_list( - move |sink_list: ListResult<&pulse::context::introspect::SinkInfo>| { - if let ListResult::Item(item) = sink_list { - list_ref.borrow_mut().as_mut().unwrap().push(item.into()); - } - }, - ); - self.wait_for_result(operation).and_then(|()| { - list.borrow_mut().take().ok_or(PulseServerError::Misc( - "get_sinks(): failed to wait for operation", - )) - }) - } - - // Get a list of input devices - pub fn get_sources(&self) -> Result, PulseServerError<'_>> { - let list: Rc>>> = Rc::new(RefCell::new(Some(Vec::new()))); - let list_ref = list.clone(); - - let operation = self.introspector.get_source_info_list( - move |sink_list: ListResult<&pulse::context::introspect::SourceInfo>| { - if let ListResult::Item(item) = sink_list { - list_ref.borrow_mut().as_mut().unwrap().push(item.into()); - } - }, - ); - self.wait_for_result(operation).and_then(|()| { - list.borrow_mut().take().ok_or(PulseServerError::Misc( - "get_sources(): Failed to wait for operation", - )) - }) - } - - pub fn get_server_info(&mut self) -> Result> { - let info = Rc::new(RefCell::new(Some(None))); - let info_ref = info.clone(); - - let op = self.introspector.get_server_info(move |res| { - info_ref.borrow_mut().as_mut().unwrap().replace(res.into()); - }); - self.wait_for_result(op)?; - info.take() - .flatten() - .ok_or(PulseServerError::Misc("get_server_info(): failed")) - } - - fn set_default_sink(&mut self, sink: &str, to_move: Vec) -> bool { - let set_default_success = Rc::new(RefCell::new(false)); - let set_default_success_ref = set_default_success.clone(); - let op = self - .context - .borrow_mut() - .set_default_sink(sink, move |ret| { - *set_default_success.borrow_mut() = ret; - }); - self.wait_for_result(op).ok(); - if !set_default_success_ref.replace(true) { - return false; - } - - for index in to_move { - let move_success = Rc::new(RefCell::new(false)); - let op = self.introspector.move_sink_input_by_name( - index, - sink, - Some(Box::new(move |ret| { - *move_success.borrow_mut() = ret; - })), - ); - - self.wait_for_result(op).ok(); - } - // TODO handle errors - true - } - - fn set_default_source(&mut self, sink: &str, to_move: Vec) -> bool { - let set_default_success = Rc::new(RefCell::new(false)); - let set_default_success_ref = set_default_success.clone(); - let op = self - .context - .borrow_mut() - .set_default_source(sink, move |ret| { - *set_default_success.borrow_mut() = ret; - }); - self.wait_for_result(op).ok(); - - if !set_default_success_ref.replace(true) { - return false; - } - - for index in to_move { - let move_success = Rc::new(RefCell::new(false)); - let op = self.introspector.move_source_output_by_name( - index, - sink, - Some(Box::new(move |ret| { - *move_success.borrow_mut() = ret; - })), - ); - - self.wait_for_result(op).ok(); - } - - true - } - - fn get_default_sink(&mut self) -> Result> { - let server_info = self.get_server_info(); - match server_info { - Ok(info) => { - let name = &info.default_sink_name.unwrap_or_default(); - let device = Rc::new(RefCell::new(Some(None))); - let dev_ref = device.clone(); - let op = self.introspector.get_sink_info_by_name( - name, - move |sink_list: ListResult<&SinkInfo>| { - if let ListResult::Item(item) = sink_list { - dev_ref.borrow_mut().as_mut().unwrap().replace(item.into()); - } - }, - ); - self.wait_for_result(op)?; - let mut result = device.borrow_mut(); - result.take().unwrap().ok_or({ - PulseServerError::Misc("get_default_sink(): Error getting requested device") - }) - } - Err(_) => Err(PulseServerError::Misc("get_default_sink() failed")), - } - } - - fn get_default_source(&mut self) -> Result> { - let server_info = self.get_server_info(); - match server_info { - Ok(info) => { - let name = &info.default_source_name.unwrap_or_default(); - let device = Rc::new(RefCell::new(Some(None))); - let dev_ref = device.clone(); - let op = self.introspector.get_source_info_by_name( - name, - move |sink_list: ListResult<&SourceInfo>| { - if let ListResult::Item(item) = sink_list { - dev_ref.borrow_mut().as_mut().unwrap().replace(item.into()); - } - }, - ); - self.wait_for_result(op)?; - let mut result = device.borrow_mut(); - result.take().unwrap().ok_or({ - PulseServerError::Misc("get_default_source(): Error getting requested device") - }) - } - Err(_) => Err(PulseServerError::Misc("get_default_source() failed")), - } - } - - fn set_sink_volume_by_name(&mut self, name: &str, volume: &ChannelVolumes) { - let op = self - .introspector - .set_sink_mute_by_name(name, volume.is_muted(), None); - self.wait_for_result(op).ok(); - - let op = self - .introspector - .set_sink_volume_by_name(name, volume, None); - self.wait_for_result(op).ok(); - } - - fn set_source_volume_by_name(&mut self, name: &str, volume: &ChannelVolumes) { - let op = self - .introspector - .set_source_mute_by_name(name, volume.is_muted(), None); - let _ = self.wait_for_result(op); - - let op = self - .introspector - .set_source_volume_by_name(name, volume, None); - let _ = self.wait_for_result(op); - } - - fn get_source_outputs(&mut self, source: u32) -> Vec { - let result = Rc::new(RefCell::new(Vec::new())); - let result_ref = Rc::new(RefCell::new(Vec::new())); - let op = self.introspector.get_source_output_info_list(move |list| { - if let ListResult::Item(item) = list { - if source == item.source { - result.borrow_mut().push(item.index); - } - } - }); - let _ = self.wait_for_result(op); - result_ref.replace(Vec::new()) - } - - fn get_sink_inputs(&mut self, sink: u32) -> Vec { - let result = Rc::new(RefCell::new(Vec::new())); - let result_ref = Rc::new(RefCell::new(Vec::new())); - let op = self.introspector.get_sink_input_info_list(move |list| { - if let ListResult::Item(item) = list { - if sink == item.sink { - result.borrow_mut().push(item.index); - } - } - }); - let _ = self.wait_for_result(op); - result_ref.replace(Vec::new()) - } - - // after building an operation such as get_devices() we need to keep polling - // the pulse audio server to "wait" for the operation to complete - fn wait_for_result( - &self, - operation: pulse::operation::Operation, - ) -> Result<(), PulseServerError<'_>> { - // TODO: make this loop async. It is already in an async context, so - // we could make this thread sleep while waiting for the pulse server's - // response. - loop { - match self.mainloop.borrow_mut().iterate(false) { - IterateResult::Err(e) => { - return Err(PulseServerError::IterateErr(IterateResult::Err(e))); - } - IterateResult::Quit(e) => { - return Err(PulseServerError::IterateErr(IterateResult::Quit(e))); - } - IterateResult::Success(_) => {} - } - match operation.get_state() { - pulse::operation::State::Done => return Ok(()), - pulse::operation::State::Running => {} - pulse::operation::State::Cancelled => { - return Err(PulseServerError::OperationErr( - pulse::operation::State::Cancelled, - )); - } - } - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct DeviceInfo { - pub name: Option, - pub description: Option, - pub volume: ChannelVolumes, - pub mute: bool, - pub index: u32, -} - -impl<'a> From<&SinkInfo<'a>> for DeviceInfo { - fn from(info: &SinkInfo<'a>) -> Self { - Self { - name: info.name.as_deref().map(str::to_string), - description: info.description.as_deref().map(str::to_string), - volume: info.volume, - mute: info.mute, - index: info.index, - } - } -} - -impl<'a> From<&SourceInfo<'a>> for DeviceInfo { - fn from(info: &SourceInfo<'a>) -> Self { - Self { - name: info.name.as_deref().map(str::to_string), - description: info.description.as_deref().map(str::to_string), - volume: info.volume, - mute: info.mute, - index: info.index, - } - } -} - -impl Eq for DeviceInfo {} - -#[derive(Debug)] -pub struct ServerInfo { - /// User name of the daemon process. - pub user_name: Option, - /// Host name the daemon is running on. - pub host_name: Option, - /// Version string of the daemon. - pub server_version: Option, - /// Server package name (usually “pulseaudio”). - pub server_name: Option, - // Default sample specification. - //pub sample_spec: sample::Spec, - /// Name of default sink. - pub default_sink_name: Option, - /// Name of default source. - pub default_source_name: Option, - /// A random cookie for identifying this instance of PulseAudio. - pub cookie: u32, - // Default channel map. - //pub channel_map: channelmap::Map, -} - -impl<'a> From<&'a pulse::context::introspect::ServerInfo<'a>> for ServerInfo { - fn from(info: &'a pulse::context::introspect::ServerInfo<'a>) -> Self { - use std::borrow::Cow; - Self { - user_name: info.user_name.as_ref().map(Cow::to_string), - host_name: info.host_name.as_ref().map(Cow::to_string), - server_version: info.server_version.as_ref().map(Cow::to_string), - server_name: info.server_name.as_ref().map(Cow::to_string), - //sample_spec: info.sample_spec, - default_sink_name: info.default_sink_name.as_ref().map(Cow::to_string), - default_source_name: info.default_source_name.as_ref().map(Cow::to_string), - cookie: info.cookie, - //channel_map: info.channel_map, - } - } -} diff --git a/cosmic-applet-battery/Cargo.toml b/cosmic-applet-battery/Cargo.toml index 7692e53a..3349d675 100644 --- a/cosmic-applet-battery/Cargo.toml +++ b/cosmic-applet-battery/Cargo.toml @@ -1,15 +1,11 @@ [package] name = "cosmic-applet-battery" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" [dependencies] anyhow.workspace = true -cosmic-settings-subscriptions = { workspace = true, features = [ - "upower", - "settings_daemon", -] } cosmic-time.workspace = true drm = "0.14.1" futures.workspace = true @@ -24,3 +20,9 @@ tracing-subscriber.workspace = true tracing.workspace = true udev = "0.9" zbus.workspace = true + +[dependencies.cosmic-settings-upower-subscription] +git = "https://github.com/pop-os/cosmic-settings" + +[dependencies.cosmic-settings-daemon-subscription] +git = "https://github.com/pop-os/cosmic-settings" diff --git a/cosmic-applet-battery/data/com.system76.CosmicAppletBattery.desktop b/cosmic-applet-battery/data/com.system76.CosmicAppletBattery.desktop index c0ffce9d..b0026ea3 100644 --- a/cosmic-applet-battery/data/com.system76.CosmicAppletBattery.desktop +++ b/cosmic-applet-battery/data/com.system76.CosmicAppletBattery.desktop @@ -12,6 +12,7 @@ Name[nl]=Energie en batterij Name[de]=Energie und Akku Name[sk]=Napájanie a batéria Name[sv]=Ström & batteri +Name[it]=Alimentazione e batteria Type=Application Exec=cosmic-applet-battery Terminal=false @@ -22,5 +23,6 @@ Icon=com.system76.CosmicAppletBattery-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-CosmicHoverPopup=Start X-OverflowPriority=10 diff --git a/cosmic-applet-battery/i18n/ar/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/ar/cosmic_applet_battery.ftl index 140e34d0..4805a411 100644 --- a/cosmic-applet-battery/i18n/ar/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/ar/cosmic_applet_battery.ftl @@ -1,9 +1,9 @@ battery = المُدَّخرة -battery-desc = الأداء وخفض استهلاك الطاقة. +battery-desc = أداء واستهلاك طاقة مخفض. balanced = متوازن -balanced-desc = مُعايرة الأداء واستهلاك المُدَّخرة. -performance = أداء عالٍ -performance-desc = أداء واستهلاك طاقة مرتفعان. +balanced-desc = أداء واستهلاك المُدَّخرة قياسي. +performance = أداء عالي +performance-desc = أداء واستهلاك طاقة عالٍ. max-charge = زِد من مدة صلاحية مُدَّخرتك بوضع حد أقصى للشحن على 80٪ seconds = ث minutes = د @@ -11,4 +11,4 @@ hours = س until-empty = حتى النفاد power-settings = إعدادات الطاقة والمُدَّخرة... dgpu-running = وحدة معالجة رسوميات مُنفردة نشطة قد تقلل من عمر المُدَّخرة -dgpu-applications = التطبيقات التي تستخدم وحدة { $gpu_name } المنفصلة +dgpu-applications = التطبيقات التي تستخدم معالج الرسوميات { $gpu_name } المُنفردة diff --git a/cosmic-applet-battery/i18n/bg/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/bg/cosmic_applet_battery.ftl index c79b926a..58ed1a36 100644 --- a/cosmic-applet-battery/i18n/bg/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/bg/cosmic_applet_battery.ftl @@ -1,8 +1,8 @@ battery = Батерия -battery-desc = Намалено потребление на енергия и тиха работа. -balanced = Баланс -balanced-desc = Тиха работа и умерено потребление на енергия. -performance = Бързина +battery-desc = Намалена производителност и ниско потребление на енергия. +balanced = Балансиран +balanced-desc = Умерена производителност и стандартно потребление на енергия. +performance = Висока Производителност performance-desc = Максимална производителност и потребление на енергия. max-charge = Увеличете живота на батерията, като зададете максимална стойност на зареждане от 80% seconds = с diff --git a/cosmic-applet-battery/i18n/bn/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/bn/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/fr/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/fr/cosmic_applet_battery.ftl index 9d529190..c8606d7a 100644 --- a/cosmic-applet-battery/i18n/fr/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/fr/cosmic_applet_battery.ftl @@ -1,14 +1,14 @@ battery = Batterie -battery-desc = Performance et consommation réduites. +battery-desc = Consommation et performances réduites. balanced = Équilibré -balanced-desc = Performance et usage de la batterie standard. +balanced-desc = Performances et usage de la batterie standard. performance = Hautes performances performance-desc = Performances et consommation élevées. -max-charge = Augmente la durée de vie de votre batterie en mettant la charge maximale à 80% +max-charge = Prolongez la durée de vie de votre batterie en arrêtant la charge à 80% seconds = s minutes = m hours = h -until-empty = avant la décharge totale +until-empty = avant décharge complète power-settings = Paramètres d'alimentation... -dgpu-running = Le GPU dédié est actif et peu réduire l'autonomie de la batterie -dgpu-applications = Applications utilisant le GPU dédié {$gpu_name} +dgpu-running = Le GPU dédié est actif et peut réduire la durée de vie de la batterie +dgpu-applications = Applications utilisant le GPU dédié { $gpu_name } diff --git a/cosmic-applet-battery/i18n/ga/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/ga/cosmic_applet_battery.ftl index 61959a8c..166b4c89 100644 --- a/cosmic-applet-battery/i18n/ga/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/ga/cosmic_applet_battery.ftl @@ -2,13 +2,13 @@ battery = Ceallraí battery-desc = Ídiú cumhachta agus feidhmíocht laghdaithe. balanced = Cothromaithe balanced-desc = Feidhmíocht chaighdeánach agus úsáid ceallraí. -performance = Ard-Fheidhmíocht -performance-desc = Ard-fheidhmíocht agus ard-úsáid cumhachta. -max-charge = Méadaigh saolré do cheallraí trí uasmhuirear 80% a shocrú +performance = Ard fheidhmíocht +performance-desc = Ard fheidhmíocht agus úsáid cumhachta. +max-charge = Méadaigh saolré do cheallraí trí uasluach muirir 80% a shocrú seconds = s -minutes = nóim +minutes = n hours = u until-empty = go dtí go mbeidh sé folamh -power-settings = Socruithe Cumhachta & Ceallraí... +power-settings = Socruithe cumhachta & ceallraí... dgpu-running = Tá an GPU ar leith gníomhach agus féadfaidh sé saol ceallraí a laghdú -dgpu-applications = Aipeanna ag úsáid {$gpu_name} GPU ar leith +dgpu-applications = Aipeanna ag úsáid { $gpu_name } GPU ar leith diff --git a/cosmic-applet-battery/i18n/hu/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/hu/cosmic_applet_battery.ftl index c51650c3..814d3605 100644 --- a/cosmic-applet-battery/i18n/hu/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/hu/cosmic_applet_battery.ftl @@ -1,14 +1,14 @@ battery = Akkumulátor -battery-desc = Csökkentett energiafogyasztás és teljesítmény. +battery-desc = Csökkentett energiafogyasztás és teljesítmény balanced = Kiegyensúlyozott -balanced-desc = Normál teljesítmény és akkumulátorhasználat. +balanced-desc = Normál teljesítmény és akkumulátorhasználat performance = Nagy teljesítmény -performance-desc = Nagy teljesítmény és energiafogyasztás. +performance-desc = Nagy teljesítmény és energiafogyasztás max-charge = Az akkumulátor élettartamának növelése érdekében állítsa a maximális töltési szintet 80%-ra seconds = másodperc minutes = perc hours = óra until-empty = a lemerülésig -power-settings = Energia- és akkumulátorbeállítások... +power-settings = Energia- és akkumulátorbeállítások… dgpu-running = A dedikált GPU aktív, ami csökkentheti az akkumulátor élettartamát -dgpu-applications = A {$gpu_name} dedikált GPU-t használó alkalmazások +dgpu-applications = A(z) { $gpu_name } dedikált GPU-t használó alkalmazások diff --git a/cosmic-applet-battery/i18n/ka/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/ka/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/kab/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/kab/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/kk/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/kk/cosmic_applet_battery.ftl new file mode 100644 index 00000000..394757ca --- /dev/null +++ b/cosmic-applet-battery/i18n/kk/cosmic_applet_battery.ftl @@ -0,0 +1,14 @@ +battery = Батарея +battery-desc = Азайтылған қуат тұтынуы және өнімділігі. +balanced = Теңгерілген +balanced-desc = Стандартты өнімділік және батареяны тұтыну. +performance = Жоғары өнімділік +performance-desc = Жоғары өнімділік және қуатты тұтыну. +max-charge = Максималды заряд мәнін 80% етіп орнату арқылы батареяңыздың қызмет ету мерзімін ұзартыңыз +seconds = с +minutes = м +hours = сағ +until-empty = таусылғанша +power-settings = Қуат және батарея баптаулары... +dgpu-running = Дискретті GPU белсенді және батареяның жұмыс уақытын азайтуы мүмкін +dgpu-applications = { $gpu_name } дискретті видеокартасын пайдаланатын қолданбалар diff --git a/cosmic-applet-battery/i18n/kmr/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/kmr/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/ko/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/ko/cosmic_applet_battery.ftl index e69de29b..9cbc3603 100644 --- a/cosmic-applet-battery/i18n/ko/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/ko/cosmic_applet_battery.ftl @@ -0,0 +1,14 @@ +power-settings = 전원 & 배터리 설정... +hours = 시간 +until-empty = 남음 +battery-desc = 감소된 성능과 전력 사용량. +max-charge = 배터리 최대 충전값을 80%로 설정하여 배터리 수명을 연장합니다 +balanced = 균형 +battery = 배터리 +seconds = 초 +dgpu-running = 외장 GPU가 활성화되어 배터리 사용 시간이 단축될 수 있습니다 +performance = 고성능 +performance-desc = 높은 성능과 전력 사용량. +minutes = 분 +balanced-desc = 표준적인 성능과 배터리 사용량. +dgpu-applications = { $gpu_name } 외장 GPU를 사용하는 앱 diff --git a/cosmic-applet-battery/i18n/lt/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/lt/cosmic_applet_battery.ftl index e69de29b..a125d7ef 100644 --- a/cosmic-applet-battery/i18n/lt/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/lt/cosmic_applet_battery.ftl @@ -0,0 +1,14 @@ +power-settings = Energijos ir Baterijos nustatymai... +hours = val +until-empty = kol bus iškrauta +battery-desc = Sumažintas energijos vartojimas ir našumas. +max-charge = Padidinti baterijo gyvavimo trukmę nustatydami 80% pakrovimo ribą +balanced = Subalansuotas +battery = Baterija +seconds = sek +dgpu-running = Dedikuotas GPU yra aktyvus ir gali sumažinti baterijos gyvavimo trukmę +performance = Didelis Našumas +performance-desc = Didelis našumas ir energijos vartojimas. +minutes = min +balanced-desc = Įprastas našumas ir baterijos naudojimas. +dgpu-applications = Aplikacijos naudojančios { $gpu_name } dedikuotą GPU diff --git a/cosmic-applet-battery/i18n/nl/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/nl/cosmic_applet_battery.ftl index a0fa6888..4b8e152b 100644 --- a/cosmic-applet-battery/i18n/nl/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/nl/cosmic_applet_battery.ftl @@ -1,5 +1,5 @@ -battery = Batterijbesparing -battery-desc = Batterijbesparing met verlaagde prestaties. +battery = Batterij +battery-desc = Verminderd energieverbruik en prestaties. balanced = Gebalanceerd balanced-desc = Normale prestaties en batterijverbruik. performance = Verhoogde prestaties @@ -9,6 +9,6 @@ seconds = s minutes = m hours = u until-empty = tot leeg -power-settings = Energie- en batterijbeheer... +power-settings = Energie- en batterijbeheer… dgpu-running = De discrete GPU is actief en kan de batterijduur verkorten -dgpu-applications = Toepassingen die de {$gpu_name} discrete GPU gebruiken +dgpu-applications = Toepassingen die de { $gpu_name } discrete GPU gebruiken diff --git a/cosmic-applet-battery/i18n/pa/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/pa/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/pt-BR/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/pt-BR/cosmic_applet_battery.ftl index 1129448c..e5a72d03 100644 --- a/cosmic-applet-battery/i18n/pt-BR/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/pt-BR/cosmic_applet_battery.ftl @@ -1,14 +1,14 @@ battery = Bateria -battery-desc = Uso reduzido de energia e desempenho. -balanced = Equilibrado -balanced-desc = Desempenho padrão e uso de bateria. +battery-desc = Uso reduzido de energia e desempenho limitado. +balanced = Balanceado +balanced-desc = Desempenho padrão e uso de bateria moderado. performance = Alto Desempenho -performance-desc = Alto desempenho e uso de energia. +performance-desc = Máximo desempenho e alto uso de energia. max-charge = Aumente a vida útil da sua bateria definindo um valor máximo de carga de 80% seconds = s minutes = m hours = h -until-empty = até vazio -power-settings = Configurações de Energia e Bateria... +until-empty = até esvaziar +power-settings = Configurações de energia e bateria... dgpu-running = GPU discreta está ativa e pode reduzir a vida útil da bateria -dgpu-applications = Aplicativos usando GPU discreta {$gpu_name} +dgpu-applications = Aplicativos usando GPU discreta { $gpu_name } diff --git a/cosmic-applet-battery/i18n/ti/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/ti/cosmic_applet_battery.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-battery/i18n/uk/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/uk/cosmic_applet_battery.ftl index 7a5a32b1..2669c305 100644 --- a/cosmic-applet-battery/i18n/uk/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/uk/cosmic_applet_battery.ftl @@ -3,12 +3,12 @@ battery-desc = Зменшене енергоспоживання та проду balanced = Збалансований balanced-desc = Стандартна продуктивність та енергоспоживання. performance = Висока продуктивність -performance-desc = Висока продуктивність та енергоспоживання. -max-charge = Подовжити термін служби акумулятора, обмеживши максимальний заряд до 80% -seconds = сек +performance-desc = Висока продуктивність і енергоспоживання. +max-charge = Подовжте термін служби акумулятора, обмеживши максимальне наснаження до 80% +seconds = с minutes = хв hours = год -until-empty = доки не розрядиться +until-empty = доки не виснажиться power-settings = Налаштування живлення та акумулятора... -dgpu-running = Активна дискретна відеокарта, це може скоротити час роботи акумулятора +dgpu-running = Дискретна відеокарта активна, що може скоротити час роботи від акумулятора dgpu-applications = Застосунки використовують дискретну відеокарту { $gpu_name } diff --git a/cosmic-applet-battery/i18n/zh-CN/cosmic_applet_battery.ftl b/cosmic-applet-battery/i18n/zh-CN/cosmic_applet_battery.ftl index 2af045e3..9b043b08 100644 --- a/cosmic-applet-battery/i18n/zh-CN/cosmic_applet_battery.ftl +++ b/cosmic-applet-battery/i18n/zh-CN/cosmic_applet_battery.ftl @@ -1,13 +1,13 @@ -battery = 省电 +battery = 电池 battery-desc = 降低功耗和性能。 balanced = 平衡 balanced-desc = 标准性能和电池使用。 performance = 高性能 performance-desc = 高性能和功耗。 max-charge = 最多充电到 80% 以增加电池寿命 -seconds = s -minutes = m -hours = h +seconds = 秒 +minutes = 分钟 +hours = 小时 until-empty = 直到电池耗尽 power-settings = 电源和电池设置... dgpu-running = 独立显卡正在运行,可能会降低电池寿命 diff --git a/cosmic-applet-battery/src/app.rs b/cosmic-applet-battery/src/app.rs index c60bf85e..206c6d12 100644 --- a/cosmic-applet-battery/src/app.rs +++ b/cosmic-applet-battery/src/app.rs @@ -29,15 +29,12 @@ use cosmic::{ surface, theme, widget::{divider, horizontal_space, icon, scrollable, slider, text, vertical_space}, }; -use cosmic_settings_subscriptions::{ - settings_daemon, - upower::{ - device::{DeviceDbusEvent, device_subscription}, - kbdbacklight::{ - KeyboardBacklightRequest, KeyboardBacklightUpdate, kbd_backlight_subscription, - }, - }, +use cosmic_settings_daemon_subscription as settings_daemon; +use cosmic_settings_upower_subscription::{ + device::{DeviceDbusEvent, device_subscription}, + kbdbacklight::{KeyboardBacklightRequest, KeyboardBacklightUpdate, kbd_backlight_subscription}, }; + use cosmic_time::{Instant, Timeline, anim, chain, id}; use rustc_hash::FxHashMap; @@ -136,10 +133,16 @@ impl CosmicBatteryApplet { } fn screen_brightness_percent(&self) -> Option { - Some( - (self.screen_brightness? as f64 / self.max_screen_brightness?.max(1) as f64) - .clamp(0.01, 1.0), - ) + let raw = self.screen_brightness? as i64; + let max = self.max_screen_brightness?.max(1) as i64; + if max <= 20 { + // Coarse panels (<=20 brightness levels) + let rung = (raw.saturating_add(1)).min(20); + Some((5 * rung) as f64 / 100.0) + } else { + let p = ((raw * 100 + max / 2) / max).clamp(1, 100) as f64; + Some(p / 100.0) + } } fn update_display(&mut self) { @@ -242,8 +245,21 @@ impl cosmic::Application for CosmicBatteryApplet { return cosmic::task::message(Message::SetKbdBrightnessDebounced); } } + // Matching brightness calculation logic from cosmic-osd and cosmic-settings-daemon Message::SetScreenBrightness(brightness) => { - self.screen_brightness = Some(brightness); + let snapped = if let Some(max) = self.max_screen_brightness { + if max > 0 && max <= 20 { + // Coarse: map raw→k by round, then back to raw setpoint round(k*max/20) + let k = ((brightness as i64 * 20 + (max as i64) / 2) / (max as i64)) + .clamp(0, 20); + (((k * (max as i64)) + 10) / 20) as i32 + } else { + brightness + } + } else { + brightness + }; + self.screen_brightness = Some(snapped); if !self.dragging_screen_brightness { self.dragging_screen_brightness = true; self.update_display(); @@ -484,7 +500,7 @@ impl cosmic::Application for CosmicBatteryApplet { } fn view(&self) -> Element<'_, Message> { - let btn = self + let btn: Element<'_, Message> = self .core .applet .icon_button(&self.icon_name) @@ -508,17 +524,24 @@ impl cosmic::Application for CosmicBatteryApplet { shadow: Shadow::default(), icon_color: Some(Color::TRANSPARENT), } - }))) - .into(); + }))); + let (dot_align_x, dot_align_y) = match self.core.applet.anchor { + PanelAnchor::Left => (Alignment::Start, Alignment::Center), + PanelAnchor::Right => (Alignment::End, Alignment::Center), + PanelAnchor::Top => (Alignment::Center, Alignment::Start), + PanelAnchor::Bottom => (Alignment::Center, Alignment::End), + }; - match self.core.applet.anchor { - PanelAnchor::Left | PanelAnchor::Right => Column::with_children([btn, dot]) - .align_x(Alignment::Center) - .into(), - PanelAnchor::Top | PanelAnchor::Bottom => Row::with_children([btn, dot]) - .align_y(Alignment::Center) - .into(), - } + cosmic::iced::widget::stack![ + btn, + container(dot) + .width(Length::Fill) + .height(Length::Fill) + .align_y(dot_align_y) + .align_x(dot_align_x) + .padding(2.0) + ] + .into() }; self.core.applet.autosize_window(content).into() @@ -656,7 +679,7 @@ impl cosmic::Application for CosmicBatteryApplet { .size(24) .symbolic(true), slider( - 1..=max_screen_brightness, + 0..=max_screen_brightness, screen_brightness, Message::SetScreenBrightness ) @@ -790,7 +813,7 @@ impl cosmic::Application for CosmicBatteryApplet { if gpu.toggled && !self.core.applet.suggested_bounds.as_ref().is_some_and(|c| { let suggested_size = self.core.applet.suggested_size(true); - let padding = self.core.applet.suggested_padding(true); + let padding = self.core.applet.suggested_padding(true).1; let w = suggested_size.0 + 2 * padding; let h = suggested_size.1 + 2 * padding; // if we have a configure for width and height, we're in a overflow popup diff --git a/cosmic-applet-bluetooth/Cargo.toml b/cosmic-applet-bluetooth/Cargo.toml index a4e0e184..d98c937d 100644 --- a/cosmic-applet-bluetooth/Cargo.toml +++ b/cosmic-applet-bluetooth/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-bluetooth" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop b/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop index 592fcc66..ab2f4100 100644 --- a/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop +++ b/cosmic-applet-bluetooth/data/com.system76.CosmicAppletBluetooth.desktop @@ -12,5 +12,6 @@ Icon=com.system76.CosmicAppletBluetooth-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-bluetooth/i18n/ar/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/ar/cosmic_applet_bluetooth.ftl index b6ba80a0..2f44c651 100644 --- a/cosmic-applet-bluetooth/i18n/ar/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/ar/cosmic_applet_bluetooth.ftl @@ -3,10 +3,10 @@ other-devices = أجهزة بلوتوث أخرى settings = إعدادات البلوتوث... connected = متصل confirm-pin = يرجى تأكيد أن رمز PIN التالي يطابق المعروض على { $deviceName } -confirm = تأكيد -cancel = إلغاء +confirm = أكِّد +cancel = ألغِ unsuccessful = فشل الاقتران check-device = تأكد من أن { $deviceName } قيد التشغيل، وفي النطاق، وجاهز للاقتران. -try-again = حاول مرة أخرى +try-again = حاول مجددًا discoverable = قابل للاكتشاف pairable = قابل للاقتران diff --git a/cosmic-applet-bluetooth/i18n/bn/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/bn/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/fr/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/fr/cosmic_applet_bluetooth.ftl index a14dd180..4a8dfcf1 100644 --- a/cosmic-applet-bluetooth/i18n/fr/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/fr/cosmic_applet_bluetooth.ftl @@ -2,11 +2,11 @@ bluetooth = Bluetooth other-devices = Autres périphériques Bluetooth settings = Paramètres Bluetooth... connected = Connecté -confirm-pin = Veuillez confirmer que le PIN suivant correspond à celui affiché sur {$deviceName} +confirm-pin = Veuillez confirmer que le PIN suivant correspond à celui affiché sur { $deviceName } confirm = Confirmer cancel = Annuler unsuccessful = L'appairage a échoué -check-device = Vérifiez que {$deviceName} est allumé, à proximité, et prêt à s'appairer. +check-device = Vérifiez que { $deviceName } est allumé, à proximité, et prêt à s'appairer. try-again = Essayez à nouveau discoverable = Visible -pairable = Appairable \ No newline at end of file +pairable = Appairable diff --git a/cosmic-applet-bluetooth/i18n/ga/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/ga/cosmic_applet_bluetooth.ftl index b73e2782..a18968f7 100644 --- a/cosmic-applet-bluetooth/i18n/ga/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/ga/cosmic_applet_bluetooth.ftl @@ -2,11 +2,11 @@ bluetooth = Bluetooth other-devices = Gléasanna Bluetooth eile settings = Socruithe Bluetooth... connected = Ceangailte -confirm-pin = Deimhnigh le do thoil go bhfuil an PIN seo a leanas ag teacht leis an gceann ar taispeáint ar {$deviceName} +confirm-pin = Deimhnigh le do thoil go bhfuil an PIN seo a leanas ag teacht leis an gceann ar taispeáint ar { $deviceName } confirm = Deimhnigh cancel = Cealaigh unsuccessful = Níor éirigh leis an bpéireáil -check-device = Cinntigh go bhfuil {$deviceName} ar siúl, laistigh den raon, agus réidh le péireáil. -try-again = Bain triail eile as +check-device = Cinntigh go bhfuil { $deviceName } ar siúl, laistigh den raon, agus réidh le péireáil. +try-again = Bain triail arís discoverable = Infheicthe pairable = Péireálach diff --git a/cosmic-applet-bluetooth/i18n/hu/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/hu/cosmic_applet_bluetooth.ftl index 72c80b1c..2e557981 100644 --- a/cosmic-applet-bluetooth/i18n/hu/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/hu/cosmic_applet_bluetooth.ftl @@ -1,12 +1,12 @@ bluetooth = Bluetooth other-devices = Egyéb Bluetooth-eszközök -settings = Bluetooth beállítások... -connected = Csatlakoztatva -confirm-pin = Ellenőrizd, hogy a következő PIN-kód megegyezik-e a(z) {$deviceName} eszközön megjelenő PIN-kóddal +settings = Bluetooth-beállítások… +connected = Csatlakozva +confirm-pin = Ellenőrizd, hogy a következő PIN-kód megegyezik-e a(z) { $deviceName } eszközön megjelenő PIN-kóddal confirm = Megerősítés cancel = Mégse -unsuccessful = A párosítás sikertelen -check-device = Győződj meg arról, hogy a(z) {$deviceName} be van kapcsolva, hatótávolságon belül van, és készen áll a párosításra. +unsuccessful = Nem sikerült a párosítás +check-device = Győződj meg arról, hogy a(z) { $deviceName } be van kapcsolva, hatótávolságon belül van, és készen áll a párosításra. try-again = Próbáld újra discoverable = Felfedezhető pairable = Párosítható diff --git a/cosmic-applet-bluetooth/i18n/ka/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/ka/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/kab/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/kab/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/kk/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/kk/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..8a8476ce --- /dev/null +++ b/cosmic-applet-bluetooth/i18n/kk/cosmic_applet_bluetooth.ftl @@ -0,0 +1,12 @@ +cancel = Бас тарту +connected = Қосылды +confirm = Растау +bluetooth = Bluetooth +other-devices = Басқа Bluetooth құрылғылары +settings = Bluetooth баптаулары... +confirm-pin = Келесі PIN коды { $deviceName } құрылғысында көрсетілген кодқа сәйкес келетінін растаңыз +unsuccessful = Жұптастыру сәтсіз аяқталды +check-device = { $deviceName } қосулы екенін, ауқым ішінде екенін және жұптастыруға дайын екенін тексеріңіз. +try-again = Қайтадан көру +discoverable = Көрінетін +pairable = Жұптастыруға болады diff --git a/cosmic-applet-bluetooth/i18n/kmr/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/kmr/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/ko/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/ko/cosmic_applet_bluetooth.ftl index 117ee7d2..9b916ebc 100644 --- a/cosmic-applet-bluetooth/i18n/ko/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/ko/cosmic_applet_bluetooth.ftl @@ -1,12 +1,12 @@ bluetooth = 블루투스 -other-devices = Other Bluetooth Devices +other-devices = 기타 블루투스 장치 settings = 블루투스 설정... connected = 연결됨 -confirm-pin = {$deviceName}에 표시되는 PIN과 화면에 표시되는 PIN이 일치하는지 확인하십시오 +confirm-pin = 다음 PIN이 { $deviceName }에 표시된 것과 일치하는지 확인해 주세요 confirm = 확인 cancel = 취소 unsuccessful = 페어링 실패 -check-device = {$deviceName}(이)가 켜저 있는지 확인하십시오. 만약 범위에 있다면 페어링 준비를 하십시오. +check-device = { $deviceName }(이)가 켜져 있고, 범위 내에 있으며, 페어링 준비가 되어있는지 확인하세요. try-again = 다시 시도 -discoverable = 찾을 수 있음 +discoverable = 검색 가능 pairable = 페어링 가능 diff --git a/cosmic-applet-bluetooth/i18n/lt/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/lt/cosmic_applet_bluetooth.ftl index e69de29b..b5ee88d6 100644 --- a/cosmic-applet-bluetooth/i18n/lt/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/lt/cosmic_applet_bluetooth.ftl @@ -0,0 +1,12 @@ +confirm-pin = Patvirtinkite, kad nurodytas PIN sutampa su PIN pavaizduotu { $deviceName } įrenginyje +bluetooth = Bluetooth +unsuccessful = Nepavyko Susieti +pairable = Suporuojamas +try-again = Pabandyti iš naujo +check-device = Įsitikinkite, kad { $deviceName } yra įjungtas, yra pasiekiame atstume ir paruoštas susiejimui. +other-devices = Kiti Bluetooth įrenginiai +cancel = Atšaukti +connected = Prisijungta +confirm = Patvirtinti +settings = Bluetooth nustatymai... +discoverable = Aptinkamas diff --git a/cosmic-applet-bluetooth/i18n/nl/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/nl/cosmic_applet_bluetooth.ftl index 7e7d44dd..a025b790 100644 --- a/cosmic-applet-bluetooth/i18n/nl/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/nl/cosmic_applet_bluetooth.ftl @@ -1,12 +1,12 @@ bluetooth = Bluetooth other-devices = Overige bluetooth-apparaten -settings = Bluetooth-instellingen... +settings = Bluetooth-instellingen… connected = Verbonden -confirm-pin = Bevestig of de volgende pincode overeenkomt met dat weergegeven op {$deviceName} -confirm = Bevestigen -cancel = Annuleren +confirm-pin = Bevestig of de volgende pincode overeenkomt met dat weergegeven op { $deviceName } +confirm = Bevestig +cancel = Annuleer unsuccessful = Koppelen mislukt -check-device = Controleer of {$deviceName} aan staat, binnen bereik is, en klaar staat om te koppelen. +check-device = Controleer of { $deviceName } aan staat, binnen bereik is, en klaar staat om te koppelen. try-again = Opnieuw proberen -discoverable = Ontdekbaar +discoverable = Vindbaar pairable = Koppelbaar diff --git a/cosmic-applet-bluetooth/i18n/pa/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/pa/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/pt-BR/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/pt-BR/cosmic_applet_bluetooth.ftl index bece4d1e..34a5241b 100644 --- a/cosmic-applet-bluetooth/i18n/pt-BR/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/pt-BR/cosmic_applet_bluetooth.ftl @@ -1,6 +1,6 @@ bluetooth = Bluetooth other-devices = Outros dispositivos Bluetooth -settings = Configurações Bluetooth... +settings = Configurações de Bluetooth... connected = Conectado confirm-pin = Por favor, confirme se o seguinte PIN corresponde ao exibido em { $deviceName } confirm = Confirmar diff --git a/cosmic-applet-bluetooth/i18n/ti/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/ti/cosmic_applet_bluetooth.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-bluetooth/i18n/uk/cosmic_applet_bluetooth.ftl b/cosmic-applet-bluetooth/i18n/uk/cosmic_applet_bluetooth.ftl index 440a2e15..82e04dd4 100644 --- a/cosmic-applet-bluetooth/i18n/uk/cosmic_applet_bluetooth.ftl +++ b/cosmic-applet-bluetooth/i18n/uk/cosmic_applet_bluetooth.ftl @@ -5,7 +5,7 @@ connected = Підключено confirm-pin = Будь ласка, підтвердіть, що вказаний PIN-код збігається з тим, що відображається на { $deviceName } confirm = Підтвердити cancel = Скасувати -unsuccessful = Не вдалося створити пару +unsuccessful = Невдале сполучення check-device = Переконайтеся, що { $deviceName } увімкнено, знаходиться в зоні досяжності та готове до сполучення. try-again = Повторити спробу discoverable = Видимий diff --git a/cosmic-applet-bluetooth/src/app.rs b/cosmic-applet-bluetooth/src/app.rs index f930e0d3..3fed7463 100644 --- a/cosmic-applet-bluetooth/src/app.rs +++ b/cosmic-applet-bluetooth/src/app.rs @@ -204,13 +204,6 @@ impl cosmic::Application for CosmicBluetoothApplet { ) => { // my headphones seem to always request this // doesn't seem to be defined in the UX mockups - // dbg!( - // "request service authorization", - // d.name, - // bluer::id::Service::try_from(service) - // .map(|s| s.to_string()) - // .unwrap_or_else(|_| "unknown".to_string()) - // ); } }, }, @@ -274,6 +267,7 @@ impl cosmic::Application for CosmicBluetoothApplet { Message::CloseRequested(id) => { if Some(id) == self.popup { self.popup = None; + set_discovery(false); } return cosmic::task::future( set_tick(Duration::from_secs(10)) diff --git a/cosmic-applet-input-sources/Cargo.toml b/cosmic-applet-input-sources/Cargo.toml index adeac40a..81c2bbc2 100644 --- a/cosmic-applet-input-sources/Cargo.toml +++ b/cosmic-applet-input-sources/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-input-sources" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-input-sources/data/com.system76.CosmicAppletInputSources.desktop b/cosmic-applet-input-sources/data/com.system76.CosmicAppletInputSources.desktop index b6ce9e52..29f1f306 100644 --- a/cosmic-applet-input-sources/data/com.system76.CosmicAppletInputSources.desktop +++ b/cosmic-applet-input-sources/data/com.system76.CosmicAppletInputSources.desktop @@ -2,7 +2,7 @@ Name=Input Sources Name[ar]=مصادر الإدخال Name[bg]=Входни устройства -Name[cs]=Metody zadávání +Name[cs]=Vstupní zdroje Name[zh_CN]=输入源 Name[hu]=Beviteli Források Name[pl]=Źródła wprowadzdania danych @@ -12,6 +12,7 @@ Name[de]=Eigangsquellen Name[sk]=Vstupné zdroje Name[es]=Fuentes de entrada de teclado Name[sv]=Inmatningskällor +Name[it]=Sorgenti di immissione Type=Application Exec=cosmic-applet-input-sources Terminal=false @@ -22,5 +23,6 @@ Icon=com.system76.CosmicAppletInputSources-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-input-sources/i18n/ar/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/ar/cosmic_applet_input_sources.ftl index 803fb4e9..e0f0a583 100644 --- a/cosmic-applet-input-sources/i18n/ar/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/ar/cosmic_applet_input_sources.ftl @@ -1,2 +1,2 @@ -show-keyboard-layout = إظهار تخطيط لوحة المفاتيح... +show-keyboard-layout = أظهر تخطيط لوحة المفاتيح... keyboard-settings = إعدادات لوحة المفاتيح... diff --git a/cosmic-applet-input-sources/i18n/bn/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/bn/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/i18n/ga/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/ga/cosmic_applet_input_sources.ftl index f4c88b4d..c77e2b6e 100644 --- a/cosmic-applet-input-sources/i18n/ga/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/ga/cosmic_applet_input_sources.ftl @@ -1,2 +1,2 @@ -show-keyboard-layout = Taispeáin Leagan Amach na Méarchláir... -keyboard-settings = Socruithe Méarchláir... +show-keyboard-layout = Taispeáin leagan amach na méarchláir... +keyboard-settings = Socruithe méarchláir... diff --git a/cosmic-applet-input-sources/i18n/hu/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/hu/cosmic_applet_input_sources.ftl index 44bfb70e..7ab52ed0 100644 --- a/cosmic-applet-input-sources/i18n/hu/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/hu/cosmic_applet_input_sources.ftl @@ -1,2 +1,2 @@ -show-keyboard-layout = Billentyűzetkiosztás megjelenítése... -keyboard-settings = Billentyűzet beállításai... +show-keyboard-layout = Billentyűzetkiosztás megjelenítése… +keyboard-settings = Billentyűzetbeállítások… diff --git a/cosmic-applet-input-sources/i18n/ka/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/ka/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/i18n/kab/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/kab/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/i18n/kk/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/kk/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..493d91e2 --- /dev/null +++ b/cosmic-applet-input-sources/i18n/kk/cosmic_applet_input_sources.ftl @@ -0,0 +1,2 @@ +show-keyboard-layout = Пернетақта жаймасын көрсету... +keyboard-settings = Пернетақта баптаулары... diff --git a/cosmic-applet-input-sources/i18n/kmr/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/kmr/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/i18n/ko/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/ko/cosmic_applet_input_sources.ftl index e69de29b..03aa414b 100644 --- a/cosmic-applet-input-sources/i18n/ko/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/ko/cosmic_applet_input_sources.ftl @@ -0,0 +1,2 @@ +show-keyboard-layout = 키보드 레이아웃 표시... +keyboard-settings = 키보드 설정... diff --git a/cosmic-applet-input-sources/i18n/lt/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/lt/cosmic_applet_input_sources.ftl index e69de29b..be452105 100644 --- a/cosmic-applet-input-sources/i18n/lt/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/lt/cosmic_applet_input_sources.ftl @@ -0,0 +1,2 @@ +show-keyboard-layout = Rodyti Klaviatūros išdėstymą... +keyboard-settings = Klaviatūros Nustatymai... diff --git a/cosmic-applet-input-sources/i18n/nl/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/nl/cosmic_applet_input_sources.ftl index 45bf02df..ea465a4f 100644 --- a/cosmic-applet-input-sources/i18n/nl/cosmic_applet_input_sources.ftl +++ b/cosmic-applet-input-sources/i18n/nl/cosmic_applet_input_sources.ftl @@ -1,2 +1,2 @@ -show-keyboard-layout = Toetsenbordindeling weergeven... -keyboard-settings = Toetsenbordinstellingen... +show-keyboard-layout = Laat toetsenbordindeling zien… +keyboard-settings = Toetsenbordinstellingen… diff --git a/cosmic-applet-input-sources/i18n/pa/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/pa/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/i18n/ti/cosmic_applet_input_sources.ftl b/cosmic-applet-input-sources/i18n/ti/cosmic_applet_input_sources.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-input-sources/src/lib.rs b/cosmic-applet-input-sources/src/lib.rs index 64de4cf7..0eae0e09 100644 --- a/cosmic-applet-input-sources/src/lib.rs +++ b/cosmic-applet-input-sources/src/lib.rs @@ -11,7 +11,7 @@ use cosmic::{ cosmic_config::{self, ConfigSet, CosmicConfigEntry}, cosmic_theme::Spacing, iced::{ - Task, + Rectangle, Task, platform_specific::shell::commands::popup::{destroy_popup, get_popup}, widget::{column, row}, window::Id, @@ -20,11 +20,17 @@ use cosmic::{ iced_runtime::{Appearance, core::window}, prelude::*, surface, theme, - widget::{self, horizontal_space, vertical_space}, + widget::{ + self, autosize, horizontal_space, + rectangle_tracker::{RectangleTracker, RectangleUpdate, rectangle_tracker_subscription}, + vertical_space, + }, }; use cosmic_comp_config::CosmicCompConfig; +use std::sync::LazyLock; use xkb_data::KeyboardLayout; +static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| widget::Id::new("autosize-main")); pub const ID: &str = "com.system76.CosmicAppletInputSources"; pub fn run() -> cosmic::iced::Result { @@ -77,6 +83,8 @@ pub struct Window { comp_config_handler: Option, layouts: Vec, active_layouts: Vec, + rectangle_tracker: Option>, + rectangle: Rectangle, } #[derive(Clone, Debug)] @@ -87,6 +95,7 @@ pub enum Message { SetActiveLayout(usize), KeyboardSettings, Surface(surface::Action), + Rectangle(RectangleUpdate), } #[derive(Debug)] @@ -119,6 +128,8 @@ impl cosmic::Application for Window { popup: None, comp_config: flags.comp_config, active_layouts: Vec::new(), + rectangle_tracker: None, + rectangle: Rectangle::default(), }; (window, Task::none()) } @@ -194,42 +205,42 @@ impl cosmic::Application for Window { cosmic::app::Action::Surface(a), )); } + Message::Rectangle(u) => match u { + RectangleUpdate::Rectangle(r) => { + self.rectangle = r.1; + } + RectangleUpdate::Init(tracker) => { + self.rectangle_tracker = Some(tracker); + } + }, } Task::none() } fn view(&self) -> Element<'_, Self::Message> { - let input_source_text = self.core.applet.text( - self.active_layouts - .first() - .map_or("", |l| l.layout.as_str()), - ); - - cosmic::widget::button::custom( - row!( - column!( - input_source_text, - horizontal_space().width(Length::Fixed( - (self.core.applet.suggested_size(true).0 - + 2 * self.core.applet.suggested_padding(true)) - as f32 - )) - ) - .width(Length::Shrink) - .height(Length::Shrink) - .align_x(Alignment::Center), - vertical_space().height(Length::Fixed( - (self.core.applet.suggested_size(true).1 - + 2 * self.core.applet.suggested_padding(true)) as f32 - )) - ) - .align_y(Alignment::Center) - .width(Length::Shrink) - .height(Length::Shrink), + let applet_text = if let Some(l) = self.active_layouts.first() { + if !l.variant.is_empty() { + format!("{} ({})", l.layout, l.variant) + } else { + l.layout.clone() + } + } else { + String::new() + }; + let input_source_text = self.core.applet.text(applet_text); + let button = self + .core + .applet + .text_button(input_source_text, Message::TogglePopup); + autosize::autosize( + if let Some(tracker) = self.rectangle_tracker.as_ref() { + Element::from(tracker.container(0, button).ignore_bounds(true)) + } else { + button.into() + }, + AUTOSIZE_MAIN_ID.clone(), ) - .on_press_down(Message::TogglePopup) - .class(cosmic::theme::Button::AppletIcon) .into() } @@ -263,18 +274,21 @@ impl cosmic::Application for Window { } fn subscription(&self) -> Subscription { - self.core - .watch_config("com.system76.CosmicComp") - .map(|update| { - if !update.errors.is_empty() { - tracing::error!( - "errors loading config {:?}: {:?}", - update.keys, - update.errors - ); - } - Message::CompConfig(Box::new(update.config)) - }) + Subscription::batch(vec![ + rectangle_tracker_subscription(0).map(|e| Message::Rectangle(e.1)), + self.core + .watch_config("com.system76.CosmicComp") + .map(|update| { + if !update.errors.is_empty() { + tracing::error!( + "errors loading config {:?}: {:?}", + update.keys, + update.errors + ); + } + Message::CompConfig(Box::new(update.config)) + }), + ]) } fn style(&self) -> Option { diff --git a/cosmic-applet-minimize/Cargo.toml b/cosmic-applet-minimize/Cargo.toml index d3fa6393..a41a3e9a 100644 --- a/cosmic-applet-minimize/Cargo.toml +++ b/cosmic-applet-minimize/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-minimize" -version = "0.1.1" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" @@ -8,9 +8,9 @@ license = "GPL-3.0-only" anyhow.workspace = true i18n-embed-fl.workspace = true i18n-embed.workspace = true -image = { version = "0.25.8", default-features = false } +image = { version = "0.25.9", default-features = false } libcosmic.workspace = true -memmap2 = "0.9.8" +memmap2 = "0.9.9" rust-embed.workspace = true rustix.workspace = true tokio.workspace = true diff --git a/cosmic-applet-minimize/data/com.system76.CosmicAppletMinimize.desktop b/cosmic-applet-minimize/data/com.system76.CosmicAppletMinimize.desktop index aa2c3291..53338ef5 100644 --- a/cosmic-applet-minimize/data/com.system76.CosmicAppletMinimize.desktop +++ b/cosmic-applet-minimize/data/com.system76.CosmicAppletMinimize.desktop @@ -12,6 +12,7 @@ Name[de]=Minimierte Fenster Name[sk]=Minimalizované okná Name[es]=Ventanas minimizadas Name[sv]=Minimera fönster +Name[it]=Finestre minimizzate Type=Application Exec=cosmic-applet-minimize Terminal=false diff --git a/cosmic-applet-minimize/src/lib.rs b/cosmic-applet-minimize/src/lib.rs index b76ebeab..e836290d 100644 --- a/cosmic-applet-minimize/src/lib.rs +++ b/cosmic-applet-minimize/src/lib.rs @@ -74,7 +74,7 @@ impl Minimize { return index; }; let button_total_size = self.core.applet.suggested_size(true).0 - + self.core.applet.suggested_padding(true) * 2 + + self.core.applet.suggested_padding(true).0 * 2 + 4; let btn_count = max_major_axis_len / button_total_size as u32; if btn_count >= self.apps.len() as u32 { @@ -143,8 +143,11 @@ impl cosmic::Application for Minimize { }; app.update_desktop_entries(); - - (app, Task::none()) + let t = iced::window::minimize::>( + app.core.main_window_id().unwrap(), + true, + ); + (app, t) } fn core(&self) -> &cosmic::app::Core { @@ -205,11 +208,22 @@ impl cosmic::Application for Minimize { } self.apps = apps; + + return iced::window::maximize(self.core.main_window_id().unwrap(), true); } ToplevelUpdate::Remove(handle) => { + let prev_was_empty = self.apps.is_empty(); self.apps .retain(|a| a.toplevel_info.foreign_toplevel != handle); self.apps.shrink_to_fit(); + let changed = prev_was_empty != self.apps.is_empty(); + if self.apps.is_empty() && changed { + // hide the window + return iced::window::minimize( + self.core.main_window_id().unwrap(), + true, + ); + } } }, WaylandUpdate::Image(handle, img) => { @@ -241,7 +255,7 @@ impl cosmic::Application for Minimize { self.overflow_popup = Some(new_id); let icon_size = self.core.applet.suggested_size(true).0 as u32 - + 2 * self.core.applet.suggested_padding(true) as u32; + + 2 * self.core.applet.suggested_padding(true).1 as u32; let spacing = self.core.system_theme().cosmic().space_xxs() as u32; let major_axis_len = (icon_size + spacing) * (pos.saturating_sub(1) as u32); let rectangle = match self.core.applet.anchor { @@ -293,9 +307,16 @@ impl cosmic::Application for Minimize { } }); let (width, _) = self.core.applet.suggested_size(false); - let padding = self.core.applet.suggested_padding(false); + let (major_padding, cross_padding) = self.core.applet.suggested_padding(false); + let padding = if matches!( + self.core.applet.anchor, + PanelAnchor::Top | PanelAnchor::Bottom + ) { + (major_padding, cross_padding) + } else { + (cross_padding, major_padding) + }; let theme = self.core.system_theme().cosmic(); - let space_xxs = theme.space_xxs(); let icon_buttons = self.apps[..max_icon_count].iter().map(|app| { self.core .applet @@ -332,6 +353,7 @@ impl cosmic::Application for Minimize { None }; + let spacing = self.core.applet.spacing; // TODO optional dividers on ends if detects app list neighbor // not sure the best way to tell if there is an adjacent app-list let icon_buttons = icon_buttons.chain(overflow_btn); @@ -343,14 +365,14 @@ impl cosmic::Application for Minimize { .align_y(cosmic::iced_core::Alignment::Center) .height(Length::Shrink) .width(Length::Shrink) - .spacing(space_xxs) + .spacing(spacing as f32) .into() } else { Column::with_children(icon_buttons) .align_x(cosmic::iced_core::Alignment::Center) .height(Length::Shrink) .width(Length::Shrink) - .spacing(space_xxs) + .spacing(spacing as f32) .into() }; @@ -388,7 +410,15 @@ impl cosmic::Application for Minimize { } }); let (width, _) = self.core.applet.suggested_size(false); - let padding = self.core.applet.suggested_padding(false); + let (major_padding, cross_padding) = self.core.applet.suggested_padding(false); + let padding = if matches!( + self.core.applet.anchor, + PanelAnchor::Top | PanelAnchor::Bottom + ) { + (major_padding, cross_padding) + } else { + (cross_padding, major_padding) + }; let theme = self.core.system_theme().cosmic(); let space_xxs = theme.space_xxs(); let icon_buttons = self.apps[max_icon_count..].iter().map(|app| { @@ -429,15 +459,13 @@ impl cosmic::Application for Minimize { Row::with_children(icon_buttons) .align_y(cosmic::iced_core::Alignment::Center) .height(Length::Shrink) - .width(Length::Shrink) - .spacing(space_xxs), + .width(Length::Shrink), ) } else { Column::with_children(icon_buttons) .align_x(cosmic::iced_core::Alignment::Center) .height(Length::Shrink) .width(Length::Shrink) - .spacing(space_xxs) .into() }, ) diff --git a/cosmic-applet-minimize/src/wayland_handler.rs b/cosmic-applet-minimize/src/wayland_handler.rs index 56fcb0a1..648a0770 100644 --- a/cosmic-applet-minimize/src/wayland_handler.rs +++ b/cosmic-applet-minimize/src/wayland_handler.rs @@ -20,7 +20,7 @@ use cctk::{ }, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, toplevel_management::{ToplevelManagerHandler, ToplevelManagerState}, - wayland_client::{self, WEnum, protocol::wl_seat::WlSeat}, + wayland_client::{self, WEnum, delegate_noop, protocol::wl_seat::WlSeat}, }; use cosmic::{ cctk::{ @@ -557,21 +557,10 @@ impl Dispatch for AppData { } } -impl Dispatch for AppData { - fn event( - _app_data: &mut Self, - _buffer: &wl_buffer::WlBuffer, - _event: wl_buffer::Event, - (): &(), - _: &Connection, - _qh: &QueueHandle, - ) { - } -} - sctk::delegate_shm!(AppData); sctk::delegate_seat!(AppData); sctk::delegate_registry!(AppData); cctk::delegate_toplevel_info!(AppData); cctk::delegate_toplevel_manager!(AppData); cctk::delegate_screencopy!(AppData); +delegate_noop!(AppData: ignore wl_buffer::WlBuffer); diff --git a/cosmic-applet-minimize/src/window_image.rs b/cosmic-applet-minimize/src/window_image.rs index e7ae9a43..7b7f639d 100644 --- a/cosmic-applet-minimize/src/window_image.rs +++ b/cosmic-applet-minimize/src/window_image.rs @@ -26,7 +26,7 @@ where icon: &fde::IconSource, size: f32, on_press: Msg, - padding: u16, + padding: (u16, u16), ) -> Self { let border = 1.0; Self { @@ -46,7 +46,7 @@ where ) } else { Element::from( - icon.as_cosmic_icon() + cosmic::widget::icon(icon.as_cosmic_icon()) .width(Length::Fixed((size - border * 2.0).max(0.))) .height(Length::Fixed((size - border * 2.0).max(0.))), ) @@ -63,8 +63,9 @@ where .height(Length::Shrink) .width(Length::Shrink), ) - .center(Length::Fixed(size + padding as f32 * 2.0)) - .padding(padding), + .center_x(Length::Fixed(size + padding.0 as f32 * 2.0)) + .center_y(Length::Fixed(size + padding.1 as f32 * 2.0)) + .padding([padding.0 as f32, padding.1 as f32]), ) .on_press(on_press) .width(Length::Shrink) @@ -72,8 +73,7 @@ where .class(Button::AppletIcon) .padding(0) .into(), - icon: icon - .as_cosmic_icon() + icon: cosmic::widget::icon(icon.as_cosmic_icon()) .width(Length::Fixed(size / 3.0)) .height(Length::Fixed(size / 3.0)) .into(), diff --git a/cosmic-applet-network/Cargo.toml b/cosmic-applet-network/Cargo.toml index f05b676e..c233d929 100644 --- a/cosmic-applet-network/Cargo.toml +++ b/cosmic-applet-network/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "cosmic-applet-network" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true +async-fn-stream = "0.3" cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" } cosmic-time.workspace = true futures.workspace = true futures-util.workspace = true i18n-embed-fl.workspace = true i18n-embed.workspace = true -itertools = "0.14.0" libcosmic = { workspace = true, features = [ "applet", "applet-token", @@ -27,3 +27,15 @@ tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true zbus.workspace = true +nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings/" } +indexmap = "2.13.0" +secure-string = "0.3.0" +uuid = { version = "1.19.0", features = ["v4"] } + + +[dependencies.cosmic-settings-network-manager-subscription] +git = "https://github.com/pop-os/cosmic-settings/" + +[dependencies.cosmic-settings-airplane-mode-subscription] +git = "https://github.com/pop-os/cosmic-settings/" + diff --git a/cosmic-applet-network/data/com.system76.CosmicAppletNetwork.desktop b/cosmic-applet-network/data/com.system76.CosmicAppletNetwork.desktop index 5b67388e..15b77434 100644 --- a/cosmic-applet-network/data/com.system76.CosmicAppletNetwork.desktop +++ b/cosmic-applet-network/data/com.system76.CosmicAppletNetwork.desktop @@ -12,6 +12,7 @@ Name[de]=Netzwerk Name[sk]=Sieť Name[sv]=Nätverk Name[es]=Red +Name[it]=Rete Type=Application Exec=cosmic-applet-network Terminal=false @@ -22,5 +23,6 @@ Icon=com.system76.CosmicAppletNetwork-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-network/i18n/ar/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/ar/cosmic_applet_network.ftl index e294389e..8574aabf 100644 --- a/cosmic-applet-network/i18n/ar/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/ar/cosmic_applet_network.ftl @@ -1,21 +1,23 @@ network = الشبكة airplane-mode = وضع الطيران airplane-mode-on = وضع الطيران مُفعّل -turn-off-airplane-mode = أوقفه لتمكين الواي فاي، البلوتوث، والنطاق العريض المتنقل. +turn-off-airplane-mode = عطّله لتفعيل الواي فاي والبلوتوث وشبكة هاتف محمول. wifi = واي فاي identity = الهوية ipv4 = عنوان IPv4 ipv6 = عنوان IPv6 mac = MAC -megabits-per-second = ميغابت/ثانية +megabits-per-second = م.بت/ث connected = متصل connecting = جارٍ الاتصال -connect = توصيل -cancel = إلغاء +connect = اتصل +cancel = ألغِ settings = إعدادات الشبكة... visible-wireless-networks = الشبكات اللاسلكية الظاهرة -enter-password = أدخل كلمة المرور أو مفتاح التشفير +enter-password = أدخل كلمة السر أو مفتاح التعمية router-wps-button = يمكنك أيضًا الاتصال بالضغط على زر "WPS" في جهاز التوجيه unable-to-connect = لا يمكن الاتصال بالشبكة check-wifi-connection = تأكد من اتصال الواي فاي بالإنترنت وصحة كلمة المرور -reset = إعادة تعيين +reset = صفّر +gigabits-per-second = ج.بت/ث +terabits-per-second = ت.بت/ث diff --git a/cosmic-applet-network/i18n/bg/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/bg/cosmic_applet_network.ftl index d8619ebd..6cc748fc 100644 --- a/cosmic-applet-network/i18n/bg/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/bg/cosmic_applet_network.ftl @@ -19,3 +19,4 @@ router-wps-button = Може да се свържете и чрез натиск unable-to-connect = Свързването към мрежата е неуспешно check-wifi-connection = Уверете се, че Wi-Fi е свързан с Интернет и паролата е правилна reset = Връщане на настройките +vpn-connections = ВЧМ връзки diff --git a/cosmic-applet-network/i18n/bn/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/bn/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/cs/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/cs/cosmic_applet_network.ftl index cbe1ec42..465e4898 100644 --- a/cosmic-applet-network/i18n/cs/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/cs/cosmic_applet_network.ftl @@ -7,16 +7,18 @@ identity = Identita ipv4 = IPv4 adresa ipv6 = IPv6 adresa mac = MAC -megabits-per-second = Mbps +megabits-per-second = Mb/s connected = Připojeno -connecting = Připojuje se +connecting = Připojování connect = Připojit cancel = Zrušit settings = Nastavení sítě... visible-wireless-networks = Viditelné bezdrátové sítě enter-password = Zadejte heslo nebo šifrovací klíč -router-wps-button = Můžete se také připojit stisknutím tlačítka "WPS" na vašem routeru +router-wps-button = Můžete se také připojit stisknutím tlačítka „WPS“ na vašem routeru unable-to-connect = Nepodařilo se připojit k síti check-wifi-connection = Ujistěte se, že Wi-Fi má připojení k internetu a heslo je správné reset = Obnovit vpn-connections = Připojení VPN +gigabits-per-second = Gb/s +terabits-per-second = Tb/s diff --git a/cosmic-applet-network/i18n/en/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/en/cosmic_applet_network.ftl index 3b05600f..639c8384 100644 --- a/cosmic-applet-network/i18n/en/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/en/cosmic_applet_network.ftl @@ -8,6 +8,8 @@ ipv4 = IPv4 address ipv6 = IPv6 address mac = MAC megabits-per-second = Mbps +gigabits-per-second = Gbps +terabits-per-second = Tbps connected = Connected connecting = Connecting connect = Connect diff --git a/cosmic-applet-network/i18n/fr/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/fr/cosmic_applet_network.ftl index 7a548252..f2c0025d 100644 --- a/cosmic-applet-network/i18n/fr/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/fr/cosmic_applet_network.ftl @@ -8,13 +8,17 @@ ipv6 = Adresse IPv6 mac = MAC megabits-per-second = Mbps connected = Connecté -connecting = Connexion en cours -connect = Se connecter +connecting = Connexion +connect = Connecter cancel = Annuler settings = Paramètres réseaux... -visible-wireless-networks = Réseaux sans-fil visibles -enter-password = Entrez le mot de passe ou la clé de cryptage +visible-wireless-networks = Réseaux Wi-Fi disponibles +enter-password = Entrez le mot de passe ou la clé de chiffrement router-wps-button = Vous pouvez aussi vous connecter en appuyant sur le bouton "WPS" du routeur unable-to-connect = Impossible de se connecter au réseau -check-wifi-connection = Vérifiez que le Wi-Fi est relié à internet et que le mot de passe est correct +check-wifi-connection = Vérifiez que le Wi-Fi est connecté à internet et que le mot de passe est correct reset = Réinitialiser +identity = Identité +vpn-connections = Connexions VPN +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/ga/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/ga/cosmic_applet_network.ftl index 60cb711f..c7f66aa4 100644 --- a/cosmic-applet-network/i18n/ga/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/ga/cosmic_applet_network.ftl @@ -1,6 +1,6 @@ network = Líonra -airplane-mode = Mód Eitleáin -airplane-mode-on = Tá Mód Eitleáin ar siúl +airplane-mode = Mód eitleáin +airplane-mode-on = Tá mód eitleáin ar siúl turn-off-airplane-mode = Múch é chun Wi-Fi, Bluetooth agus leathanbhanda soghluaiste a chumasú. wifi = Wi-Fi ipv4 = Seoladh IPv4 @@ -14,9 +14,11 @@ cancel = Cealaigh settings = Socruithe Líonra... visible-wireless-networks = Líonraí gan sreang infheicthe enter-password = Cuir isteach an focal faire nó an eochair chriptiúcháin -router-wps-button = Is féidir leat nascadh freisin trí chnaipe "WPS" an ródaire a bhrú +router-wps-button = Is féidir leat ceangal freisin trí chnaipe "WPS" an ródaire a bhrú unable-to-connect = Ní féidir ceangal leis an líonra check-wifi-connection = Cinntigh go bhfuil Wi-Fi ceangailte leis an idirlíon agus go bhfuil an focal faire ceart reset = Athshocraigh identity = Céannacht vpn-connections = Naisc VPN +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/he/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/he/cosmic_applet_network.ftl index e69de29b..7975ae72 100644 --- a/cosmic-applet-network/i18n/he/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/he/cosmic_applet_network.ftl @@ -0,0 +1,3 @@ +connect = התחברות +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/hu/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/hu/cosmic_applet_network.ftl index 9f7e6b35..d408d711 100644 --- a/cosmic-applet-network/i18n/hu/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/hu/cosmic_applet_network.ftl @@ -1,18 +1,18 @@ network = Hálózat -airplane-mode = Repülőgép üzemmód -airplane-mode-on = A repülőgép üzemmód be van kapcsolva +airplane-mode = Repülőgép-üzemmód +airplane-mode-on = A repülőgép-üzemmód be van kapcsolva turn-off-airplane-mode = Kapcsold ki a Wi-Fi, Bluetooth és mobil szélessáv engedélyezéséhez. wifi = Wi-Fi identity = Azonosító -ipv4 = IPv4 cím -ipv6 = IPv6 cím +ipv4 = IPv4-cím +ipv6 = IPv6-cím mac = MAC megabits-per-second = Mbps -connected = Csatlakoztatva -connecting = Csatlakozás +connected = Csatlakozva +connecting = Csatlakozás… connect = Csatlakozás cancel = Mégse -settings = Hálózati beállítások... +settings = Hálózati beállítások… visible-wireless-networks = Látható vezeték nélküli hálózatok enter-password = Add meg a jelszót vagy a titkosítási kulcsot router-wps-button = A router „WPS” gombjának megnyomásával is csatlakozhatsz @@ -20,3 +20,5 @@ unable-to-connect = Nem lehet csatlakozni a hálózathoz check-wifi-connection = Győződj meg arról, hogy a Wi-Fi csatlakozik az internethez, és a jelszó helyes reset = Visszaállítás vpn-connections = VPN-kapcsolatok +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/ka/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/ka/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/kab/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/kab/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/kk/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/kk/cosmic_applet_network.ftl new file mode 100644 index 00000000..6c4f311b --- /dev/null +++ b/cosmic-applet-network/i18n/kk/cosmic_applet_network.ftl @@ -0,0 +1,24 @@ +cancel = Бас тарту +connect = Байланысу +connected = Қосылды +identity = Сәйкестендіру +wifi = Wi-Fi +network = Желі +airplane-mode = Ұшақ режимі +airplane-mode-on = Ұшақ режимі қосулы +turn-off-airplane-mode = Wi-Fi, Bluetooth және мобильді кең жолақты байланысты іске қосу үшін өшіріңіз. +ipv4 = IPv4 адресі +ipv6 = IPv6 адресі +mac = MAC +megabits-per-second = Мбит/с +gigabits-per-second = Гбит/с +terabits-per-second = Тбит/с +connecting = Қосылуда +settings = Желі баптаулары... +visible-wireless-networks = Көрінетін сымсыз желілер +vpn-connections = VPN қосылымдары +enter-password = Парольді немесе шифрлау кілтін енгізіңіз +router-wps-button = Сондай-ақ роутердегі "WPS" батырмасын басу арқылы қосылуға болады +unable-to-connect = Желіге қосылу мүмкін емес +check-wifi-connection = Wi-Fi интернетке қосылғанына және пароль дұрыс екеніне көз жеткізіңіз +reset = Қалпына келтіру diff --git a/cosmic-applet-network/i18n/kmr/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/kmr/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/ko/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/ko/cosmic_applet_network.ftl index 9a526910..ff914834 100644 --- a/cosmic-applet-network/i18n/ko/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/ko/cosmic_applet_network.ftl @@ -12,7 +12,13 @@ connecting = 연결 중 connect = 연결 cancel = 취소 visible-wireless-networks = 이용 가능한 무선 네트워크 -enter-password = 비밀번호나 암호화 키 입력 -router-wps-button = 라우터의 "WPS" 버튼을 눌러서 연결하실 수도 있습니다 -unable-to-connect = 네트워크에 연결할 수 없음 -check-wifi-connection = Wi-Fi가 인터넷에 연결되어 있거나 비밀번호가 올바른지 확인하십시오 +enter-password = 암호 또는 암호화 키 입력 +router-wps-button = 라우터의 "WPS" 버튼을 눌러서 연결할 수도 있습니다 +unable-to-connect = 네트워크에 연결할 수 없습니다 +check-wifi-connection = Wi-Fi가 인터넷에 연결되어 있고 암호가 일치하는지 확인하세요 +reset = 초기화 +identity = 식별자 +vpn-connections = VPN 연결 +settings = 네트워크 설정... +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/lt/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/lt/cosmic_applet_network.ftl index e69de29b..0ed76456 100644 --- a/cosmic-applet-network/i18n/lt/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/lt/cosmic_applet_network.ftl @@ -0,0 +1,24 @@ +connect = Prijungti +check-wifi-connection = Įsitikinkite, kad Wi-Fi yra prijungtas prie interneto ir slaptažodis yra teisingas +reset = Atkurti +visible-wireless-networks = Matomi bevieliai ryšiai +enter-password = Įveskite slaptažodį ar šifravimo raktą +ipv6 = IPv6 adresas +airplane-mode-on = Įjungtas lėktuvo režimas +connecting = Jungiamasi +airplane-mode = Lėktuvo rėžimas +wifi = Wi-Fi +ipv4 = IPv4 adresas +identity = Tapatybė +unable-to-connect = Nepavyko prisijungti prie tinklo +turn-off-airplane-mode = Išjunkite, kad būtų galima įjungti Wi-Fi, Bluetooth ir mobilųjų ryšį. +network = Tinklas +cancel = Atšaukti +connected = Prisijungta +vpn-connections = VPN ryšiai +mac = MAC adresas +router-wps-button = Taip pat galite prisijungti paspausdami „WPS“ mygtuką esantį ant maršrutizatoriaus +settings = Tinklo nustatymai... +megabits-per-second = Mbps +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/nl/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/nl/cosmic_applet_network.ftl index baa1b560..dfef1df1 100644 --- a/cosmic-applet-network/i18n/nl/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/nl/cosmic_applet_network.ftl @@ -1,7 +1,7 @@ network = Netwerk airplane-mode = Vliegtuigmodus airplane-mode-on = Vliegtuigmodus ingeschakeld -turn-off-airplane-mode = Schakel vliegtuigmodus uit om wifi, bluetooth en mobiele gegevens te activeren. +turn-off-airplane-mode = Schakel vliegtuigmodus uit zodat je wifi, bluetooth en mobiele gegevens weer kan gebruiken. wifi = Wifi identity = Identiteit ipv4 = IPv4-adres @@ -9,13 +9,14 @@ ipv6 = IPv6-adres mac = MAC megabits-per-second = Mbit/s connected = Verbonden -connecting = Verbinding maken... +connecting = Verbinding maken connect = Verbind -cancel = Annuleren -settings = Netwerkinstellingen... +cancel = Annuleer +settings = Netwerkinstellingen… visible-wireless-networks = Zichtbare draadloze netwerken -enter-password = Voer het wachtwoord of encryptiesleutel in -router-wps-button = U kunt ook verbinden door op de "WPS"-knop op de router te drukken -unable-to-connect = Kon niet met het netwerk verbinden -check-wifi-connection = Zorg ervoor dat de wifi verbonden is met het internet en dat het wachtwoord correct is -reset = Resetten +enter-password = Voer wachtwoord of encryptiesleutel in +router-wps-button = Je kunt ook verbinden door op de “WPS”-knop op de router te drukken +unable-to-connect = Kon niet met netwerk verbinden +check-wifi-connection = Controleer of de wifi met het internet verbonden is en of het wachtwoord juist is +reset = Reset +vpn-connections = VPN-verbindingen diff --git a/cosmic-applet-network/i18n/pa/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/pa/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/pl/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/pl/cosmic_applet_network.ftl index 6c5463c3..dc10e8ca 100644 --- a/cosmic-applet-network/i18n/pl/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/pl/cosmic_applet_network.ftl @@ -20,3 +20,5 @@ unable-to-connect = Nie można połączyć się z siecią check-wifi-connection = Upewnij się, że Wi-Fi ma połączenie z internetem oraz hasło jest poprawne reset = Reset vpn-connections = Połączenia VPN +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/sv/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/sv/cosmic_applet_network.ftl index 35c87dd2..95e5f44e 100644 --- a/cosmic-applet-network/i18n/sv/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/sv/cosmic_applet_network.ftl @@ -20,3 +20,5 @@ unable-to-connect = Kan inte ansluta till nätverket check-wifi-connection = Säkerställ att Wi-Fi är anslutet till internet och att lösenordet är korrekt reset = Återställ vpn-connections = VPN-anslutningar +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/i18n/ti/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/ti/cosmic_applet_network.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-network/i18n/uk/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/uk/cosmic_applet_network.ftl index f1f472be..1a3dc8ab 100644 --- a/cosmic-applet-network/i18n/uk/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/uk/cosmic_applet_network.ftl @@ -3,8 +3,8 @@ airplane-mode = Режим польоту airplane-mode-on = Режим польоту увімкнено turn-off-airplane-mode = Вимкніть, щоб увімкнути Wi-Fi, Bluetooth і мобільний інтернет. wifi = Wi-Fi -ipv4 = Адреса IPv4 -ipv6 = Адреса IPv6 +ipv4 = IPv4-адреса +ipv6 = IPv6-адреса mac = MAC megabits-per-second = Мбіт/с connected = Підключено @@ -14,9 +14,11 @@ cancel = Скасувати settings = Налаштування мережі... visible-wireless-networks = Видимі бездротові мережі enter-password = Введіть пароль або ключ шифрування -router-wps-button = Також можна підключитися, натиснувши кнопку "WPS" на маршрутизаторі +router-wps-button = Також можна підключитися, натиснувши кнопку «WPS» на маршрутизаторі unable-to-connect = Не вдалося підключитися до мережі check-wifi-connection = Переконайтеся, що Wi-Fi підключено до Інтернету, а пароль — правильний reset = Скинути identity = Ідентичність vpn-connections = VPN-з'єднання +gigabits-per-second = Гбіт/с +terabits-per-second = Тбіт/с diff --git a/cosmic-applet-network/i18n/zh-CN/cosmic_applet_network.ftl b/cosmic-applet-network/i18n/zh-CN/cosmic_applet_network.ftl index 24c9579d..dd9d51f2 100644 --- a/cosmic-applet-network/i18n/zh-CN/cosmic_applet_network.ftl +++ b/cosmic-applet-network/i18n/zh-CN/cosmic_applet_network.ftl @@ -1,7 +1,7 @@ network = 网络 airplane-mode = 飞行模式 airplane-mode-on = 飞行模式已开启 -turn-off-airplane-mode = 关闭飞行模式以启用 Wi-Fi、蓝牙和移动宽带。 +turn-off-airplane-mode = 关闭飞行模式即可启用 Wi-Fi、蓝牙和移动宽带。 wifi = Wi-Fi identity = 标识 ipv4 = IPv4 地址 @@ -15,8 +15,10 @@ cancel = 取消 settings = 网络设置... visible-wireless-networks = 可见的无线网络 enter-password = 输入密码或加密密钥 -router-wps-button = 您也可以通过按路由器上的“WPS”按钮连接 +router-wps-button = 您也可以通过路由器上的“WPS”按钮连接 unable-to-connect = 无法连接到网络 -check-wifi-connection = 确保 Wi-Fi 已连接到互联网并且密码正确 +check-wifi-connection = 确保 Wi-Fi 已连接互联网并且密码正确 reset = 重置 -vpn-connections = VPN连接 +vpn-connections = VPN 连接 +gigabits-per-second = Gbps +terabits-per-second = Tbps diff --git a/cosmic-applet-network/src/app.rs b/cosmic-applet-network/src/app.rs index 891e0a21..ae956bd1 100644 --- a/cosmic-applet-network/src/app.rs +++ b/cosmic-applet-network/src/app.rs @@ -1,8 +1,25 @@ +use anyhow::Context; +use cosmic_dbus_networkmanager::settings::{NetworkManagerSettings, connection::Settings}; +use cosmic_settings_network_manager_subscription::{ + self as network_manager, NetworkManagerState, UUID, + active_conns::active_conns_subscription, + available_wifi::{AccessPoint, NetworkType}, + current_networks::ActiveConnectionInfo, + hw_address::HwAddress, + nm_secret_agent::{self, PasswordFlag, SecretSender}, +}; +use indexmap::IndexMap; use rustc_hash::FxHashSet; -use std::sync::LazyLock; +use secure_string::SecureString; +use std::{ + borrow::Cow, + collections::{BTreeMap, BTreeSet}, + sync::{Arc, LazyLock}, + time::Duration, +}; use cosmic::{ - Element, Task, app, + Apply, Element, Task, app, applet::{ menu_button, menu_control_padding, padded_control, token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, @@ -19,30 +36,19 @@ use cosmic::{ widget::{ Column, Row, button, container, divider, icon::{self, from_name}, - scrollable, text, text_input, + scrollable, secure_input, text, text_input, }, }; -use cosmic_dbus_networkmanager::interface::enums::{ - ActiveConnectionState, DeviceState, NmConnectivityState, +use cosmic_dbus_networkmanager::interface::{ + access_point, + enums::{ActiveConnectionState, DeviceState, NmConnectivityState, NmState}, }; use cosmic_time::{Instant, Timeline, anim, chain, id}; -use futures::channel::mpsc::UnboundedSender; -use zbus::Connection; +use futures::{StreamExt, channel::mpsc::TrySendError}; +use zbus::{Connection, zvariant::ObjectPath}; -use crate::{ - config, fl, - network_manager::{ - NetworkManagerEvent, NetworkManagerRequest, NetworkManagerState, - active_conns::active_conns_subscription, - available_wifi::{AccessPoint, NetworkType}, - current_networks::ActiveConnectionInfo, - devices::devices_subscription, - hw_address::HwAddress, - network_manager_subscription, - wireless_enabled::wireless_enabled_subscription, - }, -}; +use crate::{config, fl}; pub fn run() -> cosmic::iced::Result { cosmic::applet::run::(()) @@ -52,8 +58,9 @@ pub fn run() -> cosmic::iced::Result { enum NewConnectionState { EnterPassword { access_point: AccessPoint, + description: Option, identity: String, - password: String, + password: SecureString, password_hidden: bool, }, Waiting(AccessPoint), @@ -92,14 +99,71 @@ impl From for AccessPoint { static WIFI: LazyLock = LazyLock::new(id::Toggler::unique); static AIRPLANE_MODE: LazyLock = LazyLock::new(id::Toggler::unique); +#[derive(Default, Debug, Clone)] +pub struct MyNetworkState { + pub known_vpns: IndexMap, + pub ssid_to_uuid: BTreeMap, Box>, + pub devices: Vec>, + pub password: Option, + pub connecting: BTreeSet, + pub nm_state: NetworkManagerState, + pub requested_vpn: Option, +} + +#[derive(Debug, Clone)] +pub struct RequestedVpn { + name: String, + uuid: Arc, + description: Option, + password: SecureString, + password_hidden: bool, + tx: SecretSender, +} + +#[derive(Clone, Debug)] +pub enum ConnectionSettings { + Vpn(VpnConnectionSettings), + Wireguard { id: String }, +} + +#[derive(Clone, Debug, Default)] +pub struct VpnConnectionSettings { + id: String, + username: Option, + connection_type: Option, + password_flag: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum ConnectionType { + Password, +} + +impl VpnConnectionSettings { + fn password_flag(&self) -> Option { + self.connection_type + .as_ref() + .is_some_and(|ct| match ct { + ConnectionType::Password => true, + }) + .then_some(self.password_flag) + .flatten() + } +} + #[derive(Default)] struct CosmicNetworkApplet { core: cosmic::app::Core, icon_name: String, popup: Option, - nm_state: NetworkManagerState, + + // NM state + nm_sender: Option>, + nm_task: Option>, + secret_tx: Option>, + nm_state: MyNetworkState, + // UI state - nm_sender: Option>, show_visible_networks: bool, show_available_vpns: bool, new_connection: Option, @@ -107,7 +171,7 @@ struct CosmicNetworkApplet { timeline: Timeline, toggle_wifi_ctr: u128, token_tx: Option>, - failed_known_ssids: FxHashSet, + failed_known_ssids: FxHashSet>, hw_device_to_show: Option, } @@ -124,14 +188,14 @@ fn wifi_icon(strength: u8) -> &'static str { } fn vpn_section<'a>( - nm_state: &'a NetworkManagerState, + nm_state: &'a MyNetworkState, show_available_vpns: bool, space_xxs: u16, space_s: u16, ) -> Column<'a, Message> { let mut vpn_col = column![]; - if !nm_state.available_vpns.is_empty() { + if !nm_state.known_vpns.is_empty() { let dropdown_icon = if show_available_vpns { "go-up-symbolic" } else { @@ -141,6 +205,43 @@ fn vpn_section<'a>( vpn_col = vpn_col .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])); + if let Some(requested_vpn) = nm_state.requested_vpn.as_ref() { + let column_content = vec![ + text::body( + requested_vpn + .description + .as_deref() + .unwrap_or(requested_vpn.uuid.as_ref()), + ) + .width(Length::Fill) + .into(), + secure_input( + "", + Cow::Borrowed(requested_vpn.password.unsecure()), + Some(Message::ToggleVPNPasswordVisibility), + requested_vpn.password_hidden, + ) + .on_input(|s| Message::VPNPasswordUpdate(s.into())) + .on_paste(|s| Message::VPNPasswordUpdate(s.into())) + .on_submit(|_| Message::ConnectVPNWithPassword) + .width(Length::Fill) + .into(), + row![ + button::standard(fl!("cancel")).on_press(Message::CancelVPNConnection), + button::suggested(fl!("connect")).on_press(Message::ConnectVPNWithPassword) + ] + .spacing(24) + .into(), + ]; + let col = padded_control( + Column::with_children(column_content) + .spacing(8) + .align_x(Alignment::Center), + ) + .align_x(Alignment::Center); + vpn_col = vpn_col.push(col); + } + let vpn_toggle_btn = menu_button(row![ text::body(fl!("vpn-connections")) .width(Length::Fill) @@ -154,18 +255,22 @@ fn vpn_section<'a>( vpn_col = vpn_col.push(vpn_toggle_btn); if show_available_vpns { - for vpn in &nm_state.available_vpns { + for (uuid, connection) in &nm_state.known_vpns { + let id = match connection { + ConnectionSettings::Vpn(connection) => connection.id.as_str(), + ConnectionSettings::Wireguard { id } => id.as_str(), + }; // Check if this VPN is currently active - let is_active = nm_state.active_conns.iter().any(|conn| { - matches!(conn, ActiveConnectionInfo::Vpn { name, .. } if name == &vpn.name) - }); + let is_active = nm_state.nm_state.active_conns.iter().any( + |conn| matches!(conn, ActiveConnectionInfo::Vpn { name, .. } if name == id), + ); let mut btn_content = vec![ icon::from_name("network-vpn-symbolic") .size(24) .symbolic(true) .into(), - text::body(&vpn.name).width(Length::Fill).into(), + text::body(id).width(Length::Fill).into(), ]; if is_active { @@ -179,9 +284,9 @@ fn vpn_section<'a>( ); btn = if is_active { - btn.on_press(Message::DeactivateVpn(vpn.name.clone())) + btn.on_press(Message::DeactivateVpn(uuid.clone())) } else { - btn.on_press(Message::ActivateVpn(vpn.uuid.clone())) + btn.on_press(Message::ActivateVpn(uuid.clone())) }; vpn_col = vpn_col.push(btn); @@ -202,7 +307,7 @@ impl CosmicNetworkApplet { }; if matches!(state, ActiveConnectionState::Activated) { - self.failed_known_ssids.remove(&new_s.name()); + self.failed_known_ssids.remove(new_s.name().as_str()); continue; } if matches!( @@ -212,7 +317,7 @@ impl CosmicNetworkApplet { continue; } - if self.nm_state.active_conns.iter().any(|old_s| { + if self.nm_state.nm_state.active_conns.iter().any(|old_s| { matches!( old_s, ActiveConnectionInfo::WiFi { @@ -221,15 +326,16 @@ impl CosmicNetworkApplet { } if new_s.name() == old_s.name() ) }) { - self.failed_known_ssids.insert(new_s.name()); + self.failed_known_ssids.insert(new_s.name().into()); } } - self.nm_state = new_state; + self.nm_state.nm_state = new_state; self.update_icon_name(); } fn update_icon_name(&mut self) { self.icon_name = self + .nm_state .nm_state .active_conns .iter() @@ -255,7 +361,8 @@ impl CosmicNetworkApplet { fn update_togglers(&mut self, state: &NetworkManagerState) { let timeline = &mut self.timeline; let mut changed = false; - if state.wifi_enabled != self.nm_state.wifi_enabled { + if self.nm_state.nm_state.wifi_enabled != state.wifi_enabled { + self.nm_state.nm_state.wifi_enabled = state.wifi_enabled; changed = true; let chain = if state.wifi_enabled { chain::Toggler::on(WIFI.clone(), 1.) @@ -265,7 +372,8 @@ impl CosmicNetworkApplet { timeline.set_chain(chain); } - if state.airplane_mode != self.nm_state.airplane_mode { + if self.nm_state.nm_state.airplane_mode != state.airplane_mode { + self.nm_state.nm_state.airplane_mode = state.airplane_mode; changed = true; let chain = if state.airplane_mode { chain::Toggler::on(AIRPLANE_MODE.clone(), 1.) @@ -292,23 +400,65 @@ impl CosmicNetworkApplet { .popup_container(content.padding([8, 0, 8, 0])) .into() } + + fn connect_vpn(&mut self, uuid: Arc) -> Task> { + if let Some((tx, conn)) = self.nm_sender.clone().zip(self.conn.clone()) { + cosmic::task::future(async move { + // Find the connection by UUID + if let Ok(nm_settings) = NetworkManagerSettings::new(&conn).await { + if let Ok(connections) = nm_settings.list_connections().await { + for connection in connections { + if let Ok(settings) = connection.get_settings().await { + let settings = Settings::new(settings); + if let Some(conn_settings) = &settings.connection { + if conn_settings.uuid.as_ref().is_some_and(|conn_uuid| { + conn_uuid.as_str() == uuid.as_ref() + }) { + let path = connection.inner().path().clone().to_owned(); + if let Err(err) = + tx.unbounded_send(network_manager::Request::Activate( + ObjectPath::try_from("/").unwrap(), + path, + )) + { + if err.is_disconnected() { + return zbus::Connection::system() + .await + .context( + "failed to create system dbus connection", + ) + .map_or_else( + |why| Message::Error(why.to_string()), + Message::NetworkManagerConnect, + ); + } + + tracing::error!("{err:?}"); + } + break; + } + } + } + } + } + } + Message::Refresh + }) + } else { + tracing::warn!("No sender available to activate VPN."); + Task::none() + } + } } #[derive(Debug, Clone)] pub(crate) enum Message { - ActivateKnownWifi(String, HwAddress), - Disconnect(String, HwAddress), TogglePopup, CloseRequested(window::Id), ToggleAirplaneMode(bool), - ToggleWiFi(bool), ToggleVisibleNetworks, - NetworkManagerEvent(NetworkManagerEvent), SelectWirelessAccessPoint(AccessPoint), CancelNewConnection, - Password(String), - Identity(String), - SubmitPassword, Frame(Instant), Token(TokenUpdate), OpenSettings, @@ -316,9 +466,293 @@ pub(crate) enum Message { OpenHwDevice(Option), TogglePasswordVisibility, Surface(surface::Action), - ActivateVpn(String), // UUID of VPN to activate - DeactivateVpn(String), // Name of VPN to deactivate - ToggleVpnList, // Show/hide available VPNs + ActivateVpn(Arc), // UUID of VPN to activate + DeactivateVpn(Arc), // UUID of VPN to deactivate + ToggleVpnList, // Show/hide available VPNs + /// An update from the secret agent + SecretAgent(network_manager::nm_secret_agent::Event), + /// Connect to a WiFi network access point. + Connect(network_manager::SSID, HwAddress), + /// Connect with a password + ConnectWithPassword, + KnownConnections(IndexMap), + /// Settings for known connections. + ConnectionSettings(BTreeMap, Box>), + /// Disconnect from an access point. + Disconnect(network_manager::SSID, HwAddress), + /// An error occurred. + Error(String), + /// Identity update from the dialog + IdentityUpdate(String), + /// An update from the network manager daemon + NetworkManager(network_manager::Event), + /// Successfully connected to the system dbus. + NetworkManagerConnect(zbus::Connection), + /// Update the password from the dialog + PasswordUpdate(SecureString), + /// Update NetworkManagerState + UpdateState(NetworkManagerState), + /// Update the devices lists + UpdateDevices(Vec), + /// Toggle WiFi access + WiFiEnable(bool), + /// Refresh state + Refresh, + ToggleVPNPasswordVisibility, + ConnectVPNWithPassword, + VPNPasswordUpdate(SecureString), + CancelVPNConnection, +} + +#[derive(Debug, Clone)] +struct Password { + ssid: network_manager::SSID, + hw_address: HwAddress, + identity: Option, + password: SecureString, + password_hidden: bool, + tx: SecretSender, +} + +fn connection_settings(conn: zbus::Connection) -> Task { + let settings = async move { + let settings = network_manager::dbus::settings::NetworkManagerSettings::new(&conn).await?; + + _ = settings.load_connections(&[]).await; + + let settings = settings + // Get a list of known connections. + .list_connections() + .await? + // Prepare for wrapping in a concurrent stream. + .into_iter() + .map(|conn| async move { conn }) + // Create a concurrent stream for each connection. + .apply(futures::stream::FuturesOrdered::from_iter) + // Concurrently fetch settings for each connection. + .filter_map(|conn| async move { + conn.get_settings() + .await + .map(network_manager::Settings::new) + .ok() + }) + // Reduce the settings list into a SSID->UUID map. + .fold(BTreeMap::new(), |mut set, settings| async move { + if let Some(ref wifi) = settings.wifi + && let Some(ssid) = wifi + .ssid + .clone() + .and_then(|ssid| String::from_utf8(ssid).ok()) + && let Some(ref connection) = settings.connection + && let Some(uuid) = connection.uuid.clone() + { + set.insert(ssid.into(), uuid.into()); + return set; + } + + set + }) + .await; + + Ok::<_, zbus::Error>(settings) + }; + + cosmic::task::future(async move { + settings + .await + .context("failed to get connection settings") + .map_or_else( + |why| Message::Error(why.to_string()), + Message::ConnectionSettings, + ) + }) +} + +pub fn update_state(conn: zbus::Connection) -> Task { + cosmic::task::future(async move { + match NetworkManagerState::new(&conn).await { + Ok(state) => Message::UpdateState(state), + Err(why) => Message::Error(why.to_string()), + } + }) +} + +pub fn update_devices(conn: zbus::Connection) -> Task { + cosmic::task::future(async move { + let filter = + |device_type| matches!(device_type, network_manager::devices::DeviceType::Wifi); + match network_manager::devices::list(&conn, filter).await { + Ok(devices) => Message::UpdateDevices(devices), + Err(why) => Message::Error(why.to_string()), + } + }) +} + +impl CosmicNetworkApplet { + fn connect(&mut self, conn: zbus::Connection) -> Task { + if self.nm_task.is_none() { + let popup = self.popup; + let (canceller, task) = crate::utils::forward_event_loop(move |emitter| async move { + let (tx, mut rx) = futures::channel::mpsc::channel(1); + + if popup.is_some() { + let watchers = std::pin::pin!(async move { + futures::join!( + network_manager::watch(conn.clone(), tx.clone()), + network_manager::active_conns::watch(conn.clone(), tx.clone(),), + network_manager::wireless_enabled::watch(conn.clone(), tx.clone()), + network_manager::watch_connections_changed(conn, tx,) + ); + }); + let forwarder = std::pin::pin!(async move { + while let Some(message) = rx.next().await { + _ = emitter.emit(Message::NetworkManager(message)).await; + } + }); + + futures::future::select(watchers, forwarder).await; + } else { + let watchers = std::pin::pin!(async move { + futures::join!( + network_manager::watch(conn.clone(), tx.clone()), + network_manager::active_conns::watch(conn.clone(), tx.clone(),), + network_manager::wireless_enabled::watch(conn.clone(), tx.clone()), + ); + }); + let forwarder = std::pin::pin!(async move { + while let Some(message) = rx.next().await { + _ = emitter.emit(Message::NetworkManager(message)).await; + } + }); + + futures::future::select(watchers, forwarder).await; + }; + }); + + self.nm_task = Some(canceller); + return task.map(Message::from); + } + + Task::none() + } +} + +fn load_vpns(conn: zbus::Connection) -> Task { + let settings = async move { + let settings = network_manager::dbus::settings::NetworkManagerSettings::new(&conn).await?; + + _ = settings.load_connections(&[]).await; + + let settings = settings + // Get a list of known connections. + .list_connections() + .await? + // Prepare for wrapping in a concurrent stream. + .into_iter() + .map(|conn| async move { conn }) + // Create a concurrent stream for each connection. + .apply(futures::stream::FuturesOrdered::from_iter) + // Concurrently fetch settings for each connection, and filter for VPN. + .filter_map(|conn| async move { + let settings = conn.get_settings().await.ok()?; + + let connection = settings.get("connection")?; + + match connection + .get("type")? + .downcast_ref::() + .ok()? + .as_str() + { + "vpn" => (), + + "wireguard" => { + let id = connection.get("id")?.downcast_ref::().ok()?; + let uuid = connection.get("uuid")?.downcast_ref::().ok()?; + return Some((Arc::from(uuid), ConnectionSettings::Wireguard { id })); + } + + _ => return None, + } + + let vpn = settings.get("vpn")?; + let id = connection.get("id")?.downcast_ref::().ok()?; + let uuid = connection.get("uuid")?.downcast_ref::().ok()?; + + let (connection_type, username, password_flag) = vpn + .get("data") + .and_then(|data| data.downcast_ref::().ok()) + .map(|dict| { + let (mut connection_type, mut password_flag) = (None, None); + let mut username = vpn + .get("user-name") + .and_then(|u| u.downcast_ref::().ok()); + if dict + .get::(&String::from("connection-type")) + .ok() + .flatten() + .as_deref() + // may be "password" or "password-tls" + .is_some_and(|p| p.starts_with("password")) + { + connection_type = Some(ConnectionType::Password); + username = Some(username.unwrap_or_default()); + + password_flag = dict + .get::(&String::from("password-flags")) + .ok() + .flatten() + .and_then(|value| match value.as_str() { + "0" => Some(PasswordFlag::None), + "1" => Some(PasswordFlag::AgentOwned), + "2" => Some(PasswordFlag::NotSaved), + "4" => Some(PasswordFlag::NotRequired), + _ => None, + }); + } + + (connection_type, username, password_flag) + }) + .unwrap_or_default(); + + Some(( + Arc::from(uuid), + ConnectionSettings::Vpn(VpnConnectionSettings { + id, + connection_type, + password_flag, + username, + }), + )) + }) + // Reduce the settings list into + .fold(IndexMap::new(), |mut set, (uuid, data)| async move { + set.insert(uuid, data); + set + }) + .await; + + Ok::<_, zbus::Error>(settings) + }; + + cosmic::task::future(async move { + settings.await.map_or_else( + |why| Message::Error(why.to_string()), + Message::KnownConnections, + ) + }) +} + +fn system_conn() -> Task { + cosmic::Task::future(async move { + zbus::Connection::system() + .await + .context("failed to create system dbus connection") + .map_or_else( + |why| Message::Error(why.to_string()), + Message::NetworkManagerConnect, + ) + }) } impl cosmic::Application for CosmicNetworkApplet { @@ -328,15 +762,14 @@ impl cosmic::Application for CosmicNetworkApplet { const APP_ID: &'static str = config::APP_ID; fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task) { - ( - Self { - core, - icon_name: "network-offline-symbolic".to_string(), - token_tx: None, - ..Default::default() - }, - Task::none(), - ) + let mut applet = Self { + core, + icon_name: "network-wired-disconnected-symbolic".to_string(), + token_tx: None, + ..Default::default() + }; + + (applet, system_conn().map(cosmic::Action::App)) } fn core(&self) -> &cosmic::app::Core { @@ -355,6 +788,25 @@ impl cosmic::Application for CosmicNetworkApplet { self.show_visible_networks = false; return destroy_popup(p); } else { + let mut tasks = Vec::with_capacity(2); + if let Some(conn) = self.conn.clone() { + tasks.push(update_state(conn.clone())); + tasks.push(update_devices(conn.clone())); + tasks.push(load_vpns(conn)); + let (tx, rx) = tokio::sync::mpsc::channel(4); + self.secret_tx = Some(tx); + let my_id = format!( + "com.system76.CosmicSettings.Applet.{}.NetworkManager.SecretAgent", + uuid::Uuid::new_v4() + ); + tasks.push( + cosmic::Task::stream(nm_secret_agent::secret_agent_stream( + my_id.clone(), + rx, + )) + .map(Message::SecretAgent), + ); + } // TODO request update of state maybe let new_id = window::Id::unique(); self.popup.replace(new_id); @@ -368,144 +820,74 @@ impl cosmic::Application for CosmicNetworkApplet { None, ); - if let Some(tx) = self.nm_sender.as_mut() { - let _ = tx.unbounded_send(NetworkManagerRequest::Reload); - } - return get_popup(popup_settings); + tasks.push(system_conn()); + tasks.push(get_popup(popup_settings)); + + return Task::batch(tasks).map(cosmic::Action::App); } } Message::ToggleAirplaneMode(enabled) => { self.toggle_wifi_ctr += 1; if let Some(tx) = self.nm_sender.as_mut() { - let _ = tx.unbounded_send(NetworkManagerRequest::SetAirplaneMode(enabled)); - } - } - Message::ToggleWiFi(enabled) => { - self.toggle_wifi_ctr += 1; - - if let Some(tx) = self.nm_sender.as_mut() { - let _ = tx.unbounded_send(NetworkManagerRequest::SetWiFi(enabled)); - } - } - Message::NetworkManagerEvent(event) => match event { - NetworkManagerEvent::Init { - conn, - sender, - state, - } => { - self.nm_sender.replace(sender); - self.update_nm_state(state); - self.conn = Some(conn); - } - NetworkManagerEvent::WiFiEnabled(state) - | NetworkManagerEvent::WirelessAccessPoints(state) - | NetworkManagerEvent::ActiveConns(state) => { - self.update_nm_state(state); - } - NetworkManagerEvent::RequestResponse { - mut state, - success, - req, - } => { - if let NetworkManagerRequest::SelectAccessPoint( - ssid, - hw_address, - _network_type, - ) = &req + if let Err(err) = + tx.unbounded_send(network_manager::Request::SetAirplaneMode(enabled)) { - let conn_match = self - .new_connection - .as_ref() - .is_some_and(|c| c.ssid() == ssid && c.hw_address() == *hw_address); - - if conn_match && success { - if let Some(ActiveConnectionInfo::WiFi { state, .. }) = state - .active_conns - .iter_mut() - .find(|ap| &ap.name() == ssid && ap.hw_address() == *hw_address) - { - *state = ActiveConnectionState::Activated; - } - self.failed_known_ssids.remove(ssid); - self.new_connection = None; - self.show_visible_networks = false; - } else if !matches!( - &self.new_connection, - Some(NewConnectionState::EnterPassword { .. }) - ) { - self.failed_known_ssids.insert(ssid.clone()); + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); } - } else if let NetworkManagerRequest::Authenticate { - ssid, - identity: _, - password: _, - hw_address, - } = &req - { - if let Some(NewConnectionState::Waiting(access_point)) = - self.new_connection.as_ref() - { - if !success - && ssid == &access_point.ssid - && *hw_address == access_point.hw_address - { - self.new_connection = - Some(NewConnectionState::Failure(access_point.clone())); - } else { - self.new_connection = None; - self.show_visible_networks = false; - } - } else if let Some(NewConnectionState::EnterPassword { - access_point, .. - }) = self.new_connection.as_ref() - { - if success && ssid == &access_point.ssid && *hw_address == access_point.hw_address { - self.new_connection = None; - self.show_visible_networks = false; - } - } - } else if self - .new_connection - .as_ref() - .map(NewConnectionState::ssid).is_some_and(|ssid| { - state.active_conns.iter().any(|c| - matches!(c, ActiveConnectionInfo::WiFi { name, state: ActiveConnectionState::Activated, .. } if ssid == name) - ) - }) { - self.new_connection = None; - self.show_visible_networks = false; - } - if !matches!(req, NetworkManagerRequest::Reload) - && matches!(state.connectivity, NmConnectivityState::Portal) - { - let mut browser = std::process::Command::new("xdg-open"); - browser.arg("http://204.pop-os.org/"); - - tokio::spawn(cosmic::process::spawn(browser)); + tracing::error!("{err:?}"); } - - self.update_nm_state(state); } - }, + } Message::SelectWirelessAccessPoint(access_point) => { let Some(tx) = self.nm_sender.as_ref() else { return Task::none(); }; - let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint( - access_point.ssid.clone(), - access_point.hw_address, - access_point.network_type, - )); - if matches!(access_point.network_type, NetworkType::Open) { + if let Err(err) = + tx.unbounded_send(network_manager::Request::SelectAccessPoint( + access_point.ssid.clone(), + access_point.hw_address, + access_point.network_type, + self.secret_tx.clone(), + )) + { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } self.new_connection = Some(NewConnectionState::Waiting(access_point)); } else { + if self + .nm_state + .nm_state + .known_access_points + .contains(&access_point) + { + if let Err(err) = + tx.unbounded_send(network_manager::Request::SelectAccessPoint( + access_point.ssid.clone(), + access_point.hw_address, + access_point.network_type, + self.secret_tx.clone(), + )) + { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } + } self.new_connection = Some(NewConnectionState::EnterPassword { access_point, + description: None, identity: String::new(), - password: String::new(), + password: String::new().into(), password_hidden: true, }); } @@ -514,13 +896,6 @@ impl cosmic::Application for CosmicNetworkApplet { self.new_connection = None; self.show_visible_networks = !self.show_visible_networks; } - Message::Password(entered_pw) => { - if let Some(NewConnectionState::EnterPassword { password, .. }) = - &mut self.new_connection - { - *password = entered_pw; - } - } Message::TogglePasswordVisibility => { if let Some(NewConnectionState::EnterPassword { password_hidden, .. @@ -529,77 +904,18 @@ impl cosmic::Application for CosmicNetworkApplet { *password_hidden = !*password_hidden; } } - Message::SubmitPassword => { - // save password - let Some(tx) = self.nm_sender.as_ref() else { - return Task::none(); - }; - - if let Some(NewConnectionState::EnterPassword { - password, - access_point, - identity, - .. - }) = self.new_connection.take() - { - let is_enterprise: bool = matches!(access_point.network_type, NetworkType::EAP); - - let _ = tx.unbounded_send(NetworkManagerRequest::Authenticate { - ssid: access_point.ssid.clone(), - identity: is_enterprise.then(|| identity.clone()), - password, - hw_address: access_point.hw_address, - }); - self.new_connection - .replace(NewConnectionState::Waiting(access_point)); - } - } - Message::ActivateKnownWifi(ssid, hw_address) => { - let mut network_type = NetworkType::Open; - let tx = if let Some(tx) = self.nm_sender.as_ref() { - if let Some(ap) = self - .nm_state - .known_access_points - .iter_mut() - .find(|c| c.ssid == ssid && c.hw_address == hw_address) - { - network_type = ap.network_type; - ap.working = true; - } - tx - } else { - return Task::none(); - }; - let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint( - ssid, - hw_address, - network_type, - )); - } Message::CancelNewConnection => { self.new_connection = None; } - Message::Disconnect(ssid, hw_address) => { - self.new_connection = None; - let tx = if let Some(tx) = self.nm_sender.as_ref() { - if let Some(ActiveConnectionInfo::WiFi { state, .. }) = self - .nm_state - .active_conns - .iter_mut() - .find(|c| c.name() == ssid && c.hw_address() == hw_address) - { - *state = ActiveConnectionState::Deactivating; - } - tx - } else { - return Task::none(); - }; - let _ = tx.unbounded_send(NetworkManagerRequest::Disconnect(ssid, hw_address)); - } Message::CloseRequested(id) => { - self.hw_device_to_show = None; if Some(id) == self.popup { self.popup = None; + if let Some(cancel) = self.nm_task.take() { + _ = cancel.send(()); + } + + self.secret_tx = None; + return system_conn().map(cosmic::Action::App); } } Message::OpenSettings => { @@ -631,33 +947,47 @@ impl cosmic::Application for CosmicNetworkApplet { Message::OpenHwDevice(hw_address) => self.hw_device_to_show = hw_address, Message::ResetFailedKnownSsid(ssid, hw_address) => { let ap = if let Some(pos) = self + .nm_state .nm_state .known_access_points .iter() - .position(|ap| ap.ssid == ssid && ap.hw_address == hw_address) + .position(|ap| ap.ssid.as_ref() == ssid.as_str() && ap.hw_address == hw_address) { - self.nm_state.known_access_points.remove(pos) + self.nm_state.nm_state.known_access_points.remove(pos) } else if let Some((pos, ap)) = self + .nm_state .nm_state .active_conns .iter() - .position(|conn| conn.name() == ssid && conn.hw_address() == hw_address) + .position(|conn| { + conn.name() == ssid && active_conn_hw_address(conn) == hw_address + }) .zip( self.nm_state + .nm_state .wireless_access_points .iter() - .find(|ap| ap.ssid == ssid && ap.hw_address == hw_address), + .find(|ap| { + ap.ssid.as_ref() == ssid.as_str() && ap.hw_address == hw_address + }), ) { - self.nm_state.active_conns.remove(pos); + self.nm_state.nm_state.active_conns.remove(pos); ap.clone() } else { tracing::warn!("Failed to find known access point with ssid: {}", ssid); return Task::none(); }; if let Some(tx) = self.nm_sender.as_ref() { - let _ = - tx.unbounded_send(NetworkManagerRequest::Forget(ssid.clone(), hw_address)); + if let Err(err) = + tx.unbounded_send(network_manager::Request::Forget(ssid.into())) + { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } self.show_visible_networks = true; return self.update(Message::SelectWirelessAccessPoint(ap)); } @@ -667,25 +997,384 @@ impl cosmic::Application for CosmicNetworkApplet { cosmic::app::Action::Surface(a), )); } - Message::Identity(new_identity) => { + Message::ActivateVpn(uuid) => { + return self.connect_vpn(uuid.clone()); + } + Message::DeactivateVpn(name) => { + if let Some(tx) = self.nm_sender.as_ref() { + if let Err(err) = tx.unbounded_send(network_manager::Request::Deactivate(name)) + { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } + } + } + Message::ToggleVpnList => { + self.show_available_vpns = !self.show_available_vpns; + } + Message::Connect(ssid, hw_address) => { + let mut network_type = NetworkType::Open; + let tx = if let Some(tx) = self.nm_sender.as_ref() { + if let Some(ap) = self + .nm_state + .nm_state + .known_access_points + .iter_mut() + .find(|c| c.ssid == ssid && c.hw_address == hw_address) + { + network_type = ap.network_type; + ap.working = true; + } + tx + } else { + return Task::none(); + }; + if let Err(err) = tx.unbounded_send(network_manager::Request::SelectAccessPoint( + ssid, + hw_address, + network_type, + self.secret_tx.clone(), + )) { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } + } + Message::ConnectWithPassword => { + // save password + let Some(tx) = self.nm_sender.as_ref() else { + return Task::none(); + }; + + if let Some(NewConnectionState::EnterPassword { + password, + access_point, + identity, + .. + }) = self.new_connection.take() + { + let is_enterprise: bool = matches!(access_point.network_type, NetworkType::EAP); + + if let Err(err) = tx.unbounded_send(network_manager::Request::Authenticate { + ssid: access_point.ssid.to_string(), + identity: is_enterprise.then(|| identity.clone()), + password, + hw_address: access_point.hw_address, + secret_tx: self.secret_tx.clone(), + }) { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + tracing::error!("Failed to authenticate with network manager"); + } + self.new_connection + .replace(NewConnectionState::Waiting(access_point)); + } + } + Message::ConnectionSettings(btree_map) => { + self.nm_state.ssid_to_uuid = btree_map; + } + Message::Disconnect(ssid, hw_address) => { + self.new_connection = None; + let tx = if let Some(tx) = self.nm_sender.as_ref() { + if let Some(ActiveConnectionInfo::WiFi { state, .. }) = + self.nm_state.nm_state.active_conns.iter_mut().find(|c| { + let c_hw_address = match c { + ActiveConnectionInfo::Wired { hw_address, .. } + | ActiveConnectionInfo::WiFi { hw_address, .. } => { + HwAddress::from_str(hw_address).unwrap() + } + ActiveConnectionInfo::Vpn { .. } => HwAddress::default(), + }; + c.name().as_str() == ssid.as_ref() && c_hw_address == hw_address + }) + { + *state = ActiveConnectionState::Deactivating; + } + tx + } else { + return Task::none(); + }; + if let Err(err) = tx.unbounded_send(network_manager::Request::Disconnect(ssid)) { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } + } + Message::Error(error) => { + tracing::error!("error: {error:?}") + } + Message::IdentityUpdate(new_identity) => { if let Some(NewConnectionState::EnterPassword { identity, .. }) = &mut self.new_connection { *identity = new_identity; } } - Message::ActivateVpn(uuid) => { - if let Some(tx) = self.nm_sender.as_ref() { - let _ = tx.unbounded_send(NetworkManagerRequest::ActivateVpn(uuid)); + Message::NetworkManager(event) => match event { + network_manager::Event::Init { + conn, + sender, + state, + } => { + self.nm_sender = Some(sender); + self.update_nm_state(state); + self.conn = Some(conn); + } + network_manager::Event::WiFiEnabled(_) + | network_manager::Event::WirelessAccessPoints + | network_manager::Event::ActiveConns => { + if let Some(conn) = self.conn.clone() { + return Task::future(async move { + let conn = conn.clone(); + NetworkManagerState::new(&conn).await + }) + .map(|res| match res { + Ok(s) => Message::UpdateState(s), + Err(err) => Message::Error(err.to_string()), + }) + .map(cosmic::Action::App); + } + } + network_manager::Event::RequestResponse { + mut state, + success, + req, + } => { + if let network_manager::Request::SelectAccessPoint( + ssid, + hw_address, + _network_type, + secret_tx, + ) = &req + { + let conn_match = self + .new_connection + .as_ref() + .is_some_and(|c| c.ssid() == ssid.as_ref() && c.hw_address() == *hw_address); + + if conn_match && success { + if let Some(ActiveConnectionInfo::WiFi { state, .. }) = state + .active_conns + .iter_mut() + .find(|ap| { + let ap_hw_address = match ap { + ActiveConnectionInfo::Wired { hw_address, .. } + | ActiveConnectionInfo::WiFi { hw_address, .. } => { + HwAddress::from_str(&hw_address).unwrap() + } + ActiveConnectionInfo::Vpn { .. } => HwAddress::default(), + }; + ap.name().as_str() == ssid.as_ref() && ap_hw_address == *hw_address}) + { + *state = ActiveConnectionState::Activated; + } + self.failed_known_ssids.remove(ssid); + self.new_connection = None; + self.show_visible_networks = false; + } else if !matches!( + &self.new_connection, + Some(NewConnectionState::EnterPassword { .. }) + ) && !success { + self.failed_known_ssids.insert(ssid.clone()); + } + } else if let network_manager::Request::Authenticate { + ssid, + identity: _, + password: _, + hw_address, + secret_tx + } = &req + { + if let Some(NewConnectionState::Waiting(access_point)) = + self.new_connection.as_ref() + { + if !success + && ssid.as_str() == access_point.ssid.as_ref() + && *hw_address == access_point.hw_address + { + self.new_connection = + Some(NewConnectionState::Failure(access_point.clone())); + } else { + self.show_visible_networks = false; + } + } else if let Some(NewConnectionState::EnterPassword { + access_point, .. + }) = self.new_connection.as_ref() + { + if success && ssid.as_str() == access_point.ssid.as_ref() && *hw_address == access_point.hw_address { + self.new_connection = None; + self.show_visible_networks = false; + } + } + } else if self + .new_connection + .as_ref() + .map(NewConnectionState::ssid).is_some_and(|ssid| { + state.active_conns.iter().any(|c| + matches!(c, ActiveConnectionInfo::WiFi { name, state: ActiveConnectionState::Activated, .. } if ssid == name) + ) + }) { + self.new_connection = None; + self.show_visible_networks = false; + } + + if !matches!(req, network_manager::Request::Reload) + && matches!(state.connectivity, NmConnectivityState::Portal) + { + let mut browser = std::process::Command::new("xdg-open"); + browser.arg("http://204.pop-os.org/"); + + tokio::spawn(cosmic::process::spawn(browser)); + } + + self.update_nm_state(state); + } + + cosmic_settings_network_manager_subscription::Event::Devices => { + if let Some(conn) = self.conn.clone() { + return update_devices(conn).map(cosmic::Action::App); + } + } + cosmic_settings_network_manager_subscription::Event::WiFiCredentials { + ssid, + password, + security_type, + } => {} + }, + Message::NetworkManagerConnect(connection) => { + return cosmic::task::batch(vec![ + self.connect(connection.clone()), + connection_settings(connection), + ]); + } + Message::PasswordUpdate(entered_pw) => { + if let Some(NewConnectionState::EnterPassword { password, .. }) = + &mut self.new_connection + { + *password = entered_pw; } } - Message::DeactivateVpn(name) => { - if let Some(tx) = self.nm_sender.as_ref() { - let _ = tx.unbounded_send(NetworkManagerRequest::DeactivateVpn(name)); + Message::UpdateState(network_manager_state) => { + self.update_nm_state(network_manager_state); + } + Message::UpdateDevices(device_infos) => { + self.nm_state.devices = device_infos.into_iter().map(Arc::new).collect(); + } + Message::WiFiEnable(enable) => { + if let Some(sender) = self.nm_sender.as_mut() { + if let Err(err) = + sender.unbounded_send(network_manager::Request::SetWiFi(enable)) + { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } + if let Err(err) = sender.unbounded_send(network_manager::Request::Reload) { + if err.is_disconnected() { + return system_conn().map(cosmic::Action::App); + } + + tracing::error!("{err:?}"); + } } } - Message::ToggleVpnList => { - self.show_available_vpns = !self.show_available_vpns; + Message::SecretAgent(agent_event) => match agent_event { + nm_secret_agent::Event::RequestSecret { + uuid, + name, + description, + previous, + tx, + .. + } => { + if let Some(state) = self.new_connection.as_mut() { + match state { + NewConnectionState::EnterPassword { access_point, .. } + | NewConnectionState::Waiting(access_point) + | NewConnectionState::Failure(access_point) => { + if self + .nm_state + .ssid_to_uuid + .get(access_point.ssid.as_ref()) + .is_some_and(|ap_uuid| ap_uuid.as_ref() == uuid.as_str()) + { + *state = NewConnectionState::EnterPassword { + access_point: access_point.clone(), + description, + identity: String::new(), + password: String::new().into(), + password_hidden: true, + } + } + } + } + } else if self.nm_state.known_vpns.contains_key(uuid.as_str()) { + self.nm_state.requested_vpn = Some(RequestedVpn { + name, + uuid: uuid.into(), + description, + password: previous, + password_hidden: true, + tx, + }); + } + } + nm_secret_agent::Event::CancelGetSecrets { .. } => { + self.new_connection = None; + self.nm_state.requested_vpn = None; + } + nm_secret_agent::Event::Failed(error) => { + tracing::error!("Error from secret agent: {error:?}"); + } + }, + Message::KnownConnections(index_map) => { + self.nm_state.known_vpns = index_map; + } + Message::Refresh => { + if let Some(conn) = self.conn.clone() { + return Task::batch(vec![ + update_state(conn.clone()), + update_devices(conn.clone()), + load_vpns(conn), + ]) + .map(cosmic::Action::App); + } + } + Message::ToggleVPNPasswordVisibility => { + if let Some(requested_vpn) = self.nm_state.requested_vpn.as_mut() { + requested_vpn.password_hidden = !requested_vpn.password_hidden; + } + } + Message::ConnectVPNWithPassword => { + if let Some(RequestedVpn { password, tx, .. }) = self.nm_state.requested_vpn.take() + { + return Task::future(async move { + let mut guard = tx.lock().await; + if let Some(tx) = guard.take() { + let _ = tx.send(password); + } + Message::Refresh + }) + .map(cosmic::Action::App); + } + } + Message::VPNPasswordUpdate(secure_string) => { + if let Some(requested_vpn) = self.nm_state.requested_vpn.as_mut() { + requested_vpn.password = secure_string; + } + } + Message::CancelVPNConnection => { + self.nm_state.requested_vpn = None; } } Task::none() @@ -706,7 +1395,7 @@ impl cosmic::Application for CosmicNetworkApplet { let mut vpn_ethernet_col = column![]; let mut known_wifi = Vec::new(); - for conn in &self.nm_state.active_conns { + for conn in &self.nm_state.nm_state.active_conns { match conn { ActiveConnectionInfo::Vpn { name, ip_addresses } => { if self.hw_device_to_show.is_some() { @@ -744,7 +1433,7 @@ impl cosmic::Application for CosmicNetworkApplet { ip_addresses, } => { if self.hw_device_to_show.is_some() - && *hw_address != self.hw_device_to_show.unwrap() + && HwAddress::from_str(&hw_address) != self.hw_device_to_show { continue; } @@ -754,6 +1443,30 @@ impl cosmic::Application for CosmicNetworkApplet { ipv4.push(text(format!("{}: {}", fl!("ipv4"), addr)).size(12).into()); } + let mut right_column = vec![text::body(fl!("connected")).into()]; + + // Only show speed if it's greater than 0 + if *speed > 0 { + let speed_text = if *speed >= 1_000_000 { + let tbps = *speed as f64 / 1_000_000.0; + if tbps.fract() == 0.0 { + format!("{} {}", tbps as u32, fl!("terabits-per-second")) + } else { + format!("{:.1} {}", tbps, fl!("terabits-per-second")) + } + } else if *speed >= 1_000 { + let gbps = *speed as f64 / 1_000.0; + if gbps.fract() == 0.0 { + format!("{} {}", gbps as u32, fl!("gigabits-per-second")) + } else { + format!("{:.1} {}", gbps, fl!("gigabits-per-second")) + } + } else { + format!("{speed} {}", fl!("megabits-per-second")) + }; + right_column.push(text(speed_text).size(12).into()); + } + vpn_ethernet_col = vpn_ethernet_col.push(column![ row![ icon::icon( @@ -763,13 +1476,9 @@ impl cosmic::Application for CosmicNetworkApplet { ) .size(40), Column::with_children(ipv4), - text::body(format!( - "{} - {speed} {}", - fl!("connected"), - fl!("megabits-per-second") - )) - .width(Length::Fill) - .align_x(Alignment::End), + Column::with_children(right_column) + .width(Length::Fill) + .align_x(Alignment::End), ] .align_y(Alignment::Center) .spacing(8) @@ -786,7 +1495,7 @@ impl cosmic::Application for CosmicNetworkApplet { hw_address, } => { if self.hw_device_to_show.is_some() - && hw_address != self.hw_device_to_show.as_ref().unwrap() + && HwAddress::from_str(&hw_address) != self.hw_device_to_show { continue; } @@ -820,13 +1529,16 @@ impl cosmic::Application for CosmicNetworkApplet { ), _ => {} } - if self.failed_known_ssids.contains(name) { + if self.failed_known_ssids.contains(name.as_str()) { btn_content.push( cosmic::widget::button::icon( from_name("view-refresh-symbolic").size(16), ) .icon_size(16) - .on_press(Message::ResetFailedKnownSsid(name.clone(), *hw_address)) + .on_press(Message::ResetFailedKnownSsid( + name.clone(), + HwAddress::from_str(&hw_address).unwrap(), + )) .into(), ); } @@ -838,7 +1550,10 @@ impl cosmic::Application for CosmicNetworkApplet { .align_y(Alignment::Center) .spacing(8) ) - .on_press(Message::Disconnect(name.clone(), *hw_address)) + .on_press(Message::Disconnect( + Arc::from(name.as_str()), + HwAddress::from_str(&hw_address).unwrap() + )) ] .align_x(Alignment::Center), )); @@ -878,7 +1593,7 @@ impl cosmic::Application for CosmicNetworkApplet { AIRPLANE_MODE, &self.timeline, fl!("airplane-mode"), - self.nm_state.airplane_mode, + self.nm_state.nm_state.airplane_mode, |_chain, enable| { Message::ToggleAirplaneMode(enable) }, ) .text_size(14) @@ -895,8 +1610,8 @@ impl cosmic::Application for CosmicNetworkApplet { WIFI, &self.timeline, fl!("wifi"), - self.nm_state.wifi_enabled, - |_chain, enable| { Message::ToggleWiFi(enable) }, + self.nm_state.nm_state.wifi_enabled, + |_chain, enable| { Message::WiFiEnable(enable) }, ) .text_size(14) .width(Length::Fill) @@ -904,8 +1619,7 @@ impl cosmic::Application for CosmicNetworkApplet { ] .align_x(Alignment::Center) }; - - if self.nm_state.airplane_mode { + if self.nm_state.nm_state.airplane_mode { content = content.push( column!( padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), @@ -920,10 +1634,29 @@ impl cosmic::Application for CosmicNetworkApplet { .align_x(Alignment::Center) .width(Length::Fill), ); + + // Show VPN connections even in airplane mode + if !self.nm_state.known_vpns.is_empty() { + content = content.push(vpn_section( + &self.nm_state, + self.show_available_vpns, + space_xxs, + space_s, + )); + } + return self.view_window_return(content); } - if !self.nm_state.wifi_enabled { + if !self.nm_state.nm_state.wifi_enabled && !self.nm_state.known_vpns.is_empty() { + // Add VPN connections section when WiFi is disabled + content = content.push(vpn_section( + &self.nm_state, + self.show_available_vpns, + space_xxs, + space_s, + )); + return self.view_window_return(content); } @@ -931,6 +1664,7 @@ impl cosmic::Application for CosmicNetworkApplet { .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])); let wireless_hw_devices = self + .nm_state .nm_state .wireless_access_points .iter() @@ -941,11 +1675,10 @@ impl cosmic::Application for CosmicNetworkApplet { for hw_device in wireless_hw_devices { let display_name = hw_device.to_string(); - let is_connected = self - .nm_state - .active_conns - .iter() - .any(|conn| conn.hw_address() == hw_device); + let is_connected = self.nm_state.nm_state.active_conns.iter().any(|conn| { + let hw_address = active_conn_hw_address(conn); + hw_address == hw_device + }); let mut btn_content = vec![ column![ text::body(display_name), @@ -981,14 +1714,14 @@ impl cosmic::Application for CosmicNetworkApplet { return self.view_window_return(content); } - for known in &self.nm_state.known_access_points { + for known in &self.nm_state.nm_state.known_access_points { if let Some(filter_hw_address) = self.hw_device_to_show { if filter_hw_address != known.hw_address { continue; } } let mut btn_content = Vec::with_capacity(2); - let ssid = text::body(&known.ssid).width(Length::Fill); + let ssid = text::body(known.ssid.as_ref()).width(Length::Fill); if known.working { btn_content.push( icon::from_name("network-wireless-acquiring-symbolic") @@ -1021,12 +1754,12 @@ impl cosmic::Application for CosmicNetworkApplet { btn_content.push(ssid.into()); } - if self.failed_known_ssids.contains(&known.ssid) { + if self.failed_known_ssids.contains(known.ssid.as_ref()) { btn_content.push( cosmic::widget::button::icon(from_name("view-refresh-symbolic").size(16)) .icon_size(16) .on_press(Message::ResetFailedKnownSsid( - known.ssid.clone(), + known.ssid.to_string(), known.hw_address, )) .into(), @@ -1043,10 +1776,9 @@ impl cosmic::Application for CosmicNetworkApplet { | DeviceState::Unknown | DeviceState::Unmanaged | DeviceState::Disconnected - | DeviceState::NeedAuth => btn.on_press(Message::ActivateKnownWifi( - known.ssid.clone(), - known.hw_address, - )), + | DeviceState::NeedAuth => { + btn.on_press(Message::Connect(known.ssid.clone(), known.hw_address)) + } DeviceState::Activated => { btn.on_press(Message::Disconnect(known.ssid.clone(), known.hw_address)) } @@ -1078,7 +1810,7 @@ impl cosmic::Application for CosmicNetworkApplet { content = content.push(available_connections_btn); if !self.show_visible_networks { - if !self.nm_state.available_vpns.is_empty() { + if !self.nm_state.known_vpns.is_empty() { content = content.push(vpn_section( &self.nm_state, self.show_available_vpns, @@ -1093,6 +1825,7 @@ impl cosmic::Application for CosmicNetworkApplet { match new_conn_state { NewConnectionState::EnterPassword { access_point, + description, identity, password, password_hidden, @@ -1102,7 +1835,7 @@ impl cosmic::Application for CosmicNetworkApplet { icon::from_name("network-wireless-acquiring-symbolic") .size(24) .symbolic(true), - text::body(&access_point.ssid), + text::body(access_point.ssid.as_ref()), ] .align_y(Alignment::Center) .spacing(12), @@ -1110,36 +1843,38 @@ impl cosmic::Application for CosmicNetworkApplet { content = content.push(id); let is_enterprise = matches!(access_point.network_type, NetworkType::EAP); - let enter_password_col = column![] - .push_maybe(is_enterprise.then(|| text::body(fl!("identity")))) - .push_maybe(is_enterprise.then(|| { - text_input::text_input("", identity).on_input(Message::Identity) - })) - .push(text::body(fl!("enter-password"))) - .push( - text_input::secure_input( - "", - password, - Some(Message::TogglePasswordVisibility), - *password_hidden, + let enter_password_col = + column![] + .push_maybe(is_enterprise.then(|| text::body(fl!("identity")))) + .push_maybe(is_enterprise.then(|| { + text_input::text_input("", identity) + .on_input(|i| Message::IdentityUpdate(i)) + })) + .push(text::body(fl!("enter-password"))) + .push_maybe(description.as_ref().map(|d| text::body(d.clone()))) + .push( + text_input::secure_input( + "", + password.unsecure(), + Some(Message::TogglePasswordVisibility), + *password_hidden, + ) + .on_input(|s| Message::PasswordUpdate(SecureString::from(s))) + .on_paste(|s| Message::PasswordUpdate(SecureString::from(s))) + .on_submit(|_| Message::ConnectWithPassword), ) - .on_input(Message::Password) - .on_paste(Message::Password) - .on_submit(|_| Message::SubmitPassword), - ) - .push_maybe( - access_point.wps_push.then(|| { + .push_maybe(access_point.wps_push.then(|| { container(text::body(fl!("router-wps-button"))).padding(8) - }), - ) - .push( - row![ - button::standard(fl!("cancel")) - .on_press(Message::CancelNewConnection), - button::suggested(fl!("connect")).on_press(Message::SubmitPassword) - ] - .spacing(24), - ); + })) + .push( + row![ + button::standard(fl!("cancel")) + .on_press(Message::CancelNewConnection), + button::suggested(fl!("connect")) + .on_press(Message::ConnectWithPassword) + ] + .spacing(24), + ); let col = padded_control(enter_password_col.spacing(8).align_x(Alignment::Center)) .align_x(Alignment::Center); @@ -1150,7 +1885,7 @@ impl cosmic::Application for CosmicNetworkApplet { icon::from_name("network-wireless-acquiring-symbolic") .size(24) .symbolic(true), - text::body(&access_point.ssid), + text::body(access_point.ssid.as_ref()), ] .align_y(Alignment::Center) .width(Length::Fill) @@ -1172,7 +1907,7 @@ impl cosmic::Application for CosmicNetworkApplet { icon::from_name("network-wireless-error-symbolic") .size(24) .symbolic(true), - text::body(&access_point.ssid), + text::body(access_point.ssid.as_ref()), ] .align_y(Alignment::Center) .spacing(12), @@ -1200,17 +1935,17 @@ impl cosmic::Application for CosmicNetworkApplet { } } } else { - let mut list_col = Vec::with_capacity(self.nm_state.wireless_access_points.len()); - for ap in &self.nm_state.wireless_access_points { + let mut list_col = + Vec::with_capacity(self.nm_state.nm_state.wireless_access_points.len()); + for ap in &self.nm_state.nm_state.wireless_access_points { if ap.hw_address != self.hw_device_to_show.unwrap_or(ap.hw_address) { continue; } - if self - .nm_state - .active_conns - .iter() - .any(|a| ap.ssid == a.name() && ap.hw_address == a.hw_address()) - { + + if self.nm_state.nm_state.active_conns.iter().any(|a| { + let hw_address = active_conn_hw_address(a); + ap.ssid.as_ref() == &a.name() && ap.hw_address == hw_address + }) { continue; } let button = menu_button( @@ -1218,7 +1953,7 @@ impl cosmic::Application for CosmicNetworkApplet { icon::from_name(wifi_icon(ap.strength)) .size(16) .symbolic(true), - text::body(&ap.ssid).align_y(Alignment::Center) + text::body(ap.ssid.as_ref()).align_y(Alignment::Center) ] .align_y(Alignment::Center) .spacing(12), @@ -1231,7 +1966,7 @@ impl cosmic::Application for CosmicNetworkApplet { } // Add VPN connections section after wireless networks when they are expanded - if !self.nm_state.available_vpns.is_empty() { + if !self.nm_state.known_vpns.is_empty() && self.nm_state.nm_state.wifi_enabled { content = content.push(vpn_section( &self.nm_state, self.show_available_vpns, @@ -1244,29 +1979,13 @@ impl cosmic::Application for CosmicNetworkApplet { } fn subscription(&self) -> Subscription { - let network_sub = network_manager_subscription(0).map(Message::NetworkManagerEvent); let timeline = self .timeline .as_subscription() .map(|(_, now)| Message::Frame(now)); let token_sub = activation_token_subscription(0).map(Message::Token); - if let Some(conn) = self.conn.as_ref() { - let has_popup = self.popup.is_some(); - Subscription::batch([ - timeline, - network_sub, - token_sub, - active_conns_subscription(self.toggle_wifi_ctr, conn.clone()) - .map(Message::NetworkManagerEvent), - devices_subscription(self.toggle_wifi_ctr, has_popup, conn.clone()) - .map(Message::NetworkManagerEvent), - wireless_enabled_subscription(self.toggle_wifi_ctr, conn.clone()) - .map(Message::NetworkManagerEvent), - ]) - } else { - Subscription::batch([timeline, network_sub, token_sub]) - } + Subscription::batch([timeline, token_sub]) } fn style(&self) -> Option { @@ -1277,3 +1996,11 @@ impl cosmic::Application for CosmicNetworkApplet { Some(Message::CloseRequested(id)) } } + +fn active_conn_hw_address(conn: &ActiveConnectionInfo) -> HwAddress { + match conn { + ActiveConnectionInfo::Wired { hw_address, .. } + | ActiveConnectionInfo::WiFi { hw_address, .. } => HwAddress::from_str(hw_address).unwrap(), + ActiveConnectionInfo::Vpn { .. } => HwAddress::default(), + } +} diff --git a/cosmic-applet-network/src/lib.rs b/cosmic-applet-network/src/lib.rs index 1a664d6f..7d5414a5 100644 --- a/cosmic-applet-network/src/lib.rs +++ b/cosmic-applet-network/src/lib.rs @@ -3,7 +3,7 @@ mod app; mod config; mod localize; -mod network_manager; +mod utils; use crate::localize::localize; diff --git a/cosmic-applet-network/src/network_manager/active_conns.rs b/cosmic-applet-network/src/network_manager/active_conns.rs deleted file mode 100644 index e4fa7140..00000000 --- a/cosmic-applet-network/src/network_manager/active_conns.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::{NetworkManagerEvent, NetworkManagerState}; -use cosmic::{ - iced::{self, Subscription}, - iced_futures::stream, -}; -use cosmic_dbus_networkmanager::nm::NetworkManager; -use futures::{SinkExt, StreamExt}; -use std::{fmt::Debug, hash::Hash}; -use zbus::Connection; - -pub fn active_conns_subscription( - id: I, - conn: Connection, -) -> iced::Subscription { - let initial = State::Continue(conn); - Subscription::run_with_id( - id, - stream::channel(50, move |mut output| { - let mut state = initial; - - async move { - loop { - state = start_listening(state, &mut output).await; - } - } - }), - ) -} - -#[derive(Debug, Clone)] -pub enum State { - Continue(Connection), - Error, -} - -async fn start_listening( - state: State, - output: &mut futures::channel::mpsc::Sender, -) -> State { - let conn = match state { - State::Continue(conn) => conn, - State::Error => iced::futures::future::pending().await, - }; - let network_manager = match NetworkManager::new(&conn).await { - Ok(n) => n, - Err(why) => { - tracing::error!(why = why.to_string(), "Failed to connect to NetworkManager"); - return State::Error; - } - }; - - let mut active_conns_changed = network_manager.receive_active_connections_changed().await; - active_conns_changed.next().await; - - while let (Some(_change), ()) = tokio::join!( - active_conns_changed.next(), - tokio::time::sleep(tokio::time::Duration::from_secs(1)) - ) { - let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::ActiveConns(new_state)) - .await; - } - - State::Continue(conn) -} diff --git a/cosmic-applet-network/src/network_manager/available_vpns.rs b/cosmic-applet-network/src/network_manager/available_vpns.rs deleted file mode 100644 index ae9a467e..00000000 --- a/cosmic-applet-network/src/network_manager/available_vpns.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -use cosmic_dbus_networkmanager::settings::{NetworkManagerSettings, connection::Settings}; -use zbus::Connection; - -#[derive(Debug, Clone)] -pub struct VpnConnection { - pub name: String, - pub uuid: String, -} - -/// Load all available VPN connections from NetworkManager settings -pub async fn load_vpn_connections(conn: &Connection) -> anyhow::Result> { - let nm_settings = NetworkManagerSettings::new(conn).await?; - let connections = nm_settings.list_connections().await?; - - let mut vpn_connections = Vec::new(); - - for connection in connections { - let settings_map = match connection.get_settings().await { - Ok(s) => s, - Err(_) => continue, - }; - - let settings = Settings::new(settings_map); - - // Check if this is a VPN connection - if let Some(connection_settings) = &settings.connection { - if let Some(conn_type) = &connection_settings.type_ { - // VPN connections have type "vpn" or "wireguard" - if conn_type == "vpn" || conn_type == "wireguard" { - let name = connection_settings - .id - .clone() - .unwrap_or_else(|| "Unknown VPN".to_string()); - let uuid = connection_settings.uuid.clone().unwrap_or_default(); - - vpn_connections.push(VpnConnection { name, uuid }); - } - } - } - } - - // Sort by name for consistent UI - vpn_connections.sort_by(|a, b| a.name.cmp(&b.name)); - - Ok(vpn_connections) -} diff --git a/cosmic-applet-network/src/network_manager/available_wifi.rs b/cosmic-applet-network/src/network_manager/available_wifi.rs deleted file mode 100644 index 3fd7fae1..00000000 --- a/cosmic-applet-network/src/network_manager/available_wifi.rs +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -use cosmic_dbus_networkmanager::{ - device::wireless::WirelessDevice, - interface::{ - access_point::AccessPointProxy, - enums::{ApFlags, ApSecurityFlags, DeviceState}, - }, -}; - -use futures_util::StreamExt; -use rustc_hash::FxHashMap; -use std::collections::HashMap; -use zbus::zvariant::ObjectPath; - -use super::hw_address::HwAddress; - -pub async fn handle_wireless_device( - device: WirelessDevice<'_>, - hw_address: Option, -) -> zbus::Result> { - device.request_scan(HashMap::new()).await?; - let mut scan_changed = device.receive_last_scan_changed().await; - if let Some(t) = scan_changed.next().await { - if let Ok(-1) = t.get().await { - eprintln!("scan errored"); - return Ok(Vec::new()); - } - } - let access_points = device.get_access_points().await?; - let state: DeviceState = device - .upcast() - .await - .and_then(|dev| dev.cached_state()) - .unwrap_or_default() - .map_or(DeviceState::Unknown, |s| s.into()); - // Sort by strength and remove duplicates - let mut aps = FxHashMap::::default(); - for ap in access_points { - let ssid = String::from_utf8_lossy(ap.ssid().await?.as_slice()).into_owned(); - let wps_push = ap.flags().await?.contains(ApFlags::WPS_PBC); - let strength = ap.strength().await?; - if let Some(access_point) = aps.get(&ssid) { - if access_point.strength > strength { - continue; - } - } - let proxy: &AccessPointProxy = ≈ - let Ok(flags) = ap.rsn_flags().await else { - continue; - }; - - let network_type = if flags.intersects(ApSecurityFlags::KEY_MGMT_802_1X) { - NetworkType::EAP - } else if flags.intersects(ApSecurityFlags::KEY_MGMTPSK) { - NetworkType::PSK - } else if flags.is_empty() { - NetworkType::Open - } else { - continue; - }; - - aps.insert( - ssid.clone(), - AccessPoint { - ssid, - strength, - state, - working: false, - path: ap.inner().path().to_owned(), - hw_address: hw_address - .as_ref() - .and_then(|str_addr| HwAddress::from_str(str_addr)) - .unwrap_or_default(), - wps_push, - network_type, - }, - ); - } - let mut aps = aps.into_values().collect::>(); - aps.sort_unstable_by_key(|ap| ap.strength); - Ok(aps) -} - -#[derive(Debug, Clone)] -pub struct AccessPoint { - pub ssid: String, - pub strength: u8, - pub state: DeviceState, - pub working: bool, - pub path: ObjectPath<'static>, - pub hw_address: HwAddress, - pub wps_push: bool, - pub network_type: NetworkType, -} - -// TODO do we want to support eap methods other than peap in the applet? -// Then we'd need a dropdown for the eap method, -// and tls requires a cert instead of a password -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Clone, Copy)] -pub enum NetworkType { - Open, - PSK, - EAP, -} diff --git a/cosmic-applet-network/src/network_manager/current_networks.rs b/cosmic-applet-network/src/network_manager/current_networks.rs deleted file mode 100644 index aec61763..00000000 --- a/cosmic-applet-network/src/network_manager/current_networks.rs +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -use cosmic_dbus_networkmanager::{ - active_connection::ActiveConnection, device::SpecificDevice, - interface::enums::ActiveConnectionState, -}; -use std::net::Ipv4Addr; - -use super::hw_address::HwAddress; - -pub async fn active_connections( - active_connections: Vec>, -) -> zbus::Result> { - let mut info = Vec::::with_capacity(active_connections.len()); - for connection in active_connections { - let ipv4 = connection - .ip4_config() - .await? - .address_data() - .await - .unwrap_or_default(); - let addresses: Vec<_> = ipv4.iter().map(|d| d.address).collect(); - let state = connection - .state() - .await - .unwrap_or(ActiveConnectionState::Unknown); - - if connection.vpn().await.unwrap_or_default() { - info.push(ActiveConnectionInfo::Vpn { - name: connection.id().await?, - ip_addresses: addresses.clone(), - }); - continue; - } - for device in connection.devices().await.unwrap_or_default() { - match device - .downcast_to_device() - .await - .ok() - .and_then(|inner| inner) - { - Some(SpecificDevice::Wired(wired_device)) => { - info.push(ActiveConnectionInfo::Wired { - name: connection.id().await?, - hw_address: HwAddress::from_str(&wired_device.hw_address().await?) - .unwrap_or_default(), - speed: wired_device.speed().await?, - ip_addresses: addresses.clone(), - }); - } - Some(SpecificDevice::Wireless(wireless_device)) => { - if let Ok(access_point) = wireless_device.active_access_point().await { - info.push(ActiveConnectionInfo::WiFi { - name: String::from_utf8_lossy(&access_point.ssid().await?).into_owned(), - ip_addresses: addresses.clone(), - hw_address: HwAddress::from_str(&wireless_device.hw_address().await?) - .unwrap_or_default(), - state, - strength: access_point.strength().await.unwrap_or_default(), - }); - } - } - Some(SpecificDevice::WireGuard(_)) => { - info.push(ActiveConnectionInfo::Vpn { - name: connection.id().await?, - ip_addresses: addresses.clone(), - }); - } - _ => {} - } - } - } - - info.sort_unstable(); - Ok(info) -} - -#[derive(Debug, Clone)] -pub enum ActiveConnectionInfo { - Wired { - name: String, - hw_address: HwAddress, - speed: u32, - ip_addresses: Vec, - }, - WiFi { - name: String, - ip_addresses: Vec, - hw_address: HwAddress, - state: ActiveConnectionState, - strength: u8, - }, - Vpn { - name: String, - ip_addresses: Vec, - }, -} - -impl ActiveConnectionInfo { - pub fn name(&self) -> String { - match &self { - Self::Wired { name, .. } | Self::WiFi { name, .. } | Self::Vpn { name, .. } => { - name.clone() - } - } - } - pub fn hw_address(&self) -> HwAddress { - match &self { - Self::Wired { hw_address, .. } | Self::WiFi { hw_address, .. } => *hw_address, - Self::Vpn { .. } => HwAddress::default(), - } - } -} - -impl std::cmp::Ord for ActiveConnectionInfo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match (self, other) { - (Self::Vpn { .. }, Self::Wired { .. } | Self::WiFi { .. }) - | (Self::Wired { .. }, Self::WiFi { .. }) => std::cmp::Ordering::Less, - - (Self::WiFi { .. }, Self::Wired { .. } | Self::Vpn { .. }) - | (Self::Wired { .. }, Self::Vpn { .. }) => std::cmp::Ordering::Greater, - - (Self::Vpn { name: n1, .. }, Self::Vpn { name: n2, .. }) - | (Self::Wired { name: n1, .. }, Self::Wired { name: n2, .. }) - | (Self::WiFi { name: n1, .. }, Self::WiFi { name: n2, .. }) => n1.cmp(n2), - } - } -} - -impl std::cmp::Eq for ActiveConnectionInfo {} - -impl std::cmp::PartialOrd for ActiveConnectionInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl std::cmp::PartialEq for ActiveConnectionInfo { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - ( - Self::Wired { - name: n1, - hw_address: a1, - .. - }, - Self::Wired { - name: n2, - hw_address: a2, - .. - }, - ) - | ( - Self::WiFi { - name: n1, - hw_address: a1, - .. - }, - Self::WiFi { - name: n2, - hw_address: a2, - .. - }, - ) => n1 == n2 && a1 == a2, - - (Self::Vpn { name: n1, .. }, Self::Vpn { name: n2, .. }) => n1 == n2, - - _ => false, - } - } -} diff --git a/cosmic-applet-network/src/network_manager/devices.rs b/cosmic-applet-network/src/network_manager/devices.rs deleted file mode 100644 index 6d68e38d..00000000 --- a/cosmic-applet-network/src/network_manager/devices.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::{NetworkManagerEvent, NetworkManagerState}; -use cosmic::iced::{self, Subscription, stream}; -use cosmic_dbus_networkmanager::nm::NetworkManager; -use futures::{SinkExt, StreamExt}; -use std::{fmt::Debug, hash::Hash}; -use zbus::Connection; - -pub fn devices_subscription( - id: I, - has_popup: bool, - conn: Connection, -) -> iced::Subscription { - let initial = State::Continue(conn); - Subscription::run_with_id( - (id, has_popup), - stream::channel(50, move |mut output| { - let mut state = initial.clone(); - - async move { - loop { - state = start_listening(state, has_popup, &mut output).await; - } - } - }), - ) -} - -#[derive(Debug, Clone)] -pub enum State { - Continue(Connection), - Error, -} - -async fn start_listening( - state: State, - has_popup: bool, - output: &mut futures::channel::mpsc::Sender, -) -> State { - let conn = match state { - State::Continue(conn) => conn, - State::Error => iced::futures::future::pending().await, - }; - let network_manager = match NetworkManager::new(&conn).await { - Ok(n) => n, - Err(why) => { - tracing::error!(why = why.to_string(), "Failed to connect to NetworkManager"); - return State::Error; - } - }; - - let mut devices_changed = network_manager.receive_devices_changed().await; - - let secs = if has_popup { 4 } else { 60 }; - while let (Some(_change), ()) = tokio::join!( - devices_changed.next(), - tokio::time::sleep(tokio::time::Duration::from_secs(secs)) - ) { - let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::WirelessAccessPoints(new_state)) - .await; - } - - State::Continue(conn) -} diff --git a/cosmic-applet-network/src/network_manager/hw_address.rs b/cosmic-applet-network/src/network_manager/hw_address.rs deleted file mode 100644 index 4eb89ed9..00000000 --- a/cosmic-applet-network/src/network_manager/hw_address.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::fmt::Write; - -#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, PartialOrd, Ord)] -pub struct HwAddress { - address: u64, -} - -impl HwAddress { - pub fn from_str(arg: &str) -> Option { - let columnless_vec = arg.split(':').collect::>(); - if columnless_vec.len() * 3 - 1 != arg.len() { - return None; - } - for byte in &columnless_vec { - if byte.len() != 2 { - return None; - } - } - u64::from_str_radix(columnless_vec.join("").as_str(), 16) - .ok() - .map(|address| HwAddress { address }) - } -} - -impl std::fmt::Display for HwAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (index, c) in format!("{:x}", self.address).char_indices() { - if index != 0 && index % 2 == 0 { - f.write_char(':')?; - } - f.write_char(c)?; - } - - Ok(()) - } -} diff --git a/cosmic-applet-network/src/network_manager/mod.rs b/cosmic-applet-network/src/network_manager/mod.rs deleted file mode 100644 index f37af94f..00000000 --- a/cosmic-applet-network/src/network_manager/mod.rs +++ /dev/null @@ -1,822 +0,0 @@ -pub mod active_conns; -pub mod available_vpns; -pub mod available_wifi; -pub mod current_networks; -pub mod devices; -pub mod hw_address; -pub mod wireless_enabled; - -use std::{collections::HashMap, fmt::Debug, time::Duration}; - -use available_wifi::NetworkType; -use cosmic::{ - iced::{self, Subscription}, - iced_futures::stream, -}; -use cosmic_dbus_networkmanager::{ - active_connection::ActiveConnection, - device::SpecificDevice, - interface::{ - active_connection::ActiveConnectionProxy, - enums::{self, ActiveConnectionState, DeviceType, NmConnectivityState}, - }, - nm::NetworkManager, - settings::{NetworkManagerSettings, connection::Settings}, -}; -use futures::{ - SinkExt, StreamExt, - channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded}, -}; -use hw_address::HwAddress; -use tokio::process::Command; -use zbus::{ - Connection, - zvariant::{self, Value}, -}; - -use self::{ - available_vpns::{VpnConnection, load_vpn_connections}, - available_wifi::{AccessPoint, handle_wireless_device}, - current_networks::{ActiveConnectionInfo, active_connections}, -}; - -// In some distros, rfkill is only in sbin, which isn't normally in PATH -// TODO: Directly access `/dev/rfkill` -fn rfkill_path_var() -> std::ffi::OsString { - let mut path = std::env::var_os("PATH").unwrap_or_default(); - path.push(":/usr/sbin"); - path -} - -#[derive(Debug)] -pub enum State { - Ready, - Waiting(Connection, UnboundedReceiver), - Finished, -} - -pub fn network_manager_subscription( - id: I, -) -> iced::Subscription { - Subscription::run_with_id( - id, - stream::channel(50, |mut output| async move { - let mut state = State::Ready; - - loop { - state = start_listening(state, &mut output).await; - } - }), - ) -} - -async fn start_listening( - state: State, - output: &mut futures::channel::mpsc::Sender, -) -> State { - match state { - State::Ready => { - let Ok(conn) = Connection::system().await else { - return State::Finished; - }; - - let (tx, rx) = unbounded(); - let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - if output - .send(NetworkManagerEvent::Init { - conn: conn.clone(), - sender: tx, - state: nm_state, - }) - .await - .is_ok() - { - State::Waiting(conn, rx) - } else { - State::Finished - } - } - State::Waiting(conn, mut rx) => { - let Ok(network_manager) = NetworkManager::new(&conn).await else { - return State::Finished; - }; - - match rx.next().await { - Some(NetworkManagerRequest::Disconnect(ssid, hw_address)) => { - let mut success = false; - for c in network_manager - .active_connections() - .await - .unwrap_or_default() - { - if c.id().await.unwrap_or_default() != ssid { - continue; - } - let mut is_there_device = false; - for device in c.devices().await.unwrap_or_default() { - if HwAddress::from_str(device.hw_address().await.as_ref().unwrap()) - == Some(hw_address) - { - is_there_device = true; - } - } - - if is_there_device - && network_manager.deactivate_connection(&c).await.is_ok() - { - success = true; - if let Ok(ActiveConnectionState::Deactivated) = c.state().await { - break; - } else { - let mut changed = c.receive_state_changed().await; - _ = tokio::time::timeout(Duration::from_secs(5), async move { - loop { - if let Some(next) = changed.next().await { - if let Ok(ActiveConnectionState::Deactivated) = - next.get().await.map(ActiveConnectionState::from) - { - break; - } - } - } - }) - .await; - } - break; - } - } - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Disconnect(ssid.clone(), hw_address), - success, - state: NetworkManagerState::new(&conn).await.unwrap_or_default(), - }) - .await; - } - Some(NetworkManagerRequest::SetAirplaneMode(airplane_mode)) => { - // wifi - let mut success = network_manager - .set_wireless_enabled(!airplane_mode) - .await - .is_ok(); - // bluetooth - success = success - && Command::new("rfkill") - .env("PATH", rfkill_path_var()) - .arg(if airplane_mode { "block" } else { "unblock" }) - .arg("bluetooth") - .output() - .await - .is_ok(); - let mut state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - state.airplane_mode = if success { - airplane_mode - } else { - !airplane_mode - }; - if state.airplane_mode { - state.wifi_enabled = false; - } - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::SetAirplaneMode(airplane_mode), - success, - state, - }) - .await; - } - Some(NetworkManagerRequest::SetWiFi(enabled)) => { - let success = network_manager.set_wireless_enabled(enabled).await.is_ok(); - let mut state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - state.wifi_enabled = if success { enabled } else { !enabled }; - let response = NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::SetWiFi(enabled), - success, - state, - }; - _ = output.send(response).await; - } - Some(NetworkManagerRequest::Authenticate { - ssid, - identity, - password, - hw_address, - }) => { - let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - let mut success = true; - let err = nm_state - .connect_wifi( - &conn, - &ssid, - identity.as_deref(), - Some(&password), - hw_address, - ) - .await; - - if let Err(err) = err { - success = false; - tracing::error!("{:?}", &err); - } - - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Authenticate { - ssid: ssid.clone(), - identity: identity.clone(), - password: password.clone(), - hw_address, - }, - success, - state: NetworkManagerState::new(&conn).await.unwrap_or_default(), - }) - .await; - } - Some(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address, network_type)) => { - if matches!(network_type, NetworkType::Open) { - attempt_wifi_connection(&conn, ssid, hw_address, network_type, output) - .await; - } else { - // For secured networks, check if we have saved credentials - if !has_saved_wifi_credentials(&conn, &ssid).await { - return State::Waiting(conn, rx); - } - - // We have saved credentials, attempt connection - attempt_wifi_connection(&conn, ssid, hw_address, network_type, output) - .await; - } - } - Some(NetworkManagerRequest::Reload) => { - let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Reload, - success: true, - state, - }) - .await; - } - Some(NetworkManagerRequest::Forget(ssid, hw_address)) => { - let s = NetworkManagerSettings::new(&conn).await.unwrap(); - let known_conns = s.list_connections().await.unwrap_or_default(); - let mut success = false; - for c in known_conns { - let settings = c.get_settings().await.ok().unwrap_or_default(); - let s = Settings::new(settings); - if s.wifi - .as_ref() - .and_then(|w| w.ssid.as_deref()) - .is_some_and(|s| std::str::from_utf8(s).is_ok_and(|s| s == ssid)) - { - // todo most likely we can here forget ssid from wrong hw_address - _ = c.delete().await; - success = true; - break; - } - } - let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Forget(ssid.clone(), hw_address), - success, - state, - }) - .await; - } - Some(NetworkManagerRequest::ActivateVpn(uuid)) => { - tracing::info!("Activating VPN with UUID: {}", uuid); - let network_manager = match NetworkManager::new(&conn).await { - Ok(n) => n, - Err(e) => { - tracing::error!("Failed to connect to NetworkManager: {:?}", e); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::ActivateVpn(uuid), - success: false, - state: NetworkManagerState::new(&conn) - .await - .unwrap_or_default(), - }) - .await; - return State::Waiting(conn, rx); - } - }; - - let mut success = false; - - // Find the connection by UUID - if let Ok(nm_settings) = NetworkManagerSettings::new(&conn).await { - if let Ok(connections) = nm_settings.list_connections().await { - for connection in connections { - if let Ok(settings) = connection.get_settings().await { - let settings = Settings::new(settings); - if let Some(conn_settings) = &settings.connection { - if conn_settings.uuid.as_ref() == Some(&uuid) { - // Activate the VPN connection without a specific device - // Call the D-Bus method directly since VPNs don't need a device - use zbus::zvariant::ObjectPath; - let empty_device = ObjectPath::try_from("/").unwrap(); - - match network_manager - .inner() - .call_method( - "ActivateConnection", - &( - connection.inner().path(), - empty_device.clone(), - empty_device, - ), - ) - .await - { - Ok(_) => { - tracing::info!( - "Successfully activated VPN: {}", - uuid - ); - success = true; - } - Err(e) => { - tracing::error!( - "Failed to activate VPN {}: {:?}", - uuid, - e - ); - } - } - break; - } - } - } - } - } - } - - if !success { - tracing::warn!( - "VPN connection with UUID {} not found or failed to activate", - uuid - ); - } - - let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::ActivateVpn(uuid), - success, - state, - }) - .await; - } - Some(NetworkManagerRequest::DeactivateVpn(name)) => { - tracing::info!("Deactivating VPN: {}", name); - let network_manager = match NetworkManager::new(&conn).await { - Ok(n) => n, - Err(e) => { - tracing::error!("Failed to connect to NetworkManager: {:?}", e); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::DeactivateVpn(name), - success: false, - state: NetworkManagerState::new(&conn) - .await - .unwrap_or_default(), - }) - .await; - return State::Waiting(conn, rx); - } - }; - - let mut success = false; - - // Find and deactivate the active VPN connection by name - if let Ok(active_connections) = network_manager.active_connections().await { - for active_conn in active_connections { - if let Ok(conn_id) = active_conn.id().await { - if conn_id == name && active_conn.vpn().await.unwrap_or(false) { - match network_manager.deactivate_connection(&active_conn).await - { - Ok(_) => { - tracing::info!( - "Successfully deactivated VPN: {}", - name - ); - success = true; - break; - } - Err(e) => { - tracing::error!( - "Failed to deactivate VPN {}: {:?}", - name, - e - ); - } - } - } - } - } - } - - if !success { - tracing::warn!( - "Active VPN connection '{}' not found or failed to deactivate", - name - ); - } - - let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::DeactivateVpn(name), - success, - state, - }) - .await; - } - _ => { - return State::Finished; - } - } - - State::Waiting(conn, rx) - } - State::Finished => iced::futures::future::pending().await, - } -} - -async fn has_saved_wifi_credentials(conn: &Connection, ssid: &str) -> bool { - let Ok(nm_settings) = NetworkManagerSettings::new(conn).await else { - return false; - }; - - let known_conns = nm_settings.list_connections().await.unwrap_or_default(); - - for connection in known_conns { - if let Ok(settings) = connection.get_settings().await { - let settings = Settings::new(settings); - if let Some(saved_ssid) = settings - .wifi - .and_then(|w| w.ssid) - .and_then(|ssid| String::from_utf8(ssid).ok()) - { - if saved_ssid == ssid { - return true; - } - } - } - } - - false -} - -async fn attempt_wifi_connection( - conn: &Connection, - ssid: String, - hw_address: HwAddress, - network_type: NetworkType, - output: &mut futures::channel::mpsc::Sender, -) { - let state = NetworkManagerState::new(conn).await.unwrap_or_default(); - - let success = if let Err(err) = state - .connect_wifi(conn, ssid.as_ref(), None, None, hw_address) - .await - { - tracing::error!("Failed to connect to access point: {:?}", err); - false - } else { - true - }; - - _ = output - .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::SelectAccessPoint(ssid, hw_address, network_type), - success, - state: NetworkManagerState::new(conn).await.unwrap_or_default(), - }) - .await; -} - -#[derive(Debug, Clone)] -pub enum NetworkManagerRequest { - SetAirplaneMode(bool), - SetWiFi(bool), - SelectAccessPoint(String, HwAddress, NetworkType), - Disconnect(String, HwAddress), - Authenticate { - ssid: String, - identity: Option, - password: String, - hw_address: HwAddress, - }, - Forget(String, HwAddress), - Reload, - ActivateVpn(String), // UUID of VPN connection to activate - DeactivateVpn(String), // Name of active VPN connection to deactivate -} - -#[derive(Debug, Clone)] -pub enum NetworkManagerEvent { - RequestResponse { - req: NetworkManagerRequest, - state: NetworkManagerState, - success: bool, - }, - Init { - conn: Connection, - sender: UnboundedSender, - state: NetworkManagerState, - }, - WiFiEnabled(NetworkManagerState), - WirelessAccessPoints(NetworkManagerState), - ActiveConns(NetworkManagerState), -} - -#[derive(Debug, Clone)] -pub struct NetworkManagerState { - pub wireless_access_points: Vec, - pub active_conns: Vec, - pub known_access_points: Vec, - pub available_vpns: Vec, - pub wifi_enabled: bool, - pub airplane_mode: bool, - pub connectivity: NmConnectivityState, -} - -impl Default for NetworkManagerState { - fn default() -> Self { - Self { - wireless_access_points: Vec::new(), - active_conns: Vec::new(), - known_access_points: Vec::new(), - available_vpns: Vec::new(), - wifi_enabled: false, - airplane_mode: false, - connectivity: NmConnectivityState::Unknown, - } - } -} - -impl NetworkManagerState { - pub async fn new(conn: &Connection) -> anyhow::Result { - let network_manager = NetworkManager::new(conn).await?; - let mut self_ = Self::default(); - // airplane mode - let airplaine_mode = Command::new("rfkill") - .env("PATH", rfkill_path_var()) - .arg("list") - .arg("bluetooth") - .output() - .await?; - let airplane_mode = std::str::from_utf8(&airplaine_mode.stdout).unwrap_or_default(); - self_.wifi_enabled = network_manager.wireless_enabled().await.unwrap_or_default(); - self_.airplane_mode = airplane_mode.contains("Soft blocked: yes") && !self_.wifi_enabled; - - let s = NetworkManagerSettings::new(conn).await?; - _ = s.load_connections(&[]).await; - let known_conns = s.list_connections().await.unwrap_or_default(); - let active_conns = active_connections( - network_manager - .active_connections() - .await - .unwrap_or_default(), - ) - .await - .unwrap_or_default(); - // active_conns.sort(); active_connections should have already sorted the vector - let devices = network_manager.devices().await.ok().unwrap_or_default(); - let wireless_access_point_futures: Vec<_> = devices - .into_iter() - .map(|device| async move { - if let Ok(Some(SpecificDevice::Wireless(wireless_device))) = - device.downcast_to_device().await - { - handle_wireless_device(wireless_device, device.hw_address().await.ok()) - .await - .unwrap_or_default() - } else { - Vec::new() - } - }) - .collect(); - let mut wireless_access_points = Vec::with_capacity(wireless_access_point_futures.len()); - for f in wireless_access_point_futures { - let mut access_points = f.await; - wireless_access_points.append(&mut access_points); - } - let mut known_ssid = Vec::with_capacity(known_conns.len()); - for c in known_conns { - let Ok(s) = c.get_settings().await else { - tracing::info!("Failed to get settings for known connection"); - continue; - }; - let s = Settings::new(s); - if let Some(cur_ssid) = s - .wifi - .clone() - .and_then(|w| w.ssid) - .and_then(|ssid| String::from_utf8(ssid).ok()) - { - known_ssid.push(cur_ssid); - } - } - let known_access_points: Vec<_> = wireless_access_points - .iter() - .filter(|a| { - known_ssid.contains(&a.ssid) - && !active_conns - .iter() - .any(|ac| ac.name() == a.ssid && ac.hw_address() == a.hw_address) - }) - .cloned() - .collect(); - wireless_access_points.sort_by(|a, b| b.strength.cmp(&a.strength)); - self_.wireless_access_points = wireless_access_points; - for ap in &self_.wireless_access_points { - tracing::info!( - "AP ssid: {},\ttype: {:?},\tworking: {},\tstate: {:?}", - ap.ssid, - ap.network_type, - ap.working, - ap.state - ); - } - self_.active_conns = active_conns; - self_.known_access_points = known_access_points; - self_.connectivity = network_manager.connectivity().await?; - - // Load available VPN connections - self_.available_vpns = load_vpn_connections(conn).await.unwrap_or_default(); - - Ok(self_) - } - - #[allow(dead_code)] - pub fn clear(&mut self) { - self.active_conns = Vec::new(); - self.known_access_points = Vec::new(); - self.wireless_access_points = Vec::new(); - self.available_vpns = Vec::new(); - } - - async fn connect_wifi( - &self, - conn: &Connection, - ssid: &str, - identity: Option<&str>, - password: Option<&str>, - hw_address: HwAddress, - ) -> anyhow::Result<()> { - let nm = NetworkManager::new(conn).await?; - - for c in nm.active_connections().await.unwrap_or_default() { - if self.wireless_access_points.iter().any(|w| { - c.cached_id() - .is_ok_and(|opt| opt.is_some_and(|id| id == w.ssid)) - && w.hw_address == hw_address - }) { - _ = nm.deactivate_connection(&c).await; - } - } - - let Some(ap) = self - .wireless_access_points - .iter() - .find(|ap| ap.ssid == ssid && ap.hw_address == hw_address) - else { - return Err(anyhow::anyhow!("Access point not found")); - }; - - let mut conn_settings: HashMap<&str, HashMap<&str, zvariant::Value>> = HashMap::from([ - ( - "802-11-wireless", - HashMap::from([("ssid", Value::Array(ssid.as_bytes().into()))]), - ), - ( - "connection", - HashMap::from([ - ("id", Value::Str(ssid.into())), - ("type", Value::Str("802-11-wireless".into())), - ]), - ), - ]); - - if let Some(identity) = identity { - conn_settings.insert( - "802-1x", - HashMap::from([ - ("identity", Value::Str(identity.into())), - // most common default - ("eap", Value::Array(["peap"].as_slice().into())), - // most common default - ("phase2-auth", Value::Str("mschapv2".into())), - ("password", Value::Str(password.unwrap_or("").into())), - ]), - ); - let wireless = conn_settings.get_mut("802-11-wireless").unwrap(); - wireless.insert("security", Value::Str("802-11-wireless-security".into())); - wireless.insert("mode", Value::Str("infrastructure".into())); - conn_settings.insert( - "802-11-wireless-security", - HashMap::from([("key-mgmt", Value::Str("wpa-eap".into()))]), - ); - } else if let Some(pass) = password { - conn_settings.insert( - "802-11-wireless-security", - HashMap::from([ - ("psk", Value::Str(pass.into())), - ("key-mgmt", Value::Str("wpa-psk".into())), - ]), - ); - } - - let devices = nm.devices().await?; - for device in devices { - let device_hw_address = device - .hw_address() - .await - .ok() - .and_then(|device_address| HwAddress::from_str(&device_address)) - .unwrap_or_default(); - if device_hw_address != hw_address { - continue; - } - if !matches!( - device.device_type().await.unwrap_or(DeviceType::Other), - DeviceType::Wifi - ) { - continue; - } - - let s = NetworkManagerSettings::new(conn).await?; - let known_conns = s.list_connections().await.unwrap_or_default(); - let mut known_conn = None; - for c in known_conns { - let settings = c.get_settings().await.ok().unwrap_or_default(); - - let s = Settings::new(settings); - // todo try to add hw_address comparing here if it changes anything - if s.wifi - .as_ref() - .and_then(|w| w.ssid.as_deref()) - .is_some_and(|s| std::str::from_utf8(s).is_ok_and(|cur_ssid| cur_ssid == ssid)) - { - known_conn = Some(c); - break; - } - } - - let active_conn = if let Some(known_conn) = known_conn.as_ref() { - // update settings if needed - if password.is_some() { - known_conn.update(conn_settings).await?; - } - - nm.activate_connection(known_conn, &device).await? - } else { - let (_, active_conn) = nm - .add_and_activate_connection(conn_settings, device.inner().path(), &ap.path) - .await?; - let dummy = ActiveConnectionProxy::new(conn, active_conn).await?; - let active = ActiveConnectionProxy::builder(conn) - .destination(dummy.inner().destination().to_owned()) - .unwrap() - .interface(dummy.inner().interface().to_owned()) - .unwrap() - .path(dummy.inner().path().to_owned()) - .unwrap() - .build() - .await - .unwrap(); - ActiveConnection::from(active) - }; - let mut changes = active_conn.receive_state_changed().await; - () = tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - let mut count = 5; - loop { - let state = active_conn.state().await; - if let Ok(enums::ActiveConnectionState::Activated) = state { - return Ok(()); - } else if let Ok(enums::ActiveConnectionState::Deactivated) = state { - anyhow::bail!("Failed to activate connection"); - } - if let Ok(Some(s)) = - tokio::time::timeout(Duration::from_secs(20), changes.next()).await - { - let state = s.get().await.unwrap_or_default().into(); - if matches!(state, enums::ActiveConnectionState::Activated) { - return Ok(()); - } - } - - count -= 1; - if count <= 0 { - anyhow::bail!("Failed to activate connection"); - } - } - } - - Err(anyhow::anyhow!("No wifi device found")) - } -} diff --git a/cosmic-applet-network/src/network_manager/wireless_enabled.rs b/cosmic-applet-network/src/network_manager/wireless_enabled.rs deleted file mode 100644 index 850ec36e..00000000 --- a/cosmic-applet-network/src/network_manager/wireless_enabled.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::{NetworkManagerEvent, NetworkManagerState}; -use cosmic::{ - iced::{self, Subscription}, - iced_futures::stream, -}; -use cosmic_dbus_networkmanager::nm::NetworkManager; -use futures::{SinkExt, StreamExt}; -use std::{fmt::Debug, hash::Hash}; -use zbus::Connection; - -pub fn wireless_enabled_subscription( - id: I, - conn: Connection, -) -> iced::Subscription { - let initial = State::Continue(conn); - Subscription::run_with_id( - id, - stream::channel(50, move |mut output| { - let mut state = initial; - - async move { - loop { - state = start_listening(state, &mut output).await; - } - } - }), - ) -} - -#[derive(Debug, Clone)] -pub enum State { - Continue(Connection), - Error, -} - -async fn start_listening( - state: State, - output: &mut futures::channel::mpsc::Sender, -) -> State { - let conn = match state { - State::Continue(conn) => conn, - State::Error => iced::futures::future::pending().await, - }; - - let network_manager = match NetworkManager::new(&conn).await { - Ok(n) => n, - Err(why) => { - tracing::error!(why = why.to_string(), "Failed to connect to NetworkManager"); - return State::Error; - } - }; - - let mut wireless_enabled_changed = network_manager.receive_wireless_enabled_changed().await; - - while let Some(_change) = wireless_enabled_changed.next().await { - let new_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - _ = output - .send(NetworkManagerEvent::WiFiEnabled(new_state)) - .await; - } - State::Continue(conn) -} diff --git a/cosmic-applet-network/src/utils.rs b/cosmic-applet-network/src/utils.rs new file mode 100644 index 00000000..f802010f --- /dev/null +++ b/cosmic-applet-network/src/utils.rs @@ -0,0 +1,18 @@ +use futures_util::future::select; + +/// Spawn a background tasks and forward its messages +pub fn forward_event_loop + Send + 'static>( + event_loop: impl FnOnce(async_fn_stream::StreamEmitter) -> T + Send + 'static, +) -> (tokio::sync::oneshot::Sender<()>, cosmic::Task) { + let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>(); + + let task = cosmic::Task::stream(async_fn_stream::fn_stream(|emitter| async move { + select( + std::pin::pin!(cancel_rx), + std::pin::pin!(event_loop(emitter)), + ) + .await; + })); + + (cancel_tx, task) +} diff --git a/cosmic-applet-notifications/Cargo.toml b/cosmic-applet-notifications/Cargo.toml index ed8fddc6..f32e2fdf 100644 --- a/cosmic-applet-notifications/Cargo.toml +++ b/cosmic-applet-notifications/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-notifications" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" @@ -25,4 +25,4 @@ i18n-embed = { workspace = true, features = [ i18n-embed-fl.workspace = true rust-embed.workspace = true zbus = { workspace = true, features = ["tokio", "p2p"] } -url = "2.5.7" +url = "2.5.8" diff --git a/cosmic-applet-notifications/data/com.system76.CosmicAppletNotifications.desktop b/cosmic-applet-notifications/data/com.system76.CosmicAppletNotifications.desktop index b3239370..82db85c6 100644 --- a/cosmic-applet-notifications/data/com.system76.CosmicAppletNotifications.desktop +++ b/cosmic-applet-notifications/data/com.system76.CosmicAppletNotifications.desktop @@ -12,6 +12,7 @@ Name[de]=Notifizierungszentrum Name[sk]=Centrum oznámení Name[sv]=Aviseringscenter Name[es]=Centro de notificaciones +Name[it]=Centro notifiche Type=Application Exec=cosmic-applet-notifications Terminal=false @@ -21,6 +22,7 @@ Keywords=COSMIC;Iced; Icon=com.system76.CosmicAppletNotifications-symbolic NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-NotificationsApplet=true X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-notifications/i18n/ar/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/ar/cosmic_applet_notifications.ftl index 0d4d4b89..7c1eaa43 100644 --- a/cosmic-applet-notifications/i18n/ar/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/ar/cosmic_applet_notifications.ftl @@ -1,17 +1,19 @@ -show-less = عرض أقل -show-more = عرض المزيد ({ $more }) -clear-group = مسح المجموعة -clear-all = مسح كل الإشعارات +show-less = أظهر أقل +show-more = أظهر { $more } أكثر +clear-group = امحُ المجموعة +clear-all = امحُ كل الإشعارات do-not-disturb = عدم الإزعاج notification-settings = إعدادات الإشعارات... no-notifications = لا إشعارات hours-ago = - { $duration } { $duration -> - [one] ساعة - *[other] ساعات - } مضت + { $duration -> + [0] الآن + [one] منذ ساعة + *[other] منذ { $duration } ساعات + } minutes-ago = - { $duration } { $duration -> - [one] دقيقة - *[other] دقائق - } مضت + { $duration -> + [0] الآن + [one] منذ دقيقة + *[other] منذ { $duration } دقائق + } diff --git a/cosmic-applet-notifications/i18n/bg/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/bg/cosmic_applet_notifications.ftl index 7f65f7c3..573ff1fb 100644 --- a/cosmic-applet-notifications/i18n/bg/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/bg/cosmic_applet_notifications.ftl @@ -1,13 +1,15 @@ -hours-ago = Преди { $duration } { $duration -> - [one] час - *[other] часа -} -minutes-ago = Преди { $duration } { $duration -> - [one] минута - *[other] минути -} +hours-ago = + Преди { $duration } { $duration -> + [one] час + *[other] часа + } +minutes-ago = + Преди { $duration } { $duration -> + [one] минута + *[other] минути + } show-less = По-малко информация -show-more = Повече {$more} информация +show-more = Повече { $more } информация clear-group = Изчистване на групата clear-all = Изчистване на всички известия do-not-disturb = Не безпокойте diff --git a/cosmic-applet-notifications/i18n/bn/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/bn/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/cs/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/cs/cosmic_applet_notifications.ftl index 36884e60..2f1b2ed7 100644 --- a/cosmic-applet-notifications/i18n/cs/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/cs/cosmic_applet_notifications.ftl @@ -1,15 +1,22 @@ hours-ago = - Před { $duration } { $duration -> - [one] hodinou - *[other] hodinami + { $duration -> + [0] Právě teď + [one] Před 1 hodinou + *[other] Před { $duration } hodinami } minutes-ago = - Před { $duration } { $duration -> - [one] minutou - *[other] minutami + { $duration -> + [0] Právě teď + [one] Před 1 minutou + *[other] Před { $duration } minutami } show-less = Zobrazit méně -show-more = Zobrazit ještě { $more } +show-more = + Zobrazit { $more } { $more -> + [one] další + [few] další + *[other] dalších + } clear-group = Vymazat skupinu clear-all = Vymazat všechna oznámení do-not-disturb = Nerušit diff --git a/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl index a73f3a8e..72b1a875 100644 --- a/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl @@ -1,11 +1,13 @@ -hours-ago = { $duration } { $duration -> - [one] hour - *[other] hours -} ago -minutes-ago = { $duration } { $duration -> - [one] minute - *[other] minutes -} ago +hours-ago = { $duration -> + [0] Just now + [one] 1 hour ago + *[other] { $duration } hours ago +} +minutes-ago = { $duration -> + [0] Just now + [one] 1 minute ago + *[other] { $duration } minutes ago +} show-less = Show less show-more = Show {$more} more clear-group = Clear group diff --git a/cosmic-applet-notifications/i18n/fr/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/fr/cosmic_applet_notifications.ftl index 669a01ab..42fd25a7 100644 --- a/cosmic-applet-notifications/i18n/fr/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/fr/cosmic_applet_notifications.ftl @@ -1,15 +1,17 @@ -hours-ago = Il y a { $duration } { $duration -> - [one] heure - [many] de heures - *[other] heures -} -minutes-ago = Il y a { $duration } { $duration -> - [one] minute - [many] de minutes - *[other] minutes -} +hours-ago = + { $duration -> + [0] À l'instant + [one] Il y a 1 heure + *[other] Il y a { $duration } heures + } +minutes-ago = + { $duration -> + [0] À l'instant + [one] Il y a 1 minute + *[other] Il y a { $duration } minutes + } show-less = Afficher moins -show-more = Afficher {$more} de plus +show-more = Afficher { $more } de plus clear-group = Effacer le groupe clear-all = Effacer toutes les notifications do-not-disturb = Ne pas déranger diff --git a/cosmic-applet-notifications/i18n/ga/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/ga/cosmic_applet_notifications.ftl index e2adc14a..b1d77f88 100644 --- a/cosmic-applet-notifications/i18n/ga/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/ga/cosmic_applet_notifications.ftl @@ -1,13 +1,19 @@ -hours-ago = { $duration } { $duration -> - [few] huaire - [many] n-uaire - *[other] uair -} ó shin -minutes-ago = { $duration } nóiméad ó shin -show-less = Taispeáin níos lú -show-more = Taispeáin {$more} eile -clear-group = Glan grúpa -clear-all = Glan gach fógra -do-not-disturb = Ná Cuir isteach -notification-settings = Socruithe Fógraí... -no-notifications = Gan fógraí +hours-ago = + { $duration -> + [0] Díreach anois + [one] 1 uair ó shin + *[other] { $duration } uair an chloig ó shin + } +minutes-ago = + { $duration -> + [0] Díreach anois + [one] 1 nóiméad ó shin + *[other] { $duration } nóiméad ó shin + } +show-less = Taispeáin níos lú +show-more = Taispeáin { $more } níos mó +clear-group = Glan grúpa +clear-all = Glan gach fógra +do-not-disturb = Ná cuir isteach +notification-settings = Socruithe fógraí... +no-notifications = Gan fógraí diff --git a/cosmic-applet-notifications/i18n/hu/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/hu/cosmic_applet_notifications.ftl index 4eec36c8..ec1f5a91 100644 --- a/cosmic-applet-notifications/i18n/hu/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/hu/cosmic_applet_notifications.ftl @@ -1,17 +1,19 @@ hours-ago = - { $duration } { $duration -> - [one] órával - *[other] órával - } ezelőtt + { $duration -> + [0] Most + [one] 1 órával ezelőtt + *[other] { $duration } órával ezelőtt + } minutes-ago = - { $duration } { $duration -> - [one] perce - *[other] perccel - } ezelőtt + { $duration -> + [0] Most + [one] 1 perccel ezelőtt + *[other] { $duration } perccel ezelőtt + } show-less = Kevesebb megjelenítése show-more = { $more } további megjelenítése clear-group = Csoport törlése clear-all = Minden értesítés törlése do-not-disturb = Ne zavarjanak -notification-settings = Értesítési beállítások... +notification-settings = Értesítési beállítások… no-notifications = Nincsenek értesítések diff --git a/cosmic-applet-notifications/i18n/ka/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/ka/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/kab/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/kab/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/kk/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/kk/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..2065cd15 --- /dev/null +++ b/cosmic-applet-notifications/i18n/kk/cosmic_applet_notifications.ftl @@ -0,0 +1,19 @@ +hours-ago = + { $duration -> + [0] Жаңа ғана + [one] 1 сағат бұрын + *[other] { $duration } сағат бұрын + } +minutes-ago = + { $duration -> + [0] Жаңа ғана + [one] 1 минут бұрын + *[other] { $duration } минут бұрын + } +show-less = Азырақ көрсету +show-more = Тағы { $more } көрсету +clear-group = Топты тазарту +clear-all = Барлық хабарландыруларды тазарту +do-not-disturb = Мазаламаңыз +notification-settings = Хабарландыру баптаулары... +no-notifications = Хабарландырулар жоқ diff --git a/cosmic-applet-notifications/i18n/kmr/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/kmr/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/ko/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/ko/cosmic_applet_notifications.ftl index 63a78ad9..92041856 100644 --- a/cosmic-applet-notifications/i18n/ko/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/ko/cosmic_applet_notifications.ftl @@ -1,7 +1,19 @@ -hours-ago = { $duration }시간 전 -minutes-ago = { $duration }분 전 -show-more = {$more} 더 보기 +hours-ago = + { $duration -> + [0] 방금 + [one] 1 시간 전 + *[other] { $duration } 시간 전 + } +minutes-ago = + { $duration -> + [0] 방금 + [one] 1 분 전 + *[other] { $duration } 분 전 + } +show-more = { $more } 더 보기 clear-all = 모든 알림 지우기 do-not-disturb = 방해 금지 notification-settings = 알림 설정... no-notifications = 알림이 없습니다 +show-less = 덜 보기 +clear-group = 그룹 지우기 diff --git a/cosmic-applet-notifications/i18n/lt/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/lt/cosmic_applet_notifications.ftl index e69de29b..b1afe336 100644 --- a/cosmic-applet-notifications/i18n/lt/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/lt/cosmic_applet_notifications.ftl @@ -0,0 +1,19 @@ +show-more = Rodyti { $more } daugiau +clear-all = Uždaryti visus pranešimus +notification-settings = Pranešimų nustatymai... +no-notifications = Nėra pranešimų +do-not-disturb = Režimas „Netrukdyti“ +show-less = Rodyti mažiau +clear-group = Išvalyti grupę +hours-ago = + { $duration -> + [0] Ką tik + [one] Prieš 1 valandą + *[other] Prieš { $duration } valandas + } +minutes-ago = + { $duration -> + [0] Ką tik + [one] Prieš 1 minutę + *[other] Prieš { $duration } minutes + } diff --git a/cosmic-applet-notifications/i18n/nl/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/nl/cosmic_applet_notifications.ftl index 826f1af3..029f633b 100644 --- a/cosmic-applet-notifications/i18n/nl/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/nl/cosmic_applet_notifications.ftl @@ -1,10 +1,11 @@ hours-ago = { $duration } uur geleden -minutes-ago = { $duration } { $duration -> - [one] minuut - *[other] minuten -} geleden +minutes-ago = + { $duration } { $duration -> + [one] minuut + *[other] minuten + } geleden show-less = Minder weergeven -show-more = {$more} meer weergeven +show-more = { $more } meer weergeven clear-group = Groep opschonen clear-all = Alle meldingen verwijderen do-not-disturb = Niet storen diff --git a/cosmic-applet-notifications/i18n/pa/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/pa/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/pl/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/pl/cosmic_applet_notifications.ftl index aa330c06..a0cb9042 100644 --- a/cosmic-applet-notifications/i18n/pl/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/pl/cosmic_applet_notifications.ftl @@ -1,15 +1,17 @@ hours-ago = - { $duration } { $duration -> - [one] godzinę - [few] godziny - *[other] godzin - } temu + { $duration -> + [0] Przed chwilą + [one] 1 godzinę temu + [few] { $duration } godziny temu + *[other] { $duration } godzin temu + } minutes-ago = - { $duration } { $duration -> - [one] minuta - [few] minuty - *[other] minut - } temu + { $duration -> + [0] Przed chwilą + [one] 1 minutę temu + [few] { $duration } minuty temu + *[other] { $duration } minut temu + } show-less = Pokaż mniej show-more = Pokaż { $more } więcej clear-group = Wyczyść grupę diff --git a/cosmic-applet-notifications/i18n/pt-BR/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/pt-BR/cosmic_applet_notifications.ftl index da68292b..6e28f7b7 100644 --- a/cosmic-applet-notifications/i18n/pt-BR/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/pt-BR/cosmic_applet_notifications.ftl @@ -1,13 +1,17 @@ -hours-ago = { NUMBER($duration) -> - [1] 1 hora atrás - *[other] {$duration} horas atrás -} -minutes-ago = { NUMBER($duration) -> - [1] 1 minuto atrás - *[other] {$duration} minutos atrás -} +hours-ago = + { $duration -> + [0] Agora mesmo + [one] 1 hora atrás + *[other] { $duration } horas atrás + } +minutes-ago = + { $duration -> + [0] Agora mesmo + [one] 1 minuto atrás + *[other] { $duration } minutos atrás + } show-less = Mostrar menos -show-more = Mostrar {$more} mais +show-more = Mostrar mais { $more } clear-group = Limpar grupo clear-all = Limpar todas as notificações do-not-disturb = Não Perturbe diff --git a/cosmic-applet-notifications/i18n/sv/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/sv/cosmic_applet_notifications.ftl index 31c4238a..cecab247 100644 --- a/cosmic-applet-notifications/i18n/sv/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/sv/cosmic_applet_notifications.ftl @@ -1,13 +1,15 @@ hours-ago = - { $duration } { $duration -> - [one] timma - *[other] timmar - } sedan + { $duration -> + [0] Alldeles nyss + [one] timma sedan + *[other] { $duration } timmar sedan + } minutes-ago = - { $duration } { $duration -> - [one] minut - *[other] minuter - } sedan + { $duration -> + [0] Alldeles nyss + [one] 1 minut sedan + *[other] { $duration } minuter sedan + } show-less = Visa mindre show-more = Visa { $more } till clear-group = Töm grupp diff --git a/cosmic-applet-notifications/i18n/ti/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/ti/cosmic_applet_notifications.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-notifications/i18n/uk/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/uk/cosmic_applet_notifications.ftl index 196839ce..ff50b7fe 100644 --- a/cosmic-applet-notifications/i18n/uk/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/uk/cosmic_applet_notifications.ftl @@ -1,13 +1,13 @@ hours-ago = - { NUMBER($duration) -> - [one] { $duration } годину тому - [few] { $duration } години тому + { $duration -> + [0] Щойно + [one] 1 годину тому *[other] { $duration } годин тому } minutes-ago = - { NUMBER($duration) -> - [one] { $duration } хвилину тому - [few] { $duration } хвилини тому + { $duration -> + [0] Щойно + [one] 1 хвилину тому *[other] { $duration } хвилин тому } show-less = Показати менше @@ -15,5 +15,5 @@ show-more = Показати ще { $more } clear-group = Очистити групу clear-all = Очистити всі сповіщення do-not-disturb = Не турбувати -notification-settings = Параметри сповіщень... +notification-settings = Налаштування сповіщень... no-notifications = Немає сповіщень diff --git a/cosmic-applet-notifications/i18n/zh-CN/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/zh-CN/cosmic_applet_notifications.ftl index d87c17c7..8f22587f 100644 --- a/cosmic-applet-notifications/i18n/zh-CN/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/zh-CN/cosmic_applet_notifications.ftl @@ -1,14 +1,18 @@ -hours-ago = { NUMBER($duration) -> - [1] 1 小时前 - *[other] {$duration} 小时前 -} -minutes-ago = { NUMBER($duration) -> - [1] 1 分钟前 - *[other] {$duration} 分钟前 -} +hours-ago = + { $duration -> + [0] 刚刚 + [one] 1 小时前 + *[other] { $duration } 小时前 + } +minutes-ago = + { $duration -> + [0] 刚刚 + [one] 1 分钟前 + *[other] { $duration } 分钟前 + } show-less = 收起 -show-more = 显示更多 {$more} 项 -clear-group = 清除组 +show-more = 显示剩余 { $more } 项 +clear-group = 清除群组 clear-all = 清除所有通知 do-not-disturb = 勿扰模式 notification-settings = 通知设置... diff --git a/cosmic-applet-power/Cargo.toml b/cosmic-applet-power/Cargo.toml index c94f121d..041c0474 100644 --- a/cosmic-applet-power/Cargo.toml +++ b/cosmic-applet-power/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-power" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-power/data/com.system76.CosmicAppletPower.desktop b/cosmic-applet-power/data/com.system76.CosmicAppletPower.desktop index bfc65271..12ba7fd4 100644 --- a/cosmic-applet-power/data/com.system76.CosmicAppletPower.desktop +++ b/cosmic-applet-power/data/com.system76.CosmicAppletPower.desktop @@ -11,6 +11,7 @@ Name[nl]=Gebruikerssessie Name[sk]=Používateľská relácia Name[sv]=Användarsession Name[es]=Sesión de usuario +Name[it]=Sessione utente Type=Application Exec=cosmic-applet-power Terminal=false @@ -21,5 +22,6 @@ Icon=com.system76.CosmicAppletPower-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-CosmicHoverPopup=Auto X-OverflowPriority=10 diff --git a/cosmic-applet-power/i18n/ar/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/ar/cosmic_applet_power.ftl index 473fd73e..1c2a8a4f 100644 --- a/cosmic-applet-power/i18n/ar/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/ar/cosmic_applet_power.ftl @@ -2,13 +2,13 @@ power = الطاقة settings = الإعدادات... lock-screen = قفل الشاشة lock-screen-shortcut = سوبر + Escape -log-out = تسجيل الخروج +log-out = سجِّل الخروج log-out-shortcut = سوبر + Shift + Escape -suspend = عَلِّق -restart = إعادة التشغيل -shutdown = إيقاف التشغيل -confirm = تأكيد -cancel = إلغاء +suspend = علِّق +restart = أعد التشغيل +shutdown = أطفئ +confirm = أكِّد +cancel = ألغِ confirm-button = { $action -> [restart] { restart } diff --git a/cosmic-applet-power/i18n/bn/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/bn/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/cs/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/cs/cosmic_applet_power.ftl index 159ba0dd..26098fd2 100644 --- a/cosmic-applet-power/i18n/cs/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/cs/cosmic_applet_power.ftl @@ -23,7 +23,7 @@ confirm-title = [suspend] { suspend } [shutdown] { shutdown } [log-out] Ukončit všechny aplikace a odhlásit se - *[other] Použít vybranou akci + *[other] Vykonat vybranou akci } nyní? confirm-body = Systém { $action -> @@ -33,9 +33,9 @@ confirm-body = [lock-screen] se uzamkne [log-out] odhlásí uživatele *[other] vykoná vybranou akci - } automaticky za { $countdown } { $countdown -> - [one] sekundu. - [few] sekundy. - [many] sekund. - *[other] sekund. + } automaticky { $countdown -> + [0] nyní. + [one] za 1 sekundu. + [few] za { $countdown } sekundy. + *[other] za { $countdown } sekund. } diff --git a/cosmic-applet-power/i18n/fr/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/fr/cosmic_applet_power.ftl index 7928196a..ac44cb41 100644 --- a/cosmic-applet-power/i18n/fr/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/fr/cosmic_applet_power.ftl @@ -1,4 +1,4 @@ -power = Démarrage +power = Alimentation settings = Paramètres... lock-screen = Verrouiller la session lock-screen-shortcut = Super + Échap @@ -8,29 +8,29 @@ restart = Redémarrer shutdown = Éteindre confirm = Confirmer cancel = Annuler -confirm-button = { - $action -> +confirm-button = + { $action -> [restart] { restart } - [suspend] Mettre en veille - [shutdown] {shutdown } + [suspend] { suspend } + [shutdown] Éteindre [log-out] { log-out } - *[other] { confirm } -} + *[other] { confirm } + } confirm-title = { $action -> [restart] { restart } - [suspend] Mettre le système en veille + [suspend] { suspend } [shutdown] { shutdown } [log-out] Quitter toutes les applications et se déconnecter - *[other] Appliquer l'option choisie - } maintenant ? + *[other] Appliquer l'option choisie + } maintenant ? confirm-body = - Vous allez { $action -> - [restart] redémarrer l'ordinateur - [suspend] mettre l'ordinateur en veille - [shutdown] éteindre l'ordinateur - [lock-screen] verrouiller la session - [log-out] déconnecter l'utilisateur - *[other] appliquer l'option choisie + Cet ordinateur { $action -> + [restart] redémarrera + [suspend] se mettra en veille + [shutdown] s'éteindra + [lock-screen] se verrouillera + [log-out] se déconnectera + *[other] appliquera l'option choisie } automatiquement dans { $countdown } secondes. - +log-out-shortcut = Super + Maj + Échap diff --git a/cosmic-applet-power/i18n/ga/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/ga/cosmic_applet_power.ftl index 6818c3da..4af8373e 100644 --- a/cosmic-applet-power/i18n/ga/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/ga/cosmic_applet_power.ftl @@ -4,26 +4,26 @@ lock-screen = Glas an Scáileán lock-screen-shortcut = Super + Éalú log-out = Logáil Amach log-out-shortcut = Super + Shift + Éalú -suspend = Cuir ar Fionraí +suspend = Cuir ar fionraí restart = Atosaigh -shutdown = Múch +shutdown = Múchadh confirm = Deimhnigh cancel = Cealaigh -confirm-button = { - $action -> - [restart] atosaigh - [suspend] cuir ar fionraí - [shutdown] Múch an córas - [log-out] logáil amach - *[other] deimhnigh -} +confirm-button = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] Múchadh + [log-out] { log-out } + *[other] { confirm } + } confirm-title = { $action -> - [restart] atosaigh - [suspend] cuir ar fionraí - [shutdown] múch - [log-out] Dún gach aip agus logáil amach - *[other] Cuir an gníomh roghnaithe i bhfeidhm + [restart] { restart } + [suspend] { suspend } + [shutdown] { shutdown } + [log-out] Dún gach aipeanna agus logáil amach + *[other] Cuir an gníomh roghnaithe i bhfeidhm } anois? confirm-body = Déanfaidh an córas { $action -> @@ -32,5 +32,5 @@ confirm-body = [shutdown] múchadh [lock-screen] glasáil an scáileán [log-out] logáil amach - *[other] an gníomh roghnaithe a chur i bhfeidhm - } go huathoibríoch i gceann { $countdown } soicind. + *[other] an gníomh roghnaithe a chur i bhfeidhm + } go huathoibríoch i gceann { $countdown } soicindí. diff --git a/cosmic-applet-power/i18n/he/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/he/cosmic_applet_power.ftl index e69de29b..e4e925d3 100644 --- a/cosmic-applet-power/i18n/he/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/he/cosmic_applet_power.ftl @@ -0,0 +1,8 @@ +confirm-button = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] כיבוי + [log-out] { log-out } + *[other] { confirm } + } diff --git a/cosmic-applet-power/i18n/hu/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/hu/cosmic_applet_power.ftl index 4dec7ae8..9019650a 100644 --- a/cosmic-applet-power/i18n/hu/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/hu/cosmic_applet_power.ftl @@ -1,5 +1,5 @@ power = Főkapcsoló -settings = Beállítások... +settings = Beállítások… lock-screen = Képernyő zárolása lock-screen-shortcut = Super + Escape log-out = Kijelentkezés @@ -9,28 +9,28 @@ restart = Újraindítás shutdown = Leállítás confirm = Megerősítés cancel = Mégse -confirm-button = { - $action -> +confirm-button = + { $action -> [restart] { restart } - [suspend] { suspend} + [suspend] { suspend } [shutdown] Leállítás [log-out] { log-out } - *[other] { confirm} -} -confirm-title = - { $action -> + *[other] { confirm } + } +confirm-title = + { $action -> [restart] { restart } [suspend] { suspend } [shutdown] { shutdown } [log-out] Minden alkalmazás bezárása és kijelentkezés - *[other] Alkalmazzuk a kiválasztott műveletet + *[other] Alkalmazzuk a kiválasztott műveletet } most? -confirm-body = +confirm-body = A rendszer automatikusan { $action -> [restart] újra fog indulni [suspend] felfüggesztésre kerül [shutdown] leáll [lock-screen] zárolni fogja a képernyőt [log-out] kijelentkezik - *[other] alkalmazni fogja a kiválasztott műveletet - } { $countdown } másodperc múlva. + *[other] alkalmazni fogja a kiválasztott műveletet + } { $countdown } másodperc múlva diff --git a/cosmic-applet-power/i18n/ka/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/ka/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/kab/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/kab/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/kk/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/kk/cosmic_applet_power.ftl new file mode 100644 index 00000000..fc697a5e --- /dev/null +++ b/cosmic-applet-power/i18n/kk/cosmic_applet_power.ftl @@ -0,0 +1,36 @@ +cancel = Бас тарту +confirm = Растау +log-out = Жүйеден шығу +suspend = Ұйықтату +restart = Қайта іске қосу +shutdown = Сөндіру +power = Қуат +settings = Баптаулар... +lock-screen = Экранды бұғаттау +lock-screen-shortcut = Super + Escape +log-out-shortcut = Super + Shift + Escape +confirm-button = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] Сөндіру + [log-out] { log-out } + *[other] { confirm } + } +confirm-title = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] { shutdown } + [log-out] Барлық қолданбаларды жауып, жүйеден шығу + *[other] Таңдалған әрекетті іске асыру + } қазір ме? +confirm-body = + Жүйе { $action -> + [restart] қайта іске қосылады + [suspend] ұйықтатылады + [shutdown] сөндіріледі + [lock-screen] экранды бұғаттайды + [log-out] жүйеден шығады + *[other] таңдалған әрекетті іске асырады + } { $countdown } секундтан кейін автоматты түрде. diff --git a/cosmic-applet-power/i18n/kmr/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/kmr/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/ko/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/ko/cosmic_applet_power.ftl index 51421f5d..bf4e35c8 100644 --- a/cosmic-applet-power/i18n/ko/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/ko/cosmic_applet_power.ftl @@ -4,17 +4,33 @@ lock-screen = 화면 잠그기 lock-screen-shortcut = Super + Escape log-out = 로그아웃 suspend = 절전 -restart = 다시 시작 -shutdown = 시스템 종료 +restart = 재시작 +shutdown = 종료 confirm = 확인 cancel = 취소 -confirm-body = +confirm-body = { $countdown }초 후 { $action -> + [restart] 재시작이 + [suspend] 절전이 + [shutdown] 종료가 + [lock-screen] 화면 잠금이 + [log-out] 로그아웃이 + *[other] 선택한 동작이 + } 자동으로 실행됩니다. +confirm-button = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] 종료 + [log-out] { log-out } + *[other] { confirm } + } +log-out-shortcut = Super + Shift + Escape +confirm-title = + 지금 { $action -> [restart] { restart } [suspend] { suspend } [shutdown] { shutdown } - [lock-screen] Locking the screen - [log-out] Logging out - *[other] The selected action - } 동작이 실행됩니다. - + [log-out] 모든 앱 종료 후 로그아웃 + *[other] 선택된 동작을 실행 + }할까요? diff --git a/cosmic-applet-power/i18n/lt/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/lt/cosmic_applet_power.ftl index e69de29b..85c186e1 100644 --- a/cosmic-applet-power/i18n/lt/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/lt/cosmic_applet_power.ftl @@ -0,0 +1,35 @@ +confirm-button = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] Išjungti + [log-out] { log-out } + *[other] { confirm } + } +power = Maitinimas +confirm-body = + Sistema { $action -> + [restart] įsijungs iš naujo + [suspend] užmigs + [shutdown] išsijungs + [lock-screen] užrakins ekraną + [log-out] atjungs dabartinį naudotoją + *[other] taikys pasirinktą veiksmą + } automatiškai po { $countdown } sekundžių. +lock-screen = Užrakinti Ekraną +log-out = Atsijungti +restart = Paleisti iš naujo +log-out-shortcut = Super + Shift + Escape +cancel = Atšaukti +confirm = Patvirtinti +settings = Nustatymai... +lock-screen-shortcut = Super + Escape +confirm-title = + { $action -> + [restart] { restart } + [suspend] { suspend } + [shutdown] { shutdown } + [log-out] Uždaryti visas aplikacijas ir atsijungti + *[other] Taikyti pasirinktą veiksmą + } now? +shutdown = Išjungti diff --git a/cosmic-applet-power/i18n/nl/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/nl/cosmic_applet_power.ftl index b6238739..cf807cf3 100644 --- a/cosmic-applet-power/i18n/nl/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/nl/cosmic_applet_power.ftl @@ -1,36 +1,37 @@ power = Energie settings = Instellingen... -lock-screen = Scherm vergrendelen +lock-screen = Vergrendel scherm lock-screen-shortcut = Super + Esc log-out = Afmelden log-out-shortcut = Super + Shift + Esc suspend = Slaapstand -restart = Opnieuw opstarten -shutdown = Afsluiten -confirm = Bevestigen -cancel = Annuleren -confirm-button = { - $action -> +restart = Herstart +shutdown = Sluit af +confirm = Bevestig +cancel = Annuleer +confirm-button = + { $action -> [restart] { restart } [suspend] { suspend } [shutdown] { shutdown } [log-out] { log-out } - *[other] Uitvoeren -} -confirm-title = Nu - { $action -> + *[other] Uitvoeren + } +confirm-title = + Nu + { $action -> [restart] opnieuw opstarten? [suspend] in slaapstand gaan? [shutdown] afsluiten? [log-out] alle apps sluiten en afmelden? - *[other] de geselecteerde actie uitvoeren? - } -confirm-body = - De { $action -> - [restart] computer wordt in {$countdown} seconden automatisch opnieuw opgestart. - [suspend] computer wordt in {$countdown} seconden automatisch in slaapstand gezet. - [shutdown] computer wordt in {$countdown} seconden automatisch afgesloten. - [lock-screen] schermvergrendeling wordt in {$countdown} seconden automatisch geactiveerd. - [log-out] gebruiker wordt in {$countdown} seconden automatisch afgemeld. - *[other] geselecteerde actie wordt in {$countdown} seconden automatisch uitgevoerd. + *[other] de geselecteerde actie uitvoeren? + } +confirm-body = + De { $action -> + [restart] computer wordt in { $countdown } seconden automatisch opnieuw opgestart. + [suspend] computer wordt in { $countdown } seconden automatisch in slaapstand gezet. + [shutdown] computer wordt in { $countdown } seconden automatisch afgesloten. + [lock-screen] schermvergrendeling wordt in { $countdown } seconden automatisch geactiveerd. + [log-out] gebruiker wordt in { $countdown } seconden automatisch afgemeld. + *[other] geselecteerde actie wordt in { $countdown } seconden automatisch uitgevoerd. } diff --git a/cosmic-applet-power/i18n/pa/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/pa/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/ti/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/ti/cosmic_applet_power.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-power/i18n/uk/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/uk/cosmic_applet_power.ftl index b416bfcf..0962ef88 100644 --- a/cosmic-applet-power/i18n/uk/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/uk/cosmic_applet_power.ftl @@ -32,9 +32,5 @@ confirm-body = [lock-screen] заблокує екран [log-out] завершить сеанс *[other] виконає обрану дію - } автоматично за { $countdown } { $countdown -> - [one] секунду - [few] секунди - *[other] секунд - }. + } автоматично через { $countdown } секунд. log-out-shortcut = Super + Shift + Escape diff --git a/cosmic-applet-power/i18n/zh-CN/cosmic_applet_power.ftl b/cosmic-applet-power/i18n/zh-CN/cosmic_applet_power.ftl index 1c11aea8..cb53ea15 100644 --- a/cosmic-applet-power/i18n/zh-CN/cosmic_applet_power.ftl +++ b/cosmic-applet-power/i18n/zh-CN/cosmic_applet_power.ftl @@ -4,7 +4,7 @@ lock-screen = 锁定屏幕 lock-screen-shortcut = Super + Escape log-out = 登出 log-out-shortcut = Super + Shift + Escape -suspend = 挂起 +suspend = 待机 restart = 重启 shutdown = 关机 confirm = 确认 @@ -18,19 +18,19 @@ confirm-button = *[other] { confirm } } confirm-title = - 确认要{ $action -> + 立即{ $action -> [restart] { restart } [suspend] { suspend } [shutdown] { shutdown } [log-out] 退出所有应用并登出 - *[other] 应用所选操作 - }吗? + *[other] 执行所选操作 + }? confirm-body = 系统将在 { $countdown } 秒后自动{ $action -> [restart] 重启 - [suspend] 挂起 + [suspend] 待机 [shutdown] 关机 [lock-screen] 锁定屏幕 [log-out] 登出 - *[other] 应用所选操作 + *[other] 执行所选操作 }。 diff --git a/cosmic-applet-status-area/Cargo.toml b/cosmic-applet-status-area/Cargo.toml index 6880270b..fe85ad48 100644 --- a/cosmic-applet-status-area/Cargo.toml +++ b/cosmic-applet-status-area/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-status-area" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop b/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop index dfdb7b21..677c874a 100644 --- a/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop +++ b/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop @@ -11,6 +11,7 @@ Name[pt]=Área de Notificações Name[sk]=Panel oznámení Name[sv]=Fält för programikoner Name[es]=Bandeja de notificaciones +Name[it]=Area notifiche Type=Application Exec=cosmic-applet-status-area Terminal=false diff --git a/cosmic-applet-status-area/src/components/app.rs b/cosmic-applet-status-area/src/components/app.rs index 4240f2ae..e8d9f0b1 100644 --- a/cosmic-applet-status-area/src/components/app.rs +++ b/cosmic-applet-status-area/src/components/app.rs @@ -7,7 +7,7 @@ use cosmic::{ applet::token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, cctk::sctk::reexports::calloop, iced::{ - self, Subscription, + self, Length, Subscription, platform_specific::shell::commands::popup::{destroy_popup, get_popup}, window, }, @@ -20,6 +20,8 @@ use crate::{components::status_menu, subscriptions::status_notifier_watcher}; #[derive(Clone, Debug)] pub enum Msg { + None, + Activate(usize), Closed(window::Id), // XXX don't use index (unique window id? or I guess that's created and destroyed) StatusMenu((usize, status_menu::Msg)), @@ -56,7 +58,7 @@ impl App { fn resize_window(&self) -> app::Task { let icon_size = self.core.applet.suggested_size(true).0 as u32 - + self.core.applet.suggested_padding(true) as u32 * 2; + + self.core.applet.suggested_padding(true).1 as u32 * 2; let n = self.menus.len() as u32; window::resize( self.core.main_window_id().unwrap(), @@ -73,8 +75,8 @@ impl App { } })?; - let button_total_size = - self.core.applet.suggested_size(true).0 + self.core.applet.suggested_padding(true) * 2; + let button_total_size = self.core.applet.suggested_size(true).0 + + self.core.applet.suggested_padding(true).1 * 2; let menu_count = self.menus.len(); @@ -95,22 +97,11 @@ impl App { let overflow_index = self.overflow_index().unwrap_or(0); let children = self.menus.iter().skip(overflow_index).map(|(id, menu)| { mouse_area( - match menu.icon_pixmap() { - Some(icon) if menu.icon_name() == "" => self - .core - .applet - .icon_button_from_handle(icon.clone().symbolic(true)), - _ => self.core.applet.icon_button(menu.icon_name()), - } - .on_press_down(Msg::TogglePopup(*id)), + menu_icon_button(&self.core.applet, &menu).on_press_down(Msg::TogglePopup(*id)), ) .on_enter(Msg::Hovered(*id)) .into() }); - let theme = self.core.system_theme(); - let cosmic = theme.cosmic(); - let corners = cosmic.corner_radii; - let pad = corners.radius_m[0]; self.core .applet @@ -158,6 +149,26 @@ impl cosmic::Application for App { fn update(&mut self, message: Msg) -> app::Task { match message { + Msg::None => Task::none(), + Msg::Activate(id) => { + if let Some(token_tx) = self.token_tx.as_ref() { + let _ = token_tx.send(TokenRequest { + app_id: Self::APP_ID.to_string(), + exec: format!("activate:{}", id), + }); + } else { + if let Some(menu) = self.menus.get(&id) { + let item_proxy = menu.item.item_proxy().clone(); + return Task::future(async move { + match item_proxy.activate(0, 0).await { + Ok(_) => cosmic::action::app(Msg::None), + Err(_) => cosmic::action::app(Msg::TogglePopup(id)), + } + }); + } + } + Task::none() + } Msg::Closed(surface) => { if self.popup == Some(surface) { self.popup = None; @@ -237,7 +248,7 @@ impl cosmic::Application for App { Some((i, self.core.main_window_id().unwrap())) } }) - .unwrap_or((0, self.core.main_window_id().unwrap())); + .unwrap_or((i, self.core.main_window_id().unwrap())); let mut popup_settings = self .core @@ -250,11 +261,11 @@ impl cosmic::Application for App { PanelAnchor::Left | PanelAnchor::Right ) { let suggested_size = self.core.applet.suggested_size(false).1 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).1; popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32; } else { let suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).1; popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32; } cmds.push(get_popup(popup_settings)); @@ -276,6 +287,36 @@ impl cosmic::Application for App { return Task::none(); } TokenUpdate::ActivationToken { token, exec: id } => { + if let Some(id_str) = id.strip_prefix("activate:") { + if let Ok(real_id) = id_str.parse::() { + if let Some(menu) = self.menus.get(&real_id) { + let item_proxy = menu.item.item_proxy().clone(); + let token = token.clone(); + let id = real_id; + return Task::future(async move { + if let Some(t) = token { + match item_proxy.provide_xdg_activation_token(t).await { + Ok(_) => { + println!("Token provided successfully to {}", id) + } + Err(e) => eprintln!( + "Failed to provide token to {}: {}", + id, e + ), + } + } + match item_proxy.activate(0, 0).await { + Ok(_) => cosmic::action::app(Msg::None), + Err(err) => { + eprintln!("Activate failed: {}", err); + cosmic::action::app(Msg::TogglePopup(id)) + } + } + }); + } + } + return Task::none(); + } if let Some(((state, id), token)) = str::parse(&id) .ok() .and_then(|id: usize| self.menus.get_mut(&id).map(|m| (m, id))) @@ -319,7 +360,7 @@ impl cosmic::Application for App { Some((i, self.core.main_window_id().unwrap())) } }) - .unwrap_or((0, self.core.main_window_id().unwrap())); + .unwrap_or((i, self.core.main_window_id().unwrap())); let mut popup_settings = self .core @@ -331,12 +372,12 @@ impl cosmic::Application for App { self.core.applet.anchor, PanelAnchor::Left | PanelAnchor::Right ) { - let suggested_size = self.core.applet.suggested_size(false).1 - + 2 * self.core.applet.suggested_padding(false); + let suggested_size = self.core.applet.suggested_size(true).1 + + 2 * self.core.applet.suggested_padding(true).1; popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32; } else { - let suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + let suggested_size = self.core.applet.suggested_size(true).0 + + 2 * self.core.applet.suggested_padding(true).0; popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32; } cmds.push(get_popup(popup_settings)); @@ -368,13 +409,13 @@ impl cosmic::Application for App { self.core.applet.anchor, PanelAnchor::Left | PanelAnchor::Right ) { - let suggested_size = self.core.applet.suggested_size(false).1 - + 2 * self.core.applet.suggested_padding(false); + let suggested_size = self.core.applet.suggested_size(true).1 + + 2 * self.core.applet.suggested_padding(true).1; popup_settings.positioner.anchor_rect.y = overflow_index as i32 * suggested_size as i32; } else { - let suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + let suggested_size = self.core.applet.suggested_size(true).0 + + 2 * self.core.applet.suggested_padding(true).0; popup_settings.positioner.anchor_rect.x = overflow_index as i32 * suggested_size as i32; } @@ -418,11 +459,11 @@ impl cosmic::Application for App { PanelAnchor::Left | PanelAnchor::Right ) { let suggested_size = self.core.applet.suggested_size(false).1 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).1; popup_settings.positioner.anchor_rect.y = i as i32 * suggested_size as i32; } else { let suggested_size = self.core.applet.suggested_size(false).0 - + 2 * self.core.applet.suggested_padding(false); + + 2 * self.core.applet.suggested_padding(false).1; popup_settings.positioner.anchor_rect.x = i as i32 * suggested_size as i32; } cmds.push(get_popup(popup_settings)); @@ -452,18 +493,10 @@ impl cosmic::Application for App { .iter() .take(overflow_index.unwrap_or(self.menus.len())) .map(|(id, menu)| { - mouse_area( - match menu.icon_pixmap() { - Some(icon) if menu.icon_name() == "" => self - .core - .applet - .icon_button_from_handle(icon.clone().symbolic(true)), - _ => self.core.applet.icon_button(menu.icon_name()), - } - .on_press_down(Msg::TogglePopup(*id)), - ) - .on_enter(Msg::Hovered(*id)) - .into() + mouse_area(menu_icon_button(&self.core.applet, &menu).on_press(Msg::Activate(*id))) + .on_right_press(Msg::TogglePopup(*id)) + .on_enter(Msg::Hovered(*id)) + .into() }); self.core @@ -530,6 +563,47 @@ impl cosmic::Application for App { } } +fn menu_icon_button<'a>( + applet: &'a cosmic::applet::Context, + menu: &'a status_menu::State, +) -> cosmic::widget::Button<'a, Msg> { + let icon = menu.icon_handle().clone(); + + let theme = cosmic::theme::active(); + let theme = theme.cosmic(); + + let suggested = applet.suggested_size(true); + let padding = applet.suggested_padding(true).1; + // let (major_padding, applet_padding_minor_axis) = applet.suggested_padding(true); + // let (horizontal_padding, vertical_padding) = if applet.is_horizontal() { + // (major_padding, applet_padding_minor_axis) + // } else { + // (applet_padding_minor_axis, major_padding) + // }; + let symbolic = icon.symbolic; + + cosmic::widget::button::custom( + cosmic::widget::layer_container( + cosmic::widget::icon(icon) + .class(if symbolic { + cosmic::theme::Svg::Custom(std::rc::Rc::new(|theme| { + cosmic::iced_widget::svg::Style { + color: Some(theme.cosmic().background.on.into()), + } + })) + } else { + cosmic::theme::Svg::default() + }) + .width(Length::Fixed(suggested.0 as f32)) + .height(Length::Fixed(suggested.1 as f32)), + ) + .center(Length::Fill), + ) + .width(Length::Fixed((suggested.0 + 2 * padding) as f32)) + .height(Length::Fixed((suggested.1 + 2 * padding) as f32)) + .class(cosmic::theme::Button::AppletIcon) +} + pub fn main() -> iced::Result { cosmic::applet::run::(()) } diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index 37cb102f..f76ea784 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -6,8 +6,9 @@ use cosmic::{ applet::{menu_button, token::subscription::TokenRequest}, cctk::sctk::reexports::calloop, iced, - widget::icon, + widget::icon::{self, IconFallback}, }; +use std::path::{Path, PathBuf}; use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem}; @@ -20,12 +21,11 @@ pub enum Msg { } pub struct State { - item: StatusNotifierItem, + pub item: StatusNotifierItem, layout: Option, expanded: Option, - icon_name: String, // TODO handle icon with multiple sizes? - icon_pixmap: Option, + icon_handle: icon::Handle, click_event: Option<(i32, bool)>, } @@ -36,8 +36,9 @@ impl State { item, layout: None, expanded: None, - icon_name: String::new(), - icon_pixmap: None, + icon_handle: icon::from_name("application-default") + .prefer_svg(true) + .handle(), click_event: None, }, iced::Task::none(), @@ -61,30 +62,46 @@ impl State { iced::Task::none() } Msg::Icon(update) => { - match update { - IconUpdate::Name(name) => { - self.icon_name = name; - } - IconUpdate::Pixmap(icons) => { - self.icon_pixmap = icons - .into_iter() - .max_by_key(|i| (i.width, i.height)) - .map(|mut i| { - if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() { - // App sent invalid icon data during initialization - show placeholder until NewIcon signal - eprintln!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing", - i.width, i.height, i.bytes.len()); - return icon::from_name("dialog-question").symbolic(true).handle(); - } - // Convert ARGB to RGBA - for pixel in i.bytes.chunks_exact_mut(4) { - pixel.rotate_left(1); - } - icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes) - }); + let icon_name = update.name.unwrap_or_default(); + + // Use the icon pixmap if an icon was not defined by name. + if icon_name.is_empty() { + let icon_pixmap = update.pixmap.and_then(|icons| icons + .into_iter() + .max_by_key(|i| (i.width, i.height)) + .map(|mut i| { + if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() { + // App sent invalid icon data during initialization - show placeholder until NewIcon signal + tracing::debug!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing", + i.width, i.height, i.bytes.len()); + return icon::from_name("dialog-question").symbolic(true).handle(); + } + // Convert ARGB to RGBA + for pixel in i.bytes.chunks_exact_mut(4) { + pixel.rotate_left(1); + } + icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes) + })); + + if let Some(icon) = icon_pixmap { + self.icon_handle = icon.clone(); + return iced::Task::none(); } } + // Load icon by path if the name is a path. + self.icon_handle = if Path::new(&icon_name).exists() { + icon::from_path(Path::new(&icon_name).to_path_buf()).symbolic(true) + } else { + icon::from_name(icon_name) + .prefer_svg(true) + .fallback(Some(IconFallback::Names(vec![ + "application-default".into(), + "application-x-executable".into(), + ]))) + .handle() + }; + iced::Task::none() } Msg::Click(id, is_submenu) => { @@ -101,9 +118,19 @@ impl State { let Some((id, is_submenu)) = self.click_event else { return iced::Task::none(); }; - - let menu_proxy = self.item.menu_proxy().clone(); let item_proxy = self.item.item_proxy().clone(); + + let Some(menu_proxy) = self.item.menu_proxy().cloned() else { + tokio::spawn(async move { + let _ = item_proxy.provide_xdg_activation_token(token).await; + if let Err(err) = item_proxy.activate(0, 0).await { + tracing::error!( + "Error activating status notifier item without menu proxy: {err:?}" + ); + } + }); + return iced::Task::none(); + }; tokio::spawn(async move { let _ = item_proxy.provide_xdg_activation_token(token).await; let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await; @@ -126,12 +153,8 @@ impl State { self.item.name() } - pub fn icon_name(&self) -> &str { - &self.icon_name - } - - pub fn icon_pixmap(&self) -> Option<&icon::Handle> { - self.icon_pixmap.as_ref() + pub fn icon_handle(&self) -> &icon::Handle { + &self.icon_handle } pub fn popup_view(&self) -> cosmic::Element<'_, Msg> { @@ -150,7 +173,9 @@ impl State { } pub fn opened(&self) { - let menu_proxy = self.item.menu_proxy().clone(); + let Some(menu_proxy) = self.item.menu_proxy().cloned() else { + return; + }; tokio::spawn(async move { let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await; let _ = menu_proxy.about_to_show(0).await; @@ -158,7 +183,9 @@ impl State { } pub fn closed(&self) { - let menu_proxy = self.item.menu_proxy().clone(); + let Some(menu_proxy) = self.item.menu_proxy().cloned() else { + return; + }; tokio::spawn(async move { let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await; }); diff --git a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs index c9e17218..ee12cbe0 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs @@ -4,13 +4,14 @@ use cosmic::iced::{self, Subscription}; use futures::{FutureExt, StreamExt}; use rustc_hash::FxHashMap; +use std::path::PathBuf; use zbus::zvariant::{self, OwnedValue}; #[derive(Clone, Debug)] pub struct StatusNotifierItem { name: String, item_proxy: StatusNotifierItemProxy<'static>, - menu_proxy: DBusMenuProxy<'static>, + menu_proxy: Option>, } #[derive(Clone, Debug, zvariant::Value)] @@ -21,9 +22,10 @@ pub struct Icon { } #[derive(Clone, Debug)] -pub enum IconUpdate { - Name(String), - Pixmap(Vec), +pub struct IconUpdate { + pub name: Option, + pub pixmap: Option>, + // pub theme_path: Option, } impl StatusNotifierItem { @@ -42,7 +44,22 @@ impl StatusNotifierItem { .build() .await?; - let menu_path = item_proxy.menu().await?; + // XX: some items will not implement this but have a menu anyway + let is_menu = item_proxy.item_is_menu().await; + + let menu_path = item_proxy.menu().await; + + // Why would an item say it has no menu but provide a menu path? Slack does this. + let is_menu = menu_path.is_ok() || is_menu.unwrap_or(false); + + if !is_menu { + return Ok(Self { + name, + item_proxy, + menu_proxy: None, + }); + } + let menu_path = menu_path?; let menu_proxy = DBusMenuProxy::builder(connection) .destination(dest.to_string())? .path(menu_path)? @@ -52,7 +69,7 @@ impl StatusNotifierItem { Ok(Self { name, item_proxy, - menu_proxy, + menu_proxy: Some(menu_proxy), }) } @@ -62,13 +79,22 @@ impl StatusNotifierItem { // TODO: Only fetch changed part of layout, if that's any faster pub fn layout_subscription(&self) -> iced::Subscription> { - let menu_proxy = self.menu_proxy.clone(); + let Some(menu_proxy) = self.menu_proxy.clone() else { + return Subscription::none(); + }; Subscription::run_with_id( format!("status-notifier-item-layout-{}", &self.name), async move { let initial = futures::stream::once(get_layout(menu_proxy.clone())); - let layout_updated_stream = menu_proxy.receive_layout_updated().await.unwrap(); - let updates = layout_updated_stream.then(move |_| get_layout(menu_proxy.clone())); + + let layout_updated = menu_proxy.receive_layout_updated().await.unwrap(); + let props_updated = menu_proxy.receive_items_properties_updated().await.unwrap(); + + // Merge both streams - any update triggers a layout refetch + let updates = + futures::stream_select!(layout_updated.map(|_| ()), props_updated.map(|_| ())) + .then(move |()| get_layout(menu_proxy.clone())); + initial.chain(updates) } .flatten_stream(), @@ -76,22 +102,15 @@ impl StatusNotifierItem { } pub fn icon_subscription(&self) -> iced::Subscription { - fn icon_events( - item_proxy: StatusNotifierItemProxy<'static>, - ) -> impl futures::Stream + 'static { - async move { - let icon_name = item_proxy.icon_name().await; - let icon_pixmap = item_proxy.icon_pixmap().await; - futures::stream::iter( - [ - icon_name.map(IconUpdate::Name), - icon_pixmap.map(IconUpdate::Pixmap), - ] - .into_iter() - .filter_map(Result::ok), - ) + async fn icon_events(item_proxy: StatusNotifierItemProxy<'static>) -> IconUpdate { + let icon_name = item_proxy.icon_name().await; + let icon_pixmap = item_proxy.icon_pixmap().await; + // let icon_theme_path = item_proxy.icon_theme_path().await.map(PathBuf::from); + IconUpdate { + name: icon_name.ok(), + pixmap: icon_pixmap.ok(), + // theme_path: icon_theme_path.ok().filter(|x| !x.as_os_str().is_empty()), } - .flatten_stream() } let item_proxy = self.item_proxy.clone(); @@ -101,14 +120,14 @@ impl StatusNotifierItem { let new_icon_stream = item_proxy.receive_new_icon().await.unwrap(); futures::stream::once(async {}) .chain(new_icon_stream.map(|_| ())) - .flat_map(move |()| icon_events(item_proxy.clone())) + .then(move |()| icon_events(item_proxy.clone())) } .flatten_stream(), ) } - pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> { - &self.menu_proxy + pub fn menu_proxy(&self) -> Option<&DBusMenuProxy<'static>> { + self.menu_proxy.as_ref() } pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> { @@ -128,6 +147,9 @@ pub trait StatusNotifierItem { #[zbus(property)] fn icon_name(&self) -> zbus::Result; + #[zbus(property)] + fn icon_theme_path(&self) -> zbus::Result; + // https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/Icons #[zbus(property)] fn icon_pixmap(&self) -> zbus::Result>; @@ -135,10 +157,17 @@ pub trait StatusNotifierItem { #[zbus(property)] fn menu(&self) -> zbus::Result; + #[zbus(property)] + fn item_is_menu(&self) -> zbus::Result; + #[zbus(signal)] fn new_icon(&self) -> zbus::Result<()>; fn provide_xdg_activation_token(&self, token: String) -> zbus::Result<()>; + + fn activate(&self, x: i32, y: i32) -> zbus::Result<()>; + + fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>; } #[derive(Clone, Debug)] @@ -261,4 +290,11 @@ pub trait DBusMenu { #[zbus(signal)] fn layout_updated(&self, revision: u32, parent: i32) -> zbus::Result<()>; + + #[zbus(signal)] + fn items_properties_updated( + &self, + updated_props: Vec<(i32, std::collections::HashMap)>, + removed_props: Vec<(i32, Vec)>, + ) -> zbus::Result<()>; } diff --git a/cosmic-applet-tiling/Cargo.toml b/cosmic-applet-tiling/Cargo.toml index feffc317..5e9464d3 100644 --- a/cosmic-applet-tiling/Cargo.toml +++ b/cosmic-applet-tiling/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-tiling" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-tiling/data/com.system76.CosmicAppletTiling.desktop b/cosmic-applet-tiling/data/com.system76.CosmicAppletTiling.desktop index b121ed48..64db9301 100644 --- a/cosmic-applet-tiling/data/com.system76.CosmicAppletTiling.desktop +++ b/cosmic-applet-tiling/data/com.system76.CosmicAppletTiling.desktop @@ -12,6 +12,7 @@ Name[nl]=Vensters tegelen Name[sk]=Dláždenie okien Name[sv]=Kakling Name[es]=Ventanas mosaico +Name[it]=Tiling Type=Application Exec=cosmic-applet-tiling Terminal=false @@ -22,5 +23,6 @@ Icon=com.system76.CosmicAppletTiling-symbolic StartupNotify=true NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-HostWaylandDisplay=true X-CosmicHoverPopup=Auto diff --git a/cosmic-applet-tiling/i18n/ar/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/ar/cosmic_applet_tiling.ftl index e45c75de..3159b6d4 100644 --- a/cosmic-applet-tiling/i18n/ar/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/ar/cosmic_applet_tiling.ftl @@ -1,4 +1,4 @@ -tile-windows = بلّط النوافذ تلقائيًا +tile-windows = بلّط النوافذ آليًا tile-current = بلّط مساحة العمل الحالية shortcuts = اختصارات navigate-windows = تنقل بين النوافذ diff --git a/cosmic-applet-tiling/i18n/bg/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/bg/cosmic_applet_tiling.ftl index 86f1eec4..f97bc641 100644 --- a/cosmic-applet-tiling/i18n/bg/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/bg/cosmic_applet_tiling.ftl @@ -5,16 +5,16 @@ navigate-windows = Навигиране move-window = Преместване на прозорец toggle-floating-window = Превключване на плаването на прозорец view-all-shortcuts = Показване на всички клавишни комбинации... -active-hint = Подсказка +active-hint = Рамка на активния прозорец gaps = Празнина floating-window-exceptions = Изключения за плаващите прозорци... window-management-settings = Настройки за управление на прозорците... -all-workspaces = Всички пространства -per-workspace = За всяко пространство +all-workspaces = Всички работни пространства +per-workspace = За всяко работно пространство super = Super shift = Shift arrow-keys = стрелките tiled = Прилепнато floating = Плаващо -autotile-behavior = Прилепване на прозорците на пространствата -new-workspace = Поведение на новите пространства \ No newline at end of file +autotile-behavior = Прилепване на прозорците на работните пространствата +new-workspace = Поведение при нови работни пространства diff --git a/cosmic-applet-tiling/i18n/bn/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/bn/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/cs/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/cs/cosmic_applet_tiling.ftl index 843e53b6..5709ec6d 100644 --- a/cosmic-applet-tiling/i18n/cs/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/cs/cosmic_applet_tiling.ftl @@ -5,7 +5,7 @@ navigate-windows = Přepínání oken move-window = Přesouvání oken toggle-floating-window = Přepnutí plovoucího okna view-all-shortcuts = Zobrazit všechny zkratky... -active-hint = Zvýraznění aktivního okna +active-hint = Ohraničení aktivního okna gaps = Mezery floating-window-exceptions = Výjimky plovoucích oken... window-management-settings = Nastavení správy oken... diff --git a/cosmic-applet-tiling/i18n/fr/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/fr/cosmic_applet_tiling.ftl index 02ac66d6..78a6c028 100644 --- a/cosmic-applet-tiling/i18n/fr/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/fr/cosmic_applet_tiling.ftl @@ -1,20 +1,20 @@ -tile-windows = Ajuster automatiquement les fenêtres -tile-current = Ajuster automatiquement le bureau actuel +tile-windows = Agencer automatiquement les fenêtres +tile-current = Agencer automatiquement l'espace de travail actuel shortcuts = Raccourcis navigate-windows = Naviguer les fenêtres move-window = Déplacer la fenêtre toggle-floating-window = Activer les fenêtres flottantes view-all-shortcuts = Voir tous les raccourcis... -active-hint = Indice de sélection +active-hint = Indice de fenêtre active gaps = Marges floating-window-exceptions = Exceptions des fenêtres flottantes... window-management-settings = Paramètres d'agencement des fenêtres... -all-workspaces = Tous les bureaux -per-workspace = Par bureau +all-workspaces = Tous les espaces de travail +per-workspace = Par espace de travail super = Super -shift = Shift +shift = Maj arrow-keys = flèches -tiled = Ajusté -floating = Flottante -autotile-behavior = Ajuster les fenêtes -new-workspace = Comportement des nouveaux bureaux +tiled = Agencé +floating = Flottant +autotile-behavior = Agencer les fenêtres des espaces de travail +new-workspace = Comportement des nouveaux espaces de travail diff --git a/cosmic-applet-tiling/i18n/ga/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/ga/cosmic_applet_tiling.ftl index 52f00d66..34a2b167 100644 --- a/cosmic-applet-tiling/i18n/ga/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/ga/cosmic_applet_tiling.ftl @@ -1,20 +1,20 @@ tile-windows = Gnéas ailtigh na fuinneog go huathoibríoch -tile-current = Ailtigh an spás oibre reatha -shortcuts = Gnéithe tapa -navigate-windows = Nascleanaigh fuinneog -move-window = Bogadh fuinneog +tile-current = Tíleáil an spás oibre reatha +shortcuts = Aicearraí +navigate-windows = Nascleanaigh fuinneoga +move-window = Bog fuinneog toggle-floating-window = Scoránaigh fuinneog scairte -view-all-shortcuts = Taispeáin na gnéithe tapa uile... -active-hint = Comhairle gníomhach -gaps = Spásanna -floating-window-exceptions = Eisceachtaí fuinneog scairte... +view-all-shortcuts = Taispeáin gach aicearra… +active-hint = Leid ghníomhach +gaps = Bearnaí +floating-window-exceptions = Eisceachtaí fuinneoige snámhacha... window-management-settings = Socruithe bainistíochta fuinneog... all-workspaces = Gach spás oibre per-workspace = Per spás oibre super = Super shift = Shift arrow-keys = eochracha saigheada -tiled = Ailtigh -floating = Scairte -autotile-behavior = Ailtigh fuinneoga ar spásanna oibre +tiled = Tílithe +floating = Ar snámh +autotile-behavior = Fuinneoga tílithe ar spásanna oibre new-workspace = Iompraíocht spás oibre nua diff --git a/cosmic-applet-tiling/i18n/hu/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/hu/cosmic_applet_tiling.ftl index a6972769..7d622463 100644 --- a/cosmic-applet-tiling/i18n/hu/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/hu/cosmic_applet_tiling.ftl @@ -1,14 +1,14 @@ tile-windows = Automatikus ablakcsempézés -tile-current = Az aktuális munkaterület automatikus csempézése -shortcuts = Parancsikonok +tile-current = Jelenlegi munkaterület csempézése +shortcuts = Gyorsbillentyűk navigate-windows = Ablakok navigálása move-window = Ablak mozgatása toggle-floating-window = Az ablakok lebegtetésének be- és kikapcsolása -view-all-shortcuts = Az összes billentyűparancs megtekintése... +view-all-shortcuts = Az összes gyorsbillentyű megtekintése… active-hint = Aktív ablak kiemelése gaps = Hézagok -floating-window-exceptions = Lebegő ablak kivételek... -window-management-settings = Ablakkezelési beállítások... +floating-window-exceptions = Lebegő ablak kivételek… +window-management-settings = Ablakkezelési beállítások… all-workspaces = Összes munkaterület per-workspace = Munkaterületenként super = Super diff --git a/cosmic-applet-tiling/i18n/ka/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/ka/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/kab/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/kab/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/kk/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/kk/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..d0506860 --- /dev/null +++ b/cosmic-applet-tiling/i18n/kk/cosmic_applet_tiling.ftl @@ -0,0 +1,20 @@ +tile-windows = Терезелерді автоматты түрде плиткалау +tile-current = Ағымдағы жұмыс орнын плиткалау +shortcuts = Жарлықтар +navigate-windows = Терезелер бойынша шарлау +move-window = Терезені жылжыту +toggle-floating-window = Қалқымалы терезе режимін ауыстыру +view-all-shortcuts = Барлық жарлықтарды көру... +active-hint = Белсенді нұсқау +gaps = Аралықтар +floating-window-exceptions = Қалқымалы терезе ерекшеліктері... +window-management-settings = Терезелерді басқару баптаулары... +all-workspaces = Барлық жұмыс орындары +per-workspace = Жұмыс орны бойынша +super = Super +shift = Shift +arrow-keys = бағдаршалар +tiled = Плиткаланған +floating = Қалқымалы +autotile-behavior = Жұмыс орындарындағы терезелерді плиткалау +new-workspace = Жаңа жұмыс орнының әрекеті diff --git a/cosmic-applet-tiling/i18n/kmr/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/kmr/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/ko/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/ko/cosmic_applet_tiling.ftl index e69de29b..5762d85e 100644 --- a/cosmic-applet-tiling/i18n/ko/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/ko/cosmic_applet_tiling.ftl @@ -0,0 +1,20 @@ +arrow-keys = 방향키 +tiled = 타일링 +active-hint = 활성 창 강조 +navigate-windows = 창 탐색 +toggle-floating-window = 창 플로팅 전환 +all-workspaces = 모든 작업 공간 +per-workspace = 작업 공간마다 +autotile-behavior = 작업 공간에 창 타일링 +tile-current = 현재 작업 공간을 타일링 +floating = 플로팅 +gaps = 간격 +move-window = 창 이동 +tile-windows = 창 자동 타일링 +view-all-shortcuts = 모든 바로가기 보기... +shortcuts = 바로가기 +shift = Shift +floating-window-exceptions = 플로팅 창 예외... +super = Super +window-management-settings = 창 관리 설정... +new-workspace = 새 작업 공간 동작 diff --git a/cosmic-applet-tiling/i18n/lt/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/lt/cosmic_applet_tiling.ftl index e69de29b..4f581edd 100644 --- a/cosmic-applet-tiling/i18n/lt/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/lt/cosmic_applet_tiling.ftl @@ -0,0 +1,18 @@ +super = Super +arrow-keys = rodyklės +tiled = Automatiškai išdėstyti +active-hint = Aktyvi žymė +navigate-windows = Langų valdymas +toggle-floating-window = Įjungti / išjungti lango slankumą +all-workspaces = Visos darbo sritys +per-workspace = Darbo sritys atskirai +autotile-behavior = Automatiškai išdėstyti langus darbo srityse +tile-current = Automatiškai išdėstyti esamą darbo sritį +floating = Slankusis +gaps = Tarpai +move-window = Perkelti langą +tile-windows = Automatiškai išdėstyti langus +shift = Shift +floating-window-exceptions = Slankiojo lango klaida... +window-management-settings = Langų valdymo nustatymai... +new-workspace = Naujos darbo srities parinktys diff --git a/cosmic-applet-tiling/i18n/nl/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/nl/cosmic_applet_tiling.ftl index 9ffd516c..58e49f95 100644 --- a/cosmic-applet-tiling/i18n/nl/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/nl/cosmic_applet_tiling.ftl @@ -9,11 +9,11 @@ active-hint = Visuele hint actief venster gaps = Ruimte rond getegelde vensters floating-window-exceptions = Uitzonderingen voor zwevende vensters... window-management-settings = Instellingen voor vensterbeheer... -all-workspaces = Op alle werkbladen +all-workspaces = Alle werkbladen per-workspace = Per werkblad super = Super shift = Shift -arrow-keys = pijltjesknoppen +arrow-keys = pijltjes tiled = Getegeld floating = Zwevend autotile-behavior = Vensters tegelen op werkbladen diff --git a/cosmic-applet-tiling/i18n/pa/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/pa/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/pt-BR/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/pt-BR/cosmic_applet_tiling.ftl index 872d87cf..8bc9c6c9 100644 --- a/cosmic-applet-tiling/i18n/pt-BR/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/pt-BR/cosmic_applet_tiling.ftl @@ -1,5 +1,5 @@ tile-windows = Organizar janelas lado a lado automaticamente -tile-current = Modo de janelas lado a lado +tile-current = Organizar janelas lado a lado shortcuts = Atalhos navigate-windows = Navegar pelas janelas move-window = Mover janela @@ -7,14 +7,14 @@ toggle-floating-window = Alternar entre janelas flutuantes view-all-shortcuts = Ver todos os atalhos... active-hint = Realçar janela ativa gaps = Espaçamento -floating-window-exceptions = Exceções de janela flutuante... +floating-window-exceptions = Exceções de janelas flutuantes... window-management-settings = Configurações de gerenciamento de janelas... all-workspaces = Todas as áreas de trabalho per-workspace = Por área de trabalho super = Super shift = Shift arrow-keys = setas -tiled = Lado a lado +tiled = Organizadas lado a lado floating = Flutuante -autotile-behavior = Agrupar janelas lado a lado nas áreas de trabalho +autotile-behavior = Organizar janelas lado a lado nas áreas de trabalho new-workspace = Novo comportamento de área de trabalho diff --git a/cosmic-applet-tiling/i18n/ti/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/ti/cosmic_applet_tiling.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-tiling/i18n/uk/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/uk/cosmic_applet_tiling.ftl index d18e5eef..e2397dae 100644 --- a/cosmic-applet-tiling/i18n/uk/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/uk/cosmic_applet_tiling.ftl @@ -1,20 +1,20 @@ -tile-windows = Автоматичне укладання вікон -tile-current = Автоматичне укладання в поточному робочому просторі -shortcuts = Клавіатурні скорочення -navigate-windows = Перехід між вікнами +tile-windows = Автоматично укласти вікна +tile-current = Укласти вікна в поточному робочому просторі +shortcuts = Сполучення клавіш +navigate-windows = Навігація між вікнами move-window = Перемістити вікно toggle-floating-window = Перемкнути плавучість вікна -view-all-shortcuts = Переглянути всі клав. скорочення... +view-all-shortcuts = Переглянути всі сполучення клавіш… active-hint = Підсвічувати активне вікно gaps = Відступи -floating-window-exceptions = Винятки для плаваючих вікон... -window-management-settings = Параметри керування вікнами... -all-workspaces = На всіх робочих просторах -per-workspace = На окремому робочому просторі +floating-window-exceptions = Винятки для плавучих вікон... +window-management-settings = Налаштування керування вікнами… +all-workspaces = Всі робочі простори +per-workspace = Окремо для кожного робочого простору super = Super shift = Shift arrow-keys = стрілки tiled = Укладання -floating = Плавання -autotile-behavior = Укладати вікна на робочих просторах -new-workspace = Поведінка на новому робочому просторі +floating = Плавуча +autotile-behavior = Плиткове розміщення вікон у робочих просторах +new-workspace = Поведінка нового робочого простору diff --git a/cosmic-applet-tiling/i18n/zh-CN/cosmic_applet_tiling.ftl b/cosmic-applet-tiling/i18n/zh-CN/cosmic_applet_tiling.ftl index 1bbd49f5..0b14099b 100644 --- a/cosmic-applet-tiling/i18n/zh-CN/cosmic_applet_tiling.ftl +++ b/cosmic-applet-tiling/i18n/zh-CN/cosmic_applet_tiling.ftl @@ -3,11 +3,11 @@ tile-current = 自动平铺当前工作区 shortcuts = 快捷键 navigate-windows = 切换窗口 move-window = 移动窗口 -toggle-floating-window = 切换浮动窗口 +toggle-floating-window = 切换悬浮窗口 view-all-shortcuts = 显示所有快捷键... active-hint = 聚焦窗口提示 gaps = 窗口间距 -floating-window-exceptions = 浮动窗口例外... +floating-window-exceptions = 悬浮窗口例外... window-management-settings = 窗口管理设置... all-workspaces = 所有工作区 per-workspace = 每个工作区 @@ -15,6 +15,6 @@ super = Super shift = Shift arrow-keys = 方向键 tiled = 平铺 -floating = 浮动 -autotile-behavior = 自动平铺所有工作区的窗口 +floating = 悬浮 +autotile-behavior = 自动平铺所有工作区窗口 new-workspace = 新工作区行为 diff --git a/cosmic-applet-time/Cargo.toml b/cosmic-applet-time/Cargo.toml index 230e4423..6fb13f1b 100644 --- a/cosmic-applet-time/Cargo.toml +++ b/cosmic-applet-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-time" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" @@ -16,7 +16,7 @@ tokio.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true -icu = { version = "2.0.0", features = ["compiled_data"] } +icu = { version = "2.1.1", features = ["compiled_data"] } zbus.workspace = true timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" } logind-zbus = "5.3.2" diff --git a/cosmic-applet-time/data/com.system76.CosmicAppletTime.desktop b/cosmic-applet-time/data/com.system76.CosmicAppletTime.desktop index 2fcc635c..69805072 100644 --- a/cosmic-applet-time/data/com.system76.CosmicAppletTime.desktop +++ b/cosmic-applet-time/data/com.system76.CosmicAppletTime.desktop @@ -11,6 +11,7 @@ Name[nl]=Datum, tijd en agenda Name[sk]=Dátum, čas a kalendár Name[es]=Fecha, hora y calendario Name[sv]=Datum, Tid & Kalender +Name[it]=Data, ora e calendario Type=Application Exec=cosmic-applet-time Terminal=false @@ -20,6 +21,7 @@ Keywords=COSMIC;Iced; Icon=com.system76.CosmicAppletTime-symbolic NoDisplay=true X-CosmicApplet=true +X-CosmicShrinkable=true X-HostWaylandDisplay=true X-CosmicHoverPopup=Auto # No shrink priority, as this applet is always visible when configured diff --git a/cosmic-applet-time/i18n/bg/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/bg/cosmic_applet_time.ftl index b25f239a..3e4cf40a 100644 --- a/cosmic-applet-time/i18n/bg/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/bg/cosmic_applet_time.ftl @@ -1 +1 @@ -datetime-settings = Настройки на дата, час и календар... \ No newline at end of file +datetime-settings = Настройки на дата, час и календар... diff --git a/cosmic-applet-time/i18n/bn/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/bn/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/i18n/fr/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/fr/cosmic_applet_time.ftl index 9a20177a..88f6a7e9 100644 --- a/cosmic-applet-time/i18n/fr/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/fr/cosmic_applet_time.ftl @@ -1 +1 @@ -datetime-settings = Paramètres date, heure et agenda... \ No newline at end of file +datetime-settings = Paramètres date, heure et agenda... diff --git a/cosmic-applet-time/i18n/ga/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/ga/cosmic_applet_time.ftl index 55f7b8ad..c668e259 100644 --- a/cosmic-applet-time/i18n/ga/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/ga/cosmic_applet_time.ftl @@ -1 +1 @@ -datetime-settings = Socruithe Dáta, Ama agus Féilire... +datetime-settings = Socruithe dáta, ama agus féilire... diff --git a/cosmic-applet-time/i18n/hu/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/hu/cosmic_applet_time.ftl index dfc6b38c..ad864eab 100644 --- a/cosmic-applet-time/i18n/hu/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/hu/cosmic_applet_time.ftl @@ -1 +1 @@ -datetime-settings = Dátum, idő és naptár beállítások... +datetime-settings = Dátum-, idő- és naptárbeállítások… diff --git a/cosmic-applet-time/i18n/ka/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/ka/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/i18n/kab/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/kab/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/i18n/kk/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/kk/cosmic_applet_time.ftl new file mode 100644 index 00000000..c33cb0e3 --- /dev/null +++ b/cosmic-applet-time/i18n/kk/cosmic_applet_time.ftl @@ -0,0 +1 @@ +datetime-settings = Күн, уақыт және күнтізбе баптаулары... diff --git a/cosmic-applet-time/i18n/kmr/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/kmr/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/i18n/ko/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/ko/cosmic_applet_time.ftl index e69de29b..98d48e6c 100644 --- a/cosmic-applet-time/i18n/ko/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/ko/cosmic_applet_time.ftl @@ -0,0 +1 @@ +datetime-settings = 날짜, 시간 및 캘린더 설정... diff --git a/cosmic-applet-time/i18n/lt/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/lt/cosmic_applet_time.ftl index e69de29b..f4982077 100644 --- a/cosmic-applet-time/i18n/lt/cosmic_applet_time.ftl +++ b/cosmic-applet-time/i18n/lt/cosmic_applet_time.ftl @@ -0,0 +1 @@ +datetime-settings = Datos, Laiko ir Kalendoriaus nustatymai... diff --git a/cosmic-applet-time/i18n/pa/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/pa/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/i18n/ti/cosmic_applet_time.ftl b/cosmic-applet-time/i18n/ti/cosmic_applet_time.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-time/src/window.rs b/cosmic-applet-time/src/window.rs index aa47b1ae..d37f99f9 100644 --- a/cosmic-applet-time/src/window.rs +++ b/cosmic-applet-time/src/window.rs @@ -163,13 +163,27 @@ impl Window { calendar } + /// Format with strftime if non-empty and ignore errors. + /// + /// Do not use to_string(). The formatter panics on invalid specifiers. + fn maybe_strftime(&self) -> Option { + // strftime may override locale specific elements so it stands alone rather + // than using ICU. + (!self.config.format_strftime.is_empty()) + .then(|| { + let mut s = String::new(); + self.now + .format(&self.config.format_strftime) + .write_to(&mut s) + .map(|_| s) + .ok() + }) + .flatten() + } + fn vertical_layout(&self) -> Element<'_, Message> { - let elements: Vec> = if !self.config.format_strftime.is_empty() { - // strftime formatter may override locale specific elements so it stands alone rather - // than using ICU to determine a format. - self.now - .format(&self.config.format_strftime) - .to_string() + let elements: Vec> = if let Some(strftime) = self.maybe_strftime() { + strftime .split_whitespace() .map(|piece| self.core.applet.text(piece.to_owned()).into()) .collect() @@ -226,7 +240,8 @@ impl Window { date_time_col, horizontal_space().width(Length::Fixed( (self.core.applet.suggested_size(true).0 - + 2 * self.core.applet.suggested_padding(true)) as f32 + + 2 * self.core.applet.suggested_padding(true).1) + as f32 )) ) .align_x(Alignment::Center), @@ -234,8 +249,8 @@ impl Window { } fn horizontal_layout(&self) -> Element<'_, Message> { - let formatted_date = if !self.config.format_strftime.is_empty() { - self.now.format(&self.config.format_strftime).to_string() + let formatted_date = if let Some(strftime) = self.maybe_strftime() { + strftime } else { let datetime = self.create_datetime(&self.now); let mut prefs = DateTimeFormatterPreferences::from(self.locale.clone()); @@ -247,7 +262,7 @@ impl Window { if self.config.show_date_in_top_panel { if self.config.show_weekday { - let mut fs = fieldsets::MDET::long(); + let mut fs = fieldsets::MDET::medium(); if !self.config.show_seconds { fs = fs.with_time_precision(TimePrecision::Minute); } @@ -256,7 +271,7 @@ impl Window { .format(&datetime) .to_string() } else { - let mut fs = fieldsets::MDT::long(); + let mut fs = fieldsets::MDT::medium(); if !self.config.show_seconds { fs = fs.with_time_precision(TimePrecision::Minute); } @@ -282,7 +297,8 @@ impl Window { self.core.applet.text(formatted_date), container(vertical_space().height(Length::Fixed( (self.core.applet.suggested_size(true).1 - + 2 * self.core.applet.suggested_padding(true)) as f32 + + 2 * self.core.applet.suggested_padding(true).1) + as f32 ))) ) .align_y(Alignment::Center), @@ -662,9 +678,9 @@ impl cosmic::Application for Window { self.vertical_layout() }) .padding(if horizontal { - [0, self.core.applet.suggested_padding(true)] + [0, self.core.applet.suggested_padding(true).0] } else { - [self.core.applet.suggested_padding(true), 0] + [self.core.applet.suggested_padding(true).0, 0] }) .on_press_down(Message::TogglePopup) .class(cosmic::theme::Button::AppletIcon); diff --git a/cosmic-applet-workspaces/Cargo.toml b/cosmic-applet-workspaces/Cargo.toml index 8abae0db..ee9e04db 100644 --- a/cosmic-applet-workspaces/Cargo.toml +++ b/cosmic-applet-workspaces/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applet-workspaces" -version = "0.1.1" +version = "1.0.2" authors = ["Ashley Wulber "] edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop b/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop index 4c6046bc..134ef2cc 100644 --- a/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop +++ b/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop @@ -11,6 +11,7 @@ Name[nl]=Genummerde werkbladen Name[sk]=Číslované pracovné plochy Name[sv]=Numrerade arbetsytor Name[es]=Espacios de trabajo numerados +Name[it]=Spazi di lavoro numerati Type=Application Exec=cosmic-applet-workspaces Terminal=false diff --git a/cosmic-applet-workspaces/i18n/bn/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/bn/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/i18n/ga/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/ga/cosmic_applet_workspaces.ftl index e81dc6b2..58323c18 100644 --- a/cosmic-applet-workspaces/i18n/ga/cosmic_applet_workspaces.ftl +++ b/cosmic-applet-workspaces/i18n/ga/cosmic_applet_workspaces.ftl @@ -1 +1 @@ -cosmic-applet-workspaces = COSMIC Spásanna Oibre +cosmic-applet-workspaces = COSMIC spásanna oibre diff --git a/cosmic-applet-workspaces/i18n/ka/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/ka/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/i18n/kab/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/kab/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/i18n/kk/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/kk/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..6aeaff04 --- /dev/null +++ b/cosmic-applet-workspaces/i18n/kk/cosmic_applet_workspaces.ftl @@ -0,0 +1 @@ +cosmic-applet-workspaces = COSMIC жұмыс орындары diff --git a/cosmic-applet-workspaces/i18n/kmr/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/kmr/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/i18n/ko/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/ko/cosmic_applet_workspaces.ftl index f9478cd6..4af5924c 100644 --- a/cosmic-applet-workspaces/i18n/ko/cosmic_applet_workspaces.ftl +++ b/cosmic-applet-workspaces/i18n/ko/cosmic_applet_workspaces.ftl @@ -1 +1 @@ -cosmic-applet-workspaces = 코스믹 작업공간 +cosmic-applet-workspaces = COSMIC 작업 공간 diff --git a/cosmic-applet-workspaces/i18n/lt/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/lt/cosmic_applet_workspaces.ftl index e69de29b..1a096680 100644 --- a/cosmic-applet-workspaces/i18n/lt/cosmic_applet_workspaces.ftl +++ b/cosmic-applet-workspaces/i18n/lt/cosmic_applet_workspaces.ftl @@ -0,0 +1 @@ +cosmic-applet-workspaces = COSMIC Workspaces diff --git a/cosmic-applet-workspaces/i18n/pa/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/pa/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/i18n/ti/cosmic_applet_workspaces.ftl b/cosmic-applet-workspaces/i18n/ti/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..e69de29b diff --git a/cosmic-applet-workspaces/src/components/app.rs b/cosmic-applet-workspaces/src/components/app.rs index b3a73e40..07f4c13e 100644 --- a/cosmic-applet-workspaces/src/components/app.rs +++ b/cosmic-applet-workspaces/src/components/app.rs @@ -21,6 +21,7 @@ use cosmic::{ widget::{button, column, row}, }, iced_core::{Background, Border}, + scroll::DiscreteScrollState, surface, widget::{Id, autosize, container, horizontal_space, vertical_space}, }; @@ -31,14 +32,12 @@ use crate::{ wayland_subscription::{WorkspacesUpdate, workspaces}, }; -use std::{ - process::Command as ShellCommand, - sync::LazyLock, - time::{Duration, Instant}, -}; +use std::{process::Command as ShellCommand, sync::LazyLock, time::Duration}; static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| Id::new("autosize-main")); +const SCROLL_RATE_LIMIT: Duration = Duration::from_millis(200); + pub fn run() -> cosmic::iced::Result { cosmic::applet::run::(()) } @@ -54,9 +53,7 @@ struct IcedWorkspacesApplet { workspaces: Vec, workspace_tx: Option>, layout: Layout, - scroll: f64, - next_scroll: Option, - last_scroll: Instant, + scroll: DiscreteScrollState, } impl IcedWorkspacesApplet { @@ -74,7 +71,7 @@ impl IcedWorkspacesApplet { return index; }; let button_total_size = self.core.applet.suggested_size(true).0 - + self.core.applet.suggested_padding(true) * 2 + + self.core.applet.suggested_padding(true).1 * 2 + 4; let btn_count = max_major_axis_len / button_total_size as u32; if btn_count >= self.workspaces.len() as u32 { @@ -111,9 +108,7 @@ impl cosmic::Application for IcedWorkspacesApplet { core, workspaces: Vec::new(), workspace_tx: Option::default(), - scroll: 0.0, - next_scroll: None, - last_scroll: Instant::now(), + scroll: DiscreteScrollState::default().rate_limit(Some(SCROLL_RATE_LIMIT)), }, Task::none(), ) @@ -148,53 +143,21 @@ impl cosmic::Application for IcedWorkspacesApplet { } } Message::WheelScrolled(delta) => { - let (delta, debounce) = match delta { - ScrollDelta::Lines { x, y } => ((x + y) as f64, false), - ScrollDelta::Pixels { x, y } => ((x + y) as f64, true), - }; + let discrete_delta = self.scroll.update(delta); + if discrete_delta.y != 0 { + if let Some(w_i) = self + .workspaces + .iter() + .position(|w| w.state.contains(ext_workspace_handle_v1::State::Active)) + { + let d_i = (w_i as isize - discrete_delta.y) + .rem_euclid(self.workspaces.len() as isize) + as usize; - let dur = if debounce { - Duration::from_millis(350) - } else { - Duration::from_millis(200) - }; - if self.last_scroll.elapsed() > Duration::from_millis(100) - || self.scroll * delta < 0.0 - { - self.next_scroll = None; - self.scroll = 0.0; - } - self.last_scroll = Instant::now(); - - self.scroll += delta; - if let Some(next) = self.next_scroll { - if next > Instant::now() { - return cosmic::iced::Task::none(); - } - self.next_scroll = None; - } - - if self.scroll.abs() < 1.0 { - return cosmic::iced::Task::none(); - } - self.next_scroll = Some(Instant::now() + dur); - if let Some(w_i) = self - .workspaces - .iter() - .position(|w| w.state.contains(ext_workspace_handle_v1::State::Active)) - { - let max_w = self.workspaces.len().wrapping_sub(1); - let d_i = if self.scroll > 0.0 { - if w_i == 0 { max_w } else { w_i.wrapping_sub(1) } - } else if w_i == max_w { - 0 - } else { - w_i.wrapping_add(1) - }; - self.scroll = 0.0; - if let Some(w) = self.workspaces.get(d_i) { if let Some(tx) = self.workspace_tx.as_mut() { - let _ = tx.try_send(WorkspaceEvent::Activate(w.handle.clone())); + let _ = tx.try_send(WorkspaceEvent::Activate( + self.workspaces[d_i].handle.clone(), + )); } } } @@ -219,8 +182,8 @@ impl cosmic::Application for IcedWorkspacesApplet { self.core.applet.anchor, PanelAnchor::Top | PanelAnchor::Bottom ); - let suggested_total = - self.core.applet.suggested_size(true).0 + self.core.applet.suggested_padding(true) * 2; + let suggested_total = self.core.applet.suggested_size(true).0 + + self.core.applet.suggested_padding(true).1 * 2; let suggested_window_size = self.core.applet.suggested_window_size(); let popup_index = self.popup_index().unwrap_or(self.workspaces.len()); @@ -245,9 +208,9 @@ impl cosmic::Application for IcedWorkspacesApplet { .align_y(Alignment::Center), ) .padding(if horizontal { - [0, self.core.applet.suggested_padding(true)] + [0, self.core.applet.suggested_padding(true).1] } else { - [self.core.applet.suggested_padding(true), 0] + [self.core.applet.suggested_padding(true).1, 0] }) .on_press( if w.state.contains(ext_workspace_handle_v1::State::Active) { diff --git a/cosmic-applets-config/Cargo.toml b/cosmic-applets-config/Cargo.toml index f89f3fe9..b6999f16 100644 --- a/cosmic-applets-config/Cargo.toml +++ b/cosmic-applets-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applets-config" -version = "0.1.0" +version = "1.0.2" edition = "2024" [dependencies] diff --git a/cosmic-applets/Cargo.toml b/cosmic-applets/Cargo.toml index c39ad720..8e17b573 100644 --- a/cosmic-applets/Cargo.toml +++ b/cosmic-applets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-applets" -version = "0.1.1" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-panel-button/Cargo.toml b/cosmic-panel-button/Cargo.toml index 613e6ba0..dc6ffd9a 100644 --- a/cosmic-panel-button/Cargo.toml +++ b/cosmic-panel-button/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmic-panel-button" -version = "0.1.0" +version = "1.0.2" edition = "2024" license = "GPL-3.0-only" diff --git a/cosmic-panel-button/src/lib.rs b/cosmic-panel-button/src/lib.rs index bbb31729..8b94c8aa 100644 --- a/cosmic-panel-button/src/lib.rs +++ b/cosmic-panel-button/src/lib.rs @@ -41,6 +41,47 @@ enum Msg { Surface(surface::Action), } +impl Button { + pub fn icon_button_from_handle<'a, Message: Clone + 'static>( + &self, + icon: cosmic::widget::icon::Handle, + ) -> cosmic::widget::Button<'a, Message> { + let theme = cosmic::theme::active(); + let theme = theme.cosmic(); + + let suggested = self.core.applet.suggested_size(icon.symbolic); + let (major_padding, applet_padding_minor_axis) = + self.core.applet.suggested_padding(icon.symbolic); + let (horizontal_padding, vertical_padding) = if self.core.applet.is_horizontal() { + (major_padding, applet_padding_minor_axis) + } else { + (applet_padding_minor_axis, major_padding) + }; + let symbolic = icon.symbolic; + + cosmic::widget::button::custom( + cosmic::widget::layer_container( + cosmic::widget::icon(icon) + .class(if symbolic { + cosmic::theme::Svg::Custom(std::rc::Rc::new(|theme| { + cosmic::iced_widget::svg::Style { + color: Some(theme.cosmic().background.on.into()), + } + })) + } else { + cosmic::theme::Svg::default() + }) + .width(Length::Fixed(suggested.0 as f32)) + .height(Length::Fixed(suggested.1 as f32)), + ) + .center(Length::Fill), + ) + .width(Length::Fixed((suggested.0 + 2 * horizontal_padding) as f32)) + .height(Length::Fixed((suggested.1 + 2 * vertical_padding) as f32)) + .class(cosmic::theme::Button::AppletIcon) + } +} + impl cosmic::Application for Button { type Message = Msg; type Executor = cosmic::SingleThreadExecutor; @@ -124,13 +165,11 @@ impl cosmic::Application for Button { { cosmic::Element::from( self.core.applet.applet_tooltip::( - self.core - .applet - .icon_button_from_handle( - cosmic::widget::icon::from_name(self.desktop.icon.clone().unwrap()) - .handle(), - ) - .on_press_down(Msg::Press), + self.icon_button_from_handle( + cosmic::widget::icon::from_name(self.desktop.icon.clone().unwrap()) + .handle(), + ) + .on_press_down(Msg::Press), self.desktop.name.clone(), false, Msg::Surface, @@ -142,13 +181,13 @@ impl cosmic::Application for Button { self.core.applet.text(&self.desktop.name), vertical_space().height(Length::Fixed( (self.core.applet.suggested_size(true).1 - + 2 * self.core.applet.suggested_padding(true)) + + 2 * self.core.applet.suggested_padding(true).1) as f32 )) ) .align_y(iced::Alignment::Center); cosmic::widget::button::custom(content) - .padding([0, self.core.applet.suggested_padding(true)]) + .padding([0, self.core.applet.suggested_padding(true).0]) .class(cosmic::theme::Button::AppletIcon) .on_press_down(Msg::Press) .into() diff --git a/cosmic-panel-launcher-button/data/com.system76.CosmicPanelLauncherButton.desktop b/cosmic-panel-launcher-button/data/com.system76.CosmicPanelLauncherButton.desktop index a25ac181..9b65429c 100644 --- a/cosmic-panel-launcher-button/data/com.system76.CosmicPanelLauncherButton.desktop +++ b/cosmic-panel-launcher-button/data/com.system76.CosmicPanelLauncherButton.desktop @@ -11,6 +11,7 @@ Name[nl]=Knop snelstarter Name[sk]=Tlačidlo spúšťača Name[es]=Botón del lanzador Name[sv]=Knapp för programstartare +Name[it]=Tasto Launcher Type=Application Exec=cosmic-panel-button com.system76.CosmicLauncher Terminal=false diff --git a/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop b/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop index a53d0d1e..d3907a1b 100644 --- a/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop +++ b/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop @@ -11,6 +11,7 @@ Name[nl]=Knop werkbladenoverzicht Name[sk]=Tlačidlo pracovných plôch Name[es]=Botón de espacios de trabajo Name[sv]=Knapp för arbetsytor +Name[it]=Tasto Spazi di lavoro Type=Application Exec=cosmic-panel-button com.system76.CosmicWorkspaces Terminal=false diff --git a/debian/changelog b/debian/changelog index 44f7780f..e3d8e02e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,6 @@ -cosmic-applets (0.1.0) UNRELEASED; urgency=medium +cosmic-applets (1.0.2) noble; urgency=medium + [ Ashley Wulber ] * Initial release. - -- Ashley Wulber Thu, 07 Apr 2022 09:39:19 -0700 + -- Michael Murphy Tue, 13 Jan 2026 17:20:30 +0100 diff --git a/flake.nix b/flake.nix index f5f5e51c..82162a72 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ pkgDef = { pname = "cosmic-applets"; - version = "0.1.0"; + version = "1.0.0"; src = nix-filter.lib.filter { root = ./.; exclude = [ diff --git a/justfile b/justfile index 54e11026..a1ea651c 100644 --- a/justfile +++ b/justfile @@ -72,4 +72,12 @@ vendor: [private] vendor-extract: rm -rf vendor - tar pxf vendor.tar \ No newline at end of file + tar pxf vendor.tar + +# Bump cargo version, create git commit, and create tag +tag version: + find -type f -name Cargo.toml -exec sed -i '0,/^version/s/^version.*/version = "{{version}}"/' '{}' \; -exec git add '{}' \; + cargo check + cargo clean + dch -D noble -v {{version}} + git add Cargo.lock debian/changelog diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 00822fdf..ff100edc 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.85.1" +channel = "1.90.0"