From 04197b90552a7faca8e8696aca04b4895722b646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tales=20A=2E=20Mendon=C3=A7a?= Date: Thu, 2 Jul 2026 00:11:03 -0300 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix:=20make=20Arch=20?= =?UTF-8?q?package=20build=20self-contained?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove external path dependencies on big-rust-components so makepkg can build from the mirrored source tree used by CI/CD. - add local subprocess, HTTP, and UI helper modules - restore reqwest for manager HTTP requests - copy package data without preserving ownership in fakeroot - ignore generated makepkg package archives Validated with: - ./scripts/validate-customizations.sh - makepkg -f - cargo build --release --bin big-webapps-gui --locked --- .gitignore | 1 + Cargo.lock | 1180 +++++++++++++---- Cargo.toml | 2 +- crates/webapps-core/Cargo.toml | 1 - crates/webapps-core/src/desktop/paths.rs | 6 +- crates/webapps-core/src/lib.rs | 1 + crates/webapps-core/src/subprocess.rs | 69 + crates/webapps-exec/Cargo.toml | 1 - crates/webapps-exec/src/launch.rs | 12 +- crates/webapps-manager/Cargo.toml | 7 +- crates/webapps-manager/src/browser_dialog.rs | 12 +- .../webapps-manager/src/favicon/download.rs | 3 +- crates/webapps-manager/src/favicon/mod.rs | 2 +- crates/webapps-manager/src/http_client.rs | 116 ++ crates/webapps-manager/src/lib.rs | 2 + .../webapps-manager/src/platform/desktop.rs | 31 + .../webapps-manager/src/platform/dialogs.rs | 40 + .../src/platform/file_dialogs.rs | 130 ++ .../webapps-manager/src/platform/info_row.rs | 48 + crates/webapps-manager/src/platform/mod.rs | 7 + .../src/platform/status_page.rs | 58 + crates/webapps-manager/src/platform/theme.rs | 21 + .../webapps-manager/src/platform/tooltip.rs | 14 + .../webapps-manager/src/relm4_window/empty.rs | 23 +- .../webapps-manager/src/relm4_window/list.rs | 1 - .../webapps-manager/src/relm4_window/mod.rs | 10 +- .../webapps-manager/src/relm4_window/row.rs | 10 +- .../src/relm4_window/shell_spec.rs | 31 +- crates/webapps-manager/src/service/browser.rs | 8 +- .../src/service/browser_url.rs | 2 +- crates/webapps-manager/src/style.rs | 2 +- .../webapps-manager/src/template_gallery.rs | 5 +- .../src/webapp_dialog/handlers/lifecycle.rs | 2 +- .../src/webapp_dialog/handlers/media.rs | 2 +- .../webapp_dialog/ui/sections/appearance.rs | 5 +- .../src/webapp_dialog/ui/sections/behavior.rs | 5 +- .../src/webapp_dialog/ui/sections/website.rs | 2 +- crates/webapps-manager/src/welcome_dialog.rs | 10 +- .../src/window/actions/about.rs | 2 +- .../src/window/actions/browse.rs | 2 +- .../src/window/actions/import_export.rs | 4 +- .../src/window/actions/remove_all.rs | 3 +- .../src/window/actions/remove_multiple.rs | 14 +- .../webapps-manager/src/window/component.rs | 15 +- crates/webapps-manager/src/window/list.rs | 12 +- crates/webapps-manager/src/window/mod.rs | 3 - .../webapps-manager/src/window/shortcuts.rs | 2 +- .../src/window/shortcuts_window.rs | 5 +- crates/webapps-manager/src/window/ui.rs | 2 +- crates/webapps-viewer/Cargo.toml | 2 - crates/webapps-viewer/src/main.rs | 1 + crates/webapps-viewer/src/platform/desktop.rs | 31 + crates/webapps-viewer/src/platform/dialogs.rs | 25 + .../src/platform/file_dialogs.rs | 47 + .../webapps-viewer/src/platform/info_row.rs | 29 + crates/webapps-viewer/src/platform/mod.rs | 6 + crates/webapps-viewer/src/platform/theme.rs | 21 + crates/webapps-viewer/src/platform/tooltip.rs | 6 + crates/webapps-viewer/src/window/chrome.rs | 3 +- .../webapps-viewer/src/window/context_menu.rs | 3 +- .../src/window/downloads/mod.rs | 3 +- crates/webapps-viewer/src/window/loading.rs | 3 +- .../src/window/permissions/mod.rs | 3 +- .../src/window/shortcuts/mod.rs | 3 +- .../src/window/shortcuts/webview_actions.rs | 3 +- .../src/window/shortcuts/window_actions.rs | 3 +- .../src/window/shortcuts/zoom_actions.rs | 3 +- .../src/window/shortcuts_window.rs | 5 +- packaging/arch/PKGBUILD | 4 +- 69 files changed, 1738 insertions(+), 412 deletions(-) create mode 100644 crates/webapps-core/src/subprocess.rs create mode 100644 crates/webapps-manager/src/http_client.rs create mode 100644 crates/webapps-manager/src/platform/desktop.rs create mode 100644 crates/webapps-manager/src/platform/dialogs.rs create mode 100644 crates/webapps-manager/src/platform/file_dialogs.rs create mode 100644 crates/webapps-manager/src/platform/info_row.rs create mode 100644 crates/webapps-manager/src/platform/mod.rs create mode 100644 crates/webapps-manager/src/platform/status_page.rs create mode 100644 crates/webapps-manager/src/platform/theme.rs create mode 100644 crates/webapps-manager/src/platform/tooltip.rs create mode 100644 crates/webapps-viewer/src/platform/desktop.rs create mode 100644 crates/webapps-viewer/src/platform/dialogs.rs create mode 100644 crates/webapps-viewer/src/platform/file_dialogs.rs create mode 100644 crates/webapps-viewer/src/platform/info_row.rs create mode 100644 crates/webapps-viewer/src/platform/mod.rs create mode 100644 crates/webapps-viewer/src/platform/theme.rs create mode 100644 crates/webapps-viewer/src/platform/tooltip.rs diff --git a/.gitignore b/.gitignore index c1658523..e6f8d74e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ PLANNING.md # Packaging build artifacts (makepkg $srcdir/$pkgdir mirror — regenerated by PKGBUILD prepare()) packaging/arch/src/ packaging/arch/pkg/ +packaging/arch/*.pkg.tar* # Audit scratch (internal pipeline ledger — local only) tmp/ diff --git a/Cargo.lock b/Cargo.lock index 62a4dc9c..ed30453e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -58,14 +58,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "async-channel" @@ -80,53 +80,34 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.5.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "big-app-kit" -version = "0.1.0" -dependencies = [ - "big-os-kit", - "big-relm4-components", - "gtk4", - "libadwaita", - "log", - "relm4", - "serde", - "serde_json", -] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "big-os-kit" -version = "0.1.0" -dependencies = [ - "bon", - "libc", - "log", - "serde", - "serde_json", - "thiserror", -] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "big-relm4-components" -version = "0.1.0" -dependencies = [ - "big-os-kit", - "gtk4", - "libadwaita", - "log", - "relm4", -] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block" @@ -135,35 +116,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] -name = "bon" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" -dependencies = [ - "bon-macros", - "rustversion", -] - -[[package]] -name = "bon-macros" -version = "3.9.1" +name = "bumpalo" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" -dependencies = [ - "darling", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn", -] +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] -name = "bumpalo" -version = "3.20.2" +name = "bytes" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cairo-rs" @@ -171,7 +133,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -190,9 +152,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -200,9 +162,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ "smallvec", "target-lexicon", @@ -214,11 +176,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -238,9 +206,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -269,6 +237,22 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -299,37 +283,35 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.23.0" +name = "defmt" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ - "darling_core", - "darling_macro", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "darling_core" -version = "0.23.0" +name = "defmt-macros" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ - "ident_case", + "defmt-parser", + "proc-macro-error2", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "darling_core", - "quote", - "syn", + "thiserror", ] [[package]] @@ -371,14 +353,14 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -408,9 +390,9 @@ checksum = "b04dc5a38e4f151a79d9f2451ae6037fb6eaf5cba34771f44781f80e508498e3" [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -418,9 +400,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -442,7 +424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -468,11 +450,11 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.3.4", ] [[package]] @@ -528,7 +510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e72ed92b67c146290f88e9c89d60ca163ea417a446f61ffd7b72df3e7f1dfd5" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -646,9 +628,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" +checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -661,9 +643,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" +checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -705,9 +687,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", ] [[package]] @@ -732,9 +727,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.22.5" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401b600a9795c46ff45890146968b712c96ce4e9393798804133e137bd81d89c" +checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" dependencies = [ "futures-channel", "futures-core", @@ -749,24 +744,24 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" +checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "glib" -version = "0.22.5" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b7df55594e0e787d1560e23f7e12d7360d0b22e7b7c228ec2488b9e59b1b6b" +checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" dependencies = [ - "bitflags", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -783,9 +778,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.22.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda575994e3689b1bc12f89c3df621ead46ff292623b76b4710a3a5b79be54bb" +checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" dependencies = [ "heck", "proc-macro2", @@ -795,9 +790,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.22.3" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eb23a616a3dbc7fc15bbd26f58756ff0b04c8a894df3f0680cd21011db6a642" +checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" dependencies = [ "libc", "system-deps", @@ -805,9 +800,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.22.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eda93f09d3778f38255b231b17ef67195013a592c91624a4daf8bead875565" +checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" dependencies = [ "glib-sys", "libc", @@ -816,32 +811,30 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" +checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff" dependencies = [ "glib", "graphene-sys", - "libc", ] [[package]] name = "graphene-sys" -version = "0.22.0" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" +checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04" dependencies = [ "glib-sys", "libc", - "pkg-config", "system-deps", ] [[package]] name = "gsk4" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" +checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff" dependencies = [ "cairo-rs", "gdk4", @@ -854,9 +847,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.11.1" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" +checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -870,9 +863,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d47a7ca9ec6f50b5ace32eaaf11fe152c9bbc4f780a35e42c9b7fc5b046f9c" +checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9" dependencies = [ "cairo-rs", "field-offset", @@ -891,9 +884,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.11.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" +checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -903,9 +896,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a25bd07084651c77bb6e7bce7d4cea8d9f98d210acee473e400a9106bc0ce50" +checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -922,9 +915,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -942,6 +935,104 @@ dependencies = [ "markup5ever", ] +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1024,12 +1115,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -1043,9 +1128,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1061,6 +1146,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is-docker" version = "0.2.0" @@ -1117,10 +1208,11 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -1130,9 +1222,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -1141,11 +1233,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1188,15 +1281,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "libc", ] @@ -1237,9 +1330,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "malloc_buf" @@ -1263,9 +1362,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memoffset" @@ -1276,6 +1375,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1325,15 +1435,20 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.3" +version = "5.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a" dependencies = [ "is-wsl", "libc", - "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" @@ -1342,13 +1457,12 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "pango" -version = "0.22.4" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4804fb6018c6604eac198f0f897320d3696c9af7983cde056f07cef93cac9202" +checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" dependencies = [ "gio", "glib", - "libc", "pango-sys", ] @@ -1393,12 +1507,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1478,9 +1586,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -1494,6 +1602,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1501,22 +1618,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "prettyplease" -version = "0.2.37" +name = "proc-macro-crate" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", - "syn", + "quote", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1528,11 +1657,66 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -1543,13 +1727,48 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -1565,9 +1784,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1588,9 +1807,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relm4" @@ -1627,6 +1846,60 @@ dependencies = [ "syn", ] +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -1648,11 +1921,58 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1662,12 +1982,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "scc" -version = "2.4.0" +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "sdd", + "windows-sys 0.61.2", ] [[package]] @@ -1692,10 +2018,27 @@ dependencies = [ ] [[package]] -name = "sdd" -version = "3.0.10" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "selectors" @@ -1703,7 +2046,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cssparser", "derive_more", "log", @@ -1754,9 +2097,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1783,26 +2126,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serial_test" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" dependencies = [ "futures-executor", "futures-util", "log", "once_cell", "parking_lot", - "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", @@ -1820,15 +2174,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -1838,9 +2192,19 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] [[package]] name = "soup3" @@ -1913,17 +2277,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1950,9 +2329,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "temp-dir" @@ -1967,10 +2346,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2013,13 +2392,43 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.52.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ + "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", ] [[package]] @@ -2046,7 +2455,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -2083,14 +2492,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -2099,7 +2508,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -2114,6 +2523,51 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -2145,6 +2599,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2157,6 +2617,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2193,6 +2659,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2201,18 +2676,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2221,11 +2696,21 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2233,9 +2718,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -2246,18 +2731,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" dependencies = [ "phf", "phf_codegen", @@ -2270,7 +2775,6 @@ name = "webapps-core" version = "4.0.6" dependencies = [ "anyhow", - "big-os-kit", "dirs", "gettext-rs", "log", @@ -2286,7 +2790,6 @@ dependencies = [ name = "webapps-exec" version = "4.0.6" dependencies = [ - "big-os-kit", "libc", "webapps-core", ] @@ -2297,9 +2800,6 @@ version = "4.0.6" dependencies = [ "anyhow", "async-channel", - "big-app-kit", - "big-os-kit", - "big-relm4-components", "dirs", "env_logger", "fs4", @@ -2312,6 +2812,7 @@ dependencies = [ "log", "open", "relm4", + "reqwest", "scraper", "serde", "serde_json", @@ -2325,8 +2826,6 @@ dependencies = [ name = "webapps-viewer" version = "4.0.6" dependencies = [ - "big-app-kit", - "big-relm4-components", "clap", "env_logger", "gdk4", @@ -2404,6 +2903,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2413,6 +2930,135 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.15" @@ -2424,18 +3070,18 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" @@ -2445,9 +3091,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -2466,11 +3112,31 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -2487,6 +3153,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 6b85e484..70a4ce57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ env_logger = "0.11" anyhow = "1" dirs = "6" clap = { version = "4", features = ["derive"] } -big-os-kit = { path = "../big-rust-components/crates/big-os-kit" } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "blocking"] } scraper = "0.26" url = "2" gettextrs = { package = "gettext-rs", version = "0.7", features = ["gettext-system"] } diff --git a/crates/webapps-core/Cargo.toml b/crates/webapps-core/Cargo.toml index ccb097be..47dc6256 100644 --- a/crates/webapps-core/Cargo.toml +++ b/crates/webapps-core/Cargo.toml @@ -7,7 +7,6 @@ license.workspace = true publish = false # internal workspace crate; not for crates.io [dependencies] -big-os-kit = { path = "../../../big-rust-components/crates/big-os-kit" } serde.workspace = true serde_json.workspace = true log.workspace = true diff --git a/crates/webapps-core/src/desktop/paths.rs b/crates/webapps-core/src/desktop/paths.rs index c76a6f6b..618eaac1 100644 --- a/crates/webapps-core/src/desktop/paths.rs +++ b/crates/webapps-core/src/desktop/paths.rs @@ -2,10 +2,10 @@ use std::fs; use std::path::PathBuf; use anyhow::Result; -use big_os_kit::subprocess::BigSubprocessSpec; use crate::config; use crate::models::WebApp; +use crate::subprocess::SubprocessSpec; use super::builder::generate_desktop_entry; @@ -121,7 +121,7 @@ pub fn remove_desktop_file(filename: &str) -> Result<()> { fn refresh_desktop_database() { let apps_dir = config::applications_dir(); // run() blocks and reaps the child to prevent zombie processes - match BigSubprocessSpec::builder() + match SubprocessSpec::builder() .program("update-desktop-database") .arg(&apps_dir) .build() @@ -146,7 +146,7 @@ fn refresh_desktop_database() { ], ]; for args in commands { - match BigSubprocessSpec::builder() + match SubprocessSpec::builder() .program("dconf") .args(*args) .build() diff --git a/crates/webapps-core/src/lib.rs b/crates/webapps-core/src/lib.rs index ebd4d71d..9a43b0b3 100644 --- a/crates/webapps-core/src/lib.rs +++ b/crates/webapps-core/src/lib.rs @@ -7,4 +7,5 @@ pub mod config; pub mod desktop; pub mod i18n; pub mod models; +pub mod subprocess; pub mod templates; diff --git a/crates/webapps-core/src/subprocess.rs b/crates/webapps-core/src/subprocess.rs new file mode 100644 index 00000000..400dc045 --- /dev/null +++ b/crates/webapps-core/src/subprocess.rs @@ -0,0 +1,69 @@ +use std::ffi::{OsStr, OsString}; +use std::io; +use std::process::{Command, Output, Stdio}; + +#[derive(Debug, Clone)] +pub struct SubprocessSpec { + program: OsString, + args: Vec, +} + +#[derive(Debug, Default)] +pub struct SubprocessSpecBuilder { + program: Option, + args: Vec, +} + +impl SubprocessSpec { + pub fn builder() -> SubprocessSpecBuilder { + SubprocessSpecBuilder::default() + } + + pub fn run(&self) -> io::Result { + self.command().output() + } + + pub fn spawn_detached(&self) -> io::Result<()> { + self.command() + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map(|_| ()) + } + + fn command(&self) -> Command { + let mut command = Command::new(&self.program); + command.args(&self.args); + command + } +} + +impl SubprocessSpecBuilder { + pub fn program(mut self, program: impl AsRef) -> Self { + self.program = Some(program.as_ref().to_os_string()); + self + } + + pub fn arg(mut self, arg: impl AsRef) -> Self { + self.args.push(arg.as_ref().to_os_string()); + self + } + + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: AsRef, + { + self.args + .extend(args.into_iter().map(|arg| arg.as_ref().to_os_string())); + self + } + + pub fn build(self) -> SubprocessSpec { + SubprocessSpec { + program: self.program.unwrap_or_default(), + args: self.args, + } + } +} diff --git a/crates/webapps-exec/Cargo.toml b/crates/webapps-exec/Cargo.toml index 14298264..8a701e3c 100644 --- a/crates/webapps-exec/Cargo.toml +++ b/crates/webapps-exec/Cargo.toml @@ -12,5 +12,4 @@ path = "src/main.rs" [dependencies] webapps-core = { path = "../webapps-core" } -big-os-kit = { path = "../../../big-rust-components/crates/big-os-kit" } libc = "0.2" diff --git a/crates/webapps-exec/src/launch.rs b/crates/webapps-exec/src/launch.rs index 54999a04..2bb030a9 100644 --- a/crates/webapps-exec/src/launch.rs +++ b/crates/webapps-exec/src/launch.rs @@ -6,7 +6,7 @@ use std::{os::unix::process::CommandExt, path::Path, process::Command}; -use big_os_kit::subprocess::BigSubprocessSpec; +use webapps_core::subprocess::SubprocessSpec; use webapps_core::{browsers::BrowserDef, config}; use crate::{wayland, Args}; @@ -44,10 +44,8 @@ pub fn firefox( ); std::process::exit(1); }; - // bigagents: app-local-subprocess — Firefox path uses exec() to REPLACE the - // launcher process image (session managers/taskbars must track the browser - // PID, not a wrapper). BigSubprocessSpec models spawn/run, not execve, so - // this site stays on std::process::Command by design. + // Firefox must replace this process so session managers track the browser + // PID, not the launcher. let mut cmd = Command::new(&program); cmd.args(&prefix_args) .env("XAPP_FORCE_GTKWINDOW_ICON", icon) @@ -86,7 +84,7 @@ pub fn chromium(args: &Args, browser_id: &str, def: Option<&'static BrowserDef>, }; let spawn = move || { - if let Err(e) = BigSubprocessSpec::builder() + if let Err(e) = SubprocessSpec::builder() .program(program.as_str()) .args(&cmd_args) .build() @@ -110,7 +108,7 @@ pub fn chromium(args: &Args, browser_id: &str, def: Option<&'static BrowserDef>, /// sandboxed browser can read and write its profile data. pub fn grant_flatpak_access(browser_id: &str, app_id: &str) { let data_dir = config::profiles_dir().join(browser_id); - let status = BigSubprocessSpec::builder() + let status = SubprocessSpec::builder() .program("flatpak") .args([ "override", diff --git a/crates/webapps-manager/Cargo.toml b/crates/webapps-manager/Cargo.toml index 2c1ce29c..aa39a2fb 100644 --- a/crates/webapps-manager/Cargo.toml +++ b/crates/webapps-manager/Cargo.toml @@ -27,7 +27,7 @@ log.workspace = true env_logger.workspace = true anyhow.workspace = true dirs.workspace = true -big-os-kit = { workspace = true, features = ["http-client"] } +reqwest.workspace = true scraper.workspace = true url.workspace = true open = "5" @@ -35,12 +35,7 @@ async-channel = "2" fs4 = "1" gettextrs.workspace = true tempfile.workspace = true -# Relm4 + shared BigLinux components (onda6 migration). -# big-rust-components requires gtk4 v4_22 / libadwaita v1_9 — features are -# additive across the workspace, so the higher version is selected at build time. relm4 = { version = "0.11", features = ["libadwaita"] } -big-relm4-components = { path = "../../../big-rust-components/crates/big-relm4-components" } -big-app-kit = { path = "../../../big-rust-components/crates/big-app-kit", default-features = false } [dev-dependencies] tempfile = "3" diff --git a/crates/webapps-manager/src/browser_dialog.rs b/crates/webapps-manager/src/browser_dialog.rs index 4ab9cf20..57e66420 100644 --- a/crates/webapps-manager/src/browser_dialog.rs +++ b/crates/webapps-manager/src/browser_dialog.rs @@ -1,16 +1,14 @@ -// TODO(onda6): migrate to BigDialogSpec + Relm4 SimpleComponent (typed -// BrowserSelection output channel) — currently uses an interior-mutable -// once-callback + group of `gtk::CheckButton`s wired manually. use gtk4 as gtk; use libadwaita as adw; use adw::prelude::*; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use std::cell::RefCell; use std::rc::Rc; use webapps_core::models::{BrowserCollection, BrowserId}; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; + /// User's browser selection plus viewer-only options. pub struct BrowserSelection { pub browser_id: String, @@ -98,7 +96,7 @@ pub fn show( ); for browser in &browsers.browsers { - let row = BigInfoRow::new(BigInfoRowSpec::new(browser.display_name())).into_root(); + let row = InfoRow::new(InfoRowSpec::new(browser.display_name())).into_root(); row.set_activatable(true); let icon = gtk::Image::new(); @@ -206,8 +204,8 @@ fn append_viewer_row( } else { gettext("Unavailable: this site requires DRM, which the internal browser does not support.") }; - let row = BigInfoRow::new(BigInfoRowSpec::new(gettext("Internal Browser")).subtitle(subtitle)) - .into_root(); + let row = + InfoRow::new(InfoRowSpec::new(gettext("Internal Browser")).subtitle(subtitle)).into_root(); row.set_activatable(enabled); row.set_sensitive(enabled); diff --git a/crates/webapps-manager/src/favicon/download.rs b/crates/webapps-manager/src/favicon/download.rs index caf8e11c..ff1dbec3 100644 --- a/crates/webapps-manager/src/favicon/download.rs +++ b/crates/webapps-manager/src/favicon/download.rs @@ -1,10 +1,11 @@ use anyhow::{Context, Result}; -use big_os_kit::http_client::{http_get_bytes_capped, RequestHeaders}; use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::Duration; +use crate::http_client::{http_get_bytes_capped, RequestHeaders}; + /// Hard cap on icon byte size. Favicons in the wild rarely exceed 200 KB; the /// 1 MB ceiling defends against decompression abuse while leaving headroom for /// well-padded SVG/PNG sets some sites ship. diff --git a/crates/webapps-manager/src/favicon/mod.rs b/crates/webapps-manager/src/favicon/mod.rs index dd3250b1..f390b17f 100644 --- a/crates/webapps-manager/src/favicon/mod.rs +++ b/crates/webapps-manager/src/favicon/mod.rs @@ -5,10 +5,10 @@ mod download; mod html; use anyhow::Result; -use big_os_kit::http_client::{http_get_bytes_capped, RequestHeaders}; use std::path::PathBuf; use std::time::Duration; +use crate::http_client::{http_get_bytes_capped, RequestHeaders}; use webapps_core::config; use webapps_core::desktop; diff --git a/crates/webapps-manager/src/http_client.rs b/crates/webapps-manager/src/http_client.rs new file mode 100644 index 00000000..3ca23ea4 --- /dev/null +++ b/crates/webapps-manager/src/http_client.rs @@ -0,0 +1,116 @@ +use std::io::Read; +use std::time::Duration; + +use anyhow::{Context, Result}; +use reqwest::blocking::{Client, Response}; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, USER_AGENT}; +use reqwest::redirect::Policy; + +#[derive(Debug, Clone)] +pub struct RequestHeaders { + user_agent: String, + accept: String, +} + +#[derive(Debug, Clone)] +pub struct ByteResponse { + pub status: u16, + pub final_url: String, + pub content_type: Option, + pub bytes: Vec, +} + +#[derive(Debug, Clone)] +pub struct StreamResponse { + pub status: u16, + pub final_url: String, +} + +impl RequestHeaders { + pub fn browser() -> Self { + Self { + user_agent: + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120 Safari/537.36" + .to_string(), + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/*,*/*;q=0.8" + .to_string(), + } + } +} + +pub fn http_get_bytes_capped( + url: &str, + headers: &RequestHeaders, + max_bytes: u64, + timeout: Duration, +) -> Result { + let response = client(timeout) + .get(url) + .headers(header_map(headers, &[])?) + .send() + .with_context(|| format!("GET {url}"))?; + read_capped(response, max_bytes) +} + +pub fn http_get_stream_with_extra_headers( + url: &str, + headers: &RequestHeaders, + extra_headers: &[(&str, &str)], + timeout: Duration, +) -> Result { + let response = client(timeout) + .get(url) + .headers(header_map(headers, extra_headers)?) + .send() + .with_context(|| format!("GET {url}"))?; + Ok(StreamResponse { + status: response.status().as_u16(), + final_url: response.url().to_string(), + }) +} + +fn client(timeout: Duration) -> Client { + Client::builder() + .timeout(timeout) + .redirect(Policy::limited(10)) + .build() + .expect("reqwest client builder accepts static configuration") +} + +fn header_map(headers: &RequestHeaders, extra_headers: &[(&str, &str)]) -> Result { + let mut map = HeaderMap::new(); + map.insert(USER_AGENT, HeaderValue::from_str(&headers.user_agent)?); + map.insert(ACCEPT, HeaderValue::from_str(&headers.accept)?); + for (name, value) in extra_headers { + map.insert( + HeaderName::from_bytes(name.as_bytes())?, + HeaderValue::from_str(value)?, + ); + } + Ok(map) +} + +fn read_capped(mut response: Response, max_bytes: u64) -> Result { + let status = response.status().as_u16(); + let final_url = response.url().to_string(); + let content_type = response + .headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(ToOwned::to_owned); + let mut bytes = Vec::new(); + response + .by_ref() + .take(max_bytes + 1) + .read_to_end(&mut bytes) + .context("read response body")?; + if bytes.len() as u64 > max_bytes { + anyhow::bail!("response exceeds {max_bytes} bytes"); + } + Ok(ByteResponse { + status, + final_url, + content_type, + bytes, + }) +} diff --git a/crates/webapps-manager/src/lib.rs b/crates/webapps-manager/src/lib.rs index 12340840..a2a7478f 100644 --- a/crates/webapps-manager/src/lib.rs +++ b/crates/webapps-manager/src/lib.rs @@ -7,6 +7,8 @@ pub mod browser_dialog; pub mod favicon; pub mod geometry; +pub mod http_client; +pub mod platform; pub mod relm4_window; pub mod service; pub mod style; diff --git a/crates/webapps-manager/src/platform/desktop.rs b/crates/webapps-manager/src/platform/desktop.rs new file mode 100644 index 00000000..9d119e47 --- /dev/null +++ b/crates/webapps-manager/src/platform/desktop.rs @@ -0,0 +1,31 @@ +use gtk::gio; +use gtk::glib; +use gtk::prelude::*; +use gtk4 as gtk; + +pub fn install_action(target: &T, name: &str, callback: F) -> gio::SimpleAction +where + T: IsA, + F: Fn() + 'static, +{ + let action = gio::SimpleAction::new(name, None); + action.connect_activate(move |_, _| callback()); + target.add_action(&action); + action +} + +pub fn install_string_action(target: &T, name: &str, callback: F) -> gio::SimpleAction +where + T: IsA, + F: Fn(String) + 'static, +{ + let action = gio::SimpleAction::new(name, Some(&String::static_variant_type())); + action.connect_activate(move |_, parameter| { + let Some(value) = parameter.and_then(glib::Variant::str) else { + return; + }; + callback(value.to_string()); + }); + target.add_action(&action); + action +} diff --git a/crates/webapps-manager/src/platform/dialogs.rs b/crates/webapps-manager/src/platform/dialogs.rs new file mode 100644 index 00000000..13d8196a --- /dev/null +++ b/crates/webapps-manager/src/platform/dialogs.rs @@ -0,0 +1,40 @@ +use gtk4 as gtk; +use libadwaita as adw; +use libadwaita::prelude::*; + +pub fn confirm_dialog( + heading: &str, + body: &str, + cancel_label: &str, + confirm_response: &str, + confirm_label: &str, + destructive: bool, +) -> adw::AlertDialog { + let dialog = adw::AlertDialog::builder() + .heading(heading) + .body(body) + .close_response("cancel") + .default_response("cancel") + .build(); + dialog.add_response("cancel", cancel_label); + dialog.add_response(confirm_response, confirm_label); + if destructive { + dialog.set_response_appearance(confirm_response, adw::ResponseAppearance::Destructive); + } + dialog +} + +pub fn content_dialog(heading: &str, close_label: &str) -> (adw::AlertDialog, gtk::Box) { + let dialog = adw::AlertDialog::builder() + .heading(heading) + .close_response("close") + .default_response("close") + .build(); + dialog.add_response("close", close_label); + + let content = gtk::Box::new(gtk::Orientation::Vertical, 12); + content.set_margin_top(6); + content.set_margin_bottom(6); + dialog.set_extra_child(Some(&content)); + (dialog, content) +} diff --git a/crates/webapps-manager/src/platform/file_dialogs.rs b/crates/webapps-manager/src/platform/file_dialogs.rs new file mode 100644 index 00000000..445ee091 --- /dev/null +++ b/crates/webapps-manager/src/platform/file_dialogs.rs @@ -0,0 +1,130 @@ +use std::path::PathBuf; + +use gtk::gio; +use gtk::prelude::*; +use gtk4 as gtk; + +#[derive(Debug, Clone)] +pub struct FilePicker { + title: String, + initial_name: Option, + filters: Vec, +} + +#[derive(Debug, Clone)] +struct FileFilterSpec { + label: String, + patterns: Vec, + mime_types: Vec, +} + +impl FilePicker { + pub fn new(title: String) -> Self { + Self { + title, + initial_name: None, + filters: Vec::new(), + } + } + + pub fn save_file(title: String) -> Self { + Self::new(title) + } + + pub fn initial_name(mut self, name: &str) -> Self { + self.initial_name = Some(name.to_string()); + self + } + + pub fn pattern_filter(mut self, label: &str, patterns: &[&str]) -> Self { + self.filters.push(FileFilterSpec { + label: label.to_string(), + patterns: patterns.iter().map(|pattern| pattern.to_string()).collect(), + mime_types: Vec::new(), + }); + self + } + + pub fn mime_filter(mut self, label: &str, mime_types: &[&str]) -> Self { + self.filters.push(FileFilterSpec { + label: label.to_string(), + patterns: Vec::new(), + mime_types: mime_types + .iter() + .map(|mime_type| mime_type.to_string()) + .collect(), + }); + self + } + + pub fn open(self, parent: &impl IsA, callback: F) + where + F: FnOnce(PathBuf) + 'static, + { + let dialog = self.dialog(); + dialog.open(Some(parent), gio::Cancellable::NONE, move |result| { + let Ok(file) = result else { + return; + }; + if let Some(path) = file.path() { + callback(path); + } + }); + } + + pub fn open_optional(self, parent: Option<>k::Window>, callback: F) + where + F: FnOnce(PathBuf) + 'static, + { + let dialog = self.dialog(); + dialog.open(parent, gio::Cancellable::NONE, move |result| { + let Ok(file) = result else { + return; + }; + if let Some(path) = file.path() { + callback(path); + } + }); + } + + pub fn save(self, parent: &impl IsA, callback: F) + where + F: FnOnce(PathBuf) + 'static, + { + let dialog = self.dialog(); + dialog.save(Some(parent), gio::Cancellable::NONE, move |result| { + let Ok(file) = result else { + return; + }; + if let Some(path) = file.path() { + callback(path); + } + }); + } + + fn dialog(&self) -> gtk::FileDialog { + let dialog = gtk::FileDialog::builder() + .title(&self.title) + .modal(true) + .build(); + if let Some(name) = &self.initial_name { + dialog.set_initial_name(Some(name)); + } + if !self.filters.is_empty() { + let filters = gio::ListStore::new::(); + for spec in &self.filters { + let filter = gtk::FileFilter::new(); + filter.set_name(Some(&spec.label)); + for pattern in &spec.patterns { + filter.add_pattern(pattern); + } + for mime_type in &spec.mime_types { + filter.add_mime_type(mime_type); + } + filters.append(&filter); + } + dialog.set_filters(Some(&filters)); + } + dialog + } +} diff --git a/crates/webapps-manager/src/platform/info_row.rs b/crates/webapps-manager/src/platform/info_row.rs new file mode 100644 index 00000000..4f49b0c8 --- /dev/null +++ b/crates/webapps-manager/src/platform/info_row.rs @@ -0,0 +1,48 @@ +use libadwaita as adw; +use relm4::adw::prelude::*; + +#[derive(Debug, Clone)] +pub struct InfoRowSpec { + title: String, + subtitle: Option, + allow_markup: bool, +} + +pub struct InfoRow { + row: adw::ActionRow, +} + +impl InfoRowSpec { + pub fn new(title: impl Into) -> Self { + Self { + title: title.into(), + subtitle: None, + allow_markup: false, + } + } + + pub fn subtitle(mut self, subtitle: impl Into) -> Self { + self.subtitle = Some(subtitle.into()); + self + } + + pub fn allow_markup(mut self) -> Self { + self.allow_markup = true; + self + } +} + +impl InfoRow { + pub fn new(spec: InfoRowSpec) -> Self { + let row = adw::ActionRow::builder().title(spec.title).build(); + if let Some(subtitle) = spec.subtitle { + row.set_subtitle(&subtitle); + } + row.set_use_markup(spec.allow_markup); + Self { row } + } + + pub fn into_root(self) -> adw::ActionRow { + self.row + } +} diff --git a/crates/webapps-manager/src/platform/mod.rs b/crates/webapps-manager/src/platform/mod.rs new file mode 100644 index 00000000..b0debb0d --- /dev/null +++ b/crates/webapps-manager/src/platform/mod.rs @@ -0,0 +1,7 @@ +pub mod desktop; +pub mod dialogs; +pub mod file_dialogs; +pub mod info_row; +pub mod status_page; +pub mod theme; +pub mod tooltip; diff --git a/crates/webapps-manager/src/platform/status_page.rs b/crates/webapps-manager/src/platform/status_page.rs new file mode 100644 index 00000000..c4b432da --- /dev/null +++ b/crates/webapps-manager/src/platform/status_page.rs @@ -0,0 +1,58 @@ +use gtk4 as gtk; +use libadwaita as adw; +use relm4::adw::prelude::*; + +#[derive(Debug, Clone)] +pub struct EmptyStateSpec { + icon_name: String, + title: String, + body: Option, + action_label: Option, + action_name: Option, +} + +impl EmptyStateSpec { + pub fn new(icon_name: impl Into, title: impl Into) -> Self { + Self { + icon_name: icon_name.into(), + title: title.into(), + body: None, + action_label: None, + action_name: None, + } + } + + pub fn with_body(mut self, body: impl Into) -> Self { + self.body = Some(body.into()); + self + } + + pub fn with_action(mut self, label: impl Into, action_name: impl Into) -> Self { + self.action_label = Some(label.into()); + self.action_name = Some(action_name.into()); + self + } +} + +pub fn build_empty_state(spec: &EmptyStateSpec) -> (adw::StatusPage, Option) { + let page = adw::StatusPage::builder() + .icon_name(&spec.icon_name) + .title(&spec.title) + .build(); + if let Some(body) = &spec.body { + page.set_description(Some(body)); + } + + let button = spec.action_label.as_ref().map(|label| { + let button = gtk::Button::with_label(label); + button.add_css_class("suggested-action"); + if let Some(action_name) = &spec.action_name { + button.set_action_name(Some(action_name)); + } + button + }); + if let Some(button) = &button { + page.set_child(Some(button)); + } + (page, button) +} diff --git a/crates/webapps-manager/src/platform/theme.rs b/crates/webapps-manager/src/platform/theme.rs new file mode 100644 index 00000000..f6d3e4bd --- /dev/null +++ b/crates/webapps-manager/src/platform/theme.rs @@ -0,0 +1,21 @@ +use std::sync::Once; + +use gtk::gdk; +use gtk4 as gtk; + +pub fn load_app_css(css: &str) { + let Some(display) = gdk::Display::default() else { + return; + }; + let provider = gtk::CssProvider::new(); + provider.load_from_string(css); + gtk::style_context_add_provider_for_display( + &display, + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +pub fn load_app_css_once(css: &'static str, once: &'static Once) { + once.call_once(|| load_app_css(css)); +} diff --git a/crates/webapps-manager/src/platform/tooltip.rs b/crates/webapps-manager/src/platform/tooltip.rs new file mode 100644 index 00000000..b1694f1e --- /dev/null +++ b/crates/webapps-manager/src/platform/tooltip.rs @@ -0,0 +1,14 @@ +use gtk::prelude::*; +use gtk4 as gtk; + +pub fn set(widget: &impl IsA, text: &str) { + widget.set_tooltip_text(Some(text)); +} + +pub fn update(widget: &impl IsA, text: &str) { + set(widget, text); +} + +pub fn clear(widget: &impl IsA) { + widget.set_tooltip_text(None); +} diff --git a/crates/webapps-manager/src/relm4_window/empty.rs b/crates/webapps-manager/src/relm4_window/empty.rs index 38b5fe88..6a38377d 100644 --- a/crates/webapps-manager/src/relm4_window/empty.rs +++ b/crates/webapps-manager/src/relm4_window/empty.rs @@ -1,23 +1,19 @@ -//! Empty-state surface built from `BigEmptyStateSpec`. -//! -//! The spec lives in `big-relm4-components::state::empty`; this module turns -//! the spec into the concrete `adw::StatusPage` used by the webapps manager -//! when the catalog is empty. +//! Empty-state surface for the webapps list. -use big_relm4_components::display::status_page::build_empty_state; -use big_relm4_components::state::empty::BigEmptyStateSpec; use gettextrs::gettext; use libadwaita as adw; use relm4::adw::prelude::*; use relm4::gtk; +use crate::platform::status_page::{build_empty_state, EmptyStateSpec}; + /// Build the manager's empty-state spec. /// /// The CTA is left unbound — the consumer wires `connect_clicked` on the /// returned button to whatever "add webapp" path is appropriate. #[must_use] -pub fn build_spec() -> BigEmptyStateSpec { - BigEmptyStateSpec::new("big-webapps", gettext("No WebApps yet")) +pub fn build_spec() -> EmptyStateSpec { + EmptyStateSpec::new("big-webapps", gettext("No WebApps yet")) .with_body(gettext( "Turn any website into a desktop app. Press Add to get started.", )) @@ -26,13 +22,10 @@ pub fn build_spec() -> BigEmptyStateSpec { /// Realise a spec into an [`adw::StatusPage`] + optional CTA button. /// -/// Thin wrapper over the catalog renderer -/// [`big_relm4_components::display::status_page::build_empty_state`]; adds the -/// webapps-specific `empty-state-icon` style. Returns the page (already -/// containing the button as child) and the button itself so callers can wire -/// signals without re-walking the widget tree. +/// Returns the page and the button itself so callers can wire signals without +/// re-walking the widget tree. #[must_use] -pub fn build_page(spec: &BigEmptyStateSpec) -> (adw::StatusPage, Option) { +pub fn build_page(spec: &EmptyStateSpec) -> (adw::StatusPage, Option) { let (page, button) = build_empty_state(spec); page.add_css_class("empty-state-icon"); (page, button) diff --git a/crates/webapps-manager/src/relm4_window/list.rs b/crates/webapps-manager/src/relm4_window/list.rs index 8e46355f..77e79560 100644 --- a/crates/webapps-manager/src/relm4_window/list.rs +++ b/crates/webapps-manager/src/relm4_window/list.rs @@ -95,7 +95,6 @@ impl SimpleComponent for WebAppListController { WebAppSectionOutput::DeleteRequested(app) => WebAppListOutput::DeleteRequested(app), }); - // Empty state via BigEmptyStateSpec. let spec = empty::build_spec(); let (empty_page, empty_button) = empty::build_page(&spec); if let Some(btn) = empty_button { diff --git a/crates/webapps-manager/src/relm4_window/mod.rs b/crates/webapps-manager/src/relm4_window/mod.rs index 059e4fa4..ef5f7991 100644 --- a/crates/webapps-manager/src/relm4_window/mod.rs +++ b/crates/webapps-manager/src/relm4_window/mod.rs @@ -1,16 +1,14 @@ -//! Relm4 surface for the webapps manager (onda6 migration). +//! Relm4 surface for the webapps manager. //! //! Currently provides: //! - [`list::WebAppListController`] — typed list controller backed by //! [`relm4::factory::FactoryVecDeque`]; supersedes the imperative //! `window::list::populate_list` flow. //! - [`row::WebAppRowFactory`] — typed `FactoryComponent` for a single -//! webapp row (replaces `webapp_row::build_row`). +//! webapp row. //! - [`section::WebAppSectionFactory`] — per-category preferences group. -//! - [`empty`] — `BigEmptyStateSpec`-driven empty surface. -//! - [`shell_spec`] — canonical `BigShellSpec` for the manager window. -//! -//! TODO(onda6): full `BigShell`-based main window + `BigDialogSpec` dialogs. +//! - [`empty`] — empty-state surface. +//! - [`shell_spec`] — shell metadata for the manager window. pub mod empty; pub mod list; diff --git a/crates/webapps-manager/src/relm4_window/row.rs b/crates/webapps-manager/src/relm4_window/row.rs index 3e0e6af6..dd47c755 100644 --- a/crates/webapps-manager/src/relm4_window/row.rs +++ b/crates/webapps-manager/src/relm4_window/row.rs @@ -5,8 +5,6 @@ //! actions become typed `WebAppRowOutput` messages routed to the parent //! list component. -use big_relm4_components::feedback::tooltip; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use libadwaita as adw; use relm4::adw::prelude::*; use relm4::factory::{DynamicIndex, FactoryComponent, FactorySender}; @@ -14,6 +12,8 @@ use relm4::gtk; use webapps_core::models::{AppMode, Browser, WebApp}; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; +use crate::platform::tooltip; use crate::webapp_row::load_icon; /// Init payload for [`WebAppRowFactory`]. @@ -55,10 +55,8 @@ impl FactoryComponent for WebAppRowFactory { type Index = DynamicIndex; fn init_root(&self) -> Self::Root { - // Use the cataloged BigInfoRow helper (allow_markup preserves the - // legacy pre-escaped title/subtitle behaviour). - let row = BigInfoRow::new( - BigInfoRowSpec::new(glib::markup_escape_text(&self.webapp.app_name).to_string()) + let row = InfoRow::new( + InfoRowSpec::new(glib::markup_escape_text(&self.webapp.app_name).to_string()) .subtitle( glib::markup_escape_text(&crate::service::display_url(&self.webapp.app_url)) .to_string(), diff --git a/crates/webapps-manager/src/relm4_window/shell_spec.rs b/crates/webapps-manager/src/relm4_window/shell_spec.rs index 8b3aa8ae..19dcc977 100644 --- a/crates/webapps-manager/src/relm4_window/shell_spec.rs +++ b/crates/webapps-manager/src/relm4_window/shell_spec.rs @@ -1,31 +1,16 @@ -//! Canonical [`BigShellSpec`] description for the webapps manager. -//! -//! `window::build` consumes this spec for the window title and default -//! geometry (single source of truth). The chrome widget tree is still built -//! imperatively in `window/ui.rs`; the remaining fields (header policy, toast -//! usage) document the intended contract so a future migration to a -//! `BigShell`-based builder can derive the rest of the GTK tree from the spec. +//! Manager shell metadata used by `window::build`. -use big_app_kit::shell::{BigHeaderPolicy, BigShellKind, BigShellSpec}; -use webapps_core::config; +pub struct ShellSpec { + pub title: String, + pub default_width: i32, + pub default_height: i32, +} -/// Webapps manager shell spec. -/// -/// - `kind = SingleWindow`: one window, no sidebar, no tabs. -/// - `header_policy = Unified`: single header bar. -/// - `uses_toasts = true`: toast overlay drives success/error feedback. #[must_use] -pub fn build() -> BigShellSpec { - BigShellSpec { - app_id: config::APP_ID.to_string(), +pub fn build() -> ShellSpec { + ShellSpec { title: gettextrs::gettext("WebApps Manager"), - kind: BigShellKind::SingleWindow, - header_policy: BigHeaderPolicy::Unified, default_width: 820, default_height: 680, - uses_toasts: true, - uses_sidebar: false, - uses_tabs: false, - uses_footer: false, } } diff --git a/crates/webapps-manager/src/service/browser.rs b/crates/webapps-manager/src/service/browser.rs index 6c7fffd3..9276da72 100644 --- a/crates/webapps-manager/src/service/browser.rs +++ b/crates/webapps-manager/src/service/browser.rs @@ -1,8 +1,8 @@ use std::path::Path; -use big_app_kit::subprocess::BigSubprocessSpec; use webapps_core::browsers::browser_defs; use webapps_core::models::{Browser, BrowserCollection}; +use webapps_core::subprocess::SubprocessSpec; /// Detect all installed browsers (native + Flatpak) and identify the system default. /// @@ -24,7 +24,7 @@ pub fn detect_browsers() -> BrowserCollection { } // Flatpak: entries with flatpak_app_id + flatpak_id - if let Ok(output) = BigSubprocessSpec::builder() + if let Ok(output) = SubprocessSpec::builder() .program("flatpak") .args(["list", "--app", "--columns=application"]) .build() @@ -62,7 +62,7 @@ fn detect_default_browser(defs: &[webapps_core::browsers::BrowserDef]) -> Option fn query_default_browser() -> Option { // xdg-settings is the canonical source; xdg-mime is the fallback for // distros/desktops where xdg-settings isn't configured. - let primary = BigSubprocessSpec::builder() + let primary = SubprocessSpec::builder() .program("xdg-settings") .args(["get", "default-web-browser"]) .build() @@ -73,7 +73,7 @@ fn query_default_browser() -> Option { if primary.is_some() { return primary; } - BigSubprocessSpec::builder() + SubprocessSpec::builder() .program("xdg-mime") .args(["query", "default", "x-scheme-handler/http"]) .build() diff --git a/crates/webapps-manager/src/service/browser_url.rs b/crates/webapps-manager/src/service/browser_url.rs index 6c5f1e42..8fdcc1ed 100644 --- a/crates/webapps-manager/src/service/browser_url.rs +++ b/crates/webapps-manager/src/service/browser_url.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; use std::sync::{Mutex, OnceLock}; use std::time::Duration; -use big_os_kit::http_client::{http_get_stream_with_extra_headers, RequestHeaders}; +use crate::http_client::{http_get_stream_with_extra_headers, RequestHeaders}; static RESOLVED_URLS: OnceLock>> = OnceLock::new(); diff --git a/crates/webapps-manager/src/style.rs b/crates/webapps-manager/src/style.rs index f3c68f02..1d267669 100644 --- a/crates/webapps-manager/src/style.rs +++ b/crates/webapps-manager/src/style.rs @@ -1,4 +1,4 @@ -use big_relm4_components::theme; +use crate::platform::theme; const CSS: &str = r#" /* App icon in list rows — soft rounded corners for a polished look */ diff --git a/crates/webapps-manager/src/template_gallery.rs b/crates/webapps-manager/src/template_gallery.rs index d3ff13fd..4435c36d 100644 --- a/crates/webapps-manager/src/template_gallery.rs +++ b/crates/webapps-manager/src/template_gallery.rs @@ -1,11 +1,8 @@ -// TODO(onda6): migrate to BigDialogSpec + Relm4 FactoryComponent for the -// template grid (currently uses an interior-mutable boxed-FnOnce callback -// cell). use gtk4 as gtk; use libadwaita as adw; +use crate::platform::tooltip; use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use std::cell::RefCell; use std::rc::Rc; diff --git a/crates/webapps-manager/src/webapp_dialog/handlers/lifecycle.rs b/crates/webapps-manager/src/webapp_dialog/handlers/lifecycle.rs index 001e6d94..43300eb6 100644 --- a/crates/webapps-manager/src/webapp_dialog/handlers/lifecycle.rs +++ b/crates/webapps-manager/src/webapp_dialog/handlers/lifecycle.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use std::rc::Rc; +use crate::platform::tooltip; use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use gtk::glib; use gtk4 as gtk; diff --git a/crates/webapps-manager/src/webapp_dialog/handlers/media.rs b/crates/webapps-manager/src/webapp_dialog/handlers/media.rs index 00ca4d52..08dbe14a 100644 --- a/crates/webapps-manager/src/webapp_dialog/handlers/media.rs +++ b/crates/webapps-manager/src/webapp_dialog/handlers/media.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use std::rc::Rc; +use crate::platform::file_dialogs::FilePicker; use adw::prelude::*; -use big_app_kit::file_dialogs::FilePicker; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; diff --git a/crates/webapps-manager/src/webapp_dialog/ui/sections/appearance.rs b/crates/webapps-manager/src/webapp_dialog/ui/sections/appearance.rs index c7bd3509..7721c56c 100644 --- a/crates/webapps-manager/src/webapp_dialog/ui/sections/appearance.rs +++ b/crates/webapps-manager/src/webapp_dialog/ui/sections/appearance.rs @@ -1,11 +1,12 @@ use adw::prelude::*; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; use webapps_core::models::WebApp; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; + use super::super::CATEGORIES; pub(crate) struct AppearanceSection { @@ -17,7 +18,7 @@ pub(crate) struct AppearanceSection { } pub(crate) fn build_appearance_section(webapp: &WebApp) -> AppearanceSection { - let icon_row = BigInfoRow::new(BigInfoRowSpec::new(gettext("Application Icon"))).into_root(); + let icon_row = InfoRow::new(InfoRowSpec::new(gettext("Application Icon"))).into_root(); let icon_preview = gtk::Image::new(); icon_preview.set_pixel_size(32); icon_preview.set_accessible_role(gtk::AccessibleRole::Presentation); diff --git a/crates/webapps-manager/src/webapp_dialog/ui/sections/behavior.rs b/crates/webapps-manager/src/webapp_dialog/ui/sections/behavior.rs index 89a49273..d5cdecd9 100644 --- a/crates/webapps-manager/src/webapp_dialog/ui/sections/behavior.rs +++ b/crates/webapps-manager/src/webapp_dialog/ui/sections/behavior.rs @@ -2,13 +2,14 @@ use std::cell::RefCell; use std::rc::Rc; use adw::prelude::*; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; use webapps_core::models::{BrowserCollection, WebApp}; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; + pub(crate) struct BehaviorSection { pub browser_row: adw::ActionRow, pub browser_button: gtk::Button, @@ -21,7 +22,7 @@ pub(crate) fn build_behavior_section( webapp: &WebApp, browsers: Rc>, ) -> BehaviorSection { - let browser_row = BigInfoRow::new(BigInfoRowSpec::new(gettext("Browser"))).into_root(); + let browser_row = InfoRow::new(InfoRowSpec::new(gettext("Browser"))).into_root(); let browser_icon = gtk::Image::new(); browser_icon.set_pixel_size(20); browser_icon.set_accessible_role(gtk::AccessibleRole::Presentation); diff --git a/crates/webapps-manager/src/webapp_dialog/ui/sections/website.rs b/crates/webapps-manager/src/webapp_dialog/ui/sections/website.rs index 903f94b4..46ef83ca 100644 --- a/crates/webapps-manager/src/webapp_dialog/ui/sections/website.rs +++ b/crates/webapps-manager/src/webapp_dialog/ui/sections/website.rs @@ -1,5 +1,5 @@ +use crate::platform::tooltip; use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; diff --git a/crates/webapps-manager/src/welcome_dialog.rs b/crates/webapps-manager/src/welcome_dialog.rs index 8b008abc..661a0008 100644 --- a/crates/webapps-manager/src/welcome_dialog.rs +++ b/crates/webapps-manager/src/welcome_dialog.rs @@ -1,11 +1,4 @@ //! First-run welcome dialog. -//! -//! Finishes the onda6 migration: replaces the hand-built `adw::Dialog` with -//! the cataloged `big_app_kit::dialogs` content-dialog helper (an -//! `adw::AlertDialog` carrying a content box), so it matches the rest of the -//! manager's dialog surface and satisfies the component policy (no direct -//! `adw::Dialog`). The dialog is informational + a single "don't show again" -//! switch; its only effect fires on dismissal. use gtk4 as gtk; use libadwaita as adw; @@ -13,6 +6,7 @@ use libadwaita as adw; use adw::prelude::*; use gettextrs::gettext; +use crate::platform::dialogs; use crate::service; /// Show the welcome dialog on first run only. Returns immediately. @@ -24,7 +18,7 @@ pub fn show_if_needed(parent: &adw::ApplicationWindow) { } fn build_dialog() -> adw::AlertDialog { - let (dialog, content) = big_app_kit::dialogs::content_dialog( + let (dialog, content) = dialogs::content_dialog( &gettext("Welcome to WebApps Manager"), &gettext("Let's Start"), ); diff --git a/crates/webapps-manager/src/window/actions/about.rs b/crates/webapps-manager/src/window/actions/about.rs index 4cd33604..f6e1bc5b 100644 --- a/crates/webapps-manager/src/window/actions/about.rs +++ b/crates/webapps-manager/src/window/actions/about.rs @@ -1,5 +1,5 @@ +use crate::platform::desktop; use adw::prelude::*; -use big_app_kit::desktop; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; diff --git a/crates/webapps-manager/src/window/actions/browse.rs b/crates/webapps-manager/src/window/actions/browse.rs index b97cf4fa..5b263692 100644 --- a/crates/webapps-manager/src/window/actions/browse.rs +++ b/crates/webapps-manager/src/window/actions/browse.rs @@ -1,4 +1,4 @@ -use big_app_kit::desktop; +use crate::platform::desktop; use webapps_core::config; diff --git a/crates/webapps-manager/src/window/actions/import_export.rs b/crates/webapps-manager/src/window/actions/import_export.rs index 16bcd606..4870ac28 100644 --- a/crates/webapps-manager/src/window/actions/import_export.rs +++ b/crates/webapps-manager/src/window/actions/import_export.rs @@ -1,7 +1,7 @@ -use big_app_kit::desktop; -use big_app_kit::file_dialogs::FilePicker; use gettextrs::gettext; +use crate::platform::desktop; +use crate::platform::file_dialogs::FilePicker; use crate::{service, ui_async}; use super::super::context::WindowContext; diff --git a/crates/webapps-manager/src/window/actions/remove_all.rs b/crates/webapps-manager/src/window/actions/remove_all.rs index 3de57362..fd5181d4 100644 --- a/crates/webapps-manager/src/window/actions/remove_all.rs +++ b/crates/webapps-manager/src/window/actions/remove_all.rs @@ -1,9 +1,8 @@ use adw::prelude::*; -use big_app_kit::desktop; -use big_app_kit::dialogs; use gettextrs::gettext; use libadwaita as adw; +use crate::platform::{desktop, dialogs}; use crate::{service, ui_async}; use super::super::context::WindowContext; diff --git a/crates/webapps-manager/src/window/actions/remove_multiple.rs b/crates/webapps-manager/src/window/actions/remove_multiple.rs index dc8a453d..728f2e21 100644 --- a/crates/webapps-manager/src/window/actions/remove_multiple.rs +++ b/crates/webapps-manager/src/window/actions/remove_multiple.rs @@ -1,19 +1,15 @@ -// TODO(onda6): migrate to BigDialogSpec + Relm4 FactoryComponent for the -// bulk-selection list (currently uses an interior-mutable Vec of -// (WebApp, CheckButton) pairs). use std::cell::RefCell; use std::rc::Rc; use adw::prelude::*; -use big_app_kit::desktop; -use big_app_kit::dialogs; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; use webapps_core::models::WebApp; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; +use crate::platform::{desktop, dialogs}; use crate::{geometry, service, ui_async, webapp_row}; use super::super::context::WindowContext; @@ -67,7 +63,7 @@ fn open_dialog(context: &WindowContext) { group.set_margin_bottom(12); let select_all = gtk::CheckButton::with_label(&gettext("Select all")); - let select_all_row = BigInfoRow::new(BigInfoRowSpec::new(gettext("Select all"))).into_root(); + let select_all_row = InfoRow::new(InfoRowSpec::new(gettext("Select all"))).into_root(); select_all_row.set_activatable(true); select_all_row.add_prefix(&select_all); select_all_row.set_activatable_widget(Some(&select_all)); @@ -77,8 +73,8 @@ fn open_dialog(context: &WindowContext) { for app in &webapps { // allow_markup() preserves the pre-escaped strings the legacy code passed in. - let row = BigInfoRow::new( - BigInfoRowSpec::new(glib_markup_escape(&app.app_name)) + let row = InfoRow::new( + InfoRowSpec::new(glib_markup_escape(&app.app_name)) .subtitle(glib_markup_escape(&app.app_url)) .allow_markup(), ) diff --git a/crates/webapps-manager/src/window/component.rs b/crates/webapps-manager/src/window/component.rs index 8d3ef726..d9a32d97 100644 --- a/crates/webapps-manager/src/window/component.rs +++ b/crates/webapps-manager/src/window/component.rs @@ -1,23 +1,20 @@ //! `ManagerWindow` — the webapps manager window as a Relm4 [`Component`]. //! -//! Finishes the onda6 migration: the window content (header + search + the -//! Relm4 `WebAppListController` + toast overlay) is now a Relm4 component -//! (ADR-D14 mountable content; `Root` is the `adw::ToastOverlay`, never the -//! window). The launcher [`super::build`] creates the `adw::ApplicationWindow`, -//! mounts `controller.widget()`, and owns geometry + presentation. +//! The window content (header, search, list, toast overlay) is mounted as a +//! Relm4 component. The launcher [`super::build`] creates the +//! `adw::ApplicationWindow`, mounts `controller.widget()`, and owns geometry +//! plus presentation. //! //! Model owns the window-scoped [`WindowContext`]; the header's Add button and //! the search entry emit typed [`ManagerInput`] messages handled in //! [`Component::update`]. Row actions (edit/browser/delete/add) arrive as -//! `WebAppListController` outputs and are routed to the existing, tested -//! handler functions — the migration re-homes the chrome into a component -//! without rewriting the proven CRUD/load logic. +//! `WebAppListController` outputs and are routed to handler functions. use std::cell::{Cell, RefCell}; use std::rc::Rc; +use crate::platform::tooltip; use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; diff --git a/crates/webapps-manager/src/window/list.rs b/crates/webapps-manager/src/window/list.rs index 70119f94..6e012c1c 100644 --- a/crates/webapps-manager/src/window/list.rs +++ b/crates/webapps-manager/src/window/list.rs @@ -1,17 +1,12 @@ -//! Bridge between the (still-shared) `WindowContext` and the Relm4 -//! [`WebAppListController`]. +//! Bridge between `WindowContext` and the Relm4 [`WebAppListController`]. //! -//! After the onda6 migration, list rendering is driven by Relm4; this module -//! is just a thin shim that converts `state::sections_snapshot` into a -//! `WebAppListInput::Refresh` message. Row-action handlers (`handle_edit`, -//! `handle_browser_change`, `handle_delete`) are unchanged and still target -//! the existing dialogs. +//! Converts `state::sections_snapshot` into a `WebAppListInput::Refresh` +//! message and routes row actions to the dialog handlers. use std::cell::RefCell; use std::rc::Rc; use adw::prelude::*; -use big_app_kit::dialogs; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; @@ -20,6 +15,7 @@ use relm4::ComponentController; use webapps_core::models::{AppMode, BrowserId, WebApp, WebAppCollection}; use webapps_core::templates::default_registry; +use crate::platform::dialogs; use crate::relm4_window::list::{WebAppListInput, WebAppSection as Relm4Section}; use crate::{browser_dialog, service, ui_async, webapp_dialog}; diff --git a/crates/webapps-manager/src/window/mod.rs b/crates/webapps-manager/src/window/mod.rs index 5653c686..20700d89 100644 --- a/crates/webapps-manager/src/window/mod.rs +++ b/crates/webapps-manager/src/window/mod.rs @@ -38,9 +38,6 @@ thread_local! { } pub fn build(app: &adw::Application) { - // Window title + default geometry come from the canonical BigShellSpec so - // the documented shell contract is the single source of truth (was three - // divergent literals: 820×680 here vs 800×650 in the geometry fallback). let shell = crate::relm4_window::shell_spec::build(); let window = adw::ApplicationWindow::builder() diff --git a/crates/webapps-manager/src/window/shortcuts.rs b/crates/webapps-manager/src/window/shortcuts.rs index 5fd19e72..5de8c079 100644 --- a/crates/webapps-manager/src/window/shortcuts.rs +++ b/crates/webapps-manager/src/window/shortcuts.rs @@ -1,5 +1,5 @@ +use crate::platform::desktop; use adw::prelude::*; -use big_app_kit::desktop; use glib::clone; use gtk::glib; use gtk4 as gtk; diff --git a/crates/webapps-manager/src/window/shortcuts_window.rs b/crates/webapps-manager/src/window/shortcuts_window.rs index c51739bd..dde003b1 100644 --- a/crates/webapps-manager/src/window/shortcuts_window.rs +++ b/crates/webapps-manager/src/window/shortcuts_window.rs @@ -6,11 +6,12 @@ //! AT-SPI semantics (each row is announced individually). use adw::prelude::*; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; + pub(super) fn present(parent: &adw::ApplicationWindow) { let group = adw::PreferencesGroup::builder() .title(gettext("General")) @@ -34,7 +35,7 @@ pub(super) fn present(parent: &adw::ApplicationWindow) { } fn add_shortcut(group: &adw::PreferencesGroup, label: &str, accel: &str) { - let row = BigInfoRow::new(BigInfoRowSpec::new(label)).into_root(); + let row = InfoRow::new(InfoRowSpec::new(label)).into_root(); let accel_label = gtk::Label::builder() .label(format!("{}", glib_escape(accel))) .use_markup(true) diff --git a/crates/webapps-manager/src/window/ui.rs b/crates/webapps-manager/src/window/ui.rs index 3b9555c3..d83d58e1 100644 --- a/crates/webapps-manager/src/window/ui.rs +++ b/crates/webapps-manager/src/window/ui.rs @@ -1,5 +1,5 @@ +use crate::platform::tooltip; use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; diff --git a/crates/webapps-viewer/Cargo.toml b/crates/webapps-viewer/Cargo.toml index 90b8c44f..842140cf 100644 --- a/crates/webapps-viewer/Cargo.toml +++ b/crates/webapps-viewer/Cargo.toml @@ -25,5 +25,3 @@ clap.workspace = true gettextrs.workspace = true url.workspace = true relm4 = { version = "0.11", features = ["libadwaita"] } -big-relm4-components = { path = "../../../big-rust-components/crates/big-relm4-components" } -big-app-kit = { path = "../../../big-rust-components/crates/big-app-kit", default-features = false } diff --git a/crates/webapps-viewer/src/main.rs b/crates/webapps-viewer/src/main.rs index 9c1ec6ca..8702d8e9 100644 --- a/crates/webapps-viewer/src/main.rs +++ b/crates/webapps-viewer/src/main.rs @@ -1,6 +1,7 @@ //! Binary entrypoint for the WebApp Viewer: parses CLI args (URL, window //! title, profile) and launches the WebKit-based viewer window. +mod platform; mod window; use clap::Parser; diff --git a/crates/webapps-viewer/src/platform/desktop.rs b/crates/webapps-viewer/src/platform/desktop.rs new file mode 100644 index 00000000..9d119e47 --- /dev/null +++ b/crates/webapps-viewer/src/platform/desktop.rs @@ -0,0 +1,31 @@ +use gtk::gio; +use gtk::glib; +use gtk::prelude::*; +use gtk4 as gtk; + +pub fn install_action(target: &T, name: &str, callback: F) -> gio::SimpleAction +where + T: IsA, + F: Fn() + 'static, +{ + let action = gio::SimpleAction::new(name, None); + action.connect_activate(move |_, _| callback()); + target.add_action(&action); + action +} + +pub fn install_string_action(target: &T, name: &str, callback: F) -> gio::SimpleAction +where + T: IsA, + F: Fn(String) + 'static, +{ + let action = gio::SimpleAction::new(name, Some(&String::static_variant_type())); + action.connect_activate(move |_, parameter| { + let Some(value) = parameter.and_then(glib::Variant::str) else { + return; + }; + callback(value.to_string()); + }); + target.add_action(&action); + action +} diff --git a/crates/webapps-viewer/src/platform/dialogs.rs b/crates/webapps-viewer/src/platform/dialogs.rs new file mode 100644 index 00000000..6e95839b --- /dev/null +++ b/crates/webapps-viewer/src/platform/dialogs.rs @@ -0,0 +1,25 @@ +use libadwaita as adw; +use libadwaita::prelude::*; + +pub fn confirm_dialog_with_cancel_id( + heading: &str, + body: &str, + cancel_response: &str, + cancel_label: &str, + confirm_response: &str, + confirm_label: &str, + destructive: bool, +) -> adw::AlertDialog { + let dialog = adw::AlertDialog::builder() + .heading(heading) + .body(body) + .close_response(cancel_response) + .default_response(cancel_response) + .build(); + dialog.add_response(cancel_response, cancel_label); + dialog.add_response(confirm_response, confirm_label); + if destructive { + dialog.set_response_appearance(confirm_response, adw::ResponseAppearance::Destructive); + } + dialog +} diff --git a/crates/webapps-viewer/src/platform/file_dialogs.rs b/crates/webapps-viewer/src/platform/file_dialogs.rs new file mode 100644 index 00000000..4dfdf9b5 --- /dev/null +++ b/crates/webapps-viewer/src/platform/file_dialogs.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; + +use gtk::gio; +use gtk::glib; +use gtk::prelude::*; +use gtk4 as gtk; + +#[derive(Debug, Clone)] +pub struct FilePicker { + title: String, + initial_name: Option, +} + +impl FilePicker { + pub fn save_file(title: String) -> Self { + Self { + title, + initial_name: None, + } + } + + pub fn initial_name(mut self, name: &str) -> Self { + self.initial_name = Some(name.to_string()); + self + } + + pub fn save_result(self, parent: &impl IsA, callback: F) + where + F: FnOnce(Result, glib::Error>) + 'static, + { + let dialog = self.dialog(); + dialog.save(Some(parent), gio::Cancellable::NONE, move |result| { + callback(result.map(|file| file.path())); + }); + } + + fn dialog(&self) -> gtk::FileDialog { + let dialog = gtk::FileDialog::builder() + .title(&self.title) + .modal(true) + .build(); + if let Some(name) = &self.initial_name { + dialog.set_initial_name(Some(name)); + } + dialog + } +} diff --git a/crates/webapps-viewer/src/platform/info_row.rs b/crates/webapps-viewer/src/platform/info_row.rs new file mode 100644 index 00000000..be395504 --- /dev/null +++ b/crates/webapps-viewer/src/platform/info_row.rs @@ -0,0 +1,29 @@ +use libadwaita as adw; +#[derive(Debug, Clone)] +pub struct InfoRowSpec { + title: String, +} + +pub struct InfoRow { + row: adw::ActionRow, +} + +impl InfoRowSpec { + pub fn new(title: impl Into) -> Self { + Self { + title: title.into(), + } + } +} + +impl InfoRow { + pub fn new(spec: InfoRowSpec) -> Self { + Self { + row: adw::ActionRow::builder().title(spec.title).build(), + } + } + + pub fn into_root(self) -> adw::ActionRow { + self.row + } +} diff --git a/crates/webapps-viewer/src/platform/mod.rs b/crates/webapps-viewer/src/platform/mod.rs new file mode 100644 index 00000000..3b3cc4c0 --- /dev/null +++ b/crates/webapps-viewer/src/platform/mod.rs @@ -0,0 +1,6 @@ +pub mod desktop; +pub mod dialogs; +pub mod file_dialogs; +pub mod info_row; +pub mod theme; +pub mod tooltip; diff --git a/crates/webapps-viewer/src/platform/theme.rs b/crates/webapps-viewer/src/platform/theme.rs new file mode 100644 index 00000000..f6d3e4bd --- /dev/null +++ b/crates/webapps-viewer/src/platform/theme.rs @@ -0,0 +1,21 @@ +use std::sync::Once; + +use gtk::gdk; +use gtk4 as gtk; + +pub fn load_app_css(css: &str) { + let Some(display) = gdk::Display::default() else { + return; + }; + let provider = gtk::CssProvider::new(); + provider.load_from_string(css); + gtk::style_context_add_provider_for_display( + &display, + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +pub fn load_app_css_once(css: &'static str, once: &'static Once) { + once.call_once(|| load_app_css(css)); +} diff --git a/crates/webapps-viewer/src/platform/tooltip.rs b/crates/webapps-viewer/src/platform/tooltip.rs new file mode 100644 index 00000000..8a2f3520 --- /dev/null +++ b/crates/webapps-viewer/src/platform/tooltip.rs @@ -0,0 +1,6 @@ +use gtk::prelude::*; +use gtk4 as gtk; + +pub fn set(widget: &impl IsA, text: &str) { + widget.set_tooltip_text(Some(text)); +} diff --git a/crates/webapps-viewer/src/window/chrome.rs b/crates/webapps-viewer/src/window/chrome.rs index 1934f87f..e6fe6eb5 100644 --- a/crates/webapps-viewer/src/window/chrome.rs +++ b/crates/webapps-viewer/src/window/chrome.rs @@ -1,10 +1,11 @@ use adw::prelude::*; -use big_relm4_components::feedback::tooltip; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; use webkit6 as webkit; +use crate::platform::tooltip; + use super::{loading, settings}; pub(super) struct ViewerChrome { diff --git a/crates/webapps-viewer/src/window/context_menu.rs b/crates/webapps-viewer/src/window/context_menu.rs index 14d66ba7..0b2f6b53 100644 --- a/crates/webapps-viewer/src/window/context_menu.rs +++ b/crates/webapps-viewer/src/window/context_menu.rs @@ -1,10 +1,11 @@ -use big_app_kit::desktop; use gettextrs::gettext; use gtk::glib::variant::ToVariant; use gtk4 as gtk; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::desktop; + pub(super) fn setup_context_menu(webview: &webkit::WebView) { use gtk::gio; diff --git a/crates/webapps-viewer/src/window/downloads/mod.rs b/crates/webapps-viewer/src/window/downloads/mod.rs index 05d4715a..219ba169 100644 --- a/crates/webapps-viewer/src/window/downloads/mod.rs +++ b/crates/webapps-viewer/src/window/downloads/mod.rs @@ -3,7 +3,6 @@ mod connect; -use big_app_kit::file_dialogs::FilePicker; use gettextrs::gettext; use glib::clone; use gtk4 as gtk; @@ -11,6 +10,8 @@ use libadwaita as adw; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::file_dialogs::FilePicker; + pub(super) use connect::connect_download_handlers; pub(super) fn handle_download(window: &adw::ApplicationWindow, download: &webkit::Download) { diff --git a/crates/webapps-viewer/src/window/loading.rs b/crates/webapps-viewer/src/window/loading.rs index e824d17d..d30a7a54 100644 --- a/crates/webapps-viewer/src/window/loading.rs +++ b/crates/webapps-viewer/src/window/loading.rs @@ -1,12 +1,13 @@ use std::{cell::Cell, rc::Rc, sync::Once, time::Duration}; -use big_relm4_components::theme; use glib::clone; use gtk4 as gtk; use gtk4::prelude::*; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::theme; + const CONTENT_REVEAL_DELAY_MS: u64 = 250; const CSS: &str = r#" diff --git a/crates/webapps-viewer/src/window/permissions/mod.rs b/crates/webapps-viewer/src/window/permissions/mod.rs index 3376538f..9703aba2 100644 --- a/crates/webapps-viewer/src/window/permissions/mod.rs +++ b/crates/webapps-viewer/src/window/permissions/mod.rs @@ -7,11 +7,12 @@ use std::collections::HashMap; use std::path::Path; use adw::prelude::*; -use big_app_kit::dialogs; use gettextrs::gettext; use libadwaita as adw; use webkit6 as webkit; +use crate::platform::dialogs; + pub(super) use connect::connect_permission_requests; /// Decision for an incoming WebKit permission request. diff --git a/crates/webapps-viewer/src/window/shortcuts/mod.rs b/crates/webapps-viewer/src/window/shortcuts/mod.rs index 9be04816..8b736c6b 100644 --- a/crates/webapps-viewer/src/window/shortcuts/mod.rs +++ b/crates/webapps-viewer/src/window/shortcuts/mod.rs @@ -9,13 +9,14 @@ use std::cell::Cell; use std::rc::Rc; use adw::prelude::*; -use big_app_kit::desktop; use glib::clone; use gtk::glib; use gtk4 as gtk; use libadwaita as adw; use webkit6 as webkit; +use crate::platform::desktop; + pub(super) fn setup_shortcuts( window: &adw::ApplicationWindow, webview: &webkit::WebView, diff --git a/crates/webapps-viewer/src/window/shortcuts/webview_actions.rs b/crates/webapps-viewer/src/window/shortcuts/webview_actions.rs index 687a722b..ee300818 100644 --- a/crates/webapps-viewer/src/window/shortcuts/webview_actions.rs +++ b/crates/webapps-viewer/src/window/shortcuts/webview_actions.rs @@ -1,10 +1,11 @@ -use big_app_kit::desktop; use glib::clone; use gtk4 as gtk; use libadwaita as adw; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::desktop; + pub(super) fn register_navigation_actions( window: &adw::ApplicationWindow, app: >k::Application, diff --git a/crates/webapps-viewer/src/window/shortcuts/window_actions.rs b/crates/webapps-viewer/src/window/shortcuts/window_actions.rs index caf49725..0c3ea765 100644 --- a/crates/webapps-viewer/src/window/shortcuts/window_actions.rs +++ b/crates/webapps-viewer/src/window/shortcuts/window_actions.rs @@ -1,13 +1,14 @@ use std::cell::Cell; use std::rc::Rc; -use big_app_kit::desktop; use glib::clone; use gtk4 as gtk; use libadwaita as adw; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::desktop; + pub(super) fn register_fullscreen_actions( window: &adw::ApplicationWindow, app: >k::Application, diff --git a/crates/webapps-viewer/src/window/shortcuts/zoom_actions.rs b/crates/webapps-viewer/src/window/shortcuts/zoom_actions.rs index 22159991..9855062e 100644 --- a/crates/webapps-viewer/src/window/shortcuts/zoom_actions.rs +++ b/crates/webapps-viewer/src/window/shortcuts/zoom_actions.rs @@ -1,10 +1,11 @@ -use big_app_kit::desktop; use glib::clone; use gtk4 as gtk; use libadwaita as adw; use webkit6 as webkit; use webkit6::prelude::*; +use crate::platform::desktop; + pub(super) fn register_zoom_actions( window: &adw::ApplicationWindow, app: >k::Application, diff --git a/crates/webapps-viewer/src/window/shortcuts_window.rs b/crates/webapps-viewer/src/window/shortcuts_window.rs index a4f823d5..f15638a7 100644 --- a/crates/webapps-viewer/src/window/shortcuts_window.rs +++ b/crates/webapps-viewer/src/window/shortcuts_window.rs @@ -5,11 +5,12 @@ //! dialog gives the same result with better AT-SPI semantics. use adw::prelude::*; -use big_relm4_components::list::info_row::{BigInfoRow, BigInfoRowSpec}; use gettextrs::gettext; use gtk4 as gtk; use libadwaita as adw; +use crate::platform::info_row::{InfoRow, InfoRowSpec}; + pub(super) fn present(parent: &adw::ApplicationWindow) { let navigation = build_group( &gettext("Navigation"), @@ -57,7 +58,7 @@ pub(super) fn present(parent: &adw::ApplicationWindow) { fn build_group(title: &str, rows: &[(String, &str)]) -> adw::PreferencesGroup { let group = adw::PreferencesGroup::builder().title(title).build(); for (label, accel) in rows { - let row = BigInfoRow::new(BigInfoRowSpec::new(label.as_str())).into_root(); + let row = InfoRow::new(InfoRowSpec::new(label.as_str())).into_root(); let accel_label = gtk::Label::builder() .label(format!("{}", gtk::glib::markup_escape_text(accel))) .use_markup(true) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index ec656d17..5dcad7f5 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -191,11 +191,11 @@ package() { # Copy package tree (icons, profiles, skel, .desktop, systemd, menus) if [ -d "${InternalDir}/usr" ]; then - cp -a "${InternalDir}/usr" "${pkgdir}/" + cp -R --no-preserve=ownership "${InternalDir}/usr" "${pkgdir}/" fi if [ -d "${InternalDir}/etc" ]; then - cp -a "${InternalDir}/etc" "${pkgdir}/" + cp -R --no-preserve=ownership "${InternalDir}/etc" "${pkgdir}/" fi # Install Rust binaries From c7e678aa2df3432496e3354a660fe62e1cd34924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tales=20A=2E=20Mendon=C3=A7a?= Date: Fri, 3 Jul 2026 21:34:42 -0300 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix:=20avoid=20ring?= =?UTF-8?q?=20in=20Arch=20package=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 607 ++++++++++------------------------------------------- Cargo.toml | 4 +- 2 files changed, 110 insertions(+), 501 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed30453e..7aaf886f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -58,7 +58,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -97,12 +97,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.0" @@ -133,7 +127,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags 2.13.0", + "bitflags", "cairo-sys-rs", "glib", "libc", @@ -176,12 +170,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "clap" version = "4.6.1" @@ -282,38 +270,6 @@ dependencies = [ "syn", ] -[[package]] -name = "defmt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -353,7 +309,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -407,7 +363,6 @@ dependencies = [ "anstream", "anstyle", "env_filter", - "jiff", "log", ] @@ -424,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -485,6 +440,21 @@ dependencies = [ "spin", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -510,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e72ed92b67c146290f88e9c89d60ca163ea417a446f61ffd7b72df3e7f1dfd5" dependencies = [ "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -674,10 +644,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -752,7 +720,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -761,7 +729,7 @@ version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" dependencies = [ - "bitflags 2.13.0", + "bitflags", "futures-channel", "futures-core", "futures-executor", @@ -995,18 +963,18 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.27.9" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "http", + "bytes", + "http-body-util", "hyper", "hyper-util", - "rustls", - "rustls-native-certs", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", ] @@ -1206,31 +1174,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "jiff" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" -dependencies = [ - "defmt", - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "js-sys" version = "0.3.103" @@ -1334,12 +1277,6 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1383,7 +1320,24 @@ checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1443,12 +1397,49 @@ dependencies = [ "libc", ] +[[package]] +name = "openssl" +version = "0.10.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-sys" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1578,21 +1569,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.5" @@ -1602,15 +1578,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1626,28 +1593,6 @@ dependencies = [ "toml_edit 0.25.12+spec-1.1.0", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1657,61 +1602,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" version = "1.0.46" @@ -1733,42 +1623,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags", ] [[package]] @@ -1861,22 +1722,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower", "tower-http", "tower-service", @@ -1886,20 +1745,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -1921,37 +1766,11 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", + "windows-sys", ] [[package]] @@ -1960,21 +1779,9 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ - "web-time", "zeroize", ] -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -1993,7 +1800,7 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2023,7 +1830,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -2046,7 +1853,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.13.0", + "bitflags", "cssparser", "derive_more", "log", @@ -2203,7 +2010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2277,12 +2084,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "2.0.118" @@ -2349,7 +2150,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -2392,21 +2193,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.52.3" @@ -2418,16 +2204,16 @@ dependencies = [ "mio", "pin-project-lite", "socket2", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] -name = "tokio-rustls" -version = "0.26.4" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "rustls", + "native-tls", "tokio", ] @@ -2544,7 +2330,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.0", + "bitflags", "bytes", "futures-util", "http", @@ -2617,12 +2403,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "url" version = "2.5.8" @@ -2653,6 +2433,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -2748,16 +2534,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web_atoms" version = "0.2.5" @@ -2903,24 +2679,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2930,135 +2688,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.15" @@ -3112,26 +2741,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 70a4ce57..72c753be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ gdk-pixbuf = { package = "gdk-pixbuf", version = "0.22" } serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" -env_logger = "0.11" +env_logger = { version = "0.11", default-features = false, features = ["auto-color", "regex"] } anyhow = "1" dirs = "6" clap = { version = "4", features = ["derive"] } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "blocking"] } +reqwest = { version = "0.12", default-features = false, features = ["native-tls", "blocking"] } scraper = "0.26" url = "2" gettextrs = { package = "gettext-rs", version = "0.7", features = ["gettext-system"] } From 8df4624a2ef5e5e778a85c9768e9699b054d66ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tales=20A=2E=20Mendon=C3=A7a?= Date: Tue, 25 Aug 2026 17:04:29 -0300 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20(webapps):=20high-res?= =?UTF-8?q?=20icons,=20cookie-jar=20migration,=20DRM=20gate,=20PKGBUILD=20?= =?UTF-8?q?path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Icons: four defects made the launcher show a blurry 32px icon. `reqwest` lacked the `gzip` feature, so every manifest.json served gzipped failed to parse and the 512/1024px sets PWAs declare there were never seen. `magick -` could not decode any .ico (needs `ICO:-`) and without a frame index wrote icon_0-0.png, reporting success for a missing file. Ranking trusted the author-supplied `sizes` hint, so an unlabelled 512px PNG lost to a declared 32x32 — new favicon/image.rs measures real pixels from the container headers. ICO frame choice was left to gdk-pixbuf, which returns the 16px one; now the largest is picked. Also demoted mask-icon, raised the 1MB cap that rejected real 1024px PNGs, fixed extensions for JPEG/WebP/GIF, deduped candidates, kept the port in the /favicon.ico fallback, and upscale the winner to 512px. Measured pixels alone then let Discord and Slack pick their 1200x630 og:image banner, so shape and provenance gate the comparison. Over 14 sites: Spotify 32x32 -> 512x512, banners -> 512x512, rest 512x512 or scalable SVG. Cookies: libsoup's Sqlite jar overflows expiries past 2038 (2040 reads back as 2094) and degrades SameSite=None to Lax — both reproduced on 3.6.6, both visible in real profiles here. Switch to the Text jar, which preserves both. `set_persistent_storage` converts nothing, so window/cookie_migration.rs converts the legacy jar first; otherwise the first launch after a package update silently logs the user out of every internal-browser webapp. The marker is the jar's cookie count, not the file's existence: libsoup writes a header on open, so gating on the file would strand already-launched profiles. Verified on profile copies: 50, 5 and 2 cookies recovered. 1B DRM gate: `match_url` did `url.contains(domain)` over `HashMap::values()` — ──"www.netflix.com" contains "x.com", so the DRM-free X template matched Netflix, and HashMap order made music.youtube.com resolve at random, so the block from 9a906c9 fired only sometimes. Match on the parsed host with a label boundary, most specific wins, tie-break by template_id. `requires_drm` also falls back to the registrable domain, since spotify.com missed open.spotify.com. Nextcloud UA: the decision was not persisted, so every launch fetched the login page under the spoofed UA and switched mid-load, never reloaded, and listened on every cookie change dumping the whole jar. Now persisted per profile and applied before the first request, detected on load-changed with a bounded budget, reloading on adoption. Corrected the rationale too: a live GET /login sets the strict sentinel under either UA, so it identifies Nextcloud rather than signalling a failed check — the old comment implied the trigger was suppressed by the very UA meant to detect it. Packaging: move the PKGBUILD back to pkgbuild/, the path every other BigLinux repo uses and where the build farm looks; it had drifted to packaging/arch/. The PKGBUILD resolves the repo root relative to its own directory, so the offset drops from `../..` to `..` — otherwise it silently falls back to a network git fetch instead of the working tree. Fixed the prepare() excludes, .gitignore, flatpak skip list and docs to match. build-package.yml was fully commented out (no push built anything) and its guard grepped the old path; re-enabled in the canonical form used by appimage-creator. Verified with a full makepkg run from the new path. --- .github/workflows/build-package.yml | 55 +- .gitignore | 4 + AGENTS.md | 10 +- ARCHITECTURE.md | 2 +- Cargo.lock | 90 +++ Cargo.toml | 9 +- INVARIANTS.md | 2 + approved-crates.txt | 4 + crates/webapps-core/src/templates/registry.rs | 266 +++++++- .../webapps-manager/src/favicon/download.rs | 445 +++++++++++-- crates/webapps-manager/src/favicon/html.rs | 94 ++- crates/webapps-manager/src/favicon/image.rs | 608 ++++++++++++++++++ crates/webapps-manager/src/favicon/mod.rs | 395 +++++++++++- crates/webapps-viewer/Cargo.toml | 7 + .../src/window/cookie_migration.rs | 373 +++++++++++ crates/webapps-viewer/src/window/mod.rs | 1 + crates/webapps-viewer/src/window/session.rs | 22 +- crates/webapps-viewer/src/window/settings.rs | 242 +++++++ packaging/flatpak/br.com.biglinux.webapps.yml | 4 +- {packaging/arch => pkgbuild}/PKGBUILD | 19 +- .../biglinux-webapps.install | 0 scripts/validate-customizations.sh | 2 +- 22 files changed, 2502 insertions(+), 152 deletions(-) create mode 100644 crates/webapps-manager/src/favicon/image.rs create mode 100644 crates/webapps-viewer/src/window/cookie_migration.rs rename {packaging/arch => pkgbuild}/PKGBUILD (92%) rename {packaging/arch => pkgbuild}/biglinux-webapps.install (100%) diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index b057716d..3f6ecd71 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -11,33 +11,30 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # - name: Send Hooks BigLinux Build Package - # shell: bash - # run: | - # if [ -z "$(grep biglinux-package-template pkgbuild/PKGBUILD)" ];then - # curl -X POST -H "Accept: application/json" -H "Authorization: token ${{ secrets.WEBHOOK_TOKEN }}" --data '{"event_type": "${{ github.repository }}", "client_payload": { "branch": "${{ github.ref_name }}", "url": "https://github.com/${{ github.repository }}"}}' https://api.github.com/repos/BigLinux-Package-Build/build-package/dispatches - # curl -X POST -H "Accept: application/json" -H "Authorization: token ${{ secrets.WEBHOOK_TOKEN }}" --data '{"event_type": "${{ github.repository }}", "client_payload": { "branch": "${{ github.ref_name }}", "url": "https://github.com/${{ github.repository }}"}}' https://api.github.com/repos/BigLinux-Package-Build/build-package-ARM/dispatches - # fi - # curl -X POST "https://nebulaapi.nimbusnetwork.dev/webhook" \ - # -H "Authorization: token ${{ secrets.WEBHOOK_TOKEN }}" \ - # -H "Content-Type: application/json" \ - # -d '{ - # "event_type": "${{ github.repository }}", - # "branch": "${{ github.ref_name }}", - # "url": "https://github.com/${{ github.repository }}" - # }' - - # - name: Invoke deployment hook - # uses: johannes-huther/webhook.sh@v1 - # env: - # webhook_url: https://nebulaapi.nimbusnetwork.dev/webhook - # webhook_secret: ${{ secrets.WEBHOOK_TOKEN }} - # data: '{ "event_type": "${{ github.repository }}", "branch": "${{ github.ref_name }}", "url": "https://github.com/${{ github.repository }}"}' - #xpto28 - - - - - - + - name: Send Hooks BigLinux Build Package + shell: bash + # Secrets and context go through `env`, never interpolated into the + # script body: a `${{ }}` expansion inside `run` is substituted before + # bash sees the line, so a branch name containing quotes would break out + # of the string. This mirrors the form used by the other BigLinux repos. + env: + WEBHOOK_TOKEN: ${{ secrets.WEBHOOK_TOKEN }} + REPOSITORY: ${{ github.repository }} + REF_NAME: ${{ github.ref_name }} + run: | + # The template repo carries a placeholder PKGBUILD that must never be + # dispatched to the build farm. Note the path: `pkgbuild/PKGBUILD` is + # the organisation-wide convention, and this grep is exactly why the + # PKGBUILD cannot live anywhere else — it used to sit in + # `packaging/arch/`, where this check read a non-existent file. + if [ -z "$(grep biglinux-package-template pkgbuild/PKGBUILD)" ]; then + payload="{\"event_type\": \"${REPOSITORY}\", \"client_payload\": { \"branch\": \"${REF_NAME}\", \"url\": \"https://github.com/${REPOSITORY}\"}}" + for repo in build-package build-package-ARM; do + curl -X POST \ + -H "Accept: application/json" \ + -H "Authorization: token ${WEBHOOK_TOKEN}" \ + --data "$payload" \ + "https://api.github.com/repos/BigLinux-Package-Build/${repo}/dispatches" + done + fi diff --git a/.gitignore b/.gitignore index e6f8d74e..4b387a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,10 @@ PLANNING.md .audit # Packaging build artifacts (makepkg $srcdir/$pkgdir mirror — regenerated by PKGBUILD prepare()) +pkgbuild/src/ +pkgbuild/pkg/ +pkgbuild/*.pkg.tar* +# Legacy location, kept so a stale checkout does not resurrect these packaging/arch/src/ packaging/arch/pkg/ packaging/arch/*.pkg.tar* diff --git a/AGENTS.md b/AGENTS.md index 264a8056..6bdbb78d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,13 +99,13 @@ scripts/ ## Packaging channels -### Arch (`packaging/arch/PKGBUILD`) +### Arch (`pkgbuild/PKGBUILD`) - Auto-detects a local working tree via `BASH_SOURCE[0]`; falls back to `git+${url}.git` when building without one. - Version = `$(date +%y.%m.%d)-$(date +%H%M)`, surfaced to the app via the `BIGLINUX_WEBAPPS_VERSION` env var at build time. - Mirrors the source into `$srcdir/$pkgname` via rsync with `target/`, - `.git/`, and `packaging/arch/{pkg,src}/` excluded — do not casually add + `.git/`, and `pkgbuild/{pkg,src}/` excluded — do not casually add new top-level dirs without extending the exclude list. ### Flatpak (`packaging/flatpak/br.com.biglinux.webapps.yml`) @@ -150,9 +150,9 @@ That wraps: cargo fmt --check cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace -shellcheck biglinux-webapps/usr/bin/biglinux-webapps-systemd packaging/arch/PKGBUILD -shfmt -d biglinux-webapps/usr/bin/biglinux-webapps-systemd packaging/arch/PKGBUILD -bash -n biglinux-webapps/usr/bin/biglinux-webapps-systemd packaging/arch/PKGBUILD +shellcheck biglinux-webapps/usr/bin/biglinux-webapps-systemd pkgbuild/PKGBUILD +shfmt -d biglinux-webapps/usr/bin/biglinux-webapps-systemd pkgbuild/PKGBUILD +bash -n biglinux-webapps/usr/bin/biglinux-webapps-systemd pkgbuild/PKGBUILD ``` Warnings are failures. If the script is missing a tool, install it; do not diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b9da9648..ee5d828b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -113,7 +113,7 @@ executables are rejected at validation time (INVARIANTS — security). ## 8. Packaging -`packaging/arch/PKGBUILD`: declares `pkgname=biglinux-webapps`, depends on +`pkgbuild/PKGBUILD`: declares `pkgname=biglinux-webapps`, depends on `gtk4`, `libadwaita`, `webkitgtk-6.0`, `xdg-utils`, `desktop-file-utils`; makedepends on `rust`, `cargo`, `gettext`. Local-source detection prefers the workspace checkout (`BIGLINUX_WEBAPPS_LOCAL_SOURCE` env, or `Cargo.toml + diff --git a/Cargo.lock b/Cargo.lock index 7aaf886f..d58ca5ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -79,6 +85,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -216,6 +234,23 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -241,6 +276,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -428,6 +472,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.12.0" @@ -1312,6 +1366,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.1" @@ -1985,6 +2049,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "siphasher" version = "1.0.3" @@ -2217,6 +2287,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -2330,12 +2413,17 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ + "async-compression", "bitflags", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -2613,6 +2701,8 @@ dependencies = [ "log", "relm4", "serde_json", + "soup3", + "tempfile", "url", "webapps-core", "webkit6", diff --git a/Cargo.toml b/Cargo.toml index 72c753be..2d25366e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,14 @@ env_logger = { version = "0.11", default-features = false, features = ["auto-col anyhow = "1" dirs = "6" clap = { version = "4", features = ["derive"] } -reqwest = { version = "0.12", default-features = false, features = ["native-tls", "blocking"] } +# `gzip`: CDNs serve `manifest.json` with `Content-Encoding: gzip` whether or not +# the client advertises it (Google Cloud Storage stores it pre-compressed and +# replays the header). Without this feature reqwest hands the raw deflate stream +# to `serde_json`, every manifest parse fails, and the high-resolution icon set +# those manifests declare is never seen — the launcher falls back to a 32 px +# favicon. Decoding also happens before the read cap, so the cap still bounds +# decompressed bytes. +reqwest = { version = "0.12", default-features = false, features = ["native-tls", "blocking", "gzip"] } scraper = "0.26" url = "2" gettextrs = { package = "gettext-rs", version = "0.7", features = ["gettext-system"] } diff --git a/INVARIANTS.md b/INVARIANTS.md index 70f5691e..5b4e8ebc 100644 --- a/INVARIANTS.md +++ b/INVARIANTS.md @@ -33,6 +33,8 @@ Contracts the CI gates protect. Each line is enforced by a check listed in the r |---|---| | `webapps-exec` only spawns browsers resolved via the whitelist in `webapps-core::browsers` (`find_def` + `BrowserDef`) | `webapps-exec/src/launch.rs` resolves only `native_paths`/`flatpak_app_id`; unit tests in `webapps-core/src/browsers.rs` | | Atomic write for persisted permissions: `tmp + rename`, never overwrite-in-place | `webapps-viewer/src/window/permissions/mod.rs::save_permissions` | +| Viewer cookies persist via libsoup's **Text** jar, never `Sqlite` (which overflows expiries past 2038 and degrades `SameSite=None` to `Lax`) | `webapps-viewer/src/window/session.rs` sets `CookiePersistentStorage::Text`; rationale + round-trip tests in `window/cookie_migration.rs` | +| A profile holding a legacy `webkit-cookies.db` is converted to the text jar before `set_persistent_storage`, so a package update never drops sessions | `webapps-viewer/src/window/cookie_migration.rs::migrate_legacy_cookie_jar`; 10 unit tests incl. `round_trips_cookies_through_both_backends` | | Inter-process file locking via an exclusive advisory lock (`fs2::FileExt::lock_exclusive`) on `WebappsLock` for any write transaction | `webapps-manager/src/service/repository.rs::WebappsLock` | | All shell-quoted desktop-file `Exec=` lines pass `desktop::sanitize::sanitize_exec_arg` | unit tests in `webapps-core/src/desktop/sanitize.rs` (6 cases) | diff --git a/approved-crates.txt b/approved-crates.txt index 8a38186a..38824484 100644 --- a/approved-crates.txt +++ b/approved-crates.txt @@ -8,6 +8,7 @@ anstyle-wincon anyhow arbitrary async-channel +async-compression atomic-waker autocfg base64 @@ -35,6 +36,8 @@ clap_derive clap_lex colorchoice combine +compression-codecs +compression-core concurrent-queue core-foundation core-foundation-sys @@ -263,6 +266,7 @@ tinyvec tinyvec_macros tokio tokio-rustls +tokio-util toml toml_datetime toml_edit diff --git a/crates/webapps-core/src/templates/registry.rs b/crates/webapps-core/src/templates/registry.rs index d904e818..0d2d79ab 100644 --- a/crates/webapps-core/src/templates/registry.rs +++ b/crates/webapps-core/src/templates/registry.rs @@ -105,13 +105,43 @@ impl TemplateRegistry { cats } + /// Find the template whose domain best describes `url`. + /// + /// Matching is on the parsed **host**, not on the URL string, and the most + /// specific domain wins. The previous implementation did + /// `url.contains(template_domain)` over `HashMap::values()`, which was wrong + /// twice over: + /// + /// * **Substring false positives.** `"https://www.netflix.com"` contains + /// `"x.com"` (inside "netfli**x.com**"), so the DRM-free Twitter/X + /// template matched Netflix. + /// * **Non-deterministic order.** `music.youtube.com` matches both the + /// `youtube-music` domain and the plain `youtube.com` one, and + /// `HashMap` iteration order varies per process, so `find` returned + /// either one at random. + /// + /// Together those made [`Self::requires_drm`] flaky: the internal-browser + /// block for DRM sites fired on some launches and not others, letting the + /// user create a WebKit-backed Netflix webapp that plays no video. Ranking by + /// domain length makes `music.youtube.com` beat `youtube.com`, and the + /// `template_id` tiebreak keeps the outcome stable across processes for the + /// four templates that legitimately share `office.com`. pub fn match_url(&self, url: &str) -> Option<&WebAppTemplate> { - let url_lower = url.to_lowercase(); - self.templates.values().find(|tpl| { - tpl.domain() - .map(|d| url_lower.contains(&d)) - .unwrap_or(false) - }) + let host = host_of(url)?; + self.templates + .values() + .filter(|tpl| { + tpl.domain() + .is_some_and(|domain| host_matches_domain(&host, &domain)) + }) + .max_by(|left, right| { + let specificity = |tpl: &WebAppTemplate| tpl.domain().map_or(0, |d| d.len()); + specificity(left) + .cmp(&specificity(right)) + // Deterministic tiebreak: `HashMap` order is not stable + // across processes, and callers compare results between runs. + .then_with(|| right.template_id.cmp(&left.template_id)) + }) } pub fn search(&self, query: &str) -> Vec<&WebAppTemplate> { @@ -126,14 +156,69 @@ impl TemplateRegistry { .collect() } - /// Check if a webapp needs DRM — match by template_id or URL domain + /// Check if a webapp needs DRM — match by template_id or URL domain. + /// + /// When the host matches no template exactly, fall back to comparing + /// registrable domains against the DRM-requiring templates only. Several DRM + /// sites are registered under an app subdomain (`open.spotify.com`, + /// `listen.tidal.com`), so a user who types the bare `spotify.com` would + /// otherwise get the internal browser and silent playback. The asymmetry is + /// deliberate: a false positive here costs a webapp that opens in the + /// external browser, a false negative costs one that cannot play anything. pub fn requires_drm(&self, template_id: &str, url: &str) -> bool { if let Some(tpl) = self.templates.get(template_id) { return tpl.requires_drm; } - self.match_url(url) - .map(|tpl| tpl.requires_drm) - .unwrap_or(false) + if let Some(tpl) = self.match_url(url) { + return tpl.requires_drm; + } + let Some(host) = host_of(url) else { + return false; + }; + let Some(base) = registrable_domain(&host) else { + return false; + }; + self.templates.values().any(|tpl| { + tpl.requires_drm + && tpl + .domain() + .as_deref() + .and_then(registrable_domain) + .is_some_and(|tpl_base| tpl_base == base) + }) + } +} + +/// Lowercased host of `url`, tolerating input with no scheme (the manager stores +/// user-typed URLs before normalisation). +fn host_of(url: &str) -> Option { + let trimmed = url.trim(); + let parsed = url::Url::parse(trimmed) + .or_else(|_| url::Url::parse(&format!("https://{trimmed}"))) + .ok()?; + let host = parsed.host_str()?.to_lowercase(); + Some(host.strip_prefix("www.").unwrap_or(&host).to_string()) +} + +/// Whether `host` is `domain` itself or a subdomain of it. +/// +/// The leading dot is what makes this safe: without it, `"notnetflix.com"` +/// would match `"netflix.com"` by plain suffix. +fn host_matches_domain(host: &str, domain: &str) -> bool { + host == domain || host.ends_with(&format!(".{domain}")) +} + +/// Last two labels of a host — an approximation of the registrable domain. +/// +/// Not a Public Suffix List lookup, so a multi-label suffix like `co.uk` would +/// collapse to `co.uk`. That is acceptable because this is only consulted to +/// compare a host against the bundled templates, all of which sit on +/// single-label TLDs; a wrong answer there can only fail to match. +fn registrable_domain(host: &str) -> Option { + let labels: Vec<&str> = host.split('.').filter(|part| !part.is_empty()).collect(); + match labels[..] { + [.., second, top] => Some(format!("{second}.{top}")), + _ => None, } } @@ -296,4 +381,165 @@ mod tests { let tpl = sample_template("t", "Test", "https://www.example.com/path", "X"); assert_eq!(tpl.domain(), Some("example.com".into())); } + + fn drm_template(id: &str, url: &str) -> WebAppTemplate { + WebAppTemplate { + template_id: id.into(), + name: id.into(), + url: url.into(), + requires_drm: true, + ..Default::default() + } + } + + #[test] + fn host_matches_domain_requires_a_label_boundary() { + assert!(host_matches_domain("netflix.com", "netflix.com")); + assert!(host_matches_domain("www2.netflix.com", "netflix.com")); + // The dot is what stops a look-alike domain from matching. + assert!(!host_matches_domain("notnetflix.com", "netflix.com")); + assert!(!host_matches_domain("netflix.com.evil.test", "netflix.com")); + } + + #[test] + fn registrable_domain_takes_last_two_labels() { + assert_eq!( + registrable_domain("open.spotify.com").unwrap(), + "spotify.com" + ); + assert_eq!(registrable_domain("spotify.com").unwrap(), "spotify.com"); + assert!(registrable_domain("localhost").is_none()); + } + + #[test] + fn host_of_tolerates_missing_scheme_and_www() { + assert_eq!(host_of("https://www.example.com/x").unwrap(), "example.com"); + assert_eq!(host_of("example.com/x").unwrap(), "example.com"); + assert_eq!( + host_of(" HTTPS://WWW.Example.COM ").unwrap(), + "example.com" + ); + assert!(host_of("").is_none()); + } + + #[test] + fn match_url_does_not_substring_match_across_domains() { + // Regression pin: `"https://www.netflix.com".contains("x.com")` is true, + // so the DRM-free X template used to match Netflix. + let mut reg = TemplateRegistry::default(); + reg.register(sample_template("twitter", "X", "https://x.com", "Social")); + reg.register(drm_template("netflix", "https://www.netflix.com")); + + let matched = reg.match_url("https://www.netflix.com").expect("match"); + assert_eq!(matched.template_id, "netflix"); + assert!(reg.requires_drm("", "https://www.netflix.com")); + + // And X itself still matches its own domain. + assert_eq!( + reg.match_url("https://x.com/home").unwrap().template_id, + "twitter" + ); + assert!(!reg.requires_drm("", "https://x.com/home")); + } + + #[test] + fn match_url_prefers_the_most_specific_domain() { + // `music.youtube.com` matches both templates; the subdomain one must win + // so its `requires_drm` is the one that counts. + let mut reg = TemplateRegistry::default(); + reg.register(sample_template( + "youtube", + "YouTube", + "https://www.youtube.com", + "Media", + )); + reg.register(drm_template("youtube-music", "https://music.youtube.com")); + + assert_eq!( + reg.match_url("https://music.youtube.com/playlist") + .unwrap() + .template_id, + "youtube-music" + ); + assert!(reg.requires_drm("", "https://music.youtube.com/playlist")); + + // Plain YouTube keeps the less specific, non-DRM template. + assert_eq!( + reg.match_url("https://www.youtube.com/watch?v=1") + .unwrap() + .template_id, + "youtube" + ); + assert!(!reg.requires_drm("", "https://www.youtube.com/watch?v=1")); + } + + #[test] + fn match_url_is_deterministic_for_templates_sharing_a_domain() { + // Four bundled Office templates share `office.com`. `HashMap` order is + // randomised per process, so the same query must not return a different + // template on the next launch. + let reg = build_default_registry(); + let first = reg + .match_url("https://www.office.com/launch/word") + .map(|tpl| tpl.template_id.clone()); + for _ in 0..25 { + let again = build_default_registry() + .match_url("https://www.office.com/launch/word") + .map(|tpl| tpl.template_id.clone()); + assert_eq!(first, again, "match_url must be stable across registries"); + } + } + + #[test] + fn requires_drm_falls_back_to_the_registrable_domain() { + // `open.spotify.com` is the template; a user typing the bare + // `spotify.com` must still be treated as needing DRM, or the webapp + // opens in WebKit and plays nothing. + let mut reg = TemplateRegistry::default(); + reg.register(drm_template("spotify", "https://open.spotify.com")); + + assert!(reg.match_url("https://spotify.com").is_none()); + assert!(reg.requires_drm("", "https://spotify.com")); + assert!(reg.requires_drm("", "spotify.com/browse")); + // An unrelated host must not inherit it. + assert!(!reg.requires_drm("", "https://example.com")); + } + + #[test] + fn requires_drm_prefers_an_explicit_template_id() { + let mut reg = TemplateRegistry::default(); + reg.register(drm_template("netflix", "https://www.netflix.com")); + reg.register(sample_template("blog", "Blog", "https://blog.test", "X")); + + assert!(reg.requires_drm("netflix", "https://unrelated.test")); + assert!(!reg.requires_drm("blog", "https://www.netflix.com")); + } + + #[test] + fn requires_drm_on_the_bundled_registry() { + let reg = default_registry(); + for url in [ + "https://www.netflix.com/browse", + "https://www.primevideo.com", + "https://www.disneyplus.com", + "https://open.spotify.com", + "https://music.youtube.com", + "https://listen.tidal.com", + "https://www.deezer.com", + // Bare registrable domains of the subdomain-registered DRM sites. + "https://spotify.com", + "https://tidal.com", + ] { + assert!(reg.requires_drm("", url), "{url} must require DRM"); + } + for url in [ + "https://github.com", + "https://mail.google.com", + "https://x.com", + "https://www.youtube.com", + "https://example.com", + ] { + assert!(!reg.requires_drm("", url), "{url} must not require DRM"); + } + } } diff --git a/crates/webapps-manager/src/favicon/download.rs b/crates/webapps-manager/src/favicon/download.rs index ff1dbec3..80be7ab8 100644 --- a/crates/webapps-manager/src/favicon/download.rs +++ b/crates/webapps-manager/src/favicon/download.rs @@ -1,21 +1,67 @@ use anyhow::{Context, Result}; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::time::Duration; +use super::html::IconSource; +use super::image::{self, ImageFormat}; use crate::http_client::{http_get_bytes_capped, RequestHeaders}; -/// Hard cap on icon byte size. Favicons in the wild rarely exceed 200 KB; the -/// 1 MB ceiling defends against decompression abuse while leaving headroom for -/// well-padded SVG/PNG sets some sites ship. -const MAX_ICON_BYTES: usize = 1024 * 1024; +/// Hard cap on icon byte size. +/// +/// The old 1 MB ceiling silently rejected exactly the assets we most want: a +/// 1024×1024 PNG app icon with a photographic background routinely lands between +/// 1 and 3 MB, so the high-res candidate failed the cap and ranking fell back to +/// a 32 px favicon. 8 MB still bounds a hostile server's ability to fill memory +/// (the read is capped mid-stream, not after buffering) while clearing every +/// realistic icon by a wide margin. +const MAX_ICON_BYTES: usize = 8 * 1024 * 1024; + +/// Side length the winning icon is normalised to when its source is smaller. +/// +/// Launchers draw app icons at 96–128 px and HiDPI doubles that. Handing the +/// shell a 32 px file forces *it* to upscale, with a fast bilinear filter and no +/// sharpening — the mushy result in the bug report. Producing a 512 px PNG +/// ourselves with Lanczos does not invent detail, but it does put the resampling +/// under our control and stops every downstream consumer from redoing it badly. +pub(super) const TARGET_SIDE: u32 = 512; + +/// An icon fetched to the cache, with its resolution measured from the bytes +/// rather than taken from the page's `sizes` hint. +#[derive(Debug, Clone)] +pub(super) struct DownloadedIcon { + pub path: PathBuf, + /// Measured extent; `None` when the container header was unparseable. + pub dimensions: Option, + pub is_vector: bool, + /// Where the URL was advertised. Kept past the download because a + /// `mask-icon` silhouette and an `og:image` banner must stay demoted no + /// matter how many pixels they turn out to have. + pub source: IconSource, +} + +impl DownloadedIcon { + /// Resolution used for ranking. Unmeasurable assets sort below everything + /// measured but still above nothing at all. + pub(super) fn effective_side(&self) -> u32 { + self.dimensions.map_or(0, image::Dimensions::side) + } + + /// Whether the asset is shaped like an icon. Unmeasurable assets are given + /// the benefit of the doubt — they are usually favicons whose container we + /// simply do not parse, not share banners. + pub(super) fn is_icon_shaped(&self) -> bool { + self.dimensions.is_none_or(image::Dimensions::is_square_ish) + } +} pub(super) fn download_icon( url: &str, - cache_dir: &std::path::Path, + cache_dir: &Path, index: usize, -) -> Result { + source: IconSource, +) -> Result { let response = http_get_bytes_capped( url, &RequestHeaders::browser(), @@ -29,18 +75,7 @@ pub(super) fn download_icon( } if let Some(content_type) = response.content_type.as_deref() { - let content_type = content_type.to_lowercase(); - // Strict allowlist: `image/*` covers png/jpeg/webp/svg/x-icon, plus - // explicit `application/octet-stream` (some CDNs serve favicons that - // way). Reject anything else — accepting "could be image" responses - // expanded the parser attack surface unnecessarily. - let acceptable = content_type.starts_with("image/") - || content_type == "application/octet-stream" - || content_type.starts_with("application/octet-stream;") - || content_type.starts_with("application/vnd.microsoft.icon"); - if !acceptable { - anyhow::bail!("Not an image: {content_type}"); - } + reject_non_image(content_type)?; } let bytes = response.bytes; @@ -48,90 +83,370 @@ pub(super) fn download_icon( anyhow::bail!("Empty response"); } - let ext = guess_extension(url, &bytes); - let path = cache_dir.join(format!("icon_{index}.{ext}")); + let format = image::detect_format(&bytes); + if format == ImageFormat::Ico { + return store_ico(&bytes, cache_dir, index, source); + } + + let dimensions = image::measure(&bytes, format); + let path = cache_dir.join(format!("icon_{index}.{}", format.extension())); + std::fs::write(&path, &bytes)?; + Ok(DownloadedIcon { + path, + dimensions, + is_vector: format.is_vector(), + source, + }) +} + +/// Strict allowlist: `image/*` covers png/jpeg/webp/svg/x-icon, plus explicit +/// `application/octet-stream` (some CDNs serve favicons that way). Reject +/// anything else — accepting "could be an image" responses expanded the parser +/// attack surface unnecessarily. +fn reject_non_image(content_type: &str) -> Result<()> { + let content_type = content_type.to_lowercase(); + let acceptable = content_type.starts_with("image/") + || content_type == "application/octet-stream" + || content_type.starts_with("application/octet-stream;") + || content_type.starts_with("application/vnd.microsoft.icon"); + if acceptable { + Ok(()) + } else { + anyhow::bail!("Not an image: {content_type}") + } +} + +/// Store an ICO as a PNG of its **largest** frame. +/// +/// Writing the `.ico` verbatim leaves frame choice to gdk-pixbuf, which returns +/// whichever frame it decodes first — in practice the 16 px one, even when the +/// same file carries a 48 or 256 px frame. Since `favicon.ico` is the only icon +/// many sites publish, picking the frame ourselves is often the whole difference +/// between a sharp and a blurry launcher entry. +fn store_ico( + bytes: &[u8], + cache_dir: &Path, + index: usize, + source: IconSource, +) -> Result { + let png_path = cache_dir.join(format!("icon_{index}.png")); + let frame = image::largest_ico_frame(bytes); + // ICO frames are square by definition, so the frame side describes both axes. + let square = |side: u32| image::Dimensions { + width: side, + height: side, + }; + + // Fast path: modern ICOs embed the large frames as complete PNG streams, so + // the payload can be lifted out byte-for-byte with no decoder at all. + if let Some(png) = frame.as_ref().and_then(|frame| frame.embedded_png) { + std::fs::write(&png_path, png)?; + return Ok(DownloadedIcon { + path: png_path, + dimensions: image::measure(png, ImageFormat::Png), + is_vector: false, + source, + }); + } - if ext == "ico" { - let png_path = cache_dir.join(format!("icon_{index}.png")); - if convert_icon_to_png_with_magick(&bytes, &png_path).is_ok() { - return Ok(png_path); + if let Some(frame) = frame.as_ref() { + if extract_ico_frame_with_magick(bytes, frame.index, &png_path).is_ok() { + let dimensions = image::measure_file(&png_path).or_else(|| Some(square(frame.side))); + return Ok(DownloadedIcon { + path: png_path, + dimensions, + is_vector: false, + source, + }); } } - std::fs::write(&path, &bytes)?; - Ok(path) + // No ImageMagick (or it choked): keep the raw `.ico`. gdk-pixbuf can still + // render it, just without our control over frame selection. + let ico_path = cache_dir.join(format!("icon_{index}.ico")); + std::fs::write(&ico_path, bytes)?; + Ok(DownloadedIcon { + path: ico_path, + dimensions: frame.map(|frame| square(frame.side)), + is_vector: false, + source, + }) } -fn convert_icon_to_png_with_magick(bytes: &[u8], png_path: &std::path::Path) -> Result<()> { +/// Decode one addressed frame of an ICO to a PNG. +/// +/// Two details here are load-bearing and were both wrong before: +/// +/// * The input **must** carry an explicit `ICO:` format prefix. ImageMagick +/// stages piped stdin in an extension-less temp file and sniffs the format +/// from the name, so a bare `magick -` fails outright with "no decode +/// delegate" on every single ICO — the conversion never ran in production. +/// * A frame index **must** be selected. Given a multi-frame input and a +/// single-image output format, ImageMagick writes `icon_0-0.png`, +/// `icon_0-1.png`, … and never the requested `icon_0.png`, so the caller +/// would report success while pointing at a file that does not exist. +fn extract_ico_frame_with_magick(bytes: &[u8], frame: usize, png_path: &Path) -> Result<()> { + run_magick( + bytes, + &[ + format!("ICO:-[{frame}]"), + format!("PNG32:{}", png_path.display()), + ], + )?; + // The frame selector should guarantee a single output file, but a stale or + // patched ImageMagick that still numbers its output would leave us reporting + // success for a missing path. + if png_path.is_file() { + Ok(()) + } else { + anyhow::bail!("magick produced no file at {}", png_path.display()) + } +} + +/// Resample `source` up to `TARGET_SIDE` and return the new path, or `None` when +/// the upscale is unnecessary or ImageMagick is unavailable. +/// +/// `-background none` plus a centred `-extent` keeps non-square logos square and +/// transparent instead of stretched, which is what the freedesktop icon spec and +/// every launcher grid expect. +pub(super) fn upscale_to_target(source: &Path, cache_dir: &Path) -> Option { + let target = cache_dir.join("icon_hires.png"); + let args = [ + // No `FORMAT:` read hint: the extension is now derived from the real + // container magic, so ImageMagick's own sniffing is correct, and forcing + // `PNG:` would break a legitimately-webp or -jpeg source. + source.display().to_string(), + "-filter".into(), + "Lanczos".into(), + // A single fit-in-box resize both enlarges and shrinks; callers only + // reach here for sources below the target, so this always enlarges. + "-resize".into(), + format!("{TARGET_SIDE}x{TARGET_SIDE}"), + "-background".into(), + "none".into(), + "-gravity".into(), + "center".into(), + "-extent".into(), + format!("{TARGET_SIDE}x{TARGET_SIDE}"), + format!("PNG32:{}", target.display()), + ]; + match run_magick(&[], &args) { + Ok(()) if target.is_file() => Some(target), + Ok(()) => None, + Err(err) => { + log::warn!("Upscale {} to {TARGET_SIDE}px: {err}", source.display()); + None + } + } +} + +/// Run `magick` with `args`, feeding `stdin_bytes` when non-empty. +/// +/// stdin is closed before `wait_with_output` in both paths: ImageMagick reads +/// its input to EOF, so holding the pipe open while waiting for exit would +/// deadlock. Writing to a closed pipe is also tolerated — `magick` exits early +/// on malformed input, and the resulting `EPIPE` must surface as a conversion +/// failure, not a panic. +fn run_magick(stdin_bytes: &[u8], args: &[String]) -> Result<()> { let mut command = Command::new("magick"); - command.arg("-"); - command.arg(format!("PNG:{}", png_path.display())); - command.stdin(Stdio::piped()); + command.args(args); + command.stdin(if stdin_bytes.is_empty() { + Stdio::null() + } else { + Stdio::piped() + }); command.stdout(Stdio::null()); command.stderr(Stdio::piped()); + let mut child = command.spawn().context("spawn ImageMagick magick")?; - { - let stdin = child + if !stdin_bytes.is_empty() { + let mut stdin = child .stdin - .as_mut() + .take() .ok_or_else(|| anyhow::anyhow!("failed to open magick stdin"))?; - stdin.write_all(bytes)?; + let write_result = stdin.write_all(stdin_bytes); + drop(stdin); + if let Err(err) = write_result { + // Let the exit status below produce the real diagnostic; a broken + // pipe here just means magick rejected the input first. + log::debug!("magick stdin write: {err}"); + } } + let output = child.wait_with_output()?; if output.status.success() { Ok(()) } else { let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("magick icon conversion failed: {stderr}") + anyhow::bail!("magick failed: {}", stderr.trim()) } } -fn guess_extension(url: &str, bytes: &[u8]) -> &'static str { - if bytes.starts_with(b"\x89PNG") { - return "png"; - } - if bytes.starts_with(b" bool { + Command::new("magick") + .arg("-version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok_and(|status| status.success()) } - if url.contains(".svg") { - return "svg"; + + /// 3-frame ICO (16/48/32 px BMP frames) built by ImageMagick itself, so the + /// bytes exercise a real-world encoder rather than a hand-rolled header. + fn real_multi_frame_ico(dir: &Path) -> Option> { + let path = dir.join("multi.ico"); + let args: Vec = vec![ + "-size".into(), + "48x48".into(), + "xc:red".into(), + "-define".into(), + "icon:auto-resize=48,32,16".into(), + format!("ICO:{}", path.display()), + ]; + run_magick(&[], &args).ok()?; + std::fs::read(&path).ok() } - if url.contains(".ico") { - return "ico"; + + #[test] + fn content_type_allowlist() { + assert!(reject_non_image("image/png").is_ok()); + assert!(reject_non_image("IMAGE/SVG+XML").is_ok()); + assert!(reject_non_image("application/octet-stream").is_ok()); + assert!(reject_non_image("application/octet-stream; charset=binary").is_ok()); + assert!(reject_non_image("application/vnd.microsoft.icon").is_ok()); + assert!(reject_non_image("text/html").is_err()); + assert!(reject_non_image("application/json").is_err()); } - "png" -} -#[cfg(test)] -mod tests { - use super::*; + #[test] + fn effective_side_ranks_unmeasured_last() { + let measured = DownloadedIcon { + path: PathBuf::new(), + dimensions: Some(image::Dimensions { + width: 64, + height: 64, + }), + is_vector: false, + source: IconSource::Icon, + }; + let unmeasured = DownloadedIcon { + path: PathBuf::new(), + dimensions: None, + is_vector: false, + source: IconSource::Icon, + }; + assert!(measured.effective_side() > unmeasured.effective_side()); + } #[test] - fn guess_extension_png_magic() { - assert_eq!(guess_extension("https://x.com/img", b"\x89PNG\r\n"), "png"); + fn store_ico_lifts_embedded_png_without_imagemagick() { + // Single-frame ICO whose payload is a complete 256 px PNG stream. The + // fast path must return that PNG verbatim, so this test passes on hosts + // with no ImageMagick at all. + let mut png = b"\x89PNG\r\n\x1a\n".to_vec(); + png.extend_from_slice(&13u32.to_be_bytes()); + png.extend_from_slice(b"IHDR"); + png.extend_from_slice(&256u32.to_be_bytes()); + png.extend_from_slice(&256u32.to_be_bytes()); + png.extend_from_slice(&[8, 6, 0, 0, 0]); + + let mut ico = vec![0, 0, 1, 0, 1, 0]; + ico.extend_from_slice(&[0, 0, 0, 0, 1, 0, 32, 0]); + ico.extend_from_slice(&(png.len() as u32).to_le_bytes()); + ico.extend_from_slice(&22u32.to_le_bytes()); + ico.extend_from_slice(&png); + + let tmp = TempDir::new().unwrap(); + let stored = store_ico(&ico, tmp.path(), 3, IconSource::Icon).expect("stored"); + assert_eq!(stored.path, tmp.path().join("icon_3.png")); + assert_eq!(stored.effective_side(), 256); + assert_eq!(std::fs::read(&stored.path).unwrap(), png); } #[test] - fn guess_extension_svg_magic() { - assert_eq!(guess_extension("https://x.com/img", b" = vec![ + "-size".into(), + "32x32".into(), + "xc:blue".into(), + format!("PNG:{}", small.display()), + ]; + run_magick(&[], &args).unwrap(); + + let upscaled = upscale_to_target(&small, tmp.path()).expect("upscaled"); + assert_eq!( + image::measure_file(&upscaled).map(image::Dimensions::side), + Some(TARGET_SIDE) + ); } #[test] - fn guess_extension_url_fallback() { - assert_eq!(guess_extension("https://x.com/icon.svg", b"unknown"), "svg"); - assert_eq!(guess_extension("https://x.com/icon.ico", b"unknown"), "ico"); + fn upscale_pads_non_square_source_instead_of_stretching() { + if !magick_available() { + return; + } + let tmp = TempDir::new().unwrap(); + let wide = tmp.path().join("wide.png"); + let args: Vec = vec![ + "-size".into(), + "200x50".into(), + "xc:green".into(), + format!("PNG:{}", wide.display()), + ]; + run_magick(&[], &args).unwrap(); + + let upscaled = upscale_to_target(&wide, tmp.path()).expect("upscaled"); + // A square canvas is what launchers expect; stretching a 4:1 banner to + // fill it would distort the logo. + assert_eq!( + image::measure_file(&upscaled).map(image::Dimensions::side), + Some(TARGET_SIDE) + ); } #[test] - fn guess_extension_default_png() { - assert_eq!(guess_extension("https://x.com/img", b"unknown"), "png"); + fn run_magick_reports_failure_for_garbage_input() { + if !magick_available() { + return; + } + let tmp = TempDir::new().unwrap(); + let out = tmp.path().join("out.png"); + let result = run_magick( + b"not an image at all", + &["ICO:-[0]".into(), format!("PNG32:{}", out.display())], + ); + assert!(result.is_err(), "garbage input must not report success"); } } diff --git a/crates/webapps-manager/src/favicon/html.rs b/crates/webapps-manager/src/favicon/html.rs index 0b714690..5f56bba5 100644 --- a/crates/webapps-manager/src/favicon/html.rs +++ b/crates/webapps-manager/src/favicon/html.rs @@ -23,8 +23,36 @@ impl IconSource { match self { Self::Manifest => 6, Self::AppleTouch => 5, - Self::MaskIcon => 4, Self::Icon => 3, + // `mask-icon` is Safari's pinned-tab asset: a single-colour + // silhouette with no brand colour, meant to be recoloured by the + // browser chrome. It used to sit above `Icon` *and* score the vector + // bonus, so a monochrome outline beat the site's real full-colour + // icon. It stays in the list only as a last resort before og:image. + Self::MaskIcon => 1, + Self::OgImage => 0, + } + } + + /// Whether being an SVG should promote this candidate. Resolution + /// independence is worth a lot — except for `mask-icon`, where the vector is + /// a flat silhouette and winning on that bonus is precisely the wrong + /// outcome. + fn honours_vector_bonus(self) -> bool { + self != Self::MaskIcon + } + + /// Coarse band applied *after* download, ahead of measured resolution. + /// + /// Everything a site publishes as an app icon shares the top tier, so + /// resolution decides between them. The two sources that are not really app + /// icons get their own tiers below, which no pixel count can overcome: a + /// monochrome silhouette or a share banner is the wrong picture, not merely + /// a smaller one. + pub(super) fn rank_tier(self) -> u8 { + match self { + Self::Manifest | Self::AppleTouch | Self::Icon => 2, + Self::MaskIcon => 1, Self::OgImage => 0, } } @@ -53,9 +81,11 @@ impl IconCandidate { } } + /// Declared quality, used only to order *fetch* attempts — the final + /// ranking is redone in `mod.rs` on the measured pixel dimensions. fn quality_key(&self) -> (u8, u32, u32) { ( - u8::from(self.vector_hint), + u8::from(self.vector_hint && self.source.honours_vector_bonus()), self.declared_size.unwrap_or(0), u32::from(self.source.priority()), ) @@ -190,6 +220,20 @@ fn is_vector_hint(href: &str, sizes: &str, mime_type: &str) -> bool { .ends_with(".svg") } +/// Fold `incoming` into `candidates`, keeping one entry per URL. +/// +/// The same asset is routinely listed both as a `