diff --git a/.gitignore b/.gitignore index 45cd856..19b9179 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ logs/ # Claude Code local agent state .claude/ + +# Local hygiene notes (not for commit) +CLAUDE.md diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index e1449b5..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,990 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "bos-settings" -version = "0.7.0" -dependencies = [ - "async-channel", - "bread-theme", - "bread-utils", - "glib", - "gtk4", - "serde", - "serde_json", - "toml 0.8.23", - "toml_edit 0.22.27", -] - -[[package]] -name = "bread-theme" -version = "0.2.3" -source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.10#17d1bb85801b9a8c195b64c02d288cd662c9c780" -dependencies = [ - "dirs", - "gtk4", - "serde", - "serde_json", -] - -[[package]] -name = "bread-utils" -version = "0.3.0" -source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.3.0#8e82d2d833e992ce939a5b836f910ee109f2e939" -dependencies = [ - "dirs", - "serde", - "serde_json", - "toml_edit 0.22.27", -] - -[[package]] -name = "cairo-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" -dependencies = [ - "bitflags", - "cairo-sys-rs", - "glib", - "libc", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "cfg-expr" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "futures-channel" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" - -[[package]] -name = "futures-executor" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" - -[[package]] -name = "futures-macro" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-task" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" - -[[package]] -name = "futures-util" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk4" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk4-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk4-sys" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gio" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.61.2", -] - -[[package]] -name = "glib" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "graphene-rs" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff" -dependencies = [ - "glib", - "graphene-sys", -] - -[[package]] -name = "graphene-sys" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gsk4" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff" -dependencies = [ - "cairo-rs", - "gdk4", - "glib", - "graphene-rs", - "gsk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gsk4-sys" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088" -dependencies = [ - "cairo-sys-rs", - "gdk4-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk4" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9" -dependencies = [ - "cairo-rs", - "field-offset", - "futures-channel", - "gdk-pixbuf", - "gdk4", - "gio", - "glib", - "graphene-rs", - "gsk4", - "gtk4-macros", - "gtk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gtk4-macros" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gtk4-sys" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "gsk4-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libredox" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "pango" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" -dependencies = [ - "gio", - "glib", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "system-deps" -version = "7.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml 1.1.3+spec-1.1.0", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.4", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index e396c3f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "bos-settings" -version = "0.7.0" -edition = "2021" - -[dependencies] -gtk4 = { version = "0.11", features = ["v4_12"] } -glib = "0.22" -# Shared ecosystem theming — bos-settings loads the same generated stylesheet as -# breadbar/breadbox/breadpad so the whole desktop looks consistent. -bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.10", features = ["gtk"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -toml = "0.8" -# toml_edit drives non-destructive config editing: it preserves comments and -# any keys the UI doesn't model, so saving a single field never rewrites or -# drops the rest of the user's config file. -toml_edit = "0.22" -async-channel = "2" -# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern -bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["toml"] } diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..6635cf5 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..858d179 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,7 @@ +# Tauri + SvelteKit + TypeScript + +This template should help get you started developing with Tauri, SvelteKit and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..6f1479c --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1942 @@ +{ + "name": "bos-settings-frontend", + "version": "0.8.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bos-settings-frontend", + "version": "0.8.0", + "license": "MIT", + "dependencies": { + "@lucide/svelte": "^1.25.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.2", + "@tauri-apps/plugin-opener": "^2" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.6", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^6.0.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lucide/svelte": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.25.0.tgz", + "integrity": "sha512-v9m+dD68jxVnqkU3K59mG/RSRFlPGzmKCGSyMfnXcaGv9jODDQMyQkcp1CGvk3Y/cUj9v7f8rw1n//K0B53xGQ==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz", + "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.70.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.70.1.tgz", + "integrity": "sha512-nY9SPHGOZro3doud9vZXDBwl9tCZIouuJztjgSHs6PAIrv9M/z5O7eOhPV5xU7CgVHA976Jwu3BA1hIFvXztkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.2.tgz", + "integrity": "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", + "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devalue": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.2.tgz", + "integrity": "sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz", + "integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz", + "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.56.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.7.tgz", + "integrity": "sha512-5qERUZX80oQj6XrDMUmD2Uhd/cIpCPDWWKBK3ZHmyRUC9apPyamWM8xMo31mbWsIQxwG2hVoSnOJ/EcnhVkkzQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.12", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.3.tgz", + "integrity": "sha512-DHdTCGX62R0fCxBEaT+USdASAnoaRBaaNczkRJl0K7o3WyoCeVUbVxo6fKqpOll/B+WMWCsiFK0eFrJSNBKZIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..08d2563 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "name": "bos-settings-frontend", + "version": "0.8.0", + "description": "Frontend for BOS Settings", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "tauri": "tauri" + }, + "license": "MIT", + "dependencies": { + "@lucide/svelte": "^1.25.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.2", + "@tauri-apps/plugin-opener": "^2" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.6", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^6.0.3" + } +} diff --git a/frontend/src/app.html b/frontend/src/app.html new file mode 100644 index 0000000..d367d13 --- /dev/null +++ b/frontend/src/app.html @@ -0,0 +1,13 @@ + + + + + + + BOS Settings + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/frontend/src/lib/components/ChipPickerField.svelte b/frontend/src/lib/components/ChipPickerField.svelte new file mode 100644 index 0000000..0e790c0 --- /dev/null +++ b/frontend/src/lib/components/ChipPickerField.svelte @@ -0,0 +1,126 @@ + + +
+
+ {label} +
+
+ {#each value as name (name)} + + {name} + + + {/each} + {#if adding} + {#if available.length > 0} + + + {:else} + {emptyOptionsHint} + {/if} + {:else if available.length > 0} + + {/if} +
+
+ + diff --git a/frontend/src/lib/components/CsvField.svelte b/frontend/src/lib/components/CsvField.svelte new file mode 100644 index 0000000..b515002 --- /dev/null +++ b/frontend/src/lib/components/CsvField.svelte @@ -0,0 +1,35 @@ + + + + + + + diff --git a/frontend/src/lib/components/EmptyState.svelte b/frontend/src/lib/components/EmptyState.svelte new file mode 100644 index 0000000..357d48f --- /dev/null +++ b/frontend/src/lib/components/EmptyState.svelte @@ -0,0 +1,32 @@ + + +
+ + {title} + {hint} +
+ + diff --git a/frontend/src/lib/components/FileField.svelte b/frontend/src/lib/components/FileField.svelte new file mode 100644 index 0000000..e320be6 --- /dev/null +++ b/frontend/src/lib/components/FileField.svelte @@ -0,0 +1,67 @@ + + + +
+ + +
+
+ + diff --git a/frontend/src/lib/components/Group.svelte b/frontend/src/lib/components/Group.svelte new file mode 100644 index 0000000..5a6caae --- /dev/null +++ b/frontend/src/lib/components/Group.svelte @@ -0,0 +1,60 @@ + + +
+

{title}

+ {#if hint}

{hint}

{/if} +
+ {@render children()} +
+
+ + diff --git a/frontend/src/lib/components/Hint.svelte b/frontend/src/lib/components/Hint.svelte new file mode 100644 index 0000000..450e2ad --- /dev/null +++ b/frontend/src/lib/components/Hint.svelte @@ -0,0 +1,18 @@ + + +

{text}

+ + diff --git a/frontend/src/lib/components/HyprColorField.svelte b/frontend/src/lib/components/HyprColorField.svelte new file mode 100644 index 0000000..115d4d7 --- /dev/null +++ b/frontend/src/lib/components/HyprColorField.svelte @@ -0,0 +1,56 @@ + + + +
+ update(e.currentTarget.value, parsed.alpha)} /> + update(parsed.hex, Number(e.currentTarget.value))} + /> +
+
+ + diff --git a/frontend/src/lib/components/InfoRow.svelte b/frontend/src/lib/components/InfoRow.svelte new file mode 100644 index 0000000..f714f94 --- /dev/null +++ b/frontend/src/lib/components/InfoRow.svelte @@ -0,0 +1,16 @@ + + + + {value} + + + diff --git a/frontend/src/lib/components/LogView.svelte b/frontend/src/lib/components/LogView.svelte new file mode 100644 index 0000000..a3206b5 --- /dev/null +++ b/frontend/src/lib/components/LogView.svelte @@ -0,0 +1,24 @@ + + +{#if lines.length > 0} +
{lines.join("\n")}
+{/if} + + diff --git a/frontend/src/lib/components/NumberField.svelte b/frontend/src/lib/components/NumberField.svelte new file mode 100644 index 0000000..7d0f8ac --- /dev/null +++ b/frontend/src/lib/components/NumberField.svelte @@ -0,0 +1,31 @@ + + + + + + + diff --git a/frontend/src/lib/components/PasswordField.svelte b/frontend/src/lib/components/PasswordField.svelte new file mode 100644 index 0000000..fb47eff --- /dev/null +++ b/frontend/src/lib/components/PasswordField.svelte @@ -0,0 +1,60 @@ + + + +
+ + +
+
+ + diff --git a/frontend/src/lib/components/PathListField.svelte b/frontend/src/lib/components/PathListField.svelte new file mode 100644 index 0000000..ac56678 --- /dev/null +++ b/frontend/src/lib/components/PathListField.svelte @@ -0,0 +1,135 @@ + + +
+
+ {label} + +
+ {#if hint}

{hint}

{/if} + {#if value.length === 0} +

None added.

+ {:else} +
+ {#each value as path (path)} + + {shorten(path)} + + + {/each} +
+ {/if} +
+ + diff --git a/frontend/src/lib/components/Placeholder.svelte b/frontend/src/lib/components/Placeholder.svelte new file mode 100644 index 0000000..0f37632 --- /dev/null +++ b/frontend/src/lib/components/Placeholder.svelte @@ -0,0 +1,17 @@ + + +
+

"{page}" hasn't been migrated to Tauri yet.

+
+ + diff --git a/frontend/src/lib/components/Row.svelte b/frontend/src/lib/components/Row.svelte new file mode 100644 index 0000000..b04dbd7 --- /dev/null +++ b/frontend/src/lib/components/Row.svelte @@ -0,0 +1,57 @@ + + +
+ {label} +
+ {@render children()} +
+
+ + diff --git a/frontend/src/lib/components/SaveButton.svelte b/frontend/src/lib/components/SaveButton.svelte new file mode 100644 index 0000000..5dcd916 --- /dev/null +++ b/frontend/src/lib/components/SaveButton.svelte @@ -0,0 +1,55 @@ + + +
+ + {status} +
+ + diff --git a/frontend/src/lib/components/SelectField.svelte b/frontend/src/lib/components/SelectField.svelte new file mode 100644 index 0000000..2dc50c6 --- /dev/null +++ b/frontend/src/lib/components/SelectField.svelte @@ -0,0 +1,34 @@ + + + + + + + diff --git a/frontend/src/lib/components/ServiceControl.svelte b/frontend/src/lib/components/ServiceControl.svelte new file mode 100644 index 0000000..1c81d29 --- /dev/null +++ b/frontend/src/lib/components/ServiceControl.svelte @@ -0,0 +1,93 @@ + + + + + {active ? "Running" : "Stopped"} + + + {enabled ? "Yes" : "No"} + +
+ + + +
+ {#if hasConfig} + + {/if} +
+ + diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte new file mode 100644 index 0000000..95fb43e --- /dev/null +++ b/frontend/src/lib/components/Sidebar.svelte @@ -0,0 +1,84 @@ + + + + + diff --git a/frontend/src/lib/components/Switch.svelte b/frontend/src/lib/components/Switch.svelte new file mode 100644 index 0000000..e2504d3 --- /dev/null +++ b/frontend/src/lib/components/Switch.svelte @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/lib/components/SwitchField.svelte b/frontend/src/lib/components/SwitchField.svelte new file mode 100644 index 0000000..a49caaf --- /dev/null +++ b/frontend/src/lib/components/SwitchField.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/frontend/src/lib/components/TagsField.svelte b/frontend/src/lib/components/TagsField.svelte new file mode 100644 index 0000000..ee7793d --- /dev/null +++ b/frontend/src/lib/components/TagsField.svelte @@ -0,0 +1,104 @@ + + +
+
+ {label} +
+
+ {#each value as name (name)} + + {name} + + + {/each} + +
+
+ + diff --git a/frontend/src/lib/components/TextField.svelte b/frontend/src/lib/components/TextField.svelte new file mode 100644 index 0000000..62fa918 --- /dev/null +++ b/frontend/src/lib/components/TextField.svelte @@ -0,0 +1,29 @@ + + + + + + + diff --git a/frontend/src/lib/components/ViewScaffold.svelte b/frontend/src/lib/components/ViewScaffold.svelte new file mode 100644 index 0000000..bb27e82 --- /dev/null +++ b/frontend/src/lib/components/ViewScaffold.svelte @@ -0,0 +1,40 @@ + + +
+

{title}

+
+ {@render children()} +
+
+ + diff --git a/frontend/src/lib/sidebar.ts b/frontend/src/lib/sidebar.ts new file mode 100644 index 0000000..7e945ed --- /dev/null +++ b/frontend/src/lib/sidebar.ts @@ -0,0 +1,87 @@ +// Ports src/ui/sidebar.rs's declarative item lists verbatim. Grouped by task, +// not "app vs system internals" — a user thinks "I want to change my Wi-Fi", +// not "which of these is a bread-ecosystem app" (why breadcrumbs/Wi-Fi +// Profiles lives in System, not Personalization). + +import type { Component } from "svelte"; +import Wifi from "@lucide/svelte/icons/wifi"; +import Network from "@lucide/svelte/icons/network"; +import Bluetooth from "@lucide/svelte/icons/bluetooth"; +import Shield from "@lucide/svelte/icons/shield"; +import Volume2 from "@lucide/svelte/icons/volume-2"; +import BatteryFull from "@lucide/svelte/icons/battery-full"; +import Clock from "@lucide/svelte/icons/clock"; +import Monitor from "@lucide/svelte/icons/monitor"; +import Keyboard from "@lucide/svelte/icons/keyboard"; +import Rocket from "@lucide/svelte/icons/rocket"; +import Users from "@lucide/svelte/icons/users"; +import Palette from "@lucide/svelte/icons/palette"; +import Image from "@lucide/svelte/icons/image"; +import LayoutGrid from "@lucide/svelte/icons/layout-grid"; +import Grid3x3 from "@lucide/svelte/icons/grid-3x3"; +import Clipboard from "@lucide/svelte/icons/clipboard"; +import NotebookPen from "@lucide/svelte/icons/notebook-pen"; +import Search from "@lucide/svelte/icons/search"; +import Cog from "@lucide/svelte/icons/cog"; +import Package from "@lucide/svelte/icons/package"; +import RefreshCw from "@lucide/svelte/icons/refresh-cw"; +import History from "@lucide/svelte/icons/history"; +import Info from "@lucide/svelte/icons/info"; + +export interface SidebarItem { + /** Must match a key in the view component map (see routing in +page.svelte). */ + id: string; + label: string; + /** Dim second line — the underlying binary/config name, for items whose + * human label doesn't already make that obvious. */ + sublabel?: string; + icon: Component; +} + +export const SYSTEM_ITEMS: SidebarItem[] = [ + { id: "network", label: "Network", icon: Wifi }, + { id: "breadcrumbs", label: "Wi-Fi Profiles", sublabel: "breadcrumbs", icon: Network }, + { id: "bluetooth", label: "Bluetooth", icon: Bluetooth }, + { id: "firewall", label: "Firewall", icon: Shield }, + { id: "sound", label: "Sound", icon: Volume2 }, + { id: "power", label: "Power", icon: BatteryFull }, + { id: "datetime", label: "Date & Time", icon: Clock }, + { id: "hyprland", label: "Display", sublabel: "monitors.json", icon: Monitor }, + { id: "keybinds", label: "Keybinds", sublabel: "binds.json", icon: Keyboard }, + { id: "autostart", label: "Startup Apps", sublabel: "autostart.json", icon: Rocket }, + { id: "users", label: "Users", icon: Users }, +]; + +export const PERSONALIZATION_ITEMS: SidebarItem[] = [ + { id: "appearance", label: "Appearance", sublabel: "settings.json", icon: Palette }, + { id: "breadpaper", label: "Wallpaper", sublabel: "breadpaper", icon: Image }, + { id: "breadbar", label: "Bar", sublabel: "breadbar", icon: LayoutGrid }, + { id: "breadbox", label: "Launcher", sublabel: "breadbox", icon: Grid3x3 }, + { id: "breadclip", label: "Clipboard", sublabel: "breadclipd", icon: Clipboard }, + { id: "breadpad", label: "Notes", sublabel: "breadpad", icon: NotebookPen }, + { id: "breadsearch", label: "File Search", sublabel: "breadsearch", icon: Search }, + { id: "bread", label: "Daemon", sublabel: "breadd", icon: Cog }, +]; + +export const MAINTENANCE_ITEMS: SidebarItem[] = [ + { id: "packages", label: "Packages", icon: Package }, + { id: "aur", label: "AUR", icon: Search }, + { id: "firmware", label: "Firmware", icon: RefreshCw }, + { id: "snapshots", label: "Snapshots", icon: History }, +]; + +export const ABOUT_ITEMS: SidebarItem[] = [{ id: "about", label: "About", icon: Info }]; + +export interface SidebarSection { + title: string | null; + items: SidebarItem[]; +} + +export const SIDEBAR_SECTIONS: SidebarSection[] = [ + { title: "System", items: SYSTEM_ITEMS }, + { title: "Personalization", items: PERSONALIZATION_ITEMS }, + { title: "Maintenance", items: MAINTENANCE_ITEMS }, + { title: null, items: ABOUT_ITEMS }, +]; + +export const DEFAULT_PAGE = "about"; diff --git a/frontend/src/lib/streaming.ts b/frontend/src/lib/streaming.ts new file mode 100644 index 0000000..cd285f7 --- /dev/null +++ b/frontend/src/lib/streaming.ts @@ -0,0 +1,25 @@ +// Frontend half of the event-streaming command pattern (see +// src-tauri/src/commands/streaming.rs) — runs a command, appends each +// stdout/stderr line to a reactive log as it arrives, and resolves once the +// process exits with whether it succeeded. + +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; + +export async function runStreamingCommand( + program: string, + args: string[], + onLine: (line: string) => void, +): Promise { + const sessionId = crypto.randomUUID(); + + const unlisten = await listen<{ session_id: string; line: string }>("cmd-output", (event) => { + if (event.payload.session_id === sessionId) onLine(event.payload.line); + }); + + try { + return await invoke("run_streaming_command", { sessionId, program, args }); + } finally { + unlisten(); + } +} diff --git a/frontend/src/lib/theme/index.ts b/frontend/src/lib/theme/index.ts new file mode 100644 index 0000000..a358759 --- /dev/null +++ b/frontend/src/lib/theme/index.ts @@ -0,0 +1,29 @@ +// Bridges bread-theme's pywal-derived palette into the webview. Mirrors +// bread_theme::gtk::apply_shared()'s two-phase pattern: fetch once at +// startup, then keep it live via a backend-pushed event — see +// src-tauri/src/commands/theme.rs for the file-watch side of this. + +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; + +const STYLE_ELEMENT_ID = "bread-theme"; + +function applyThemeCss(css: string) { + let style = document.getElementById(STYLE_ELEMENT_ID); + if (!style) { + style = document.createElement("style"); + style.id = STYLE_ELEMENT_ID; + document.head.appendChild(style); + } + style.textContent = css; +} + +/** Call once at startup (e.g. from a root `$effect`/`onMount`). */ +export async function initTheme(): Promise { + const css = await invoke("get_theme_css"); + applyThemeCss(css); + + await listen("theme-changed", (event) => { + applyThemeCss(event.payload); + }); +} diff --git a/frontend/src/lib/views/About.svelte b/frontend/src/lib/views/About.svelte new file mode 100644 index 0000000..894aca2 --- /dev/null +++ b/frontend/src/lib/views/About.svelte @@ -0,0 +1,132 @@ + + + + {#if info} + + + + + + + + + + + +
+ + +
+ {#if status} + {status} + {/if} +
+ {/if} +
+ + diff --git a/frontend/src/lib/views/Appearance.svelte b/frontend/src/lib/views/Appearance.svelte new file mode 100644 index 0000000..b9d7541 --- /dev/null +++ b/frontend/src/lib/views/Appearance.svelte @@ -0,0 +1,128 @@ + + + + {#if cfg} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/if} + + + diff --git a/frontend/src/lib/views/Aur.svelte b/frontend/src/lib/views/Aur.svelte new file mode 100644 index 0000000..11c035f --- /dev/null +++ b/frontend/src/lib/views/Aur.svelte @@ -0,0 +1,151 @@ + + + + +
+ e.key === "Enter" && search()} /> + +
+ + + +
+ {#if results && results.length === 0} + + {:else if results} + {#each results as r (r.name)} +
+
+ {r.name} + {r.version} + +
+ {r.description} +
+ {/each} + {/if} +
+
+
+ + diff --git a/frontend/src/lib/views/Autostart.svelte b/frontend/src/lib/views/Autostart.svelte new file mode 100644 index 0000000..5bc5248 --- /dev/null +++ b/frontend/src/lib/views/Autostart.svelte @@ -0,0 +1,168 @@ + + + + {#if entries} + + {#each entries as entry, i (i)} +
+ +
+ + +
+ + +
+ {/each} + + + +
+ {/if} +
+ + diff --git a/frontend/src/lib/views/Bluetooth.svelte b/frontend/src/lib/views/Bluetooth.svelte new file mode 100644 index 0000000..49d5962 --- /dev/null +++ b/frontend/src/lib/views/Bluetooth.svelte @@ -0,0 +1,188 @@ + + + + {#if powered === null} + + {:else} + + powered ?? false, (v) => togglePower(v)} /> + + + +
+ {#if paired === null} + + {:else if paired.length === 0} + + {:else} + {#each paired as dev (dev.address)} +
+ {dev.name}{dev.connected ? " (connected)" : ""} + + +
+ {/each} + {/if} +
+
+ + +
+ {#if scanResults === null} + + {:else if scanResults.length === 0} + + {:else} + {#each scanResults as dev (dev.address)} +
+ {dev.name} + +
+ {/each} + {/if} +
+ +
+ + + {/if} +
+ + diff --git a/frontend/src/lib/views/Bread.svelte b/frontend/src/lib/views/Bread.svelte new file mode 100644 index 0000000..e4e4d87 --- /dev/null +++ b/frontend/src/lib/views/Bread.svelte @@ -0,0 +1,86 @@ + + + + + + {#if cfg} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/if} + diff --git a/frontend/src/lib/views/Breadbar.svelte b/frontend/src/lib/views/Breadbar.svelte new file mode 100644 index 0000000..ee844ef --- /dev/null +++ b/frontend/src/lib/views/Breadbar.svelte @@ -0,0 +1,178 @@ + + + + {#if style} + + + + + + + + + + + + + +
+ + {Math.round(style.workspace_inactive_opacity * 100)}% +
+
+
+ + + + + + + + + {/if} + + + + + {#if advancedOpen} + +
+ + {cssStatus} +
+ {/if} +
+
+ + diff --git a/frontend/src/lib/views/Breadbox.svelte b/frontend/src/lib/views/Breadbox.svelte new file mode 100644 index 0000000..db0a5ef --- /dev/null +++ b/frontend/src/lib/views/Breadbox.svelte @@ -0,0 +1,128 @@ + + + + {#if contexts} + + {#if contexts.length === 0} +
No launcher contexts yet. Add one to control which apps/categories breadbox surfaces first.
+ {/if} + {#each contexts as ctx, i (i)} +
+ + setPriority(ctx, e.currentTarget.value)} + placeholder="firefox, code, Development, ..." + class="priority" + /> + +
+ {/each} + +
+ + + {/if} + + +
+ + diff --git a/frontend/src/lib/views/Breadclip.svelte b/frontend/src/lib/views/Breadclip.svelte new file mode 100644 index 0000000..4f3c2ef --- /dev/null +++ b/frontend/src/lib/views/Breadclip.svelte @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/frontend/src/lib/views/Breadcrumbs.svelte b/frontend/src/lib/views/Breadcrumbs.svelte new file mode 100644 index 0000000..5c85e17 --- /dev/null +++ b/frontend/src/lib/views/Breadcrumbs.svelte @@ -0,0 +1,276 @@ + + + + + + {#if cfg} + + {#if profileNames.length > 0} + + {:else} + + {/if} + + + + + + + + + +
+ {#each cfg.networks as net, i (i)} +
+ + +
+ + +
+ +
+ {/each} +
+ +
+ + + {#each cfg.profiles as profile, i (i)} +
+
+ + +
+ + + + + + +
+ + +
+
+ + +
+
+ {/each} + +
+ + + {/if} +
+ + diff --git a/frontend/src/lib/views/Breadpad.svelte b/frontend/src/lib/views/Breadpad.svelte new file mode 100644 index 0000000..e3eb6e3 --- /dev/null +++ b/frontend/src/lib/views/Breadpad.svelte @@ -0,0 +1,138 @@ + + + + {#if cfg} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/if} + + + diff --git a/frontend/src/lib/views/Breadpaper.svelte b/frontend/src/lib/views/Breadpaper.svelte new file mode 100644 index 0000000..745ff65 --- /dev/null +++ b/frontend/src/lib/views/Breadpaper.svelte @@ -0,0 +1,200 @@ + + + + +
+ {#if currentPath} + Current wallpaper + {currentPath.split("/").pop()} + {:else} +
No wallpaper set
+ {/if} +
+ +
+ + {status} +
+
+ + + {#if library === null} + + {:else if library.length === 0} +
Nothing under {libraryDir} — use Choose image… above instead.
+ {:else} +
+ {#each library as item (item.path)} + + {/each} +
+ {/if} +
+
+ + diff --git a/frontend/src/lib/views/Breadsearch.svelte b/frontend/src/lib/views/Breadsearch.svelte new file mode 100644 index 0000000..c46fec9 --- /dev/null +++ b/frontend/src/lib/views/Breadsearch.svelte @@ -0,0 +1,105 @@ + + + + + + {#if cfg} + + + + + + + + + + + + + + + + + + + + + + + + + {/if} + + + diff --git a/frontend/src/lib/views/DateTime.svelte b/frontend/src/lib/views/DateTime.svelte new file mode 100644 index 0000000..fa92944 --- /dev/null +++ b/frontend/src/lib/views/DateTime.svelte @@ -0,0 +1,121 @@ + + + + {#if info} + + + + + + {#if info.timezones.length === 0} + + {:else} + + + + {#if tzStatus} + {tzStatus} + {/if} + {/if} + + + + + + + + {/if} + + + diff --git a/frontend/src/lib/views/Display.svelte b/frontend/src/lib/views/Display.svelte new file mode 100644 index 0000000..296bc3d --- /dev/null +++ b/frontend/src/lib/views/Display.svelte @@ -0,0 +1,152 @@ + + + + + {#if liveMonitors && liveMonitors.length > 0} + {#each liveMonitors as m (m.name)} + + {/each} + {:else} + + {/if} + + + + + + + + {#if rules} + + {#each rules as rule, i (i)} +
+ + + + + +
+ {/each} + + + +
+ {/if} +
+ + diff --git a/frontend/src/lib/views/Firewall.svelte b/frontend/src/lib/views/Firewall.svelte new file mode 100644 index 0000000..9bbdca0 --- /dev/null +++ b/frontend/src/lib/views/Firewall.svelte @@ -0,0 +1,205 @@ + + + + + + + + + + +
+ + +
+
+ + +
+ {#if status === "unloaded"} + + {:else if "error" in status} + + {:else if status.rules.length === 0} + + {:else} + {#each status.rules as rule (rule.number)} +
+ {rule.text} + +
+ {/each} + {/if} +
+ + +
+ + +
+ + diff --git a/frontend/src/lib/views/Firmware.svelte b/frontend/src/lib/views/Firmware.svelte new file mode 100644 index 0000000..36c9fe9 --- /dev/null +++ b/frontend/src/lib/views/Firmware.svelte @@ -0,0 +1,140 @@ + + + + +
+ {#if devices === null} + + {:else if devices.length === 0} + + {:else} + {#each devices as dev (dev.name)} +
+ {dev.name} + {dev.version} +
+ {/each} + {/if} +
+ +
+ + + +
+
+ + +
+ + diff --git a/frontend/src/lib/views/Keybinds.svelte b/frontend/src/lib/views/Keybinds.svelte new file mode 100644 index 0000000..dc710cf --- /dev/null +++ b/frontend/src/lib/views/Keybinds.svelte @@ -0,0 +1,653 @@ + + +{#snippet extraFields(row: EditRow)} + {#if row.action === "exec"} + setExtra(row, "command", e.currentTarget.value)} + /> + + + {:else if row.action === "focus" || row.action === "move"} + setExtra(row, "workspace", parseWorkspaceValue(e.currentTarget.value))} + /> + {:else if row.action === "move_dir"} + + {:else if row.action === "resize_dir"} + setExtra(row, "x", e.currentTarget.value === "" ? undefined : Number(e.currentTarget.value))} + /> + setExtra(row, "y", e.currentTarget.value === "" ? undefined : Number(e.currentTarget.value))} + /> + + {:else if row.action === "layout"} + setExtra(row, "layout", e.currentTarget.value)} + /> + {:else if row.action === "drag"} + Mouse-drag bind — nothing else to set. + {:else if row.action === "close" || row.action === "fullscreen" || row.action === "float" || row.action === "pseudo" || row.action === "resize" || row.action === "focus_last" || row.action === "exit"} + No extra options for this action. + {:else} + onExtraInput(row, e.currentTarget.value)} + /> + {/if} + + {#if KNOWN_ACTIONS.includes(row.action as (typeof KNOWN_ACTIONS)[number]) && row.action !== "drag"} + + {/if} +{/snippet} + +{#snippet section(rows: EditRow[], onAdd: () => void, onRemove: (i: number) => void)} +
+ {#each rows as row, i (i)} +
+
+ (row.modsTouched = true)} + /> + + + {#if !KNOWN_ACTIONS.includes(row.action as (typeof KNOWN_ACTIONS)[number])} + + {/if} + +
+
+ {@render extraFields(row)} +
+ {#if row.advancedOpen && KNOWN_ACTIONS.includes(row.action as (typeof KNOWN_ACTIONS)[number]) && row.action !== "drag"} + onExtraInput(row, e.currentTarget.value)} + /> + {/if} +
+ {/each} +
+ +{/snippet} + + + {#if loadError} + + + + {/if} + {#if loaded} + {#if kind === "unknown"} + + + + {:else if kind === "flat"} + + + + + + {@render section( + bindingsRows, + () => bindingsRows.push(emptyRow()), + (i) => bindingsRows.splice(i, 1) + )} + + + + {:else} + + {#if layoutOrder.length > 0} + + {/if} + + + + + {@render section( + globalsRows, + () => globalsRows.push(emptyRow()), + (i) => globalsRows.splice(i, 1) + )} + + + + {@render section( + commonRows, + () => commonRows.push(emptyRow()), + (i) => commonRows.splice(i, 1) + )} + + + {#each layoutOrder as name (name)} + + + {@render section( + layoutRows[name] ?? [], + () => (layoutRows[name] ?? (layoutRows[name] = [])).push(emptyRow()), + (i) => layoutRows[name]?.splice(i, 1) + )} + + {/each} + + +
+ + +
+
+ + + {/if} + {/if} +
+ + diff --git a/frontend/src/lib/views/Network.svelte b/frontend/src/lib/views/Network.svelte new file mode 100644 index 0000000..0a8e788 --- /dev/null +++ b/frontend/src/lib/views/Network.svelte @@ -0,0 +1,276 @@ + + + + + + + + + + {#if ethernetLabel} + + + + {/if} + + +
+ {#if networks === null} +
+ + Not scanned yet + Press Scan to see nearby networks. +
+ {:else if networks.length === 0} +
+ + No networks found + Try Scan again, or check Wi-Fi radio is on. +
+ {:else} + {#each networks as net (net.ssid)} +
+ {net.ssid}{net.active ? " (connected)" : ""} + {#if net.secured}{/if} + {signalLabel(net.signal)} + {#if !net.active} + + {/if} +
+ {/each} + {/if} +
+ + {#if pendingSsid} +
+ + +
+ {/if} + {#if status} + {status} + {/if} + + +
+ + + + +
+ + diff --git a/frontend/src/lib/views/Packages.svelte b/frontend/src/lib/views/Packages.svelte new file mode 100644 index 0000000..fd3d92f --- /dev/null +++ b/frontend/src/lib/views/Packages.svelte @@ -0,0 +1,152 @@ + + + + +
+ {#if packages === null} + + {:else if packages.length === 0} + + {:else} + {#each packages as pkg (pkg.name)} +
+ {pkg.name} + {pkg.version} + +
+ {/each} + {/if} +
+ +
+ + +
+
+ + + + + + +
+ + diff --git a/frontend/src/lib/views/Power.svelte b/frontend/src/lib/views/Power.svelte new file mode 100644 index 0000000..d81ea9c --- /dev/null +++ b/frontend/src/lib/views/Power.svelte @@ -0,0 +1,105 @@ + + + + {#if info} + + {#each info.battery as [label, value] (label)} + + {/each} + + + + + {#if info.brightness_pct !== null} + + setBrightness(brightness)} /> + {brightness}% + + {:else} + + {/if} + + + {#if info.charge_start !== null && info.charge_end !== null} + + + setChargeThreshold("start", chargeStart)} /> + + + setChargeThreshold("end", chargeEnd)} /> + + + {/if} + + + + + {/if} + + + diff --git a/frontend/src/lib/views/Snapshots.svelte b/frontend/src/lib/views/Snapshots.svelte new file mode 100644 index 0000000..8687a00 --- /dev/null +++ b/frontend/src/lib/views/Snapshots.svelte @@ -0,0 +1,176 @@ + + + + +
+ {#if snapshots === "loading"} + + {:else if errorHint} + + {:else if snapshots.length === 0} + + {:else} + {#each snapshots as snap (snap.number)} + + {/each} + {/if} +
+ +
+ + + +
+
+
+ + diff --git a/frontend/src/lib/views/Sound.svelte b/frontend/src/lib/views/Sound.svelte new file mode 100644 index 0000000..b863d45 --- /dev/null +++ b/frontend/src/lib/views/Sound.svelte @@ -0,0 +1,131 @@ + + +{#snippet deviceSection(section: DeviceSection | null)} + {#if section} + + {#if section.devices.length === 0} + + {:else} + + + + + setVolume(section, Number(e.currentTarget.value))} + /> + {section.devices[section.selected].percent}% + + section.devices[section.selected].mute, + (v) => setMute(section, v) + } + /> + {/if} + + {/if} +{/snippet} + + + {@render deviceSection(output)} + {@render deviceSection(input)} + + + + + + + diff --git a/frontend/src/lib/views/Users.svelte b/frontend/src/lib/views/Users.svelte new file mode 100644 index 0000000..08868f5 --- /dev/null +++ b/frontend/src/lib/views/Users.svelte @@ -0,0 +1,196 @@ + + + + +
+ {#if accounts} + {#each accounts as acc (acc.username)} +
+
+ {acc.username}{acc.full_name ? ` (${acc.full_name})` : ""} + + +
+ {#if openPasswordFor === acc.username} +
+ + +
+ {/if} + {#if rowStatus[acc.username]} + {rowStatus[acc.username]} + {/if} +
+ {/each} + {/if} +
+
+ + + + + + + + + + + + + {#if addStatus} + {addStatus} + {/if} + +
+ + diff --git a/frontend/src/lib/views/registry.ts b/frontend/src/lib/views/registry.ts new file mode 100644 index 0000000..9c9ed44 --- /dev/null +++ b/frontend/src/lib/views/registry.ts @@ -0,0 +1,56 @@ +// Maps a sidebar page id to its view component. Pages not yet migrated +// fall back to Placeholder (see +page.svelte) — this map only lists pages +// that actually have a real Tauri-backed view. + +import type { Component } from "svelte"; +import About from "./About.svelte"; +import Breadclip from "./Breadclip.svelte"; +import Bread from "./Bread.svelte"; +import Breadbar from "./Breadbar.svelte"; +import Breadbox from "./Breadbox.svelte"; +import Breadpad from "./Breadpad.svelte"; +import Breadpaper from "./Breadpaper.svelte"; +import Breadsearch from "./Breadsearch.svelte"; +import Breadcrumbs from "./Breadcrumbs.svelte"; +import Appearance from "./Appearance.svelte"; +import Autostart from "./Autostart.svelte"; +import Display from "./Display.svelte"; +import Keybinds from "./Keybinds.svelte"; +import Sound from "./Sound.svelte"; +import DateTime from "./DateTime.svelte"; +import Power from "./Power.svelte"; +import Network from "./Network.svelte"; +import Bluetooth from "./Bluetooth.svelte"; +import Firewall from "./Firewall.svelte"; +import Users from "./Users.svelte"; +import Packages from "./Packages.svelte"; +import Aur from "./Aur.svelte"; +import Firmware from "./Firmware.svelte"; +import Snapshots from "./Snapshots.svelte"; + +export const VIEWS: Record = { + about: About, + breadclip: Breadclip, + bread: Bread, + breadbar: Breadbar, + breadbox: Breadbox, + breadpad: Breadpad, + breadpaper: Breadpaper, + breadsearch: Breadsearch, + breadcrumbs: Breadcrumbs, + appearance: Appearance, + autostart: Autostart, + hyprland: Display, + keybinds: Keybinds, + sound: Sound, + datetime: DateTime, + power: Power, + network: Network, + bluetooth: Bluetooth, + firewall: Firewall, + users: Users, + packages: Packages, + aur: Aur, + firmware: Firmware, + snapshots: Snapshots, +}; diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts new file mode 100644 index 0000000..9d24899 --- /dev/null +++ b/frontend/src/routes/+layout.ts @@ -0,0 +1,5 @@ +// Tauri doesn't have a Node.js server to do proper SSR +// so we use adapter-static with a fallback to index.html to put the site in SPA mode +// See: https://svelte.dev/docs/kit/single-page-apps +// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info +export const ssr = false; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte new file mode 100644 index 0000000..b9fe32e --- /dev/null +++ b/frontend/src/routes/+page.svelte @@ -0,0 +1,61 @@ + + +
+ +
+ {#if ActiveView} + + {:else} + + {/if} +
+
+ + diff --git a/frontend/static/favicon.png b/frontend/static/favicon.png new file mode 100644 index 0000000..825b9e6 Binary files /dev/null and b/frontend/static/favicon.png differ diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js new file mode 100644 index 0000000..a7830ea --- /dev/null +++ b/frontend/svelte.config.js @@ -0,0 +1,18 @@ +// Tauri doesn't have a Node.js server to do proper SSR +// so we use adapter-static with a fallback to index.html to put the site in SPA mode +// See: https://svelte.dev/docs/kit/single-page-apps +// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info +import adapter from "@sveltejs/adapter-static"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter({ + fallback: "index.html", + }), + }, +}; + +export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..f4d0a0e --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..3ecfa0a --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import { sveltekit } from "@sveltejs/kit/vite"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [sveltekit()], + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +})); diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..74cae45 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5311 @@ +# This file is automatically @generated by Cargo. +# 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bos-settings" +version = "0.8.0" +dependencies = [ + "bread-theme", + "bread-utils", + "notify", + "regex", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-opener", + "tokio", + "toml_edit 0.22.27", +] + +[[package]] +name = "bread-theme" +version = "0.3.1" +dependencies = [ + "dirs 5.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "bread-utils" +version = "0.3.1" +dependencies = [ + "dirs 5.0.1", + "serde", + "serde_json", + "toml_edit 0.22.27", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[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 = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[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-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.3+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +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 = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[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]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.1", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +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 = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.19", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "http-range", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.19", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.19", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d3c1dbe38037e7f590cdf2492594d5ceebe031e7bc7e827509b22a999d2940" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.19", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.19", + "toml 1.1.3+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.19", + "toml 1.1.3+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.3+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[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.1", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +dependencies = [ + "crossbeam-channel", + "dirs 6.0.0", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.19", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "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.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[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_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.19", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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 0.2.1", + "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-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.19", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zvariant" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "winnow 1.0.4", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..f0105fd --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bos-settings" +version = "0.8.0" +description = "System settings app for BOS (Bread Operating System)" +authors = ["Breadway"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "bos_settings_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["protocol-asset"] } +tauri-plugin-opener = "2" +tauri-plugin-dialog = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +toml_edit = "0.22" +tokio = { version = "1", features = ["process", "io-util", "time", "macros"] } +notify = "7" +regex = "1" +# TODO(owner): switch to a tag-pinned git dependency once bread-theme cuts a +# release including css_custom_properties/css_tokens (added 2026-07-21 for +# this migration, unreleased) — matches the same pending-tag pattern +# bos-settings' old GTK Cargo.toml used for bread-utils. +bread-theme = { path = "../../bread-ecosystem/bread-theme" } +bread-utils = { path = "../../bread-ecosystem/bread-utils", features = ["toml"] } + diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..778bfb5 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "dialog:default" + ] +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src/ui/views/about.rs b/src-tauri/src/commands/about.rs similarity index 57% rename from src/ui/views/about.rs rename to src-tauri/src/commands/about.rs index 8f4cdcb..7ec9f56 100644 --- a/src/ui/views/about.rs +++ b/src-tauri/src/commands/about.rs @@ -3,12 +3,21 @@ //! ships `BUILD_ID=rolling` on purpose), so there's no "BOS 1.2.3" readout //! here the way a point-release distro's About panel would have one. -use gtk4::prelude::*; -use gtk4::{Box as GBox, Button, Entry, Label, Orientation}; +use serde::Serialize; use std::fs; -use std::process::Command; +use tokio::process::Command; -use crate::ui::widgets as w; +#[derive(Serialize)] +pub struct SystemInfo { + os: String, + kernel: String, + cpu: String, + gpu: String, + memory: String, + disk: String, + uptime: String, + hostname: String, +} fn os_pretty_name() -> String { fs::read_to_string("/etc/os-release") @@ -26,10 +35,11 @@ fn hostname() -> String { .unwrap_or_else(|_| "unknown".to_string()) } -fn kernel() -> String { +async fn kernel() -> String { Command::new("uname") .arg("-r") .output() + .await .ok() .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) .unwrap_or_else(|| "unknown".to_string()) @@ -66,8 +76,8 @@ fn memory() -> String { } } -fn gpu() -> String { - let Ok(output) = Command::new("lspci").output() else { +async fn gpu() -> String { + let Ok(output) = Command::new("lspci").output().await else { return "unknown".to_string(); }; let text = String::from_utf8_lossy(&output.stdout); @@ -85,8 +95,8 @@ fn gpu() -> String { .to_string() } -fn disk_usage() -> String { - let Ok(output) = Command::new("df").args(["-h", "--output=used,size,pcent", "/"]).output() else { +async fn disk_usage() -> String { + let Ok(output) = Command::new("df").args(["-h", "--output=used,size,pcent", "/"]).output().await else { return "unknown".to_string(); }; let text = String::from_utf8_lossy(&output.stdout); @@ -102,71 +112,44 @@ fn disk_usage() -> String { .unwrap_or_else(|| "unknown".to_string()) } -fn uptime() -> String { +async fn uptime() -> String { Command::new("uptime") .arg("-p") .output() + .await .ok() .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) .unwrap_or_else(|| "unknown".to_string()) } -pub fn build() -> GBox { - let (outer, content) = w::view_scaffold("About"); - - content.append(&w::info_row("Operating system", &os_pretty_name())); - content.append(&w::info_row("Kernel", &kernel())); - content.append(&w::info_row("CPU", &cpu())); - content.append(&w::info_row("GPU", &gpu())); - content.append(&w::info_row("Memory", &memory())); - content.append(&w::info_row("Disk (/)", &disk_usage())); - content.append(&w::info_row("Uptime", &uptime())); - - content.append(&w::section("Hostname")); - content.append(&w::hint( - "Changes the machine's network name. Takes effect immediately; \ - needs your password (polkit).", - )); - - let hn_row = GBox::new(Orientation::Horizontal, 12); - let entry = Entry::new(); - entry.set_text(&hostname()); - entry.set_hexpand(true); - let apply_btn = Button::with_label("Apply"); - let status = Label::new(None); - status.add_css_class("dim-label"); - - { - let entry = entry.clone(); - let status = status.clone(); - apply_btn.connect_clicked(move |_| { - let name = entry.text().to_string(); - if name.trim().is_empty() { - status.set_text("Hostname can't be empty"); - return; - } - let log_buf = gtk4::TextBuffer::new(None); - let status2 = status.clone(); - status.set_text("Applying…"); - w::stream_command_then( - &["pkexec", "hostnamectl", "set-hostname", name.trim()], - log_buf.clone(), - move || { - let text = log_buf.text(&log_buf.start_iter(), &log_buf.end_iter(), false); - if text.trim().is_empty() { - status2.set_text("Applied"); - } else { - status2.set_text(&format!("Error: {}", text.trim())); - } - }, - ); - }); +#[tauri::command] +pub async fn get_system_info() -> SystemInfo { + SystemInfo { + os: os_pretty_name(), + kernel: kernel().await, + cpu: cpu(), + gpu: gpu().await, + memory: memory(), + disk: disk_usage().await, + uptime: uptime().await, + hostname: hostname(), + } +} + +#[tauri::command] +pub async fn set_hostname(name: String) -> Result<(), String> { + let name = name.trim(); + if name.is_empty() { + return Err("Hostname can't be empty".into()); + } + let output = Command::new("pkexec") + .args(["hostnamectl", "set-hostname", name]) + .output() + .await + .map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) } - - hn_row.append(&entry); - hn_row.append(&apply_btn); - content.append(&hn_row); - content.append(&status); - - outer } diff --git a/src-tauri/src/commands/appearance.rs b/src-tauri/src/commands/appearance.rs new file mode 100644 index 0000000..0920f0f --- /dev/null +++ b/src-tauri/src/commands/appearance.rs @@ -0,0 +1,73 @@ +//! hypr/settings.json — Hyprland gaps/borders/blur/shadow/input, read by +//! `scripts/ui/settings.lua` on the Hyprland side. No comments to preserve, +//! so it's a plain typed struct round-tripped whole. +//! +//! `Default` here must stay in sync with `scripts/ui/settings.lua`'s +//! `DEFAULTS` table on the Hyprland side — two different languages/ +//! processes reading the same file, neither able to import the other's +//! defaults. + +use serde::{Deserialize, Serialize}; + +use super::config; + +#[derive(Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct Appearance { + gaps_in: i64, + gaps_out: i64, + border_size: i64, + active_border: String, + inactive_border: String, + layout: String, + resize_on_border: bool, + rounding: i64, + blur_enabled: bool, + blur_size: i64, + blur_passes: i64, + shadow_enabled: bool, + shadow_range: i64, + shadow_render_power: i64, + kb_layout: String, + follow_mouse: i64, + natural_scroll: bool, +} + +impl Default for Appearance { + fn default() -> Self { + Self { + gaps_in: 5, + gaps_out: 10, + border_size: 2, + active_border: "rgba(88c0d0ff)".to_string(), + inactive_border: "rgba(4c566aff)".to_string(), + layout: "dwindle".to_string(), + resize_on_border: true, + rounding: 8, + blur_enabled: true, + blur_size: 6, + blur_passes: 2, + shadow_enabled: true, + shadow_range: 12, + shadow_render_power: 3, + kb_layout: "us".to_string(), + follow_mouse: 1, + natural_scroll: true, + } + } +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("hypr/settings.json") +} + +#[tauri::command] +pub fn get_appearance() -> Appearance { + std::fs::read_to_string(config_path()).ok().and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default() +} + +#[tauri::command] +pub fn save_appearance(appearance: Appearance) -> Result<(), String> { + let json = serde_json::to_string_pretty(&appearance).map_err(|e| e.to_string())?; + config::atomic_write(&config_path(), &json).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/aur.rs b/src-tauri/src/commands/aur.rs new file mode 100644 index 0000000..226af63 --- /dev/null +++ b/src-tauri/src/commands/aur.rs @@ -0,0 +1,47 @@ +//! AUR search via yay — graphical discovery beyond bakery's bread ecosystem +//! and [breadway]'s own republished packages. +//! +//! Installing opens a terminal running `yay -S ` instead of a silent +//! `--noconfirm` install — deliberate, not a shortcut skipped. AUR packages +//! run arbitrary maintainer-supplied build scripts, and yay's interactive +//! PKGBUILD diff review (plus the sudo prompt) is the actual safety +//! mechanism against a malicious/compromised package; automating it away +//! would remove the one step that exists to catch that. + +use serde::Serialize; + +#[derive(Serialize, Clone)] +pub struct AurResult { + name: String, + version: String, + description: String, +} + +#[tauri::command] +pub async fn search_aur(query: String) -> Vec { + let Ok(output) = tokio::process::Command::new("yay").args(["-Ss", "--aur", &query]).output().await else { + return Vec::new(); + }; + let text = String::from_utf8_lossy(&output.stdout); + let mut results = Vec::new(); + let mut lines = text.lines().peekable(); + while let Some(header) = lines.next() { + // "aur/name version (+votes score) [Orphaned]" — name/version are + // always the first two whitespace-separated fields after "aur/". + let Some(rest) = header.strip_prefix("aur/") else { continue }; + let mut parts = rest.split_whitespace(); + let Some(name) = parts.next() else { continue }; + let version = parts.next().unwrap_or("").to_string(); + let description = lines.next().unwrap_or("").trim().to_string(); + results.push(AurResult { name: name.to_string(), version, description }); + if results.len() >= 50 { + break; + } + } + results +} + +#[tauri::command] +pub fn install_aur_package(pkg: String) { + let _ = std::process::Command::new("kitty").args(["-e", "yay", "-S", &pkg]).spawn(); +} diff --git a/src-tauri/src/commands/autostart.rs b/src-tauri/src/commands/autostart.rs new file mode 100644 index 0000000..47bcb26 --- /dev/null +++ b/src-tauri/src/commands/autostart.rs @@ -0,0 +1,59 @@ +//! hypr/autostart.json — the *extra*, user-toggleable autostart apps. The +//! core bootstrap sequence stays hardcoded in hyprland.lua on purpose (it's +//! timing/order-sensitive infrastructure, not something this panel exposes). + +use serde::{Deserialize, Serialize}; + +use super::config; + +#[derive(Clone, Serialize, Deserialize)] +pub struct AutostartEntry { + command: String, + #[serde(default)] + label: String, + #[serde(default = "default_true")] + enabled: bool, +} + +fn default_true() -> bool { + true +} + +#[derive(Serialize, Deserialize)] +struct AutostartFile { + #[serde(default)] + extra: Vec, +} + +fn default_extra() -> Vec { + vec![ + AutostartEntry { command: "breadbar".into(), label: "Bar (breadbar)".into(), enabled: true }, + AutostartEntry { command: "hypridle".into(), label: "Idle / lock daemon (hypridle)".into(), enabled: true }, + AutostartEntry { command: "bos-netcheck".into(), label: "Network connectivity check".into(), enabled: true }, + AutostartEntry { command: "breadhelp --autostart".into(), label: "BOS Help (first-run onboarding)".into(), enabled: true }, + ] +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("hypr/autostart.json") +} + +#[tauri::command] +pub fn get_autostart_entries() -> Vec { + std::fs::read_to_string(config_path()) + .ok() + .and_then(|s| serde_json::from_str::(&s).ok()) + .map(|f| f.extra) + .unwrap_or_else(default_extra) +} + +/// Empty-command rows (still-being-typed "Add app" entries) are dropped on +/// save rather than written as a broken autostart.json entry the Lua loader +/// would otherwise have to reject. +#[tauri::command] +pub fn save_autostart_entries(entries: Vec) -> Result<(), String> { + let entries: Vec = entries.into_iter().filter(|e| !e.command.trim().is_empty()).collect(); + let file = AutostartFile { extra: entries }; + let json = serde_json::to_string_pretty(&file).map_err(|e| e.to_string())?; + config::atomic_write(&config_path(), &json).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/bluetooth.rs b/src-tauri/src/commands/bluetooth.rs new file mode 100644 index 0000000..4bb82f5 --- /dev/null +++ b/src-tauri/src/commands/bluetooth.rs @@ -0,0 +1,123 @@ +//! Bluetooth over `bluetoothctl`'s non-interactive mode — no D-Bus +//! dependency needed, same "shell out to the standard CLI" choice as +//! Network (nmcli). None of this needs `pkexec` — BlueZ's D-Bus policy +//! already allows the active session user. +//! +//! Pairing only covers "Just Works" Simple Secure Pairing — bluetoothd's +//! own built-in default agent auto-accepts that for most audio/HID +//! devices. A device that requires PIN/passkey confirmation isn't +//! supported (would need this app to register its own bluetoothd agent); +//! pairing such a device just fails, surfaced as an error. + +use serde::Serialize; +use std::collections::HashSet; +use tokio::process::Command; + +#[derive(Serialize, Clone)] +pub struct BtDevice { + address: String, + name: String, + connected: bool, +} + +#[tauri::command] +pub async fn get_adapter_powered() -> Option { + let output = Command::new("bluetoothctl").arg("show").output().await.ok()?; + if !output.status.success() { + return None; + } + let text = String::from_utf8_lossy(&output.stdout); + if !text.trim_start().starts_with("Controller") { + return None; + } + Some(text.lines().any(|l| l.trim() == "Powered: yes")) +} + +#[tauri::command] +pub async fn set_adapter_powered(on: bool) { + let val = if on { "on" } else { "off" }; + let _ = Command::new("bluetoothctl").args(["power", val]).status().await; +} + +fn parse_device_lines(text: &str) -> Vec { + text.lines() + .filter_map(|l| { + let rest = l.strip_prefix("Device ")?; + let (addr, name) = rest.split_once(' ')?; + Some(BtDevice { address: addr.trim().to_string(), name: name.trim().to_string(), connected: false }) + }) + .collect() +} + +async fn run_devices(filter: Option<&str>) -> Vec { + let mut args = vec!["devices"]; + if let Some(f) = filter { + args.push(f); + } + let Ok(out) = Command::new("bluetoothctl").args(&args).output().await else { + return Vec::new(); + }; + parse_device_lines(&String::from_utf8_lossy(&out.stdout)) +} + +#[tauri::command] +pub async fn get_paired_devices() -> Vec { + let mut devices = run_devices(Some("Paired")).await; + let connected: HashSet = run_devices(Some("Connected")).await.into_iter().map(|d| d.address).collect(); + for d in &mut devices { + d.connected = connected.contains(&d.address); + } + devices +} + +/// Scans for a few seconds and returns every device BlueZ has seen that +/// isn't already paired. +#[tauri::command] +pub async fn scan_bluetooth() -> Vec { + let _ = Command::new("bluetoothctl").args(["--timeout", "5", "scan", "on"]).output().await; + let paired: HashSet = run_devices(Some("Paired")).await.into_iter().map(|d| d.address).collect(); + run_devices(None).await.into_iter().filter(|d| !paired.contains(&d.address)).collect() +} + +#[tauri::command] +pub async fn bt_connect(address: String) -> Result<(), String> { + let out = Command::new("bluetoothctl").args(["connect", &address]).output().await.map_err(|e| e.to_string())?; + if out.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } +} + +#[tauri::command] +pub async fn bt_disconnect(address: String) -> Result<(), String> { + let out = Command::new("bluetoothctl").args(["disconnect", &address]).output().await.map_err(|e| e.to_string())?; + if out.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } +} + +#[tauri::command] +pub async fn bt_forget(address: String) -> Result<(), String> { + let out = Command::new("bluetoothctl").args(["remove", &address]).output().await.map_err(|e| e.to_string())?; + if out.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } +} + +/// Pairs, then confirms it actually landed in the paired-device cache — +/// bluetoothctl's exit code alone isn't a reliable signal for pairing. +#[tauri::command] +pub async fn bt_pair(address: String) -> Result<(), String> { + let out = Command::new("bluetoothctl").args(["pair", &address]).output().await.map_err(|e| e.to_string())?; + let now_paired = run_devices(Some("Paired")).await.iter().any(|d| d.address == address); + if now_paired { + Ok(()) + } else { + Err(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } +} diff --git a/src-tauri/src/commands/bread.rs b/src-tauri/src/commands/bread.rs new file mode 100644 index 0000000..5fc710f --- /dev/null +++ b/src-tauri/src/commands/bread.rs @@ -0,0 +1,80 @@ +//! breadd.toml — the bread daemon config. Schema mirrors +//! breadd/src/core/config.rs (daemon, lua, modules, adapters, events, +//! notifications). Edited non-destructively via `commands::config`. + +use serde::{Deserialize, Serialize}; + +use super::config; + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("bread/breadd.toml") +} + +#[derive(Serialize, Deserialize)] +pub struct BreadConfig { + log_level: String, + socket_path: String, + lua_entry_point: String, + lua_module_path: String, + modules_builtin: bool, + modules_disable: Vec, + adapter_hyprland: bool, + adapter_udev: bool, + udev_subsystems: Vec, + adapter_power: bool, + power_poll_interval_secs: i64, + adapter_network: bool, + adapter_bluetooth: bool, + dedup_window_ms: i64, + notif_default_timeout_ms: i64, + notif_default_urgency: String, + notif_notify_send_path: String, +} + +#[tauri::command] +pub fn get_bread_config() -> BreadConfig { + let doc = config::load_doc(&config_path()); + BreadConfig { + log_level: config::get_str(&doc, &["daemon", "log_level"]).unwrap_or_else(|| "info".into()), + socket_path: config::get_str(&doc, &["daemon", "socket_path"]).unwrap_or_default(), + lua_entry_point: config::get_str(&doc, &["lua", "entry_point"]).unwrap_or_default(), + lua_module_path: config::get_str(&doc, &["lua", "module_path"]).unwrap_or_default(), + modules_builtin: config::get_bool(&doc, &["modules", "builtin"]).unwrap_or(true), + modules_disable: config::get_str_list(&doc, &["modules", "disable"]), + adapter_hyprland: config::get_bool(&doc, &["adapters", "hyprland", "enabled"]).unwrap_or(true), + adapter_udev: config::get_bool(&doc, &["adapters", "udev", "enabled"]).unwrap_or(true), + udev_subsystems: config::get_str_list(&doc, &["adapters", "udev", "subsystems"]), + adapter_power: config::get_bool(&doc, &["adapters", "power", "enabled"]).unwrap_or(true), + power_poll_interval_secs: config::get_i64(&doc, &["adapters", "power", "poll_interval_secs"]).unwrap_or(30), + adapter_network: config::get_bool(&doc, &["adapters", "network", "enabled"]).unwrap_or(true), + adapter_bluetooth: config::get_bool(&doc, &["adapters", "bluetooth", "enabled"]).unwrap_or(true), + dedup_window_ms: config::get_i64(&doc, &["events", "dedup_window_ms"]).unwrap_or(250), + notif_default_timeout_ms: config::get_i64(&doc, &["notifications", "default_timeout_ms"]).unwrap_or(5000), + notif_default_urgency: config::get_str(&doc, &["notifications", "default_urgency"]).unwrap_or_else(|| "normal".into()), + notif_notify_send_path: config::get_str(&doc, &["notifications", "notify_send_path"]).unwrap_or_default(), + } +} + +#[tauri::command] +pub fn save_bread_config(cfg: BreadConfig) -> Result<(), String> { + let path = config_path(); + let mut doc = config::load_doc(&path); + config::set_str(&mut doc, &["daemon", "log_level"], &cfg.log_level); + config::set_str_or_remove(&mut doc, &["daemon", "socket_path"], &cfg.socket_path); + config::set_str_or_remove(&mut doc, &["lua", "entry_point"], &cfg.lua_entry_point); + config::set_str_or_remove(&mut doc, &["lua", "module_path"], &cfg.lua_module_path); + config::set_bool(&mut doc, &["modules", "builtin"], cfg.modules_builtin); + config::set_str_list(&mut doc, &["modules", "disable"], &cfg.modules_disable); + config::set_bool(&mut doc, &["adapters", "hyprland", "enabled"], cfg.adapter_hyprland); + config::set_bool(&mut doc, &["adapters", "udev", "enabled"], cfg.adapter_udev); + config::set_str_list(&mut doc, &["adapters", "udev", "subsystems"], &cfg.udev_subsystems); + config::set_bool(&mut doc, &["adapters", "power", "enabled"], cfg.adapter_power); + config::set_i64(&mut doc, &["adapters", "power", "poll_interval_secs"], cfg.power_poll_interval_secs); + config::set_bool(&mut doc, &["adapters", "network", "enabled"], cfg.adapter_network); + config::set_bool(&mut doc, &["adapters", "bluetooth", "enabled"], cfg.adapter_bluetooth); + config::set_i64(&mut doc, &["events", "dedup_window_ms"], cfg.dedup_window_ms); + config::set_i64(&mut doc, &["notifications", "default_timeout_ms"], cfg.notif_default_timeout_ms); + config::set_str(&mut doc, &["notifications", "default_urgency"], &cfg.notif_default_urgency); + config::set_str_or_remove(&mut doc, &["notifications", "notify_send_path"], &cfg.notif_notify_send_path); + config::save_doc(&path, &doc).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/breadbar.rs b/src-tauri/src/commands/breadbar.rs new file mode 100644 index 0000000..51fdefa --- /dev/null +++ b/src-tauri/src/commands/breadbar.rs @@ -0,0 +1,144 @@ +//! breadbar/style.css — CSS overrides for the bar. No systemd unit (breadbar +//! is launched directly by hyprland.lua's exec-once); SIGHUP is its own +//! documented live-reload mechanism. +//! +//! The file has a fixed, hand-written structure (see the shipped template's +//! comments), so the common properties users actually tweak — font, bar +//! chrome, workspace indicator, spacing, tray/notification radii — are +//! exposed as a typed `BreadbarStyle` struct. `get`/`set_value` locate a +//! known `selector { ... }` block and rewrite just one declaration's value +//! inside it, leaving comments, ordering, and every unmodeled property +//! (font-weight, per-element opacities, notification padding, etc.) +//! untouched. Anything not modeled here stays reachable via the raw +//! `get`/`save_breadbar_css` pair, kept as an "Advanced" escape hatch. + +use regex::{Captures, Regex}; + +use super::config; + +fn css_path() -> std::path::PathBuf { + config::config_dir().join("breadbar/style.css") +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct BreadbarStyle { + pub font_family: String, + pub font_size: u32, + pub bar_border_radius: u32, + pub bar_padding: u32, + pub workspace_inactive_opacity: f64, + pub workspace_font_size: u32, + pub stat_gap: u32, + pub tray_icon_size: u32, + pub notification_border_radius: u32, +} + +fn find_block<'a>(css: &'a str, selector: &str) -> Option<(usize, usize)> { + let pat = format!(r"(?m)^\s*{}\s*\{{", regex::escape(selector)); + let re = Regex::new(&pat).ok()?; + let m = re.find(css)?; + let body_start = m.end(); + let body_end = body_start + css[body_start..].find('}')?; + Some((body_start, body_end)) +} + +fn get_value(css: &str, selector: &str, property: &str) -> Option { + let (start, end) = find_block(css, selector)?; + let body = &css[start..end]; + let re = Regex::new(&format!(r"(?m)^\s*{}\s*:\s*([^;]+);", regex::escape(property))).ok()?; + Some(re.captures(body)?.get(1)?.as_str().trim().to_string()) +} + +fn set_value(css: &str, selector: &str, property: &str, new_value: &str) -> Option { + let (start, end) = find_block(css, selector)?; + let body = &css[start..end]; + let re = Regex::new(&format!(r"(?m)(^\s*{}\s*:\s*)([^;]+)(;)", regex::escape(property))).ok()?; + if !re.is_match(body) { + return None; + } + let new_body = re + .replace(body, |caps: &Captures| format!("{}{}{}", &caps[1], new_value, &caps[3])) + .into_owned(); + Some(format!("{}{}{}", &css[..start], new_body, &css[end..])) +} + +fn strip_px(v: &str) -> u32 { + v.trim().trim_end_matches("px").trim().parse().unwrap_or(0) +} + +#[tauri::command] +pub fn get_breadbar_css() -> String { + std::fs::read_to_string(css_path()).unwrap_or_default() +} + +/// Saves the CSS and sends breadbar SIGHUP to live-reload it. Returns +/// whether the reload signal was actually delivered (`false` just means +/// breadbar isn't running — the file is saved either way). +#[tauri::command] +pub fn save_breadbar_css(css: String) -> Result { + config::atomic_write(&css_path(), &css).map_err(|e| e.to_string())?; + reload_breadbar() +} + +#[tauri::command] +pub fn get_breadbar_style() -> BreadbarStyle { + let css = std::fs::read_to_string(css_path()).unwrap_or_default(); + BreadbarStyle { + font_family: get_value(&css, "*", "font-family") + .and_then(|v| v.split(',').next().map(|s| s.trim().trim_matches(['\'', '"']).to_string())) + .unwrap_or_else(|| "Varela Round".to_string()), + font_size: get_value(&css, "*", "font-size").map(|v| strip_px(&v)).unwrap_or(14), + bar_border_radius: get_value(&css, "window.breadbar", "border-radius") + .map(|v| strip_px(&v)) + .unwrap_or(0), + bar_padding: get_value(&css, "window.breadbar", "padding").map(|v| strip_px(&v)).unwrap_or(0), + workspace_inactive_opacity: get_value(&css, ".workspace-btn", "opacity") + .and_then(|v| v.trim().parse().ok()) + .unwrap_or(0.45), + workspace_font_size: get_value(&css, ".workspace-btn", "font-size") + .map(|v| strip_px(&v)) + .unwrap_or(20), + stat_gap: get_value(&css, ".stat-pair", "margin-right").map(|v| strip_px(&v)).unwrap_or(12), + tray_icon_size: get_value(&css, ".tray-btn image", "-gtk-icon-size") + .map(|v| strip_px(&v)) + .unwrap_or(16), + notification_border_radius: get_value(&css, "window.breadbar-notification", "border-radius") + .map(|v| strip_px(&v)) + .unwrap_or(6), + } +} + +#[tauri::command] +pub fn save_breadbar_style(style: BreadbarStyle) -> Result { + let mut css = std::fs::read_to_string(css_path()).unwrap_or_default(); + let px = |n: u32| format!("{n}px"); + + let edits: [(&str, &str, String); 9] = [ + ("*", "font-family", format!("'{}', sans-serif", style.font_family)), + ("*", "font-size", px(style.font_size)), + ("window.breadbar", "border-radius", px(style.bar_border_radius)), + ("window.breadbar", "padding", px(style.bar_padding)), + (".workspace-btn", "opacity", style.workspace_inactive_opacity.to_string()), + (".workspace-btn", "font-size", px(style.workspace_font_size)), + (".stat-pair", "margin-right", px(style.stat_gap)), + (".tray-btn image", "-gtk-icon-size", px(style.tray_icon_size)), + ("window.breadbar-notification", "border-radius", px(style.notification_border_radius)), + ]; + + for (selector, property, value) in &edits { + if let Some(updated) = set_value(&css, selector, property, value) { + css = updated; + } + } + + config::atomic_write(&css_path(), &css).map_err(|e| e.to_string())?; + reload_breadbar() +} + +fn reload_breadbar() -> Result { + Ok(std::process::Command::new("pkill") + .args(["-HUP", "-x", "breadbar"]) + .status() + .map(|s| s.success()) + .unwrap_or(false)) +} diff --git a/src-tauri/src/commands/breadbox.rs b/src-tauri/src/commands/breadbox.rs new file mode 100644 index 0000000..61ecbb8 --- /dev/null +++ b/src-tauri/src/commands/breadbox.rs @@ -0,0 +1,64 @@ +//! breadbox config.toml — launcher contexts. Schema mirrors breadbox-shared +//! (`#[serde(rename = "context")]` — the TOML key is `[[context]]`, +//! singular, despite the Rust field being `contexts`), with `name` + +//! `priority`, an ordered list of app/category hints. The context array is +//! rewritten on save; any other top-level keys/comments are preserved. + +use serde::{Deserialize, Serialize}; +use toml_edit::{value, Array, ArrayOfTables, DocumentMut, Item, Table}; + +use super::config; + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadbox/config.toml") +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct Context { + name: String, + priority: Vec, +} + +fn read_contexts(doc: &DocumentMut) -> Vec { + let Some(aot) = doc.get("context").and_then(Item::as_array_of_tables) else { + return Vec::new(); + }; + aot.iter() + .map(|t| Context { + name: t.get("name").and_then(Item::as_str).unwrap_or("").to_string(), + priority: t + .get("priority") + .and_then(Item::as_array) + .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_default(), + }) + .collect() +} + +fn write_contexts(doc: &mut DocumentMut, ctxs: &[Context]) { + let mut aot = ArrayOfTables::new(); + for ctx in ctxs { + let mut t = Table::new(); + t.insert("name", value(&ctx.name)); + let mut arr = Array::new(); + for p in &ctx.priority { + arr.push(p.as_str()); + } + t.insert("priority", value(arr)); + aot.push(t); + } + doc.as_table_mut().insert("context", Item::ArrayOfTables(aot)); +} + +#[tauri::command] +pub fn get_breadbox_contexts() -> Vec { + read_contexts(&config::load_doc(&config_path())) +} + +#[tauri::command] +pub fn save_breadbox_contexts(contexts: Vec) -> Result<(), String> { + let path = config_path(); + let mut doc = config::load_doc(&path); + write_contexts(&mut doc, &contexts); + config::save_doc(&path, &doc).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/breadclip.rs b/src-tauri/src/commands/breadclip.rs new file mode 100644 index 0000000..097c116 --- /dev/null +++ b/src-tauri/src/commands/breadclip.rs @@ -0,0 +1,8 @@ +//! breadclip has no config file to edit — this panel exists purely to make +//! its background daemon (breadclipd, via `service.rs`) and its +//! on-demand popup visible/controllable from Settings. + +#[tauri::command] +pub fn open_breadclip() { + let _ = std::process::Command::new("breadclip").spawn(); +} diff --git a/src-tauri/src/commands/breadcrumbs.rs b/src-tauri/src/commands/breadcrumbs.rs new file mode 100644 index 0000000..3572f82 --- /dev/null +++ b/src-tauri/src/commands/breadcrumbs.rs @@ -0,0 +1,176 @@ +//! breadcrumbs.toml — Wi-Fi profile state machine. Schema mirrors +//! breadcrumbs/src/config.rs: +//! [settings] scalar tunables +//! [[networks]] saved networks (ssid / password / hidden) +//! [profiles.] per-location profile (networks, tailscale, …) +//! `[settings]` is edited in place; `networks`/`profiles` are rewritten from +//! their editors on save. Other keys/comments are preserved. + +use serde::{Deserialize, Serialize}; +use toml_edit::{value, Array, ArrayOfTables, DocumentMut, Item, Table}; + +use super::config; + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadcrumbs/breadcrumbs.toml") +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Network { + ssid: String, + password: String, + hidden: bool, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Profile { + name: String, + networks: Vec, + detect_ssids: Vec, + bootstrap: String, + exit_node: String, + tailscale: bool, + include_all_known: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct Settings { + default_profile: String, + dns: String, + exit_node: String, + ping_host: String, + connectivity_url: String, + nmcli_wait: i64, + watch_interval: i64, +} + +#[derive(Serialize)] +pub struct BreadcrumbsConfig { + settings: Settings, + networks: Vec, + profiles: Vec, +} + +fn read_networks(doc: &DocumentMut) -> Vec { + let Some(aot) = doc.get("networks").and_then(Item::as_array_of_tables) else { + return Vec::new(); + }; + aot.iter() + .map(|t| Network { + ssid: t.get("ssid").and_then(Item::as_str).unwrap_or("").to_string(), + password: t.get("password").and_then(Item::as_str).unwrap_or("").to_string(), + hidden: t.get("hidden").and_then(Item::as_bool).unwrap_or(false), + }) + .collect() +} + +fn write_networks(doc: &mut DocumentMut, nets: &[Network]) { + let mut aot = ArrayOfTables::new(); + for n in nets { + let mut t = Table::new(); + t.insert("ssid", value(&n.ssid)); + t.insert("password", value(&n.password)); + t.insert("hidden", value(n.hidden)); + aot.push(t); + } + doc.as_table_mut().insert("networks", Item::ArrayOfTables(aot)); +} + +fn read_profiles(doc: &DocumentMut) -> Vec { + let Some(tbl) = doc.get("profiles").and_then(Item::as_table) else { + return Vec::new(); + }; + let str_list = |item: Option<&Item>| -> Vec { + item.and_then(Item::as_array) + .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_default() + }; + tbl.iter() + .filter_map(|(name, item)| { + let p = item.as_table()?; + Some(Profile { + name: name.to_string(), + networks: str_list(p.get("networks")), + detect_ssids: str_list(p.get("detect_ssids")), + bootstrap: p.get("bootstrap").and_then(Item::as_str).unwrap_or("").to_string(), + exit_node: p.get("exit_node").and_then(Item::as_str).unwrap_or("").to_string(), + tailscale: p.get("tailscale").and_then(Item::as_bool).unwrap_or(false), + include_all_known: p.get("include_all_known").and_then(Item::as_bool).unwrap_or(false), + }) + }) + .collect() +} + +fn write_profiles(doc: &mut DocumentMut, profiles: &[Profile]) { + let mut tbl = Table::new(); + let to_arr = |items: &[String]| { + let mut a = Array::new(); + for s in items { + a.push(s.as_str()); + } + a + }; + for p in profiles { + if p.name.is_empty() { + continue; + } + let mut t = Table::new(); + t.insert("networks", value(to_arr(&p.networks))); + t.insert("tailscale", value(p.tailscale)); + t.insert("include_all_known", value(p.include_all_known)); + if !p.detect_ssids.is_empty() { + t.insert("detect_ssids", value(to_arr(&p.detect_ssids))); + } + if !p.bootstrap.is_empty() { + t.insert("bootstrap", value(&p.bootstrap)); + } + if !p.exit_node.is_empty() { + t.insert("exit_node", value(&p.exit_node)); + } + tbl.insert(&p.name, Item::Table(t)); + } + doc.as_table_mut().insert("profiles", Item::Table(tbl)); +} + +#[tauri::command] +pub fn get_breadcrumbs_config() -> BreadcrumbsConfig { + let doc = config::load_doc(&config_path()); + BreadcrumbsConfig { + settings: Settings { + // breadcrumbs' own default_profile_name() is "away", not "home". + default_profile: config::get_str(&doc, &["settings", "default_profile"]).unwrap_or_else(|| "away".into()), + dns: config::get_str(&doc, &["settings", "dns"]).unwrap_or_else(|| "1.1.1.1".into()), + exit_node: config::get_str(&doc, &["settings", "exit_node"]).unwrap_or_default(), + ping_host: config::get_str(&doc, &["settings", "ping_host"]).unwrap_or_else(|| "1.1.1.1".into()), + connectivity_url: config::get_str(&doc, &["settings", "connectivity_url"]) + .unwrap_or_else(|| "http://connectivitycheck.gstatic.com/generate_204".into()), + nmcli_wait: config::get_i64(&doc, &["settings", "nmcli_wait"]).unwrap_or(8), + watch_interval: config::get_i64(&doc, &["settings", "watch_interval"]).unwrap_or(12), + }, + networks: read_networks(&doc), + profiles: read_profiles(&doc), + } +} + +#[derive(Deserialize)] +pub struct SaveBreadcrumbsInput { + settings: Settings, + networks: Vec, + profiles: Vec, +} + +#[tauri::command] +pub fn save_breadcrumbs_config(input: SaveBreadcrumbsInput) -> Result<(), String> { + let path = config_path(); + let mut doc = config::load_doc(&path); + config::set_str(&mut doc, &["settings", "default_profile"], &input.settings.default_profile); + config::set_str(&mut doc, &["settings", "dns"], &input.settings.dns); + config::set_str_or_remove(&mut doc, &["settings", "exit_node"], &input.settings.exit_node); + config::set_str(&mut doc, &["settings", "ping_host"], &input.settings.ping_host); + config::set_str(&mut doc, &["settings", "connectivity_url"], &input.settings.connectivity_url); + config::set_i64(&mut doc, &["settings", "nmcli_wait"], input.settings.nmcli_wait); + config::set_i64(&mut doc, &["settings", "watch_interval"], input.settings.watch_interval); + write_networks(&mut doc, &input.networks); + write_profiles(&mut doc, &input.profiles); + config::save_doc(&path, &doc).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/breadpad.rs b/src-tauri/src/commands/breadpad.rs new file mode 100644 index 0000000..5f0fb5d --- /dev/null +++ b/src-tauri/src/commands/breadpad.rs @@ -0,0 +1,78 @@ +//! breadpad.toml — the breadpad notes/reminders config. Schema mirrors +//! breadpad-shared/src/config.rs (settings, model + model.ollama, reminders, +//! calendar). Edited non-destructively (calendar password + model paths +//! are preserved across saves). + +use serde::{Deserialize, Serialize}; + +use super::config; + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadpad/breadpad.toml") +} + +#[derive(Serialize, Deserialize)] +pub struct BreadpadConfig { + default_type: String, + workspace_tag: bool, + snooze_options: Vec, + archive_after_days: i64, + model_path: String, + tokenizer_path: String, + ollama_enabled: bool, + ollama_endpoint: String, + ollama_model: String, + ollama_confidence_threshold: f64, + reminders_default_morning: String, + reminders_missed_grace_minutes: i64, + calendar_enabled: bool, + calendar_url: String, + calendar_username: String, + calendar_password: String, +} + +#[tauri::command] +pub fn get_breadpad_config() -> BreadpadConfig { + let doc = config::load_doc(&config_path()); + BreadpadConfig { + default_type: config::get_str(&doc, &["settings", "default_type"]).unwrap_or_else(|| "note".into()), + workspace_tag: config::get_bool(&doc, &["settings", "workspace_tag"]).unwrap_or(true), + snooze_options: config::get_str_list(&doc, &["settings", "snooze_options"]), + archive_after_days: config::get_i64(&doc, &["settings", "archive_after_days"]).unwrap_or(30), + model_path: config::get_str(&doc, &["model", "path"]).unwrap_or_default(), + tokenizer_path: config::get_str(&doc, &["model", "tokenizer"]).unwrap_or_default(), + ollama_enabled: config::get_bool(&doc, &["model", "ollama", "enabled"]).unwrap_or(true), + ollama_endpoint: config::get_str(&doc, &["model", "ollama", "endpoint"]).unwrap_or_default(), + ollama_model: config::get_str(&doc, &["model", "ollama", "model"]).unwrap_or_default(), + ollama_confidence_threshold: config::get_f64(&doc, &["model", "ollama", "confidence_threshold"]).unwrap_or(0.6), + reminders_default_morning: config::get_str(&doc, &["reminders", "default_morning"]).unwrap_or_else(|| "7:00".into()), + reminders_missed_grace_minutes: config::get_i64(&doc, &["reminders", "missed_grace_minutes"]).unwrap_or(60), + calendar_enabled: config::get_bool(&doc, &["calendar", "enabled"]).unwrap_or(false), + calendar_url: config::get_str(&doc, &["calendar", "url"]).unwrap_or_default(), + calendar_username: config::get_str(&doc, &["calendar", "username"]).unwrap_or_default(), + calendar_password: config::get_str(&doc, &["calendar", "password"]).unwrap_or_default(), + } +} + +#[tauri::command] +pub fn save_breadpad_config(cfg: BreadpadConfig) -> Result<(), String> { + let path = config_path(); + let mut doc = config::load_doc(&path); + config::set_str(&mut doc, &["settings", "default_type"], &cfg.default_type); + config::set_bool(&mut doc, &["settings", "workspace_tag"], cfg.workspace_tag); + config::set_str_list(&mut doc, &["settings", "snooze_options"], &cfg.snooze_options); + config::set_i64(&mut doc, &["settings", "archive_after_days"], cfg.archive_after_days); + config::set_str_or_remove(&mut doc, &["model", "path"], &cfg.model_path); + config::set_str_or_remove(&mut doc, &["model", "tokenizer"], &cfg.tokenizer_path); + config::set_bool(&mut doc, &["model", "ollama", "enabled"], cfg.ollama_enabled); + config::set_str_or_remove(&mut doc, &["model", "ollama", "endpoint"], &cfg.ollama_endpoint); + config::set_str_or_remove(&mut doc, &["model", "ollama", "model"], &cfg.ollama_model); + config::set_f64(&mut doc, &["model", "ollama", "confidence_threshold"], cfg.ollama_confidence_threshold); + config::set_str_or_remove(&mut doc, &["reminders", "default_morning"], &cfg.reminders_default_morning); + config::set_i64(&mut doc, &["reminders", "missed_grace_minutes"], cfg.reminders_missed_grace_minutes); + config::set_bool(&mut doc, &["calendar", "enabled"], cfg.calendar_enabled); + config::set_str_or_remove(&mut doc, &["calendar", "url"], &cfg.calendar_url); + config::set_str_or_remove(&mut doc, &["calendar", "username"], &cfg.calendar_username); + config::set_str_or_remove(&mut doc, &["calendar", "password"], &cfg.calendar_password); + config::save_doc(&path, &doc).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/breadpaper.rs b/src-tauri/src/commands/breadpaper.rs new file mode 100644 index 0000000..62a48f5 --- /dev/null +++ b/src-tauri/src/commands/breadpaper.rs @@ -0,0 +1,111 @@ +//! breadpaper — wallpaper manager. No config file to edit here; breadpaper +//! takes no persistent settings, just an image path via its CLI +//! (`breadpaper set ` / `breadpaper get`). These commands are a thin +//! backend for that CLI so wallpaper (and the pywal-driven theme it +//! generates) has a discoverable home in Settings. + +use serde::Serialize; +use std::path::{Path, PathBuf}; +use tokio::process::Command; + +/// Extensions breadpaper's own `validate()` accepts. +const WALLPAPER_EXTS: &[&str] = &["png", "jpg", "jpeg", "webp", "gif", "bmp"]; + +/// Caps how many thumbnails the library ever returns, and how deep the +/// recursive scan goes (the library is organized in subfolders, e.g. by +/// show/series, so a non-recursive scan would find nothing). +const MAX_LIBRARY_ITEMS: usize = 80; +const MAX_SCAN_DEPTH: usize = 4; + +pub fn wallpaper_library_dir() -> PathBuf { + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); + PathBuf::from(home).join("Pictures/Backgrounds") +} + +fn is_wallpaper_file(path: &Path) -> bool { + path.extension() + .and_then(|e| e.to_str()) + .map(|e| WALLPAPER_EXTS.iter().any(|ext| ext.eq_ignore_ascii_case(e))) + .unwrap_or(false) +} + +fn scan_wallpapers(dir: &Path) -> Vec { + fn walk(dir: &Path, depth: usize, out: &mut Vec) { + if depth == 0 || out.len() >= MAX_LIBRARY_ITEMS { + return; + } + let Ok(entries) = std::fs::read_dir(dir) else { return }; + let mut entries: Vec<_> = entries.flatten().collect(); + entries.sort_by_key(|e| e.file_name()); + for entry in entries { + if out.len() >= MAX_LIBRARY_ITEMS { + return; + } + let path = entry.path(); + if path.is_dir() { + walk(&path, depth - 1, out); + } else if is_wallpaper_file(&path) { + out.push(path); + } + } + } + let mut out = Vec::new(); + walk(dir, MAX_SCAN_DEPTH, &mut out); + out +} + +#[tauri::command] +pub async fn get_current_wallpaper() -> Option { + let out = Command::new("breadpaper").arg("get").output().await.ok()?; + if !out.status.success() { + return None; + } + let s = String::from_utf8_lossy(&out.stdout).trim().to_string(); + if s.is_empty() { + None + } else { + Some(s) + } +} + +#[tauri::command] +pub async fn set_wallpaper(path: String) -> Result<(), String> { + let ok = Command::new("breadpaper") + .arg("set") + .arg(&path) + .status() + .await + .map(|s| s.success()) + .unwrap_or(false); + if ok { + Ok(()) + } else { + Err("breadpaper failed — see terminal/journal".into()) + } +} + +#[derive(Serialize)] +pub struct LibraryEntry { + path: String, + name: String, +} + +/// Lists wallpapers under the library dir. Bounded/depth-limited (see the +/// constants above) — this is gated behind an explicit "Browse" click on +/// the frontend, not run at app launch, same "costs real time, gated +/// behind a button" posture as the network view's Wi-Fi scan. +#[tauri::command] +pub fn list_wallpaper_library() -> Vec { + scan_wallpapers(&wallpaper_library_dir()) + .into_iter() + .map(|p| LibraryEntry { + name: p.file_name().map(|f| f.to_string_lossy().to_string()).unwrap_or_default(), + path: p.to_string_lossy().to_string(), + }) + .collect() +} + +#[tauri::command] +pub fn wallpaper_library_dir_display() -> String { + wallpaper_library_dir().to_string_lossy().to_string() +} diff --git a/src-tauri/src/commands/breadsearch.rs b/src-tauri/src/commands/breadsearch.rs new file mode 100644 index 0000000..3b1be38 --- /dev/null +++ b/src-tauri/src/commands/breadsearch.rs @@ -0,0 +1,55 @@ +//! breadsearch/config.toml — semantic search indexer (breadmill) + GUI. +//! Schema mirrors breadsearch-shared::Config ([index], [search], [model], [power]). + +use serde::{Deserialize, Serialize}; + +use super::config; + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("breadsearch/config.toml") +} + +#[derive(Serialize, Deserialize)] +pub struct BreadsearchConfig { + power_enabled: bool, + run_on_battery: bool, + backend: String, + index_roots: Vec, + index_excludes: Vec, + index_extensions: Vec, + max_file_mb: f64, + search_limit: i64, + snippet_len: i64, +} + +#[tauri::command] +pub fn get_breadsearch_config() -> BreadsearchConfig { + let doc = config::load_doc(&config_path()); + BreadsearchConfig { + power_enabled: config::get_bool(&doc, &["power", "enabled"]).unwrap_or(true), + run_on_battery: config::get_bool(&doc, &["power", "run_on_battery"]).unwrap_or(false), + backend: config::get_str(&doc, &["model", "backend"]).unwrap_or_else(|| "cpu".into()), + index_roots: config::get_str_list(&doc, &["index", "roots"]), + index_excludes: config::get_str_list(&doc, &["index", "excludes"]), + index_extensions: config::get_str_list(&doc, &["index", "extensions"]), + max_file_mb: config::get_f64(&doc, &["index", "max_file_mb"]).unwrap_or(10.0), + search_limit: config::get_i64(&doc, &["search", "limit"]).unwrap_or(10), + snippet_len: config::get_i64(&doc, &["search", "snippet_len"]).unwrap_or(200), + } +} + +#[tauri::command] +pub fn save_breadsearch_config(cfg: BreadsearchConfig) -> Result<(), String> { + let path = config_path(); + let mut doc = config::load_doc(&path); + config::set_bool(&mut doc, &["power", "enabled"], cfg.power_enabled); + config::set_bool(&mut doc, &["power", "run_on_battery"], cfg.run_on_battery); + config::set_str(&mut doc, &["model", "backend"], &cfg.backend); + config::set_str_list(&mut doc, &["index", "roots"], &cfg.index_roots); + config::set_str_list(&mut doc, &["index", "excludes"], &cfg.index_excludes); + config::set_str_list(&mut doc, &["index", "extensions"], &cfg.index_extensions); + config::set_f64(&mut doc, &["index", "max_file_mb"], cfg.max_file_mb); + config::set_i64(&mut doc, &["search", "limit"], cfg.search_limit); + config::set_i64(&mut doc, &["search", "snippet_len"], cfg.snippet_len); + config::save_doc(&path, &doc).map_err(|e| e.to_string()) +} diff --git a/src/config/mod.rs b/src-tauri/src/commands/config.rs similarity index 100% rename from src/config/mod.rs rename to src-tauri/src/commands/config.rs diff --git a/src-tauri/src/commands/datetime.rs b/src-tauri/src/commands/datetime.rs new file mode 100644 index 0000000..2e8660b --- /dev/null +++ b/src-tauri/src/commands/datetime.rs @@ -0,0 +1,82 @@ +//! Timezone + NTP, over `timedatectl`. `systemd-timesyncd` is enabled by +//! default, so NTP sync is on out of the box — this is mostly for picking a +//! timezone and confirming sync is healthy. + +use serde::Serialize; +use tokio::process::Command; + +async fn show_property(prop: &str) -> String { + Command::new("timedatectl") + .args(["show", &format!("--property={prop}")]) + .output() + .await + .ok() + .and_then(|o| { + String::from_utf8_lossy(&o.stdout) + .trim() + .strip_prefix(&format!("{prop}=")) + .map(str::to_string) + }) + .unwrap_or_default() +} + +async fn list_timezones() -> Vec { + Command::new("timedatectl") + .arg("list-timezones") + .output() + .await + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).lines().map(str::to_string).collect()) + .unwrap_or_default() +} + +async fn current_time_label() -> String { + Command::new("date") + .arg("+%A, %d %B %Y %H:%M") + .output() + .await + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .unwrap_or_default() +} + +#[derive(Serialize)] +pub struct DateTimeInfo { + current_time: String, + timezones: Vec, + current_tz: String, + ntp_enabled: bool, + ntp_synced: bool, +} + +#[tauri::command] +pub async fn get_datetime_info() -> DateTimeInfo { + DateTimeInfo { + current_time: current_time_label().await, + timezones: list_timezones().await, + current_tz: show_property("Timezone").await, + ntp_enabled: show_property("NTP").await == "yes", + ntp_synced: show_property("NTPSynchronized").await == "yes", + } +} + +#[tauri::command] +pub async fn set_timezone(tz: String) -> Result<(), String> { + let output = Command::new("pkexec") + .args(["timedatectl", "set-timezone", &tz]) + .output() + .await + .map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err("Error — check the timezone name".into()) + } +} + +#[tauri::command] +pub async fn set_ntp_enabled(enabled: bool) -> Result<(), String> { + let val = if enabled { "true" } else { "false" }; + Command::new("pkexec").args(["timedatectl", "set-ntp", val]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src-tauri/src/commands/firewall.rs b/src-tauri/src/commands/firewall.rs new file mode 100644 index 0000000..0841d7b --- /dev/null +++ b/src-tauri/src/commands/firewall.rs @@ -0,0 +1,89 @@ +//! ufw firewall rules. `ufw status` itself requires root (confirmed against +//! the installed ufw script), so unlike every other read-only panel, this +//! doesn't query eagerly — the frontend only calls `get_firewall_status` +//! when the user clicks Refresh, deferring the one unavoidable polkit +//! prompt to an explicit action instead of forcing it on app open. + +use serde::Serialize; +use tokio::process::Command; + +#[derive(Serialize, Clone)] +pub struct FirewallRule { + number: String, + text: String, +} + +#[derive(Serialize)] +pub struct FirewallStatus { + active: bool, + rules: Vec, +} + +/// One `pkexec ufw status numbered` call, parsed for both the active/ +/// inactive line and the numbered rules. +#[tauri::command] +pub async fn get_firewall_status() -> Result { + let output = Command::new("pkexec") + .args(["ufw", "status", "numbered"]) + .output() + .await + .map_err(|e| format!("couldn't run pkexec: {e}"))?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + return Err(if stderr.is_empty() { + match output.status.code() { + Some(127) => "no polkit authentication agent is available in this session".to_string(), + Some(code) => format!("pkexec exited with status {code}"), + None => "pkexec was terminated by a signal".to_string(), + } + } else { + stderr + }); + } + let text = String::from_utf8_lossy(&output.stdout); + let active = text.lines().next().is_some_and(|l| l.trim() == "Status: active"); + let rules = text + .lines() + .filter_map(|l| { + let l = l.trim_start(); + if !l.starts_with('[') { + return None; + } + let (num, rest) = l.split_once(']')?; + let number = num.trim_start_matches('[').trim().to_string(); + Some(FirewallRule { number, text: rest.trim().to_string() }) + }) + .collect(); + Ok(FirewallStatus { active, rules }) +} + +#[tauri::command] +pub async fn set_firewall_enabled(enabled: bool) -> Result<(), String> { + let verb = if enabled { "enable" } else { "disable" }; + let output = Command::new("pkexec").args(["ufw", "--force", verb]).output().await.map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +#[tauri::command] +pub async fn add_firewall_rule(rule: String) -> Result<(), String> { + let output = Command::new("pkexec").args(["ufw", "allow", rule.trim()]).output().await.map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +#[tauri::command] +pub async fn remove_firewall_rule(number: String) -> Result<(), String> { + let output = Command::new("pkexec").args(["ufw", "--force", "delete", &number]).output().await.map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} diff --git a/src-tauri/src/commands/firmware.rs b/src-tauri/src/commands/firmware.rs new file mode 100644 index 0000000..f3a5c87 --- /dev/null +++ b/src-tauri/src/commands/firmware.rs @@ -0,0 +1,32 @@ +use serde::Serialize; + +#[derive(Serialize, Clone)] +pub struct FwDevice { + name: String, + version: String, +} + +#[tauri::command] +pub async fn get_updatable_firmware() -> Vec { + let Ok(output) = tokio::process::Command::new("fwupdmgr").args(["get-devices", "--json"]).output().await else { + return Vec::new(); + }; + let Ok(root) = serde_json::from_slice::(&output.stdout) else { + return Vec::new(); + }; + let Some(devices) = root.get("Devices").and_then(|d| d.as_array()) else { + return Vec::new(); + }; + devices + .iter() + .filter(|d| { + d.get("Flags").and_then(|f| f.as_array()).is_some_and(|flags| flags.iter().any(|f| f.as_str() == Some("updatable"))) + }) + .filter_map(|d| { + Some(FwDevice { + name: d.get("Name")?.as_str()?.to_string(), + version: d.get("Version").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(), + }) + }) + .collect() +} diff --git a/src-tauri/src/commands/hyprland.rs b/src-tauri/src/commands/hyprland.rs new file mode 100644 index 0000000..eeb1817 --- /dev/null +++ b/src-tauri/src/commands/hyprland.rs @@ -0,0 +1,110 @@ +//! Display: live-connected-monitor readout (from `hyprctl monitors -j`) plus +//! an editor for `hypr/monitors.json` — the monitor *layout* Hyprland itself +//! reads at login. Like appearance.rs/autostart.rs, a plain typed struct +//! round-tripped whole (JSON has no comments to preserve). +//! +//! `Default` here (the single wildcard rule) must stay in sync with +//! `scripts/display/monitors.lua`'s own `DEFAULT_MONITORS` fallback. + +use serde::{Deserialize, Serialize}; + +use super::config; + +#[derive(Clone, Serialize, Deserialize)] +pub struct MonitorRule { + output: String, + #[serde(default = "default_mode")] + mode: String, + #[serde(default = "default_position")] + position: String, + #[serde(default = "default_scale")] + scale: String, +} + +fn default_mode() -> String { + "preferred".to_string() +} +fn default_position() -> String { + "auto".to_string() +} +fn default_scale() -> String { + "auto".to_string() +} + +impl Default for MonitorRule { + fn default() -> Self { + Self { output: String::new(), mode: default_mode(), position: default_position(), scale: default_scale() } + } +} + +#[derive(Serialize, Deserialize)] +struct MonitorsFile { + #[serde(default)] + monitors: Vec, +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("hypr/monitors.json") +} + +fn hypr_path(name: &str) -> std::path::PathBuf { + config::config_dir().join("hypr").join(name) +} + +#[derive(Serialize)] +pub struct LiveMonitor { + name: String, + mode: String, +} + +#[tauri::command] +pub fn get_live_monitors() -> Vec { + let Some(value) = bread_utils::proc::run_json("hyprctl", &["monitors", "-j"], std::time::Duration::from_secs(3)) + else { + return Vec::new(); + }; + let Ok(monitors) = serde_json::from_value::>(value) else { + return Vec::new(); + }; + monitors + .iter() + .filter_map(|m| { + let name = m.get("name")?.as_str()?; + let w = m.get("width")?.as_u64()?; + let h = m.get("height")?.as_u64()?; + let refresh = m.get("refreshRate")?.as_f64()?; + Some(LiveMonitor { name: name.to_string(), mode: format!("{w}x{h} @ {refresh:.0}Hz") }) + }) + .collect() +} + +#[tauri::command] +pub fn get_monitor_rules() -> Vec { + std::fs::read_to_string(config_path()) + .ok() + .and_then(|s| serde_json::from_str::(&s).ok()) + .filter(|f| !f.monitors.is_empty()) + .map(|f| f.monitors) + .unwrap_or_else(|| vec![MonitorRule::default()]) +} + +#[tauri::command] +pub fn save_monitor_rules(rules: Vec) -> Result<(), String> { + let file = MonitorsFile { monitors: rules }; + let json = serde_json::to_string_pretty(&file).map_err(|e| e.to_string())?; + config::atomic_write(&config_path(), &json).map_err(|e| e.to_string()) +} + +/// Opens `hyprland.lua` in `$EDITOR` (nano if unset) inside a terminal — +/// spawning a TUI editor with no terminal to attach to is a silent no-op. +#[tauri::command] +pub fn open_hyprland_conf() { + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string()); + let path = hypr_path("hyprland.lua"); + let _ = std::process::Command::new("kitty").args(["-e", &editor]).arg(path).spawn(); +} + +#[tauri::command] +pub fn open_keybinds_viewer() { + let _ = std::process::Command::new("breadhelp").spawn(); +} diff --git a/src-tauri/src/commands/keybinds.rs b/src-tauri/src/commands/keybinds.rs new file mode 100644 index 0000000..f46cf7a --- /dev/null +++ b/src-tauri/src/commands/keybinds.rs @@ -0,0 +1,354 @@ +//! hypr/binds.json — Hyprland keybind editor, read by +//! `scripts/ui/binds.lua` on the Hyprland side (see hyprland.lua). +//! +//! This file has TWO real on-disk shapes, and which one applies depends on +//! the machine: +//! +//! - **Flat** (`default_mods` + a single `bindings` array) — what BOS itself +//! ships (`iso/airootfs/etc/skel/.config/hypr/binds.json`, read by the +//! BOS-shipped `scripts/input/binds.lua`). No layouts. Each bind carries +//! `label`/`category`/`demo_cmd` fields breadhelp depends on for its +//! cheatsheet and guided tour. +//! - **MultiLayout** (`globals`/`common`/one `layouts` entry per keyboard +//! layout) — a personal, per-machine schema some dev setups use instead, +//! read by a different, personal `binds.lua`. +//! +//! `SchemaKind` detects which shape is actually on disk (from the top-level +//! key set) and `save()` always emits that SAME shape back — see +//! `SchemaKind::detect` and `save_to`. Loading a real BOS (Flat) file under +//! the wrong assumption and saving it back would silently drop the +//! `bindings` key entirely — still valid JSON, so the Lua `pcall` +//! failsafes on the reading side would never catch it. +//! +//! Each bind's shape also varies by `action` (`exec` needs `command`, +//! `move_dir` needs `direction`, workspace-focus needs `workspace`, mouse +//! binds need `options.mouse`, ...). Rather than modelling every action's +//! field set as its own row layout — which would mean a combinatorial +//! explosion of widgets and silently dropping any action shape this editor +//! doesn't already know about — `action`/`key`/`mods` get real fields (the +//! ones every bind has) and everything else round-trips through +//! `#[serde(flatten)]` into a small inline-JSON column, same trade-off the +//! other Hyprland JSON editors (appearance.rs, hyprland.rs, autostart.rs) +//! already make: no comments to preserve, so this is a whole-file round +//! trip, not the `toml_edit`/`Doc` path-based pattern. + +use std::collections::BTreeMap; +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +use super::config; + +/// Which on-disk shape `binds.json` was loaded as. Detected once at load +/// time from the top-level key set present in the JSON, then pinned for the +/// lifetime of the editor session (round-tripped to the frontend and back +/// on save) so `save()` always writes back the same shape it read, +/// regardless of what the in-memory model happens to have populated. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SchemaKind { + /// `{ "default_mods": [...], "bindings": [...] }` — BOS's real shipped + /// shape. No layout-switching UI applies; there's nothing to switch. + Flat, + /// `{ "active_layout", "default_mods", "globals", "common", "layouts" }` + /// — the personal, multi-keyboard-layout schema this editor was + /// originally built against. + MultiLayout, + /// Neither key set matched — an empty file, a totally different shape, + /// or unparsable JSON. Loading still renders (empty), but `save()` + /// refuses outright rather than guessing a shape and risking silently + /// destroying whatever the real file's actual schema was. + Unknown, +} + +impl SchemaKind { + fn detect(top_level: &Map) -> Self { + if top_level.contains_key("bindings") { + SchemaKind::Flat + } else if top_level.contains_key("globals") + || top_level.contains_key("common") + || top_level.contains_key("layouts") + { + SchemaKind::MultiLayout + } else { + SchemaKind::Unknown + } + } +} + +#[derive(Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Bind { + action: String, + #[serde(skip_serializing_if = "Option::is_none")] + key: Option, + /// `None` (key omitted) means "fall back to `default_mods`"; `Some(_)` + /// — including `Some(vec![])` — means "use exactly this, even if that's + /// no modifiers at all." Real BOS binds rely on that distinction (e.g. + /// media keys pin `"mods": []` on purpose so they never inherit + /// `default_mods`), so this can't collapse both cases to "omit the + /// key" the way a bare `Vec` with `skip_serializing_if` would — + /// that would silently turn an explicit "no mods" into "use the + /// default" the next time this editor saves the file. + #[serde(skip_serializing_if = "Option::is_none")] + mods: Option>, + /// Everything else a bind can carry — `command`, `direction`, + /// `workspace`, `x`, `y`, `layout`, `options`, `label`, `category`, + /// `demo_cmd`, and any action shape not yet invented. Edited on the + /// frontend as compact inline JSON. This flatten is what keeps + /// breadhelp's `label`/`category`/`demo_cmd` fields — which this + /// editor's UI has no dedicated widgets for — alive across a full + /// load/save round trip instead of being silently dropped. + #[serde(flatten)] + extra: Map, +} + +#[derive(Serialize, Deserialize, Default)] +#[serde(default)] +pub struct BindsFile { + #[serde(skip_serializing_if = "String::is_empty")] + active_layout: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + default_mods: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + globals: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + common: Vec, + /// A `BTreeMap` (alphabetical), not the file's original insertion order + /// — same "whole-file round trip, formatting not preserved" trade-off as + /// the rest of this file's JSON-config siblings. + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + layouts: BTreeMap>, + /// Flat-schema bind list — BOS's real shipped shape. Only ever populated + /// when `SchemaKind::Flat` was detected at load time; stays empty (and + /// so omitted, see `to_json`) for a MultiLayout file. + #[serde(skip_serializing_if = "Vec::is_empty")] + bindings: Vec, +} + +/// What the frontend fetches once at load: which shape was detected, plus +/// the data itself. Sent back verbatim to `save_keybinds` so a stray code +/// path on either side can't accidentally save `file` without knowing which +/// shape it's supposed to come back out as. +#[derive(Serialize)] +pub struct BindsPayload { + kind: SchemaKind, + file: BindsFile, +} + +fn config_path() -> std::path::PathBuf { + config::config_dir().join("hypr/binds.json") +} + +fn load_from(path: &Path) -> (BindsFile, SchemaKind) { + let Ok(text) = std::fs::read_to_string(path) else { + // No file yet (fresh install/environment) — nothing on disk to + // misdetect or destroy. BOS itself ships the flat schema, so a new + // file defaults to Flat rather than the personal MultiLayout schema + // this editor originally assumed. + return (BindsFile::default(), SchemaKind::Flat); + }; + let kind = match serde_json::from_str::(&text) { + Ok(Value::Object(top_level)) => SchemaKind::detect(&top_level), + // Unparsable JSON, or valid JSON that isn't even an object — treat + // as Unknown so save() refuses rather than silently overwriting + // whatever this file actually was with an empty default. + _ => SchemaKind::Unknown, + }; + let file: BindsFile = serde_json::from_str(&text).unwrap_or_default(); + (file, kind) +} + +fn load() -> (BindsFile, SchemaKind) { + load_from(&config_path()) +} + +/// Serialize `f` in exactly the shape `kind` implies: +/// - `Flat` -> `{ "default_mods": [...], "bindings": [...] }`, nothing else +/// — no `active_layout`/`globals`/`common`/`layouts` keys, even if the +/// struct happens to carry empty values for them. +/// - `MultiLayout` -> today's existing shape (whatever fields are +/// non-empty), via `BindsFile`'s own `Serialize` impl. +fn to_json(f: &BindsFile, kind: SchemaKind) -> Value { + match kind { + SchemaKind::Flat => serde_json::json!({ + "default_mods": f.default_mods, + "bindings": f.bindings, + }), + SchemaKind::MultiLayout => serde_json::to_value(f).unwrap_or(Value::Null), + SchemaKind::Unknown => Value::Null, + } +} + +fn save_to(path: &Path, f: &BindsFile, kind: SchemaKind) -> std::io::Result<()> { + if kind == SchemaKind::Unknown { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "binds.json's schema wasn't recognized (expected a \"bindings\" key, or one of \ + \"globals\"/\"common\"/\"layouts\") — refusing to save so nothing gets silently \ + overwritten. Fix or remove the file, then reopen this panel.", + )); + } + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let text = serde_json::to_string_pretty(&to_json(f, kind)).unwrap_or_default(); + config::atomic_write(path, &text) +} + +fn save(f: &BindsFile, kind: SchemaKind) -> std::io::Result<()> { + save_to(&config_path(), f, kind) +} + +#[tauri::command] +pub fn get_keybinds() -> BindsPayload { + let (file, kind) = load(); + BindsPayload { kind, file } +} + +#[tauri::command] +pub fn save_keybinds(file: BindsFile, kind: SchemaKind) -> Result<(), String> { + save(&file, kind).map_err(|e| e.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A representative slice of BOS's real shipped `binds.json` + /// (`iso/airootfs/etc/skel/.config/hypr/binds.json`, flat schema) — + /// chosen to exercise the extra-field variety breadhelp reads (`label`, + /// `category`, `demo_cmd`), an explicit `mods: []` override, a nested + /// `options` object, and both integer and string `workspace` values. + /// This is the fixture that would have caught the original GTK-editor + /// bug: mis-detecting this shape as MultiLayout and silently dropping + /// the whole `bindings` array on save. + const REAL_BOS_FLAT_FIXTURE: &str = r#"{ + "default_mods": ["SUPER"], + "bindings": [ + { "action": "exec", "command": "kitty", "key": "RETURN", "label": "Open a terminal", "category": "apps" }, + { "action": "close", "key": "BACKSPACE", "label": "Close the focused window", "category": "windows" }, + { "action": "exec", "command": "breadbox", "key": "SPACE", "label": "Open the app launcher (breadbox)", "category": "apps", "demo_cmd": "breadbox" }, + { "action": "exec", "command": "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+", "key": "XF86AudioRaiseVolume", "mods": [], "options": { "locked": true, "repeating": true }, "label": "Volume up", "category": "media" }, + { "action": "focus", "workspace": 1, "key": "1", "label": "Switch to workspace 1", "category": "workspaces" }, + { "action": "focus", "workspace": "e+1", "key": "bracketright", "label": "Next workspace", "category": "workspaces" }, + { "action": "resize_dir", "x": 30, "y": 0, "key": "right", "mods": ["SUPER", "SHIFT"], "options": { "repeating": true }, "label": "Resize the focused window (grow right)", "category": "focus" }, + { "action": "drag", "key": "mouse:272", "options": { "mouse": true }, "label": "Move a window (drag)", "category": "mouse" } + ] +}"#; + + fn parse(text: &str) -> (BindsFile, SchemaKind) { + let kind = match serde_json::from_str::(text) { + Ok(Value::Object(top)) => SchemaKind::detect(&top), + _ => SchemaKind::Unknown, + }; + let file: BindsFile = serde_json::from_str(text).unwrap_or_default(); + (file, kind) + } + + #[test] + fn detects_flat_schema_from_real_bos_binds_json() { + let (_, kind) = parse(REAL_BOS_FLAT_FIXTURE); + assert_eq!(kind, SchemaKind::Flat); + } + + #[test] + fn round_trips_real_bos_flat_binds_json_through_load_and_save() { + let (file, kind) = parse(REAL_BOS_FLAT_FIXTURE); + assert_eq!(kind, SchemaKind::Flat); + + let original: Value = serde_json::from_str(REAL_BOS_FLAT_FIXTURE).unwrap(); + let saved = to_json(&file, kind); + + // Flat save must emit EXACTLY {default_mods, bindings} — no + // active_layout/globals/common/layouts keys leaking in. + let saved_obj = saved.as_object().expect("flat save must be a JSON object"); + assert_eq!( + saved_obj.keys().cloned().collect::>(), + ["default_mods", "bindings"].into_iter().map(String::from).collect(), + "Flat schema must round-trip as exactly {{default_mods, bindings}}" + ); + + // The `bindings` array — and every per-bind extra field (label, + // category, demo_cmd, mods, options, integer vs string workspace, + // ...) — must survive the round trip semantically untouched. + assert_eq!(saved["bindings"], original["bindings"]); + assert_eq!(saved["default_mods"], original["default_mods"]); + } + + #[test] + fn round_trip_via_files_preserves_bindings_key_and_extras() { + let dir = std::env::temp_dir().join(format!("bos-settings-keybinds-test-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("binds.json"); + std::fs::write(&path, REAL_BOS_FLAT_FIXTURE).unwrap(); + + let (file, kind) = load_from(&path); + assert_eq!(kind, SchemaKind::Flat); + save_to(&path, &file, kind).unwrap(); + + let saved_text = std::fs::read_to_string(&path).unwrap(); + let saved: Value = serde_json::from_str(&saved_text).unwrap(); + let original: Value = serde_json::from_str(REAL_BOS_FLAT_FIXTURE).unwrap(); + + assert!(saved.get("bindings").is_some(), "bindings key must survive a load -> save round trip"); + assert_eq!(saved["bindings"], original["bindings"]); + assert_eq!(saved["default_mods"], original["default_mods"]); + + // Backup safety net: a second save must leave `.bak` holding the + // prior contents. + save_to(&path, &file, kind).unwrap(); + let backup_path = dir.join("binds.json.bak"); + assert!(backup_path.exists(), "save must back up the previous file"); + let backup: Value = serde_json::from_str(&std::fs::read_to_string(&backup_path).unwrap()).unwrap(); + assert_eq!(backup["bindings"], original["bindings"]); + + let _ = std::fs::remove_dir_all(&dir); + } + + #[test] + fn detects_and_round_trips_multi_layout_schema() { + let text = r#"{ + "active_layout": "qwerty", + "default_mods": ["SUPER"], + "globals": [{ "action": "exec", "command": "kitty", "key": "RETURN" }], + "common": [], + "layouts": { "qwerty": [{ "action": "close", "key": "BACKSPACE" }] } + }"#; + let (file, kind) = parse(text); + assert_eq!(kind, SchemaKind::MultiLayout); + + let saved = to_json(&file, kind); + assert!(saved.get("bindings").is_none(), "MultiLayout save must not emit a flat `bindings` key"); + assert_eq!(saved["active_layout"], "qwerty"); + assert_eq!(saved["layouts"]["qwerty"][0]["action"], "close"); + assert_eq!(saved["globals"][0]["command"], "kitty"); + } + + #[test] + fn unknown_schema_is_detected_and_refuses_to_save() { + let text = r#"{ "some_other_shape": true }"#; + let (file, kind) = parse(text); + assert_eq!(kind, SchemaKind::Unknown); + + let dir = std::env::temp_dir().join(format!("bos-settings-keybinds-unknown-test-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("binds.json"); + + let result = save_to(&path, &file, kind); + assert!(result.is_err(), "save() must refuse when schema kind is Unknown"); + assert!(!path.exists(), "refusing to save must not create/touch the target file"); + + let _ = std::fs::remove_dir_all(&dir); + } + + #[test] + fn missing_file_defaults_to_flat_not_multi_layout() { + let dir = std::env::temp_dir().join(format!("bos-settings-keybinds-missing-test-{}", std::process::id())); + // Don't create the file at all. + let path = dir.join("binds.json"); + let (_, kind) = load_from(&path); + assert_eq!(kind, SchemaKind::Flat); + } +} diff --git a/src/ui/views/mod.rs b/src-tauri/src/commands/mod.rs similarity index 86% rename from src/ui/views/mod.rs rename to src-tauri/src/commands/mod.rs index fca274f..e26b415 100644 --- a/src/ui/views/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -11,6 +11,7 @@ pub mod breadcrumbs; pub mod breadpad; pub mod breadpaper; pub mod breadsearch; +pub mod config; pub mod datetime; pub mod firewall; pub mod firmware; @@ -19,6 +20,9 @@ pub mod keybinds; pub mod network; pub mod packages; pub mod power; +pub mod service; pub mod snapshots; pub mod sound; +pub mod streaming; +pub mod theme; pub mod users; diff --git a/src-tauri/src/commands/network.rs b/src-tauri/src/commands/network.rs new file mode 100644 index 0000000..cf0254f --- /dev/null +++ b/src-tauri/src/commands/network.rs @@ -0,0 +1,117 @@ +//! Wi-Fi + Ethernet over `nmcli`. NetworkManager lets the active session +//! user manage connections via polkit already, so the common paths (scan, +//! connect, toggle radio) need no `pkexec`. VPN import, 802.1x, and other +//! edge cases are punted to `nm-connection-editor` via the Advanced button. + +use serde::Serialize; +use std::collections::{HashMap, HashSet}; +use tokio::process::Command; + +#[derive(Serialize, Clone)] +pub struct WifiNetwork { + ssid: String, + signal: i32, + secured: bool, + active: bool, + known: bool, +} + +async fn radio_enabled() -> bool { + Command::new("nmcli") + .args(["radio", "wifi"]) + .output() + .await + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "enabled") + .unwrap_or(false) +} + +async fn ethernet_status() -> Option { + let out = Command::new("nmcli").args(["-t", "-f", "DEVICE,TYPE,STATE"]).arg("dev").output().await.ok()?; + let text = String::from_utf8_lossy(&out.stdout); + text.lines().find_map(|l| { + let mut cols = l.splitn(3, ':'); + let (dev, ty, state) = (cols.next()?, cols.next()?, cols.next()?); + (ty == "ethernet").then(|| format!("{dev}: {state}")) + }) +} + +async fn known_connection_names() -> HashSet { + let Ok(out) = Command::new("nmcli").args(["-t", "-f", "NAME"]).arg("con").arg("show").output().await else { + return HashSet::new(); + }; + String::from_utf8_lossy(&out.stdout).lines().map(str::to_string).collect() +} + +#[derive(Serialize)] +pub struct NetworkInfo { + radio_enabled: bool, + ethernet: Option, +} + +#[tauri::command] +pub async fn get_network_info() -> NetworkInfo { + NetworkInfo { radio_enabled: radio_enabled().await, ethernet: ethernet_status().await } +} + +#[tauri::command] +pub async fn set_wifi_radio(enabled: bool) -> Result<(), String> { + let val = if enabled { "on" } else { "off" }; + Command::new("nmcli").args(["radio", "wifi", val]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} + +/// Scan + list Wi-Fi networks, deduplicated by SSID (keeping the strongest +/// signal — the same AP shows once per band/BSSID otherwise). +#[tauri::command] +pub async fn scan_wifi() -> Vec { + let _ = Command::new("nmcli").args(["dev", "wifi", "rescan"]).output().await; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let Ok(out) = Command::new("nmcli").args(["-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "dev", "wifi", "list"]).output().await + else { + return Vec::new(); + }; + let known = known_connection_names().await; + let text = String::from_utf8_lossy(&out.stdout); + let mut by_ssid: HashMap = HashMap::new(); + for line in text.lines() { + let mut cols = line.splitn(4, ':'); + let (ssid, signal, security, in_use) = (cols.next(), cols.next(), cols.next(), cols.next()); + let Some(ssid) = ssid.filter(|s| !s.is_empty()) else { continue }; + let signal: i32 = signal.and_then(|s| s.parse().ok()).unwrap_or(0); + let net = WifiNetwork { + ssid: ssid.to_string(), + signal, + secured: security.map(|s| !s.is_empty()).unwrap_or(false), + active: in_use == Some("*"), + known: known.contains(ssid), + }; + by_ssid.entry(ssid.to_string()).and_modify(|existing| if net.signal > existing.signal { *existing = net.clone() }).or_insert(net); + } + let mut list: Vec<_> = by_ssid.into_values().collect(); + list.sort_by(|a, b| b.signal.cmp(&a.signal)); + list +} + +#[tauri::command] +pub async fn connect_wifi(ssid: String, password: Option) -> Result<(), String> { + let known = known_connection_names().await; + let output = if let Some(password) = password { + Command::new("nmcli").args(["dev", "wifi", "connect", &ssid, "password", &password]).output().await + } else if known.contains(&ssid) { + Command::new("nmcli").args(["con", "up", &ssid]).output().await + } else { + Command::new("nmcli").args(["dev", "wifi", "connect", &ssid]).output().await + } + .map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +#[tauri::command] +pub fn open_connection_editor() { + let _ = std::process::Command::new("nm-connection-editor").spawn(); +} diff --git a/src-tauri/src/commands/packages.rs b/src-tauri/src/commands/packages.rs new file mode 100644 index 0000000..29e522b --- /dev/null +++ b/src-tauri/src/commands/packages.rs @@ -0,0 +1,39 @@ +use serde::Serialize; +use std::collections::HashMap; + +#[derive(Serialize, Clone)] +pub struct InstalledPackage { + name: String, + version: String, +} + +#[tauri::command] +pub fn get_installed_packages() -> Vec { + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); + let path = std::path::Path::new(&home).join(".local/state/bakery/installed.json"); + + let Ok(text) = std::fs::read_to_string(&path) else { + return Vec::new(); + }; + let Ok(mut parsed) = serde_json::from_str::(&text) else { + return Vec::new(); + }; + // installed.json is {"packages": {name: {version, binaries, services}}}, + // not a flat map of package name to metadata. + let Some(packages) = parsed.get_mut("packages").map(std::mem::take) else { + return Vec::new(); + }; + let Ok(packages) = serde_json::from_value::>(packages) else { + return Vec::new(); + }; + + let mut list: Vec = packages + .into_iter() + .map(|(name, val)| { + let version = val.get("version").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(); + InstalledPackage { name, version } + }) + .collect(); + list.sort_by(|a, b| a.name.cmp(&b.name)); + list +} diff --git a/src-tauri/src/commands/power.rs b/src-tauri/src/commands/power.rs new file mode 100644 index 0000000..3d82def --- /dev/null +++ b/src-tauri/src/commands/power.rs @@ -0,0 +1,145 @@ +//! Battery/power status (upower), brightness (brightnessctl), and TLP's +//! current profile. Deliberately no AC/Battery/Performance *switcher* — TLP +//! automatically picks a profile by power source, so this only exposes +//! controls for things that are actually user choices: brightness, and +//! charge thresholds where the hardware supports them. + +use serde::Serialize; +use tokio::process::Command; + +async fn upower_device(kind: &str) -> Option { + let out = Command::new("upower").arg("-e").output().await.ok()?; + String::from_utf8_lossy(&out.stdout).lines().find(|l| l.to_lowercase().contains(kind)).map(str::to_string) +} + +async fn upower_field(device: &str, field: &str) -> Option { + let out = Command::new("upower").args(["-i", device]).output().await.ok()?; + let text = String::from_utf8_lossy(&out.stdout); + text.lines() + .find(|l| l.trim_start().starts_with(field)) + .and_then(|l| l.split(':').nth(1)) + .map(|v| v.trim().to_string()) +} + +async fn battery_summary() -> Vec<(String, String)> { + let Some(bat) = upower_device("bat").await else { + return vec![("Battery".to_string(), "No battery detected".to_string())]; + }; + let mut rows = Vec::new(); + if let Some(state) = upower_field(&bat, "state").await { + rows.push(("Status".to_string(), state)); + } + if let Some(pct) = upower_field(&bat, "percentage").await { + rows.push(("Charge".to_string(), pct)); + } + let t = match upower_field(&bat, "time to empty").await { + Some(t) => Some(t), + None => upower_field(&bat, "time to full").await, + }; + if let Some(t) = t { + rows.push(("Time remaining".to_string(), t)); + } + let full: Option = upower_field(&bat, "energy-full").await.and_then(|v| v.split_whitespace().next()?.parse().ok()); + let design: Option = + upower_field(&bat, "energy-full-design").await.and_then(|v| v.split_whitespace().next()?.parse().ok()); + if let (Some(full), Some(design)) = (full, design) { + if design > 0.0 { + rows.push(("Battery health".to_string(), format!("{:.0}% of design capacity", full / design * 100.0))); + } + } + rows +} + +async fn power_source() -> String { + match upower_device("ac").await { + Some(ac) => match upower_field(&ac, "online").await { + Some(v) if v == "yes" => "AC power".to_string(), + Some(_) => "Battery".to_string(), + None => "Unknown".to_string(), + }, + None => "Unknown".to_string(), + } +} + +async fn tlp_profile() -> Option { + let out = Command::new("tlp-stat").arg("-s").output().await.ok()?; + let text = String::from_utf8_lossy(&out.stdout); + text.lines().find(|l| l.trim_start().starts_with("TLP profile")).and_then(|l| l.split('=').nth(1)).map(|v| v.trim().to_string()) +} + +async fn brightness_device() -> Option { + let out = Command::new("brightnessctl").output().await.ok()?; + String::from_utf8_lossy(&out.stdout).lines().find(|l| l.starts_with("Device")).and_then(|l| l.split('\'').nth(1)).map(str::to_string) +} + +async fn brightness_pct() -> Option { + let out = Command::new("brightnessctl").output().await.ok()?; + let text = String::from_utf8_lossy(&out.stdout); + text.lines() + .find(|l| l.contains("Current brightness")) + .and_then(|l| l.split('(').nth(1)) + .and_then(|v| v.trim_end_matches("%)").parse().ok()) +} + +/// Charge-threshold sysfs paths, only Some when the running kernel driver +/// actually exposes them — genuinely hardware-dependent, not every install +/// will have this. +fn charge_threshold_paths() -> Option<(std::path::PathBuf, std::path::PathBuf)> { + let base = std::path::Path::new("/sys/class/power_supply"); + let entries = std::fs::read_dir(base).ok()?; + for entry in entries.flatten() { + let start = entry.path().join("charge_control_start_threshold"); + let end = entry.path().join("charge_control_end_threshold"); + if start.exists() && end.exists() { + return Some((start, end)); + } + } + None +} + +fn read_threshold(path: &std::path::Path) -> i64 { + std::fs::read_to_string(path).ok().and_then(|s| s.trim().parse().ok()).unwrap_or(100) +} + +#[derive(Serialize)] +pub struct PowerInfo { + battery: Vec<(String, String)>, + power_source: String, + brightness_pct: Option, + charge_start: Option, + charge_end: Option, + tlp_profile: Option, +} + +#[tauri::command] +pub async fn get_power_info() -> PowerInfo { + let charge = charge_threshold_paths(); + PowerInfo { + battery: battery_summary().await, + power_source: power_source().await, + brightness_pct: brightness_pct().await, + charge_start: charge.as_ref().map(|(s, _)| read_threshold(s)), + charge_end: charge.as_ref().map(|(_, e)| read_threshold(e)), + tlp_profile: tlp_profile().await, + } +} + +#[tauri::command] +pub async fn set_brightness(percent: i64) -> Result<(), String> { + let Some(device) = brightness_device().await else { + return Err("No controllable backlight found".into()); + }; + let pct = format!("{percent}%"); + Command::new("brightnessctl").args(["--device", &device, "set", &pct]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub async fn set_charge_threshold(which: String, percent: i64) -> Result<(), String> { + let Some((start, end)) = charge_threshold_paths() else { + return Err("No charge threshold support on this hardware".into()); + }; + let path = if which == "start" { start } else { end }; + Command::new("pkexec").args(["tee", &path.display().to_string()]).arg(percent.to_string()).output().await.map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src-tauri/src/commands/service.rs b/src-tauri/src/commands/service.rs new file mode 100644 index 0000000..c99fee4 --- /dev/null +++ b/src-tauri/src/commands/service.rs @@ -0,0 +1,77 @@ +//! Live systemd `--user` unit status plus start/stop/restart/logs — every +//! bread-ecosystem panel whose app is actually a daemon (not just a config +//! file) gets this. Ported from `src/ui/widgets.rs`'s `service_control`; +//! the `critical`-unit confirm-before-stop behavior moves to the frontend +//! (a confirm step gating the call to `service_action`), since that's UI +//! policy, not something the command itself needs to know. + +use serde::{Deserialize, Serialize}; +use tokio::process::Command; + +#[derive(Serialize)] +pub struct ServiceStatus { + active: bool, + enabled: bool, +} + +#[derive(Deserialize)] +pub enum ServiceAction { + Start, + Stop, + Restart, +} + +async fn systemctl_active(unit: &str) -> bool { + Command::new("systemctl") + .args(["--user", "is-active", "--quiet", unit]) + .status() + .await + .map(|s| s.success()) + .unwrap_or(false) +} + +async fn systemctl_enabled(unit: &str) -> bool { + Command::new("systemctl") + .args(["--user", "is-enabled", "--quiet", unit]) + .status() + .await + .map(|s| s.success()) + .unwrap_or(false) +} + +#[tauri::command] +pub async fn get_service_status(unit: String) -> ServiceStatus { + ServiceStatus { + active: systemctl_active(&unit).await, + enabled: systemctl_enabled(&unit).await, + } +} + +#[tauri::command] +pub async fn service_action(unit: String, action: ServiceAction) -> Result<(), String> { + let verb = match action { + ServiceAction::Start => "start", + ServiceAction::Stop => "stop", + ServiceAction::Restart => "restart", + }; + let output = Command::new("systemctl") + .args(["--user", verb, &unit]) + .output() + .await + .map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +/// Opens a terminal following the unit's journal — same as today's GTK +/// panel, no reason to pull an open-ended `journalctl -f` tail into the +/// webview. +#[tauri::command] +pub fn open_logs(unit: String) { + let _ = std::process::Command::new("kitty") + .args(["-e", "journalctl", "--user", "-u", &unit, "-f"]) + .spawn(); +} diff --git a/src-tauri/src/commands/snapshots.rs b/src-tauri/src/commands/snapshots.rs new file mode 100644 index 0000000..f80a509 --- /dev/null +++ b/src-tauri/src/commands/snapshots.rs @@ -0,0 +1,62 @@ +use serde::Serialize; +use tokio::process::Command; + +#[derive(Serialize, Clone)] +pub struct SnapshotRow { + number: String, + date: String, + description: String, +} + +/// `Err` carries snapper's trimmed stderr — distinct from `Ok(vec![])` +/// (snapper works fine, there just aren't any snapshots yet). +#[tauri::command] +pub async fn get_snapshots() -> Result, String> { + // NOTE: the real flag is --columns, not --output-cols (snapper rejects + // that outright) — confirmed against snapper 0.13's own --help. + let output = Command::new("snapper") + .args(["list", "--columns", "number,date,description"]) + .output() + .await + .map_err(|e| e.to_string())?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + eprintln!("bos-settings: snapper list failed: {stderr}"); + return Err(stderr); + } + + let text = String::from_utf8_lossy(&output.stdout); + Ok(text + .lines() + .skip(2) // header + separator + .filter_map(|line| { + let mut cols = line.splitn(3, '|'); + let number = cols.next()?.trim().to_string(); + // Snapshot 0 ("current") always exists, can't be rolled back to + // or deleted, and isn't a real snapshot. + if number == "0" { + return None; + } + Some(SnapshotRow { number, date: cols.next()?.trim().to_string(), description: cols.next()?.trim().to_string() }) + }) + .collect()) +} + +#[tauri::command] +pub async fn delete_snapshot(number: String) -> Result<(), String> { + let output = Command::new("snapper").args(["delete", &number]).status().await.map_err(|e| e.to_string())?; + if output.success() { + Ok(()) + } else { + Err("snapper delete exited with an error — the snapshot wasn't removed.".into()) + } +} + +/// BOS boots with root pinned to a named subvolume (grub emits +/// rootflags=subvol=@), so `snapper rollback`'s usual mechanism has no +/// effect here. The real way back is grub-btrfs, which generates a GRUB +/// submenu entry per snapshot — this just reboots so the user can pick it. +#[tauri::command] +pub fn reboot_system() { + let _ = std::process::Command::new("systemctl").arg("reboot").spawn(); +} diff --git a/src-tauri/src/commands/sound.rs b/src-tauri/src/commands/sound.rs new file mode 100644 index 0000000..4cc2fb4 --- /dev/null +++ b/src-tauri/src/commands/sound.rs @@ -0,0 +1,102 @@ +//! Output/input volume and device selection over PipeWire's pulse +//! compatibility layer (`pactl`) — the same surface hyprland.lua's media +//! keys already use via `wpctl`. `pactl` is used here instead because it +//! can enumerate devices with human-readable descriptions and switch the +//! default in one command; `wpctl` cannot easily do either. + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tokio::process::Command; + +#[derive(Deserialize, Serialize, Clone)] +pub struct SoundDevice { + name: String, + description: String, + mute: bool, + percent: f64, +} + +#[derive(Deserialize)] +struct RawDevice { + name: String, + description: String, + mute: bool, + volume: HashMap, +} + +#[derive(Deserialize)] +struct RawVolumeChannel { + value_percent: String, +} + +impl RawDevice { + fn percent(&self) -> f64 { + self.volume + .values() + .next() + .and_then(|v| v.value_percent.trim_end_matches('%').trim().parse::().ok()) + .unwrap_or(0.0) + } +} + +async fn list_devices(kind: &str) -> Vec { + let Ok(output) = Command::new("pactl").args(["-f", "json", "list", kind]).output().await else { + return Vec::new(); + }; + let raw: Vec = serde_json::from_slice(&output.stdout).unwrap_or_default(); + raw.into_iter() + .map(|d| SoundDevice { name: d.name.clone(), description: d.description.clone(), mute: d.mute, percent: d.percent() }) + .collect() +} + +async fn default_device_name(kind: &str) -> Option { + let flag = if kind == "sinks" { "get-default-sink" } else { "get-default-source" }; + Command::new("pactl") + .arg(flag) + .output() + .await + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|s| !s.is_empty()) +} + +#[derive(Serialize)] +pub struct SoundSection { + devices: Vec, + default_name: Option, +} + +#[tauri::command] +pub async fn get_sound_section(kind: String) -> SoundSection { + let devices = list_devices(&kind).await; + let default_name = default_device_name(&kind).await; + SoundSection { devices, default_name } +} + +#[tauri::command] +pub async fn set_default_sound_device(kind: String, name: String) -> Result<(), String> { + let flag = if kind == "sinks" { "set-default-sink" } else { "set-default-source" }; + Command::new("pactl").args([flag, &name]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub async fn set_sound_volume(kind: String, name: String, percent: i64) -> Result<(), String> { + let flag = if kind == "sinks" { "set-sink-volume" } else { "set-source-volume" }; + let pct = format!("{percent}%"); + Command::new("pactl").args([flag, &name, &pct]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub async fn set_sound_mute(kind: String, name: String, mute: bool) -> Result<(), String> { + let flag = if kind == "sinks" { "set-sink-mute" } else { "set-source-mute" }; + let val = if mute { "1" } else { "0" }; + Command::new("pactl").args([flag, &name, val]).status().await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub fn open_mixer() { + let _ = std::process::Command::new("pavucontrol").spawn(); +} diff --git a/src-tauri/src/commands/streaming.rs b/src-tauri/src/commands/streaming.rs new file mode 100644 index 0000000..d7d6a40 --- /dev/null +++ b/src-tauri/src/commands/streaming.rs @@ -0,0 +1,57 @@ +//! Shared event-streaming command runner for the genuinely long-running +//! operations (package/firmware updates) where the GTK app treated output +//! as "watch the log scroll" — the Tauri-side analog of +//! `stream_command_then`'s async_channel → glib::spawn_future_local +//! pipeline, using Tauri's event bus instead of a GLib main-loop channel. +//! Most other commands are simple request/response (see the other modules) +//! since the operations they wrap finish in well under a second. + +use serde::Serialize; +use std::process::Stdio; +use tauri::{AppHandle, Emitter}; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; + +#[derive(Clone, Serialize)] +struct CmdOutputEvent { + session_id: String, + line: String, +} + +/// Runs `program args...`, emitting one `cmd-output` event per line of +/// stdout/stderr (tagged with `session_id` so the frontend can route +/// concurrent streams), and resolves to whether it exited successfully — +/// the frontend awaits this call directly rather than needing a second +/// "done" event. +#[tauri::command] +pub async fn run_streaming_command(app: AppHandle, session_id: String, program: String, args: Vec) -> bool { + let child = Command::new(&program).args(&args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn(); + let mut child = match child { + Ok(c) => c, + Err(e) => { + let _ = app.emit("cmd-output", CmdOutputEvent { session_id, line: format!("Error: {e}") }); + return false; + } + }; + + let stdout = child.stdout.take().expect("stdout piped"); + let stderr = child.stderr.take().expect("stderr piped"); + + let read_stdout = async { + let mut lines = BufReader::new(stdout).lines(); + while let Ok(Some(line)) = lines.next_line().await { + let _ = app.emit("cmd-output", CmdOutputEvent { session_id: session_id.clone(), line }); + } + }; + let stderr_app = app.clone(); + let stderr_session = session_id.clone(); + let read_stderr = async move { + let mut lines = BufReader::new(stderr).lines(); + while let Ok(Some(line)) = lines.next_line().await { + let _ = stderr_app.emit("cmd-output", CmdOutputEvent { session_id: stderr_session.clone(), line }); + } + }; + + tokio::join!(read_stdout, read_stderr); + child.wait().await.map(|s| s.success()).unwrap_or(false) +} diff --git a/src-tauri/src/commands/theme.rs b/src-tauri/src/commands/theme.rs new file mode 100644 index 0000000..6aaf367 --- /dev/null +++ b/src-tauri/src/commands/theme.rs @@ -0,0 +1,74 @@ +//! Bridges `bread-theme`'s pywal-derived palette into the webview as CSS +//! custom properties, and keeps it live: `bread-theme`'s generator rewrites +//! its shared stylesheet with a temp-then-rename (atomic replace), which +//! kills a direct file watch (inotify reports DELETE_SELF and never +//! re-arms) — so this watches the *parent directory* and filters by +//! filename instead, the same strategy `bread_theme::gtk::watch_theme_file` +//! uses for the GTK apps. + +use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use tauri::{AppHandle, Emitter, Manager}; + +/// Initial theme fetch — called once by the frontend at startup. +#[tauri::command] +pub fn get_theme_css() -> String { + render_theme_css() +} + +fn render_theme_css() -> String { + let palette = bread_theme::load_palette(); + format!( + "{}\n{}", + bread_theme::css_custom_properties(&palette), + bread_theme::css_tokens(), + ) +} + +/// Start watching the shared theme file and emit `theme-changed` with the +/// freshly rendered CSS whenever it's rewritten (palette change from a new +/// wallpaper, or a manual `bread-theme reload`). Call once from `setup`. +pub fn watch_and_emit(app: &AppHandle) { + let target = bread_theme::shared_css_path(); + let Some(dir) = target.parent() else { return }; + let _ = std::fs::create_dir_all(dir); + + let app_for_watcher = app.clone(); + let target_for_watcher = target.clone(); + let mut watcher = match RecommendedWatcher::new( + move |res: notify::Result| { + let Ok(event) = res else { return }; + // Rewrites land as CREATE/MODIFY/RENAME events touching the + // stylesheet's path specifically — the directory watch also + // sees unrelated siblings, so filter to the target file. + let touches_target = matches!( + event.kind, + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) + ) && event.paths.iter().any(|p| p == &target_for_watcher); + if touches_target { + let _ = app_for_watcher.emit("theme-changed", render_theme_css()); + } + }, + notify::Config::default(), + ) { + Ok(w) => w, + Err(e) => { + tracing_or_eprintln(&format!("theme watcher: failed to create: {e}")); + return; + } + }; + + if let Err(e) = watcher.watch(dir, RecursiveMode::NonRecursive) { + tracing_or_eprintln(&format!("theme watcher: failed to watch {}: {e}", dir.display())); + return; + } + + // Leaked to stay alive for the process lifetime — this app has exactly + // one theme watcher, created once at startup, never torn down. + app.manage(WatcherHandle(watcher)); +} + +struct WatcherHandle(RecommendedWatcher); + +fn tracing_or_eprintln(msg: &str) { + eprintln!("{msg}"); +} diff --git a/src-tauri/src/commands/users.rs b/src-tauri/src/commands/users.rs new file mode 100644 index 0000000..d26ff59 --- /dev/null +++ b/src-tauri/src/commands/users.rs @@ -0,0 +1,104 @@ +//! User account management — add/remove users, change passwords. Everything +//! here needs root (useradd/userdel/chpasswd), so every action goes through +//! `pkexec`. + +use serde::Serialize; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +#[derive(Serialize, Clone)] +pub struct Account { + username: String, + full_name: String, +} + +fn list_accounts() -> Vec { + let Ok(text) = std::fs::read_to_string("/etc/passwd") else { + return Vec::new(); + }; + text.lines() + .filter_map(|line| { + let f: Vec<&str> = line.split(':').collect(); + if f.len() < 7 { + return None; + } + let uid: u32 = f[2].parse().ok()?; + let shell = f[6]; + // Real human accounts: normal UID range, a real login shell + // (excludes system/service accounts like greeter, avahi, etc). + if !(1000..60000).contains(&uid) || shell.ends_with("nologin") || shell.ends_with("/false") { + return None; + } + Some(Account { username: f[0].to_string(), full_name: f[4].split(',').next().unwrap_or("").to_string() }) + }) + .collect() +} + +#[derive(Serialize)] +pub struct UsersInfo { + accounts: Vec, + current_user: String, +} + +#[tauri::command] +pub fn get_users_info() -> UsersInfo { + UsersInfo { accounts: list_accounts(), current_user: std::env::var("USER").unwrap_or_default() } +} + +/// Runs a root command that needs a line of input on stdin (chpasswd's own +/// "user:password" format). `pkexec` inherits the spawning process's stdin +/// only when explicitly piped, so this pipes it through. +async fn run_with_stdin(args: &[&str], input: String) -> bool { + let Ok(mut child) = Command::new(args[0]).args(&args[1..]).stdin(std::process::Stdio::piped()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).spawn() + else { + return false; + }; + if let Some(mut stdin) = child.stdin.take() { + if stdin.write_all(input.as_bytes()).await.is_err() { + return false; + } + } + child.wait().await.map(|s| s.success()).unwrap_or(false) +} + +#[tauri::command] +pub async fn change_password(username: String, password: String) -> Result<(), String> { + let input = format!("{username}:{password}\n"); + if run_with_stdin(&["pkexec", "chpasswd"], input).await { + Ok(()) + } else { + Err("Failed to change password".into()) + } +} + +#[tauri::command] +pub async fn remove_user(username: String) -> Result<(), String> { + let output = Command::new("pkexec").args(["userdel", "-r", &username]).output().await.map_err(|e| e.to_string())?; + if output.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +#[tauri::command] +pub async fn add_user(username: String, full_name: String, password: String) -> Result<(), String> { + let username = username.trim().to_string(); + let mut useradd_args = vec!["pkexec".to_string(), "useradd".to_string(), "-m".to_string(), "-s".to_string(), "/bin/bash".to_string()]; + if !full_name.trim().is_empty() { + useradd_args.push("-c".to_string()); + useradd_args.push(full_name.trim().to_string()); + } + useradd_args.push(username.clone()); + let args_ref: Vec<&str> = useradd_args.iter().map(String::as_str).collect(); + let output = Command::new(args_ref[0]).args(&args_ref[1..]).output().await.map_err(|e| e.to_string())?; + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).trim().to_string()); + } + let input = format!("{username}:{password}\n"); + if run_with_stdin(&["pkexec", "chpasswd"], input).await { + Ok(()) + } else { + Err("User created, but setting the password failed.".into()) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..c057bfa --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,92 @@ +mod commands; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_dialog::init()) + .setup(|app| { + commands::theme::watch_and_emit(app.handle()); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + commands::theme::get_theme_css, + commands::about::get_system_info, + commands::about::set_hostname, + commands::service::get_service_status, + commands::service::service_action, + commands::service::open_logs, + commands::breadclip::open_breadclip, + commands::bread::get_bread_config, + commands::bread::save_bread_config, + commands::breadpad::get_breadpad_config, + commands::breadpad::save_breadpad_config, + commands::breadsearch::get_breadsearch_config, + commands::breadsearch::save_breadsearch_config, + commands::breadbar::get_breadbar_css, + commands::breadbar::save_breadbar_css, + commands::breadbar::get_breadbar_style, + commands::breadbar::save_breadbar_style, + commands::breadbox::get_breadbox_contexts, + commands::breadbox::save_breadbox_contexts, + commands::breadcrumbs::get_breadcrumbs_config, + commands::breadcrumbs::save_breadcrumbs_config, + commands::breadpaper::get_current_wallpaper, + commands::breadpaper::set_wallpaper, + commands::breadpaper::list_wallpaper_library, + commands::breadpaper::wallpaper_library_dir_display, + commands::appearance::get_appearance, + commands::appearance::save_appearance, + commands::autostart::get_autostart_entries, + commands::autostart::save_autostart_entries, + commands::hyprland::get_live_monitors, + commands::hyprland::get_monitor_rules, + commands::hyprland::save_monitor_rules, + commands::hyprland::open_hyprland_conf, + commands::hyprland::open_keybinds_viewer, + commands::keybinds::get_keybinds, + commands::keybinds::save_keybinds, + commands::sound::get_sound_section, + commands::sound::set_default_sound_device, + commands::sound::set_sound_volume, + commands::sound::set_sound_mute, + commands::sound::open_mixer, + commands::datetime::get_datetime_info, + commands::datetime::set_timezone, + commands::datetime::set_ntp_enabled, + commands::power::get_power_info, + commands::power::set_brightness, + commands::power::set_charge_threshold, + commands::network::get_network_info, + commands::network::set_wifi_radio, + commands::network::scan_wifi, + commands::network::connect_wifi, + commands::network::open_connection_editor, + commands::bluetooth::get_adapter_powered, + commands::bluetooth::set_adapter_powered, + commands::bluetooth::get_paired_devices, + commands::bluetooth::scan_bluetooth, + commands::bluetooth::bt_connect, + commands::bluetooth::bt_disconnect, + commands::bluetooth::bt_forget, + commands::bluetooth::bt_pair, + commands::firewall::get_firewall_status, + commands::firewall::set_firewall_enabled, + commands::firewall::add_firewall_rule, + commands::firewall::remove_firewall_rule, + commands::users::get_users_info, + commands::users::change_password, + commands::users::remove_user, + commands::users::add_user, + commands::streaming::run_streaming_command, + commands::packages::get_installed_packages, + commands::aur::search_aur, + commands::aur::install_aur_package, + commands::firmware::get_updatable_firmware, + commands::snapshots::get_snapshots, + commands::snapshots::delete_snapshot, + commands::snapshots::reboot_system, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..d138433 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + bos_settings_lib::run() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..b4c5066 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "BOS Settings", + "version": "0.8.0", + "identifier": "dev.breadway.bos-settings", + "build": { + "beforeDevCommand": "npm --prefix frontend run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm --prefix frontend run build", + "frontendDist": "../frontend/build" + }, + "app": { + "windows": [ + { + "title": "BOS Settings", + "width": 960, + "height": 640, + "decorations": false, + "backgroundColor": "#0c0c0c" + } + ], + "security": { + "csp": null, + "assetProtocol": { + "enable": true, + "scope": ["$HOME/Pictures/Backgrounds/**"] + } + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 1c28518..0000000 --- a/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -mod config; -mod theme; -mod ui; - -use gtk4::gio::ApplicationFlags; -use gtk4::prelude::*; - -fn main() { - let app = gtk4::Application::builder() - .application_id("com.breadway.bos-settings") - // HANDLES_COMMAND_LINE: without it, GApplication validates argv - // against registered GOptionEntries (none here) and would reject - // `--page ` with "unknown option" before `activate` ever runs. - .flags(ApplicationFlags::HANDLES_COMMAND_LINE) - .build(); - - app.connect_command_line(|app, cmdline| { - let page = parse_page_arg(&cmdline.arguments()); - ui::window::build_ui(app, page); - glib::ExitCode::SUCCESS - }); - - app.run(); -} - -/// Best-effort: like any non-command-line-aware launch of an already-running -/// GApplication, this is only seen by the launch that becomes primary — a -/// `--page` while bos-settings is already open just refocuses the existing -/// window on whatever page it was already showing. -fn parse_page_arg(args: &[std::ffi::OsString]) -> Option { - let mut it = args.iter().skip(1); - while let Some(arg) = it.next() { - if arg == "--page" { - return it.next().and_then(|s| s.to_str()).map(str::to_string); - } - } - None -} diff --git a/src/theme.rs b/src/theme.rs deleted file mode 100644 index 5b57ca8..0000000 --- a/src/theme.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Theming for bos-settings. -//! -//! bos-settings deliberately owns almost no styling: it loads the ecosystem's -//! shared stylesheet (the same one breadbar/breadbox/breadpad use, generated by -//! `bread-theme` from the pywal palette) and adds only the few layout rules -//! specific to this app's sidebar + content shell. This keeps it visually -//! identical to the rest of the bread desktop and live-recolouring for free. - -use gtk4::CssProvider; -use std::cell::RefCell; - -// App-specific layout only — everything visual (colours, buttons, entries, -// switches, sidebar/row styling, cards, scrollbars) comes from the shared sheet. -const APP_CSS: &str = "\ -.view-content { padding: 24px; }\n\ -.view-content > label.title { margin-bottom: 16px; }\n\ -/* Sidebar row sub-labels (the underlying binary/config name under a row's \ - human label) — smaller than the shared sheet's default dim-label size. */\n\ -.caption { font-size: 11px; }\n\ -/* bread-theme's shared sheet only overrides background-color on \ - suggested/destructive buttons, not background-image — so Adwaita's \ - built-in gradient (bright blue/red) paints over our flat colour \ - underneath it. Belongs upstream in bread-theme; patched locally here \ - until that's worth its own release. */\n\ -button.suggested-action, button.destructive-action { background-image: none; }\n\ -/* Same upstream gap for scale (Sound's volume sliders) — the shared sheet \ - has no `scale` rules at all, so they render in Adwaita's default blue. */\n\ -scale trough { background-color: alpha(@on-surface, 0.15); border-radius: 999px; min-height: 6px; background-image: none; }\n\ -scale highlight { background-color: @accent; background-image: none; border-radius: 999px; }\n\ -scale slider { background-color: @on-surface; border-radius: 999px; }\n\ -/* Destructive actions must not follow the wallpaper palette: @red is \ - pywal's color1, which can land on gold/yellow/anything depending on the \ - wallpaper (it did, this session) — making Delete/Remove look like a \ - primary action instead of a dangerous one. Fixed regardless of palette. */\n\ -button.destructive-action { background-color: #c0392b; color: #ffffff; }\n\ -button.destructive-action:hover { background-color: #d64535; }\n\ -/* Adwaita's default switch slider (the knob) carries a box-shadow used for \ - its 3D bevel look — bread-theme's override only sets background-color, \ - so that shadow still renders as a pale ring around the knob on top of \ - our flat colour. */\n\ -switch slider { box-shadow: none; outline: none; border: none; background-image: none; }\n\ -switch { box-shadow: none; outline: none; border: none; background-image: none; }\n\ -"; - -thread_local! { - static APP_PROVIDER: RefCell> = const { RefCell::new(None) }; -} - -pub fn load(_display: >k4::gdk::Display) { - // Shared ecosystem stylesheet (loads the generated file or a rendered - // fallback, and live-reloads when the palette changes). - bread_theme::gtk::apply_shared(); - - // bos-settings layout, layered on top at APPLICATION priority. - APP_PROVIDER.with(|cell| bread_theme::gtk::apply_css(APP_CSS, cell)); -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index 3867fcd..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod sidebar; -pub mod views; -pub mod widgets; -pub mod window; diff --git a/src/ui/sidebar.rs b/src/ui/sidebar.rs deleted file mode 100644 index b4859a7..0000000 --- a/src/ui/sidebar.rs +++ /dev/null @@ -1,157 +0,0 @@ -use gtk4::prelude::*; -use gtk4::{Box as GBox, Image, Label, ListBox, ListBoxRow, Orientation}; - -pub struct SidebarItem { - /// Must match the `Stack` page name registered in `window.rs`. - pub id: &'static str, - pub label: &'static str, - /// Dim second line — the underlying binary/config name, for items whose - /// human label doesn't already make that obvious. - pub sublabel: Option<&'static str>, - /// A `-symbolic` icon name from the system icon theme (Papirus-Dark ships - /// the full Adwaita-compatible symbolic set this app relies on). - pub icon: &'static str, -} - -const fn item(id: &'static str, label: &'static str, icon: &'static str) -> SidebarItem { - SidebarItem { id, label, sublabel: None, icon } -} - -const fn item_sub( - id: &'static str, - label: &'static str, - sublabel: &'static str, - icon: &'static str, -) -> SidebarItem { - SidebarItem { id, label, sublabel: Some(sublabel), icon } -} - -// Grouped by task, not by "app vs system internals" — a user thinks "I want -// to change my Wi-Fi" or "I want to change my wallpaper", not "which of -// these is a bread-ecosystem app". breadcrumbs (Wi-Fi profiles) moves out of -// the old "Apps" bucket into System for the same reason. -pub const SYSTEM_ITEMS: &[SidebarItem] = &[ - item("network", "Network", "network-wireless-symbolic"), - item_sub("breadcrumbs", "Wi-Fi Profiles", "breadcrumbs", "network-workgroup-symbolic"), - item("bluetooth", "Bluetooth", "bluetooth-symbolic"), - item("firewall", "Firewall", "security-high-symbolic"), - item("sound", "Sound", "audio-volume-high-symbolic"), - item("power", "Power", "battery-good-symbolic"), - item("datetime", "Date & Time", "preferences-system-time-symbolic"), - item_sub("hyprland", "Display", "monitors.json", "video-display-symbolic"), - item_sub("keybinds", "Keybinds", "binds.json", "input-keyboard-symbolic"), - item_sub("autostart", "Startup Apps", "autostart.json", "system-run-symbolic"), - item("users", "Users", "system-users-symbolic"), -]; - -pub const PERSONALIZATION_ITEMS: &[SidebarItem] = &[ - item_sub("appearance", "Appearance", "settings.json", "applications-graphics-symbolic"), - item_sub("breadpaper", "Wallpaper", "breadpaper", "preferences-desktop-wallpaper-symbolic"), - item_sub("breadbar", "Bar", "breadbar", "view-grid-symbolic"), - item_sub("breadbox", "Launcher", "breadbox", "view-app-grid-symbolic"), - item_sub("breadclip", "Clipboard", "breadclipd", "edit-paste-symbolic"), - item_sub("breadpad", "Notes", "breadpad", "text-editor-symbolic"), - item_sub("breadsearch", "File Search", "breadsearch", "system-search-symbolic"), - item_sub("bread", "Daemon", "breadd", "applications-system-symbolic"), -]; - -pub const MAINTENANCE_ITEMS: &[SidebarItem] = &[ - item("packages", "Packages", "package-x-generic-symbolic"), - item("aur", "AUR", "system-search-symbolic"), - item("firmware", "Firmware", "software-update-available-symbolic"), - item("snapshots", "Snapshots", "document-open-recent-symbolic"), -]; - -pub const ABOUT_ITEMS: &[SidebarItem] = &[item("about", "About", "help-about-symbolic")]; - -/// `default_id` must match whatever page `window.rs` sets as the `Stack`'s -/// initial visible child — previously these were two independent hardcoded -/// "about" literals in different files with no link between them, so -/// changing one without the other silently desynced the sidebar highlight -/// from the actually-displayed page. -pub fn build(default_id: &str) -> (GBox, ListBox) { - let vbox = GBox::new(Orientation::Vertical, 0); - vbox.add_css_class("sidebar"); - vbox.set_width_request(210); - - let list = ListBox::new(); - list.set_selection_mode(gtk4::SelectionMode::Single); - list.add_css_class("sidebar"); - - append_section(&list, "System", SYSTEM_ITEMS); - append_section(&list, "Personalization", PERSONALIZATION_ITEMS); - append_section(&list, "Maintenance", MAINTENANCE_ITEMS); - append_section(&list, None, ABOUT_ITEMS); - - let mut i = 0; - loop { - match list.row_at_index(i) { - None => break, - Some(row) if row.widget_name() == default_id => { - list.select_row(Some(&row)); - break; - } - _ => i += 1, - } - } - - // Unscrolled, this list's ~30 rows (4 sections' worth of items plus - // headers) force a minimum window height that exceeds the *logical* - // screen height at Hyprland scale factors above 1.0 (e.g. a 1200px-tall - // panel becomes 800 logical px at scale 1.5) — GTK can't shrink below a - // widget's natural size, so the window (and anything below the fold, - // like a panel's Save button) gets clipped by the compositor with no way - // to reach it. Scrolling the nav list independently of the content pane - // lets the window's minimum height drop to whatever a single row needs. - let scroll = gtk4::ScrolledWindow::new(); - scroll.set_vexpand(true); - scroll.set_hscrollbar_policy(gtk4::PolicyType::Never); - scroll.set_child(Some(&list)); - vbox.append(&scroll); - (vbox, list) -} - -fn append_section(list: &ListBox, title: impl Into>, items: &[SidebarItem]) { - if let Some(title) = title.into() { - let header_row = ListBoxRow::new(); - header_row.set_selectable(false); - header_row.set_activatable(false); - let header_lbl = Label::new(Some(title)); - header_lbl.add_css_class("section-header"); - header_lbl.set_xalign(0.0); - header_row.set_child(Some(&header_lbl)); - list.append(&header_row); - } - - for entry in items { - let row = ListBoxRow::new(); - row.set_widget_name(entry.id); - - let hbox = GBox::new(Orientation::Horizontal, 10); - hbox.set_margin_top(4); - hbox.set_margin_bottom(4); - - let icon = Image::from_icon_name(entry.icon); - icon.set_pixel_size(16); - hbox.append(&icon); - - let labels = GBox::new(Orientation::Vertical, 0); - let lbl = Label::new(Some(entry.label)); - lbl.set_xalign(0.0); - labels.append(&lbl); - if let Some(sub) = entry.sublabel { - let sub_lbl = Label::new(Some(sub)); - sub_lbl.add_css_class("dim-label"); - sub_lbl.set_xalign(0.0); - // Match the sidebar's smaller "section-header" scale rather than - // the shared sheet's default dim-label size, so it reads as a - // caption under the row label, not a second full-size label. - sub_lbl.add_css_class("caption"); - labels.append(&sub_lbl); - } - hbox.append(&labels); - - row.set_child(Some(&hbox)); - list.append(&row); - } -} diff --git a/src/ui/views/appearance.rs b/src/ui/views/appearance.rs deleted file mode 100644 index 419ac94..0000000 --- a/src/ui/views/appearance.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! hypr/settings.json — Hyprland gaps/borders/blur/shadow/input, read by -//! `scripts/ui/settings.lua` on the Hyprland side (see hyprland.lua). Unlike -//! the TOML-backed views, this has no comments to preserve, so it's modeled -//! as a plain typed struct (round-tripped whole) rather than the -//! `toml_edit`/`Doc` path-based editor the other panels use. -//! -//! `Default` here must stay in sync with `scripts/ui/settings.lua`'s -//! `DEFAULTS` table on the Hyprland side — both independently define "what -//! BOS ships out of the box," the same duplication `binds.json`/ -//! `content/keybinds.rs` already accept for the same reason (two different -//! languages/processes reading the same file, neither able to import the -//! other's defaults). - -use std::cell::RefCell; -use std::rc::Rc; - -use gtk4::prelude::*; -use gtk4::{ - Adjustment, Box as GBox, ColorDialog, ColorDialogButton, DropDown, Entry, Expression, Orientation, - SpinButton, StringList, Switch, -}; -use serde::{Deserialize, Serialize}; - -use crate::ui::widgets as w; - -#[derive(Clone, Serialize, Deserialize)] -#[serde(default)] -struct Appearance { - gaps_in: i64, - gaps_out: i64, - border_size: i64, - active_border: String, - inactive_border: String, - layout: String, - resize_on_border: bool, - rounding: i64, - blur_enabled: bool, - blur_size: i64, - blur_passes: i64, - shadow_enabled: bool, - shadow_range: i64, - shadow_render_power: i64, - kb_layout: String, - follow_mouse: i64, - natural_scroll: bool, -} - -impl Default for Appearance { - fn default() -> Self { - Self { - gaps_in: 5, - gaps_out: 10, - border_size: 2, - active_border: "rgba(88c0d0ff)".to_string(), - inactive_border: "rgba(4c566aff)".to_string(), - layout: "dwindle".to_string(), - resize_on_border: true, - rounding: 8, - blur_enabled: true, - blur_size: 6, - blur_passes: 2, - shadow_enabled: true, - shadow_range: 12, - shadow_render_power: 3, - kb_layout: "us".to_string(), - follow_mouse: 1, - natural_scroll: true, - } - } -} - -fn config_path() -> std::path::PathBuf { - crate::config::config_dir().join("hypr/settings.json") -} - -/// A missing or malformed file yields defaults — same failsafe posture as -/// the Lua loader reading this same file on the Hyprland side. -fn load() -> Appearance { - std::fs::read_to_string(config_path()).ok().and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default() -} - -fn save(a: &Appearance) -> std::io::Result<()> { - let path = config_path(); - crate::config::atomic_write(&path, &serde_json::to_string_pretty(a).unwrap_or_default()) -} - -/// "rgba(RRGGBBAA)" (Hyprland's format) <-> gdk::RGBA, so the color fields -/// get a real color-picker button instead of a raw hex text field. -fn parse_hypr_color(s: &str) -> Option { - let inner = s.strip_prefix("rgba(")?.strip_suffix(')')?; - if inner.len() != 8 { - return None; - } - let r = u8::from_str_radix(&inner[0..2], 16).ok()?; - let g = u8::from_str_radix(&inner[2..4], 16).ok()?; - let b = u8::from_str_radix(&inner[4..6], 16).ok()?; - let a = u8::from_str_radix(&inner[6..8], 16).ok()?; - Some(gtk4::gdk::RGBA::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0)) -} - -fn to_hypr_color(c: >k4::gdk::RGBA) -> String { - let clamp = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8; - format!("rgba({:02x}{:02x}{:02x}{:02x})", clamp(c.red()), clamp(c.green()), clamp(c.blue()), clamp(c.alpha())) -} - -fn color_row(label: &str, model: &Rc>, get: fn(&Appearance) -> &str, set: fn(&mut Appearance, String)) -> GBox { - let cur = parse_hypr_color(get(&model.borrow())).unwrap_or(gtk4::gdk::RGBA::new(0.5, 0.5, 0.5, 1.0)); - let btn = ColorDialogButton::new(Some(ColorDialog::builder().with_alpha(true).build())); - btn.set_rgba(&cur); - let model = model.clone(); - btn.connect_rgba_notify(move |b| { - set(&mut model.borrow_mut(), to_hypr_color(&b.rgba())); - }); - w::row(label, &btn) -} - -fn spin_row(label: &str, model: &Rc>, min: f64, max: f64, get: fn(&Appearance) -> i64, set: fn(&mut Appearance, i64)) -> GBox { - let cur = get(&model.borrow()); - let adj = Adjustment::new(cur as f64, min, max, 1.0, 1.0, 0.0); - let spin = SpinButton::new(Some(&adj), 1.0, 0); - let model = model.clone(); - spin.connect_value_changed(move |s| set(&mut model.borrow_mut(), s.value() as i64)); - w::row(label, &spin) -} - -fn switch_row(label: &str, model: &Rc>, get: fn(&Appearance) -> bool, set: fn(&mut Appearance, bool)) -> GBox { - let sw = Switch::new(); - sw.set_active(get(&model.borrow())); - let model = model.clone(); - sw.connect_active_notify(move |s| set(&mut model.borrow_mut(), s.is_active())); - w::row(label, &sw) -} - -fn entry_row(label: &str, model: &Rc>, get: fn(&Appearance) -> String, set: fn(&mut Appearance, String)) -> GBox { - let entry = Entry::new(); - entry.set_text(&get(&model.borrow())); - entry.set_hexpand(true); - entry.set_width_chars(16); - let model = model.clone(); - entry.connect_changed(move |e| set(&mut model.borrow_mut(), e.text().to_string())); - w::row(label, &entry) -} - -fn dropdown_row(label: &str, model: &Rc>, options: &[&str], get: fn(&Appearance) -> &str, set: fn(&mut Appearance, String)) -> GBox { - let cur = get(&model.borrow()).to_string(); - let dd = DropDown::new(Some(StringList::new(options)), Expression::NONE); - dd.set_selected(options.iter().position(|o| *o == cur).unwrap_or(0) as u32); - let owned: Vec = options.iter().map(|s| s.to_string()).collect(); - let model = model.clone(); - dd.connect_selected_notify(move |dd| { - if let Some(opt) = owned.get(dd.selected() as usize) { - set(&mut model.borrow_mut(), opt.clone()); - } - }); - w::row(label, &dd) -} - -pub fn build() -> GBox { - let (outer, content) = w::view_scaffold("Appearance"); - - content.append(&w::hint( - "Gaps, borders, blur, and input feel for Hyprland — the same \ - settings.json the compositor itself reads at login.", - )); - - let model = Rc::new(RefCell::new(load())); - - content.append(&w::section("Layout & borders")); - content.append(&spin_row("Gaps (inner)", &model, 0.0, 50.0, |a| a.gaps_in, |a, v| a.gaps_in = v)); - content.append(&spin_row("Gaps (outer)", &model, 0.0, 50.0, |a| a.gaps_out, |a, v| a.gaps_out = v)); - content.append(&spin_row("Border width", &model, 0.0, 10.0, |a| a.border_size, |a, v| a.border_size = v)); - content.append(&color_row("Active border color", &model, |a| &a.active_border, |a, v| a.active_border = v)); - content.append(&color_row("Inactive border color", &model, |a| &a.inactive_border, |a, v| a.inactive_border = v)); - content.append(&dropdown_row("Tiling layout", &model, &["dwindle", "master"], |a| &a.layout, |a, v| a.layout = v)); - content.append(&switch_row("Resize by dragging borders", &model, |a| a.resize_on_border, |a, v| a.resize_on_border = v)); - - content.append(&w::section("Effects")); - content.append(&spin_row("Corner rounding", &model, 0.0, 30.0, |a| a.rounding, |a, v| a.rounding = v)); - content.append(&switch_row("Blur", &model, |a| a.blur_enabled, |a, v| a.blur_enabled = v)); - content.append(&spin_row("Blur size", &model, 0.0, 20.0, |a| a.blur_size, |a, v| a.blur_size = v)); - content.append(&spin_row("Blur passes", &model, 1.0, 5.0, |a| a.blur_passes, |a, v| a.blur_passes = v)); - content.append(&switch_row("Window shadows", &model, |a| a.shadow_enabled, |a, v| a.shadow_enabled = v)); - content.append(&spin_row("Shadow range", &model, 0.0, 40.0, |a| a.shadow_range, |a, v| a.shadow_range = v)); - content.append(&spin_row("Shadow render power", &model, 1.0, 4.0, |a| a.shadow_render_power, |a, v| a.shadow_render_power = v)); - - content.append(&w::section("Input")); - content.append(&entry_row("Keyboard layout", &model, |a| a.kb_layout.clone(), |a, v| a.kb_layout = v)); - let follow_mouse_row = spin_row("Focus-follows-mouse mode", &model, 0.0, 3.0, |a| a.follow_mouse, |a, v| a.follow_mouse = v); - content.append(&follow_mouse_row); - content.append(&w::hint("0-3 — see the Hyprland wiki's follow_mouse setting for exact behavior of each value.")); - content.append(&switch_row("Natural scrolling (touchpad)", &model, |a| a.natural_scroll, |a, v| a.natural_scroll = v)); - - content.append(&w::hint("Applies on next login/Hyprland reload — this saves settings.json, it doesn't reload Hyprland live.")); - - let btn_row = GBox::new(Orientation::Horizontal, 12); - btn_row.set_margin_top(16); - let save_btn = gtk4::Button::with_label("Save"); - save_btn.add_css_class("suggested-action"); - let status = gtk4::Label::new(None); - status.add_css_class("dim-label"); - { - let model = model.clone(); - let status = status.clone(); - save_btn.connect_clicked(move |_| match save(&model.borrow()) { - Ok(()) => { - status.set_text("Saved"); - let lbl = status.clone(); - glib::timeout_add_seconds_local(3, move || { - lbl.set_text(""); - glib::ControlFlow::Break - }); - } - Err(e) => status.set_text(&format!("Error: {e}")), - }); - } - btn_row.append(&save_btn); - btn_row.append(&status); - outer.append(&btn_row); - - outer -} diff --git a/src/ui/views/aur.rs b/src/ui/views/aur.rs deleted file mode 100644 index b526224..0000000 --- a/src/ui/views/aur.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! AUR search via yay — graphical discovery for the wider AUR beyond -//! bakery's bread ecosystem and [breadway]'s own republished packages. -//! -//! Search and browsing are fully graphical; actually installing a package -//! opens a terminal running `yay -S ` instead of a silent `--noconfirm` -//! install. That's deliberate, not a shortcut we didn't get around to: -//! AUR packages run arbitrary maintainer-supplied build scripts, and yay's -//! interactive PKGBUILD diff review (plus the sudo password prompt) is the -//! actual safety mechanism against a malicious/compromised AUR package — -//! automating it away in the name of "no terminal" would remove the one -//! step that exists to catch that. - -use gtk4::prelude::*; -use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow}; -use std::process::Command; - -use crate::ui::widgets as w; - -#[derive(Clone)] -struct AurResult { - name: String, - version: String, - description: String, -} - -fn search(query: &str) -> Vec { - let Ok(output) = Command::new("yay").args(["-Ss", "--aur", query]).output() else { - return Vec::new(); - }; - let text = String::from_utf8_lossy(&output.stdout); - let mut results = Vec::new(); - let mut lines = text.lines().peekable(); - while let Some(header) = lines.next() { - // "aur/name version (+votes score) [Orphaned]" — name/version are - // always the first two whitespace-separated fields after the repo/. - let Some(rest) = header.strip_prefix("aur/") else { continue }; - let mut parts = rest.split_whitespace(); - let Some(name) = parts.next() else { continue }; - let version = parts.next().unwrap_or("").to_string(); - let description = lines.next().unwrap_or("").trim().to_string(); - results.push(AurResult { name: name.to_string(), version, description }); - } - results -} - -fn install_in_terminal(pkg: &str) { - let _ = Command::new("kitty").args(["-e", "yay", "-S", pkg]).spawn(); -} - -pub fn build() -> GBox { - let (outer, content) = w::view_scaffold("AUR"); - content.append(&w::hint( - "Search the Arch User Repository via yay. Installing opens a \ - terminal — AUR packages run arbitrary build scripts, and reviewing \ - what yay is about to do (and entering your password) is a real \ - safety step, not just a formality.", - )); - - let search_row = GBox::new(Orientation::Horizontal, 8); - let search_entry = Entry::new(); - search_entry.set_hexpand(true); - search_entry.set_placeholder_text(Some("Search the AUR…")); - let search_btn = Button::with_label("Search"); - search_btn.add_css_class("suggested-action"); - search_row.append(&search_entry); - search_row.append(&search_btn); - content.append(&search_row); - - let status = w::hint("Search for a package to see results here."); - content.append(&status); - - let list = ListBox::new(); - list.set_selection_mode(gtk4::SelectionMode::None); - let scroll = ScrolledWindow::new(); - scroll.set_vexpand(true); - scroll.set_min_content_height(320); - scroll.set_child(Some(&list)); - content.append(&scroll); - - let run_search = { - let list = list.clone(); - let status = status.clone(); - let search_entry = search_entry.clone(); - move |btn: Option