diff --git a/.vscode/settings.json b/.vscode/settings.json index b78607e..16d6c7b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "codegen", "ctrlassist", "deadzone", + "demux", "devnode", "devnodes", "devpath", @@ -15,6 +16,7 @@ "gamepads", "gilrs", "hidraw", + "ksni", "libc", "libudev", "metainfo", diff --git a/Cargo.lock b/Cargo.lock index 2dab5a5..3c55a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" +checksum = "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce" dependencies = [ "enumflags2", "futures-channel", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" @@ -275,9 +275,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "shlex", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -362,10 +362,10 @@ version = "0.3.0" dependencies = [ "ashpd", "clap", - "ctrlc", "dirs", "env_logger", "evdev", + "fs2", "futures", "futures-util", "gilrs", @@ -375,23 +375,13 @@ dependencies = [ "notify-rust", "parking_lot", "serde", + "signal-hook", "tokio", "toml", "udev", "uuid", ] -[[package]] -name = "ctrlc" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" -dependencies = [ - "dispatch2", - "nix 0.30.1", - "windows-sys 0.61.2", -] - [[package]] name = "deranged" version = "0.5.5" @@ -429,8 +419,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags", - "block2", - "libc", "objc2", ] @@ -552,9 +540,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "fnv" @@ -571,6 +559,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -681,9 +679,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -705,7 +703,8 @@ dependencies = [ [[package]] name = "gilrs" version = "0.11.1" -source = "git+https://gitlab.com/gilrs-project/gilrs.git?branch=maint#984c4337f090431c0ab8f59287de9b4266d14ab2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462" dependencies = [ "fnv", "gilrs-core", @@ -717,7 +716,8 @@ dependencies = [ [[package]] name = "gilrs-core" version = "0.6.7" -source = "git+https://gitlab.com/gilrs-project/gilrs.git?branch=maint#984c4337f090431c0ab8f59287de9b4266d14ab2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04" dependencies = [ "inotify", "js-sys", @@ -731,7 +731,7 @@ dependencies = [ "vec_map", "wasm-bindgen", "web-sys", - "windows", + "windows 0.62.2", ] [[package]] @@ -868,9 +868,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -915,9 +915,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -928,9 +928,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -968,9 +968,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -1073,7 +1073,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -1260,9 +1259,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -1308,9 +1307,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -1326,9 +1325,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1367,9 +1366,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" dependencies = [ "getrandom 0.3.4", ] @@ -1389,7 +1388,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror", ] @@ -1504,12 +1503,23 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1541,12 +1551,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -1555,9 +1559,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1589,7 +1593,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml", "thiserror", - "windows", + "windows 0.61.3", "windows-version", ] @@ -1657,9 +1661,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -1796,14 +1800,15 @@ checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -1933,11 +1938,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -1946,7 +1963,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -1958,8 +1984,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -1968,9 +2007,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -2013,10 +2063,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -2026,6 +2086,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -2035,6 +2104,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2103,6 +2181,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -2257,9 +2344,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" dependencies = [ "async-broadcast", "async-executor", @@ -2275,8 +2362,9 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix", "serde", "serde_repr", "tokio", @@ -2292,9 +2380,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2307,30 +2395,29 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2393,9 +2480,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" dependencies = [ "endi", "enumflags2", @@ -2408,9 +2495,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2421,9 +2508,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f806491..435631d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,30 +30,29 @@ exclude = [ github = { repository = "ruffsl/CtrlAssist" } [dependencies] -clap = { version = "4.5.54", features = ["derive"] } -ctrlc = "3.5.1" -dirs = "6.0.0" -env_logger = "0.11.8" -evdev = "0.13.2" -# gilrs = "0.11.1" -# use gilrs from local path -# gilrs = { path = "../gilrs/gilrs" } -gilrs = { git = "https://gitlab.com/gilrs-project/gilrs.git", branch = "maint" } -libc = "0.2.180" -log = "0.4.29" -udev = "0.9.3" -uuid = "1.19.0" +clap = { version = "^4.5", features = ["derive"] } +dirs = "6" +env_logger = "0.11" +evdev = "0.13" +fs2 = "0.4" +gilrs = "^0.11.1" +libc = "0.2" +log = "0.4" +signal-hook = "0.4" tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } +udev = "0.9" +uuid = "1" + # New dependencies for tray -ashpd = "0.12.0" -futures = "0.3.31" -futures-util = "0.3.31" -ksni = "0.3.3" -notify-rust = "4.11.7" -parking_lot = "0.12.5" -serde = "1.0.228" -toml = "0.9.11" +ashpd = "0.12" +futures = "0.3" +futures-util = "0.3" +ksni = "^0.3.3" +notify-rust = "4.11" +parking_lot = "0.12" +serde = "1" +toml = "0.9" [package.metadata.deb] depends = "libudev1" diff --git a/README.md b/README.md index 4752807..02544ea 100644 --- a/README.md +++ b/README.md @@ -129,15 +129,16 @@ Use the `--help` flag for information on each CLI subcommand: ```sh $ ctrlassist --help -Multiplex multiple controllers into virtual gamepad +Controller Assist for gaming on Linux Usage: ctrlassist Commands: - list List all detected controllers and respective IDs - mux Multiplex connected controllers into virtual gamepad - tray Launch system tray app for graphical control - help Print this message or the help of the given subcommand(s) + list List all detected controllers and respective IDs + mux Multiplex connected controllers into virtual gamepad + demux Demultiplex one controller to multiple virtual gamepads + tray Launch system tray app for graphical control + help Print this message or the help of the given subcommand(s) Options: -h, --help Print help @@ -228,6 +229,12 @@ Target force feedback to either, none, or both physical controllers: $ ctrlassist mux --rumble both ``` +Some modes also support dynamically targeting active controllers: + +```sh +$ ctrlassist mux --mode toggle --rumble active +``` + ### 🙈 Hide Physical Devices Multiple hiding strategies are available to avoid input conflicts: @@ -407,6 +414,8 @@ CtrlAssist was first created out of personal necessity. After migrating househol Following its initial release and personal household success, as well as the broader trend of Linux adoption, CtrlAssist evolved from a simple CLI tool into a desktop-friendly utility. This category of accessibility features has significantly enhanced family gaming time, transforming passive spectators into active participants. From helping grandparents experience new immersive and interactive single player stories, to leveling age gaps across nieces and nephews in multiplayer PvPs, to rescuing friends from critical damage and finally overcoming a challenging boss, assistive players may expect as much enjoyment as primary players. +
More QA + ### **What games are compatible?** CtrlAssist works with most Linux games that support standard gamepad input. Some games or launchers may require restarting after changing controller visibility or virtual device settings. Note that many games have no explicit setting for controller selection, thus the motivation for various hiding strategies to avoid input conflicts between physical and virtual devices. For best compatibility, use the appropriate hiding strategy as described above. @@ -431,12 +440,68 @@ Yes! For scenarios where multiple primary players would like assistance, such as Additionally, each instance can use different hiding strategies, spoofing options, and rumble targets to suit the needs of each player. Just be mindful that selected hiding strategies do not conflict between instances, causing one virtual device to be hidden by another instance. -### **How else can CtrlAssist be used?** +
+ +# 🧑‍🍳 Cookbook + +Other basic examples of how else CtrlAssist can be used include: -Examples include: - Dual wielding one for each hand, like split Nintendo Switch Joy-Cons - Combining a standard gamepad with an accessible Xbox Adaptive Controller -- Assist multiple Primary players using demux outputs as mux Assist inputs + +However, because running multiple instances is possible, more complex setups can be achieved by chaining multiple mux and demux commands together. + +## Couch Co-Op Swap + +Two players can take turns assisting each other using toggle mode: + +```sh +$ ctrlassist list +(0) PS4 Controller +(1) PS5 Controller +``` + +```bash +#!/usr/bin/env bash +ctrlassist mux --primary 0 --assist 1 --mode toggle --hide steam & +sleep 1 # wait to ensure virtual devices are discoverable +ctrlassist mux --primary 1 --assist 0 --mode toggle --hide steam & +wait +``` + +```mermaid +flowchart LR + A[Assist 1
Controller] --> C[Mux
Toggle] + B[Assist 2
Controller] --> C + A --> D[Mux
Toggle] + B --> D + C --> E[Virtual 1
Gamepad] + D --> F[Virtual 2
Gamepad] +``` + +Or specify a single assist controller by toggling once before duplicating first mux. + +## Double Agent Tag Team + +Assist multiple Primary players using demux outputs as mux Assist inputs: + +```sh +$ ctrlassist list +(0) PS4 Controller +(1) PS5 Controller +(2) Xbox One Controller +``` + +```bash +#!/usr/bin/env bash +ctrlassist demux --primary 2 --virtuals 2 --mode unicast \ + --hide steam --spoof primary & # spoof to not also hide virtual 1 & 2 +sleep 1 # wait to ensure virtual devices are discoverable +ctrlassist mux --primary 0 --assist 3 --mode priority --hide steam & +sleep 1 # wait to ensure virtual devices are discoverable +ctrlassist mux --primary 1 --assist 4 --mode priority --hide steam & +wait +``` ```mermaid flowchart LR @@ -451,6 +516,8 @@ flowchart LR F --> J[Virtual 2
Gamepad] ``` +Simply scale with number of Primary players by adjusting the `--virtuals` count. + # 📚 Background - [Controller Assist on Xbox and Windows](https://support.xbox.com/en-US/help/account-profile/accessibility/copilot) diff --git a/flatpak/cargo-sources.json b/flatpak/cargo-sources.json index 5bb323f..d3a22b7 100644 --- a/flatpak/cargo-sources.json +++ b/flatpak/cargo-sources.json @@ -80,14 +80,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ashpd/ashpd-0.12.0.crate", - "sha256": "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd", - "dest": "cargo/vendor/ashpd-0.12.0" + "url": "https://static.crates.io/crates/ashpd/ashpd-0.12.1.crate", + "sha256": "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce", + "dest": "cargo/vendor/ashpd-0.12.1" }, { "type": "inline", - "contents": "{\"package\": \"da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd\", \"files\": {}}", - "dest": "cargo/vendor/ashpd-0.12.0", + "contents": "{\"package\": \"618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce\", \"files\": {}}", + "dest": "cargo/vendor/ashpd-0.12.1", "dest-filename": ".cargo-checksum.json" }, { @@ -301,14 +301,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.0.crate", - "sha256": "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43", - "dest": "cargo/vendor/bumpalo-3.19.0" + "url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.1.crate", + "sha256": "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510", + "dest": "cargo/vendor/bumpalo-3.19.1" }, { "type": "inline", - "contents": "{\"package\": \"46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43\", \"files\": {}}", - "dest": "cargo/vendor/bumpalo-3.19.0", + "contents": "{\"package\": \"5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510\", \"files\": {}}", + "dest": "cargo/vendor/bumpalo-3.19.1", "dest-filename": ".cargo-checksum.json" }, { @@ -327,14 +327,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/cc/cc-1.2.50.crate", - "sha256": "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c", - "dest": "cargo/vendor/cc-1.2.50" + "url": "https://static.crates.io/crates/cc/cc-1.2.52.crate", + "sha256": "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3", + "dest": "cargo/vendor/cc-1.2.52" }, { "type": "inline", - "contents": "{\"package\": \"9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c\", \"files\": {}}", - "dest": "cargo/vendor/cc-1.2.50", + "contents": "{\"package\": \"cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3\", \"files\": {}}", + "dest": "cargo/vendor/cc-1.2.52", "dest-filename": ".cargo-checksum.json" }, { @@ -366,27 +366,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap/clap-4.5.51.crate", - "sha256": "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5", - "dest": "cargo/vendor/clap-4.5.51" + "url": "https://static.crates.io/crates/clap/clap-4.5.54.crate", + "sha256": "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394", + "dest": "cargo/vendor/clap-4.5.54" }, { "type": "inline", - "contents": "{\"package\": \"4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5\", \"files\": {}}", - "dest": "cargo/vendor/clap-4.5.51", + "contents": "{\"package\": \"c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394\", \"files\": {}}", + "dest": "cargo/vendor/clap-4.5.54", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.51.crate", - "sha256": "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a", - "dest": "cargo/vendor/clap_builder-4.5.51" + "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.54.crate", + "sha256": "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00", + "dest": "cargo/vendor/clap_builder-4.5.54" }, { "type": "inline", - "contents": "{\"package\": \"75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a\", \"files\": {}}", - "dest": "cargo/vendor/clap_builder-4.5.51", + "contents": "{\"package\": \"fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00\", \"files\": {}}", + "dest": "cargo/vendor/clap_builder-4.5.54", "dest-filename": ".cargo-checksum.json" }, { @@ -405,14 +405,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.6.crate", - "sha256": "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d", - "dest": "cargo/vendor/clap_lex-0.7.6" + "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.7.crate", + "sha256": "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32", + "dest": "cargo/vendor/clap_lex-0.7.7" }, { "type": "inline", - "contents": "{\"package\": \"a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d\", \"files\": {}}", - "dest": "cargo/vendor/clap_lex-0.7.6", + "contents": "{\"package\": \"c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32\", \"files\": {}}", + "dest": "cargo/vendor/clap_lex-0.7.7", "dest-filename": ".cargo-checksum.json" }, { @@ -441,32 +441,6 @@ "dest": "cargo/vendor/concurrent-queue-2.5.0", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/core-foundation/core-foundation-0.10.1.crate", - "sha256": "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6", - "dest": "cargo/vendor/core-foundation-0.10.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6\", \"files\": {}}", - "dest": "cargo/vendor/core-foundation-0.10.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/core-foundation-sys/core-foundation-sys-0.8.7.crate", - "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b", - "dest": "cargo/vendor/core-foundation-sys-0.8.7" - }, - { - "type": "inline", - "contents": "{\"package\": \"773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b\", \"files\": {}}", - "dest": "cargo/vendor/core-foundation-sys-0.8.7", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -480,19 +454,6 @@ "dest": "cargo/vendor/crossbeam-utils-0.8.21", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ctrlc/ctrlc-3.5.1.crate", - "sha256": "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790", - "dest": "cargo/vendor/ctrlc-3.5.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790\", \"files\": {}}", - "dest": "cargo/vendor/ctrlc-3.5.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -704,14 +665,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.5.crate", - "sha256": "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844", - "dest": "cargo/vendor/find-msvc-tools-0.1.5" + "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.7.crate", + "sha256": "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41", + "dest": "cargo/vendor/find-msvc-tools-0.1.7" }, { "type": "inline", - "contents": "{\"package\": \"3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844\", \"files\": {}}", - "dest": "cargo/vendor/find-msvc-tools-0.1.5", + "contents": "{\"package\": \"f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41\", \"files\": {}}", + "dest": "cargo/vendor/find-msvc-tools-0.1.7", "dest-filename": ".cargo-checksum.json" }, { @@ -740,6 +701,19 @@ "dest": "cargo/vendor/form_urlencoded-1.2.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/fs2/fs2-0.4.3.crate", + "sha256": "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213", + "dest": "cargo/vendor/fs2-0.4.3" + }, + { + "type": "inline", + "contents": "{\"package\": \"9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213\", \"files\": {}}", + "dest": "cargo/vendor/fs2-0.4.3", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -886,14 +860,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.16.crate", - "sha256": "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592", - "dest": "cargo/vendor/getrandom-0.2.16" + "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.17.crate", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0", + "dest": "cargo/vendor/getrandom-0.2.17" }, { "type": "inline", - "contents": "{\"package\": \"335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592\", \"files\": {}}", - "dest": "cargo/vendor/getrandom-0.2.16", + "contents": "{\"package\": \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\", \"files\": {}}", + "dest": "cargo/vendor/getrandom-0.2.17", "dest-filename": ".cargo-checksum.json" }, { @@ -912,27 +886,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/gilrs/gilrs-0.11.0.crate", - "sha256": "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f", - "dest": "cargo/vendor/gilrs-0.11.0" + "url": "https://static.crates.io/crates/gilrs/gilrs-0.11.1.crate", + "sha256": "3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462", + "dest": "cargo/vendor/gilrs-0.11.1" }, { "type": "inline", - "contents": "{\"package\": \"bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f\", \"files\": {}}", - "dest": "cargo/vendor/gilrs-0.11.0", + "contents": "{\"package\": \"3fa85c2e35dc565c90511917897ea4eae16b77f2773d5223536f7b602536d462\", \"files\": {}}", + "dest": "cargo/vendor/gilrs-0.11.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/gilrs-core/gilrs-core-0.6.6.crate", - "sha256": "be11a71ac3564f6965839e2ed275bf4fcf5ce16d80d396e1dfdb7b2d80bd587e", - "dest": "cargo/vendor/gilrs-core-0.6.6" + "url": "https://static.crates.io/crates/gilrs-core/gilrs-core-0.6.7.crate", + "sha256": "d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04", + "dest": "cargo/vendor/gilrs-core-0.6.7" }, { "type": "inline", - "contents": "{\"package\": \"be11a71ac3564f6965839e2ed275bf4fcf5ce16d80d396e1dfdb7b2d80bd587e\", \"files\": {}}", - "dest": "cargo/vendor/gilrs-core-0.6.6", + "contents": "{\"package\": \"d23f2cc5144060a7f8d9e02d3fce5d06705376568256a509cdbc3c24d47e4f04\", \"files\": {}}", + "dest": "cargo/vendor/gilrs-core-0.6.7", "dest-filename": ".cargo-checksum.json" }, { @@ -1120,14 +1094,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/indexmap/indexmap-2.12.1.crate", - "sha256": "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2", - "dest": "cargo/vendor/indexmap-2.12.1" + "url": "https://static.crates.io/crates/indexmap/indexmap-2.13.0.crate", + "sha256": "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017", + "dest": "cargo/vendor/indexmap-2.13.0" }, { "type": "inline", - "contents": "{\"package\": \"0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2\", \"files\": {}}", - "dest": "cargo/vendor/indexmap-2.12.1", + "contents": "{\"package\": \"7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017\", \"files\": {}}", + "dest": "cargo/vendor/indexmap-2.13.0", "dest-filename": ".cargo-checksum.json" }, { @@ -1156,19 +1130,6 @@ "dest": "cargo/vendor/inotify-sys-0.1.5", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/io-kit-sys/io-kit-sys-0.4.1.crate", - "sha256": "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b", - "dest": "cargo/vendor/io-kit-sys-0.4.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b\", \"files\": {}}", - "dest": "cargo/vendor/io-kit-sys-0.4.1", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -1198,27 +1159,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/jiff/jiff-0.2.16.crate", - "sha256": "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35", - "dest": "cargo/vendor/jiff-0.2.16" + "url": "https://static.crates.io/crates/jiff/jiff-0.2.18.crate", + "sha256": "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50", + "dest": "cargo/vendor/jiff-0.2.18" }, { "type": "inline", - "contents": "{\"package\": \"49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35\", \"files\": {}}", - "dest": "cargo/vendor/jiff-0.2.16", + "contents": "{\"package\": \"e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50\", \"files\": {}}", + "dest": "cargo/vendor/jiff-0.2.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/jiff-static/jiff-static-0.2.16.crate", - "sha256": "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69", - "dest": "cargo/vendor/jiff-static-0.2.16" + "url": "https://static.crates.io/crates/jiff-static/jiff-static-0.2.18.crate", + "sha256": "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78", + "dest": "cargo/vendor/jiff-static-0.2.18" }, { "type": "inline", - "contents": "{\"package\": \"980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69\", \"files\": {}}", - "dest": "cargo/vendor/jiff-static-0.2.16", + "contents": "{\"package\": \"e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78\", \"files\": {}}", + "dest": "cargo/vendor/jiff-static-0.2.18", "dest-filename": ".cargo-checksum.json" }, { @@ -1250,27 +1211,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/libc/libc-0.2.178.crate", - "sha256": "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091", - "dest": "cargo/vendor/libc-0.2.178" + "url": "https://static.crates.io/crates/libc/libc-0.2.180.crate", + "sha256": "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc", + "dest": "cargo/vendor/libc-0.2.180" }, { "type": "inline", - "contents": "{\"package\": \"37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091\", \"files\": {}}", - "dest": "cargo/vendor/libc-0.2.178", + "contents": "{\"package\": \"bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc\", \"files\": {}}", + "dest": "cargo/vendor/libc-0.2.180", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/libredox/libredox-0.1.11.crate", - "sha256": "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50", - "dest": "cargo/vendor/libredox-0.1.11" + "url": "https://static.crates.io/crates/libredox/libredox-0.1.12.crate", + "sha256": "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616", + "dest": "cargo/vendor/libredox-0.1.12" }, { "type": "inline", - "contents": "{\"package\": \"df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50\", \"files\": {}}", - "dest": "cargo/vendor/libredox-0.1.11", + "contents": "{\"package\": \"3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616\", \"files\": {}}", + "dest": "cargo/vendor/libredox-0.1.12", "dest-filename": ".cargo-checksum.json" }, { @@ -1328,14 +1289,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/log/log-0.4.28.crate", - "sha256": "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432", - "dest": "cargo/vendor/log-0.4.28" + "url": "https://static.crates.io/crates/log/log-0.4.29.crate", + "sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897", + "dest": "cargo/vendor/log-0.4.29" }, { "type": "inline", - "contents": "{\"package\": \"34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432\", \"files\": {}}", - "dest": "cargo/vendor/log-0.4.28", + "contents": "{\"package\": \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\", \"files\": {}}", + "dest": "cargo/vendor/log-0.4.29", "dest-filename": ".cargo-checksum.json" }, { @@ -1351,19 +1312,6 @@ "dest": "cargo/vendor/mac-notification-sys-0.6.9", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/mach2/mach2-0.4.3.crate", - "sha256": "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44", - "dest": "cargo/vendor/mach2-0.4.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44\", \"files\": {}}", - "dest": "cargo/vendor/mach2-0.4.3", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -1507,6 +1455,19 @@ "dest": "cargo/vendor/objc2-foundation-0.3.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/objc2-io-kit/objc2-io-kit-0.3.2.crate", + "sha256": "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15", + "dest": "cargo/vendor/objc2-io-kit-0.3.2" + }, + { + "type": "inline", + "contents": "{\"package\": \"33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15\", \"files\": {}}", + "dest": "cargo/vendor/objc2-io-kit-0.3.2", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -1692,14 +1653,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.11.1.crate", - "sha256": "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483", - "dest": "cargo/vendor/portable-atomic-1.11.1" + "url": "https://static.crates.io/crates/portable-atomic/portable-atomic-1.13.0.crate", + "sha256": "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950", + "dest": "cargo/vendor/portable-atomic-1.13.0" }, { "type": "inline", - "contents": "{\"package\": \"f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483\", \"files\": {}}", - "dest": "cargo/vendor/portable-atomic-1.11.1", + "contents": "{\"package\": \"f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950\", \"files\": {}}", + "dest": "cargo/vendor/portable-atomic-1.13.0", "dest-filename": ".cargo-checksum.json" }, { @@ -1770,14 +1731,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.103.crate", - "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", - "dest": "cargo/vendor/proc-macro2-1.0.103" + "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.105.crate", + "sha256": "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7", + "dest": "cargo/vendor/proc-macro2-1.0.105" }, { "type": "inline", - "contents": "{\"package\": \"5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8\", \"files\": {}}", - "dest": "cargo/vendor/proc-macro2-1.0.103", + "contents": "{\"package\": \"535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7\", \"files\": {}}", + "dest": "cargo/vendor/proc-macro2-1.0.105", "dest-filename": ".cargo-checksum.json" }, { @@ -1796,14 +1757,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/quote/quote-1.0.42.crate", - "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", - "dest": "cargo/vendor/quote-1.0.42" + "url": "https://static.crates.io/crates/quote/quote-1.0.43.crate", + "sha256": "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a", + "dest": "cargo/vendor/quote-1.0.43" }, { "type": "inline", - "contents": "{\"package\": \"a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f\", \"files\": {}}", - "dest": "cargo/vendor/quote-1.0.42", + "contents": "{\"package\": \"dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a\", \"files\": {}}", + "dest": "cargo/vendor/quote-1.0.43", "dest-filename": ".cargo-checksum.json" }, { @@ -1861,14 +1822,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rand_core/rand_core-0.9.3.crate", - "sha256": "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38", - "dest": "cargo/vendor/rand_core-0.9.3" + "url": "https://static.crates.io/crates/rand_core/rand_core-0.9.4.crate", + "sha256": "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa", + "dest": "cargo/vendor/rand_core-0.9.4" }, { "type": "inline", - "contents": "{\"package\": \"99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38\", \"files\": {}}", - "dest": "cargo/vendor/rand_core-0.9.3", + "contents": "{\"package\": \"4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa\", \"files\": {}}", + "dest": "cargo/vendor/rand_core-0.9.4", "dest-filename": ".cargo-checksum.json" }, { @@ -2056,14 +2017,27 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/signal-hook-registry/signal-hook-registry-1.4.7.crate", - "sha256": "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad", - "dest": "cargo/vendor/signal-hook-registry-1.4.7" + "url": "https://static.crates.io/crates/signal-hook/signal-hook-0.4.1.crate", + "sha256": "2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952", + "dest": "cargo/vendor/signal-hook-0.4.1" + }, + { + "type": "inline", + "contents": "{\"package\": \"2a37d01603c37b5466f808de79f845c7116049b0579adb70a6b7d47c1fa3a952\", \"files\": {}}", + "dest": "cargo/vendor/signal-hook-0.4.1", + "dest-filename": ".cargo-checksum.json" + }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/signal-hook-registry/signal-hook-registry-1.4.8.crate", + "sha256": "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b", + "dest": "cargo/vendor/signal-hook-registry-1.4.8" }, { "type": "inline", - "contents": "{\"package\": \"7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad\", \"files\": {}}", - "dest": "cargo/vendor/signal-hook-registry-1.4.7", + "contents": "{\"package\": \"c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b\", \"files\": {}}", + "dest": "cargo/vendor/signal-hook-registry-1.4.8", "dest-filename": ".cargo-checksum.json" }, { @@ -2118,19 +2092,6 @@ "dest": "cargo/vendor/stable_deref_trait-1.2.1", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/static_assertions/static_assertions-1.1.0.crate", - "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", - "dest": "cargo/vendor/static_assertions-1.1.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\", \"files\": {}}", - "dest": "cargo/vendor/static_assertions-1.1.0", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2147,14 +2108,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/syn/syn-2.0.110.crate", - "sha256": "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea", - "dest": "cargo/vendor/syn-2.0.110" + "url": "https://static.crates.io/crates/syn/syn-2.0.114.crate", + "sha256": "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a", + "dest": "cargo/vendor/syn-2.0.114" }, { "type": "inline", - "contents": "{\"package\": \"a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea\", \"files\": {}}", - "dest": "cargo/vendor/syn-2.0.110", + "contents": "{\"package\": \"d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a\", \"files\": {}}", + "dest": "cargo/vendor/syn-2.0.114", "dest-filename": ".cargo-checksum.json" }, { @@ -2277,14 +2238,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/tokio/tokio-1.48.0.crate", - "sha256": "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408", - "dest": "cargo/vendor/tokio-1.48.0" + "url": "https://static.crates.io/crates/tokio/tokio-1.49.0.crate", + "sha256": "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86", + "dest": "cargo/vendor/tokio-1.49.0" }, { "type": "inline", - "contents": "{\"package\": \"ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408\", \"files\": {}}", - "dest": "cargo/vendor/tokio-1.48.0", + "contents": "{\"package\": \"72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86\", \"files\": {}}", + "dest": "cargo/vendor/tokio-1.49.0", "dest-filename": ".cargo-checksum.json" }, { @@ -2303,14 +2264,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml/toml-0.9.10+spec-1.1.0.crate", - "sha256": "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48", - "dest": "cargo/vendor/toml-0.9.10+spec-1.1.0" + "url": "https://static.crates.io/crates/toml/toml-0.9.11+spec-1.1.0.crate", + "sha256": "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46", + "dest": "cargo/vendor/toml-0.9.11+spec-1.1.0" }, { "type": "inline", - "contents": "{\"package\": \"0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48\", \"files\": {}}", - "dest": "cargo/vendor/toml-0.9.10+spec-1.1.0", + "contents": "{\"package\": \"f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46\", \"files\": {}}", + "dest": "cargo/vendor/toml-0.9.11+spec-1.1.0", "dest-filename": ".cargo-checksum.json" }, { @@ -2446,14 +2407,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/url/url-2.5.7.crate", - "sha256": "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b", - "dest": "cargo/vendor/url-2.5.7" + "url": "https://static.crates.io/crates/url/url-2.5.8.crate", + "sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed", + "dest": "cargo/vendor/url-2.5.8" }, { "type": "inline", - "contents": "{\"package\": \"08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b\", \"files\": {}}", - "dest": "cargo/vendor/url-2.5.7", + "contents": "{\"package\": \"ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed\", \"files\": {}}", + "dest": "cargo/vendor/url-2.5.8", "dest-filename": ".cargo-checksum.json" }, { @@ -3252,66 +3213,66 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus/zbus-5.12.0.crate", - "sha256": "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91", - "dest": "cargo/vendor/zbus-5.12.0" + "url": "https://static.crates.io/crates/zbus/zbus-5.13.1.crate", + "sha256": "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b", + "dest": "cargo/vendor/zbus-5.13.1" }, { "type": "inline", - "contents": "{\"package\": \"b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91\", \"files\": {}}", - "dest": "cargo/vendor/zbus-5.12.0", + "contents": "{\"package\": \"17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b\", \"files\": {}}", + "dest": "cargo/vendor/zbus-5.13.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus_macros/zbus_macros-5.12.0.crate", - "sha256": "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314", - "dest": "cargo/vendor/zbus_macros-5.12.0" + "url": "https://static.crates.io/crates/zbus_macros/zbus_macros-5.13.1.crate", + "sha256": "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37", + "dest": "cargo/vendor/zbus_macros-5.13.1" }, { "type": "inline", - "contents": "{\"package\": \"1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314\", \"files\": {}}", - "dest": "cargo/vendor/zbus_macros-5.12.0", + "contents": "{\"package\": \"aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37\", \"files\": {}}", + "dest": "cargo/vendor/zbus_macros-5.13.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zbus_names/zbus_names-4.2.0.crate", - "sha256": "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97", - "dest": "cargo/vendor/zbus_names-4.2.0" + "url": "https://static.crates.io/crates/zbus_names/zbus_names-4.3.1.crate", + "sha256": "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f", + "dest": "cargo/vendor/zbus_names-4.3.1" }, { "type": "inline", - "contents": "{\"package\": \"7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97\", \"files\": {}}", - "dest": "cargo/vendor/zbus_names-4.2.0", + "contents": "{\"package\": \"ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f\", \"files\": {}}", + "dest": "cargo/vendor/zbus_names-4.3.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.31.crate", - "sha256": "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3", - "dest": "cargo/vendor/zerocopy-0.8.31" + "url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.33.crate", + "sha256": "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd", + "dest": "cargo/vendor/zerocopy-0.8.33" }, { "type": "inline", - "contents": "{\"package\": \"fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3\", \"files\": {}}", - "dest": "cargo/vendor/zerocopy-0.8.31", + "contents": "{\"package\": \"668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-0.8.33", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.31.crate", - "sha256": "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a", - "dest": "cargo/vendor/zerocopy-derive-0.8.31" + "url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.33.crate", + "sha256": "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1", + "dest": "cargo/vendor/zerocopy-derive-0.8.33" }, { "type": "inline", - "contents": "{\"package\": \"d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a\", \"files\": {}}", - "dest": "cargo/vendor/zerocopy-derive-0.8.31", + "contents": "{\"package\": \"2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-derive-0.8.33", "dest-filename": ".cargo-checksum.json" }, { @@ -3382,40 +3343,40 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant/zvariant-5.8.0.crate", - "sha256": "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c", - "dest": "cargo/vendor/zvariant-5.8.0" + "url": "https://static.crates.io/crates/zvariant/zvariant-5.9.1.crate", + "sha256": "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543", + "dest": "cargo/vendor/zvariant-5.9.1" }, { "type": "inline", - "contents": "{\"package\": \"2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c\", \"files\": {}}", - "dest": "cargo/vendor/zvariant-5.8.0", + "contents": "{\"package\": \"326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543\", \"files\": {}}", + "dest": "cargo/vendor/zvariant-5.9.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant_derive/zvariant_derive-5.8.0.crate", - "sha256": "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006", - "dest": "cargo/vendor/zvariant_derive-5.8.0" + "url": "https://static.crates.io/crates/zvariant_derive/zvariant_derive-5.9.1.crate", + "sha256": "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7", + "dest": "cargo/vendor/zvariant_derive-5.9.1" }, { "type": "inline", - "contents": "{\"package\": \"da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006\", \"files\": {}}", - "dest": "cargo/vendor/zvariant_derive-5.8.0", + "contents": "{\"package\": \"ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7\", \"files\": {}}", + "dest": "cargo/vendor/zvariant_derive-5.9.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zvariant_utils/zvariant_utils-3.2.1.crate", - "sha256": "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599", - "dest": "cargo/vendor/zvariant_utils-3.2.1" + "url": "https://static.crates.io/crates/zvariant_utils/zvariant_utils-3.3.0.crate", + "sha256": "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9", + "dest": "cargo/vendor/zvariant_utils-3.3.0" }, { "type": "inline", - "contents": "{\"package\": \"c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599\", \"files\": {}}", - "dest": "cargo/vendor/zvariant_utils-3.2.1", + "contents": "{\"package\": \"f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9\", \"files\": {}}", + "dest": "cargo/vendor/zvariant_utils-3.3.0", "dest-filename": ".cargo-checksum.json" }, { diff --git a/src/demux/manager.rs b/src/demux/manager.rs index 99aa1bf..662a269 100644 --- a/src/demux/manager.rs +++ b/src/demux/manager.rs @@ -1,8 +1,9 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; use crate::demux::runtime::DemuxRuntimeSettings; use crate::utils::evdev::VirtualGamepadInfo; use crate::utils::hide::ScopedDeviceHider; -use crate::{DemuxRumbleTarget, HideType, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use evdev::Device; use gilrs::{GamepadId, Gilrs}; use log::info; @@ -101,11 +102,13 @@ pub fn start_demux( let mut v_uinput = crate::utils::evdev::create_virtual_gamepad(&virtual_info, tag_str)?; let v_resource = crate::utils::gilrs::wait_for_virtual_device(&mut v_uinput)?; - info!( - "Virtual: {} @ {}", + let virtual_msg = format!( + "Virtual: (#) {} @ {}", v_resource.name, v_resource.path.display() ); + info!("{}", virtual_msg); + println!("{}", virtual_msg); virtual_devices.push(VirtualDeviceInfo { path: v_resource.path.clone(), diff --git a/src/demux/mod.rs b/src/demux/mod.rs index f880e97..a13b963 100644 --- a/src/demux/mod.rs +++ b/src/demux/mod.rs @@ -1,3 +1,12 @@ pub mod manager; pub mod modes; pub mod runtime; + +#[derive( + clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, +)] +pub enum DemuxRumbleTarget { + #[default] + Active, + None, +} diff --git a/src/demux/runtime.rs b/src/demux/runtime.rs index aa4ab8b..743df3b 100644 --- a/src/demux/runtime.rs +++ b/src/demux/runtime.rs @@ -1,3 +1,4 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; use crate::utils::ff::{EffectManager, PhysicalFFDev}; use crate::utils::gilrs::GamepadResource; @@ -12,16 +13,6 @@ use std::time::Duration; const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); -/// Rumble target for demux -#[derive( - clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, -)] -pub enum DemuxRumbleTarget { - #[default] - Active, - None, -} - /// Runtime-updatable demux settings pub struct DemuxRuntimeSettings { pub mode: Arc>, diff --git a/src/main.rs b/src/main.rs index 068e5ac..1b8217e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ +use crate::demux::DemuxRumbleTarget; +use crate::mux::MuxRumbleTarget; use clap::{Parser, Subcommand, ValueEnum}; -use demux::runtime::DemuxRumbleTarget; use log::info; use serde::{Deserialize, Serialize}; +use signal_hook::consts::signal::{SIGINT, SIGTERM}; +use signal_hook::iterator::Signals; use std::error::Error; mod demux; @@ -55,8 +58,8 @@ struct MuxArgs { mode: crate::mux::modes::MuxModeType, /// Rumble target for virtual device. - #[arg(long, value_enum, default_value_t = RumbleTarget::default())] - rumble: RumbleTarget, + #[arg(long, value_enum, default_value_t = MuxRumbleTarget::default())] + rumble: MuxRumbleTarget, } #[derive(clap::Args, Debug)] @@ -96,19 +99,10 @@ pub enum HideType { #[derive(ValueEnum, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub enum SpoofTarget { - Primary, Assist, #[default] None, -} - -#[derive(ValueEnum, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub enum RumbleTarget { Primary, - Assist, - #[default] - Both, - None, } #[tokio::main] @@ -202,10 +196,14 @@ fn run_mux(args: MuxArgs) -> Result<(), Box> { mux_handle.0.shutdown(); }); - ctrlc::set_handler(move || { - println!("\nShutting down..."); - let _ = shutdown_tx.send(()); - })?; + // Handle both SIGINT and SIGTERM + let mut signals = Signals::new([SIGINT, SIGTERM])?; + std::thread::spawn(move || { + if let Some(_sig) = signals.forever().next() { + println!("\nShutting down..."); + let _ = shutdown_tx.send(()); + } + }); info!("Mux Active. Press Ctrl+C to exit."); println!("Mux Active. Press Ctrl+C to exit."); @@ -255,10 +253,14 @@ fn run_demux(args: DemuxArgs) -> Result<(), Box> { demux_handle.0.shutdown(); }); - ctrlc::set_handler(move || { - println!("\nShutting down..."); - let _ = shutdown_tx.send(()); - })?; + // Handle both SIGINT and SIGTERM + let mut signals = Signals::new([SIGINT, SIGTERM])?; + std::thread::spawn(move || { + if let Some(_sig) = signals.forever().next() { + println!("\nShutting down..."); + let _ = shutdown_tx.send(()); + } + }); info!("Demux Active. Press Ctrl+C to exit."); println!("Demux Active. Press Ctrl+C to exit."); diff --git a/src/mux/manager.rs b/src/mux/manager.rs index eba0283..dcd2bfb 100644 --- a/src/mux/manager.rs +++ b/src/mux/manager.rs @@ -2,7 +2,7 @@ use crate::mux::modes::MuxModeType; use crate::mux::runtime::RuntimeSettings; use crate::utils::evdev::VirtualGamepadInfo; use crate::utils::hide::ScopedDeviceHider; -use crate::{HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, MuxRumbleTarget, SpoofTarget}; use evdev::Device; use gilrs::{GamepadId, Gilrs}; use log::info; @@ -19,7 +19,7 @@ pub struct MuxConfig { pub mode: MuxModeType, pub hide: HideType, pub spoof: SpoofTarget, - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, } /// Handle to a running mux session @@ -93,14 +93,21 @@ pub fn start_mux( let v_resource = crate::utils::gilrs::wait_for_virtual_device(&mut v_uinput)?; let virtual_device_path = v_resource.path.clone(); - info!( - "Virtual: {} @ {}", + let virtual_msg = format!( + "Virtual: (#) {} @ {}", v_resource.name, v_resource.path.display() ); + info!("{}", virtual_msg); + println!("{}", virtual_msg); // Create runtime settings - let runtime_settings = Arc::new(RuntimeSettings::new(config.mode, config.rumble)); + let runtime_settings = Arc::new(RuntimeSettings::new( + config.mode, + config.rumble, + config.primary_id, + config.assist_id, + )); // Setup shutdown signal let shutdown = Arc::new(AtomicBool::new(false)); diff --git a/src/mux/mod.rs b/src/mux/mod.rs index f880e97..fe4c95f 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -1,3 +1,15 @@ pub mod manager; pub mod modes; pub mod runtime; + +#[derive( + clap::ValueEnum, Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, +)] +pub enum MuxRumbleTarget { + #[default] + Active, + Assist, + Both, + None, + Primary, +} diff --git a/src/mux/modes/average.rs b/src/mux/modes/average.rs index 9f387aa..d381e5a 100644 --- a/src/mux/modes/average.rs +++ b/src/mux/modes/average.rs @@ -1,5 +1,4 @@ -use super::{MuxMode, helpers}; -use evdev::InputEvent; +use super::{MuxMode, MuxOutput, helpers}; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; #[derive(Default)] @@ -12,7 +11,7 @@ impl MuxMode for AverageMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { // Filter out irrelevant devices if event.id != primary_id && event.id != assist_id { return None; @@ -42,7 +41,8 @@ impl MuxMode for AverageMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| vec![e]) + helpers::create_button_key_event(btn, is_pressed) + .map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { @@ -82,7 +82,7 @@ impl MuxMode for AverageMode { helpers::create_trigger_event(final_value, abs_axis) }; - Some(vec![event]) + Some(MuxOutput::events(vec![event])) } EventType::AxisChanged(axis, _, _) => { @@ -114,7 +114,7 @@ impl MuxMode for AverageMode { .filter_map(|(ax, val)| helpers::create_stick_event(ax, val)) .collect::>(); - (!events.is_empty()).then_some(events) + (!events.is_empty()).then_some(MuxOutput::events(events)) } _ => None, diff --git a/src/mux/modes/mod.rs b/src/mux/modes/mod.rs index 9c6bbec..bf1f0a2 100644 --- a/src/mux/modes/mod.rs +++ b/src/mux/modes/mod.rs @@ -16,6 +16,32 @@ pub enum MuxModeType { Toggle, } +/// Output from a mux mode handling an event +pub struct MuxOutput { + /// Events to forward to the virtual device + pub events: Vec, + /// Optional request to update which controllers are considered "active" for FF + pub set_active_controllers: Option>, +} + +impl MuxOutput { + /// Create output with only events + pub fn events(events: Vec) -> Self { + Self { + events, + set_active_controllers: None, + } + } + + /// Create output with events and active controller update + pub fn with_active(events: Vec, active: Vec) -> Self { + Self { + events, + set_active_controllers: Some(active), + } + } +} + /// The trait all muxing modes must implement pub trait MuxMode { fn handle_event( @@ -24,7 +50,17 @@ pub trait MuxMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &gilrs::Gilrs, - ) -> Option>; + ) -> Option; + + /// Return the initial set of active controllers for this mode. + /// Used when the mode is first initialized or changes. + fn initial_active_controllers( + &self, + primary_id: GamepadId, + assist_id: GamepadId, + ) -> Vec { + vec![primary_id, assist_id] + } } /// Factory function to create the correct mux mode diff --git a/src/mux/modes/priority.rs b/src/mux/modes/priority.rs index cabac63..c5d3568 100644 --- a/src/mux/modes/priority.rs +++ b/src/mux/modes/priority.rs @@ -1,5 +1,4 @@ -use super::{MuxMode, helpers}; -use evdev::InputEvent; +use super::{MuxMode, MuxOutput, helpers}; use gilrs::{Button, Event, EventType, GamepadId, Gilrs}; #[derive(Default)] @@ -12,7 +11,7 @@ impl MuxMode for PriorityMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { // Filter out irrelevant devices if event.id != primary_id && event.id != assist_id { return None; @@ -38,7 +37,8 @@ impl MuxMode for PriorityMode { return None; } - helpers::create_button_key_event(btn, is_pressed).map(|e| vec![e]) + helpers::create_button_key_event(btn, is_pressed) + .map(|e| MuxOutput::events(vec![e])) } EventType::ButtonChanged(btn, _, _) => { @@ -67,7 +67,7 @@ impl MuxMode for PriorityMode { helpers::create_trigger_event(max_val, abs_axis) }; - Some(vec![event]) + Some(MuxOutput::events(vec![event])) } EventType::AxisChanged(axis, _, _) => { @@ -92,7 +92,7 @@ impl MuxMode for PriorityMode { }) .collect::>(); - (!events.is_empty()).then_some(events) + (!events.is_empty()).then_some(MuxOutput::events(events)) } _ => None, diff --git a/src/mux/modes/toggle.rs b/src/mux/modes/toggle.rs index a46a632..902418b 100644 --- a/src/mux/modes/toggle.rs +++ b/src/mux/modes/toggle.rs @@ -1,4 +1,4 @@ -use super::{MuxMode, helpers}; +use super::{MuxMode, MuxOutput, helpers}; use evdev::InputEvent; use gilrs::{Event, EventType, GamepadId, Gilrs}; @@ -82,7 +82,7 @@ impl MuxMode for ToggleMode { primary_id: GamepadId, assist_id: GamepadId, gilrs: &Gilrs, - ) -> Option> { + ) -> Option { let active_id = self.active_id.get_or_insert(primary_id); // Handle toggle logic @@ -97,7 +97,10 @@ impl MuxMode for ToggleMode { }; let active = gilrs.gamepad(*active_id); - return Some(Self::sync_controller_state(active, *active_id, assist_id)); + let sync_events = Self::sync_controller_state(active, *active_id, assist_id); + + // Report the active controller change + return Some(MuxOutput::with_active(sync_events, vec![*active_id])); } // Only forward events from the active controller @@ -106,6 +109,15 @@ impl MuxMode for ToggleMode { } let active = gilrs.gamepad(*active_id); - Self::convert_event(event, active) + Self::convert_event(event, active).map(MuxOutput::events) + } + + /// Toggle always starts with primary active + fn initial_active_controllers( + &self, + primary_id: GamepadId, + _assist_id: GamepadId, + ) -> Vec { + vec![primary_id] } } diff --git a/src/mux/runtime.rs b/src/mux/runtime.rs index 5d9ce69..7b28d39 100644 --- a/src/mux/runtime.rs +++ b/src/mux/runtime.rs @@ -1,4 +1,4 @@ -use crate::RumbleTarget; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; use crate::utils::ff::{EffectManager, PhysicalFFDev}; use crate::utils::gilrs::GamepadResource; @@ -7,9 +7,9 @@ use evdev::{Device, EventType, InputEvent}; use gilrs::{GamepadId, Gilrs}; use log::{debug, error, info, warn}; use parking_lot::RwLock; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::Duration; const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); @@ -17,34 +17,80 @@ const NEXT_EVENT_TIMEOUT: Duration = Duration::from_millis(1000); /// Runtime-updatable mux settings pub struct RuntimeSettings { pub mode: Arc>, - pub rumble: Arc>, + pub rumble: Arc>, + /// Track currently active controllers for force feedback + active_controllers: Arc>>, + /// Generation counter incremented when FF targets need rebuilding + /// This covers both rumble target changes AND active controller changes + ff_generation: Arc, } impl RuntimeSettings { - pub fn new(mode: MuxModeType, rumble: RumbleTarget) -> Self { + pub fn new( + mode: MuxModeType, + rumble: MuxRumbleTarget, + primary_id: GamepadId, + assist_id: GamepadId, + ) -> Self { + // Get initial active controllers from the mode + let mode_impl = crate::mux::modes::create_mux_mode(mode.clone()); + let initial_active = mode_impl.initial_active_controllers(primary_id, assist_id); + Self { mode: Arc::new(RwLock::new(mode)), rumble: Arc::new(RwLock::new(rumble)), + active_controllers: Arc::new(RwLock::new(initial_active.into_iter().collect())), + ff_generation: Arc::new(AtomicU64::new(0)), } } - pub fn update_mode(&self, new_mode: MuxModeType) { + pub fn update_mode(&self, new_mode: MuxModeType, initial_active: Vec) { let mut mode = self.mode.write(); *mode = new_mode; + + // Update active controllers directly with the provided list + let mut active = self.active_controllers.write(); + active.clear(); + active.extend(initial_active); + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); + } + + pub fn set_active_controllers(&self, controllers: Vec) { + let mut active = self.active_controllers.write(); + active.clear(); + active.extend(controllers); + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); } - pub fn update_rumble(&self, new_rumble: RumbleTarget) { + pub fn update_rumble(&self, new_rumble: MuxRumbleTarget) { let mut rumble = self.rumble.write(); *rumble = new_rumble; + + // Increment generation to trigger FF rebuild + self.ff_generation.fetch_add(1, Ordering::Release); } pub fn get_mode(&self) -> MuxModeType { self.mode.read().clone() } - pub fn get_rumble(&self) -> RumbleTarget { + pub fn get_rumble(&self) -> MuxRumbleTarget { self.rumble.read().clone() } + + /// Get current FF generation for change detection + pub fn ff_generation(&self) -> u64 { + self.ff_generation.load(Ordering::Acquire) + } + + /// Get the current set of active controllers for FF routing + pub fn get_active_controllers(&self) -> Vec { + self.active_controllers.read().iter().copied().collect() + } } pub fn run_input_loop( @@ -61,12 +107,17 @@ pub fn run_input_loop( while !shutdown.load(Ordering::SeqCst) { // Check for mode changes let current_mode = runtime_settings.get_mode(); + if current_mode != last_mode { info!( "Switching mux mode from {:?} to {:?}", last_mode, current_mode ); + mux_mode = crate::mux::modes::create_mux_mode(current_mode.clone()); + let defaults = mux_mode.initial_active_controllers(p_id, a_id); + runtime_settings.update_mode(current_mode.clone(), defaults); + last_mode = current_mode; } @@ -77,12 +128,20 @@ pub fn run_input_loop( if event.id != p_id && event.id != a_id { continue; } - if let Some(mut out_events) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) - && !out_events.is_empty() - { - out_events.push(InputEvent::new(EventType::SYNCHRONIZATION.0, 0, 0)); - if let Err(e) = v_dev.send_events(&out_events) { - error!("Failed to write input events: {}", e); + + if let Some(output) = mux_mode.handle_event(&event, p_id, a_id, &gilrs) { + // Update active controllers if requested by the mode + if let Some(new_active) = output.set_active_controllers { + runtime_settings.set_active_controllers(new_active); + } + + // Send events + if !output.events.is_empty() { + let mut out_events = output.events; + out_events.push(InputEvent::new(EventType::SYNCHRONIZATION.0, 0, 0)); + if let Err(e) = v_dev.send_events(&out_events) { + error!("Failed to write input events: {}", e); + } } } } @@ -101,23 +160,22 @@ pub fn run_ff_loop( let mut effect_manager = EffectManager::new(); // Current physical devices - let mut phys_devs = build_ff_targets(&all_resources, runtime_settings.get_rumble(), p_id, a_id); - let mut last_rumble = runtime_settings.get_rumble(); + let mut phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id); + let mut last_ff_generation = runtime_settings.ff_generation(); info!("FF Thread started."); while !shutdown.load(Ordering::SeqCst) { - // Check for rumble target changes - let current_rumble = runtime_settings.get_rumble(); - if current_rumble != last_rumble { + // Check if FF targets need rebuilding (catches rumble changes AND active controller changes) + let current_generation = runtime_settings.ff_generation(); + if current_generation != last_ff_generation { info!( - "Switching rumble target from {:?} to {:?}", - last_rumble, current_rumble + "FF targets changed, rebuilding (generation {} -> {})", + last_ff_generation, current_generation ); // Build new device set - let mut new_phys_devs = - build_ff_targets(&all_resources, current_rumble.clone(), p_id, a_id); + let mut new_phys_devs = build_ff_targets(&all_resources, &runtime_settings, p_id, a_id); // Synchronize all effects to new devices for dev in &mut new_phys_devs { @@ -140,7 +198,7 @@ pub fn run_ff_loop( } phys_devs = new_phys_devs; - last_rumble = current_rumble; + last_ff_generation = current_generation; } // Process events @@ -269,15 +327,18 @@ pub fn run_ff_loop( // Helper function to build FF targets based on rumble setting fn build_ff_targets( all_resources: &HashMap, - rumble: RumbleTarget, + runtime_settings: &Arc, p_id: GamepadId, a_id: GamepadId, ) -> Vec { + let rumble = runtime_settings.get_rumble(); + let rumble_ids = match rumble { - RumbleTarget::Primary => vec![p_id], - RumbleTarget::Assist => vec![a_id], - RumbleTarget::Both => vec![p_id, a_id], - RumbleTarget::None => vec![], + MuxRumbleTarget::Active => runtime_settings.get_active_controllers(), + MuxRumbleTarget::Assist => vec![a_id], + MuxRumbleTarget::Both => vec![p_id, a_id], + MuxRumbleTarget::None => vec![], + MuxRumbleTarget::Primary => vec![p_id], }; rumble_ids diff --git a/src/tray/app.rs b/src/tray/app.rs index dadaad3..654b462 100644 --- a/src/tray/app.rs +++ b/src/tray/app.rs @@ -1,8 +1,10 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::manager::{DemuxConfig, DemuxHandle}; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::manager::{MuxConfig, MuxHandle}; use crate::mux::modes::MuxModeType; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use gilrs::GamepadId; use ksni::{Category, MenuItem, Status, ToolTip, Tray, menu}; use log::{error, info}; @@ -520,7 +522,20 @@ impl Tray for CtrlAssistTray { enabled: true, access: { mux.mode }, on_change: |_t, s, v| { - if let Some(r) = &s.mux.runtime_settings { r.update_mode(v.clone()); } + if let Some(r) = &s.mux.runtime_settings { + if let (Some(primary_id), Some(assist_id)) = ( + s.mux.selected_primary.as_ref(), + s.mux.selected_assist.as_ref(), + ) { + let mode_impl = crate::mux::modes::create_mux_mode(v.clone()); + let defaults = mode_impl.initial_active_controllers(*primary_id, *assist_id); + r.update_mode(v.clone(), defaults); + } else { + error!( + "Cannot update mux mode: primary or assist controller not selected" + ); + } + } } ), enum_menu!( @@ -547,8 +562,8 @@ impl Tray for CtrlAssistTray { label: "Rumble: {:?}", icon: "notification-active", current: state.mux.rumble, - type: RumbleTarget, - variants: [Both, Primary, Assist, None], + type: MuxRumbleTarget, + variants: [Active, Both, Primary, Assist, None], enabled: true, access: { mux.rumble }, on_change: |_t, s, v| { diff --git a/src/tray/config.rs b/src/tray/config.rs index 686694b..5255492 100644 --- a/src/tray/config.rs +++ b/src/tray/config.rs @@ -1,7 +1,9 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; use crate::tray::state::OperationMode; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use log::{info, warn}; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -35,7 +37,7 @@ pub struct MuxConfig { pub spoof: SpoofTarget, /// Last used rumble target #[serde(default)] - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/src/tray/mod.rs b/src/tray/mod.rs index f72b1fd..214fd1d 100644 --- a/src/tray/mod.rs +++ b/src/tray/mod.rs @@ -1,4 +1,9 @@ +use ashpd::is_sandboxed; use futures_util::TryFutureExt; +use ksni::TrayMethods; +use std::error::Error; +use tokio::signal::unix::{SignalKind, signal}; +use tokio::sync::watch; pub mod app; pub mod config; @@ -6,11 +11,6 @@ pub mod state; pub use app::CtrlAssistTray; -use ashpd::is_sandboxed; -use ksni::TrayMethods; -use std::error::Error; -use tokio::sync::watch; - pub async fn run_tray() -> Result<(), Box> { let (tray, mut shutdown_rx) = CtrlAssistTray::new()?; @@ -27,13 +27,22 @@ pub async fn run_tray() -> Result<(), Box> { .await? }; - // Create a separate shutdown channel for Ctrl+C + // Create a separate shutdown channel for Ctrl+C or SIGTERM let (ctrlc_tx, mut ctrlc_rx) = watch::channel(false); tokio::spawn(async move { - // Wait for Ctrl+C signal - if tokio::signal::ctrl_c().await.is_ok() { - let _ = ctrlc_tx.send(true); + // Wait for either SIGINT or SIGTERM + let mut sigint = + signal(SignalKind::interrupt()).expect("Failed to register SIGINT handler"); + let mut sigterm = + signal(SignalKind::terminate()).expect("Failed to register SIGTERM handler"); + tokio::select! { + _ = sigint.recv() => { + let _ = ctrlc_tx.send(true); + } + _ = sigterm.recv() => { + let _ = ctrlc_tx.send(true); + } } }); @@ -41,13 +50,13 @@ pub async fn run_tray() -> Result<(), Box> { println!("Configure and control the mux from your system tray"); println!("Press Ctrl+C to exit"); - // Wait for either shutdown signal or Ctrl+C + // Wait for either shutdown signal or Ctrl+C/SIGTERM tokio::select! { _ = shutdown_rx.changed() => { // Exit button clicked, tray handled shutdown } _ = ctrlc_rx.changed() => { - // Ctrl+C pressed, handle shutdown here + // Ctrl+C or SIGTERM, handle shutdown here handle.update(|tray: &mut CtrlAssistTray| { tray.shutdown(); }).await; diff --git a/src/tray/state.rs b/src/tray/state.rs index 51be75d..9310923 100644 --- a/src/tray/state.rs +++ b/src/tray/state.rs @@ -1,6 +1,8 @@ +use crate::demux::DemuxRumbleTarget; use crate::demux::modes::DemuxModeType; +use crate::mux::MuxRumbleTarget; use crate::mux::modes::MuxModeType; -use crate::{DemuxRumbleTarget, HideType, RumbleTarget, SpoofTarget}; +use crate::{HideType, SpoofTarget}; use gilrs::{GamepadId, Gilrs}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -67,7 +69,7 @@ pub struct MuxState { /// Current spoof target pub spoof: SpoofTarget, /// Current rumble target - pub rumble: RumbleTarget, + pub rumble: MuxRumbleTarget, /// Shared runtime settings for live updates pub runtime_settings: Option>, } diff --git a/src/utils/hide.rs b/src/utils/hide.rs index d8f7eca..ac16690 100644 --- a/src/utils/hide.rs +++ b/src/utils/hide.rs @@ -1,5 +1,6 @@ use crate::HideType; use crate::utils::gilrs::GamepadResource; +use fs2::FileExt; use std::collections::HashSet; use std::error::Error; use std::fs; @@ -126,7 +127,19 @@ fn modify_steam_config(path: &Path, modifier: F) -> Result<(), Box where F: FnOnce(&mut HashSet), { - let content = fs::read_to_string(path)?; + // Lock the config file for exclusive access + let mut file = fs::OpenOptions::new().read(true).write(true).open(path)?; + file.lock_exclusive()?; + + // Read the file contents after locking + let mut content = String::new(); + use std::io::Seek; + use std::io::SeekFrom; + { + use std::io::Read; + file.seek(SeekFrom::Start(0))?; + file.read_to_string(&mut content)?; + } let mut lines: Vec = content.lines().map(String::from).collect(); let (line_idx, current_val) = match lines @@ -197,11 +210,15 @@ where lines.insert(line_idx, new_line); } + // Write back atomically using a temp file, then rename, while holding the lock let tmp_path = path.with_extension("tmp"); - let mut file = fs::File::create(&tmp_path)?; - file.write_all(lines.join("\n").as_bytes())?; - file.sync_all()?; - fs::rename(tmp_path, path)?; + { + let mut tmp_file = fs::File::create(&tmp_path)?; + tmp_file.write_all(lines.join("\n").as_bytes())?; + tmp_file.sync_all()?; + } + fs::rename(&tmp_path, path)?; + file.unlock()?; Ok(()) }