diff --git a/Cargo.lock b/Cargo.lock index 81e80c3..1222bc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,4682 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.4", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width 0.1.14", + "yansi-term", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[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.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[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 1.1.2", + "slab", + "windows-sys 0.61.0", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-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 1.1.2", +] + +[[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", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.2", + "signal-hook-registry", + "slab", + "windows-sys 0.61.0", +] + +[[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.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "annotate-snippets", + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", +] + +[[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.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.2", +] + +[[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 = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.4", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width 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 = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17" +dependencies = [ + "bitflags 1.3.2", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "cpal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f" +dependencies = [ + "alsa", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[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-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[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", + "windows-sys 0.61.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.1", + "libc", + "objc2 0.6.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bdf37f8d5bd9aa7f753573fdda9cf7343afa73dd28d7bfe9593bd9798fc07e" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d1c15e7bd136b309bd3487e6ffe5f668b354cd9768636a836dd738ac90eb0b" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "windows-sys 0.59.0", + "winit", +] + +[[package]] +name = "egui" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5d0306cd61ca75e29682926d71f2390160247f135965242e904a636f51c0dc" +dependencies = [ + "ahash", + "bitflags 2.9.4", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "egui-wgpu" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12eca13293f8eba27a32aaaa1c765bfbf31acd43e8d30d5881dcbe5e99ca0c7" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95d0a91f9cb0dc2e732d49c2d521ac8948e1f0b758f306fb7b14d6f5db3927f" +dependencies = [ + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7037813341727937f9e22f78d912f3e29bc3c46e2f40a9e82bb51cbf5e4cfb" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "egui_material_icons" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaf2524c06db70ec9154db2de26b43ea278cd304547c0ee9339d5a46a4d6a04" +dependencies = [ + "egui", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "emath" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45fd7bc25f769a3c198fe1cf183124bf4de3bd62ef7b4f1eaf6b08711a3af8db" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[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", +] + +[[package]] +name = "epaint" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63adcea970b7a13094fe97a36ab9307c35a750f9e24bf00bb7ef3de573e0fddb" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "rayon", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1537accc50c9cab5a272c39300bdd0dd5dca210f6e5e8d70be048df9596e7ca2" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[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.0", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[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 = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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", +] + +[[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 = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" +dependencies = [ + "rustix 1.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.9.4", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[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 = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.0", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.4", + "libc", + "redox_syscall 0.5.17", +] + +[[package]] +name = "libspa" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" +dependencies = [ + "bitflags 2.9.4", + "cc", + "convert_case", + "cookie-factory", + "libc", + "libspa-sys", + "nix 0.27.1", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" +dependencies = [ + "bindgen", + "cc", + "system-deps", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[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.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.4", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.15.5", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "strum", + "thiserror 2.0.16", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.4", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.1", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07" +dependencies = [ + "bitflags 2.9.4", + "libc", + "objc2 0.6.2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82" +dependencies = [ + "dispatch2", + "objc2 0.6.2", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[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 = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.17", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pipewire" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" +dependencies = [ + "anyhow", + "bitflags 2.9.4", + "libc", + "libspa", + "libspa-sys", + "nix 0.27.1", + "once_cell", + "pipewire-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "pipewire-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "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 1.1.2", + "windows-sys 0.61.0", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + [[package]] name = "pwsp" version = "1.0.0" +dependencies = [ + "async-trait", + "clap", + "dirs", + "eframe", + "egui", + "egui_material_icons", + "futures", + "pipewire", + "rfd", + "rodio", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "pxfm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +dependencies = [ + "num-traits", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +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 = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.16", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.1", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rodio" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40ecf59e742e03336be6a3d53755e789fd05a059fa22dfa0ed624722319e183" +dependencies = [ + "cpal", + "dasp_sample", + "num-rational", + "symphonia", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.9.4", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-caf", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-caf" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43c99c696a388295a29fe71b133079f5d8b18041cf734c5459c35ad9097af50" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[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 = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +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", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +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", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.2", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.9.4", + "rustix 1.1.2", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.4", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" +dependencies = [ + "rustix 1.1.2", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.2", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +dependencies = [ + "arrayvec", + "bitflags 2.9.4", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "js-sys", + "log", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.9.4", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.16", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" +dependencies = [ + "bitflags 2.9.4", + "cfg_aliases", + "libloading", + "log", + "naga", + "parking_lot", + "portable-atomic", + "raw-window-handle", + "renderdoc-sys", + "thiserror 2.0.16", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.16", + "web-sys", +] + +[[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.0", +] + +[[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 = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.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.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + +[[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.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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.4", + "block2 0.5.1", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.2", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.4", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +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", + "nix 0.30.1", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.60.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 1d4e3cb..56566ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,44 @@ name = "pwsp" version = "1.0.0" edition = "2024" authors = ["arabian"] -description = "A simple soundpad application written in Rust using egui for the GUI, pipewire for audio input/output, and rodio for audio decoding." +description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients." readme = "README.md" -homepage = "https://github.com/arabianq/pipewire-soundpad" +homepage = "https://pwsp.arabianq.ru" repository = "https://github.com/arabianq/pipewire-soundpad" license = "MIT" -keywords = ["soundpad", "pipewire"] +keywords = ["soundpad", "pipewire", "linux", "cli", "gui"] [dependencies] +tokio = { version = "1.47.1", features = ["full"] } +futures = { version = "0.3.31", features = ["thread-pool"] } +async-trait = "0.1.89" + +serde = { version = "1.0.226", features = ["derive"] } +serde_json = "1.0.145" + +clap = { version = "4.5.48", default-features = false, features = ["std", "suggestions", "help", "usage", "error-context", "derive"] } +dirs = "6.0.0" + +rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] } +pipewire = "0.8.0" +rfd = "0.15.4" + +egui = { version = "0.32.3", default-features = false, features = ["default_fonts", "rayon"] } +eframe = { version = "0.32.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] } +egui_material_icons = "0.4.0" + +[[bin]] +name = "pwsp-daemon" +path = "src/bin/daemon.rs" + +[[bin]] +name = "pwsp-cli" +path = "src/bin/cli.rs" + +[[bin]] +name = "pwsp-gui" +path = "src/main.rs" [profile.release] strip = true @@ -19,3 +48,23 @@ lto = true codegen-units = 1 opt-level = "z" panic = "abort" + +[package.metadata.generate-rpm] +assets = [ + { source = "target/release/pwsp-daemon", dest = "/usr/bin/pwsp-daemon", mode = "755" }, + { source = "target/release/pwsp-cli", dest = "/usr/bin/pwsp-cli", mode = "755" }, + { source = "target/release/pwsp-gui", dest = "/usr/bin/pwsp-gui", mode = "755" }, + { source = "pwsp-gui.desktop", dest = "/usr/share/applications/pwsp.desktop", mode = "644" }, + { source = "icon.png", dest = "/usr/share/icons/hicolor/256x256/apps/pwsp.png", mode = "644" }, + { source = "pwsp-daemon.service", dest = "/usr/lib/systemd/user/pwsp-daemon.service", mode = "644" }, +] + +[package.metadata.deb] +assets = [ + ["target/release/pwsp-daemon", "usr/bin/", "755"], + ["target/release/pwsp-cli", "usr/bin/", "755"], + ["target/release/pwsp-gui", "usr/bin/", "755"], + ["assets/pwsp-gui.desktop", "usr/share/applications/pwsp.desktop", "644"], + ["assets/icon.png", "usr/share/icons/hicolor/256x256/apps/pwsp.png", "644"], + ["assets/pwsp-daemon.service", "usr/lib/systemd/user/pwsp-daemon.service", "644"], +] \ No newline at end of file diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..5552913 Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/pwsp-daemon.service b/assets/pwsp-daemon.service new file mode 100644 index 0000000..675f214 --- /dev/null +++ b/assets/pwsp-daemon.service @@ -0,0 +1,10 @@ +[Unit] +Description=Pipewire Soundpad Daemon + +[Service] +ExecStart=/usr/bin/pwsp-daemon +Restart=no +RuntimeDirectory=pwsp + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/pwsp.desktop b/assets/pwsp-gui.desktop similarity index 50% rename from pwsp.desktop rename to assets/pwsp-gui.desktop index 4d711cf..5883cc7 100644 --- a/pwsp.desktop +++ b/assets/pwsp-gui.desktop @@ -1,7 +1,8 @@ [Desktop Entry] Name=PWSP (Soundpad) -Exec=pwsp %u -Icon= +Comment=Let's you play audio files through you microphone +Exec=pwsp-gui %u +Icon=pwsp Terminal=false Type=Application Categories=Audio \ No newline at end of file diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..1068a9f Binary files /dev/null and b/assets/screenshot.png differ diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index 41b8eec..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..98e6909 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd "$(dirname "$(realpath "$0")")/.." || exit + +cargo build --release \ No newline at end of file diff --git a/scripts/build_all.sh b/scripts/build_all.sh new file mode 100755 index 0000000..dd335fa --- /dev/null +++ b/scripts/build_all.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +cd "$(dirname "$(realpath "$0")")/.." || exit + +bash ./scripts/build.sh + +bash ./scripts/build_rpm.sh +bash ./scripts/build_deb.sh + +if command -v upx >/dev/null 2>&1; then + upx --best ./target/release/pwsp-gui + upx --best ./target/release/pwsp-cli + upx --best ./target/release/pwsp-daemon + upx -t ./target/release/pwsp-gui + upx -t ./target/release/pwsp-cli + upx -t ./target/release/pwsp-daemon +fi + +rm -rf ./target/for_github_release +mkdir ./target/for_github_release + +cp "$(find ./target/debian/pwsp_*_amd64.deb | sort -V | tail -n 1)" ./target/for_github_release/ +cp "$(find ./target/generate-rpm/pwsp-*.x86_64.rpm | sort -V | tail -n 1)" ./target/for_github_release/ +zip -9j ./target/for_github_release/pwsp-x86_64-linux.zip ./target/release/pwsp-gui ./target/release/pwsp-cli ./target/release/pwsp-daemon \ No newline at end of file diff --git a/scripts/build_deb.sh b/scripts/build_deb.sh new file mode 100755 index 0000000..4cd140f --- /dev/null +++ b/scripts/build_deb.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cd "$(dirname "$(realpath "$0")")/.." || exit + +rm -rf ./target/debian + +cargo install cargo-deb +cargo-deb \ No newline at end of file diff --git a/scripts/build_rpm.sh b/scripts/build_rpm.sh new file mode 100755 index 0000000..28b03b9 --- /dev/null +++ b/scripts/build_rpm.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cd "$(dirname "$(realpath "$0")")/.." || exit + +rm -rf ./target/cargo-generate-rpm + +cargo install cargo-generate-rpm +cargo-generate-rpm \ No newline at end of file diff --git a/src/bin/cli.rs b/src/bin/cli.rs new file mode 100644 index 0000000..14db786 --- /dev/null +++ b/src/bin/cli.rs @@ -0,0 +1,113 @@ +use clap::{Parser, Subcommand}; +use pwsp::{ + types::socket::Request, + utils::daemon::{make_request, wait_for_daemon}, +}; +use std::{error::Error, path::PathBuf}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Perform an action (ping, pause, resume, stop, play) + Action { + #[clap(subcommand)] + action: Actions, + }, + /// Get information from the player (is paused, volume, position, state) + Get { + #[clap(subcommand)] + parameter: GetCommands, + }, + /// Set information in the player (volume, position) + Set { + #[clap(subcommand)] + parameter: SetCommands, + }, +} + +#[derive(Subcommand, Debug)] +enum Actions { + /// Ping the daemon + Ping, + /// Pause audio playback + Pause, + /// Resume audio playback + Resume, + /// Stop audio playback and clear the queue + Stop, + /// Play a file + Play { file_path: PathBuf }, +} + +#[derive(Subcommand, Debug)] +enum GetCommands { + /// Check if the player is paused + IsPaused, + /// Playback volume + Volume, + /// Playback position + Position, + /// Duration of the current file + Duration, + /// Player state + State, + /// Current playing file path + CurrentFilePath, + /// Current audio input + Input, + /// All audio inputs + Inputs, +} + +#[derive(Subcommand, Debug)] +enum SetCommands { + /// Playback volume + Volume { volume: f32 }, + /// Playback position + Position { position: f32 }, + /// Input + Input { id: u32 }, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cli = Cli::parse(); + + wait_for_daemon().await?; + + let request = match cli.command { + Commands::Action { action } => match action { + Actions::Ping => Request::ping(), + Actions::Pause => Request::pause(), + Actions::Resume => Request::resume(), + Actions::Stop => Request::stop(), + Actions::Play { file_path } => Request::play(file_path.to_str().unwrap()), + }, + Commands::Get { parameter } => match parameter { + GetCommands::IsPaused => Request::get_is_paused(), + GetCommands::Volume => Request::get_volume(), + GetCommands::Position => Request::get_position(), + GetCommands::Duration => Request::get_duration(), + GetCommands::State => Request::get_state(), + GetCommands::CurrentFilePath => Request::get_current_file_path(), + GetCommands::Input => Request::get_input(), + GetCommands::Inputs => Request::get_inputs(), + }, + Commands::Set { parameter } => match parameter { + SetCommands::Volume { volume } => Request::set_volume(volume), + SetCommands::Position { position } => Request::seek(position), + SetCommands::Input { id } => Request::set_input(id), + }, + }; + + let response = make_request(request).await?; + println!("{} : {}", response.status, response.message); + + Ok(()) +} diff --git a/src/bin/daemon.rs b/src/bin/daemon.rs new file mode 100644 index 0000000..329c499 --- /dev/null +++ b/src/bin/daemon.rs @@ -0,0 +1,96 @@ +use pwsp::{ + types::socket::{Request, Response}, + utils::{ + commands::parse_command, + daemon::{ + create_runtime_dir, get_audio_player, get_daemon_config, get_runtime_dir, + is_daemon_running, link_player_to_virtual_mic, + }, + pipewire::create_virtual_mic, + }, +}; +use std::{error::Error, fs}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::UnixListener, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + create_runtime_dir()?; + + if is_daemon_running()? { + return Err("Another instance is already running.".into()); + } + + get_daemon_config(); // Initialize daemon config + create_virtual_mic()?; + get_audio_player().await; // Initialize audio player + link_player_to_virtual_mic().await?; + + let runtime_dir = get_runtime_dir(); + + let lock_file = fs::File::create(runtime_dir.join("daemon.lock"))?; + lock_file.lock()?; + + let socket_path = runtime_dir.join("daemon.sock"); + if fs::metadata(&socket_path).is_ok() { + fs::remove_file(&socket_path)?; + } + + let listener = UnixListener::bind(&socket_path)?; + println!( + "Daemon started. Listening on {}", + socket_path.to_str().unwrap_or_default() + ); + + loop { + let (mut stream, _addr) = listener.accept().await?; + + tokio::spawn(async move { + // ---------- Read request (start) ---------- + let mut len_bytes = [0u8; 4]; + if stream.read_exact(&mut len_bytes).await.is_err() { + eprintln!("Failed to read message length from client!"); + return; + } + + let request_len = u32::from_le_bytes(len_bytes) as usize; + + let mut buffer = vec![0u8; request_len]; + if stream.read_exact(&mut buffer).await.is_err() { + eprintln!("Failed to read message from client!"); + return; + } + + let request: Request = serde_json::from_slice(&buffer).unwrap(); + println!("Received request: {:?}", request); + // ---------- Read request (end) ---------- + + // ---------- Generate response (start) ---------- + let command = parse_command(&request); + let response: Response; + if let Some(command) = command { + response = command.execute().await; + } else { + response = Response::new(false, "Unknown command"); + } + // ---------- Generate response (end) ---------- + + // ---------- Send response (start) ---------- + let response_data = serde_json::to_vec(&response).unwrap(); + let response_len = response_data.len() as u32; + + if stream.write_all(&response_len.to_le_bytes()).await.is_err() { + eprintln!("Failed to write response length to client!"); + return; + } + if stream.write_all(&response_data).await.is_err() { + eprintln!("Failed to write response to client!"); + return; + } + println!("Sent response: {:?}", response); + // ---------- Send response (end) ---------- + }); + } +} diff --git a/src/gui/draw.rs b/src/gui/draw.rs new file mode 100644 index 0000000..27fd563 --- /dev/null +++ b/src/gui/draw.rs @@ -0,0 +1,320 @@ +use crate::gui::SoundpadGui; +use egui::{ + Button, Color32, ComboBox, FontFamily, Label, RichText, ScrollArea, Slider, TextEdit, Ui, Vec2, +}; +use egui_material_icons::icons; +use pwsp::types::audio_player::PlayerState; +use pwsp::utils::gui::format_time_pair; +use std::{error::Error, path::PathBuf}; + +impl SoundpadGui { + pub fn draw_waiting_for_daemon(&mut self, ui: &mut Ui) { + ui.centered_and_justified(|ui| { + ui.label( + RichText::new("Waiting for PWSP daemon to start...") + .size(34.0) + .monospace(), + ); + }); + } + + pub fn draw_settings(&mut self, ui: &mut Ui) { + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = 5.0; + // --------- Back Button and Title ---------- + ui.horizontal_top(|ui| { + let back_button = Button::new(icons::ICON_ARROW_BACK).frame(false); + let back_button_response = ui.add(back_button); + if back_button_response.clicked() { + self.app_state.show_settings = false; + } + + ui.add_space(ui.available_width() / 2.0 - 40.0); + + ui.label(RichText::new("Settings").color(Color32::WHITE).monospace()); + }); + // -------------------------------- + + ui.separator(); + ui.add_space(20.0); + + // --------- Checkboxes ---------- + let save_volume_response = + ui.checkbox(&mut self.config.save_volume, "Always remember volume"); + let save_input_response = + ui.checkbox(&mut self.config.save_input, "Always remember microphone"); + let save_scale_response = ui.checkbox( + &mut self.config.save_scale_factor, + "Always remember UI scale factor", + ); + + if save_volume_response.changed() + || save_input_response.changed() + || save_scale_response.changed() + { + self.config.save_to_file().ok(); + } + // -------------------------------- + }); + } + + pub fn draw(&mut self, ui: &mut Ui) -> Result<(), Box> { + self.draw_header(ui); + self.draw_body(ui); + ui.separator(); + self.draw_footer(ui); + Ok(()) + } + + fn draw_header(&mut self, ui: &mut Ui) { + ui.vertical_centered_justified(|ui| { + // Current file name + ui.label( + RichText::new( + self.audio_player_state + .current_file_path + .to_string_lossy() + .to_string(), + ) + .color(Color32::WHITE) + .family(FontFamily::Monospace), + ); + // Media controls + self.draw_controls(ui); + ui.separator(); + }); + } + + fn draw_controls(&mut self, ui: &mut Ui) { + ui.horizontal_top(|ui| { + // ---------- Play Button ---------- + let play_button = Button::new(match self.audio_player_state.state { + PlayerState::Playing => icons::ICON_PAUSE, + PlayerState::Paused | PlayerState::Stopped => icons::ICON_PLAY_ARROW, + }) + .corner_radius(15.0); + + let play_button_response = ui.add_sized([30.0, 30.0], play_button); + if play_button_response.clicked() { + self.play_toggle(); + } + // -------------------------------- + + // ---------- Position Slider ---------- + let position_slider = Slider::new( + &mut self.app_state.position_slider_value, + 0.0..=self.audio_player_state.duration, + ) + .show_value(false) + .step_by(1.0); + + let default_slider_width = ui.spacing().slider_width; + let position_slider_width = ui.available_width() + - (30.0 * 3.0) + - default_slider_width + - (ui.spacing().item_spacing.x * 5.0); + ui.spacing_mut().slider_width = position_slider_width; + let position_slider_response = ui.add_sized([30.0, 30.0], position_slider); + if position_slider_response.drag_stopped() { + self.app_state.position_dragged = true; + } + // -------------------------------- + + // ---------- Time Label ---------- + let time_label = Label::new( + RichText::new(format_time_pair( + self.audio_player_state.position, + self.audio_player_state.duration, + )) + .monospace(), + ); + ui.add_sized([30.0, 30.0], time_label); + // -------------------------------- + + // ---------- Volume Icon ---------- + let volume_icon = if self.audio_player_state.volume > 0.7 { + icons::ICON_VOLUME_UP + } else if self.audio_player_state.volume == 0.0 { + icons::ICON_VOLUME_OFF + } else if self.audio_player_state.volume < 0.3 { + icons::ICON_VOLUME_MUTE + } else { + icons::ICON_VOLUME_DOWN + }; + let volume_icon = Label::new(RichText::new(volume_icon).size(18.0)); + ui.add_sized([30.0, 25.0], volume_icon); + // -------------------------------- + + // ---------- Volume Slider ---------- + let volume_slider = Slider::new(&mut self.app_state.volume_slider_value, 0.0..=1.0) + .show_value(false) + .step_by(0.01); + + ui.spacing_mut().slider_width = default_slider_width; + ui.spacing_mut().item_spacing.x = 0.0; + + let volume_slider_response = ui.add_sized([30.0, 30.0], volume_slider); + if volume_slider_response.drag_stopped() { + self.app_state.volume_dragged = true; + } + // -------------------------------- + }); + } + + fn draw_body(&mut self, ui: &mut Ui) { + let dirs_size = Vec2::new(ui.available_width() / 4.0, ui.available_height() - 40.0); + + ui.horizontal(|ui| { + self.draw_dirs(ui, dirs_size); + ui.separator(); + + let files_size = Vec2::new(ui.available_width(), ui.available_height() - 40.0); + self.draw_files(ui, files_size); + }); + } + + fn draw_dirs(&mut self, ui: &mut Ui, area_size: Vec2) { + ui.vertical(|ui| { + ui.set_min_width(area_size.x); + ui.set_min_height(area_size.y); + + ScrollArea::vertical().id_salt(0).show(ui, |ui| { + let mut dirs: Vec = self.app_state.dirs.iter().cloned().collect(); + dirs.sort(); + for path in dirs.iter() { + ui.horizontal(|ui| { + let name = path + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| path.to_string_lossy().to_string()); + + let dir_button = Button::new(name).frame(false); + let dir_button_response = ui.add(dir_button); + if dir_button_response.clicked() { + self.app_state.current_dir = Some(path.clone()); + } + + let delete_dir_button = Button::new(icons::ICON_DELETE).frame(false); + let delete_dir_button_response = + ui.add_sized([18.0, 18.0], delete_dir_button); + if delete_dir_button_response.clicked() { + self.remove_dir(path.clone()); + } + }); + } + + ui.horizontal(|ui| { + let add_dir_button = egui::Button::new(icons::ICON_ADD).frame(false); + let add_dir_button_response = ui.add_sized([18.0, 18.0], add_dir_button); + if add_dir_button_response.clicked() { + self.add_dir(); + } + }); + }); + }); + } + + fn draw_files(&mut self, ui: &mut Ui, area_size: Vec2) { + let extensions = [ + "mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "webm", "avi", + ]; + + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.add_sized( + [ui.available_width(), 22.0], + TextEdit::singleline(&mut self.app_state.search_query).hint_text("Search..."), + ); + }); + + ui.separator(); + + ScrollArea::vertical().id_salt(1).show(ui, |ui| { + ui.set_min_width(area_size.x); + ui.set_min_height(area_size.y); + + ui.vertical(|ui| { + if let Some(path) = self.app_state.current_dir.clone() { + for entry in path.read_dir().unwrap() { + let entry = entry.unwrap(); + let entry_path = entry.path(); + + if entry_path.is_dir() { + continue; + } + + if !extensions.contains( + &entry_path.extension().unwrap_or_default().to_str().unwrap(), + ) { + continue; + } + + let file_name = entry_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + + let search_query = self + .app_state + .search_query + .to_lowercase() + .trim() + .to_string(); + + if !file_name.to_lowercase().contains(search_query.as_str()) { + continue; + } + + let file_button = Button::new(file_name).frame(false); + let file_button_response = ui.add(file_button); + if file_button_response.clicked() { + self.play_file(entry_path); + } + } + } + }); + }); + }); + } + + fn draw_footer(&mut self, ui: &mut Ui) { + ui.add_space(5.0); + ui.horizontal_top(|ui| { + // ---------- Microphone selection ---------- + let mut mics: Vec<(&u32, &String)> = + self.audio_player_state.all_inputs.iter().collect(); + mics.sort_by_key(|(k, _)| *k); + + let mut selected_input = self.audio_player_state.current_input.to_owned(); + let prev_input = selected_input.to_owned(); + ComboBox::from_label("Choose microphone") + .selected_text( + self.audio_player_state + .all_inputs + .get(&selected_input) + .unwrap_or(&String::new()), + ) + .show_ui(ui, |ui| { + for (index, device) in mics { + ui.selectable_value(&mut selected_input, index.to_owned(), device); + } + }); + + if selected_input != prev_input { + self.set_input(selected_input); + } + // -------------------------------- + + ui.add_space(ui.available_width() - 18.0 - ui.spacing().item_spacing.x); + + // ---------- Settings button ---------- + let settings_button = Button::new(icons::ICON_SETTINGS).frame(false); + let settings_button_response = ui.add_sized([18.0, 18.0], settings_button); + if settings_button_response.clicked() { + self.app_state.show_settings = true; + } + // -------------------------------- + }); + } +} diff --git a/src/gui/input.rs b/src/gui/input.rs new file mode 100644 index 0000000..ee85fbb --- /dev/null +++ b/src/gui/input.rs @@ -0,0 +1,24 @@ +use crate::gui::SoundpadGui; +use egui::{Context, Key}; + +impl SoundpadGui { + pub fn handle_input(&mut self, ctx: &Context) { + ctx.input(|i| { + if i.key_pressed(Key::Escape) { + std::process::exit(0); + } + + if !self.app_state.show_settings && i.key_pressed(Key::Space) { + self.play_toggle(); + } + + if i.key_pressed(Key::Slash) { + self.app_state.show_settings = !self.app_state.show_settings; + } + + if self.app_state.show_settings && i.key_pressed(Key::Backspace) { + self.app_state.show_settings = false; + } + }); + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..1ce2711 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,134 @@ +mod draw; +mod input; +mod update; + +use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native}; +use egui::{Context, Vec2, ViewportBuilder}; +use pwsp::{ + types::{ + audio_player::PlayerState, + config::GuiConfig, + gui::{AppState, AudioPlayerState}, + socket::Request, + }, + utils::{ + daemon::get_daemon_config, + gui::{get_gui_config, make_request_sync, start_app_state_thread}, + }, +}; +use rfd::FileDialog; +use std::path::PathBuf; +use std::{ + error::Error, + sync::{Arc, Mutex}, +}; + +struct SoundpadGui { + pub app_state: AppState, + pub config: GuiConfig, + pub audio_player_state: AudioPlayerState, + pub audio_player_state_shared: Arc>, +} + +impl SoundpadGui { + fn new(ctx: &Context) -> Self { + let audio_player_state = Arc::new(Mutex::new(AudioPlayerState::default())); + start_app_state_thread(audio_player_state.clone()); + + let config = get_gui_config(); + + ctx.set_zoom_factor(config.scale_factor); + + let mut soundpad_gui = SoundpadGui { + app_state: AppState::default(), + config: config.clone(), + audio_player_state: AudioPlayerState::default(), + audio_player_state_shared: audio_player_state.clone(), + }; + + soundpad_gui.app_state.dirs = config.dirs; + + soundpad_gui + } + + pub fn play_toggle(&mut self) { + let mut guard = self.audio_player_state_shared.lock().unwrap(); + guard.state = match guard.state { + PlayerState::Playing => { + make_request_sync(Request::pause()).ok(); + guard.new_state = Some(PlayerState::Paused); + PlayerState::Paused + } + PlayerState::Paused => { + make_request_sync(Request::resume()).ok(); + guard.new_state = Some(PlayerState::Playing); + PlayerState::Playing + } + PlayerState::Stopped => PlayerState::Stopped, + }; + } + + pub fn add_dir(&mut self) { + let file_dialog = FileDialog::new(); + if let Some(path) = file_dialog.pick_folder() { + self.app_state.dirs.insert(path); + self.config.dirs = self.app_state.dirs.clone(); + self.config.save_to_file().ok(); + } + } + + pub fn remove_dir(&mut self, path: PathBuf) { + self.app_state.dirs.remove(&path); + if let Some(current_dir) = &self.app_state.current_dir + && current_dir == &path + { + self.app_state.current_dir = None; + } + self.config.dirs = self.app_state.dirs.clone(); + self.config.save_to_file().ok(); + } + + pub fn play_file(&mut self, path: PathBuf) { + make_request_sync(Request::play(path.to_str().unwrap())).ok(); + } + + pub fn set_input(&mut self, id: u32) { + make_request_sync(Request::set_input(id)).ok(); + + if self.config.save_input { + let mut daemon_config = get_daemon_config(); + daemon_config.default_input_id = Some(id); + daemon_config.save_to_file().ok(); + } + } +} + +pub async fn run() -> Result<(), Box> { + const ICON: &[u8] = include_bytes!("../../assets/icon.png"); + + let options = NativeOptions { + vsync: true, + centered: true, + hardware_acceleration: HardwareAcceleration::Preferred, + + viewport: ViewportBuilder::default() + .with_app_id("ru.arabianq.pwsp") + .with_inner_size(Vec2::new(1200.0, 800.0)) + .with_min_inner_size(Vec2::new(800.0, 600.0)) + .with_icon(from_png_bytes(ICON)?), + + ..Default::default() + }; + + match run_native( + "Pipewire Soundpad", + options, + Box::new(|cc| { + egui_material_icons::initialize(&cc.egui_ctx); + Ok(Box::new(SoundpadGui::new(&cc.egui_ctx))) + }), + ) { + Ok(_) => Ok(()), + Err(e) => Err(e.into()), + } +} diff --git a/src/gui/update.rs b/src/gui/update.rs new file mode 100644 index 0000000..a74bb28 --- /dev/null +++ b/src/gui/update.rs @@ -0,0 +1,77 @@ +use crate::gui::SoundpadGui; +use eframe::{App, Frame as EFrame}; +use egui::{CentralPanel, Context}; +use pwsp::{ + types::socket::Request, + utils::{ + daemon::{get_daemon_config, is_daemon_running}, + gui::make_request_sync, + }, +}; + +impl App for SoundpadGui { + fn update(&mut self, ctx: &Context, _frame: &mut EFrame) { + { + let guard = self.audio_player_state_shared.lock().unwrap(); + self.audio_player_state = guard.clone(); + } + + let old_scale_factor = self.config.scale_factor; + let new_scale_factor = ctx.zoom_factor().clamp(0.5, 2.0); + + ctx.set_zoom_factor(new_scale_factor); + self.config.scale_factor = new_scale_factor; + + if new_scale_factor != old_scale_factor && self.config.save_scale_factor { + self.config.save_to_file().ok(); + } + + self.handle_input(ctx); + + CentralPanel::default().show(ctx, |ui| { + if !is_daemon_running().unwrap() { + self.draw_waiting_for_daemon(ui); + return; + } + + if self.app_state.show_settings { + self.draw_settings(ui); + return; + } + + self.draw(ui).ok(); + }); + + if self.app_state.position_dragged { + make_request_sync(Request::seek(self.app_state.position_slider_value)).ok(); + let mut guard = self.audio_player_state_shared.lock().unwrap(); + guard.new_position = Some(self.app_state.position_slider_value); + guard.position = self.app_state.position_slider_value; + self.app_state.position_dragged = false; + } else { + self.app_state.position_slider_value = self.audio_player_state.position; + } + + if self.app_state.volume_dragged { + let new_volume = self.app_state.volume_slider_value; + + make_request_sync(Request::set_volume(new_volume)).ok(); + + let mut guard = self.audio_player_state_shared.lock().unwrap(); + guard.new_volume = Some(self.app_state.volume_slider_value); + guard.volume = self.app_state.volume_slider_value; + + self.app_state.volume_dragged = false; + + if self.config.save_volume { + let mut daemon_config = get_daemon_config(); + daemon_config.default_volume = Some(new_volume); + daemon_config.save_to_file().ok(); + } + } else { + self.app_state.volume_slider_value = self.audio_player_state.volume; + } + + ctx.request_repaint_after_secs(1.0 / 60.0); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b4ab6a6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod types; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 29f9ff3..146394a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,8 @@ -fn main () { +mod gui; -} \ No newline at end of file +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + gui::run().await +} diff --git a/src/types/audio_player.rs b/src/types/audio_player.rs new file mode 100644 index 0000000..9738a18 --- /dev/null +++ b/src/types/audio_player.rs @@ -0,0 +1,240 @@ +use crate::{ + types::pipewire::{AudioDevice, DeviceType, Terminate}, + utils::{ + daemon::get_daemon_config, + pipewire::{create_link, get_all_devices, get_device}, + }, +}; +use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source}; +use serde::{Deserialize, Serialize}; +use std::{ + error::Error, + fs, + path::{Path, PathBuf}, + time::Duration, +}; + +#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)] +pub enum PlayerState { + #[default] + Stopped, + Paused, + Playing, +} + +pub struct AudioPlayer { + _stream_handle: OutputStream, + sink: Sink, + + input_link_sender: Option>, + pub current_input_device: Option, + + pub volume: f32, + pub duration: Option, + + pub current_file_path: Option, +} + +impl AudioPlayer { + pub async fn new() -> Result> { + let daemon_config = get_daemon_config(); + let default_volume = daemon_config.default_volume.unwrap_or(1.0); + let mut default_input_device: Option = None; + if let Some(id) = daemon_config.default_input_id + && let Ok(device) = get_device(id).await + && device.device_type == DeviceType::Input + { + default_input_device = Some(device); + } + + let stream_handle = OutputStreamBuilder::open_default_stream()?; + let sink = Sink::connect_new(stream_handle.mixer()); + sink.set_volume(default_volume); + + let mut audio_player = AudioPlayer { + _stream_handle: stream_handle, + sink, + + input_link_sender: None, + current_input_device: default_input_device.clone(), + + volume: default_volume, + duration: None, + + current_file_path: None, + }; + + if default_input_device.is_some() { + audio_player.link_devices().await?; + } + + Ok(audio_player) + } + + fn abort_link_thread(&mut self) { + if let Some(sender) = &self.input_link_sender { + match sender.send(Terminate {}) { + Ok(_) => println!("Sent terminate signal to link thread"), + Err(_) => println!("Failed to send terminate signal to link thread"), + } + } + } + + async fn link_devices(&mut self) -> Result<(), Box> { + self.abort_link_thread(); + + if self.current_input_device.is_none() { + println!("No input device selected, skipping device linking"); + return Ok(()); + } + + let (input_devices, _) = get_all_devices().await?; + + let mut pwsp_daemon_input: Option = None; + for input_device in input_devices { + if input_device.name == "pwsp-virtual-mic" { + pwsp_daemon_input = Some(input_device); + break; + } + } + + if pwsp_daemon_input.is_none() { + println!("Could not find pwsp-daemon input device, skipping device linking"); + return Ok(()); + } + + let pwsp_daemon_input = pwsp_daemon_input.unwrap(); + + let current_input_device = self.current_input_device.clone().unwrap(); + let output_fl = current_input_device + .clone() + .output_fl + .expect("Failed to get pwsp-daemon output_fl"); + let output_fr = current_input_device + .clone() + .output_fr + .expect("Failed to get pwsp-daemon output_fl"); + let input_fl = pwsp_daemon_input + .clone() + .input_fl + .expect("Failed to get pwsp-daemon input_fl"); + let input_fr = pwsp_daemon_input + .clone() + .input_fr + .expect("Failed to get pwsp-daemon input_fr"); + self.input_link_sender = Some(create_link(output_fl, output_fr, input_fl, input_fr)?); + + Ok(()) + } + + pub fn pause(&mut self) { + if self.get_state() == PlayerState::Playing { + self.sink.pause(); + } + } + + pub fn resume(&mut self) { + if self.get_state() == PlayerState::Paused { + self.sink.play(); + } + } + + pub fn stop(&mut self) { + self.sink.stop(); + } + + pub fn is_paused(&self) -> bool { + self.sink.is_paused() + } + + pub fn get_state(&mut self) -> PlayerState { + if self.sink.len() == 0 { + return PlayerState::Stopped; + } + + if self.sink.is_paused() { + return PlayerState::Paused; + } + + PlayerState::Playing + } + + pub fn set_volume(&mut self, volume: f32) { + self.volume = volume; + self.sink.set_volume(volume); + } + + pub fn get_position(&mut self) -> f32 { + if self.get_state() == PlayerState::Stopped { + return 0.0; + } + + self.sink.get_pos().as_secs_f32() + } + + pub fn seek(&mut self, position: f32) -> Result<(), Box> { + match self.sink.try_seek(Duration::from_secs_f32(position)) { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } + } + + pub fn get_duration(&mut self) -> Result> { + if self.get_state() == PlayerState::Stopped { + Err("Nothing is playing right now".into()) + } else { + match self.duration { + Some(duration) => Ok(duration), + None => Err("Couldn't determine duration for current file".into()), + } + } + } + + pub async fn play(&mut self, file_path: &Path) -> Result<(), Box> { + if !file_path.exists() { + return Err(format!("File does not exist: {}", file_path.display()).into()); + } + + let file = fs::File::open(file_path)?; + match Decoder::try_from(file) { + Ok(source) => { + self.current_file_path = Some(file_path.to_path_buf()); + + if let Some(duration) = source.total_duration() { + self.duration = Some(duration.as_secs_f32()); + } else { + self.duration = None; + } + + self.sink.stop(); + self.sink.append(source); + self.sink.play(); + self.link_devices().await?; + + Ok(()) + } + Err(err) => Err(err.into()), + } + } + + pub fn get_current_file_path(&mut self) -> &Option { + if self.get_state() == PlayerState::Stopped { + self.current_file_path = None; + } + &self.current_file_path + } + + pub async fn set_current_input_device(&mut self, id: u32) -> Result<(), Box> { + let input_device = get_device(id).await?; + + if input_device.device_type != DeviceType::Input { + return Err("Selected device is not an input device".into()); + } + + self.current_input_device = Some(input_device); + + self.link_devices().await?; + + Ok(()) + } +} diff --git a/src/types/commands.rs b/src/types/commands.rs new file mode 100644 index 0000000..4e90645 --- /dev/null +++ b/src/types/commands.rs @@ -0,0 +1,234 @@ +use crate::{ + types::socket::Response, + utils::{daemon::get_audio_player, pipewire::get_all_devices}, +}; +use async_trait::async_trait; +use std::path::PathBuf; + +#[async_trait] +pub trait Executable { + async fn execute(&self) -> Response; +} + +pub struct PingCommand {} + +pub struct PauseCommand {} + +pub struct ResumeCommand {} + +pub struct StopCommand {} + +pub struct IsPausedCommand {} + +pub struct GetStateCommand {} + +pub struct GetVolumeCommand {} + +pub struct SetVolumeCommand { + pub volume: Option, +} + +pub struct GetPositionCommand {} + +pub struct SeekCommand { + pub position: Option, +} + +pub struct GetDurationCommand {} + +pub struct PlayCommand { + pub file_path: Option, +} + +pub struct GetCurrentFilePathCommand {} + +pub struct GetCurrentInputCommand {} + +pub struct GetAllInputsCommand {} + +pub struct SetCurrentInputCommand { + pub id: Option, +} + +#[async_trait] +impl Executable for PingCommand { + async fn execute(&self) -> Response { + Response::new(true, "pong") + } +} + +#[async_trait] +impl Executable for PauseCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + audio_player.pause(); + Response::new(true, "Audio was paused") + } +} + +#[async_trait] +impl Executable for ResumeCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + audio_player.resume(); + Response::new(true, "Audio was resumed") + } +} + +#[async_trait] +impl Executable for StopCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + audio_player.stop(); + Response::new(true, "Audio was stopped") + } +} + +#[async_trait] +impl Executable for IsPausedCommand { + async fn execute(&self) -> Response { + let audio_player = get_audio_player().await.lock().await; + let is_paused = audio_player.is_paused().to_string(); + Response::new(true, is_paused) + } +} + +#[async_trait] +impl Executable for GetStateCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + let state = audio_player.get_state(); + Response::new(true, serde_json::to_string(&state).unwrap()) + } +} + +#[async_trait] +impl Executable for GetVolumeCommand { + async fn execute(&self) -> Response { + let audio_player = get_audio_player().await.lock().await; + let volume = audio_player.volume; + Response::new(true, volume.to_string()) + } +} + +#[async_trait] +impl Executable for SetVolumeCommand { + async fn execute(&self) -> Response { + if let Some(volume) = self.volume { + let mut audio_player = get_audio_player().await.lock().await; + audio_player.set_volume(volume); + Response::new(true, format!("Audio volume was set to {}", volume)) + } else { + Response::new(false, "Invalid volume value") + } + } +} + +#[async_trait] +impl Executable for GetPositionCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + let position = audio_player.get_position(); + Response::new(true, position.to_string()) + } +} + +#[async_trait] +impl Executable for SeekCommand { + async fn execute(&self) -> Response { + if let Some(position) = self.position { + let mut audio_player = get_audio_player().await.lock().await; + match audio_player.seek(position) { + Ok(_) => Response::new(true, format!("Audio position was set to {}", position)), + Err(err) => Response::new(false, err.to_string()), + } + } else { + Response::new(false, "Invalid position value") + } + } +} + +#[async_trait] +impl Executable for GetDurationCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + match audio_player.get_duration() { + Ok(duration) => Response::new(true, duration.to_string()), + Err(err) => Response::new(false, err.to_string()), + } + } +} + +#[async_trait] +impl Executable for PlayCommand { + async fn execute(&self) -> Response { + if let Some(file_path) = &self.file_path { + let mut audio_player = get_audio_player().await.lock().await; + match audio_player.play(file_path).await { + Ok(_) => Response::new(true, format!("Now playing {}", file_path.display())), + Err(err) => Response::new(false, err.to_string()), + } + } else { + Response::new(false, "Invalid file path") + } + } +} + +#[async_trait] +impl Executable for GetCurrentFilePathCommand { + async fn execute(&self) -> Response { + let mut audio_player = get_audio_player().await.lock().await; + let current_file_path = audio_player.get_current_file_path(); + if let Some(current_file_path) = current_file_path { + Response::new(true, current_file_path.to_str().unwrap()) + } else { + Response::new(false, "No file is playing") + } + } +} + +#[async_trait] +impl Executable for GetCurrentInputCommand { + async fn execute(&self) -> Response { + let audio_player = get_audio_player().await.lock().await; + if let Some(input_device) = &audio_player.current_input_device { + Response::new(true, format!("{} - {}", input_device.id, input_device.nick)) + } else { + Response::new(false, "No input device selected") + } + } +} + +#[async_trait] +impl Executable for GetAllInputsCommand { + async fn execute(&self) -> Response { + let (input_devices, _output_devices) = get_all_devices().await.unwrap(); + let mut input_devices_strings = vec![]; + for device in input_devices { + if device.name == "pwsp-virtual-mic" { + continue; + } + + let string = format!("{} - {}", device.id, device.nick); + input_devices_strings.push(string); + } + let response_message = input_devices_strings.join("; "); + + Response::new(true, response_message) + } +} + +#[async_trait] +impl Executable for SetCurrentInputCommand { + async fn execute(&self) -> Response { + if let Some(id) = self.id { + let mut audio_player = get_audio_player().await.lock().await; + match audio_player.set_current_input_device(id).await { + Ok(_) => Response::new(true, "Input device was set"), + Err(err) => Response::new(false, err.to_string()), + } + } else { + Response::new(false, "Invalid index value") + } + } +} diff --git a/src/types/config.rs b/src/types/config.rs new file mode 100644 index 0000000..9eead72 --- /dev/null +++ b/src/types/config.rs @@ -0,0 +1,81 @@ +use crate::utils::config::get_config_path; +use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, error::Error, fs, path::PathBuf}; + +#[derive(Default, Clone, Serialize, Deserialize)] +pub struct DaemonConfig { + pub default_input_id: Option, + pub default_volume: Option, +} + +impl DaemonConfig { + pub fn save_to_file(&self) -> Result<(), Box> { + let config_path = get_config_path()?.join("daemon.json"); + let config_dir = config_path.parent().unwrap(); + + if !config_path.exists() { + fs::create_dir_all(config_dir)?; + } + + let config_json = serde_json::to_string_pretty(self)?; + fs::write(config_path, config_json.as_bytes())?; + Ok(()) + } + + pub fn load_from_file() -> Result> { + let config_path = get_config_path()?.join("daemon.json"); + let bytes = fs::read(config_path)?; + Ok(serde_json::from_slice::(&bytes)?) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct GuiConfig { + pub scale_factor: f32, + + pub save_volume: bool, + pub save_input: bool, + pub save_scale_factor: bool, + + pub dirs: HashSet, +} + +impl Default for GuiConfig { + fn default() -> Self { + GuiConfig { + scale_factor: 1.0, + + save_volume: false, + save_input: false, + save_scale_factor: false, + + dirs: HashSet::default(), + } + } +} + +impl GuiConfig { + pub fn save_to_file(&mut self) -> Result<(), Box> { + let config_path = get_config_path()?.join("gui.json"); + let config_dir = config_path.parent().unwrap(); + + if !config_path.exists() { + fs::create_dir_all(config_dir)?; + } + + // Do not save scale factor if user does not want to + if !self.save_scale_factor { + self.scale_factor = 1.0; + } + + let config_json = serde_json::to_string_pretty(self)?; + fs::write(config_path, config_json.as_bytes())?; + Ok(()) + } + + pub fn load_from_file() -> Result> { + let config_path = get_config_path()?.join("gui.json"); + let bytes = fs::read(config_path)?; + Ok(serde_json::from_slice::(&bytes)?) + } +} diff --git a/src/types/gui.rs b/src/types/gui.rs new file mode 100644 index 0000000..9197608 --- /dev/null +++ b/src/types/gui.rs @@ -0,0 +1,39 @@ +use crate::types::audio_player::PlayerState; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; + +#[derive(Default, Debug)] +pub struct AppState { + pub search_query: String, + + pub position_slider_value: f32, + pub volume_slider_value: f32, + + pub position_dragged: bool, + pub volume_dragged: bool, + + pub show_settings: bool, + + pub current_dir: Option, + pub dirs: HashSet, +} + +#[derive(Default, Debug, Clone)] +pub struct AudioPlayerState { + pub state: PlayerState, + pub new_state: Option, + pub current_file_path: PathBuf, + + pub is_paused: bool, + + pub volume: f32, + pub new_volume: Option, + pub position: f32, + pub new_position: Option, + pub duration: f32, + + pub current_input: u32, + pub all_inputs: HashMap, +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..b095ede --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,6 @@ +pub mod audio_player; +pub mod commands; +pub mod config; +pub mod gui; +pub mod pipewire; +pub mod socket; diff --git a/src/types/pipewire.rs b/src/types/pipewire.rs new file mode 100644 index 0000000..1dcc89a --- /dev/null +++ b/src/types/pipewire.rs @@ -0,0 +1,31 @@ +#[derive(Debug)] +pub struct Terminate {} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct Port { + pub node_id: u32, + pub port_id: u32, + + pub name: String, +} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum DeviceType { + Input, + Output, +} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct AudioDevice { + pub id: u32, + + pub nick: String, + pub name: String, + + pub device_type: DeviceType, + + pub input_fl: Option, + pub input_fr: Option, + pub output_fl: Option, + pub output_fr: Option, +} diff --git a/src/types/socket.rs b/src/types/socket.rs new file mode 100644 index 0000000..ab2f8d9 --- /dev/null +++ b/src/types/socket.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct Request { + pub name: String, + pub args: HashMap, +} + +impl Request { + pub fn new>(function_name: T, data: Vec<(T, T)>) -> Self { + let hashmap_data: HashMap = data + .into_iter() + .map(|(key, value)| (key.as_ref().to_string(), value.as_ref().to_string())) + .collect(); + + Request { + name: function_name.as_ref().to_string(), + args: hashmap_data, + } + } + + pub fn ping() -> Self { + Request::new("ping", vec![]) + } + + pub fn pause() -> Self { + Request::new("pause", vec![]) + } + + pub fn resume() -> Self { + Request::new("resume", vec![]) + } + + pub fn stop() -> Self { + Request::new("stop", vec![]) + } + + pub fn play(file_path: &str) -> Self { + Request::new("play", vec![("file_path", file_path)]) + } + + pub fn get_is_paused() -> Self { + Request::new("is_paused", vec![]) + } + + pub fn get_volume() -> Self { + Request::new("get_volume", vec![]) + } + + pub fn get_position() -> Self { + Request::new("get_position", vec![]) + } + + pub fn get_duration() -> Self { + Request::new("get_duration", vec![]) + } + + pub fn get_state() -> Self { + Request::new("get_state", vec![]) + } + + pub fn get_current_file_path() -> Self { + Request::new("get_current_file_path", vec![]) + } + + pub fn get_input() -> Self { + Request::new("get_input", vec![]) + } + + pub fn get_inputs() -> Self { + Request::new("get_inputs", vec![]) + } + + pub fn set_volume(volume: f32) -> Self { + Request::new("set_volume", vec![("volume", &volume.to_string())]) + } + + pub fn seek(position: f32) -> Self { + Request::new("seek", vec![("position", &position.to_string())]) + } + + pub fn set_input(id: u32) -> Self { + Request::new("set_input", vec![("input_id", &id.to_string())]) + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct Response { + pub status: bool, + pub message: String, +} + +impl Response { + pub fn new>(status: bool, message: T) -> Self { + Response { + status, + message: message.as_ref().to_string(), + } + } +} diff --git a/src/utils/commands.rs b/src/utils/commands.rs new file mode 100644 index 0000000..32e6cba --- /dev/null +++ b/src/utils/commands.rs @@ -0,0 +1,57 @@ +use crate::types::{commands::*, socket::Request}; + +use std::path::PathBuf; + +pub fn parse_command(request: &Request) -> Option> { + match request.name.as_str() { + "ping" => Some(Box::new(PingCommand {})), + "pause" => Some(Box::new(PauseCommand {})), + "resume" => Some(Box::new(ResumeCommand {})), + "stop" => Some(Box::new(StopCommand {})), + "is_paused" => Some(Box::new(IsPausedCommand {})), + "get_state" => Some(Box::new(GetStateCommand {})), + "get_volume" => Some(Box::new(GetVolumeCommand {})), + "set_volume" => { + let volume = request + .args + .get("volume") + .unwrap_or(&String::new()) + .parse::() + .ok(); + Some(Box::new(SetVolumeCommand { volume })) + } + "get_position" => Some(Box::new(GetPositionCommand {})), + "seek" => { + let position = request + .args + .get("position") + .unwrap_or(&String::new()) + .parse::() + .ok(); + Some(Box::new(SeekCommand { position })) + } + "get_duration" => Some(Box::new(GetDurationCommand {})), + "play" => { + let file_path = request + .args + .get("file_path") + .unwrap_or(&String::new()) + .parse::() + .ok(); + Some(Box::new(PlayCommand { file_path })) + } + "get_current_file_path" => Some(Box::new(GetCurrentFilePathCommand {})), + "get_input" => Some(Box::new(GetCurrentInputCommand {})), + "get_inputs" => Some(Box::new(GetAllInputsCommand {})), + "set_input" => { + let id = request + .args + .get("input_id") + .unwrap_or(&String::new()) + .parse::() + .ok(); + Some(Box::new(SetCurrentInputCommand { id })) + } + _ => None, + } +} diff --git a/src/utils/config.rs b/src/utils/config.rs new file mode 100644 index 0000000..127cc7a --- /dev/null +++ b/src/utils/config.rs @@ -0,0 +1,6 @@ +use std::{error::Error, path::PathBuf}; + +pub fn get_config_path() -> Result> { + let config_path = dirs::config_dir().expect("Failed to obtain config dir"); + Ok(config_path.join("pwsp")) +} diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs new file mode 100644 index 0000000..985d4de --- /dev/null +++ b/src/utils/daemon.rs @@ -0,0 +1,156 @@ +use crate::{ + types::{ + audio_player::AudioPlayer, + config::DaemonConfig, + pipewire::AudioDevice, + socket::{Request, Response}, + }, + utils::pipewire::{create_link, get_all_devices}, +}; +use std::path::PathBuf; +use std::{error::Error, fs}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::UnixStream, + sync::{Mutex, OnceCell}, + time::{Duration, sleep}, +}; + +static AUDIO_PLAYER: OnceCell> = OnceCell::const_new(); + +pub async fn get_audio_player() -> &'static Mutex { + AUDIO_PLAYER + .get_or_init(|| async { + println!("Initializing audio player"); + Mutex::new(AudioPlayer::new().await.unwrap()) + }) + .await +} + +pub fn get_daemon_config() -> DaemonConfig { + DaemonConfig::load_from_file().unwrap_or_else(|_| { + let config = DaemonConfig::default(); + config.save_to_file().ok(); + config + }) +} + +pub async fn link_player_to_virtual_mic() -> Result<(), Box> { + let (input_devices, output_devices) = get_all_devices().await?; + + let mut pwsp_daemon_output: Option = None; + for output_device in output_devices { + if output_device.name == "alsa_playback.pwsp-daemon" { + pwsp_daemon_output = Some(output_device); + break; + } + } + + if pwsp_daemon_output.is_none() { + println!("Could not find pwsp-daemon output device, skipping device linking"); + return Ok(()); + } + + let mut pwsp_daemon_input: Option = None; + for input_device in input_devices { + if input_device.name == "pwsp-virtual-mic" { + pwsp_daemon_input = Some(input_device); + break; + } + } + + if pwsp_daemon_input.is_none() { + println!("Could not find pwsp-daemon input device, skipping device linking"); + return Ok(()); + } + + let pwsp_daemon_output = pwsp_daemon_output.unwrap(); + let pwsp_daemon_input = pwsp_daemon_input.unwrap(); + + let output_fl = pwsp_daemon_output + .clone() + .output_fl + .expect("Failed to get pwsp-daemon output_fl"); + let output_fr = pwsp_daemon_output + .clone() + .output_fr + .expect("Failed to get pwsp-daemon output_fl"); + let input_fl = pwsp_daemon_input + .clone() + .input_fl + .expect("Failed to get pwsp-daemon input_fl"); + let input_fr = pwsp_daemon_input + .clone() + .input_fr + .expect("Failed to get pwsp-daemon input_fr"); + create_link(output_fl, output_fr, input_fl, input_fr)?; + + Ok(()) +} + +pub fn get_runtime_dir() -> PathBuf { + dirs::runtime_dir().unwrap_or(PathBuf::from("/run/pwsp")) +} + +pub fn create_runtime_dir() -> Result<(), Box> { + let runtime_dir = get_runtime_dir(); + if !runtime_dir.exists() { + fs::create_dir_all(&runtime_dir)?; + } + + Ok(()) +} + +pub fn is_daemon_running() -> Result> { + let lock_file = fs::File::create(get_runtime_dir().join("daemon.lock"))?; + match lock_file.try_lock() { + Ok(_) => Ok(false), + Err(_) => Ok(true), + } +} + +pub async fn wait_for_daemon() -> Result<(), Box> { + if is_daemon_running()? { + return Ok(()); + } + + println!("Daemon not found, waiting for it..."); + while !is_daemon_running()? { + sleep(Duration::from_millis(100)).await; + } + + println!("Found running daemon"); + + Ok(()) +} + +pub async fn make_request(request: Request) -> Result> { + let socket_path = get_runtime_dir().join("daemon.sock"); + let mut stream = UnixStream::connect(socket_path).await?; + + // ---------- Send request (start) ---------- + let request_data = serde_json::to_vec(&request)?; + let request_len = request_data.len() as u32; + if stream.write_all(&request_len.to_le_bytes()).await.is_err() { + return Err("Failed to send request length".into()); + }; + if stream.write_all(&request_data).await.is_err() { + return Err("Failed to send request".into()); + } + // ---------- Send request (end) ---------- + + // ---------- Read response (start) ---------- + let mut len_bytes = [0u8; 4]; + if stream.read_exact(&mut len_bytes).await.is_err() { + return Err("Failed to read response length".into()); + } + let response_len = u32::from_le_bytes(len_bytes) as usize; + + let mut buffer = vec![0u8; response_len]; + if stream.read_exact(&mut buffer).await.is_err() { + return Err("Failed to read response".into()); + }; + // ---------- Read response (end) ---------- + + Ok(serde_json::from_slice(&buffer)?) +} diff --git a/src/utils/gui.rs b/src/utils/gui.rs new file mode 100644 index 0000000..1f836fc --- /dev/null +++ b/src/utils/gui.rs @@ -0,0 +1,159 @@ +use crate::{ + types::{ + audio_player::PlayerState, + config::GuiConfig, + gui::AudioPlayerState, + socket::{Request, Response}, + }, + utils::daemon::{make_request, wait_for_daemon}, +}; +use std::{ + collections::HashMap, + error::Error, + path::PathBuf, + sync::{Arc, Mutex}, +}; +use tokio::time::{Duration, sleep}; + +pub fn get_gui_config() -> GuiConfig { + GuiConfig::load_from_file().unwrap_or_else(|_| { + let mut config = GuiConfig::default(); + config.save_to_file().ok(); + config + }) +} + +pub fn make_request_sync(request: Request) -> Result> { + futures::executor::block_on(make_request(request)) +} + +pub fn format_time_pair(position: f32, duration: f32) -> String { + fn format_time(seconds: f32) -> String { + let total_seconds = seconds.round() as u32; + let minutes = total_seconds / 60; + let secs = total_seconds % 60; + format!("{:02}:{:02}", minutes, secs) + } + + format!("{}/{}", format_time(position), format_time(duration)) +} + +pub fn start_app_state_thread(audio_player_state_shared: Arc>) { + tokio::spawn(async move { + let sleep_duration = Duration::from_millis(100); + + loop { + wait_for_daemon().await.ok(); + + let state_req = Request::get_state(); + let file_path_req = Request::get_current_file_path(); + let is_paused_req = Request::get_is_paused(); + let volume_req = Request::get_volume(); + let position_req = Request::get_position(); + let duration_req = Request::get_duration(); + let current_input_req = Request::get_input(); + let all_inputs_req = Request::get_inputs(); + + let state_res = make_request(state_req).await.unwrap_or_default(); + let file_path_res = make_request(file_path_req).await.unwrap_or_default(); + let is_paused_res = make_request(is_paused_req).await.unwrap_or_default(); + let volume_res = make_request(volume_req).await.unwrap_or_default(); + let position_res = make_request(position_req).await.unwrap_or_default(); + let duration_res = make_request(duration_req).await.unwrap_or_default(); + let current_input_res = make_request(current_input_req).await.unwrap_or_default(); + let all_inputs_res = make_request(all_inputs_req).await.unwrap_or_default(); + + let state = match state_res.status { + true => serde_json::from_str::(&state_res.message).unwrap(), + false => PlayerState::default(), + }; + + let file_path = match file_path_res.status { + true => PathBuf::from(file_path_res.message), + false => PathBuf::new(), + }; + let is_paused = match is_paused_res.status { + true => is_paused_res.message == "true", + false => false, + }; + let volume = match volume_res.status { + true => volume_res.message.parse::().unwrap(), + false => 0.0, + }; + let position = match position_res.status { + true => position_res.message.parse::().unwrap(), + false => 0.0, + }; + let duration = match duration_res.status { + true => duration_res.message.parse::().unwrap(), + false => 0.0, + }; + let current_input = match current_input_res.status { + true => current_input_res + .message + .as_str() + .split(" - ") + .collect::>() + .first() + .unwrap() + .to_string() + .parse::() + .unwrap_or_default(), + false => 0, + }; + let all_inputs = match all_inputs_res.status { + true => all_inputs_res + .message + .as_str() + .split(';') + .filter_map(|entry| { + let entry = entry.trim(); + if entry.is_empty() { + return None; + } + entry.split_once(" - ").and_then(|(k, v)| { + k.trim() + .parse::() + .ok() + .map(|key| (key, v.trim().to_string())) + }) + }) + .collect(), + false => HashMap::new(), + }; + + { + let mut guard = audio_player_state_shared.lock().unwrap(); + + guard.state = match guard.new_state.clone() { + Some(new_state) => { + guard.new_state = None; + new_state + } + None => state, + }; + guard.current_file_path = file_path; + guard.is_paused = is_paused; + guard.volume = match guard.new_volume { + Some(new_volume) => { + guard.new_volume = None; + new_volume + } + None => volume, + }; + guard.position = match guard.new_position { + Some(new_position) => { + guard.new_position = None; + new_position + } + None => position, + }; + guard.duration = if duration > 0.0 { duration } else { 1.0 }; + guard.current_input = current_input; + guard.all_inputs = all_inputs; + } + + sleep(sleep_duration).await; + } + }); +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..e23bcd1 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod commands; +pub mod config; +pub mod daemon; +pub mod gui; +pub mod pipewire; diff --git a/src/utils/pipewire.rs b/src/utils/pipewire.rs new file mode 100644 index 0000000..fa66522 --- /dev/null +++ b/src/utils/pipewire.rs @@ -0,0 +1,299 @@ +use crate::types::pipewire::{AudioDevice, DeviceType, Port, Terminate}; +use pipewire::{ + context::Context, link::Link, main_loop::MainLoop, properties::properties, + registry::GlobalObject, spa::utils::dict::DictRef, +}; +use std::{collections::HashMap, error::Error, thread}; +use tokio::{ + sync::mpsc, + time::{Duration, timeout}, +}; + +fn parse_global_object( + global_object: &GlobalObject<&DictRef>, +) -> (Option, Option) { + // Only objects with props can be devices/ports + if let Some(props) = global_object.props { + // Only objects with media.class can be devices + if let Some(media_class) = props.get("media.class") { + let node_id = global_object.id; + let node_nick = props.get("node.nick"); + let node_name = props.get("node.name"); + let node_description = props.get("node.description"); + + // Check if the device is an input or output + return if media_class.starts_with("Audio/Source") { + let input_device = AudioDevice { + id: node_id, + nick: node_nick + .unwrap_or(node_description.unwrap_or(node_name.unwrap_or_default())) + .to_string(), + name: node_name.unwrap_or_default().to_string(), + device_type: DeviceType::Input, + + input_fl: None, + input_fr: None, + output_fl: None, + output_fr: None, + }; + (Some(input_device), None) + } else if media_class.starts_with("Stream/Output/Audio") { + let output_device = AudioDevice { + id: node_id, + nick: node_nick + .unwrap_or(node_description.unwrap_or(node_name.unwrap_or_default())) + .to_string(), + name: node_name.unwrap_or_default().to_string(), + device_type: DeviceType::Output, + + input_fl: None, + input_fr: None, + output_fl: None, + output_fr: None, + }; + (Some(output_device), None) + } else { + (None, None) + }; + // Check if the object is a port + } else if props.get("port.direction").is_some() { + let node_id = props.get("node.id").unwrap().parse::().unwrap(); + let port_id = props.get("port.id").unwrap().parse::().unwrap(); + let port_name = props.get("port.name").unwrap(); + + let port = Port { + node_id, + port_id, + name: port_name.to_string(), + }; + + return (None, Some(port)); + } + } + (None, None) +} + +async fn pw_get_global_objects_thread( + main_sender: mpsc::Sender<(Option, Option)>, + pw_receiver: pipewire::channel::Receiver, +) { + let main_loop = MainLoop::new(None).expect("Failed to initialize pipewire main loop"); + + // Stop main loop on Terminate message + let _receiver = pw_receiver.attach(main_loop.loop_(), { + let _main_loop = main_loop.clone(); + move |_| _main_loop.quit() + }); + + let context = Context::new(&main_loop).expect("Failed to create pipewire context"); + let core = context + .connect(None) + .expect("Failed to connect to pipewire context"); + let registry = core + .get_registry() + .expect("Failed to get registry from pipewire context"); + + let _listener = registry + .add_listener_local() + .global(move |global| { + // Try to parse every global object pipewire finds + let (device, port) = parse_global_object(global); + + // Send message to the main thread + let sender_clone = main_sender.clone(); + tokio::task::spawn(async move { + sender_clone.send((device, port)).await.ok(); + }); + }) + .register(); + + main_loop.run(); +} + +pub async fn get_all_devices() -> Result<(Vec, Vec), Box> { + // Channels to communicate with pipewire thread + let (main_sender, mut main_receiver) = mpsc::channel(10); + let (pw_sender, pw_receiver) = pipewire::channel::channel(); + + // Spawn pipewire thread in background + let _pw_thread = + tokio::spawn(async move { pw_get_global_objects_thread(main_sender, pw_receiver).await }); + + let mut input_devices: HashMap = HashMap::new(); + let mut output_devices: HashMap = HashMap::new(); + let mut ports: Vec = vec![]; + + loop { + // If we don't receive a message in 100ms, we can assume that pipewire thread is finished + match timeout(Duration::from_millis(100), main_receiver.recv()).await { + Ok(Some((device, port))) => { + if let Some(device) = device { + match device.device_type { + DeviceType::Input => { + input_devices.insert(device.id, device); + } + DeviceType::Output => { + output_devices.insert(device.id, device); + } + } + } else if let Some(port) = port { + ports.push(port); + } + } + Ok(None) | Err(_) => { + // Pipewire thread is finished and we can collect our devices + pw_sender + .send(Terminate {}) + .expect("Failed to terminate pipewire thread"); + + for port in ports { + let node_id = port.node_id; + + if input_devices.contains_key(&node_id) { + let input_device = input_devices.get_mut(&node_id).unwrap(); + match port.name.as_str() { + "input_FL" => input_device.input_fl = Some(port), + "input_FR" => input_device.input_fr = Some(port), + "output_FL" => input_device.output_fl = Some(port), + "output_FR" => input_device.output_fr = Some(port), + "capture_FL" => input_device.output_fl = Some(port), + "capture_FR" => input_device.output_fr = Some(port), + "input_MONO" => { + input_device.input_fl = Some(port.clone()); + input_device.input_fr = Some(port) + } + _ => {} + } + } else if output_devices.contains_key(&node_id) { + let output_device = output_devices.get_mut(&node_id).unwrap(); + match port.name.as_str() { + "input_FL" => output_device.input_fl = Some(port), + "input_FR" => output_device.input_fr = Some(port), + "output_FL" => output_device.output_fl = Some(port), + "output_FR" => output_device.output_fr = Some(port), + "capture_FL" => output_device.output_fl = Some(port), + "capture_FR" => output_device.output_fr = Some(port), + "output_MONO" => { + output_device.output_fl = Some(port.clone()); + output_device.output_fr = Some(port) + } + "capture_MONO" => { + output_device.output_fl = Some(port.clone()); + output_device.output_fr = Some(port) + } + _ => {} + } + } + } + + let mut input_devices: Vec = input_devices.values().cloned().collect(); + let mut output_devices: Vec = + output_devices.values().cloned().collect(); + + input_devices.sort_by(|a, b| a.id.cmp(&b.id)); + output_devices.sort_by(|a, b| a.id.cmp(&b.id)); + + return Ok((input_devices, output_devices)); + } + } + } +} + +pub async fn get_device(node_id: u32) -> Result> { + let (mut input_devices, output_devices) = get_all_devices().await?; + input_devices.extend(output_devices); + + for device in input_devices { + if device.id == node_id { + return Ok(device); + } + } + + Err("Device not found".into()) +} + +pub fn create_virtual_mic() -> Result, Box> { + let (pw_sender, pw_receiver) = pipewire::channel::channel::(); + + let _pw_thread = thread::spawn(move || { + let main_loop = MainLoop::new(None).expect("Failed to initialize pipewire main loop"); + let context = Context::new(&main_loop).expect("Failed to create pipewire context"); + let core = context + .connect(None) + .expect("Failed to connect to pipewire context"); + + let props = properties!( + "factory.name" => "support.null-audio-sink", + "node.name" => "pwsp-virtual-mic", + "node.description" => "PWSP Virtual Mic", + "media.class" => "Audio/Source/Virtual", + "audio.position" => "[ FL FR ]", + "audio.channels" => "2", + "object.linger" => "false", // Destroy the node on app exit + ); + + let _node = core + .create_object::("adapter", &props) + .expect("Failed to create virtual mic"); + + let _receiver = pw_receiver.attach(main_loop.loop_(), { + let _main_loop = main_loop.clone(); + move |_| _main_loop.quit() + }); + + println!("Virtual mic created"); + main_loop.run(); + }); + + Ok(pw_sender) +} + +pub fn create_link( + output_fl: Port, + output_fr: Port, + input_fl: Port, + input_fr: Port, +) -> Result, Box> { + let (pw_sender, pw_receiver) = pipewire::channel::channel::(); + + let _pw_thread = thread::spawn(move || { + let main_loop = MainLoop::new(None).expect("Failed to initialize pipewire main loop"); + let context = Context::new(&main_loop).expect("Failed to create pipewire context"); + let core = context + .connect(None) + .expect("Failed to connect to pipewire context"); + + let props_fl = properties! { + "link.output.node" => format!("{}", output_fl.node_id).as_str(), + "link.output.port" => format!("{}", output_fl.port_id).as_str(), + "link.input.node" => format!("{}", input_fl.node_id).as_str(), + "link.input.port" => format!("{}", input_fl.port_id).as_str(), + }; + let props_fr = properties! { + "link.output.node" => format!("{}", output_fr.node_id).as_str(), + "link.output.port" => format!("{}", output_fr.port_id).as_str(), + "link.input.node" => format!("{}", input_fr.node_id).as_str(), + "link.input.port" => format!("{}", input_fr.port_id).as_str(), + }; + + let _link_fl = core + .create_object::("link-factory", &props_fl) + .expect("Failed to create link FL"); + let _link_fr = core + .create_object::("link-factory", &props_fr) + .expect("Failed to create link FR"); + + let _receiver = pw_receiver.attach(main_loop.loop_(), { + let _main_loop = main_loop.clone(); + move |_| _main_loop.quit() + }); + + println!( + "Link created: FL: {}-{} FR: {}-{}", + output_fl.node_id, input_fl.node_id, output_fr.node_id, input_fr.node_id + ); + main_loop.run(); + }); + + Ok(pw_sender) +}