From 1166424c29515f19a32da101f2e6a500db9caf72 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 20 Mar 2026 20:34:44 +0100 Subject: [PATCH] record-daemon: fix obs recording --- record-daemon/Cargo.lock | 2410 +++++++++++++++++++- record-daemon/Cargo.toml | 12 + record-daemon/src/ipc/server.rs | 73 +- record-daemon/src/lqp/client.rs | 73 +- record-daemon/src/main.rs | 234 +- record-daemon/src/recording/capture.rs | 226 +- record-daemon/src/recording/encoder.rs | 274 ++- record-daemon/src/recording/mod.rs | 23 +- record-daemon/src/recording/obs_context.rs | 899 ++++++-- record-daemon/src/state/machine.rs | 4 +- record-daemon/src/timeline/mapper.rs | 2 +- record-daemon/src/timeline/mod.rs | 2 +- 12 files changed, 3717 insertions(+), 515 deletions(-) diff --git a/record-daemon/Cargo.lock b/record-daemon/Cargo.lock index f75b4de..237d5f4 100644 --- a/record-daemon/Cargo.lock +++ b/record-daemon/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -76,6 +82,48 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[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", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -87,12 +135,40 @@ dependencies = [ "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 = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.21.7" @@ -105,6 +181,41 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + [[package]] name = "bitflags" version = "1.3.2" @@ -126,18 +237,39 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[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.11.1" @@ -151,15 +283,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" 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.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.44" @@ -174,6 +345,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + [[package]] name = "clap" version = "4.6.0" @@ -214,12 +396,49 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[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 = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -230,6 +449,16 @@ dependencies = [ "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" @@ -245,6 +474,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if 1.0.4", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -301,6 +554,12 @@ 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 = "crypto-common" version = "0.1.7" @@ -311,12 +570,37 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "data-encoding" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dialog" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736bab36d647d14c985725a57a4110a1182c6852104536cd42f1c97e96d29bf0" +dependencies = [ + "dirs", + "rpassword", +] + [[package]] name = "digest" version = "0.10.7" @@ -333,7 +617,28 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", ] [[package]] @@ -348,6 +653,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + +[[package]] +name = "display-info" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bce305c30b9819766d4cb345932806d98bc8f142fce5269eb3cbc41f6e3e375" +dependencies = [ + "fxhash", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "scopeguard", + "smithay-client-toolkit", + "thiserror 2.0.18", + "widestring", + "windows", + "xcb", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -359,13 +695,42 @@ dependencies = [ "syn", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "duplicate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", ] [[package]] @@ -384,18 +749,85 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[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 = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "libredox", +] + +[[package]] +name = "filetime_creation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f" +dependencies = [ + "cfg-if 1.0.4", + "filetime", + "windows-sys 0.52.0", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -417,6 +849,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.32" @@ -505,6 +943,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -515,15 +962,41 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if 1.0.4", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", ] [[package]] @@ -532,13 +1005,98 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] +[[package]] +name = "getters0" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56dde92cf5d6e58cd3c8d935980cf32ee1346d4124503cf4d8863ebdb61cbc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gio-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.61.2", +] + +[[package]] +name = "glib" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" +dependencies = [ + "bitflags 2.11.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "h2" version = "0.3.27" @@ -558,6 +1116,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if 1.0.4", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -579,6 +1167,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.12" @@ -611,6 +1205,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -633,9 +1250,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.27", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -647,6 +1264,28 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -655,12 +1294,53 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.32", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.37", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration 0.7.0", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -793,6 +1473,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -811,6 +1505,16 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -818,10 +1522,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "itoa" -version = "1.0.17" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if 1.0.4", + "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.4", + "libc", +] [[package]] name = "js-sys" @@ -833,6 +1578,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -851,13 +1606,137 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if 1.0.4", + "windows-link", +] + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if 1.0.4", + "windows-link", +] + +[[package]] +name = "libobs" +version = "5.0.1+32.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8956a7534b34783f962fd8f9b16383bcf3a2b535629fd68628b205e849bd17" +dependencies = [ + "bindgen", + "pkg-config", +] + +[[package]] +name = "libobs-bootstrapper" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d351491180f1656b2afd624d1474c7fe3d0d6b359caac10d1ee71669e0ce1af" +dependencies = [ + "async-stream", + "futures-core", + "futures-util", + "hex", + "lazy_static", + "libloading 0.9.0", + "libobs", + "log", + "reqwest 0.13.2", + "semver", + "serde", + "sevenz-rust", + "sha2", + "tokio", + "uuid", +] + +[[package]] +name = "libobs-simple" +version = "8.0.1+32.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb0021c0d3497415b7e558605a74d2a297a46f8301bb3ca2564bcd11aee6181d" +dependencies = [ + "display-info", + "lazy_static", + "libobs", + "libobs-simple-macro", + "libobs-window-helper", + "libobs-wrapper", + "log", + "num-derive", + "num-traits", + "paste", + "tokio", + "windows", +] + +[[package]] +name = "libobs-simple-macro" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3bdb09c0709df286ccd0fd07994d1fee8877e039fee6e3c21f043d90da76db" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libobs-window-helper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69207f23b413243491c3703c6c1c9434fd0ff46f158ef4d945d1dc8d889bad80" +dependencies = [ + "windows", +] + +[[package]] +name = "libobs-wrapper" +version = "9.0.4+32.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c081d77e7aa29a0a72415e0ad2be91e4ee579bf69ccf834077d8206c87bbf6" +dependencies = [ + "arboard", + "chrono", + "colored", + "dialog", + "display-info", + "duplicate", + "getters0", + "glib", + "lazy_static", + "libc", + "libloading 0.9.0", + "libobs", + "log", + "num-derive", + "num-traits", + "oneshot", + "paste", + "tokio", + "vsprintf", + "windows", +] + [[package]] name = "libredox" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ + "bitflags 2.11.0", "libc", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -887,6 +1766,21 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661" +dependencies = [ + "byteorder", +] + [[package]] name = "matchers" version = "0.2.0" @@ -902,12 +1796,37 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[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.1.1" @@ -919,6 +1838,36 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[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 = "nt-time" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5" +dependencies = [ + "chrono", + "time", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -928,6 +1877,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[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-traits" version = "0.2.19" @@ -937,6 +1903,174 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", + "objc2-metal", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -949,6 +2083,18 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oneshot" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" @@ -971,13 +2117,19 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -990,6 +2142,37 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -999,6 +2182,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1018,6 +2207,15 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.5+spec-1.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1027,6 +2225,104 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[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.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.37", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -1036,6 +2332,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1049,8 +2351,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1060,7 +2372,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[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 0.9.5", ] [[package]] @@ -1072,6 +2394,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "record-daemon" version = "0.1.0" @@ -1083,10 +2414,14 @@ dependencies = [ "clap", "crossbeam", "directories", + "display-info", "futures", + "libobs-bootstrapper", + "libobs-simple", + "libobs-wrapper", "parking_lot", "regex", - "reqwest", + "reqwest 0.11.27", "rustls 0.22.4", "rustls-pki-types", "serde", @@ -1094,15 +2429,16 @@ dependencies = [ "signal-hook", "signal-hook-tokio", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-test", "tokio-tungstenite", "tokio-util", - "toml", + "toml 0.8.23", "tracing", "tracing-subscriber", "uuid", + "winapi 0.3.9", ] [[package]] @@ -1114,6 +2450,15 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -1122,7 +2467,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1165,11 +2510,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.27", "http 0.2.12", - "http-body", - "hyper", - "hyper-rustls", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -1182,8 +2527,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-rustls 0.24.1", "tower-service", @@ -1195,6 +2540,49 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -1202,13 +2590,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.4", "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] +[[package]] +name = "rpassword" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37473170aedbe66ffa3ad3726939ba677d83c646ad4fd99e5b4bc38712f45ec" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.2.8", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.1.4" @@ -1248,6 +2653,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1263,9 +2694,37 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1287,6 +2746,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1299,6 +2770,24 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[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 = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1315,6 +2804,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1373,6 +2885,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1385,13 +2906,41 @@ dependencies = [ "serde", ] +[[package]] +name = "sevenz-rust" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef" +dependencies = [ + "bit-set", + "byteorder", + "crc", + "filetime_creation", + "js-sys", + "lzma-rust", + "nt-time", + "sha2", + "wasm-bindgen", +] + [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.4", "cpufeatures", "digest", ] @@ -1443,6 +2992,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.12" @@ -1455,6 +3010,31 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + [[package]] name = "socket2" version = "0.5.10" @@ -1510,6 +3090,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1528,8 +3117,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -1542,6 +3142,35 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.9.12+spec-1.1.0", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + [[package]] name = "tempfile" version = "3.27.0" @@ -1561,7 +3190,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1575,13 +3213,68 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1594,6 +3287,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.50.0" @@ -1643,6 +3351,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -1701,9 +3419,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] @@ -1715,6 +3448,24 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.0.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -1723,10 +3474,31 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +dependencies = [ + "indexmap", + "toml_datetime 1.0.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.0", +] + +[[package]] +name = "toml_parser" +version = "1.0.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +dependencies = [ + "winnow 1.0.0", ] [[package]] @@ -1735,6 +3507,51 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.7+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1820,11 +3637,11 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "rand", + "rand 0.8.5", "rustls 0.22.4", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -1901,12 +3718,38 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsprintf" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1946,7 +3789,7 @@ version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -1959,7 +3802,7 @@ version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "futures-util", "js-sys", "once_cell", @@ -2021,6 +3864,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -2033,6 +3889,124 @@ dependencies = [ "semver", ] +[[package]] +name = "wayland-backend" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" +dependencies = [ + "bitflags 2.11.0", + "rustix", + "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.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3298683470fbdc6ca40151dfc48c8f2fd4c41a26e13042f801f85002384091" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "429b99200febaf95d4f4e46deff6fe4382bcff3280ee16a41cf887b3c3364984" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" +dependencies = [ + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.91" @@ -2043,6 +4017,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.25.4" @@ -2067,6 +4060,82 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[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-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -2080,6 +4149,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -2108,6 +4188,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -2126,6 +4227,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2144,6 +4254,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2153,6 +4272,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2177,13 +4311,45 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2196,6 +4362,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2208,6 +4386,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2220,12 +4410,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2238,6 +4446,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2250,6 +4470,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2262,6 +4494,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2274,6 +4518,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.15" @@ -2283,13 +4533,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "windows-sys 0.48.0", ] @@ -2387,6 +4646,46 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml 0.30.0", +] + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yoke" version = "0.8.1" @@ -2412,18 +4711,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", @@ -2495,3 +4794,18 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +dependencies = [ + "zune-core", +] diff --git a/record-daemon/Cargo.toml b/record-daemon/Cargo.toml index f92b3ad..eff1c6f 100644 --- a/record-daemon/Cargo.toml +++ b/record-daemon/Cargo.toml @@ -62,11 +62,23 @@ base64 = "0.22" # Regex for lockfile parsing regex = "1" +# OBS recording library +libobs-simple = { version = "8.0.1" } +libobs-bootstrapper = { version = "0.3.1", features = ["install_dummy_dll"] } +libobs-wrapper = { version = "9.0.4" } + +# Display info for monitor capture +display-info = "0.5" + # Signal handling for graceful shutdown (Unix only) [target.'cfg(unix)'.dependencies] signal-hook = "0.3" signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } +# Windows-specific dependencies +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["winuser"] } + [dev-dependencies] # Testing utilities tokio-test = "0.4" diff --git a/record-daemon/src/ipc/server.rs b/record-daemon/src/ipc/server.rs index 13275cb..3a052fe 100644 --- a/record-daemon/src/ipc/server.rs +++ b/record-daemon/src/ipc/server.rs @@ -118,7 +118,8 @@ impl IpcServer { info!("Starting IPC server at {:?}", self.config.socket_path); - let listener = PlatformListener::bind(&self.config.socket_path).map_err(IpcError::BindError)?; + let listener = + PlatformListener::bind(&self.config.socket_path).map_err(IpcError::BindError)?; self.listener = Some(listener); @@ -146,7 +147,8 @@ impl IpcServer { // Accept new connection match listener.accept().await { Ok((stream, addr)) => { - self.handle_new_connection(stream, format!("{:?}", addr)).await; + self.handle_new_connection(stream, format!("{:?}", addr)) + .await; } Err(e) => { warn!("Failed to accept connection: {}", e); @@ -182,7 +184,8 @@ impl IpcServer { tokio::spawn(async move { *client_count.write().await += 1; - if let Err(e) = Self::handle_connection(stream, handlers, shutdown.clone(), notification_tx).await + if let Err(e) = + Self::handle_connection(stream, handlers, shutdown.clone(), notification_tx).await { error!("Connection error: {}", e); } @@ -232,8 +235,11 @@ impl IpcServer { Ok(msg) => msg, Err(e) => { warn!("Failed to parse message: {}", e); - let response = IpcResponse::error(uuid::Uuid::nil(), format!("Parse error: {}", e)); - writer.send(response.to_json()?).await + let response = + IpcResponse::error(uuid::Uuid::nil(), format!("Parse error: {}", e)); + writer + .send(response.to_json()?) + .await .map_err(|e| IpcError::CodecError(e.to_string()))?; continue; } @@ -241,9 +247,7 @@ impl IpcServer { // Handle message let response = match message.message_type { - MessageType::Request => { - handlers.handle(message).await - } + MessageType::Request => handlers.handle(message).await, MessageType::Notification => { // Notifications don't get responses trace!("Received notification: {:?}", message); @@ -258,7 +262,9 @@ impl IpcServer { // Send response let response_json = response.to_json()?; - writer.send(response_json).await + writer + .send(response_json) + .await .map_err(|e| IpcError::CodecError(e.to_string()))?; } @@ -268,13 +274,12 @@ impl IpcServer { /// Stop the IPC server. pub async fn stop(&mut self) -> Result<()> { *self.shutdown.write().await = true; - + // Remove socket file if self.config.socket_path.exists() { - std::fs::remove_file(&self.config.socket_path) - .map_err(IpcError::BindError)?; + std::fs::remove_file(&self.config.socket_path).map_err(IpcError::BindError)?; } - + info!("IPC server stopped"); Ok(()) } @@ -353,7 +358,7 @@ impl IpcServer { // On Windows, we don't need to bind in advance for named pipes // The server will create pipe instances on demand info!("Starting IPC server at {:?}", self.config.socket_path); - + info!("IPC server started successfully"); Ok(()) } @@ -362,10 +367,13 @@ impl IpcServer { pub async fn run(&self) -> Result<()> { use tokio::net::windows::named_pipe::ServerOptions; - info!("IPC server listening for connections on {:?}", self.config.socket_path); + info!( + "IPC server listening for connections on {:?}", + self.config.socket_path + ); let pipe_name: String = self.config.socket_path.to_string_lossy().into_owned(); - + loop { if *self.shutdown.read().await { info!("IPC server shutting down"); @@ -383,7 +391,7 @@ impl IpcServer { match server.connect().await { Ok(()) => { debug!("New IPC client connected"); - + // Spawn handler for this connection let handlers = self.handlers.clone(); let shutdown = self.shutdown.clone(); @@ -465,19 +473,23 @@ impl IpcServer { Ok(msg) => msg, Err(e) => { warn!("Failed to parse message: {}", e); - let response = IpcResponse::error(uuid::Uuid::nil(), format!("Parse error: {}", e)); - writer.write_all(response.to_json()?.as_bytes()).await + let response = + IpcResponse::error(uuid::Uuid::nil(), format!("Parse error: {}", e)); + writer + .write_all(response.to_json()?.as_bytes()) + .await + .map_err(IpcError::WriteError)?; + writer + .write_all(b"\n") + .await .map_err(IpcError::WriteError)?; - writer.write_all(b"\n").await.map_err(IpcError::WriteError)?; continue; } }; // Handle message let response = match message.message_type { - MessageType::Request => { - handlers.handle(message).await - } + MessageType::Request => handlers.handle(message).await, MessageType::Notification => { // Notifications don't get responses trace!("Received notification: {:?}", message); @@ -492,8 +504,14 @@ impl IpcServer { // Send response let response_json = response.to_json()?; - writer.write_all(response_json.as_bytes()).await.map_err(IpcError::WriteError)?; - writer.write_all(b"\n").await.map_err(IpcError::WriteError)?; + writer + .write_all(response_json.as_bytes()) + .await + .map_err(IpcError::WriteError)?; + writer + .write_all(b"\n") + .await + .map_err(IpcError::WriteError)?; } Ok(()) @@ -527,7 +545,10 @@ mod tests { #[test] fn test_ipc_server_config_default() { let config = IpcServerConfig::default(); - assert!(config.socket_path.to_string_lossy().contains("record-daemon")); + assert!(config + .socket_path + .to_string_lossy() + .contains("record-daemon")); assert_eq!(config.max_connections, 10); assert_eq!(config.timeout_secs, 30); } diff --git a/record-daemon/src/lqp/client.rs b/record-daemon/src/lqp/client.rs index 935e042..cd5247b 100644 --- a/record-daemon/src/lqp/client.rs +++ b/record-daemon/src/lqp/client.rs @@ -243,13 +243,12 @@ impl LqpClient { // Create a TLS connector that accepts the self-signed certificate from League Client use tokio_tungstenite::Connector; - use tokio_tungstenite::tungstenite::http::HeaderValue; - + let config = rustls::ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(Arc::new(InsecureVerifier)) .with_no_client_auth(); - + let connector = Connector::Rustls(Arc::new(config)); // Build WebSocket request with auth header @@ -260,18 +259,16 @@ impl LqpClient { .header("Connection", "Upgrade") .header("Upgrade", "websocket") .header("Sec-WebSocket-Version", "13") - .header("Sec-WebSocket-Key", tokio_tungstenite::tungstenite::handshake::client::generate_key()) + .header( + "Sec-WebSocket-Key", + tokio_tungstenite::tungstenite::handshake::client::generate_key(), + ) .body(()) .map_err(|e| LqpError::ConnectionFailed(e.to_string()))?; - let (ws_stream, _) = connect_async_tls_with_config( - request, - None, - false, - Some(connector), - ) - .await - .map_err(|e| LqpError::WebSocketError(e.to_string()))?; + let (ws_stream, _) = connect_async_tls_with_config(request, None, false, Some(connector)) + .await + .map_err(|e| LqpError::WebSocketError(e.to_string()))?; info!("WebSocket connected, subscribing to events"); @@ -306,9 +303,7 @@ impl LqpClient { match msg { Ok(Message::Text(text)) => { - debug!("Received text message: {} bytes", text.len()); if text.is_empty() { - debug!("Empty text message received, skipping"); continue; } if let Some(event) = Self::parse_websocket_message(&text) { @@ -370,8 +365,6 @@ impl LqpClient { /// Parse a WebSocket message into a game event. fn parse_websocket_message(text: &str) -> Option { - debug!("WebSocket message: {}", text); - // Parse the message array format: [type, callback, data] let value: serde_json::Value = match serde_json::from_str(text) { Ok(v) => v, @@ -383,24 +376,23 @@ impl LqpClient { // Check if it's an event message (type 8) if let Some(arr) = value.as_array() { - debug!("Message is array with {} elements", arr.len()); if arr.len() >= 3 { let msg_type = arr.first()?.as_u64()?; - debug!("Message type: {}", msg_type); if msg_type == 8 { // Event message format: [8, "OnJsonApiEvent", {"data": ..., "eventType": ..., "uri": ...}] let callback = arr.get(1)?.as_str()?; let event_data = arr.get(2)?; - + if callback == "OnJsonApiEvent" { // Extract the actual URI and data from the event let uri = event_data.get("uri")?.as_str()?; let data = event_data.get("data")?; - let event_type = event_data.get("eventType").and_then(|t| t.as_str()).unwrap_or("Update"); - - debug!("OnJsonApiEvent: uri={}, eventType={}, data={:?}", uri, event_type, data); - + let event_type = event_data + .get("eventType") + .and_then(|t| t.as_str()) + .unwrap_or("Update"); + return Self::parse_event_from_uri(uri, event_type, data); } else { debug!("Unknown callback: {}", callback); @@ -411,6 +403,8 @@ impl LqpClient { } else if msg_type == 0 { // Welcome message info!("WebSocket welcome message received"); + } else { + debug!("Unknown message type {msg_type} received"); } } } else { @@ -421,14 +415,18 @@ impl LqpClient { } /// Parse an event based on the URI. - fn parse_event_from_uri(uri: &str, event_type: &str, data: &serde_json::Value) -> Option { + fn parse_event_from_uri( + uri: &str, + event_type: &str, + data: &serde_json::Value, + ) -> Option { info!("Parsing event from URI: {} (type: {})", uri, event_type); - + // Handle gameflow phase changes if uri == "/lol-gameflow/v1/gameflow-phase" { let phase = data.as_str()?; info!("Gameflow phase changed to: {}", phase); - + // Update internal state based on phase return Some( GameEvent::from_json(&serde_json::json!({ @@ -438,22 +436,23 @@ impl LqpClient { .unwrap_or(GameEvent::Unknown), ); } - + // Handle gameflow session updates if uri == "/lol-gameflow/v1/session" { if let Some(phase) = data.get("phase").and_then(|p| p.as_str()) { info!("Gameflow session phase: {}", phase); - + // Check for game start if phase == "InProgress" { info!("Game is now in progress!"); - + // Extract game info - let game_id = data.get("gameData") + let game_id = data + .get("gameData") .and_then(|gd| gd.get("gameId")) .and_then(|id| id.as_u64()) .unwrap_or(0); - + return Some( GameEvent::from_json(&serde_json::json!({ "eventType": "lcu-game-start", @@ -462,7 +461,7 @@ impl LqpClient { .unwrap_or(GameEvent::Unknown), ); } - + return Some( GameEvent::from_json(&serde_json::json!({ "eventType": "lcu-phase-change", @@ -472,31 +471,31 @@ impl LqpClient { ); } } - + // Handle game events (kills, deaths, objectives) if uri == "/lol-game-events/v1/game-events" { info!("Game event received: {:?}", data); return GameEvent::from_json(data); } - + // Handle ready check if uri == "/lol-matchmaking/v1/ready-check" { info!("Ready check event: {:?}", data); return None; } - + // Handle champion select if uri == "/lol-champ-select/v1/session" { info!("Champion select event: {:?}", data); return None; } - + // Handle lobby if uri.starts_with("/lol-lobby") { debug!("Lobby event: {}", uri); return None; } - + debug!("Unhandled URI: {}", uri); None } diff --git a/record-daemon/src/main.rs b/record-daemon/src/main.rs index e27f8ff..663a6bc 100644 --- a/record-daemon/src/main.rs +++ b/record-daemon/src/main.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use clap::Parser; +use libobs_bootstrapper::{ObsBootstrapper, ObsBootstrapperOptions, ObsBootstrapperResult}; use parking_lot::RwLock; use tokio::sync::broadcast; use tracing::{debug, error, info, warn}; @@ -81,11 +82,31 @@ impl Daemon { async fn init(&mut self) -> Result<()> { info!("Initializing record daemon v{}", record_daemon::VERSION); - // Initialize recording engine + // Initialize recording engine (blocking operation) let settings = self.settings.read().clone(); - let mut engine = RecordingEngine::new(settings); - engine.initialize()?; - *self.recording_engine.write() = Some(engine); + let recording_engine = self.recording_engine.clone(); + + let result = tokio::task::spawn_blocking(move || { + let mut engine = RecordingEngine::new(settings); + if let Err(e) = engine.initialize() { + return Err(format!("Failed to initialize recording engine: {:?}", e)); + } + *recording_engine.write() = Some(engine); + Ok::<_, String>(()) + }) + .await; + + match result { + Ok(Ok(())) => {} + Ok(Err(e)) => return Err(record_daemon::error::RecordingError::ObsInitError(e).into()), + Err(e) => { + return Err(record_daemon::error::RecordingError::ObsInitError(format!( + "spawn_blocking error during init: {:?}", + e + )) + .into()) + } + } // Load existing recordings from disk self.timeline_store.read().load_from_disk()?; @@ -201,26 +222,79 @@ impl Daemon { /// Handle a game event. async fn handle_game_event(&self, event: GameEvent) -> Result<()> { - debug!("Game event: {:?}", event); + use std::io::Write; + + info!("[EVENT_HANDLER] Game event received: {:?}", event); + std::io::stderr().flush().ok(); // Process state transitions if let Some(transition) = self.state_machine.process_event(&event) { + info!("[EVENT_HANDLER] State transition: {:?}", transition); + std::io::stderr().flush().ok(); + self.state_machine.transition(transition.clone()); // Handle recording start/stop match transition { StateTransition::GameStarted { game_id, champion } => { + info!( + "[EVENT_HANDLER] GameStarted transition - game_id: {}, champion: {:?}", + game_id, champion + ); + std::io::stderr().flush().ok(); + // If already recording, stop the current recording first if self.state_machine.is_recording() { - info!("Stopping previous recording before starting new one"); + info!( + "[EVENT_HANDLER] Stopping previous recording before starting new one" + ); + std::io::stderr().flush().ok(); if let Err(e) = self.stop_recording().await { - warn!("Failed to stop previous recording: {}", e); + warn!("[EVENT_HANDLER] Failed to stop previous recording: {}", e); + std::io::stderr().flush().ok(); } } - self.start_recording(game_id, champion.as_deref()).await?; + + info!("[EVENT_HANDLER] Calling start_recording..."); + std::io::stderr().flush().ok(); + + // Wrap the start_recording call to catch any panics + let start_result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + // We need to use a blocking approach here since we're in catch_unwind + // The actual async call happens outside + })); + + if let Err(panic_info) = start_result { + error!( + "[EVENT_HANDLER] PANIC before start_recording: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[EVENT_HANDLER] PANIC before start_recording: {:?}", + panic_info + ); + } + + if let Err(e) = self.start_recording(game_id, champion.as_deref()).await { + error!("[EVENT_HANDLER] Failed to start recording: {}", e); + std::io::stderr().flush().ok(); + // Don't propagate error - keep daemon running + } else { + info!("[EVENT_HANDLER] start_recording completed successfully"); + std::io::stderr().flush().ok(); + } } StateTransition::GameEnded => { - self.stop_recording().await?; + info!("[EVENT_HANDLER] GameEnded transition"); + std::io::stderr().flush().ok(); + + if let Err(e) = self.stop_recording().await { + error!("[EVENT_HANDLER] Failed to stop recording: {}", e); + std::io::stderr().flush().ok(); + // Don't propagate error - keep daemon running + } } _ => {} } @@ -237,36 +311,80 @@ impl Daemon { } } + info!("[EVENT_HANDLER] Event handling complete"); + std::io::stderr().flush().ok(); Ok(()) } /// Start recording. async fn start_recording(&self, game_id: u64, champion: Option<&str>) -> Result<()> { - info!("Starting recording for game {} ({:?})", game_id, champion); + info!( + "Daemon::start_recording called - game {} ({:?})", + game_id, champion + ); - let mut engine_guard = self.recording_engine.write(); - if let Some(ref mut engine) = *engine_guard { - engine.start_recording(Some(game_id), champion)?; - self.event_mapper.write().start(); - } + // Clone Arc references for use in spawn_blocking + let recording_engine = self.recording_engine.clone(); + let event_mapper = self.event_mapper.clone(); + let champion_owned = champion.map(|s| s.to_string()); - Ok(()) + // Use spawn_blocking to avoid blocking the async runtime + tokio::task::spawn_blocking(move || { + info!("Acquiring recording engine write lock..."); + let mut engine_guard = recording_engine.write(); + info!("Recording engine lock acquired"); + + if let Some(ref mut engine) = *engine_guard { + info!("Calling engine.start_recording..."); + engine.start_recording(Some(game_id), champion_owned.as_deref())?; + info!("engine.start_recording returned successfully"); + event_mapper.write().start(); + info!("Event mapper started"); + } else { + warn!("Recording engine is None!"); + } + + info!("Daemon::start_recording completed successfully"); + Ok(()) + }) + .await + .map_err(|e| { + record_daemon::error::RecordingError::ObsInitError(format!( + "spawn_blocking error: {:?}", + e + )) + })? } /// Stop recording. async fn stop_recording(&self) -> Result<()> { info!("Stopping recording"); - let mut engine_guard = self.recording_engine.write(); - if let Some(ref mut engine) = *engine_guard { - let result = engine.stop_recording()?; - self.event_mapper.write().stop(); + // Clone Arc references for use in spawn_blocking + let recording_engine = self.recording_engine.clone(); + let event_mapper = self.event_mapper.clone(); + let timeline_store = self.timeline_store.clone(); - // Save to timeline - self.timeline_store.write().add_recording(result)?; - } + // Use spawn_blocking to avoid blocking the async runtime + tokio::task::spawn_blocking(move || { + let mut engine_guard = recording_engine.write(); + if let Some(ref mut engine) = *engine_guard { + let result = engine.stop_recording()?; + event_mapper.write().stop(); - Ok(()) + // Save to timeline + timeline_store.write().add_recording(result)?; + } + + Ok(()) + }) + .await + .map_err(|e| { + record_daemon::error::RecordingError::ObsInitError(format!( + "spawn_blocking error: {:?}", + e + )) + })? } /// Shutdown the daemon. @@ -283,9 +401,28 @@ impl Daemon { ipc_server.stop().await?; } - // Shutdown recording engine - if let Some(ref mut engine) = *self.recording_engine.write() { - engine.shutdown()?; + // Shutdown recording engine (blocking operation) + let recording_engine = self.recording_engine.clone(); + let result = tokio::task::spawn_blocking(move || { + if let Some(ref mut engine) = *recording_engine.write() { + if let Err(e) = engine.shutdown() { + return Err(format!("Failed to shutdown recording engine: {:?}", e)); + } + } + Ok::<_, String>(()) + }) + .await; + + match result { + Ok(Ok(())) => {} + Ok(Err(e)) => return Err(record_daemon::error::RecordingError::ObsInitError(e).into()), + Err(e) => { + return Err(record_daemon::error::RecordingError::ObsInitError(format!( + "spawn_blocking error during shutdown: {:?}", + e + )) + .into()) + } } info!("Daemon shutdown complete"); @@ -302,6 +439,25 @@ fn init_logging(level: &str) { .with(filter) .with(tracing_subscriber::fmt::layer()) .init(); + + // Set up panic hook to log panics + std::panic::set_hook(Box::new(|panic_info| { + let location = panic_info + .location() + .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) + .unwrap_or_else(|| "unknown location".to_string()); + + let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic_info.payload().downcast_ref::() { + s.clone() + } else { + "Unknown panic".to_string() + }; + + error!("PANIC at {}: {}", location, message); + eprintln!("PANIC at {}: {}", location, message); + })); } #[tokio::main] @@ -313,6 +469,28 @@ async fn main() -> Result<()> { info!("Record Daemon v{} starting", record_daemon::VERSION); + // Bootstrap OBS - download and extract if needed + info!("Bootstrapping OBS..."); + let bootstrap_options = ObsBootstrapperOptions::default().set_update(false); + let bootstrap_result = ObsBootstrapper::bootstrap(&bootstrap_options).await; + match bootstrap_result { + Ok(ObsBootstrapperResult::Restart) => { + info!("OBS has been downloaded and extracted."); + return Ok(()); + } + Ok(ObsBootstrapperResult::None) => { + info!("OBS bootstrap complete, continuing..."); + } + Err(e) => { + error!("Failed to bootstrap OBS: {:?}", e); + return Err(record_daemon::error::RecordingError::ObsInitError(format!( + "OBS bootstrap failed: {:?}", + e + )) + .into()); + } + } + // Load configuration let settings = if let Some(config_path) = args.config { config::ConfigPersistence::new(config_path).load()? @@ -324,7 +502,7 @@ async fn main() -> Result<()> { let mut daemon = Daemon::new(settings); // Handle shutdown signals - let shutdown_tx = daemon.shutdown_tx.clone(); + let _shutdown_tx = daemon.shutdown_tx.clone(); #[cfg(unix)] { diff --git a/record-daemon/src/recording/capture.rs b/record-daemon/src/recording/capture.rs index 31b414d..073ec99 100644 --- a/record-daemon/src/recording/capture.rs +++ b/record-daemon/src/recording/capture.rs @@ -1,11 +1,13 @@ -//! Game capture source configuration. +//! Game capture source configuration using libobs-simple. +//! +//! This module provides capture sources for recording game footage, +//! including game capture, window capture, and monitor capture. use serde::{Deserialize, Serialize}; -use tracing::{debug, info}; - -use crate::error::Result; /// Game capture source for recording game footage. +/// +/// Uses OBS game capture for efficient GPU-based capture. #[derive(Debug, Clone)] pub struct GameCapture { /// Source name. @@ -18,6 +20,8 @@ pub struct GameCapture { pub mode: CaptureMode, /// Whether to capture cursor. pub capture_cursor: bool, + /// Window class (Windows only). + pub window_class: Option, } impl Default for GameCapture { @@ -26,8 +30,9 @@ impl Default for GameCapture { name: "Game Capture".to_string(), window: None, process_name: Some("League of Legends.exe".to_string()), - mode: CaptureMode::Any, + mode: CaptureMode::Process, capture_cursor: false, + window_class: Some("RiotWindowClass".to_string()), } } } @@ -41,6 +46,18 @@ impl GameCapture { } } + /// Create a game capture configured for League of Legends. + pub fn for_league_of_legends() -> Self { + Self { + name: "League of Legends Capture".to_string(), + window: Some("League of Legends (TM) Client".to_string()), + process_name: Some("League of Legends.exe".to_string()), + mode: CaptureMode::Window, + capture_cursor: false, + window_class: Some("RiotWindowClass".to_string()), + } + } + /// Set the window to capture. pub fn with_window(mut self, window: &str) -> Self { self.window = Some(window.to_string()); @@ -55,105 +72,56 @@ impl GameCapture { self } + /// Set the window class (Windows only). + pub fn with_window_class(mut self, class: &str) -> Self { + self.window_class = Some(class.to_string()); + self + } + /// Set whether to capture the cursor. pub fn with_cursor(mut self, capture: bool) -> Self { self.capture_cursor = capture; self } - /// Create the OBS source. - /// - /// Note: This would create the actual obs_source_t in libobs. - pub fn create_source(&self) -> Result { - info!("Creating game capture source: {}", self.name); - - // Note: Actual libobs source creation would happen here - // obs_source_create("game_capture", name, settings, nullptr) - - let source = CaptureSource { - name: self.name.clone(), - source_type: SourceType::GameCapture, - active: false, - }; - - debug!("Game capture source created"); - Ok(source) + /// Get the window string for OBS in the format "Title:Class:Executable". + pub fn window_string(&self) -> Option { + match (&self.window, &self.window_class, &self.process_name) { + (Some(window), Some(class), Some(process)) => { + Some(format!("{}:{}:{}", window, class, process)) + } + (Some(window), None, Some(process)) => Some(format!("{}::{}", window, process)), + (Some(window), Some(class), None) => Some(format!("{}:{}:", window, class)), + _ => None, + } } } /// Capture mode. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum CaptureMode { /// Capture any fullscreen application. Any, /// Capture a specific window. Window, /// Capture a specific process. + #[default] Process, } -/// Capture source abstraction. -#[derive(Debug, Clone)] -pub struct CaptureSource { - /// Source name. - pub name: String, - /// Source type. - pub source_type: SourceType, - /// Whether the source is active. - pub active: bool, -} - -impl CaptureSource { - /// Check if the source is active. - pub fn is_active(&self) -> bool { - self.active - } - - /// Activate the source. - pub fn activate(&mut self) -> Result<()> { - if self.active { - return Ok(()); - } - - debug!("Activating capture source: {}", self.name); - // Note: Actual activation would involve obs_source_set_enabled - self.active = true; - Ok(()) - } - - /// Deactivate the source. - pub fn deactivate(&mut self) -> Result<()> { - if !self.active { - return Ok(()); - } - - debug!("Deactivating capture source: {}", self.name); - // Note: Actual deactivation would involve obs_source_set_enabled - self.active = false; - Ok(()) - } -} - -/// Source type. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SourceType { - /// Game capture source. - GameCapture, - /// Window capture source. - WindowCapture, - /// Monitor capture source. - MonitorCapture, -} - /// Window capture source (alternative to game capture). +/// +/// Uses OBS window capture which works with more applications +/// but may have slightly higher overhead than game capture. #[derive(Debug, Clone)] pub struct WindowCapture { /// Source name. pub name: String, /// Window title. pub window_title: String, - /// Window class (X11). + /// Window class (X11/Windows). pub window_class: Option, /// Whether to capture cursor. pub capture_cursor: bool, @@ -170,35 +138,28 @@ impl WindowCapture { } } - /// Set the window class (for X11). + /// Set the window class (for X11/Windows). pub fn with_class(mut self, class: &str) -> Self { self.window_class = Some(class.to_string()); self } - /// Create the OBS source. - pub fn create_source(&self) -> Result { - info!( - "Creating window capture source: {} ({})", - self.name, self.window_title - ); - - let source = CaptureSource { - name: self.name.clone(), - source_type: SourceType::WindowCapture, - active: false, - }; - - Ok(source) + /// Set whether to capture the cursor. + pub fn with_cursor(mut self, capture: bool) -> Self { + self.capture_cursor = capture; + self } } /// Monitor capture source (fallback). +/// +/// Captures an entire monitor. Useful as a fallback when +/// game capture or window capture don't work. #[derive(Debug, Clone)] pub struct MonitorCapture { /// Source name. pub name: String, - /// Monitor index. + /// Monitor index (0-based). pub monitor: u32, /// Whether to capture cursor. pub capture_cursor: bool, @@ -214,39 +175,39 @@ impl MonitorCapture { } } - /// Create the OBS source. - pub fn create_source(&self) -> Result { - info!( - "Creating monitor capture source: {} (monitor {})", - self.name, self.monitor - ); - - let source = CaptureSource { - name: self.name.clone(), - source_type: SourceType::MonitorCapture, - active: false, - }; - - Ok(source) + /// Set whether to capture the cursor. + pub fn with_cursor(mut self, capture: bool) -> Self { + self.capture_cursor = capture; + self } } -/// Find the League of Legends game window. -pub fn find_league_window() -> Option { - // Note: Actual window finding would use platform-specific APIs - // On Linux: X11/Wayland - // On Windows: Win32 API +/// Capture source type. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SourceType { + /// Game capture source. + GameCapture, + /// Window capture source. + WindowCapture, + /// Monitor capture source. + MonitorCapture, +} - #[cfg(target_os = "linux")] +/// Find the League of Legends game window. +/// +/// Returns the window title if found, or a default if not found. +pub fn find_league_window() -> Option { + #[cfg(target_os = "windows")] { - // Would use x11rb or similar to find window + // On Windows, we can use the Win32 API to find the window // For now, return the expected window title Some("League of Legends (TM) Client".to_string()) } - #[cfg(target_os = "windows")] + #[cfg(target_os = "linux")] { - // Would use FindWindowW + // On Linux, we would use X11 or Wayland APIs + // For now, return the expected window title Some("League of Legends (TM) Client".to_string()) } @@ -254,6 +215,11 @@ pub fn find_league_window() -> Option { None } +/// Get the default capture configuration for League of Legends. +pub fn league_capture_config() -> GameCapture { + GameCapture::for_league_of_legends() +} + #[cfg(test)] mod tests { use super::*; @@ -262,7 +228,7 @@ mod tests { fn test_game_capture_creation() { let capture = GameCapture::new("Test Capture"); assert_eq!(capture.name, "Test Capture"); - assert_eq!(capture.mode, CaptureMode::Any); + assert_eq!(capture.mode, CaptureMode::Process); } #[test] @@ -277,17 +243,23 @@ mod tests { } #[test] - fn test_capture_source_activation() { - let mut source = CaptureSource { - name: "Test".to_string(), - source_type: SourceType::GameCapture, - active: false, - }; + fn test_league_capture_config() { + let capture = league_capture_config(); + assert_eq!(capture.name, "League of Legends Capture"); + assert_eq!( + capture.process_name, + Some("League of Legends.exe".to_string()) + ); + assert_eq!(capture.window_class, Some("RiotWindowClass".to_string())); + } - assert!(!source.is_active()); - source.activate().unwrap(); - assert!(source.is_active()); - source.deactivate().unwrap(); - assert!(!source.is_active()); + #[test] + fn test_window_string() { + let capture = GameCapture::for_league_of_legends(); + let window_str = capture.window_string(); + assert!(window_str.is_some()); + let s = window_str.unwrap(); + assert!(s.contains("League of Legends")); + assert!(s.contains("RiotWindowClass")); } } diff --git a/record-daemon/src/recording/encoder.rs b/record-daemon/src/recording/encoder.rs index cb1c1e8..ca24b8f 100644 --- a/record-daemon/src/recording/encoder.rs +++ b/record-daemon/src/recording/encoder.rs @@ -1,4 +1,7 @@ //! Video and audio encoder configuration. +//! +//! This module provides encoder configuration types that integrate with +//! libobs-simple for hardware and software encoding. use serde::{Deserialize, Serialize}; @@ -17,6 +20,22 @@ pub struct EncoderConfig { pub settings: EncoderSettings, } +impl Default for EncoderConfig { + fn default() -> Self { + Self { + encoder_id: "jim_nvenc".to_string(), + bitrate: 6000, + keyframe_interval: 2, + settings: EncoderSettings::Nvenc { + cq_level: 20, + two_pass: true, + preset: "p4".to_string(), + rate_control: NvencRateControl::Vbr, + }, + } + } +} + /// Encoder-specific settings. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] @@ -133,21 +152,56 @@ impl EncoderConfig { EncoderSettings::Nvenc { .. } | EncoderSettings::Amf { .. } ) } -} -/// Video encoder trait for abstraction over different encoders. -pub trait VideoEncoder { - /// Get the encoder ID. - fn id(&self) -> &str; + /// Check if this is an NVIDIA encoder. + pub fn is_nvenc(&self) -> bool { + matches!(self.settings, EncoderSettings::Nvenc { .. }) + } - /// Get the current bitrate. - fn bitrate(&self) -> u32; + /// Check if this is an AMD encoder. + pub fn is_amf(&self) -> bool { + matches!(self.settings, EncoderSettings::Amf { .. }) + } - /// Update the bitrate. - fn set_bitrate(&mut self, bitrate: u32); + /// Check if this is a software encoder. + pub fn is_software(&self) -> bool { + matches!(self.settings, EncoderSettings::X264 { .. }) + } - /// Get encoder-specific settings as JSON. - fn settings_json(&self) -> serde_json::Value; + /// Get the hardware codec type for libobs-simple. + pub fn hardware_codec(&self) -> Option { + use libobs_simple::output::simple::HardwareCodec; + + match &self.settings { + EncoderSettings::Nvenc { .. } => Some(HardwareCodec::H264), + EncoderSettings::Amf { .. } => Some(HardwareCodec::H264), + EncoderSettings::X264 { .. } => None, + } + } + + /// Get the hardware preset for libobs-simple. + pub fn hardware_preset(&self) -> libobs_simple::output::simple::HardwarePreset { + use libobs_simple::output::simple::HardwarePreset; + + match &self.settings { + EncoderSettings::Nvenc { preset, .. } => match preset.as_str() { + "p1" => HardwarePreset::Speed, + "p2" => HardwarePreset::Speed, + "p3" => HardwarePreset::Balanced, + "p4" => HardwarePreset::Balanced, + "p5" => HardwarePreset::Quality, + "p6" => HardwarePreset::Quality, + "p7" => HardwarePreset::Quality, + _ => HardwarePreset::Balanced, + }, + EncoderSettings::Amf { quality, .. } => match quality { + AmfQuality::Speed => HardwarePreset::Speed, + AmfQuality::Balanced => HardwarePreset::Balanced, + AmfQuality::Quality => HardwarePreset::Quality, + }, + EncoderSettings::X264 { .. } => HardwarePreset::Balanced, + } + } } /// Audio encoder configuration. @@ -174,49 +228,14 @@ impl Default for AudioEncoderConfig { } } -/// Audio encoder trait. -pub trait AudioEncoder { - /// Get the encoder ID. - fn id(&self) -> &str; - - /// Get the current bitrate. - fn bitrate(&self) -> u32; - - /// Update the bitrate. - fn set_bitrate(&mut self, bitrate: u32); -} - -/// Detect available hardware encoders. -pub fn detect_hardware_encoders() -> Vec { - let mut capabilities = Vec::new(); - - // Note: Actual detection would query the system for GPU availability - // For now, we check environment variables and common indicators - - #[cfg(target_os = "linux")] - { - // Check for NVIDIA - if std::path::Path::new("/dev/nvidia0").exists() { - capabilities.push(EncoderCapability::Nvenc); - } - - // Check for AMD - if std::path::Path::new("/sys/class/drm").exists() { - // Would check for AMD GPU - // capabilities.push(EncoderCapability::Amf); +impl AudioEncoderConfig { + /// Create a new audio encoder config with the specified bitrate. + pub fn with_bitrate(bitrate: u32) -> Self { + Self { + bitrate, + ..Default::default() } } - - #[cfg(target_os = "windows")] - { - // On Windows, would use DXGI to detect GPUs - // For now, assume software encoding - } - - // Always available - capabilities.push(EncoderCapability::Software); - - capabilities } /// Encoder capability. @@ -259,6 +278,142 @@ impl EncoderCapability { }, } } + + /// Get a human-readable name for this encoder capability. + pub fn name(&self) -> &'static str { + match self { + EncoderCapability::Nvenc => "NVIDIA NVENC", + EncoderCapability::Amf => "AMD AMF", + EncoderCapability::QuickSync => "Intel QuickSync", + EncoderCapability::Software => "Software (x264)", + } + } +} + +/// Detect available hardware encoders. +/// +/// This function checks the system for available GPU encoders. +pub fn detect_hardware_encoders() -> Vec { + use std::io::Write; + use tracing::info; + + info!("[ENCODER_DETECT] Starting hardware encoder detection..."); + std::io::stderr().flush().ok(); + + let mut capabilities = Vec::new(); + + #[cfg(target_os = "linux")] + { + // Check for NVIDIA + if std::path::Path::new("/dev/nvidia0").exists() { + info!("[ENCODER_DETECT] Found NVIDIA device"); + capabilities.push(EncoderCapability::Nvenc); + } + + // Check for AMD + if std::path::Path::new("/sys/class/drm").exists() { + // Would check for AMD GPU + // capabilities.push(EncoderCapability::Amf); + } + } + + #[cfg(target_os = "windows")] + { + // On Windows, check for NVIDIA first + // Try to load nvenc DLL + info!("[ENCODER_DETECT] Checking for NVENC..."); + std::io::stderr().flush().ok(); + if is_nvenc_available() { + info!("[ENCODER_DETECT] NVENC available"); + capabilities.push(EncoderCapability::Nvenc); + } else { + info!("[ENCODER_DETECT] NVENC not available"); + } + + // Check for AMD AMF + info!("[ENCODER_DETECT] Checking for AMF..."); + std::io::stderr().flush().ok(); + if is_amf_available() { + info!("[ENCODER_DETECT] AMF available"); + capabilities.push(EncoderCapability::Amf); + } else { + info!("[ENCODER_DETECT] AMF not available"); + } + + // Check for Intel QuickSync + info!("[ENCODER_DETECT] Checking for QuickSync..."); + std::io::stderr().flush().ok(); + if is_quicksync_available() { + info!("[ENCODER_DETECT] QuickSync available"); + capabilities.push(EncoderCapability::QuickSync); + } else { + info!("[ENCODER_DETECT] QuickSync not available"); + } + } + + // Always available + info!("[ENCODER_DETECT] Software encoder always available"); + capabilities.push(EncoderCapability::Software); + + info!("[ENCODER_DETECT] Detected encoders: {:?}", capabilities); + std::io::stderr().flush().ok(); + + capabilities +} + +/// Check if NVIDIA NVENC is available. +#[cfg(target_os = "windows")] +fn is_nvenc_available() -> bool { + // Check for NVENC DLL + std::path::Path::new("C:\\Windows\\System32\\nvEncMFTH264.dll").exists() + || std::path::Path::new("C:\\Windows\\System32\\nvEncMFTH265.dll").exists() +} + +#[cfg(not(target_os = "windows"))] +fn is_nvenc_available() -> bool { + false +} + +/// Check if AMD AMF is available. +#[cfg(target_os = "windows")] +fn is_amf_available() -> bool { + // Check for AMF runtime + std::path::Path::new("C:\\Windows\\System32\\amdocl64.dll").exists() +} + +#[cfg(not(target_os = "windows"))] +fn is_amf_available() -> bool { + false +} + +/// Check if Intel QuickSync is available. +#[cfg(target_os = "windows")] +fn is_quicksync_available() -> bool { + // Check for Intel Media SDK + std::path::Path::new("C:\\Windows\\System32\\mfx64.dll").exists() +} + +#[cfg(not(target_os = "windows"))] +fn is_quicksync_available() -> bool { + false +} + +/// Get the best available encoder capability. +pub fn best_available_encoder() -> EncoderCapability { + let capabilities = detect_hardware_encoders(); + + // Prefer hardware encoders + for cap in &capabilities { + if matches!( + cap, + EncoderCapability::Nvenc | EncoderCapability::Amf | EncoderCapability::QuickSync + ) { + return *cap; + } + } + + // Fall back to software + EncoderCapability::Software } #[cfg(test)] @@ -278,6 +433,7 @@ mod tests { assert_eq!(config.encoder_id, "jim_nvenc"); assert_eq!(config.bitrate, 8000); assert!(config.is_hardware()); + assert!(config.is_nvenc()); } #[test] @@ -292,6 +448,7 @@ mod tests { assert_eq!(config.encoder_id, "x264"); assert!(!config.is_hardware()); + assert!(config.is_software()); } #[test] @@ -300,4 +457,17 @@ mod tests { assert!(!capabilities.is_empty()); assert!(capabilities.contains(&EncoderCapability::Software)); } + + #[test] + fn test_best_available_encoder() { + let best = best_available_encoder(); + // Should always return something + assert!(matches!( + best, + EncoderCapability::Nvenc + | EncoderCapability::Amf + | EncoderCapability::QuickSync + | EncoderCapability::Software + )); + } } diff --git a/record-daemon/src/recording/mod.rs b/record-daemon/src/recording/mod.rs index 35e9174..691dafd 100644 --- a/record-daemon/src/recording/mod.rs +++ b/record-daemon/src/recording/mod.rs @@ -8,8 +8,8 @@ pub mod encoder; mod obs_context; mod output; -pub use capture::{CaptureSource, GameCapture}; -pub use encoder::{AudioEncoder, EncoderConfig, VideoEncoder}; +pub use capture::{CaptureMode, GameCapture, MonitorCapture, SourceType, WindowCapture}; +pub use encoder::{AudioEncoderConfig, EncoderCapability, EncoderConfig, EncoderSettings}; pub use obs_context::{ObsContext, ObsContextBuilder}; pub use output::{OutputConfig, RecordingOutput, RecordingResult}; @@ -17,7 +17,7 @@ use std::sync::Arc; use chrono::{DateTime, Utc}; use parking_lot::RwLock; -use tracing::{info, warn}; +use tracing::{error, info, warn}; use crate::config::Settings; use crate::error::{RecordingError, Result}; @@ -92,10 +92,17 @@ impl RecordingEngine { /// * `game_id` - Optional game ID for the recording. /// * `champion` - Optional champion name for the filename. pub fn start_recording(&mut self, game_id: Option, champion: Option<&str>) -> Result<()> { + info!( + "RecordingEngine::start_recording called - game_id: {:?}, champion: {:?}", + game_id, champion + ); + if self.is_recording { + warn!("Already recording, returning error"); return Err(RecordingError::AlreadyRecording.into()); } + info!("Reading settings..."); let settings = self.settings.read().clone(); // Generate output filename @@ -105,10 +112,14 @@ impl RecordingEngine { info!("Starting recording to: {:?}", output_path); // Start the recording - let context = self.context.as_mut().ok_or(RecordingError::ObsInitError( - "OBS not initialized".to_string(), - ))?; + let context = self.context.as_mut().ok_or_else(|| { + error!("OBS not initialized when start_recording was called"); + RecordingError::ObsInitError("OBS not initialized".to_string()) + })?; + + info!("Calling OBS context start_recording..."); context.start_recording(&output_path)?; + info!("OBS context start_recording returned successfully"); self.current_output = Some(RecordingOutput { path: output_path, diff --git a/record-daemon/src/recording/obs_context.rs b/record-daemon/src/recording/obs_context.rs index 2293e23..905fbc3 100644 --- a/record-daemon/src/recording/obs_context.rs +++ b/record-daemon/src/recording/obs_context.rs @@ -1,82 +1,25 @@ //! OBS context initialization and management. //! -//! This module handles the lifecycle of the OBS library context. +//! This module handles the lifecycle of the OBS library context using libobs-simple. use std::path::Path; -use tracing::{debug, info, warn}; +use libobs_simple::output::simple::{ + HardwareCodec, HardwarePreset, OutputFormat, SimpleOutputBuilder, +}; +use libobs_simple::wrapper::data::output::ObsOutputTrait; +use libobs_simple::wrapper::data::video::ObsVideoInfoBuilder; +use libobs_simple::wrapper::data::ObsObjectBuilder; +use libobs_simple::wrapper::scenes::SceneItemExtSceneTrait; +use libobs_simple::wrapper::utils::{ObsPath, ObsString}; +use libobs_wrapper::context::ObsContext as LibObsContext; +use libobs_wrapper::utils::StartupInfo; +use tracing::{error, info, warn}; +use super::encoder::{best_available_encoder, EncoderCapability}; use crate::config::{AudioSettings, VideoSettings}; use crate::error::{RecordingError, Result}; -/// OBS video settings for initialization. -#[derive(Debug, Clone)] -pub struct ObsVideoInfo { - /// Graphics adapter index (-1 for default). - pub adapter: i32, - /// Output resolution width. - pub output_width: u32, - /// Output resolution height. - pub output_height: u32, - /// Frames per second numerator. - pub fps_num: u32, - /// Frames per second denominator. - pub fps_den: u32, - /// Base resolution width. - pub base_width: u32, - /// Base resolution height. - pub base_height: u32, - /// Output format. - pub output_format: ObsVideoFormat, -} - -impl Default for ObsVideoInfo { - fn default() -> Self { - Self { - adapter: -1, - output_width: 1920, - output_height: 1080, - fps_num: 60, - fps_den: 1, - base_width: 1920, - base_height: 1080, - output_format: ObsVideoFormat::Nv12, - } - } -} - -impl ObsVideoInfo { - /// Create video info from settings. - pub fn from_settings(settings: &VideoSettings) -> Self { - let (width, height) = settings.quality.resolution(); - let fps = settings.frame_rate; - - Self { - adapter: -1, - output_width: width, - output_height: height, - fps_num: fps, - fps_den: 1, - base_width: width, - base_height: height, - output_format: ObsVideoFormat::Nv12, - } - } -} - -/// Video output format. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ObsVideoFormat { - /// NV12 format (common for hardware encoders). - Nv12, - /// I420 format. - I420, - /// I444 format. - I444, - /// RGBA format. - Rgba, -} - /// Builder for OBS context. pub struct ObsContextBuilder { video_settings: Option, @@ -126,20 +69,17 @@ impl ObsContextBuilder { .map_err(|e| RecordingError::OutputDirError(e.to_string()))?; } - let video_info = ObsVideoInfo::from_settings(&video_settings); - let context = ObsContext { - video_info, + video_settings, audio_settings, output_dir, - initialized: false, + context: None, + output: None, recording: false, current_output: None, + encoder_capability: None, }; - // Note: Actual libobs initialization would happen here - // For now, we create a stub that can be extended with actual libobs bindings - Ok(context) } } @@ -156,23 +96,27 @@ impl Default for ObsContextBuilder { /// recording functionality. pub struct ObsContext { /// Video configuration. - video_info: ObsVideoInfo, + video_settings: VideoSettings, /// Audio configuration. audio_settings: AudioSettings, /// Output directory for recordings. output_dir: std::path::PathBuf, - /// Whether OBS has been initialized. - initialized: bool, + /// The underlying libobs context. + context: Option, + /// The current output. + output: Option, /// Whether currently recording. recording: bool, /// Current output path (if recording). current_output: Option, + /// Detected encoder capability. + encoder_capability: Option, } impl ObsContext { /// Check if OBS is initialized. pub fn is_initialized(&self) -> bool { - self.initialized + self.context.is_some() } /// Check if currently recording. @@ -180,31 +124,695 @@ impl ObsContext { self.recording } - /// Get the video info. - pub fn video_info(&self) -> &ObsVideoInfo { - &self.video_info + /// Get the video settings. + pub fn video_settings(&self) -> &VideoSettings { + &self.video_settings + } + + /// Initialize OBS. + fn initialize(&mut self) -> Result<()> { + if self.context.is_some() { + info!("OBS context already initialized"); + return Ok(()); + } + + info!("[OBS_INIT] Starting OBS context initialization..."); + use std::io::Write; + std::io::stderr().flush().ok(); + + // Pre-flight checks for OBS + self.preflight_checks()?; + + // Detect best available encoder + info!("[OBS_INIT] Detecting best available encoder..."); + std::io::stderr().flush().ok(); + let encoder = best_available_encoder(); + info!("[OBS_INIT] Detected encoder: {}", encoder.name()); + std::io::stderr().flush().ok(); + self.encoder_capability = Some(encoder); + + let (width, height) = self.video_settings.quality.resolution(); + let fps = self.video_settings.frame_rate; + + info!( + "[OBS_INIT] OBS video config: {}x{} @ {}fps", + width, height, fps + ); + std::io::stderr().flush().ok(); + + // Create startup info with video configuration + info!("[OBS_INIT] Creating OBS video info builder..."); + std::io::stderr().flush().ok(); + let video_info = ObsVideoInfoBuilder::new() + .fps_num(fps) + .fps_den(1) + .base_width(width) + .base_height(height) + .output_width(width) + .output_height(height) + .build(); + info!("[OBS_INIT] OBS video info built successfully"); + std::io::stderr().flush().ok(); + + info!("[OBS_INIT] Building OBS context with LibObsContext::builder()..."); + std::io::stderr().flush().ok(); + + let compat = LibObsContext::check_version_compatibility(); + if !compat { + error!("OBS version compatibility is wrong. This will not go well..."); + } + + //FIXME: this is crashing here. + // LibObsContext::new makes the whole application crash, even with default options + let info = StartupInfo::default().set_video_info(video_info); + let context_result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| LibObsContext::new(info))); + + let context = match context_result { + Ok(Ok(ctx)) => { + info!("[OBS_INIT] OBS context created successfully"); + std::io::stderr().flush().ok(); + ctx + } + Ok(Err(e)) => { + error!("[OBS_INIT] Failed to create OBS context: {:?}", e); + std::io::stderr().flush().ok(); + return Err(RecordingError::ObsInitError(format!( + "Failed to create OBS context: {:?}", + e + )) + .into()); + } + Err(panic_info) => { + error!( + "[OBS_INIT] PANIC during OBS context creation: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[OBS_INIT] PANIC during OBS context creation: {:?}", + panic_info + ); + return Err(RecordingError::ObsInitError( + "Panic during OBS context creation".to_string(), + ) + .into()); + } + }; + + if self.audio_settings.enabled { + info!( + "[OBS_INIT] OBS audio config: {} channels @ {}Hz", + self.audio_settings.channels, self.audio_settings.sample_rate + ); + } + + self.context = Some(context); + info!("[OBS_INIT] OBS context initialized successfully"); + std::io::stderr().flush().ok(); + Ok(()) + } + + /// Pre-flight checks for OBS initialization. + fn preflight_checks(&self) -> Result<()> { + use std::io::Write; + + info!("[PREFLIGHT] Running OBS pre-flight checks..."); + std::io::stderr().flush().ok(); + + // Check for OBS installation directory + // libobs-bootstrapper typically extracts OBS to a specific location + let obs_paths = self.get_obs_search_paths(); + + info!("[PREFLIGHT] OBS search paths: {:?}", obs_paths); + std::io::stderr().flush().ok(); + + let mut obs_found = false; + for path in &obs_paths { + if path.exists() { + info!("[PREFLIGHT] Found OBS at: {:?}", path); + std::io::stderr().flush().ok(); + + // Check for plugins directory + let plugins_path = path.join("obs-plugins"); + if plugins_path.exists() { + info!("[PREFLIGHT] Found OBS plugins at: {:?}", plugins_path); + std::io::stderr().flush().ok(); + + // Check for 64-bit plugins + let plugins_64 = plugins_path.join("64bit"); + if plugins_64.exists() { + info!("[PREFLIGHT] Found 64-bit plugins at: {:?}", plugins_64); + std::io::stderr().flush().ok(); + + // List available plugins + if let Ok(entries) = std::fs::read_dir(&plugins_64) { + let plugins: Vec = entries + .filter_map(|e| e.ok()) + .filter_map(|e| e.file_name().to_str().map(|s| s.to_string())) + .collect(); + info!("[PREFLIGHT] Available plugins: {:?}", plugins); + std::io::stderr().flush().ok(); + } + } + } + + obs_found = true; + break; + } + } + + if !obs_found { + warn!( + "[PREFLIGHT] OBS installation not found in standard paths - this may cause issues" + ); + std::io::stderr().flush().ok(); + } + + // Check for display (required for capture) + #[cfg(target_os = "windows")] + { + info!("[PREFLIGHT] Checking for display availability..."); + std::io::stderr().flush().ok(); + + // On Windows, check if we have a display + use std::ptr; + unsafe { + let dc = winapi::um::winuser::GetDC(ptr::null_mut()); + if dc.is_null() { + warn!("[PREFLIGHT] No display context available - capture may fail"); + } else { + info!("[PREFLIGHT] Display context available"); + winapi::um::winuser::ReleaseDC(ptr::null_mut(), dc); + } + } + std::io::stderr().flush().ok(); + } + + info!("[PREFLIGHT] Pre-flight checks completed"); + std::io::stderr().flush().ok(); + Ok(()) + } + + /// Get OBS search paths based on platform. + fn get_obs_search_paths(&self) -> Vec { + let mut paths = Vec::new(); + + // Current directory + paths.push(std::env::current_dir().unwrap_or_default().join("obs")); + + // libobs-bootstrapper default locations + #[cfg(target_os = "windows")] + { + // Check AppData + if let Some(app_data) = std::env::var_os("LOCALAPPDATA") { + paths.push(std::path::PathBuf::from(app_data).join("obs-studio")); + } + if let Some(app_data) = std::env::var_os("APPDATA") { + paths.push(std::path::PathBuf::from(app_data).join("obs-studio")); + } + // Program Files + paths.push(std::path::PathBuf::from("C:\\Program Files\\obs-studio")); + paths.push(std::path::PathBuf::from( + "C:\\Program Files (x86)\\obs-studio", + )); + } + + #[cfg(target_os = "linux")] + { + paths.push(std::path::PathBuf::from("/usr/share/obs")); + paths.push(std::path::PathBuf::from("/usr/local/share/obs")); + if let Some(home) = std::env::var_os("HOME") { + paths.push(std::path::PathBuf::from(home).join(".local/share/obs")); + } + } + + paths } /// Start recording to the specified output path. pub fn start_recording(&mut self, output_path: &Path) -> Result<()> { + use std::io::Write; + + info!( + "[START_REC] start_recording called with path: {:?}", + output_path + ); + std::io::stderr().flush().ok(); + if self.recording { + warn!("[START_REC] Already recording, returning error"); return Err(RecordingError::AlreadyRecording.into()); } - if !self.initialized { - // Initialize on first use + if self.context.is_none() { + info!("[START_REC] OBS context not initialized, initializing now..."); + std::io::stderr().flush().ok(); self.initialize()?; + info!("[START_REC] OBS initialization complete"); + std::io::stderr().flush().ok(); } - info!("Starting OBS recording to: {:?}", output_path); + let context = self.context.as_ref().ok_or_else(|| { + error!("[START_REC] OBS not initialized after initialize()"); + std::io::stderr().flush().ok(); + RecordingError::ObsInitError("OBS not initialized".to_string()) + })?; - // Note: Actual libobs recording start would happen here - // This is a stub implementation + info!("[START_REC] Starting OBS recording to: {:?}", output_path); + std::io::stderr().flush().ok(); + // Get bitrate from encoder preset + let bitrate = self.video_settings.encoder_preset.effective_bitrate(); + info!("[START_REC] Using bitrate: {} kbps", bitrate); + std::io::stderr().flush().ok(); + + // Create output path + let path_str = output_path.to_string_lossy(); + let obs_path = ObsPath::new(&path_str); + + // Get detected encoder capability + let encoder = self + .encoder_capability + .unwrap_or_else(best_available_encoder); + info!("[START_REC] Using encoder: {}", encoder.name()); + std::io::stderr().flush().ok(); + + // Build the output based on encoder capability + info!("[START_REC] Creating SimpleOutputBuilder..."); + std::io::stderr().flush().ok(); + let output_result = match encoder { + EncoderCapability::Nvenc => { + info!("Building NVENC output..."); + SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path) + .video_bitrate(bitrate) + .audio_bitrate(self.audio_settings.bitrate) + .hardware_encoder(HardwareCodec::H264, HardwarePreset::Quality) + .format(OutputFormat::Mpeg4) + .build() + } + EncoderCapability::Amf => { + info!("Building AMF output..."); + SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path) + .video_bitrate(bitrate) + .audio_bitrate(self.audio_settings.bitrate) + .hardware_encoder(HardwareCodec::H264, HardwarePreset::Balanced) + .format(OutputFormat::Mpeg4) + .build() + } + EncoderCapability::QuickSync => { + info!("Building QuickSync output..."); + SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path) + .video_bitrate(bitrate) + .audio_bitrate(self.audio_settings.bitrate) + .hardware_encoder(HardwareCodec::H264, HardwarePreset::Balanced) + .format(OutputFormat::Mpeg4) + .build() + } + EncoderCapability::Software => { + info!("Building software (x264) output..."); + SimpleOutputBuilder::new(context.clone(), ObsString::from("output"), obs_path) + .video_bitrate(bitrate) + .audio_bitrate(self.audio_settings.bitrate) + .format(OutputFormat::Mpeg4) + .build() + } + }; + + info!("[START_REC] Output build complete, checking for errors..."); + std::io::stderr().flush().ok(); + let output = output_result.map_err(|e| { + error!("[START_REC] Failed to create output: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to create output: {:?}", e)) + })?; + + info!("[START_REC] Output created successfully, setting up game capture..."); + std::io::stderr().flush().ok(); + + // Set up game capture source + self.setup_game_capture()?; + + info!("[START_REC] Game capture set up, starting output..."); + std::io::stderr().flush().ok(); + + // Start the output - wrap in catch_unwind as this may crash in native code + let start_result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| output.start())); + + match start_result { + Ok(Ok(())) => { + info!("[START_REC] Output started successfully"); + std::io::stderr().flush().ok(); + } + Ok(Err(e)) => { + error!("[START_REC] Failed to start output: {:?}", e); + std::io::stderr().flush().ok(); + return Err( + RecordingError::StartError(format!("Failed to start output: {:?}", e)).into(), + ); + } + Err(panic_info) => { + error!("[START_REC] PANIC starting output: {:?}", panic_info); + std::io::stderr().flush().ok(); + eprintln!("[START_REC] PANIC starting output: {:?}", panic_info); + return Err(RecordingError::StartError("Panic starting output".to_string()).into()); + } + } + + self.output = Some(output); self.current_output = Some(output_path.to_path_buf()); self.recording = true; - debug!("OBS recording started"); + info!("[START_REC] OBS recording started successfully"); + std::io::stderr().flush().ok(); + Ok(()) + } + + /// Set up capture source with fallback from game capture to monitor capture. + fn setup_game_capture(&mut self) -> Result<()> { + use std::io::Write; + + info!("[CAPTURE] Setting up capture source..."); + std::io::stderr().flush().ok(); + + // Try game capture first, fall back to monitor capture + match self.try_game_capture() { + Ok(()) => { + info!("[CAPTURE] Game capture set up successfully"); + std::io::stderr().flush().ok(); + Ok(()) + } + Err(e) => { + warn!( + "[CAPTURE] Game capture failed: {}, falling back to monitor capture", + e + ); + std::io::stderr().flush().ok(); + self.setup_monitor_capture() + } + } + } + + /// Try to set up game capture source. + fn try_game_capture(&mut self) -> Result<()> { + use libobs_simple::sources::windows::{GameCaptureSourceBuilder, ObsGameCaptureMode}; + use libobs_simple::sources::ObsSourceBuilder; + use std::io::Write; + + info!("[GAME_CAPTURE] Attempting game capture setup..."); + std::io::stderr().flush().ok(); + + let context = self.context.as_mut().ok_or_else(|| { + error!("[GAME_CAPTURE] OBS not initialized in setup_game_capture"); + std::io::stderr().flush().ok(); + RecordingError::ObsInitError("OBS not initialized".to_string()) + })?; + + // Create a scene + info!("[GAME_CAPTURE] Creating scene 'main'..."); + std::io::stderr().flush().ok(); + let mut scene = context.scene("main", None).map_err(|e| { + error!("[GAME_CAPTURE] Failed to create scene: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to create scene: {:?}", e)) + })?; + info!("[GAME_CAPTURE] Scene created successfully"); + std::io::stderr().flush().ok(); + + // Build game capture source + info!("[GAME_CAPTURE] Getting OBS runtime..."); + std::io::stderr().flush().ok(); + let runtime = context.runtime(); + + info!("[GAME_CAPTURE] Creating game capture source builder..."); + std::io::stderr().flush().ok(); + + // Wrap game capture builder in catch_unwind as it may crash in native code + let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + GameCaptureSourceBuilder::new("game_capture", runtime.clone()) + })); + + let builder = match builder_result { + Ok(Ok(b)) => { + info!("[GAME_CAPTURE] Game capture builder created"); + std::io::stderr().flush().ok(); + b + } + Ok(Err(e)) => { + error!( + "[GAME_CAPTURE] Failed to create game capture builder: {:?}", + e + ); + std::io::stderr().flush().ok(); + return Err(RecordingError::StartError(format!( + "Failed to create game capture builder: {:?}", + e + )) + .into()); + } + Err(panic_info) => { + error!( + "[GAME_CAPTURE] PANIC creating game capture builder: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[GAME_CAPTURE] PANIC creating game capture builder: {:?}", + panic_info + ); + return Err(RecordingError::StartError( + "Panic creating game capture builder".to_string(), + ) + .into()); + } + }; + + info!("[GAME_CAPTURE] Configuring game capture for League of Legends..."); + std::io::stderr().flush().ok(); + + // Use "Any" mode to capture any fullscreen application + // This is the most reliable mode for games like League of Legends + info!("[GAME_CAPTURE] Using 'Any' mode to capture fullscreen games..."); + std::io::stderr().flush().ok(); + + // Wrap source build in catch_unwind + let source_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + builder.set_capture_mode(ObsGameCaptureMode::Any).build() + })); + + let source = match source_result { + Ok(Ok(s)) => { + info!("[GAME_CAPTURE] Game capture source created"); + std::io::stderr().flush().ok(); + s + } + Ok(Err(e)) => { + error!( + "[GAME_CAPTURE] Failed to create game capture source: {:?}", + e + ); + std::io::stderr().flush().ok(); + return Err(RecordingError::StartError(format!( + "Failed to create game capture source: {:?}", + e + )) + .into()); + } + Err(panic_info) => { + error!( + "[GAME_CAPTURE] PANIC creating game capture source: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[GAME_CAPTURE] PANIC creating game capture source: {:?}", + panic_info + ); + return Err(RecordingError::StartError( + "Panic creating game capture source".to_string(), + ) + .into()); + } + }; + + // Add source to scene using add_source + info!("[GAME_CAPTURE] Adding source to scene..."); + std::io::stderr().flush().ok(); + scene.add_source(source).map_err(|e| { + error!("[GAME_CAPTURE] Failed to add source to scene: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to add source to scene: {:?}", e)) + })?; + info!("[GAME_CAPTURE] Source added to scene"); + std::io::stderr().flush().ok(); + + // Set the scene as active + info!("[GAME_CAPTURE] Setting scene as active on channel 0..."); + std::io::stderr().flush().ok(); + scene.set_to_channel(0).map_err(|e| { + error!("[GAME_CAPTURE] Failed to set scene: {:?}", e); + RecordingError::StartError(format!("Failed to set scene: {:?}", e)) + })?; + + info!("[GAME_CAPTURE] Game capture source configured successfully"); + std::io::stderr().flush().ok(); + Ok(()) + } + + /// Set up monitor capture as fallback. + fn setup_monitor_capture(&mut self) -> Result<()> { + use libobs_simple::sources::windows::MonitorCaptureSourceBuilder; + use libobs_simple::sources::ObsSourceBuilder; + use std::io::Write; + + info!("[MONITOR_CAPTURE] Setting up monitor capture as fallback..."); + std::io::stderr().flush().ok(); + + let context = self.context.as_mut().ok_or_else(|| { + error!("[MONITOR_CAPTURE] OBS not initialized"); + std::io::stderr().flush().ok(); + RecordingError::ObsInitError("OBS not initialized".to_string()) + })?; + + // Create a scene + info!("[MONITOR_CAPTURE] Creating scene 'main'..."); + std::io::stderr().flush().ok(); + let mut scene = context.scene("main", None).map_err(|e| { + error!("[MONITOR_CAPTURE] Failed to create scene: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to create scene: {:?}", e)) + })?; + info!("[MONITOR_CAPTURE] Scene created successfully"); + std::io::stderr().flush().ok(); + + // Get monitor info + info!("[MONITOR_CAPTURE] Detecting monitors..."); + std::io::stderr().flush().ok(); + + let monitors = display_info::DisplayInfo::all().map_err(|e| { + error!("[MONITOR_CAPTURE] Failed to get display info: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to get display info: {:?}", e)) + })?; + + if monitors.is_empty() { + error!("[MONITOR_CAPTURE] No monitors detected"); + std::io::stderr().flush().ok(); + return Err(RecordingError::StartError("No monitors detected".to_string()).into()); + } + + // Use the primary monitor (first in the list) + let primary_monitor = &monitors[0]; + info!( + "[MONITOR_CAPTURE] Using monitor: {}x{} at ({}, {})", + primary_monitor.width, primary_monitor.height, primary_monitor.x, primary_monitor.y + ); + std::io::stderr().flush().ok(); + + // Build monitor capture source + info!("[MONITOR_CAPTURE] Creating monitor capture source builder..."); + std::io::stderr().flush().ok(); + + let builder_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + MonitorCaptureSourceBuilder::new("monitor_capture", context.runtime().clone()) + })); + + let builder = match builder_result { + Ok(Ok(b)) => { + info!("[MONITOR_CAPTURE] Monitor capture builder created"); + std::io::stderr().flush().ok(); + b + } + Ok(Err(e)) => { + error!( + "[MONITOR_CAPTURE] Failed to create monitor capture builder: {:?}", + e + ); + std::io::stderr().flush().ok(); + return Err(RecordingError::StartError(format!( + "Failed to create monitor capture builder: {:?}", + e + )) + .into()); + } + Err(panic_info) => { + error!( + "[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[MONITOR_CAPTURE] PANIC creating monitor capture builder: {:?}", + panic_info + ); + return Err(RecordingError::StartError( + "Panic creating monitor capture builder".to_string(), + ) + .into()); + } + }; + + // Build the source + let source_result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| builder.build())); + + let source = match source_result { + Ok(Ok(s)) => { + info!("[MONITOR_CAPTURE] Monitor capture source created"); + std::io::stderr().flush().ok(); + s + } + Ok(Err(e)) => { + error!( + "[MONITOR_CAPTURE] Failed to create monitor capture source: {:?}", + e + ); + std::io::stderr().flush().ok(); + return Err(RecordingError::StartError(format!( + "Failed to create monitor capture source: {:?}", + e + )) + .into()); + } + Err(panic_info) => { + error!( + "[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}", + panic_info + ); + std::io::stderr().flush().ok(); + eprintln!( + "[MONITOR_CAPTURE] PANIC creating monitor capture source: {:?}", + panic_info + ); + return Err(RecordingError::StartError( + "Panic creating monitor capture source".to_string(), + ) + .into()); + } + }; + + // Add source to scene + info!("[MONITOR_CAPTURE] Adding source to scene..."); + std::io::stderr().flush().ok(); + scene.add_source(source).map_err(|e| { + error!("[MONITOR_CAPTURE] Failed to add source to scene: {:?}", e); + std::io::stderr().flush().ok(); + RecordingError::StartError(format!("Failed to add source to scene: {:?}", e)) + })?; + info!("[MONITOR_CAPTURE] Source added to scene"); + std::io::stderr().flush().ok(); + + // Set the scene as active + info!("[MONITOR_CAPTURE] Setting scene as active on channel 0..."); + std::io::stderr().flush().ok(); + scene.set_to_channel(0).map_err(|e| { + error!("[MONITOR_CAPTURE] Failed to set scene: {:?}", e); + RecordingError::StartError(format!("Failed to set scene: {:?}", e)) + })?; + + info!("[MONITOR_CAPTURE] Monitor capture source configured successfully"); + std::io::stderr().flush().ok(); Ok(()) } @@ -216,49 +824,16 @@ impl ObsContext { info!("Stopping OBS recording"); - // Note: Actual libobs recording stop would happen here - // This is a stub implementation + if let Some(mut output) = self.output.take() { + output.stop().map_err(|e| { + RecordingError::StopError(format!("Failed to stop output: {:?}", e)) + })?; + } self.recording = false; self.current_output = None; - debug!("OBS recording stopped"); - Ok(()) - } - - /// Initialize OBS. - fn initialize(&mut self) -> Result<()> { - if self.initialized { - return Ok(()); - } - - info!("Initializing OBS context"); - - // Note: Actual libobs initialization would happen here - // This would involve: - // 1. obs_startup() - // 2. obs_reset_video() - // 3. obs_reset_audio() - // 4. Loading modules (obs-ffmpeg, etc.) - // 5. Creating scene and source - - // For now, we simulate initialization - debug!( - "OBS video config: {}x{} @ {}fps", - self.video_info.output_width, - self.video_info.output_height, - self.video_info.fps_num / self.video_info.fps_den - ); - - if self.audio_settings.enabled { - debug!( - "OBS audio config: {} channels @ {}Hz", - self.audio_settings.channels, self.audio_settings.sample_rate - ); - } - - self.initialized = true; - info!("OBS context initialized successfully"); + info!("OBS recording stopped successfully"); Ok(()) } @@ -268,13 +843,14 @@ impl ObsContext { self.stop_recording()?; } - if self.initialized { + if self.output.is_some() { + self.output = None; + } + + if self.context.is_some() { info!("Shutting down OBS context"); - - // Note: Actual libobs shutdown would happen here - // obs_shutdown() - - self.initialized = false; + // The LibObsContext handles cleanup on drop + self.context = None; } Ok(()) @@ -289,49 +865,6 @@ impl Drop for ObsContext { } } -// Note: When actual libobs bindings are available, we would add -// FFI bindings here. For now, this provides the interface that -// will be implemented with real libobs calls. - -/// Stub module for libobs FFI bindings. -/// -/// When actual bindings are available, this would contain: -/// - obs_startup -/// - obs_shutdown -/// - obs_reset_video -/// - obs_reset_audio -/// - obs_scene_create -/// - obs_source_create -/// - obs_output_create -/// - obs_encoder_create -/// etc. -pub mod ffi { - //! libobs FFI bindings (stub). - //! - //! This module will contain the actual FFI bindings to libobs. - //! Currently using stubs until libobs-rs or similar bindings are integrated. - - /// Placeholder for obs_video_info struct. - #[repr(C)] - pub struct ObsVideoInfo { - pub adapter: i32, - pub output_width: u32, - pub output_height: u32, - pub fps_num: u32, - pub fps_den: u32, - pub base_width: u32, - pub base_height: u32, - pub output_format: u32, - } - - /// Placeholder for obs_audio_info struct. - #[repr(C)] - pub struct ObsAudioInfo { - pub samples_per_sec: u32, - pub speakers: u32, - } -} - #[cfg(test)] mod tests { use super::*; @@ -347,14 +880,4 @@ mod tests { assert!(!context.is_initialized()); assert!(!context.is_recording()); } - - #[test] - fn test_video_info_from_settings() { - let settings = VideoSettings::default(); - let info = ObsVideoInfo::from_settings(&settings); - - assert_eq!(info.output_width, 1920); - assert_eq!(info.output_height, 1080); - assert_eq!(info.fps_num, 60); - } } diff --git a/record-daemon/src/state/machine.rs b/record-daemon/src/state/machine.rs index f23d829..d9ab262 100644 --- a/record-daemon/src/state/machine.rs +++ b/record-daemon/src/state/machine.rs @@ -179,7 +179,9 @@ impl DaemonStateMachine { // From Recording (DaemonState::Recording, StateTransition::GameEnded) => Some(DaemonState::Monitoring), // Allow GameStarted from Recording (handles case where GameEnded wasn't received) - (DaemonState::Recording, StateTransition::GameStarted { .. }) => Some(DaemonState::Recording), + (DaemonState::Recording, StateTransition::GameStarted { .. }) => { + Some(DaemonState::Recording) + } (DaemonState::Recording, StateTransition::ClientStopped) => Some(DaemonState::Idle), (DaemonState::Recording, StateTransition::Error(_)) => Some(DaemonState::Error), (DaemonState::Recording, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown), diff --git a/record-daemon/src/timeline/mapper.rs b/record-daemon/src/timeline/mapper.rs index 678ccb7..10b2c97 100644 --- a/record-daemon/src/timeline/mapper.rs +++ b/record-daemon/src/timeline/mapper.rs @@ -41,7 +41,7 @@ impl EventMapper { } /// Map a game event to video and game timestamps. - pub fn map_event(&self, event: &GameEvent) -> Option<(Duration, Option)> { + pub fn map_event(&self, _event: &GameEvent) -> Option<(Duration, Option)> { let start_time = self.start_time?; let now = Utc::now(); diff --git a/record-daemon/src/timeline/mod.rs b/record-daemon/src/timeline/mod.rs index c2dffec..f40048b 100644 --- a/record-daemon/src/timeline/mod.rs +++ b/record-daemon/src/timeline/mod.rs @@ -152,7 +152,7 @@ fn event_type_name(event: &GameEvent) -> String { #[cfg(test)] mod tests { use super::*; - use crate::lqp::{KillEvent, ObjectiveEvent, ObjectiveType}; + use crate::lqp::KillEvent; #[test] fn test_timeline_creation() {