diff --git a/.gitignore b/.gitignore index 5b54e79..7def844 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ xcuserdata/ # Debian package build output /dist/ + +# Windows App SDK staging by windows-reactor build.rs +/temp/ +/winmd/ diff --git a/Cargo.lock b/Cargo.lock index ad3122c..aeef7c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,22 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - [[package]] name = "aead" version = "0.5.2" @@ -53,19 +37,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -75,31 +46,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-activity" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" -dependencies = [ - "android-properties", - "bitflags 2.13.0", - "cc", - "jni 0.22.4", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "num_enum", - "thiserror 2.0.18", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - [[package]] name = "android_log-sys" version = "0.3.2" @@ -198,24 +144,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -463,7 +391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "annotate-snippets", - "bitflags 2.13.0", + "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", @@ -490,12 +418,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.0" @@ -520,15 +442,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - [[package]] name = "bumpalo" version = "3.20.3" @@ -553,7 +466,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags 2.13.0", + "bitflags", "cairo-sys-rs", "glib", "libc", @@ -570,32 +483,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.13.0", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - [[package]] name = "cast" version = "0.3.0" @@ -809,16 +696,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -835,30 +712,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -962,12 +815,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1046,12 +893,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "displaydoc" version = "0.2.6" @@ -1063,27 +904,12 @@ dependencies = [ "syn", ] -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading", -] - [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - [[package]] name = "dunce" version = "1.0.5" @@ -1201,7 +1027,7 @@ version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d" dependencies = [ - "bitflags 2.13.0", + "bitflags", "ffmpeg-sys-next", "libc", ] @@ -1271,33 +1097,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1491,16 +1290,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix 1.1.4", - "windows-link", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -1587,7 +1376,7 @@ version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" dependencies = [ - "bitflags 2.13.0", + "bitflags", "futures-channel", "futures-core", "futures-executor", @@ -2004,36 +1793,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.18", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn", -] - [[package]] name = "jni-sys" version = "0.3.1" @@ -2156,7 +1915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2165,25 +1924,13 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" -dependencies = [ - "bitflags 2.13.0", - "libc", - "plain", - "redox_syscall 0.8.1", -] - [[package]] name = "libspa" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ - "bitflags 2.13.0", + "bitflags", "cc", "convert_case", "cookie-factory", @@ -2205,12 +1952,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2338,7 +2079,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.0", + "bitflags", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2347,12 +2088,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -2368,7 +2103,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.13.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -2506,209 +2241,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.13.0", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.13.0", - "block2", - "dispatch", - "libc", - "objc2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.13.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - [[package]] name = "oid-registry" version = "0.7.1" @@ -2778,16 +2310,6 @@ dependencies = [ "audiopus_sys", ] -[[package]] -name = "orbclient" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" -dependencies = [ - "libc", - "libredox", -] - [[package]] name = "ordered-stream" version = "0.2.0" @@ -2798,15 +2320,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" -dependencies = [ - "ttf-parser", -] - [[package]] name = "pango" version = "0.22.6" @@ -2855,9 +2368,9 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2891,26 +2404,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2924,7 +2417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" dependencies = [ "anyhow", - "bitflags 2.13.0", + "bitflags", "libc", "libspa", "libspa-sys", @@ -2972,26 +2465,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "polyval" version = "0.6.2" @@ -3055,7 +2528,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.13.0", + "bitflags", "num-traits", "rand 0.9.4", "rand_chacha 0.9.0", @@ -3071,7 +2544,7 @@ name = "punktfunk-android" version = "0.0.1" dependencies = [ "android_logger", - "jni 0.21.1", + "jni", "log", "ndk", "opus", @@ -3122,15 +2595,14 @@ dependencies = [ "mdns-sd", "opus", "punktfunk-core", - "raw-window-handle", "sdl3", "serde", "serde_json", "tracing", "tracing-subscriber", "wasapi", - "windows", - "winit", + "windows 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-reactor", ] [[package]] @@ -3215,7 +2687,7 @@ dependencies = [ "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-scanner", - "windows", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", "x509-parser", "xkbcommon", ] @@ -3427,31 +2899,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ad765b21a08b1a8e5cdce052719188a23772bcbefb3c439f0baaf62c56ceac" -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", -] - -[[package]] -name = "redox_syscall" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" -dependencies = [ - "bitflags 2.13.0", + "bitflags", ] [[package]] @@ -3504,7 +2958,7 @@ dependencies = [ "enumflags2", "futures", "log", - "rustix 1.1.4", + "rustix", "tokio", ] @@ -3572,29 +3026,16 @@ dependencies = [ "nom 7.1.3", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -3651,9 +3092,9 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", - "jni 0.21.1", + "jni", "log", "once_cell", "rustls", @@ -3744,38 +3185,19 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", -] - [[package]] name = "sdl3" version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25bd22eb1bbc9137e914022b4994ed35591eea0884e9e3e98e6d9895cad6e1d2" dependencies = [ - "bitflags 2.13.0", + "bitflags", "libc", "sdl3-image-sys", "sdl3-mixer-sys", @@ -3869,8 +3291,8 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", - "core-foundation 0.10.1", + "bitflags", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -4030,22 +3452,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd_cesu8" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "siphasher" version = "1.0.3" @@ -4064,40 +3470,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.13.0", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - [[package]] name = "socket-pktinfo" version = "0.3.2" @@ -4150,12 +3522,6 @@ dependencies = [ "der", ] -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" - [[package]] name = "strsim" version = "0.11.1" @@ -4224,7 +3590,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -4308,31 +3674,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -4575,12 +3916,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - [[package]] name = "typenum" version = "1.20.1" @@ -4771,8 +4106,8 @@ dependencies = [ "log", "num-integer", "thiserror 2.0.18", - "windows", - "windows-core", + "windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4812,16 +4147,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.123" @@ -4882,7 +4207,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.13.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -4896,8 +4221,7 @@ checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.4", - "scoped-tls", + "rustix", "smallvec", "wayland-sys", ] @@ -4908,41 +4232,19 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.13.0", - "rustix 1.1.4", + "bitflags", + "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.13.0", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" -dependencies = [ - "rustix 1.1.4", - "wayland-client", - "xcursor", -] - [[package]] name = "wayland-protocols" version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "bitflags 2.13.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4954,20 +4256,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "bitflags 2.13.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" -dependencies = [ - "bitflags 2.13.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4980,7 +4269,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.13.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5004,22 +4293,9 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ - "dlib", - "log", - "once_cell", "pkg-config", ] -[[package]] -name = "web-sys" -version = "0.3.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -5086,10 +4362,23 @@ 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", + "windows-collections 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-future 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-numerics 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-collections 0.3.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-future 0.3.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-numerics 0.3.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-reference", + "windows-time", ] [[package]] @@ -5098,7 +4387,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5107,11 +4404,23 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-implement 0.60.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-interface 0.59.3 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-result 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-strings 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-implement 0.60.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-interface 0.59.3 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-result 0.4.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-strings 0.5.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5120,9 +4429,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core", - "windows-link", - "windows-threading", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-threading 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-threading 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5136,6 +4455,16 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -5147,20 +4476,68 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" + [[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", + "windows-core 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", +] + +[[package]] +name = "windows-reactor" +version = "0.0.0" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "rustc-hash", + "windows-collections 0.3.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-future 0.3.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-numerics 0.3.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-reference", + "windows-threading 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-time", +] + +[[package]] +name = "windows-reference" +version = "0.1.0" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", + "windows-time", ] [[package]] @@ -5169,7 +4546,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5178,7 +4563,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5223,7 +4616,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -5263,7 +4656,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5280,7 +4673,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link", + "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", +] + +[[package]] +name = "windows-time" +version = "0.1.0" +source = "git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1#b4129fcc1ae81eec8bf1217539883db821bca3a1" +dependencies = [ + "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=b4129fcc1ae81eec8bf1217539883db821bca3a1)", ] [[package]] @@ -5421,58 +4830,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winit" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.13.0", - "block2", - "bytemuck", - "calloop", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - [[package]] name = "winnow" version = "0.7.15" @@ -5552,7 +4909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.13.0", + "bitflags", "indexmap", "log", "serde", @@ -5582,38 +4939,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 1.1.4", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - [[package]] name = "x509-parser" version = "0.16.0" @@ -5631,12 +4956,6 @@ dependencies = [ "time", ] -[[package]] -name = "xcursor" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" - [[package]] name = "xkbcommon" version = "0.8.0" @@ -5648,19 +4967,6 @@ dependencies = [ "xkeysym", ] -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.13.0", - "dlib", - "log", - "once_cell", - "xkeysym", -] - [[package]] name = "xkeysym" version = "0.2.1" @@ -5692,7 +4998,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.4", + "rustix", "serde", "serde_repr", "tokio", diff --git a/crates/punktfunk-client-windows/Cargo.toml b/crates/punktfunk-client-windows/Cargo.toml index 3824074..13a7e63 100644 --- a/crates/punktfunk-client-windows/Cargo.toml +++ b/crates/punktfunk-client-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "punktfunk-client-windows" -description = "Native Windows punktfunk/1 client — winit/D3D11 shell, FFmpeg decode, WASAPI audio, SDL3 gamepads" +description = "Native Windows punktfunk/1 client — WinUI 3 (windows-reactor) shell, D3D11/SwapChainPanel present, FFmpeg decode, WASAPI audio, SDL3 gamepads" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -21,28 +21,23 @@ path = "src/main.rs" # is Sync (mutexed plane receivers), so it drops into a UI app cleanly. punktfunk-core = { path = "../punktfunk-core", features = ["quic"] } -# Win32 / Direct3D11 / DXGI surface for the present path + raw input. Software (WARP) device on -# the GPU-less dev box; the same code drives a hardware adapter on a real GPU. -windows = { version = "0.62", features = [ +# WinUI 3 UI via windows-reactor (a declarative React-like framework backed by WinUI). Its +# `build.rs` downloads the Windows App SDK NuGets and stages the bootstrap DLL + resources.pri +# next to the exe; it requires `CARGO_WORKSPACE_DIR` to be set in the build env. Unpublished +# (version 0.0.0) and fast-moving, so pinned to a verified commit. +windows-reactor = { git = "https://github.com/microsoft/windows-rs", rev = "b4129fcc1ae81eec8bf1217539883db821bca3a1" } +# Win32 / Direct3D11 / DXGI for the SwapChainPanel composition swapchain. Pulled from the SAME +# windows-rs commit as windows-reactor so their `windows-core` unifies — the `IDXGISwapChain1` +# we hand to `SwapChainPanelHandle::set_swap_chain` must satisfy reactor's `windows_core::Interface`. +windows = { git = "https://github.com/microsoft/windows-rs", rev = "b4129fcc1ae81eec8bf1217539883db821bca3a1", features = [ "Win32_Foundation", - "Win32_System_Com", "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", "Win32_Graphics_Direct3D", "Win32_Graphics_Direct3D11", "Win32_Graphics_Direct3D_Fxc", - "Win32_UI_Input", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_UI_WindowsAndMessaging", - "Win32_UI_HiDpi", ] } -# UI shell: a winit window + a raw DXGI flip-model swapchain on its HWND (the proven present -# path; the WinUI3/Reactor option is a documented follow-up). raw-window-handle extracts the -# HWND for swapchain creation. -winit = "0.30" -raw-window-handle = "0.6" - # Video decode (same FFmpeg pin as the host/Linux client) — software HEVC on the GPU-less dev # box; D3D11VA hardware decode is a follow-up for the real-GPU box. ffmpeg-next = "8" diff --git a/crates/punktfunk-client-windows/src/app.rs b/crates/punktfunk-client-windows/src/app.rs index af36103..acfdfca 100644 --- a/crates/punktfunk-client-windows/src/app.rs +++ b/crates/punktfunk-client-windows/src/app.rs @@ -1,440 +1,602 @@ -//! The winit application shell: one window hosting a Direct3D11 swapchain, the decoded-frame -//! present loop, and local keyboard/mouse capture forwarded on the wire contract. +//! The WinUI 3 (windows-reactor) application shell — host list, settings, PIN/TOFU pairing, and +//! the stream page (a `SwapChainPanel` bound to the D3D11 composition swapchain in +//! [`crate::present`], driven by reactor's per-frame `on_rendering`). //! -//! Input capture is a deliberate, reversible STATE (Moonlight-style, mirroring the other -//! native clients): engaged when the user clicks into the window (that click is suppressed -//! toward the host) or on first focus; released by Ctrl+Alt+Shift+Q (toggles) or focus loss — -//! held keys/buttons are flushed host-side on release so nothing sticks down. While captured -//! the cursor is hidden and confined; F11 toggles fullscreen. -//! -//! Keys are winit physical `KeyCode`s → VK via `keymap` (layout-independent). Mouse is -//! absolute (`MouseMoveAbs` scaled into the negotiated mode through the letterbox transform, -//! surface size packed in `flags`) — relative pointer-lock is a follow-up (RAWINPUT). +//! Declarative React-like model: a single root component routes on a `Screen` value held in +//! `use_async_state` so background threads (discovery, the session pump) can drive navigation. +//! The present + decoded-frame handoff crosses to the UI thread through a `Mutex` side-channel +//! and thread-locals (the windows-reactor SwapChainPanel sample's pattern), since the per-frame +//! present must not go through state/rerender. -use crate::keymap; -use crate::present::{Renderer, SwapChain}; -use crate::session::{SessionEvent, SessionHandle}; -use crate::trust::{KnownHost, KnownHosts}; +use crate::discovery::{self, DiscoveredHost}; +use crate::gamepad::GamepadService; +use crate::present::Presenter; +use crate::session::{self, SessionEvent, SessionParams}; +use crate::trust::{self, KnownHost, KnownHosts, Settings}; use crate::video::DecodedFrame; use punktfunk_core::client::NativeClient; -use punktfunk_core::config::Mode; -use punktfunk_core::input::{InputEvent, InputKind}; -use raw_window_handle::{HasWindowHandle, RawWindowHandle}; -use std::collections::HashSet; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; -use windows::Win32::Foundation::HWND; -use winit::application::ApplicationHandler; -use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; -use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey}; -use winit::window::{CursorGrabMode, Fullscreen, Window, WindowId}; +use punktfunk_core::config::{CompositorPref, GamepadPref, Mode}; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; +use windows_reactor::*; -/// How we reached this host (for persisting a TOFU fingerprint after `Connected`). -pub struct ConnectInfo { - pub name: String, - pub addr: String, - pub port: u16, - /// TOFU connect (no pin supplied) — persist the observed fingerprint on `Connected`. - pub tofu: bool, +const RESOLUTIONS: &[(u32, u32)] = &[ + (0, 0), + (1280, 720), + (1920, 1080), + (2560, 1440), + (3840, 2160), +]; +const REFRESH: &[u32] = &[0, 30, 60, 90, 120, 144, 165, 240]; + +#[derive(Clone, PartialEq)] +enum Screen { + Hosts, + Connecting, + Stream, + Settings, + Pair, } -pub struct WinApp { - handle: SessionHandle, - info: ConnectInfo, - /// App-lifetime SDL gamepad service: per-session capture + rumble/HID feedback. - gamepad: crate::gamepad::GamepadService, - - window: Option>, - renderer: Option, - swap: Option, - - connector: Option>, - mode: Mode, - have_frame: bool, - - captured: bool, - modifiers: ModifiersState, - held_keys: HashSet, - held_buttons: HashSet, +/// The host we're about to connect to / pair with (carried into the Pair screen). +#[derive(Clone, Default)] +struct Target { + name: String, + addr: String, + port: u16, + fp_hex: Option, + pair_optional: bool, } -impl WinApp { - pub fn new( - handle: SessionHandle, - info: ConnectInfo, - gamepad: crate::gamepad::GamepadService, - ) -> WinApp { - WinApp { - handle, - info, - gamepad, - window: None, - renderer: None, - swap: None, - connector: None, - mode: Mode { - width: 1280, - height: 720, - refresh_hz: 60, - }, - have_frame: false, - captured: false, - modifiers: ModifiersState::empty(), - held_keys: HashSet::new(), - held_buttons: HashSet::new(), - } - } - - pub fn run(self) -> anyhow::Result<()> { - let event_loop = EventLoop::new()?; - event_loop.set_control_flow(ControlFlow::Poll); - let mut app = self; - event_loop.run_app(&mut app)?; - Ok(()) - } - - fn send(&self, kind: InputKind, code: u32, x: i32, y: i32, flags: u32) { - if let Some(c) = &self.connector { - let _ = c.send_input(&InputEvent { - kind, - _pad: [0; 3], - code, - x, - y, - flags, - }); - } - } - - /// Forward an absolute pointer position: window pixels → video pixels through the - /// Contain-fit letterbox (`flags` packs the coordinate-space size, the host's contract). - fn send_abs(&self, x: f64, y: f64) { - let Some(window) = &self.window else { return }; - let size = window.inner_size(); - let (ww, wh) = (size.width.max(1) as f64, size.height.max(1) as f64); - let (vw, vh) = ( - self.mode.width.max(1) as f64, - self.mode.height.max(1) as f64, - ); - let scale = (ww / vw).min(wh / vh); - let (ox, oy) = ((ww - vw * scale) / 2.0, (wh - vh * scale) / 2.0); - let px = (((x - ox) / scale).round()).clamp(0.0, vw - 1.0) as i32; - let py = (((y - oy) / scale).round()).clamp(0.0, vh - 1.0) as i32; - let flags = (self.mode.width << 16) | (self.mode.height & 0xffff); - self.send(InputKind::MouseMoveAbs, 0, px, py, flags); - } - - fn engage(&mut self) { - if self.captured { - return; - } - self.captured = true; - if let Some(w) = &self.window { - w.set_cursor_visible(false); - // Confined keeps absolute mapping working; Locked (relative) is the follow-up. - let _ = w.set_cursor_grab(CursorGrabMode::Confined); - } - } - - fn release(&mut self) { - if !self.captured { - return; - } - self.captured = false; - if let Some(w) = &self.window { - w.set_cursor_visible(true); - let _ = w.set_cursor_grab(CursorGrabMode::None); - } - // Flush everything held so nothing sticks down on the host. - for vk in self.held_keys.drain().collect::>() { - self.send(InputKind::KeyUp, vk as u32, 0, 0, 0); - } - for b in self.held_buttons.drain().collect::>() { - self.send(InputKind::MouseButtonUp, b, 0, 0, 0); - } - } - - fn toggle_fullscreen(&self) { - if let Some(w) = &self.window { - if w.fullscreen().is_some() { - w.set_fullscreen(None); - } else { - w.set_fullscreen(Some(Fullscreen::Borderless(None))); - } - } - } - - /// Drain session events + the newest decoded frame; returns true if a frame is ready to - /// present. Called every loop turn. - fn pump(&mut self, event_loop: &ActiveEventLoop) -> bool { - while let Ok(ev) = self.handle.events.try_recv() { - match ev { - SessionEvent::Connected { - connector, - mode, - fingerprint, - } => { - self.mode = mode; - if self.info.tofu { - let fp_hex = crate::trust::hex(&fingerprint); - let mut known = KnownHosts::load(); - known.upsert(KnownHost { - name: self.info.name.clone(), - addr: self.info.addr.clone(), - port: self.info.port, - fp_hex: fp_hex.clone(), - paired: false, - }); - let _ = known.save(); - tracing::info!(fp = %fp_hex, "trusted on first use — pinned"); - } - if let Some(w) = &self.window { - w.set_title(&format!( - "Punktfunk — {} · {}×{}@{}", - self.info.name, mode.width, mode.height, mode.refresh_hz - )); - } - self.gamepad.attach(connector.clone()); - self.connector = Some(connector); - tracing::info!(?mode, "connected — streaming"); - } - SessionEvent::Stats(s) => tracing::debug!( - fps = format!("{:.0}", s.fps), - mbps = format!("{:.1}", s.mbps), - lat_ms = format!("{:.2}", s.latency_ms), - "stats" - ), - SessionEvent::Failed { - msg, - trust_rejected, - } => { - tracing::error!(%msg, trust_rejected, "connect failed"); - if trust_rejected { - tracing::error!( - "host fingerprint changed or pairing required — re-pair with --pair PIN" - ); - } - self.gamepad.detach(); - event_loop.exit(); - return false; - } - SessionEvent::Ended(err) => { - tracing::info!(reason = err.as_deref().unwrap_or("done"), "session ended"); - self.gamepad.detach(); - event_loop.exit(); - return false; - } - } - } - // Keep only the newest frame (freshness over completeness). - let mut newest = None; - while let Ok(f) = self.handle.frames.try_recv() { - newest = Some(f); - } - if let (Some(DecodedFrame::Cpu(c)), Some(r)) = (&newest, self.renderer.as_mut()) { - if let Err(e) = r.upload(c) { - tracing::warn!(error = %e, "frame upload failed"); - } else { - self.have_frame = true; - } - } - newest.is_some() - } - - fn render(&mut self) { - let (Some(swap), Some(renderer)) = (self.swap.as_mut(), self.renderer.as_ref()) else { - return; - }; - if !self.have_frame { - return; - } - match swap.rtv() { - Ok(rtv) => { - renderer.draw( - &rtv, - swap.width, - swap.height, - self.mode.width, - self.mode.height, - ); - swap.present(); - } - Err(e) => tracing::warn!(error = %e, "acquire back buffer"), - } - } +/// UI-thread-only present context: the D3D11 presenter plus the decoded-frame receiver. +struct PresentCtx { + presenter: Presenter, + frames: async_channel::Receiver, } -fn hwnd_of(window: &Window) -> Option { - match window.window_handle().ok()?.as_raw() { - RawWindowHandle::Win32(h) => Some(HWND(h.hwnd.get() as *mut core::ffi::c_void)), - _ => None, - } +thread_local! { + static PRESENT: RefCell> = const { RefCell::new(None) }; + static PENDING_FRAMES: RefCell>> = + const { RefCell::new(None) }; } -/// winit MouseButton → GameStream button id (1=left, 2=middle, 3=right, 4=X1, 5=X2). -fn mouse_button_id(b: MouseButton) -> Option { - Some(match b { - MouseButton::Left => 1, - MouseButton::Middle => 2, - MouseButton::Right => 3, - MouseButton::Back => 4, - MouseButton::Forward => 5, - _ => return None, - }) +/// Cross-thread handoff from the session pump (off-thread) to the stream page (UI thread). +#[derive(Default)] +struct Shared { + handoff: Mutex, async_channel::Receiver)>>, + target: Mutex, } -impl ApplicationHandler for WinApp { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - if self.window.is_some() { - return; - } - let attrs = Window::default_attributes() - .with_title("Punktfunk") - .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0)); - let window = match event_loop.create_window(attrs) { - Ok(w) => Arc::new(w), - Err(e) => { - tracing::error!(error = %e, "create window"); - event_loop.exit(); - return; - } - }; - let renderer = match Renderer::new() { - Ok(r) => r, - Err(e) => { - tracing::error!(error = %e, "D3D11 renderer"); - event_loop.exit(); - return; - } - }; - let size = window.inner_size(); - let swap = hwnd_of(&window) - .ok_or_else(|| anyhow::anyhow!("no HWND")) - .and_then(|hwnd| { - SwapChain::new(renderer.device(), hwnd, size.width, size.height) - .map_err(|e| anyhow::anyhow!(e)) - }); - match swap { - Ok(s) => self.swap = Some(s), - Err(e) => { - tracing::error!(error = %e, "swapchain"); - event_loop.exit(); - return; - } - } - self.renderer = Some(renderer); - self.window = Some(window); - } +pub struct AppCtx { + identity: (String, String), + settings: Mutex, + gamepad: GamepadService, + shared: Arc, +} - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - match event { - WindowEvent::CloseRequested => { - self.handle.stop.store(true, Ordering::SeqCst); - self.gamepad.detach(); - event_loop.exit(); - } - WindowEvent::Resized(size) => { - if let Some(swap) = self.swap.as_mut() { - if let Err(e) = swap.resize(size.width, size.height) { - tracing::warn!(error = %e, "swapchain resize"); - } - } - } - WindowEvent::Focused(false) => self.release(), - WindowEvent::ModifiersChanged(m) => self.modifiers = m.state(), - WindowEvent::KeyboardInput { event, .. } => { - let PhysicalKey::Code(code) = event.physical_key else { - return; - }; - // Local chords (intercepted, never forwarded): capture toggle + fullscreen. - if code == KeyCode::KeyQ - && event.state.is_pressed() - && self.modifiers.control_key() - && self.modifiers.alt_key() - && self.modifiers.shift_key() - { - if self.captured { - self.release(); +pub fn run(identity: (String, String), gamepad: GamepadService) -> windows_reactor::Result<()> { + let ctx = Arc::new(AppCtx { + identity, + settings: Mutex::new(Settings::load()), + gamepad, + shared: Arc::new(Shared::default()), + }); + App::new() + .title("Punktfunk") + .inner_size(1100.0, 720.0) + .render(move |cx| root(cx, &ctx)) +} + +fn root(cx: &mut RenderCx, ctx: &Arc) -> Element { + let (screen, set_screen) = cx.use_async_state(Screen::Hosts); + let (hosts, set_hosts) = cx.use_async_state(Vec::::new()); + let (status, set_status) = cx.use_async_state(String::new()); + + // Continuous LAN discovery (spawned once). + cx.use_effect((), { + let set_hosts = set_hosts.clone(); + move || { + let rx = discovery::browse(); + std::thread::spawn(move || { + let mut acc: Vec = Vec::new(); + while let Ok(h) = rx.recv_blocking() { + if let Some(e) = acc.iter_mut().find(|e| e.key == h.key) { + *e = h; } else { - self.engage(); + acc.push(h); } - return; + set_hosts.call(acc.clone()); } - if code == KeyCode::F11 && event.state.is_pressed() { - self.toggle_fullscreen(); - return; - } - if !self.captured { - return; - } - let Some(vk) = keymap::keycode_to_vk(code) else { - return; - }; - if event.state.is_pressed() { - // Track held state for flush-on-release; re-send on auto-repeat too (the - // host treats KeyDown as a state set, so repeats are harmless). - self.held_keys.insert(vk); - self.send(InputKind::KeyDown, vk as u32, 0, 0, 0); - } else if self.held_keys.remove(&vk) { - self.send(InputKind::KeyUp, vk as u32, 0, 0, 0); - } - } - WindowEvent::CursorMoved { position, .. } => { - if self.captured { - self.send_abs(position.x, position.y); - } - } - WindowEvent::MouseInput { state, button, .. } => { - if !self.captured { - if state == ElementState::Pressed && button == MouseButton::Left { - self.engage(); // the engaging click is suppressed toward the host - } - return; - } - let Some(id) = mouse_button_id(button) else { - return; - }; - if state == ElementState::Pressed { - self.held_buttons.insert(id); - self.send(InputKind::MouseButtonDown, id, 0, 0, 0); - } else if self.held_buttons.remove(&id) { - self.send(InputKind::MouseButtonUp, id, 0, 0, 0); - } - } - WindowEvent::MouseWheel { delta, .. } => { - if !self.captured { - return; - } - // The wire carries WHEEL_DELTA(120) units, positive = up / right. - let (dx, dy) = match delta { - MouseScrollDelta::LineDelta(x, y) => (x, y), - MouseScrollDelta::PixelDelta(p) => (p.x as f32 / 120.0, p.y as f32 / 120.0), - }; - let vy = (dy * 120.0) as i32; - if vy != 0 { - self.send(InputKind::MouseScroll, 0, vy, 0, 0); - } - let vx = (dx * 120.0) as i32; - if vx != 0 { - self.send(InputKind::MouseScroll, 1, vx, 0, 0); - } - } - WindowEvent::RedrawRequested => self.render(), - _ => {} + }); + } + }); + + match screen { + Screen::Hosts => hosts_page(cx, ctx, &hosts, &status, &set_screen, &set_status), + Screen::Connecting => vstack(( + text_block("Connecting…").font_size(20.0), + text_block(status.clone()), + )) + .spacing(12.0) + .into(), + Screen::Settings => settings_page(ctx, &set_screen), + Screen::Pair => pair_page(cx, ctx, &set_screen, &set_status), + Screen::Stream => stream_page(cx, ctx), + } +} + +fn hosts_page( + cx: &mut RenderCx, + ctx: &Arc, + hosts: &[DiscoveredHost], + status: &str, + set_screen: &AsyncSetState, + set_status: &AsyncSetState, +) -> Element { + let (manual, set_manual) = cx.use_state(String::new()); + let known = KnownHosts::load(); + + let mut rows: Vec = Vec::new(); + rows.push(text_block("Punktfunk").font_size(28.0).bold().into()); + + // Saved (trusted/paired) hosts. + if !known.hosts.is_empty() { + rows.push(text_block("Saved hosts").font_size(16.0).bold().into()); + for k in &known.hosts { + let t = Target { + name: k.name.clone(), + addr: k.addr.clone(), + port: k.port, + fp_hex: Some(k.fp_hex.clone()), + pair_optional: false, + }; + let (ctx2, ss, st) = (ctx.clone(), set_screen.clone(), set_status.clone()); + rows.push( + button(format!( + "{} · {}:{} · {}", + k.name, + k.addr, + k.port, + if k.paired { "paired" } else { "trusted" } + )) + .on_click(move || initiate(&ctx2, t.clone(), &ss, &st)) + .into(), + ); } } - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let new_frame = self.pump(event_loop); - if new_frame { - if let Some(w) = &self.window { - w.request_redraw(); - } - } else { - // No frame this turn — yield briefly instead of spinning a core flat-out. - std::thread::sleep(Duration::from_millis(1)); - } + // Discovered hosts. + rows.push( + text_block("Hosts on this network") + .font_size(16.0) + .bold() + .into(), + ); + if hosts.is_empty() { + rows.push(text_block("Searching the LAN…").into()); + } + for h in hosts { + let t = Target { + name: h.name.clone(), + addr: h.addr.clone(), + port: h.port, + fp_hex: (!h.fp_hex.is_empty()).then(|| h.fp_hex.clone()), + pair_optional: h.pair == "optional", + }; + let (ctx2, ss, st) = (ctx.clone(), set_screen.clone(), set_status.clone()); + rows.push( + button(format!( + "{} · {}:{} · pairing {}", + h.name, + h.addr, + h.port, + if h.pair.is_empty() { + "optional" + } else { + &h.pair + } + )) + .on_click(move || initiate(&ctx2, t.clone(), &ss, &st)) + .into(), + ); + } + + // Manual connection. + rows.push( + text_block("Manual connection") + .font_size(16.0) + .bold() + .into(), + ); + rows.push( + text_box(manual.clone()) + .placeholder("host:port") + .on_changed(move |s| set_manual.call(s)) + .into(), + ); + { + let (ctx2, ss, st, text) = (ctx.clone(), set_screen.clone(), set_status.clone(), manual); + rows.push( + button("Connect") + .accent() + .on_click(move || { + let text = text.trim(); + if text.is_empty() { + return; + } + let (addr, port) = match text.rsplit_once(':') { + Some((a, p)) => (a.to_string(), p.parse().unwrap_or(9777)), + None => (text.to_string(), 9777), + }; + initiate( + &ctx2, + Target { + name: addr.clone(), + addr, + port, + fp_hex: None, + pair_optional: false, + }, + &ss, + &st, + ); + }) + .into(), + ); + } + + { + let ss = set_screen.clone(); + rows.push( + button("Settings") + .on_click(move || ss.call(Screen::Settings)) + .into(), + ); + } + if !status.is_empty() { + rows.push(text_block(status.to_string()).into()); + } + + vstack(rows).spacing(8.0).into() +} + +/// The trust gate (mirrors the GTK client's `initiate_connect`): pinned fingerprint → silent +/// connect; known address → stored pin; advertised `pair=optional` → TOFU; otherwise → PIN +/// pairing. +fn initiate( + ctx: &Arc, + target: Target, + set_screen: &AsyncSetState, + set_status: &AsyncSetState, +) { + let known = KnownHosts::load(); + let pin = target + .fp_hex + .as_ref() + .and_then(|fp| known.find_by_fp(fp).map(|_| fp.clone())) + .or_else(|| { + known + .find_by_addr(&target.addr, target.port) + .map(|k| k.fp_hex.clone()) + }) + .and_then(|fp| trust::parse_hex32(&fp)); + + if let Some(pin) = pin { + connect(ctx, &target, Some(pin), set_screen, set_status); + } else if target.pair_optional { + connect(ctx, &target, None, set_screen, set_status); // TOFU + } else { + *ctx.shared.target.lock().unwrap() = target; + set_screen.call(Screen::Pair); } } + +fn connect( + ctx: &Arc, + target: &Target, + pin: Option<[u8; 32]>, + set_screen: &AsyncSetState, + set_status: &AsyncSetState, +) { + let s = ctx.settings.lock().unwrap().clone(); + let mode = if s.width != 0 && s.refresh_hz != 0 { + Mode { + width: s.width, + height: s.height, + refresh_hz: s.refresh_hz, + } + } else { + Mode { + width: 1920, + height: 1080, + refresh_hz: 60, + } + }; + let gamepad_pref = match GamepadPref::from_name(&s.gamepad) { + Some(GamepadPref::Auto) | None => ctx.gamepad.auto_pref(), + Some(explicit) => explicit, + }; + let handle = session::start(SessionParams { + host: target.addr.clone(), + port: target.port, + mode, + compositor: CompositorPref::Auto, + gamepad: gamepad_pref, + bitrate_kbps: s.bitrate_kbps, + mic_enabled: s.mic_enabled, + pin, + identity: ctx.identity.clone(), + }); + set_screen.call(Screen::Connecting); + + let tofu = pin.is_none(); + let (shared, gamepad) = (ctx.shared.clone(), ctx.gamepad.clone()); + let (ss, st) = (set_screen.clone(), set_status.clone()); + let target = target.clone(); + std::thread::spawn(move || loop { + match handle.events.recv_blocking() { + Ok(SessionEvent::Connected { + connector, + fingerprint, + .. + }) => { + if tofu { + let mut k = KnownHosts::load(); + k.upsert(KnownHost { + name: target.name.clone(), + addr: target.addr.clone(), + port: target.port, + fp_hex: trust::hex(&fingerprint), + paired: false, + }); + let _ = k.save(); + } + gamepad.attach(connector.clone()); + *shared.handoff.lock().unwrap() = Some((connector, handle.frames.clone())); + ss.call(Screen::Stream); + } + Ok(SessionEvent::Failed { + msg, + trust_rejected, + }) => { + st.call(msg); + gamepad.detach(); + if trust_rejected { + // Pinned-fingerprint mismatch / pairing required → re-pair via the PIN screen. + *shared.target.lock().unwrap() = target.clone(); + ss.call(Screen::Pair); + } else { + ss.call(Screen::Hosts); + } + break; + } + Ok(SessionEvent::Ended(err)) => { + st.call(err.unwrap_or_else(|| "Session ended".into())); + gamepad.detach(); + ss.call(Screen::Hosts); + break; + } + Ok(SessionEvent::Stats(_)) => {} + Err(_) => { + gamepad.detach(); + ss.call(Screen::Hosts); + break; + } + } + }); +} + +fn pair_page( + cx: &mut RenderCx, + ctx: &Arc, + set_screen: &AsyncSetState, + set_status: &AsyncSetState, +) -> Element { + let (code, set_code) = cx.use_state(String::new()); + let target = ctx.shared.target.lock().unwrap().clone(); + + let (ctx2, ss, st, code2, target2) = ( + ctx.clone(), + set_screen.clone(), + set_status.clone(), + code.clone(), + target.clone(), + ); + let pair_btn = button("Pair & Connect").accent().on_click(move || { + let pin = code2.trim().to_string(); + let (ctx3, ss, st, target3) = (ctx2.clone(), ss.clone(), st.clone(), target2.clone()); + std::thread::spawn(move || { + let name = std::env::var("COMPUTERNAME").unwrap_or_else(|_| "windows-client".into()); + match NativeClient::pair( + &target3.addr, + target3.port, + (&ctx3.identity.0, &ctx3.identity.1), + &pin, + &name, + std::time::Duration::from_secs(90), + ) { + Ok(fp) => { + let mut k = KnownHosts::load(); + k.upsert(KnownHost { + name: target3.name.clone(), + addr: target3.addr.clone(), + port: target3.port, + fp_hex: trust::hex(&fp), + paired: true, + }); + let _ = k.save(); + connect(&ctx3, &target3, Some(fp), &ss, &st); + } + Err(e) => { + st.call(format!("Pairing failed: {e:?} (wrong PIN, or not armed?)")); + ss.call(Screen::Hosts); + } + } + }); + }); + let back = { + let ss = set_screen.clone(); + button("Cancel").on_click(move || ss.call(Screen::Hosts)) + }; + + vstack(( + text_block(format!("Pair with {}", target.name)) + .font_size(22.0) + .bold(), + text_block("Arm pairing on the host (console or web UI), then enter the 4-digit PIN."), + text_box(code) + .placeholder("PIN") + .on_changed(move |s| set_code.call(s)), + hstack((pair_btn, back)).spacing(8.0), + )) + .spacing(12.0) + .into() +} + +fn settings_page(ctx: &Arc, set_screen: &AsyncSetState) -> Element { + let s = ctx.settings.lock().unwrap().clone(); + let res_i = RESOLUTIONS + .iter() + .position(|&(w, h)| w == s.width && h == s.height) + .unwrap_or(0) as i32; + let hz_i = REFRESH.iter().position(|&r| r == s.refresh_hz).unwrap_or(0) as i32; + + let res_names: Vec = RESOLUTIONS + .iter() + .map(|&(w, h)| { + if w == 0 { + "Native display".into() + } else { + format!("{w} × {h}") + } + }) + .collect(); + let hz_names: Vec = REFRESH + .iter() + .map(|&r| { + if r == 0 { + "Native".into() + } else { + format!("{r} Hz") + } + }) + .collect(); + + let res_combo = { + let ctx = ctx.clone(); + ComboBox::new(res_names) + .header("Resolution") + .selected_index(res_i) + .on_selection_changed(move |i: i32| { + let (w, h) = RESOLUTIONS[(i.max(0) as usize).min(RESOLUTIONS.len() - 1)]; + let mut s = ctx.settings.lock().unwrap(); + (s.width, s.height) = (w, h); + s.save(); + }) + }; + let hz_combo = { + let ctx = ctx.clone(); + ComboBox::new(hz_names) + .header("Refresh rate") + .selected_index(hz_i) + .on_selection_changed(move |i: i32| { + let mut s = ctx.settings.lock().unwrap(); + s.refresh_hz = REFRESH[(i.max(0) as usize).min(REFRESH.len() - 1)]; + s.save(); + }) + }; + let mic_toggle = { + let ctx = ctx.clone(); + check_box(s.mic_enabled) + .label("Stream microphone to the host") + .on_changed(move |on: bool| { + let mut s = ctx.settings.lock().unwrap(); + s.mic_enabled = on; + s.save(); + }) + }; + let back = { + let ss = set_screen.clone(); + button("Back") + .accent() + .on_click(move || ss.call(Screen::Hosts)) + }; + + vstack(( + text_block("Settings").font_size(28.0).bold(), + res_combo, + hz_combo, + mic_toggle, + back, + )) + .spacing(12.0) + .into() +} + +fn present_newest(ctx: &mut PresentCtx) { + let mut newest = None; + while let Ok(f) = ctx.frames.try_recv() { + newest = Some(f); + } + let cpu = newest.as_ref().map(|DecodedFrame::Cpu(c)| c); + ctx.presenter.present(cpu); +} + +fn stream_page(cx: &mut RenderCx, ctx: &Arc) -> Element { + // Take the connector + frames handoff once on mount; keep the connector alive (and for + // input once that lands) in a use_ref, stash frames for `on_ready`. + let connector_ref = cx.use_ref::>>(None); + cx.use_effect((), { + let shared = ctx.shared.clone(); + let connector_ref = connector_ref.clone(); + move || { + if let Some((connector, frames)) = shared.handoff.lock().unwrap().take() { + connector_ref.set(Some(connector)); + PENDING_FRAMES.with(|c| *c.borrow_mut() = Some(frames)); + } + } + }); + + let rendering = cx.use_ref::>(None); + cx.use_effect((), { + let rendering = rendering.clone(); + move || { + if let Ok(r) = on_rendering(|| { + PRESENT.with(|cell| { + if let Some(ctx) = cell.borrow_mut().as_mut() { + present_newest(ctx); + } + }); + }) { + rendering.set(Some(r)); + } + } + }); + + swap_chain_panel() + .on_ready(|panel| match Presenter::new(1280, 720) { + Ok(p) => { + if let Err(e) = panel.set_swap_chain(p.swap_chain()) { + tracing::error!(error = %e, "set_swap_chain"); + } + if let Some(frames) = PENDING_FRAMES.with(|c| c.borrow_mut().take()) { + PRESENT.with(|cell| { + *cell.borrow_mut() = Some(PresentCtx { + presenter: p, + frames, + }); + }); + tracing::info!("stream presenter bound to SwapChainPanel"); + } + } + Err(e) => tracing::error!(error = %e, "create presenter"), + }) + .on_resize(|w, h| { + PRESENT.with(|cell| { + if let Some(ctx) = cell.borrow_mut().as_mut() { + ctx.presenter.resize(w as u32, h as u32); + } + }); + }) + .into() +} diff --git a/crates/punktfunk-client-windows/src/discovery.rs b/crates/punktfunk-client-windows/src/discovery.rs index e825f80..e8d0a6e 100644 --- a/crates/punktfunk-client-windows/src/discovery.rs +++ b/crates/punktfunk-client-windows/src/discovery.rs @@ -4,7 +4,7 @@ use mdns_sd::{ServiceDaemon, ServiceEvent}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct DiscoveredHost { /// Stable row key: the advertised host id, falling back to the mDNS fullname. pub key: String, diff --git a/crates/punktfunk-client-windows/src/gamepad.rs b/crates/punktfunk-client-windows/src/gamepad.rs index 6f282da..1fb03c9 100644 --- a/crates/punktfunk-client-windows/src/gamepad.rs +++ b/crates/punktfunk-client-windows/src/gamepad.rs @@ -51,7 +51,9 @@ pub struct GamepadService { pads: Arc>>, active: Arc>>, pinned: Arc>>, - ctl: Sender, + // `Arc>` (not a bare `Sender`, which is `!Sync`) so the service is `Sync` — the + // WinUI app shares it across the UI thread and the session-pump thread (attach/detach). + ctl: Arc>>, } impl GamepadService { @@ -75,7 +77,7 @@ impl GamepadService { pads, active, pinned, - ctl, + ctl: Arc::new(Mutex::new(ctl)), } } @@ -95,15 +97,15 @@ impl GamepadService { #[allow(dead_code)] // consumed by the settings GUI (follow-up) pub fn set_pinned(&self, id: Option) { - let _ = self.ctl.send(Ctl::Pin(id)); + let _ = self.ctl.lock().unwrap().send(Ctl::Pin(id)); } pub fn attach(&self, connector: Arc) { - let _ = self.ctl.send(Ctl::Attach(connector)); + let _ = self.ctl.lock().unwrap().send(Ctl::Attach(connector)); } pub fn detach(&self) { - let _ = self.ctl.send(Ctl::Detach); + let _ = self.ctl.lock().unwrap().send(Ctl::Detach); } /// What "Automatic" resolves to right now — the virtual pad matching the physical one diff --git a/crates/punktfunk-client-windows/src/keymap.rs b/crates/punktfunk-client-windows/src/keymap.rs deleted file mode 100644 index 6969784..0000000 --- a/crates/punktfunk-client-windows/src/keymap.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Local key/button codes → the punktfunk input wire contract. -//! -//! The wire carries Windows Virtual-Key codes (the GameStream convention; the host maps them -//! back with `inject::vk_to_evdev`). On Windows the VK is the *native* source — but winit -//! hands us a layout-independent **physical** `KeyCode` (`KeyA` is always the QWERTY-A -//! position), which is exactly what a game wants: positional keys map positionally regardless -//! of the user's keyboard layout. This table is that physical-position → VK mapping, the -//! direct analogue of the Linux client's evdev table. - -use winit::keyboard::KeyCode; - -/// Map a winit physical `KeyCode` to the Windows VK code the host expects. `None` = a key the -/// wire contract doesn't cover (media keys etc.) — drop it rather than guess. -pub fn keycode_to_vk(code: KeyCode) -> Option { - use KeyCode::*; - Some(match code { - // --- Navigation / editing / whitespace --- - Backspace => 0x08, - Tab => 0x09, - Enter => 0x0D, - Pause => 0x13, - CapsLock => 0x14, - Escape => 0x1B, - Space => 0x20, - PageUp => 0x21, - PageDown => 0x22, - End => 0x23, - Home => 0x24, - ArrowLeft => 0x25, - ArrowUp => 0x26, - ArrowRight => 0x27, - ArrowDown => 0x28, - PrintScreen => 0x2C, - Insert => 0x2D, - Delete => 0x2E, - - // --- Digit row --- - Digit0 => 0x30, - Digit1 => 0x31, - Digit2 => 0x32, - Digit3 => 0x33, - Digit4 => 0x34, - Digit5 => 0x35, - Digit6 => 0x36, - Digit7 => 0x37, - Digit8 => 0x38, - Digit9 => 0x39, - - // --- Letters --- - KeyA => 0x41, - KeyB => 0x42, - KeyC => 0x43, - KeyD => 0x44, - KeyE => 0x45, - KeyF => 0x46, - KeyG => 0x47, - KeyH => 0x48, - KeyI => 0x49, - KeyJ => 0x4A, - KeyK => 0x4B, - KeyL => 0x4C, - KeyM => 0x4D, - KeyN => 0x4E, - KeyO => 0x4F, - KeyP => 0x50, - KeyQ => 0x51, - KeyR => 0x52, - KeyS => 0x53, - KeyT => 0x54, - KeyU => 0x55, - KeyV => 0x56, - KeyW => 0x57, - KeyX => 0x58, - KeyY => 0x59, - KeyZ => 0x5A, - - // --- Meta / context-menu --- - SuperLeft => 0x5B, // VK_LWIN - SuperRight => 0x5C, // VK_RWIN - ContextMenu => 0x5D, - - // --- Numpad --- - Numpad0 => 0x60, - Numpad1 => 0x61, - Numpad2 => 0x62, - Numpad3 => 0x63, - Numpad4 => 0x64, - Numpad5 => 0x65, - Numpad6 => 0x66, - Numpad7 => 0x67, - Numpad8 => 0x68, - Numpad9 => 0x69, - NumpadMultiply => 0x6A, - NumpadAdd => 0x6B, - NumpadEnter => 0x6C, // VK_SEPARATOR (matches the Linux client's KP_ENTER mapping) - NumpadSubtract => 0x6D, - NumpadDecimal => 0x6E, - NumpadDivide => 0x6F, - - // --- Function keys --- - F1 => 0x70, - F2 => 0x71, - F3 => 0x72, - F4 => 0x73, - F5 => 0x74, - F6 => 0x75, - F7 => 0x76, - F8 => 0x77, - F9 => 0x78, - F10 => 0x79, - F11 => 0x7A, - F12 => 0x7B, - - // --- Locks --- - NumLock => 0x90, - ScrollLock => 0x91, - - // --- Left/right modifiers --- - ShiftLeft => 0xA0, - ShiftRight => 0xA1, - ControlLeft => 0xA2, - ControlRight => 0xA3, - AltLeft => 0xA4, - AltRight => 0xA5, - - // --- OEM punctuation (US-layout positions) --- - Semicolon => 0xBA, // VK_OEM_1 - Equal => 0xBB, // VK_OEM_PLUS - Comma => 0xBC, // VK_OEM_COMMA - Minus => 0xBD, // VK_OEM_MINUS - Period => 0xBE, // VK_OEM_PERIOD - Slash => 0xBF, // VK_OEM_2 - Backquote => 0xC0, // VK_OEM_3 - BracketLeft => 0xDB, // VK_OEM_4 - Backslash => 0xDC, // VK_OEM_5 - BracketRight => 0xDD, // VK_OEM_6 - Quote => 0xDE, // VK_OEM_7 - IntlBackslash => 0xE2, // VK_OEM_102 (the 102nd key) - - _ => return None, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Spot-check positions against the Windows VK constants the host's `vk_to_evdev` knows. - #[test] - fn maps_known_positions() { - assert_eq!(keycode_to_vk(KeyCode::KeyA), Some(0x41)); - assert_eq!(keycode_to_vk(KeyCode::KeyZ), Some(0x5A)); - assert_eq!(keycode_to_vk(KeyCode::Digit0), Some(0x30)); - assert_eq!(keycode_to_vk(KeyCode::Escape), Some(0x1B)); - assert_eq!(keycode_to_vk(KeyCode::F11), Some(0x7A)); - assert_eq!(keycode_to_vk(KeyCode::ShiftLeft), Some(0xA0)); - assert_eq!(keycode_to_vk(KeyCode::IntlBackslash), Some(0xE2)); - assert_eq!(keycode_to_vk(KeyCode::Numpad9), Some(0x69)); - // A key outside the wire contract is dropped, not guessed. - assert_eq!(keycode_to_vk(KeyCode::AudioVolumeMute), None); - } -} diff --git a/crates/punktfunk-client-windows/src/main.rs b/crates/punktfunk-client-windows/src/main.rs index 497694b..58e4bdb 100644 --- a/crates/punktfunk-client-windows/src/main.rs +++ b/crates/punktfunk-client-windows/src/main.rs @@ -1,18 +1,16 @@ //! `punktfunk-client` — the native Windows punktfunk/1 client. //! -//! Pure Rust: `NativeClient` linked as a crate (no C ABI, like the GTK Linux client) · -//! FFmpeg decode · WASAPI audio · SDL3 gamepads · a winit window + Direct3D11 flip-model -//! swapchain present surface. The trust surface mirrors the other native clients: persistent -//! identity, trust-on-first-use, SPAKE2 PIN pairing. +//! Pure Rust: `NativeClient` linked as a crate (no C ABI, like the GTK Linux client) · FFmpeg +//! decode · WASAPI audio · SDL3 gamepads · a **WinUI 3** shell (windows-reactor) with the video +//! on a `SwapChainPanel` bound to a D3D11 composition swapchain. The trust surface mirrors the +//! other native clients: persistent identity, trust-on-first-use, SPAKE2 PIN pairing — all in-app +//! (host list, settings, pairing). `--headless` keeps a CLI connect path for tests/measurement. //! //! Usage: -//! punktfunk-client --connect host[:port] [--pin HEX] [--pair PIN] [--mode WxHxHz] -//! [--bitrate MBPS] [--mic] -//! punktfunk-client --headless --connect … (no window; count frames + print stats) -//! -//! Trust: an explicit `--pin HEX` (or a host already pinned in the known-hosts store) connects -//! silently; `--pair PIN` runs the SPAKE2 ceremony first; otherwise the connect is -//! trust-on-first-use (the observed fingerprint is pinned on success). +//! punktfunk-client (open the WinUI 3 window: host list, settings, pairing) +//! punktfunk-client --discover (list punktfunk hosts on the LAN) +//! punktfunk-client --headless --connect host[:port] [--pin HEX] [--pair PIN] [--mode WxHxHz] +//! [--bitrate MBPS] [--mic] (no window; count frames + print stats) #[cfg(windows)] mod app; @@ -23,8 +21,6 @@ mod discovery; #[cfg(windows)] mod gamepad; #[cfg(windows)] -mod keymap; -#[cfg(windows)] mod present; #[cfg(windows)] mod session; @@ -35,8 +31,6 @@ mod video; #[cfg(windows)] fn main() { - use punktfunk_core::config::{CompositorPref, GamepadPref, Mode}; - tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()), @@ -44,12 +38,6 @@ fn main() { .init(); let args: Vec = std::env::args().collect(); - let arg = |name: &str| -> Option { - args.iter() - .position(|a| a == name) - .and_then(|i| args.get(i + 1)) - .cloned() - }; let flag = |name: &str| args.iter().any(|a| a == name); if flag("--discover") { @@ -57,57 +45,6 @@ fn main() { return; } - let Some(target) = arg("--connect") else { - eprintln!( - "punktfunk-client: --connect host[:port] [--pin HEX] [--pair PIN] [--mode WxHxHz] \ - [--bitrate MBPS] [--mic] [--headless]\n\ - punktfunk-client --discover (list punktfunk hosts on the LAN)" - ); - std::process::exit(2); - }; - - // Saved settings supply defaults when a CLI flag is absent (the GUI host-list/settings - // chrome is a follow-up; until then these are the persisted preferences). A CLI flag both - // overrides and is written back, so the next bare run reuses it. - let mut settings = trust::Settings::load(); - let (host, port) = match target.rsplit_once(':') { - Some((a, p)) => (a.to_string(), p.parse().unwrap_or(9777)), - None => (target.clone(), 9777u16), - }; - // CLI overrides fold into the persisted settings, then we derive the effective values. - if let Some(m) = arg("--mode").and_then(|m| { - let mut it = m.split(['x', 'X']); - Some(( - it.next()?.parse::().ok()?, - it.next()?.parse::().ok()?, - it.next()?.parse::().ok()?, - )) - }) { - (settings.width, settings.height, settings.refresh_hz) = m; - } - if let Some(b) = arg("--bitrate").and_then(|b| b.parse::().ok()) { - settings.bitrate_kbps = b * 1000; - } - if flag("--mic") { - settings.mic_enabled = true; - } - settings.save(); - let mode = if settings.width != 0 && settings.refresh_hz != 0 { - Mode { - width: settings.width, - height: settings.height, - refresh_hz: settings.refresh_hz, - } - } else { - Mode { - width: 1920, - height: 1080, - refresh_hz: 60, - } - }; - let bitrate_kbps = settings.bitrate_kbps; - let mic_enabled = settings.mic_enabled; - let identity = match trust::load_or_create_identity() { Ok(i) => i, Err(e) => { @@ -116,7 +53,61 @@ fn main() { } }; - // Resolve trust: explicit pin > already-pinned host > pairing ceremony > TOFU. + if flag("--headless") { + run_headless_cli(&args, identity); + return; + } + + // Windowed (default): the WinUI 3 app owns host selection, settings, and pairing. + let gamepad = gamepad::GamepadService::start(); + if let Err(e) = app::run(identity, gamepad) { + tracing::error!(error = %e, "WinUI app failed"); + std::process::exit(1); + } +} + +/// `--headless --connect host[:port] …`: connect from the CLI, count frames, print stats — the +/// Windows analogue of `punktfunk-client-rs`. +#[cfg(windows)] +fn run_headless_cli(args: &[String], identity: (String, String)) { + use punktfunk_core::config::{CompositorPref, GamepadPref, Mode}; + use std::time::{Duration, Instant}; + + let arg = |name: &str| -> Option { + args.iter() + .position(|a| a == name) + .and_then(|i| args.get(i + 1)) + .cloned() + }; + let flag = |name: &str| args.iter().any(|a| a == name); + + let Some(target) = arg("--connect") else { + eprintln!("--headless requires --connect host[:port]"); + std::process::exit(2); + }; + let (host, port) = match target.rsplit_once(':') { + Some((a, p)) => (a.to_string(), p.parse().unwrap_or(9777)), + None => (target.clone(), 9777u16), + }; + let mode = arg("--mode") + .and_then(|m| { + let mut it = m.split(['x', 'X']); + Some(Mode { + width: it.next()?.parse().ok()?, + height: it.next()?.parse().ok()?, + refresh_hz: it.next()?.parse().ok()?, + }) + }) + .unwrap_or(Mode { + width: 1280, + height: 720, + refresh_hz: 60, + }); + let bitrate_kbps = arg("--bitrate") + .and_then(|b| b.parse::().ok()) + .map(|m| m * 1000) + .unwrap_or(0); + let known = trust::KnownHosts::load(); let mut pin = arg("--pin") .and_then(|h| trust::parse_hex32(&h)) @@ -133,7 +124,7 @@ fn main() { (&identity.0, &identity.1), code.trim(), &name, - std::time::Duration::from_secs(90), + Duration::from_secs(90), ) { Ok(fp) => { let mut k = trust::KnownHosts::load(); @@ -149,59 +140,25 @@ fn main() { pin = Some(fp); } Err(e) => { - eprintln!("Pairing failed: {e:?} (wrong PIN, or pairing not armed on the host?)"); + eprintln!("Pairing failed: {e:?}"); std::process::exit(1); } } } - let headless = flag("--headless"); - // The app-lifetime gamepad service runs only for the windowed client; it also resolves the - // "Automatic" pad type to whatever physical controller is attached (other-client parity). - let gamepad_service = (!headless).then(gamepad::GamepadService::start); - let gamepad_pref = match GamepadPref::from_name(&settings.gamepad) { - Some(GamepadPref::Auto) | None => gamepad_service - .as_ref() - .map_or(GamepadPref::Auto, |s| s.auto_pref()), - Some(explicit) => explicit, - }; - - tracing::info!(%host, port, ?mode, tofu = pin.is_none(), "connecting"); + tracing::info!(%host, port, ?mode, tofu = pin.is_none(), "connecting (headless)"); let handle = session::start(session::SessionParams { - host: host.clone(), + host, port, mode, compositor: CompositorPref::Auto, - gamepad: gamepad_pref, + gamepad: GamepadPref::Auto, bitrate_kbps, - mic_enabled, + mic_enabled: flag("--mic"), pin, identity, }); - if headless { - run_headless(handle); - return; - } - - let info = app::ConnectInfo { - name: host.clone(), - addr: host, - port, - tofu: pin.is_none(), - }; - let gamepad_service = gamepad_service.expect("started for the windowed path"); - if let Err(e) = app::WinApp::new(handle, info, gamepad_service).run() { - tracing::error!(error = %e, "windowed app failed"); - std::process::exit(1); - } -} - -/// Headless runner (`--headless`): drain events + frames, print stats, exit when the host -/// ends or the harness deadline elapses — the Windows analogue of `punktfunk-client-rs`. -#[cfg(windows)] -fn run_headless(handle: session::SessionHandle) { - use std::time::{Duration, Instant}; let deadline = Instant::now() + Duration::from_secs(60); let mut frames_seen = 0u64; loop { @@ -218,16 +175,8 @@ fn run_headless(handle: session::SessionHandle) { frames_seen, "stats" ), - session::SessionEvent::Failed { - msg, - trust_rejected, - } => { - tracing::error!(%msg, trust_rejected, "connect failed"); - if trust_rejected { - tracing::error!( - "host fingerprint changed or pairing required — re-pair with --pair PIN" - ); - } + session::SessionEvent::Failed { msg, .. } => { + tracing::error!(%msg, "connect failed"); return; } session::SessionEvent::Ended(err) => { @@ -248,8 +197,7 @@ fn run_headless(handle: session::SessionHandle) { } } -/// `--discover`: browse the LAN for punktfunk hosts (mDNS) and print them, then exit — the -/// CLI analogue of the GTK client's discovered-hosts list. +/// `--discover`: browse the LAN for punktfunk hosts (mDNS) and print them, then exit. #[cfg(windows)] fn discover_and_print() { use std::time::{Duration, Instant}; @@ -281,9 +229,9 @@ fn discover_and_print() { } } -/// Win32/Direct3D11/WASAPI/SDL3 are Windows turf; this stub keeps `cargo build --workspace` -/// green on Linux/macOS (the other native clients live in crates/punktfunk-client-linux and -/// clients/apple). +/// WinUI 3 / Direct3D11 / WASAPI / SDL3 are Windows turf; this stub keeps `cargo build +/// --workspace` green on Linux/macOS (the other native clients live in +/// crates/punktfunk-client-linux and clients/apple). #[cfg(not(windows))] fn main() { eprintln!( diff --git a/crates/punktfunk-client-windows/src/present.rs b/crates/punktfunk-client-windows/src/present.rs index f02bad1..5740b97 100644 --- a/crates/punktfunk-client-windows/src/present.rs +++ b/crates/punktfunk-client-windows/src/present.rs @@ -1,17 +1,19 @@ -//! Direct3D11 presenter: upload a decoded `CpuFrame` (RGBA) into a dynamic texture and draw -//! it Contain-fit into a flip-model swapchain bound to the window's HWND, then present. +//! Direct3D11 presenter for a WinUI 3 `SwapChainPanel`: upload a decoded `CpuFrame` (RGBA) +//! into a dynamic texture and draw it Contain-fit into a **composition** flip-model swapchain, +//! which the reactor stream page binds to the panel via `SwapChainPanelHandle::set_swap_chain`. //! -//! The device prefers a hardware adapter and falls back to **WARP** (the GPU-less dev box -//! runs the whole present path in software). The draw is a single full-screen triangle -//! sampling the video texture; a letterbox is produced by clearing the back buffer black and -//! setting the viewport to the Contain-fit rect (no per-frame vertex buffer). This is the -//! SDR 8-bit path; the 10-bit/HDR present (`R10G10B10A2` + `SetColorSpace1(...G2084_P2020)`) -//! is a follow-up alongside the P010 D3D11VA decode. +//! The device prefers a hardware adapter and falls back to **WARP** (the GPU-less dev box runs +//! the whole present path in software). The draw is a single full-screen triangle sampling the +//! video texture; a letterbox is produced by clearing the back buffer black and setting the +//! viewport to the Contain-fit rect (no per-frame vertex buffer). SDR 8-bit path; the +//! 10-bit/HDR present (`R10G10B10A2` + `SetColorSpace1`) is a follow-up alongside P010 decode. +//! +//! All `windows` types here come from the same windows-rs commit as `windows-reactor`, so the +//! `IDXGISwapChain1` handed to `set_swap_chain` satisfies reactor's `windows_core::Interface`. use crate::video::CpuFrame; use anyhow::{anyhow, Context, Result}; use windows::core::{Interface, PCSTR}; -use windows::Win32::Foundation::{HMODULE, HWND}; use windows::Win32::Graphics::Direct3D::Fxc::{D3DCompile, D3DCOMPILE_OPTIMIZATION_LEVEL3}; use windows::Win32::Graphics::Direct3D::{ ID3DBlob, D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_FEATURE_LEVEL_11_0, @@ -35,63 +37,114 @@ SamplerState smp : register(s0); float4 ps_main(VSOut i) : SV_Target { return tex.Sample(smp, i.uv); } "#; -pub struct Renderer { +pub struct Presenter { device: ID3D11Device, context: ID3D11DeviceContext, vs: ID3D11VertexShader, ps: ID3D11PixelShader, sampler: ID3D11SamplerState, - /// Video texture + its SRV + dimensions; recreated when the decoded size changes. + swap: IDXGISwapChain1, + rtv: Option, + /// Video texture + SRV + dimensions; recreated when the decoded size changes. tex: Option<(ID3D11Texture2D, ID3D11ShaderResourceView, u32, u32)>, + /// Panel (swapchain) size in pixels, updated on resize. + panel_w: u32, + panel_h: u32, } -impl Renderer { - pub fn new() -> Result { +impl Presenter { + /// Create the D3D11 device + composition swapchain + shaders, sized to the panel. + pub fn new(width: u32, height: u32) -> Result { let (device, context) = create_device()?; - let vs_blob = compile(SHADER_HLSL, "vs_main", "vs_5_0")?; - let ps_blob = compile(SHADER_HLSL, "ps_main", "ps_5_0")?; - let (vs, ps) = unsafe { - let mut vs = None; - device - .CreateVertexShader(blob_bytes(&vs_blob), None, Some(&mut vs)) - .context("CreateVertexShader")?; - let mut ps = None; - device - .CreatePixelShader(blob_bytes(&ps_blob), None, Some(&mut ps)) - .context("CreatePixelShader")?; - (vs.unwrap(), ps.unwrap()) - }; - let sampler = unsafe { - let desc = D3D11_SAMPLER_DESC { - Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, - AddressU: D3D11_TEXTURE_ADDRESS_CLAMP, - AddressV: D3D11_TEXTURE_ADDRESS_CLAMP, - AddressW: D3D11_TEXTURE_ADDRESS_CLAMP, - MaxLOD: D3D11_FLOAT32_MAX, - ..Default::default() - }; - let mut s = None; - device - .CreateSamplerState(&desc, Some(&mut s)) - .context("CreateSamplerState")?; - s.unwrap() - }; - Ok(Renderer { + let (vs, ps, sampler) = build_pipeline(&device)?; + let swap = create_composition_swapchain(&device, width.max(1), height.max(1))?; + Ok(Presenter { device, context, vs, ps, sampler, + swap, + rtv: None, tex: None, + panel_w: width.max(1), + panel_h: height.max(1), }) } - pub fn device(&self) -> &ID3D11Device { - &self.device + /// The DXGI swapchain to hand to `SwapChainPanelHandle::set_swap_chain`. + pub fn swap_chain(&self) -> &IDXGISwapChain1 { + &self.swap } - /// Upload one decoded RGBA frame, recreating the GPU texture if the size changed. - pub fn upload(&mut self, frame: &CpuFrame) -> Result<()> { + /// Resize the back buffers to the panel's new size (drops the stale RTV). + pub fn resize(&mut self, width: u32, height: u32) { + if width == 0 || height == 0 || (width == self.panel_w && height == self.panel_h) { + return; + } + self.rtv = None; // release all back-buffer refs before ResizeBuffers + unsafe { + let _ = self.swap.ResizeBuffers( + 0, + width, + height, + DXGI_FORMAT_UNKNOWN, + DXGI_SWAP_CHAIN_FLAG(0), + ); + } + self.panel_w = width; + self.panel_h = height; + } + + /// Present one decoded frame (Contain-fit) — or, when `frame` is `None`, just re-present the + /// last texture (or black). Called from the reactor `on_rendering` per-frame callback. + pub fn present(&mut self, frame: Option<&CpuFrame>) { + if let Some(f) = frame { + if let Err(e) = self.upload(f) { + tracing::warn!(error = %e, "frame upload failed"); + } + } + let Ok(rtv) = self.rtv() else { + return; + }; + let (pw, ph) = (self.panel_w, self.panel_h); + unsafe { + let c = &self.context; + c.ClearRenderTargetView(&rtv, &[0.0, 0.0, 0.0, 1.0]); + if let Some((_, srv, vw, vh)) = &self.tex { + // Contain-fit viewport: scale to the smaller axis, centre, letterbox the rest. + let (ww, wh, vfw, vfh) = ( + pw as f32, + ph as f32, + (*vw).max(1) as f32, + (*vh).max(1) as f32, + ); + let scale = (ww / vfw).min(wh / vfh); + let (dw, dh) = (vfw * scale, vfh * scale); + let (ox, oy) = ((ww - dw) / 2.0, (wh - dh) / 2.0); + c.OMSetRenderTargets(Some(&[Some(rtv.clone())]), None); + let vp = D3D11_VIEWPORT { + TopLeftX: ox, + TopLeftY: oy, + Width: dw, + Height: dh, + MinDepth: 0.0, + MaxDepth: 1.0, + }; + c.RSSetViewports(Some(&[vp])); + c.IASetInputLayout(None); + c.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + c.VSSetShader(&self.vs, None); + c.PSSetShader(&self.ps, None); + c.PSSetShaderResources(0, Some(&[Some(srv.clone())])); + c.PSSetSamplers(0, Some(&[Some(self.sampler.clone())])); + c.Draw(3, 0); + } + let _ = self.swap.Present(1, DXGI_PRESENT(0)); + } + } + + fn upload(&mut self, frame: &CpuFrame) -> Result<()> { let (w, h) = (frame.width, frame.height); let need_new = !matches!(&self.tex, Some((_, _, tw, th)) if *tw == w && *th == h); if need_new { @@ -148,122 +201,7 @@ impl Renderer { Ok(()) } - /// Clear the target black and draw the current video texture Contain-fit into the window. - pub fn draw( - &self, - rtv: &ID3D11RenderTargetView, - win_w: u32, - win_h: u32, - vid_w: u32, - vid_h: u32, - ) { - let Some((_, srv, _, _)) = &self.tex else { - return; - }; - // Contain-fit: scale to the smaller axis, centre, letterbox the rest. - let (ww, wh, vw, vh) = ( - win_w as f32, - win_h as f32, - vid_w.max(1) as f32, - vid_h.max(1) as f32, - ); - let scale = (ww / vw).min(wh / vh); - let (dw, dh) = (vw * scale, vh * scale); - let (ox, oy) = ((ww - dw) / 2.0, (wh - dh) / 2.0); - unsafe { - let c = &self.context; - c.ClearRenderTargetView(rtv, &[0.0, 0.0, 0.0, 1.0]); - c.OMSetRenderTargets(Some(&[Some(rtv.clone())]), None); - let vp = D3D11_VIEWPORT { - TopLeftX: ox, - TopLeftY: oy, - Width: dw, - Height: dh, - MinDepth: 0.0, - MaxDepth: 1.0, - }; - c.RSSetViewports(Some(&[vp])); - c.IASetInputLayout(None); - c.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - c.VSSetShader(&self.vs, None); - c.PSSetShader(&self.ps, None); - c.PSSetShaderResources(0, Some(&[Some(srv.clone())])); - c.PSSetSamplers(0, Some(&[Some(self.sampler.clone())])); - c.Draw(3, 0); - } - } -} - -/// A flip-model swapchain bound to a window HWND, with a lazily-(re)built render-target view. -pub struct SwapChain { - swap: IDXGISwapChain1, - device: ID3D11Device, - rtv: Option, - pub width: u32, - pub height: u32, -} - -impl SwapChain { - pub fn new(device: &ID3D11Device, hwnd: HWND, width: u32, height: u32) -> Result { - let dxdev: IDXGIDevice = device.cast().context("IDXGIDevice cast")?; - let factory: IDXGIFactory2 = unsafe { - let adapter = dxdev.GetAdapter().context("GetAdapter")?; - adapter.GetParent().context("GetParent (IDXGIFactory2)")? - }; - let desc = DXGI_SWAP_CHAIN_DESC1 { - Width: width.max(1), - Height: height.max(1), - Format: DXGI_FORMAT_R8G8B8A8_UNORM, - Stereo: false.into(), - SampleDesc: DXGI_SAMPLE_DESC { - Count: 1, - Quality: 0, - }, - BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, - BufferCount: 2, - Scaling: DXGI_SCALING_STRETCH, - SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD, - AlphaMode: DXGI_ALPHA_MODE_IGNORE, - Flags: 0, - }; - let swap = unsafe { - factory - .CreateSwapChainForHwnd(device, hwnd, &desc, None, None) - .context("CreateSwapChainForHwnd")? - }; - Ok(SwapChain { - swap, - device: device.clone(), - rtv: None, - width: width.max(1), - height: height.max(1), - }) - } - - /// Resize the back buffers (window resize); drops the stale RTV so it rebuilds lazily. - pub fn resize(&mut self, width: u32, height: u32) -> Result<()> { - if width == 0 || height == 0 || (width == self.width && height == self.height) { - return Ok(()); - } - self.rtv = None; // must release all back-buffer references before ResizeBuffers - unsafe { - self.swap - .ResizeBuffers( - 0, - width, - height, - DXGI_FORMAT_UNKNOWN, - DXGI_SWAP_CHAIN_FLAG(0), - ) - .context("ResizeBuffers")?; - } - self.width = width; - self.height = height; - Ok(()) - } - - /// The current back-buffer render-target view (built on first use after create/resize). - pub fn rtv(&mut self) -> Result { + fn rtv(&mut self) -> Result { if self.rtv.is_none() { let back: ID3D11Texture2D = unsafe { self.swap.GetBuffer(0).context("GetBuffer")? }; let rtv = unsafe { @@ -277,13 +215,6 @@ impl SwapChain { } Ok(self.rtv.clone().unwrap()) } - - /// Present the back buffer (vsync on — a stream is host-paced, tearing-free wins here). - pub fn present(&self) { - unsafe { - let _ = self.swap.Present(1, DXGI_PRESENT(0)); - } - } } fn create_device() -> Result<(ID3D11Device, ID3D11DeviceContext)> { @@ -294,7 +225,7 @@ fn create_device() -> Result<(ID3D11Device, ID3D11DeviceContext)> { D3D11CreateDevice( None, driver, - HMODULE::default(), + None, D3D11_CREATE_DEVICE_BGRA_SUPPORT, Some(&[D3D_FEATURE_LEVEL_11_0]), D3D11_SDK_VERSION, @@ -304,12 +235,12 @@ fn create_device() -> Result<(ID3D11Device, ID3D11DeviceContext)> { ) }; if r.is_ok() { - let driver_name = if driver == D3D_DRIVER_TYPE_HARDWARE { + let name = if driver == D3D_DRIVER_TYPE_HARDWARE { "hardware" } else { "WARP (software)" }; - tracing::info!(driver = driver_name, "D3D11 device created"); + tracing::info!(driver = name, "D3D11 device created"); return Ok((device.unwrap(), context.unwrap())); } } @@ -318,6 +249,70 @@ fn create_device() -> Result<(ID3D11Device, ID3D11DeviceContext)> { )) } +/// A composition flip-model swapchain (no HWND) for binding to a XAML `SwapChainPanel`. +fn create_composition_swapchain( + device: &ID3D11Device, + width: u32, + height: u32, +) -> Result { + let dxdev: IDXGIDevice = device.cast().context("IDXGIDevice cast")?; + let factory: IDXGIFactory2 = unsafe { + let adapter = dxdev.GetAdapter().context("GetAdapter")?; + adapter.GetParent().context("GetParent (IDXGIFactory2)")? + }; + let desc = DXGI_SWAP_CHAIN_DESC1 { + Width: width, + Height: height, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + Stereo: false.into(), + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: 2, + Scaling: DXGI_SCALING_STRETCH, + SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, + Flags: 0, + }; + unsafe { + factory + .CreateSwapChainForComposition(device, &desc, None) + .context("CreateSwapChainForComposition") + } +} + +fn build_pipeline( + device: &ID3D11Device, +) -> Result<(ID3D11VertexShader, ID3D11PixelShader, ID3D11SamplerState)> { + let vs_blob = compile(SHADER_HLSL, "vs_main", "vs_5_0")?; + let ps_blob = compile(SHADER_HLSL, "ps_main", "ps_5_0")?; + unsafe { + let mut vs = None; + device + .CreateVertexShader(blob_bytes(&vs_blob), None, Some(&mut vs)) + .context("CreateVertexShader")?; + let mut ps = None; + device + .CreatePixelShader(blob_bytes(&ps_blob), None, Some(&mut ps)) + .context("CreatePixelShader")?; + let sdesc = D3D11_SAMPLER_DESC { + Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, + AddressU: D3D11_TEXTURE_ADDRESS_CLAMP, + AddressV: D3D11_TEXTURE_ADDRESS_CLAMP, + AddressW: D3D11_TEXTURE_ADDRESS_CLAMP, + MaxLOD: D3D11_FLOAT32_MAX, + ..Default::default() + }; + let mut sampler = None; + device + .CreateSamplerState(&sdesc, Some(&mut sampler)) + .context("CreateSamplerState")?; + Ok((vs.unwrap(), ps.unwrap(), sampler.unwrap())) + } +} + fn compile(src: &str, entry: &str, target: &str) -> Result { let entry_c = std::ffi::CString::new(entry).unwrap(); let target_c = std::ffi::CString::new(target).unwrap();