├── .cargo └── config.toml ├── .envrc ├── .github └── FUNDING.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bin ├── driver-proxy │ ├── Cargo.toml │ └── src │ │ ├── driver │ │ ├── camera.rs │ │ ├── hmd.rs │ │ ├── mod.rs │ │ └── server_tracked_provider.rs │ │ ├── error.rs │ │ ├── factory.rs │ │ ├── lib.rs │ │ ├── log.rs │ │ ├── server │ │ ├── driver_context.rs │ │ ├── driver_host.rs │ │ └── mod.rs │ │ └── settings.rs └── lens-server │ ├── Cargo.toml │ └── src │ └── main.rs ├── crates ├── .gitignore ├── lens-client │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── lens-protocol │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── openvr │ ├── Cargo.toml │ └── src │ │ ├── .gitignore │ │ ├── a.hpp │ │ ├── a.jsonnet │ │ ├── gen_mod_rs.sh │ │ ├── lib.rs │ │ └── mod_rs.jsonnet ├── valve-pm │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── vive-hid │ ├── Cargo.toml │ └── src │ └── lib.rs ├── dist-proxy ├── driver_lighthouse_real.sew ├── install.sh └── lens-server │ ├── LibLensDistortion.dll │ └── opencv_world346.dll ├── flake.lock ├── flake.nix └── kernel-patches ├── .config ├── .dockerignore ├── .gitignore ├── 0001-drm-edid-Add-Vive-Cosmos-Vive-Pro-2-to-non-desktop-l.patch ├── 0002-drm-edid-parse-DRM-VESA-dsc-bpp-target.patch ├── 0003-drm-amd-use-fixed-dsc-bits-per-pixel-from-edid.patch ├── Dockerfile.archlinux-kernel ├── Dockerfile.debian-kernel ├── PKGBUILD ├── build-archlinux.sh ├── build-debian.sh ├── config └── merged └── drm-edid-type-7-timings.patch /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-gnu] 2 | linker = "x86_64-w64-mingw32-cc" 3 | runner = "wine64" 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: 0lach 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /result 3 | /dist-proxy/lens-server/lens-server.exe 4 | /dist-proxy/driver_lighthouse.so 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.64" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" 25 | 26 | [[package]] 27 | name = "async-trait" 28 | version = "0.1.74" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" 31 | dependencies = [ 32 | "proc-macro2", 33 | "quote", 34 | "syn 2.0.39", 35 | ] 36 | 37 | [[package]] 38 | name = "autocfg" 39 | version = "1.1.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 42 | 43 | [[package]] 44 | name = "backtrace" 45 | version = "0.3.69" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 48 | dependencies = [ 49 | "addr2line", 50 | "cc", 51 | "cfg-if", 52 | "libc", 53 | "miniz_oxide", 54 | "object", 55 | "rustc-demangle", 56 | ] 57 | 58 | [[package]] 59 | name = "bitflags" 60 | version = "1.3.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 63 | 64 | [[package]] 65 | name = "bitflags" 66 | version = "2.4.1" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 69 | 70 | [[package]] 71 | name = "block" 72 | version = "0.1.6" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 75 | 76 | [[package]] 77 | name = "bluez-async" 78 | version = "0.7.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "5ce7d4413c940e8e3cb6afc122d3f4a07096aca259d286781128683fc9f39d9b" 81 | dependencies = [ 82 | "async-trait", 83 | "bitflags 2.4.1", 84 | "bluez-generated", 85 | "dbus", 86 | "dbus-tokio", 87 | "futures", 88 | "itertools", 89 | "log", 90 | "serde", 91 | "serde-xml-rs", 92 | "thiserror", 93 | "tokio", 94 | "uuid", 95 | ] 96 | 97 | [[package]] 98 | name = "bluez-generated" 99 | version = "0.3.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "4d1c659dbc82f0b8ca75606c91a371e763589b7f6acf36858eeed0c705afe367" 102 | dependencies = [ 103 | "dbus", 104 | ] 105 | 106 | [[package]] 107 | name = "btleplug" 108 | version = "0.11.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "ba674f1e8564205eeb1406abca78f3f4beff2408ad6b06a970da23fadeba2534" 111 | dependencies = [ 112 | "async-trait", 113 | "bitflags 2.4.1", 114 | "bluez-async", 115 | "cocoa", 116 | "dashmap", 117 | "dbus", 118 | "futures", 119 | "jni", 120 | "jni-utils", 121 | "libc", 122 | "log", 123 | "objc", 124 | "once_cell", 125 | "static_assertions", 126 | "thiserror", 127 | "tokio", 128 | "tokio-stream", 129 | "uuid", 130 | "windows", 131 | ] 132 | 133 | [[package]] 134 | name = "bytes" 135 | version = "1.2.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 138 | 139 | [[package]] 140 | name = "cc" 141 | version = "1.0.83" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 144 | dependencies = [ 145 | "libc", 146 | ] 147 | 148 | [[package]] 149 | name = "cesu8" 150 | version = "1.1.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 153 | 154 | [[package]] 155 | name = "cfg-if" 156 | version = "1.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 159 | 160 | [[package]] 161 | name = "cobs" 162 | version = "0.2.3" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" 165 | 166 | [[package]] 167 | name = "cocoa" 168 | version = "0.25.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" 171 | dependencies = [ 172 | "bitflags 1.3.2", 173 | "block", 174 | "cocoa-foundation", 175 | "core-foundation", 176 | "core-graphics", 177 | "foreign-types 0.5.0", 178 | "libc", 179 | "objc", 180 | ] 181 | 182 | [[package]] 183 | name = "cocoa-foundation" 184 | version = "0.1.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 187 | dependencies = [ 188 | "bitflags 1.3.2", 189 | "block", 190 | "core-foundation", 191 | "core-graphics-types", 192 | "foreign-types 0.3.2", 193 | "libc", 194 | "objc", 195 | ] 196 | 197 | [[package]] 198 | name = "combine" 199 | version = "4.6.6" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 202 | dependencies = [ 203 | "bytes", 204 | "memchr", 205 | ] 206 | 207 | [[package]] 208 | name = "core-foundation" 209 | version = "0.9.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 212 | dependencies = [ 213 | "core-foundation-sys", 214 | "libc", 215 | ] 216 | 217 | [[package]] 218 | name = "core-foundation-sys" 219 | version = "0.8.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 222 | 223 | [[package]] 224 | name = "core-graphics" 225 | version = "0.23.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" 228 | dependencies = [ 229 | "bitflags 1.3.2", 230 | "core-foundation", 231 | "core-graphics-types", 232 | "foreign-types 0.5.0", 233 | "libc", 234 | ] 235 | 236 | [[package]] 237 | name = "core-graphics-types" 238 | version = "0.1.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 241 | dependencies = [ 242 | "bitflags 1.3.2", 243 | "core-foundation", 244 | "foreign-types 0.3.2", 245 | "libc", 246 | ] 247 | 248 | [[package]] 249 | name = "cppvtbl" 250 | version = "0.2.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "1730ce1c1ace2663aae6433ad316230868b4c111e11d61e1742f019016771906" 253 | dependencies = [ 254 | "cppvtbl-macros", 255 | ] 256 | 257 | [[package]] 258 | name = "cppvtbl-macros" 259 | version = "0.2.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "05081a934c57bb229a28a61ee9a343df2c0695d7d48113922aa82471b9172f05" 262 | dependencies = [ 263 | "proc-macro2", 264 | "quote", 265 | "syn 1.0.109", 266 | ] 267 | 268 | [[package]] 269 | name = "crc32fast" 270 | version = "1.3.2" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 273 | dependencies = [ 274 | "cfg-if", 275 | ] 276 | 277 | [[package]] 278 | name = "ctrlc" 279 | version = "3.4.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" 282 | dependencies = [ 283 | "nix", 284 | "windows-sys", 285 | ] 286 | 287 | [[package]] 288 | name = "dashmap" 289 | version = "5.5.3" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 292 | dependencies = [ 293 | "cfg-if", 294 | "hashbrown", 295 | "lock_api", 296 | "once_cell", 297 | "parking_lot_core", 298 | ] 299 | 300 | [[package]] 301 | name = "dbus" 302 | version = "0.9.7" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" 305 | dependencies = [ 306 | "futures-channel", 307 | "futures-util", 308 | "libc", 309 | "libdbus-sys", 310 | "winapi", 311 | ] 312 | 313 | [[package]] 314 | name = "dbus-tokio" 315 | version = "0.7.6" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "007688d459bc677131c063a3a77fb899526e17b7980f390b69644bdbc41fad13" 318 | dependencies = [ 319 | "dbus", 320 | "libc", 321 | "tokio", 322 | ] 323 | 324 | [[package]] 325 | name = "driver-proxy" 326 | version = "0.1.0" 327 | dependencies = [ 328 | "cppvtbl", 329 | "lens-client", 330 | "lens-protocol", 331 | "libloading", 332 | "once_cell", 333 | "openvr", 334 | "process_path", 335 | "real_c_string", 336 | "thiserror", 337 | "tokio", 338 | "tracing", 339 | "tracing-subscriber", 340 | "valve-pm", 341 | "vive-hid", 342 | ] 343 | 344 | [[package]] 345 | name = "either" 346 | version = "1.8.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 349 | 350 | [[package]] 351 | name = "flate2" 352 | version = "1.0.28" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 355 | dependencies = [ 356 | "crc32fast", 357 | "miniz_oxide", 358 | ] 359 | 360 | [[package]] 361 | name = "foreign-types" 362 | version = "0.3.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 365 | dependencies = [ 366 | "foreign-types-shared 0.1.1", 367 | ] 368 | 369 | [[package]] 370 | name = "foreign-types" 371 | version = "0.5.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 374 | dependencies = [ 375 | "foreign-types-macros", 376 | "foreign-types-shared 0.3.1", 377 | ] 378 | 379 | [[package]] 380 | name = "foreign-types-macros" 381 | version = "0.2.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 384 | dependencies = [ 385 | "proc-macro2", 386 | "quote", 387 | "syn 2.0.39", 388 | ] 389 | 390 | [[package]] 391 | name = "foreign-types-shared" 392 | version = "0.1.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 395 | 396 | [[package]] 397 | name = "foreign-types-shared" 398 | version = "0.3.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 401 | 402 | [[package]] 403 | name = "futures" 404 | version = "0.3.29" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" 407 | dependencies = [ 408 | "futures-channel", 409 | "futures-core", 410 | "futures-executor", 411 | "futures-io", 412 | "futures-sink", 413 | "futures-task", 414 | "futures-util", 415 | ] 416 | 417 | [[package]] 418 | name = "futures-channel" 419 | version = "0.3.29" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 422 | dependencies = [ 423 | "futures-core", 424 | "futures-sink", 425 | ] 426 | 427 | [[package]] 428 | name = "futures-core" 429 | version = "0.3.29" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 432 | 433 | [[package]] 434 | name = "futures-executor" 435 | version = "0.3.29" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" 438 | dependencies = [ 439 | "futures-core", 440 | "futures-task", 441 | "futures-util", 442 | ] 443 | 444 | [[package]] 445 | name = "futures-io" 446 | version = "0.3.29" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" 449 | 450 | [[package]] 451 | name = "futures-macro" 452 | version = "0.3.29" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" 455 | dependencies = [ 456 | "proc-macro2", 457 | "quote", 458 | "syn 2.0.39", 459 | ] 460 | 461 | [[package]] 462 | name = "futures-sink" 463 | version = "0.3.29" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 466 | 467 | [[package]] 468 | name = "futures-task" 469 | version = "0.3.29" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 472 | 473 | [[package]] 474 | name = "futures-util" 475 | version = "0.3.29" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 478 | dependencies = [ 479 | "futures-channel", 480 | "futures-core", 481 | "futures-io", 482 | "futures-macro", 483 | "futures-sink", 484 | "futures-task", 485 | "memchr", 486 | "pin-project-lite", 487 | "pin-utils", 488 | "slab", 489 | ] 490 | 491 | [[package]] 492 | name = "gimli" 493 | version = "0.28.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 496 | 497 | [[package]] 498 | name = "hashbrown" 499 | version = "0.14.2" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" 502 | 503 | [[package]] 504 | name = "hermit-abi" 505 | version = "0.3.3" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 508 | 509 | [[package]] 510 | name = "hidapi" 511 | version = "2.4.1" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79" 514 | dependencies = [ 515 | "cc", 516 | "libc", 517 | "pkg-config", 518 | "winapi", 519 | ] 520 | 521 | [[package]] 522 | name = "itertools" 523 | version = "0.10.5" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 526 | dependencies = [ 527 | "either", 528 | ] 529 | 530 | [[package]] 531 | name = "itoa" 532 | version = "1.0.9" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 535 | 536 | [[package]] 537 | name = "jni" 538 | version = "0.19.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" 541 | dependencies = [ 542 | "cesu8", 543 | "combine", 544 | "jni-sys", 545 | "log", 546 | "thiserror", 547 | "walkdir", 548 | ] 549 | 550 | [[package]] 551 | name = "jni-sys" 552 | version = "0.3.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 555 | 556 | [[package]] 557 | name = "jni-utils" 558 | version = "0.1.1" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "259e9f2c3ead61de911f147000660511f07ab00adeed1d84f5ac4d0386e7a6c4" 561 | dependencies = [ 562 | "dashmap", 563 | "futures", 564 | "jni", 565 | "log", 566 | "once_cell", 567 | "static_assertions", 568 | "uuid", 569 | ] 570 | 571 | [[package]] 572 | name = "lazy_static" 573 | version = "1.4.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 576 | 577 | [[package]] 578 | name = "lens-client" 579 | version = "0.1.0" 580 | dependencies = [ 581 | "lens-protocol", 582 | "process_path", 583 | "serde_json", 584 | "thiserror", 585 | "tracing", 586 | "tracing-subscriber", 587 | ] 588 | 589 | [[package]] 590 | name = "lens-protocol" 591 | version = "0.1.0" 592 | dependencies = [ 593 | "postcard", 594 | "serde", 595 | "serde_json", 596 | "thiserror", 597 | ] 598 | 599 | [[package]] 600 | name = "lens-server" 601 | version = "0.1.0" 602 | dependencies = [ 603 | "anyhow", 604 | "lens-protocol", 605 | "libloading", 606 | "process_path", 607 | "serde_json", 608 | "tracing", 609 | "tracing-subscriber", 610 | ] 611 | 612 | [[package]] 613 | name = "libc" 614 | version = "0.2.150" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 617 | 618 | [[package]] 619 | name = "libdbus-sys" 620 | version = "0.2.5" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" 623 | dependencies = [ 624 | "pkg-config", 625 | ] 626 | 627 | [[package]] 628 | name = "libloading" 629 | version = "0.8.1" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" 632 | dependencies = [ 633 | "cfg-if", 634 | "windows-sys", 635 | ] 636 | 637 | [[package]] 638 | name = "lock_api" 639 | version = "0.4.11" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 642 | dependencies = [ 643 | "autocfg", 644 | "scopeguard", 645 | ] 646 | 647 | [[package]] 648 | name = "log" 649 | version = "0.4.20" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 652 | 653 | [[package]] 654 | name = "malloc_buf" 655 | version = "0.0.6" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 658 | dependencies = [ 659 | "libc", 660 | ] 661 | 662 | [[package]] 663 | name = "memchr" 664 | version = "2.6.4" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 667 | 668 | [[package]] 669 | name = "miniz_oxide" 670 | version = "0.7.1" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 673 | dependencies = [ 674 | "adler", 675 | ] 676 | 677 | [[package]] 678 | name = "mio" 679 | version = "0.8.9" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 682 | dependencies = [ 683 | "libc", 684 | "wasi", 685 | "windows-sys", 686 | ] 687 | 688 | [[package]] 689 | name = "nix" 690 | version = "0.27.1" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 693 | dependencies = [ 694 | "bitflags 2.4.1", 695 | "cfg-if", 696 | "libc", 697 | ] 698 | 699 | [[package]] 700 | name = "nu-ansi-term" 701 | version = "0.46.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 704 | dependencies = [ 705 | "overload", 706 | "winapi", 707 | ] 708 | 709 | [[package]] 710 | name = "num_cpus" 711 | version = "1.16.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 714 | dependencies = [ 715 | "hermit-abi", 716 | "libc", 717 | ] 718 | 719 | [[package]] 720 | name = "objc" 721 | version = "0.2.7" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 724 | dependencies = [ 725 | "malloc_buf", 726 | ] 727 | 728 | [[package]] 729 | name = "object" 730 | version = "0.32.1" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 733 | dependencies = [ 734 | "memchr", 735 | ] 736 | 737 | [[package]] 738 | name = "once_cell" 739 | version = "1.18.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 742 | 743 | [[package]] 744 | name = "openvr" 745 | version = "0.1.0" 746 | dependencies = [ 747 | "cppvtbl", 748 | "real_c_string", 749 | ] 750 | 751 | [[package]] 752 | name = "overload" 753 | version = "0.1.1" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 756 | 757 | [[package]] 758 | name = "parking_lot_core" 759 | version = "0.9.9" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 762 | dependencies = [ 763 | "cfg-if", 764 | "libc", 765 | "redox_syscall", 766 | "smallvec", 767 | "windows-targets", 768 | ] 769 | 770 | [[package]] 771 | name = "pin-project-lite" 772 | version = "0.2.13" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 775 | 776 | [[package]] 777 | name = "pin-utils" 778 | version = "0.1.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 781 | 782 | [[package]] 783 | name = "pkg-config" 784 | version = "0.3.27" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 787 | 788 | [[package]] 789 | name = "postcard" 790 | version = "1.0.4" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "cfa512cd0d087cc9f99ad30a1bf64795b67871edbead083ffc3a4dfafa59aa00" 793 | dependencies = [ 794 | "cobs", 795 | "serde", 796 | ] 797 | 798 | [[package]] 799 | name = "proc-macro2" 800 | version = "1.0.69" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 803 | dependencies = [ 804 | "unicode-ident", 805 | ] 806 | 807 | [[package]] 808 | name = "process_path" 809 | version = "0.1.4" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "f676f11eb0b3e2ea0fbaee218fa6b806689e2297b8c8adc5bf73df465c4f6171" 812 | dependencies = [ 813 | "libc", 814 | "winapi", 815 | ] 816 | 817 | [[package]] 818 | name = "quote" 819 | version = "1.0.33" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 822 | dependencies = [ 823 | "proc-macro2", 824 | ] 825 | 826 | [[package]] 827 | name = "real_c_string" 828 | version = "1.0.1" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "ae244cf1357665c9cf83bb4a8f35340c8a7e70eae29961c1758031bb8471af13" 831 | dependencies = [ 832 | "quote", 833 | "syn 2.0.39", 834 | ] 835 | 836 | [[package]] 837 | name = "redox_syscall" 838 | version = "0.4.1" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 841 | dependencies = [ 842 | "bitflags 1.3.2", 843 | ] 844 | 845 | [[package]] 846 | name = "rustc-demangle" 847 | version = "0.1.23" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 850 | 851 | [[package]] 852 | name = "ryu" 853 | version = "1.0.15" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 856 | 857 | [[package]] 858 | name = "same-file" 859 | version = "1.0.6" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 862 | dependencies = [ 863 | "winapi-util", 864 | ] 865 | 866 | [[package]] 867 | name = "scopeguard" 868 | version = "1.1.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 871 | 872 | [[package]] 873 | name = "serde" 874 | version = "1.0.192" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" 877 | dependencies = [ 878 | "serde_derive", 879 | ] 880 | 881 | [[package]] 882 | name = "serde-xml-rs" 883 | version = "0.6.0" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" 886 | dependencies = [ 887 | "log", 888 | "serde", 889 | "thiserror", 890 | "xml-rs", 891 | ] 892 | 893 | [[package]] 894 | name = "serde_derive" 895 | version = "1.0.192" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" 898 | dependencies = [ 899 | "proc-macro2", 900 | "quote", 901 | "syn 2.0.39", 902 | ] 903 | 904 | [[package]] 905 | name = "serde_json" 906 | version = "1.0.108" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 909 | dependencies = [ 910 | "itoa", 911 | "ryu", 912 | "serde", 913 | ] 914 | 915 | [[package]] 916 | name = "sharded-slab" 917 | version = "0.1.7" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 920 | dependencies = [ 921 | "lazy_static", 922 | ] 923 | 924 | [[package]] 925 | name = "slab" 926 | version = "0.4.9" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 929 | dependencies = [ 930 | "autocfg", 931 | ] 932 | 933 | [[package]] 934 | name = "smallvec" 935 | version = "1.11.1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 938 | 939 | [[package]] 940 | name = "socket2" 941 | version = "0.5.5" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 944 | dependencies = [ 945 | "libc", 946 | "windows-sys", 947 | ] 948 | 949 | [[package]] 950 | name = "static_assertions" 951 | version = "1.1.0" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 954 | 955 | [[package]] 956 | name = "syn" 957 | version = "1.0.109" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 960 | dependencies = [ 961 | "proc-macro2", 962 | "quote", 963 | "unicode-ident", 964 | ] 965 | 966 | [[package]] 967 | name = "syn" 968 | version = "2.0.39" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 971 | dependencies = [ 972 | "proc-macro2", 973 | "quote", 974 | "unicode-ident", 975 | ] 976 | 977 | [[package]] 978 | name = "thiserror" 979 | version = "1.0.50" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 982 | dependencies = [ 983 | "thiserror-impl", 984 | ] 985 | 986 | [[package]] 987 | name = "thiserror-impl" 988 | version = "1.0.50" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 991 | dependencies = [ 992 | "proc-macro2", 993 | "quote", 994 | "syn 2.0.39", 995 | ] 996 | 997 | [[package]] 998 | name = "thread_local" 999 | version = "1.1.7" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1002 | dependencies = [ 1003 | "cfg-if", 1004 | "once_cell", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "tokio" 1009 | version = "1.34.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" 1012 | dependencies = [ 1013 | "backtrace", 1014 | "libc", 1015 | "mio", 1016 | "num_cpus", 1017 | "pin-project-lite", 1018 | "socket2", 1019 | "tokio-macros", 1020 | "windows-sys", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "tokio-macros" 1025 | version = "2.2.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1028 | dependencies = [ 1029 | "proc-macro2", 1030 | "quote", 1031 | "syn 2.0.39", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "tokio-stream" 1036 | version = "0.1.14" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 1039 | dependencies = [ 1040 | "futures-core", 1041 | "pin-project-lite", 1042 | "tokio", 1043 | "tokio-util", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "tokio-util" 1048 | version = "0.7.3" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 1051 | dependencies = [ 1052 | "bytes", 1053 | "futures-core", 1054 | "futures-sink", 1055 | "pin-project-lite", 1056 | "tokio", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "tracing" 1061 | version = "0.1.40" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1064 | dependencies = [ 1065 | "pin-project-lite", 1066 | "tracing-attributes", 1067 | "tracing-core", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "tracing-attributes" 1072 | version = "0.1.27" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1075 | dependencies = [ 1076 | "proc-macro2", 1077 | "quote", 1078 | "syn 2.0.39", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "tracing-core" 1083 | version = "0.1.32" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1086 | dependencies = [ 1087 | "once_cell", 1088 | "valuable", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "tracing-log" 1093 | version = "0.1.4" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" 1096 | dependencies = [ 1097 | "log", 1098 | "once_cell", 1099 | "tracing-core", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "tracing-subscriber" 1104 | version = "0.3.17" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 1107 | dependencies = [ 1108 | "nu-ansi-term", 1109 | "sharded-slab", 1110 | "smallvec", 1111 | "thread_local", 1112 | "tracing-core", 1113 | "tracing-log", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "unicode-ident" 1118 | version = "1.0.12" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1121 | 1122 | [[package]] 1123 | name = "uuid" 1124 | version = "1.5.0" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" 1127 | 1128 | [[package]] 1129 | name = "valuable" 1130 | version = "0.1.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1133 | 1134 | [[package]] 1135 | name = "valve-pm" 1136 | version = "0.1.0" 1137 | dependencies = [ 1138 | "btleplug", 1139 | "ctrlc", 1140 | "futures-util", 1141 | "once_cell", 1142 | "thiserror", 1143 | "tokio", 1144 | "tracing", 1145 | "tracing-subscriber", 1146 | "uuid", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "vive-hid" 1151 | version = "0.1.0" 1152 | dependencies = [ 1153 | "flate2", 1154 | "hidapi", 1155 | "once_cell", 1156 | "serde", 1157 | "serde_json", 1158 | "thiserror", 1159 | "tracing", 1160 | "tracing-subscriber", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "walkdir" 1165 | version = "2.3.2" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1168 | dependencies = [ 1169 | "same-file", 1170 | "winapi", 1171 | "winapi-util", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "wasi" 1176 | version = "0.11.0+wasi-snapshot-preview1" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1179 | 1180 | [[package]] 1181 | name = "winapi" 1182 | version = "0.3.9" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1185 | dependencies = [ 1186 | "winapi-i686-pc-windows-gnu", 1187 | "winapi-x86_64-pc-windows-gnu", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "winapi-i686-pc-windows-gnu" 1192 | version = "0.4.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1195 | 1196 | [[package]] 1197 | name = "winapi-util" 1198 | version = "0.1.5" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1201 | dependencies = [ 1202 | "winapi", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "winapi-x86_64-pc-windows-gnu" 1207 | version = "0.4.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1210 | 1211 | [[package]] 1212 | name = "windows" 1213 | version = "0.51.1" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" 1216 | dependencies = [ 1217 | "windows-core", 1218 | "windows-targets", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "windows-core" 1223 | version = "0.51.1" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 1226 | dependencies = [ 1227 | "windows-targets", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "windows-sys" 1232 | version = "0.48.0" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1235 | dependencies = [ 1236 | "windows-targets", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "windows-targets" 1241 | version = "0.48.5" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1244 | dependencies = [ 1245 | "windows_aarch64_gnullvm", 1246 | "windows_aarch64_msvc", 1247 | "windows_i686_gnu", 1248 | "windows_i686_msvc", 1249 | "windows_x86_64_gnu", 1250 | "windows_x86_64_gnullvm", 1251 | "windows_x86_64_msvc", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "windows_aarch64_gnullvm" 1256 | version = "0.48.5" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1259 | 1260 | [[package]] 1261 | name = "windows_aarch64_msvc" 1262 | version = "0.48.5" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1265 | 1266 | [[package]] 1267 | name = "windows_i686_gnu" 1268 | version = "0.48.5" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1271 | 1272 | [[package]] 1273 | name = "windows_i686_msvc" 1274 | version = "0.48.5" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1277 | 1278 | [[package]] 1279 | name = "windows_x86_64_gnu" 1280 | version = "0.48.5" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1283 | 1284 | [[package]] 1285 | name = "windows_x86_64_gnullvm" 1286 | version = "0.48.5" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1289 | 1290 | [[package]] 1291 | name = "windows_x86_64_msvc" 1292 | version = "0.48.5" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1295 | 1296 | [[package]] 1297 | name = "xml-rs" 1298 | version = "0.8.4" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 1301 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*", "bin/*"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vive Pro 2 linux driver 2 | /driver_lighthouse.so 3 | ## Why 4 | 5 | Because HTC doesn't care about non-windows users, even if OG Vive worked just fine for them 6 | 7 | ## How 8 | 9 | Current implementation of driver intercepts some calls between SteamVR and common headset driver `driver_lighthouse`, which is used for OG Vive, and makes it work with newer Vive Pro 2 10 | 11 | ## Progress 12 | 13 | ### Working 14 | 15 | - HMD image - standard interface (DP) used, however there are some things missing in kernel, see patches below 16 | - Audio output - standard interface used 17 | - Front facing camera - works, with minor noise/distortion, standard interface (UVC) used 18 | - Lighthouse power management 19 | - Headset, controllers, Vive tracker (tracking) - part of original driver 20 | - Headset/controllers firmware updates - part of original driver 21 | 22 | ### TODO 23 | 24 | - Configuration utilities - most of reconfiguration (resolution, noise cancelation, brightness, lighthouse power management) abilities are already reverse-engineered, they just aren't easly configurable, some GUI utility should be written 25 | - Focus knob overlay (Some third-party may work though). Focusing does work, but there is no visual helper. 26 | - Audio output is not targeted to correct device yet (You need to manually switch it every time), it should be possible to implement this feature in this driver however 27 | - Front facing camera noise - can be solved with some kernel tinkering (UVC driver) 28 | - Front facing camera distortion - distortion matrix should be submitted to SteamVR 29 | - Standalone driver/integration with OpenHMD/Monado - most complex parts (tracking) is already implemented in open source, only thing needed - is to port vive pro 2 specific features/tools 30 | - Vive Wireless Adapter - support may be implemented, i have some ideas about how it should work, however i dont have one for testing, see donate section below, first received donations will be spent on one 31 | 32 | ### Will not work 33 | 34 | - Vive Console software (GUI utilities etc) - this driver own utilities should be used instead 35 | 36 | 37 | ## Installation 38 | 39 | This driver can be built using [nix package manager](https://nixos.org/download.html) (it works on any distribution), build is fully reproducible, all needed dependencies will be downloaded automatically: 40 | 41 | ```sh 42 | # Note: the #driver-proxy-release part of this command is not a comment, it's just github syntax highlighter is wrong 43 | nix build --extra-experimental-features nix-command --extra-experimental-features flakes .#driver-proxy-release 44 | ``` 45 | 46 | ...or using manual building instructions from here (i dont provide any guarantees about contents of this repo) https://github.com/santeri3700/vive-pro-2-on-linux#install-vive-pro-2-linux-driver-by-certainlach 47 | 48 | ...or be downloaded from patreon page, see donate section below 49 | 50 | And then installed via 51 | 52 | ```sh 53 | ./install.sh 54 | ``` 55 | 56 | Latest version of driver [automatically patches](https://github.com/CertainLach/VivePro2-Linux-Driver/commit/70687011f80d58c78ee77868895def9d77adf262) SteamVR, so VIVE Console no longer required to be installed 57 | 58 | ## Configuration 59 | 60 | In `steamvr.vrsettings`: 61 | 62 | `vivepro2.resolution`: `0-5`, 0 by default, to make it most compatible with every hardware 63 | 64 | Reconfigures helmet to specified resolution/framerate before startup 65 | 66 | - 0 - 2448x1224 90fps 67 | - 1 - 2448x1224 120fps 68 | - 2 - 3264x1632 90fps 69 | - 3 - 3680x1836 90fps 70 | - 4 - 4896x2448 90fps 71 | - 5 - 4896x2448 120fps 72 | 73 | Similar to original vive console utility 74 | 75 | `vivepro2.brightness`: `1-130`, 130 by default 76 | 77 | Display brightness 78 | 79 | Original vive console seems to fade from 0 to 130 on start, and then no longer touch this setting 80 | 81 | `vivepro2.noiseCancel`: `true/false`, disabled by default 82 | 83 | Toggle built-in microphone noise canceling 84 | 85 | Similar option exists in vive console 86 | 87 | ## Required kernel patches 88 | 89 | - Mark HMD as non-display (Otherwise SteamVR will not be able to occupy display) https://lore.kernel.org/linux-kernel/20220118170037.14584-1-iam@lach.pw/ - should be fixed in kernel 5.18 with another patch 90 | 91 | - Support type VII timings in DisplayID (VIVE reports mode in them) https://lore.kernel.org/linux-kernel/20220118215956.17229-1-iam@lach.pw/ - merged to kernel in 5.18 92 | 93 | - Support fixed DSC BPP rate https://lore.kernel.org/linux-kernel/20220220151940.58327-1-iam@lach.pw/, https://lore.kernel.org/linux-kernel/20220220151940.58327-2-iam@lach.pw/ - not yet merged, currently planning to propose it to 5.19, without this patch highest resolution modes will not work 94 | 95 | All of those patches are kept up-to-date in this repo, in `kernel-patches` subdirectory 96 | 97 | I recommend you to use kernel 5.17+, as there is other issues in older kernels 98 | 99 | If you use NixOS, then you can use kernelPatches from this flake: 100 | ```nix 101 | boot.kernelPatches = vivepro2-linux-driver.kernelPatches; 102 | ``` 103 | 104 | If you use arch btw, then you can use this kernel package with all required patches applied (i have not tested it, and can't provide any guarantees about contents of this repo): https://github.com/santeri3700/vive-pro-2-on-linux 105 | 106 | I don't recommend using other distributions, because it will be harder, because of usage of bleeding-edge kernel, but it should work, and I will fix any issues with them (I.e I have fixed usage of this driver on ubuntu) 107 | 108 | ## Donate 109 | 110 | I dont have enough motivation making this thing work for everyone/adding features everyone wants/needs 111 | 112 | You can, however, help me to develop this motivation, here: https://patreon.com/0lach 113 | 114 | # Thanks 115 | 116 | https://github.com/ChristophHaag for initial OpenVR guidance, which helped me to fix some issues 117 | 118 | https://github.com/santeri3700 for writing driver/kernel build instructions here: https://github.com/santeri3700/vive-pro-2-on-linux (i don't provide any guarantees about contents of this repo) 119 | 120 | Testers, backers, everyone else 121 | -------------------------------------------------------------------------------- /bin/driver-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "driver-proxy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libloading = "0.8.1" 8 | once_cell = "1.18.0" 9 | process_path = "0.1.4" 10 | thiserror = "1.0.50" 11 | tracing = "0.1.40" 12 | tracing-subscriber = "0.3.17" 13 | 14 | cppvtbl = "0.2.1" 15 | real_c_string = "1.0.1" 16 | 17 | valve-pm = { path = "../../crates/valve-pm" } 18 | vive-hid = { path = "../../crates/vive-hid" } 19 | lens-client = { path = "../../crates/lens-client" } 20 | lens-protocol = { path = "../../crates/lens-protocol" } 21 | openvr = { path = "../../crates/openvr" } 22 | tokio = { version = "1.34.0", features = ["rt", "rt-multi-thread"] } 23 | 24 | [lib] 25 | crate-type = ["cdylib"] 26 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/driver/camera.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | use cppvtbl::VtableRef; 4 | use openvr::{ 5 | ECameraCompatibilityMode, ECameraVideoStreamFormat, EVRDistortionFunctionType, 6 | EVRTrackedCameraFrameType, HmdMatrix44_t, HmdVector2_t, ICameraVideoSinkCallbackVtable, 7 | IVRCameraComponent, IVRCameraComponentVtable, 8 | }; 9 | use vive_hid::{ConfigCamera, DistortType}; 10 | 11 | struct HmdCamera { 12 | real: &'static VtableRef, 13 | cameras: Vec, 14 | } 15 | 16 | impl IVRCameraComponent for HmdCamera { 17 | fn GetCameraFrameDimensions( 18 | &self, 19 | nVideoStreamFormat: ECameraVideoStreamFormat, 20 | pWidth: *mut u32, 21 | pHeight: *mut u32, 22 | ) -> bool { 23 | self.real 24 | .GetCameraFrameDimensions(nVideoStreamFormat, pWidth, pHeight) 25 | } 26 | 27 | fn GetCameraFrameBufferingRequirements( 28 | &self, 29 | pDefaultFrameQueueSize: *mut i32, 30 | pFrameBufferDataSize: *mut u32, 31 | ) -> bool { 32 | self.real 33 | .GetCameraFrameBufferingRequirements(pDefaultFrameQueueSize, pFrameBufferDataSize) 34 | } 35 | 36 | fn SetCameraFrameBuffering( 37 | &self, 38 | nFrameBufferCount: i32, 39 | ppFrameBuffers: *mut *mut c_void, 40 | nFrameBufferDataSize: u32, 41 | ) -> bool { 42 | self.real 43 | .SetCameraFrameBuffering(nFrameBufferCount, ppFrameBuffers, nFrameBufferDataSize) 44 | } 45 | 46 | fn SetCameraVideoStreamFormat(&self, nVideoStreamFormat: ECameraVideoStreamFormat) -> bool { 47 | self.real.SetCameraVideoStreamFormat(nVideoStreamFormat) 48 | } 49 | 50 | fn GetCameraVideoStreamFormat(&self) -> ECameraVideoStreamFormat { 51 | self.real.GetCameraVideoStreamFormat() 52 | } 53 | 54 | fn StartVideoStream(&self) -> bool { 55 | self.real.StartVideoStream() 56 | } 57 | 58 | fn StopVideoStream(&self) { 59 | self.real.StopVideoStream() 60 | } 61 | 62 | fn IsVideoStreamActive(&self, pbPaused: *mut bool, pflElapsedTime: *mut f32) -> bool { 63 | self.real.IsVideoStreamActive(pbPaused, pflElapsedTime) 64 | } 65 | 66 | fn GetVideoStreamFrame(&self) -> *const openvr::CameraVideoStreamFrame_t { 67 | self.real.GetVideoStreamFrame() 68 | } 69 | 70 | fn ReleaseVideoStreamFrame(&self, pFrameImage: *const openvr::CameraVideoStreamFrame_t) { 71 | self.real.ReleaseVideoStreamFrame(pFrameImage) 72 | } 73 | 74 | fn SetAutoExposure(&self, bEnable: bool) -> bool { 75 | self.real.SetAutoExposure(bEnable) 76 | } 77 | 78 | fn PauseVideoStream(&self) -> bool { 79 | self.real.PauseVideoStream() 80 | } 81 | 82 | fn ResumeVideoStream(&self) -> bool { 83 | self.real.ResumeVideoStream() 84 | } 85 | 86 | fn GetCameraDistortion( 87 | &self, 88 | nCameraIndex: u32, 89 | flInputU: f32, 90 | flInputV: f32, 91 | pflOutputU: *mut f32, 92 | pflOutputV: *mut f32, 93 | ) -> bool { 94 | self.real 95 | .GetCameraDistortion(nCameraIndex, flInputU, flInputV, pflOutputU, pflOutputV) 96 | } 97 | 98 | fn GetCameraProjection( 99 | &self, 100 | nCameraIndex: u32, 101 | eFrameType: EVRTrackedCameraFrameType, 102 | flZNear: f32, 103 | flZFar: f32, 104 | pProjection: *mut HmdMatrix44_t, 105 | ) -> bool { 106 | self.real 107 | .GetCameraProjection(nCameraIndex, eFrameType, flZNear, flZFar, pProjection) 108 | } 109 | 110 | fn SetFrameRate(&self, nISPFrameRate: i32, nSensorFrameRate: i32) -> bool { 111 | self.real.SetFrameRate(nISPFrameRate, nSensorFrameRate) 112 | } 113 | 114 | fn SetCameraVideoSinkCallback( 115 | &self, 116 | pCameraVideoSinkCallback: *const VtableRef, 117 | ) -> bool { 118 | self.real 119 | .SetCameraVideoSinkCallback(pCameraVideoSinkCallback) 120 | } 121 | 122 | fn GetCameraCompatibilityMode( 123 | &self, 124 | pCameraCompatibilityMode: *mut ECameraCompatibilityMode, 125 | ) -> bool { 126 | self.real 127 | .GetCameraCompatibilityMode(pCameraCompatibilityMode) 128 | } 129 | 130 | fn SetCameraCompatibilityMode( 131 | &self, 132 | nCameraCompatibilityMode: ECameraCompatibilityMode, 133 | ) -> bool { 134 | self.real 135 | .SetCameraCompatibilityMode(nCameraCompatibilityMode) 136 | } 137 | 138 | fn GetCameraFrameBounds( 139 | &self, 140 | eFrameType: EVRTrackedCameraFrameType, 141 | pLeft: *mut u32, 142 | pTop: *mut u32, 143 | pWidth: *mut u32, 144 | pHeight: *mut u32, 145 | ) -> bool { 146 | self.real 147 | .GetCameraFrameBounds(eFrameType, pLeft, pTop, pWidth, pHeight) 148 | } 149 | 150 | fn GetCameraIntrinsics( 151 | &self, 152 | nCameraIndex: u32, 153 | eFrameType: EVRTrackedCameraFrameType, 154 | pFocalLength: *mut HmdVector2_t, 155 | pCenter: *mut HmdVector2_t, 156 | peDistortionType: *mut EVRDistortionFunctionType, 157 | rCoefficients: *mut f64, 158 | ) -> bool { 159 | let camera = if let Some(camera) = self.cameras.get(nCameraIndex as usize) { 160 | camera 161 | } else { 162 | tracing::warn!("unknown camera, fallback"); 163 | return self.real.GetCameraIntrinsics( 164 | nCameraIndex, 165 | eFrameType, 166 | pFocalLength, 167 | pCenter, 168 | peDistortionType, 169 | rCoefficients, 170 | ); 171 | }; 172 | unsafe { 173 | *pFocalLength = HmdVector2_t { 174 | v: [camera.intrinsics.focal_x, camera.intrinsics.focal_y], 175 | }; 176 | *pCenter = HmdVector2_t { 177 | v: [ 178 | camera.intrinsics.distort.center_x, 179 | camera.intrinsics.distort.center_y, 180 | ], 181 | }; 182 | *peDistortionType = match camera.intrinsics.distort.r#type { 183 | DistortType::DistortFtheta => { 184 | EVRDistortionFunctionType::VRDistortionFunctionType_FTheta 185 | } 186 | }; 187 | let slice = std::slice::from_raw_parts_mut( 188 | rCoefficients, 189 | camera.intrinsics.distort.coeffs.len(), 190 | ); 191 | slice.copy_from_slice(&camera.intrinsics.distort.coeffs); 192 | }; 193 | return true; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/driver/hmd.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CStr}, 3 | os::raw::c_char, 4 | rc::Rc, 5 | }; 6 | 7 | use crate::{ 8 | driver_context::{self, DRIVER_CONTEXT}, 9 | settings::{set_properties, Property, PropertyValue, PROPERTIES}, 10 | Result, 11 | }; 12 | use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables}; 13 | use lens_protocol::{Eye, LensClient}; 14 | use openvr::{ 15 | k_unFloatPropertyTag, EPropertyWriteType, ETrackedDeviceProperty, ETrackedPropertyError, 16 | HmdVector2_t, IVRProperties, PropertyWrite_t, 17 | }; 18 | use tracing::{error, info, instrument}; 19 | use vive_hid::{Mode, ViveConfig, ViveDevice}; 20 | 21 | use crate::openvr::{ 22 | DistortionCoordinates_t, DriverPose_t, EVREye, EVRInitError, ITrackedDeviceServerDriver, 23 | ITrackedDeviceServerDriverVtable, IVRDisplayComponent, IVRDisplayComponentVtable, 24 | IVRDisplayComponent_Version, 25 | }; 26 | 27 | fn map_eye(eye: EVREye) -> Eye { 28 | match eye { 29 | EVREye::Eye_Left => Eye::Left, 30 | EVREye::Eye_Right => Eye::Right, 31 | } 32 | } 33 | 34 | #[impl_vtables(IVRDisplayComponent)] 35 | struct HmdDisplay { 36 | // steam: Rc, 37 | // vive: Rc, 38 | lens: Rc, 39 | real: &'static VtableRef, 40 | mode: Mode, 41 | } 42 | 43 | impl IVRDisplayComponent for HmdDisplay { 44 | #[instrument(skip(self))] 45 | fn GetWindowBounds(&self, pnX: *mut i32, pnY: *mut i32, pnWidth: *mut u32, pnHeight: *mut u32) { 46 | let Mode { width, height, .. } = self.mode; 47 | unsafe { 48 | *pnX = 0; 49 | *pnY = 0; 50 | *pnWidth = width; 51 | *pnHeight = height; 52 | } 53 | } 54 | 55 | fn IsDisplayOnDesktop(&self) -> bool { 56 | self.real.IsDisplayOnDesktop() 57 | } 58 | 59 | fn IsDisplayRealDisplay(&self) -> bool { 60 | self.real.IsDisplayRealDisplay() 61 | } 62 | 63 | fn GetRecommendedRenderTargetSize(&self, pnWidth: *mut u32, pnHeight: *mut u32) { 64 | let Mode { width, height, .. } = self.mode; 65 | unsafe { 66 | *pnWidth = width / 2; 67 | *pnHeight = height; 68 | } 69 | } 70 | 71 | #[instrument(skip(self))] 72 | fn GetEyeOutputViewport( 73 | &self, 74 | eEye: EVREye, 75 | pnX: *mut u32, 76 | pnY: *mut u32, 77 | pnWidth: *mut u32, 78 | pnHeight: *mut u32, 79 | ) { 80 | // let err: Result<()> = try { 81 | let Mode { width, height, .. } = self.mode; 82 | unsafe { 83 | *pnX = if eEye == EVREye::Eye_Left { 84 | 0 85 | } else { 86 | width / 2 87 | }; 88 | *pnY = 0; 89 | *pnWidth = width / 2; 90 | *pnHeight = height; 91 | } 92 | // return; 93 | // }; 94 | // error!("failed: {}", err.err().unwrap()); 95 | // self.real 96 | // .GetEyeOutputViewport(eEye, pnX, pnY, pnWidth, pnHeight) 97 | } 98 | 99 | #[instrument(skip(self))] 100 | fn GetProjectionRaw( 101 | &self, 102 | eEye: EVREye, 103 | pfLeft: *mut f32, 104 | pfRight: *mut f32, 105 | pfTop: *mut f32, 106 | pfBottom: *mut f32, 107 | ) { 108 | let err: Result<()> = try { 109 | let result = self.lens.project(map_eye(eEye))?; 110 | unsafe { 111 | *pfLeft = result.left; 112 | *pfRight = result.right; 113 | if self.lens.matrix_needs_inversion()? { 114 | *pfTop = result.bottom; 115 | *pfBottom = result.top; 116 | } else { 117 | *pfTop = result.top; 118 | *pfBottom = result.bottom; 119 | } 120 | } 121 | return; 122 | }; 123 | error!("failed: {}", err.err().unwrap()); 124 | self.real 125 | .GetProjectionRaw(eEye, pfLeft, pfRight, pfTop, pfBottom) 126 | } 127 | 128 | #[instrument(skip(self))] 129 | fn ComputeDistortion(&self, eEye: EVREye, fU: f32, fV: f32) -> DistortionCoordinates_t { 130 | let err: Result<()> = try { 131 | let inverse = self.lens.matrix_needs_inversion()?; 132 | let result = self 133 | .lens 134 | .distort(map_eye(eEye), [fU, if inverse { 1.0 - fV } else { fV }])?; 135 | return DistortionCoordinates_t { 136 | rfRed: result.red, 137 | rfGreen: result.green, 138 | rfBlue: result.blue, 139 | }; 140 | }; 141 | error!("failed: {}", err.err().unwrap()); 142 | self.real.ComputeDistortion(eEye, fU, fV) 143 | } 144 | 145 | fn ComputeInverseDistortion( 146 | &self, 147 | _idk1: *mut HmdVector2_t, 148 | _eEye: EVREye, 149 | _fU: f32, 150 | _fV: f32, 151 | ) -> i32 { 152 | // Not entirely sure what should this function do, but original impl has only `xor eax eax; retn` inside, 153 | // so fine by me. Not delegating to real display, to prevent it from somehow breaking in the future. 154 | 0 155 | } 156 | } 157 | 158 | #[impl_vtables(ITrackedDeviceServerDriver)] 159 | pub struct HmdDriver { 160 | pub vive: Rc, 161 | pub vive_config: ViveConfig, 162 | pub lens: Rc, 163 | pub real: &'static VtableRef, 164 | pub mode: Mode, 165 | } 166 | 167 | impl ITrackedDeviceServerDriver for HmdDriver { 168 | fn Activate(&self, unObjectId: u32) -> EVRInitError { 169 | let res = self.real.Activate(unObjectId); 170 | if res != EVRInitError::VRInitError_None { 171 | return res; 172 | } 173 | let container = PROPERTIES.TrackedDeviceToPropertyContainer(unObjectId); 174 | 175 | set_properties( 176 | container, 177 | vec![ 178 | Property::new( 179 | ETrackedDeviceProperty::Prop_DisplayFrequency_Float, 180 | PropertyValue::Float(self.mode.frame_rate), 181 | ), 182 | Property::new( 183 | ETrackedDeviceProperty::Prop_DisplaySupportsMultipleFramerates_Bool, 184 | PropertyValue::Bool(true), 185 | ), 186 | Property::new( 187 | ETrackedDeviceProperty::Prop_SecondsFromVsyncToPhotons_Float, 188 | PropertyValue::Float( 189 | (1.0 / self.mode.frame_rate) + self.mode.extra_photon_vsync, 190 | ), 191 | ), 192 | // Property::new( 193 | // ETrackedDeviceProperty::Prop_MinimumIpdStepMeters_Float, 194 | // PropertyValue::Float(0.0005), 195 | // ), 196 | // Property::new( 197 | // ETrackedDeviceProperty::Prop_UserIpdMeters_Float, 198 | // // TODO 199 | // PropertyValue::Float(0.0005), 200 | // ), 201 | Property::new( 202 | ETrackedDeviceProperty::Prop_UserHeadToEyeDepthMeters_Float, 203 | PropertyValue::Float(0.015), 204 | ), 205 | Property::new( 206 | ETrackedDeviceProperty::Prop_DisplayAvailableFrameRates_Float_Array, 207 | PropertyValue::FloatArray(if self.mode.frame_rate == 90.0 { 208 | vec![90.0, 120.0] 209 | } else { 210 | vec![120.0, 90.0] 211 | }), 212 | ), 213 | Property::new( 214 | ETrackedDeviceProperty::Prop_DisplaySupportsRuntimeFramerateChange_Bool, 215 | PropertyValue::Bool(true), 216 | ), 217 | ], 218 | ); 219 | 220 | EVRInitError::VRInitError_None 221 | } 222 | 223 | fn Deactivate(&self) { 224 | self.real.Deactivate() 225 | } 226 | 227 | fn EnterStandby(&self) { 228 | self.real.EnterStandby() 229 | } 230 | 231 | fn GetComponent(&self, pchComponentNameAndVersion: *const c_char) -> *mut c_void { 232 | let name = unsafe { CStr::from_ptr(pchComponentNameAndVersion) }; 233 | info!("getting {name:?} hmd component"); 234 | let real = self.real.GetComponent(pchComponentNameAndVersion); 235 | if name == unsafe { CStr::from_ptr(IVRDisplayComponent_Version) } { 236 | info!("faking display"); 237 | let display = Box::leak(Box::new(WithVtables::new(HmdDisplay { 238 | // steam: self.steam.clone(), 239 | // vive: self.vive.clone(), 240 | lens: self.lens.clone(), 241 | real: unsafe { VtableRef::from_raw(real as *const _) }, 242 | mode: self.mode, 243 | }))); 244 | VtableRef::into_raw_mut(HasVtable::::get_mut(display)) 245 | as *mut _ 246 | } else { 247 | real 248 | } 249 | } 250 | 251 | fn DebugRequest( 252 | &self, 253 | pchRequest: *const c_char, 254 | pchResponseBuffer: *mut c_char, 255 | unResponseBufferSize: u32, 256 | ) { 257 | self.real 258 | .DebugRequest(pchRequest, pchResponseBuffer, unResponseBufferSize) 259 | } 260 | 261 | fn GetPose(&self) -> DriverPose_t { 262 | self.real.GetPose() 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/driver/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod camera; 2 | pub mod hmd; 3 | pub mod server_tracked_provider; 4 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/driver/server_tracked_provider.rs: -------------------------------------------------------------------------------- 1 | use std::{os::raw::c_char, sync::Mutex}; 2 | 3 | use crate::{ 4 | driver_context::{try_init_driver_context, DRIVER_CONTEXT}, 5 | factory::{get_hmd_driver_factory, TOKIO_RUNTIME}, 6 | log::try_init_driver_log, 7 | setting, 8 | settings::Setting, 9 | try_vr, 10 | }; 11 | use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables}; 12 | use once_cell::sync::Lazy; 13 | use openvr::{IVRDriverLogVtable, IVRDriverLog_Version}; 14 | use tokio::task::LocalSet; 15 | use tracing::info; 16 | use valve_pm::{start_manager, StationCommand, StationControl, StationState}; 17 | 18 | use crate::openvr::{ 19 | EVRInitError, IServerTrackedDeviceProvider, IServerTrackedDeviceProviderVtable, 20 | IServerTrackedDeviceProvider_Version, IVRDriverContextVtable, 21 | }; 22 | 23 | // (name ":" "BS2" ":" "0"/"1") ** "," 24 | const BASE_STATIONS: Setting = setting!("driver_lighthouse", "PowerManagedBaseStations2"); 25 | // 0 - disabled 26 | // 1 - sleep 27 | // 2 - standby 28 | const POWER_MANAGEMENT: Setting = setting!("vivepro2", "basestationPowerManagement"); 29 | 30 | #[impl_vtables(IServerTrackedDeviceProvider)] 31 | pub struct ServerTrackedProvider { 32 | real: &'static VtableRef, 33 | stations: Mutex>, 34 | standby_state: Mutex, 35 | } 36 | impl IServerTrackedDeviceProvider for ServerTrackedProvider { 37 | fn Init( 38 | &self, 39 | pDriverContext: *const cppvtbl::VtableRef, 40 | ) -> EVRInitError { 41 | try_init_driver_context(unsafe { &*pDriverContext }); 42 | let context = DRIVER_CONTEXT.get().expect("context just initialized"); 43 | let logger: *const cppvtbl::VtableRef = context 44 | .get_generic_interface(IVRDriverLog_Version) 45 | .expect("always able to initialize driver log") 46 | .cast(); 47 | try_init_driver_log(unsafe { &*logger }); 48 | 49 | let power_management = POWER_MANAGEMENT.get(); 50 | *self.standby_state.lock().expect("lock") = match power_management { 51 | 0 => StationState::Unknown, 52 | 2 => StationState::Standby, 53 | _ => StationState::Sleeping, 54 | }; 55 | 56 | 'stations: { 57 | if *self.standby_state.lock().expect("lock") != StationState::Unknown { 58 | let _runtime = TOKIO_RUNTIME.enter(); 59 | let stations = BASE_STATIONS.get(); 60 | 61 | let stations: Vec<_> = stations.split(",").filter(|s| !s.is_empty()).collect(); 62 | if stations.is_empty() { 63 | break 'stations; 64 | } 65 | let Ok(manager) = TOKIO_RUNTIME.block_on(start_manager()) else { 66 | break 'stations; 67 | }; 68 | let stations: Vec<_> = stations 69 | .iter() 70 | .filter_map(|line| { 71 | let mut parts = line.split(":"); 72 | let name = parts.next()?; 73 | let _bs2 = parts.next()?; 74 | let enabled = parts.next()?; 75 | 76 | if enabled == "1" { 77 | Some(name.to_owned()) 78 | } else { 79 | None 80 | } 81 | }) 82 | .map(|name| { 83 | StationControl::new(manager.clone(), name.to_owned(), StationState::On) 84 | }) 85 | .collect(); 86 | info!("enabled power management for {} stations", stations.len()); 87 | self.stations.lock().expect("lock").extend(stations); 88 | } 89 | }; 90 | 91 | self.real.Init( 92 | VtableRef::into_raw(HasVtable::::get(&context)) as *const _, 93 | ) 94 | } 95 | 96 | fn Cleanup(&self) { 97 | self.real.Cleanup(); 98 | info!("disconnecting from base stations"); 99 | let _runtime = TOKIO_RUNTIME.enter(); 100 | let localset = LocalSet::new(); 101 | for station in self.stations.lock().expect("lock").drain(..) { 102 | localset.spawn_local(station.finish()); 103 | } 104 | TOKIO_RUNTIME.block_on(localset); 105 | } 106 | 107 | fn GetInterfaceVersions(&self) -> *const *const c_char { 108 | self.real.GetInterfaceVersions() 109 | } 110 | 111 | fn RunFrame(&self) { 112 | self.real.RunFrame() 113 | } 114 | 115 | fn ShouldBlockStandbyMode(&self) -> bool { 116 | false 117 | } 118 | 119 | fn EnterStandby(&self) { 120 | self.real.EnterStandby(); 121 | info!("making station standby"); 122 | for station in self.stations.lock().expect("lock").iter_mut() { 123 | station.send(StationCommand::SetState( 124 | *self.standby_state.lock().expect("lock"), 125 | )) 126 | } 127 | } 128 | 129 | fn LeaveStandby(&self) { 130 | self.real.LeaveStandby(); 131 | info!("waking up base stations"); 132 | for station in self.stations.lock().expect("lock").iter_mut() { 133 | station.send(StationCommand::SetState(StationState::On)) 134 | } 135 | } 136 | } 137 | 138 | pub static SERVER_TRACKED_DEVICE_PROVIDER: Lazy> = 139 | Lazy::new(|| { 140 | info!("intializing server tracker provider"); 141 | let real = unsafe { 142 | let factory = get_hmd_driver_factory().expect("factory should exist"); 143 | try_vr!(factory(IServerTrackedDeviceProvider_Version)) 144 | .expect("failed to obtain tracked device provider from factory") 145 | }; 146 | 147 | WithVtables::new(ServerTrackedProvider { 148 | real: unsafe { VtableRef::from_raw(real as *const _) }, 149 | stations: Mutex::new(vec![]), 150 | standby_state: Mutex::new(StationState::Unknown), 151 | }) 152 | }); 153 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::result; 2 | 3 | use crate::openvr::EVRInitError; 4 | 5 | #[derive(thiserror::Error, Debug)] 6 | pub enum Error { 7 | #[error("openvr error: {0:?}")] 8 | VR(EVRInitError), 9 | #[error("lens error: {0}")] 10 | Lens(#[from] lens_protocol::Error), 11 | #[error("lens client error: {0}")] 12 | LensClient(#[from] lens_client::Error), 13 | #[error("libloading: {0}")] 14 | LibLoading(#[from] libloading::Error), 15 | #[error("hid error: {0}")] 16 | Hid(#[from] vive_hid::Error), 17 | #[error("internal error: {0}")] 18 | Internal(&'static str), 19 | } 20 | 21 | impl From for Error { 22 | fn from(e: EVRInitError) -> Self { 23 | Self::VR(e) 24 | } 25 | } 26 | 27 | pub type Result = result::Result; 28 | 29 | #[macro_export] 30 | macro_rules! try_vr { 31 | ($($call:ident).+ ($($arg:expr),*)) => {{ 32 | let mut error = crate::openvr::EVRInitError::VRInitError_None; 33 | let res = $($call).+($($arg,)* &mut error); 34 | 35 | if error != crate::openvr::EVRInitError::VRInitError_None { 36 | Err(crate::Error::VR(error)) 37 | } else { 38 | Ok(res) 39 | } 40 | }}; 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! vr_result { 45 | ($result:ident, $res:expr, $err:expr) => { 46 | #[allow(unreachable_patterns)] 47 | match $res { 48 | Ok(v) => { 49 | if !$result.is_null() { 50 | unsafe { *$result = crate::openvr::EVRInitError::VRInitError_None }; 51 | } 52 | v 53 | } 54 | Err(crate::Error::VR(vr)) => { 55 | if !$result.is_null() { 56 | unsafe { *$result = vr }; 57 | } 58 | $err 59 | } 60 | Err(_) => { 61 | if !$result.is_null() { 62 | unsafe { *$result = crate::openvr::EVRInitError::VRInitError_Unknown }; 63 | } 64 | $err 65 | } 66 | } 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/factory.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CStr}, 3 | os::raw::c_char, 4 | ptr::null, 5 | }; 6 | 7 | use crate::{ 8 | log::LogWriter, server_tracked_provider::SERVER_TRACKED_DEVICE_PROVIDER, Error, Result, 9 | }; 10 | use cppvtbl::{HasVtable, VtableRef}; 11 | use libloading::{Library, Symbol}; 12 | use once_cell::sync::{Lazy, OnceCell}; 13 | use tokio::runtime::Runtime; 14 | use tracing::info; 15 | 16 | use crate::openvr::{ 17 | EVRInitError, IServerTrackedDeviceProviderVtable, IServerTrackedDeviceProvider_Version, 18 | }; 19 | 20 | pub type HmdDriverFactory = 21 | unsafe extern "C" fn(*const c_char, result: *mut EVRInitError) -> *const c_void; 22 | static HMD_DRIVER_FACTORY: OnceCell> = OnceCell::new(); 23 | pub fn get_hmd_driver_factory() -> Result<&'static Symbol<'static, HmdDriverFactory>> { 24 | HMD_DRIVER_FACTORY.get_or_try_init(|| { 25 | let mut path = 26 | process_path::get_dylib_path().ok_or(Error::Internal("process path failed"))?; 27 | path.pop(); 28 | path.push("driver_lighthouse_real.so"); 29 | 30 | let library: &'static mut Library = 31 | Box::leak(Box::new(unsafe { libloading::Library::new(&path)? })); 32 | Ok(unsafe { library.get(b"HmdDriverFactory") }.expect("can't find HmdDriverFactory")) 33 | }) 34 | } 35 | 36 | pub static TOKIO_RUNTIME: Lazy = 37 | Lazy::new(|| Runtime::new().expect("tokio init should not fail")); 38 | 39 | fn HmdDriverFactory_impl(iface: *const c_char) -> Result<*const c_void> { 40 | // May be already installed 41 | if tracing_subscriber::fmt() 42 | .without_time() 43 | .with_writer(LogWriter::default) 44 | .try_init() 45 | .is_ok() 46 | { 47 | // This magic string is also used for installation detection! 48 | info!("https://patreon.com/0lach"); 49 | } 50 | 51 | let ifacen = unsafe { CStr::from_ptr(iface) }; 52 | info!("requested interface: {ifacen:?}"); 53 | 54 | if ifacen == unsafe { CStr::from_ptr(IServerTrackedDeviceProvider_Version) } { 55 | Ok( 56 | VtableRef::into_raw(HasVtable::::get( 57 | &SERVER_TRACKED_DEVICE_PROVIDER, 58 | )) as *const _ as *const c_void, 59 | ) 60 | } else { 61 | let factory = get_hmd_driver_factory()?; 62 | unsafe { try_vr!(factory(iface)) } 63 | } 64 | } 65 | 66 | #[no_mangle] 67 | pub extern "C" fn HmdDriverFactory( 68 | iface: *const c_char, 69 | result: *mut EVRInitError, 70 | ) -> *const c_void { 71 | eprintln!("factory call"); 72 | vr_result!(result, HmdDriverFactory_impl(iface), null()) 73 | } 74 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type, try_blocks, thread_local)] 2 | #![allow(non_snake_case)] 3 | 4 | #[macro_use] 5 | extern crate openvr; 6 | 7 | /// Wrappers for things, returned from original driver 8 | mod driver; 9 | pub use driver::{camera, hmd, server_tracked_provider}; 10 | 11 | /// Wrappers for things, passed from vrserver to original driver 12 | mod server; 13 | pub use server::{driver_context, driver_host}; 14 | 15 | #[macro_use] 16 | mod error; 17 | mod factory; 18 | #[macro_use] 19 | mod settings; 20 | mod log; 21 | 22 | pub use error::{Error, Result}; 23 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/log.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, io::Write}; 2 | 3 | use cppvtbl::VtableRef; 4 | use once_cell::sync::OnceCell; 5 | 6 | use crate::openvr::{IVRDriverLog, IVRDriverLogVtable}; 7 | 8 | static DRIVER_LOG: OnceCell<&'static VtableRef> = OnceCell::new(); 9 | 10 | #[derive(Default)] 11 | pub struct LogWriter(Vec); 12 | impl LogWriter { 13 | fn flush_line(&mut self) { 14 | if let Some(driver) = DRIVER_LOG.get() { 15 | self.0.retain(|&x| x != 0); 16 | let v = CString::new(self.0.as_slice()).unwrap(); 17 | driver.Log(v.as_ptr()); 18 | } else { 19 | eprintln!("{}", String::from_utf8_lossy(self.0.as_slice())) 20 | } 21 | self.0.clear(); 22 | } 23 | } 24 | impl Write for LogWriter { 25 | fn write(&mut self, mut buf: &[u8]) -> std::io::Result { 26 | while let Some(pos) = buf.iter().position(|v| *v == b'\n') { 27 | self.0.extend_from_slice(&buf[..pos]); 28 | self.flush_line(); 29 | buf = &buf[pos + 1..]; 30 | } 31 | self.0.extend_from_slice(buf); 32 | Ok(buf.len()) 33 | } 34 | 35 | fn flush(&mut self) -> std::io::Result<()> { 36 | self.flush_line(); 37 | Ok(()) 38 | } 39 | } 40 | 41 | pub fn try_init_driver_log(log: &'static VtableRef) { 42 | let _ = DRIVER_LOG.set(log); 43 | } 44 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/server/driver_context.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CStr}, 3 | os::raw::c_char, 4 | }; 5 | 6 | use crate::{driver_host::DRIVER_HOST, openvr::IVRServerDriverHostVtable, try_vr, Result}; 7 | use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables}; 8 | use once_cell::sync::OnceCell; 9 | use tracing::info; 10 | 11 | use crate::openvr::{ 12 | DriverHandle_t, EVRInitError, IVRDriverContext, IVRDriverContextVtable, 13 | IVRServerDriverHost_Version, 14 | }; 15 | 16 | #[impl_vtables(IVRDriverContext)] 17 | pub struct DriverContext { 18 | pub real: &'static VtableRef, 19 | } 20 | impl DriverContext { 21 | pub fn get_generic_interface(&self, name: *const c_char) -> Result<*mut c_void> { 22 | try_vr!(self.real.GetGenericInterface(name)) 23 | } 24 | } 25 | 26 | impl IVRDriverContext for DriverContext { 27 | fn GetGenericInterface( 28 | &self, 29 | pchInterfaceVersion: *const c_char, 30 | result: *mut EVRInitError, 31 | ) -> *mut c_void { 32 | let name = unsafe { CStr::from_ptr(pchInterfaceVersion) }; 33 | info!("get generic interface {name:?}"); 34 | if name == unsafe { CStr::from_ptr(IVRServerDriverHost_Version) } { 35 | info!("hooked!"); 36 | VtableRef::into_raw(HasVtable::::get(&*DRIVER_HOST)) 37 | as *mut _ 38 | } else { 39 | self.real.GetGenericInterface(pchInterfaceVersion, result) 40 | } 41 | } 42 | 43 | fn GetDriverHandle(&self) -> DriverHandle_t { 44 | self.real.GetDriverHandle() 45 | } 46 | } 47 | 48 | pub static DRIVER_CONTEXT: OnceCell> = OnceCell::new(); 49 | 50 | pub fn try_init_driver_context(real: &'static VtableRef) { 51 | if DRIVER_CONTEXT.get().is_some() { 52 | return; 53 | } 54 | let new_ctx = WithVtables::new(DriverContext { real }); 55 | DRIVER_CONTEXT 56 | .set(new_ctx) 57 | .map_err(|_| ()) 58 | .expect("context is not set"); 59 | } 60 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/server/driver_host.rs: -------------------------------------------------------------------------------- 1 | use cppvtbl::{impl_vtables, HasVtable, VtableRef, WithVtables}; 2 | use lens_client::start_lens_server; 3 | use lens_protocol::{LensClient, StubClient}; 4 | use once_cell::sync::Lazy; 5 | use std::env::var_os; 6 | use std::ffi::{CStr, OsString}; 7 | use std::os::raw::c_char; 8 | use std::process::Command; 9 | use std::rc::Rc; 10 | use tracing::{error, info, warn}; 11 | use vive_hid::{SteamDevice, ViveDevice}; 12 | 13 | use crate::driver_context::DRIVER_CONTEXT; 14 | use crate::hmd::HmdDriver; 15 | use crate::openvr::{ 16 | Compositor_FrameTiming, DriverPose_t, ETrackedDeviceClass, EVREventType, HmdMatrix34_t, 17 | HmdRect2_t, ITrackedDeviceServerDriverVtable, IVRServerDriverHost_Version, TrackedDevicePose_t, 18 | VREvent_t, 19 | }; 20 | use crate::openvr::{IVRServerDriverHost, IVRServerDriverHostVtable, VREvent_Data_t}; 21 | use crate::settings::Setting; 22 | use crate::{setting, Result}; 23 | 24 | #[impl_vtables(IVRServerDriverHost)] 25 | pub struct DriverHost { 26 | real: &'static VtableRef, 27 | } 28 | 29 | const HMD_RESOLUTION: Setting = setting!("vivepro2", "resolution"); 30 | const BRIGHTNESS: Setting = setting!("vivepro2", "brightness"); 31 | const NOISE_CANCEL: Setting = setting!("vivepro2", "noiseCancel"); 32 | 33 | impl IVRServerDriverHost for DriverHost { 34 | fn TrackedDeviceAdded( 35 | &self, 36 | pchDeviceSerialNumber: *const c_char, 37 | eDeviceClass: ETrackedDeviceClass, 38 | pDriver: *const VtableRef, 39 | ) -> bool { 40 | let sn = unsafe { CStr::from_ptr(pchDeviceSerialNumber) } 41 | .to_string_lossy() 42 | .to_string(); 43 | info!("added tracked device: {sn:?} ({eDeviceClass:?})"); 44 | if eDeviceClass == ETrackedDeviceClass::TrackedDeviceClass_HMD { 45 | let err: Result<()> = try { 46 | // Steam part is opened for checking if this is really a needed HMD device 47 | let _steam = Rc::new(SteamDevice::open(&sn)?); 48 | // We don't know for sure this device serial 49 | let vive = Rc::new(ViveDevice::open_first()?); 50 | 51 | let mode = { 52 | let res = HMD_RESOLUTION.get(); 53 | let modes = vive.query_modes(); 54 | let mode = *modes.iter().find(|m| m.id == res as u8).unwrap_or( 55 | modes 56 | .first() 57 | .expect("device has at least one mode if opened"), 58 | ); 59 | HMD_RESOLUTION.set(mode.id as i32); 60 | 61 | vive.set_mode(mode.id)?; 62 | mode 63 | }; 64 | { 65 | let nc = NOISE_CANCEL.get(); 66 | NOISE_CANCEL.set(nc); 67 | 68 | vive.toggle_noise_canceling(nc)?; 69 | } 70 | { 71 | let mut brightness = BRIGHTNESS.get(); 72 | if brightness == 0 { 73 | brightness = 130; 74 | } 75 | BRIGHTNESS.set(brightness); 76 | 77 | vive.set_brightness(brightness as u8)?; 78 | } 79 | 80 | let vive_config = vive.read_config()?; 81 | 82 | let lens = start_lens_server(vive_config.inhouse_lens_correction.clone()) 83 | .map(|v| Rc::new(v) as Rc) 84 | .unwrap_or_else(|e| { 85 | let zenity = var_os("STEAM_ZENITY").unwrap_or_else(|| OsString::from("zenity")); 86 | let mut cmd = Command::new(zenity); 87 | cmd.arg("--no-wrap").arg("--error").arg("--text").arg(format!("Lens distortion helper is failed to launch, HMD image most probaly will be distorted and unusable.\nError: {e}\n\nMake sure you have any recent version of proton installed.")); 88 | match cmd.spawn().and_then(|p| p.wait_with_output()) { 89 | Ok(v) => { 90 | info!("zenity finished: {}\n{:?}\n{:?}", v.status, v.stdout, v.stderr) 91 | }, 92 | Err(e) => { 93 | warn!("fatal error remains unnoticed: {e}") 94 | }, 95 | } 96 | error!("lens server start failed: {e}"); 97 | Rc::new(StubClient) 98 | }); 99 | let real = unsafe { VtableRef::from_raw(pDriver) }; 100 | 101 | let hmd = Box::leak(Box::new(WithVtables::new(HmdDriver { 102 | // steam, 103 | vive, 104 | vive_config, 105 | lens, 106 | real, 107 | mode, 108 | }))); 109 | 110 | return self.real.TrackedDeviceAdded( 111 | pchDeviceSerialNumber, 112 | eDeviceClass, 113 | HasVtable::::get(hmd), 114 | ); 115 | }; 116 | error!("failed to wrap hmd: {}", err.err().unwrap()); 117 | } 118 | self.real 119 | .TrackedDeviceAdded(pchDeviceSerialNumber, eDeviceClass, pDriver) 120 | } 121 | 122 | fn TrackedDevicePoseUpdated( 123 | &self, 124 | unWhichDevice: u32, 125 | newPose: *const DriverPose_t, 126 | unPoseStructSize: u32, 127 | ) { 128 | self.real 129 | .TrackedDevicePoseUpdated(unWhichDevice, newPose, unPoseStructSize) 130 | } 131 | 132 | fn VsyncEvent(&self, vsyncTimeOffsetSeconds: f64) { 133 | self.real.VsyncEvent(vsyncTimeOffsetSeconds) 134 | } 135 | 136 | fn VendorSpecificEvent( 137 | &self, 138 | unWhichDevice: u32, 139 | eventType: EVREventType, 140 | eventData: *const VREvent_Data_t, 141 | eventTimeOffset: f64, 142 | ) { 143 | self.real 144 | .VendorSpecificEvent(unWhichDevice, eventType, eventData, eventTimeOffset) 145 | } 146 | 147 | fn IsExiting(&self) -> bool { 148 | self.real.IsExiting() 149 | } 150 | 151 | fn PollNextEvent(&self, pEvent: *mut VREvent_t, uncbVREvent: u32) -> bool { 152 | self.real.PollNextEvent(pEvent, uncbVREvent) 153 | } 154 | 155 | fn GetRawTrackedDevicePoses( 156 | &self, 157 | fPredictedSecondsFromNow: f32, 158 | pTrackedDevicePoseArray: *mut TrackedDevicePose_t, 159 | unTrackedDevicePoseArrayCount: u32, 160 | ) { 161 | self.real.GetRawTrackedDevicePoses( 162 | fPredictedSecondsFromNow, 163 | pTrackedDevicePoseArray, 164 | unTrackedDevicePoseArrayCount, 165 | ) 166 | } 167 | 168 | fn RequestRestart( 169 | &self, 170 | pchLocalizedReason: *const c_char, 171 | pchExecutableToStart: *const c_char, 172 | pchArguments: *const c_char, 173 | pchWorkingDirectory: *const c_char, 174 | ) { 175 | self.real.RequestRestart( 176 | pchLocalizedReason, 177 | pchExecutableToStart, 178 | pchArguments, 179 | pchWorkingDirectory, 180 | ) 181 | } 182 | 183 | fn GetFrameTimings(&self, pTiming: *mut Compositor_FrameTiming, nFrames: u32) -> u32 { 184 | self.real.GetFrameTimings(pTiming, nFrames) 185 | } 186 | 187 | fn SetDisplayEyeToHead( 188 | &self, 189 | unWhichDevice: u32, 190 | eyeToHeadLeft: *const HmdMatrix34_t, 191 | eyeToHeadRight: *const HmdMatrix34_t, 192 | ) { 193 | self.real 194 | .SetDisplayEyeToHead(unWhichDevice, eyeToHeadLeft, eyeToHeadRight) 195 | } 196 | 197 | fn SetDisplayProjectionRaw( 198 | &self, 199 | unWhichDevice: u32, 200 | eyeLeft: *const HmdRect2_t, 201 | eyeRight: *const HmdRect2_t, 202 | ) { 203 | self.real 204 | .SetDisplayProjectionRaw(unWhichDevice, eyeLeft, eyeRight) 205 | } 206 | 207 | fn SetRecommendedRenderTargetSize(&self, unWhichDevice: u32, nWidth: u32, nHeight: u32) { 208 | self.real 209 | .SetRecommendedRenderTargetSize(unWhichDevice, nWidth, nHeight) 210 | } 211 | } 212 | 213 | pub static DRIVER_HOST: Lazy> = Lazy::new(|| { 214 | let context = DRIVER_CONTEXT 215 | .get() 216 | .expect("driver context should be initialized at this point"); 217 | let real = unsafe { 218 | &*(context 219 | .get_generic_interface(IVRServerDriverHost_Version) 220 | .expect("missing server driver host") as *const _ 221 | as *const VtableRef) 222 | }; 223 | WithVtables::new(DriverHost { real }) 224 | }); 225 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod driver_context; 2 | pub mod driver_host; 3 | -------------------------------------------------------------------------------- /bin/driver-proxy/src/settings.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CString}; 2 | use std::{marker::PhantomData, os::raw::c_char}; 3 | 4 | use cppvtbl::VtableRef; 5 | use once_cell::sync::Lazy; 6 | use openvr::{ 7 | k_unBoolPropertyTag, k_unFloatPropertyTag, EPropertyWriteType, ETrackedDeviceProperty, 8 | ETrackedPropertyError, IVRProperties, IVRPropertiesVtable, IVRProperties_Version, 9 | PropertyWrite_t, 10 | }; 11 | use tracing::{error, instrument}; 12 | 13 | use crate::driver_context::DRIVER_CONTEXT; 14 | use crate::openvr::{EVRSettingsError, IVRSettings, IVRSettingsVtable, IVRSettings_Version}; 15 | use crate::{Error, Result}; 16 | 17 | #[derive(Debug)] 18 | pub struct Setting(*const c_char, *const c_char, PhantomData); 19 | impl Setting { 20 | pub const fn unsafe_new(section: *const c_char, name: *const c_char) -> Self { 21 | Self(section, name, PhantomData) 22 | } 23 | } 24 | macro_rules! impl_setting { 25 | ($ty:ty, $get_meth:ident, $set_meth:ident, $default:expr) => { 26 | #[allow(dead_code)] 27 | impl Setting<$ty> { 28 | #[instrument] 29 | pub fn get(&self) -> $ty { 30 | let mut err = EVRSettingsError::VRSettingsError_None; 31 | let v = SETTINGS.$get_meth(self.0, self.1, &mut err); 32 | if err != EVRSettingsError::VRSettingsError_None { 33 | error!("failed: {:?}", err); 34 | return $default; 35 | } 36 | v 37 | } 38 | #[instrument] 39 | pub fn set(&self, value: $ty) { 40 | let mut err = EVRSettingsError::VRSettingsError_None; 41 | SETTINGS.$set_meth(self.0, self.1, value, &mut err); 42 | if err != EVRSettingsError::VRSettingsError_None { 43 | error!("failed: {:?}", err); 44 | } 45 | } 46 | } 47 | }; 48 | } 49 | impl_setting!(i32, GetInt32, SetInt32, 0); 50 | impl_setting!(bool, GetBool, SetBool, false); 51 | 52 | const STRING_SIZE: usize = 65535; 53 | impl Setting { 54 | #[instrument] 55 | pub fn get(&self) -> String { 56 | let err: Result<()> = try { 57 | let mut err = EVRSettingsError::VRSettingsError_None; 58 | let mut buf = vec![0u8; STRING_SIZE]; 59 | SETTINGS.GetString( 60 | self.0, 61 | self.1, 62 | buf.as_mut_ptr().cast(), 63 | STRING_SIZE as u32, 64 | &mut err, 65 | ); 66 | 67 | if err == EVRSettingsError::VRSettingsError_None { 68 | buf.truncate(buf.iter().position(|&c| c == 0).unwrap_or(buf.len())); 69 | 70 | return String::from_utf8(buf) 71 | .map_err(|_| Error::Internal("setting value is not utf-8"))?; 72 | }; 73 | Err(Error::Internal("failed to get string"))?; 74 | }; 75 | error!("failed: {}", err.err().unwrap()); 76 | "".to_owned() 77 | } 78 | #[instrument] 79 | pub fn set(&self, value: String) { 80 | let err: Result<()> = try { 81 | let cstring = 82 | CString::new(value).map_err(|_| Error::Internal("setting value contains \\0"))?; 83 | let mut err = EVRSettingsError::VRSettingsError_None; 84 | SETTINGS.SetString(self.0, self.1, cstring.as_ptr(), &mut err); 85 | return; 86 | }; 87 | error!("failed: {}", err.err().unwrap()); 88 | } 89 | } 90 | 91 | #[macro_export] 92 | macro_rules! setting { 93 | ($section:expr, $name:expr) => { 94 | crate::settings::Setting::unsafe_new( 95 | ::real_c_string::real_c_string!($section), 96 | ::real_c_string::real_c_string!($name), 97 | ) 98 | }; 99 | } 100 | 101 | pub static SETTINGS: Lazy<&'static VtableRef> = Lazy::new(|| { 102 | let ctx = DRIVER_CONTEXT 103 | .get() 104 | .expect("context should be initialized at this point"); 105 | let raw = ctx 106 | .get_generic_interface(IVRSettings_Version) 107 | .expect("there should be settings interface"); 108 | unsafe { VtableRef::from_raw(raw as *const VtableRef) } 109 | }); 110 | 111 | pub enum PropertyValue { 112 | Float(f32), 113 | FloatArray(Vec), 114 | Bool(bool), 115 | } 116 | impl PropertyValue { 117 | fn tag(&self) -> u32 { 118 | match self { 119 | Self::Float(_) | Self::FloatArray(_) => k_unFloatPropertyTag, 120 | Self::Bool(_) => k_unBoolPropertyTag, 121 | } 122 | } 123 | fn size(&self) -> u32 { 124 | match self { 125 | PropertyValue::Float(_) => 4, 126 | PropertyValue::FloatArray(v) => 4 * v.len() as u32, 127 | PropertyValue::Bool(_) => 1, 128 | } 129 | } 130 | fn buf(&mut self) -> *mut c_void { 131 | match self { 132 | PropertyValue::Float(f) => (f as *mut f32).cast(), 133 | PropertyValue::FloatArray(f) => f.as_mut_ptr().cast(), 134 | PropertyValue::Bool(v) => (v as *mut bool).cast(), 135 | } 136 | } 137 | } 138 | pub struct Property { 139 | name: ETrackedDeviceProperty, 140 | value: PropertyValue, 141 | } 142 | impl Property { 143 | pub fn new(name: ETrackedDeviceProperty, value: PropertyValue) -> Self { 144 | Self { name, value } 145 | } 146 | } 147 | pub fn set_properties(container: u64, mut props: Vec) { 148 | let mut batch = Vec::with_capacity(props.len()); 149 | for prop in props.iter_mut() { 150 | batch.push(PropertyWrite_t { 151 | writeType: EPropertyWriteType::PropertyWrite_Set, 152 | prop: prop.name, 153 | unTag: prop.value.tag(), 154 | unBufferSize: prop.value.size(), 155 | pvBuffer: prop.value.buf(), 156 | 157 | eError: ETrackedPropertyError::TrackedProp_Success, 158 | eSetError: ETrackedPropertyError::TrackedProp_Success, 159 | }); 160 | } 161 | PROPERTIES.WritePropertyBatch(container, batch.as_mut_ptr(), batch.len() as u32); 162 | } 163 | 164 | pub static PROPERTIES: Lazy<&'static VtableRef> = Lazy::new(|| { 165 | let ctx = DRIVER_CONTEXT 166 | .get() 167 | .expect("context should be initialized at this point"); 168 | let raw = ctx 169 | .get_generic_interface(IVRProperties_Version) 170 | .expect("there should be properties interface"); 171 | unsafe { VtableRef::from_raw(raw as *const VtableRef) } 172 | }); 173 | -------------------------------------------------------------------------------- /bin/lens-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lens-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | lens-protocol = { path = "../../crates/lens-protocol" } 10 | libloading = "0.8.1" 11 | process_path = "0.1.4" 12 | tracing-subscriber = "0.3.17" 13 | tracing = "0.1.40" 14 | anyhow = "1.0" 15 | serde_json = "1.0" 16 | -------------------------------------------------------------------------------- /bin/lens-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env::args, 3 | ffi::OsStr, 4 | process::exit, 5 | sync::atomic::{AtomicBool, Ordering}, 6 | }; 7 | 8 | use anyhow::{ensure, Context, Result}; 9 | use lens_protocol::{DistortOutput, Eye, LeftRightTopBottom, Request, Server}; 10 | use libloading::{Library, Symbol}; 11 | use serde_json::Value; 12 | use tracing::info; 13 | 14 | static CREATED: AtomicBool = AtomicBool::new(false); 15 | 16 | struct LensLibraryMethods { 17 | init: Symbol<'static, unsafe extern "C" fn() -> u32>, 18 | set_resolution: Symbol<'static, unsafe extern "C" fn(width: u32, height: u32) -> u32>, 19 | load_json_str: Symbol<'static, unsafe extern "C" fn(str: *const u8, len: usize) -> u32>, 20 | distort_uv: Symbol< 21 | 'static, 22 | unsafe extern "C" fn( 23 | eye: u32, 24 | color: u32, 25 | u: f32, 26 | v: f32, 27 | c1: &mut f32, 28 | c2: &mut f32, 29 | ) -> u32, 30 | >, 31 | grow_for_undistort: Symbol<'static, unsafe extern "C" fn(eye: u32, out: &mut [f32; 4]) -> u32>, 32 | intrinsic: Symbol<'static, unsafe extern "C" fn(eye: u32, out: &mut [f32; 8]) -> u32>, 33 | } 34 | impl LensLibraryMethods { 35 | fn init(&self) -> Result<()> { 36 | ensure!(unsafe { (self.init)() } == 0); 37 | Ok(()) 38 | } 39 | fn set_resolution(&self, width: u32, height: u32) -> Result<()> { 40 | ensure!(unsafe { (self.set_resolution)(width, height) } == 0); 41 | Ok(()) 42 | } 43 | fn load_json_str(&self, str: &str) -> Result<()> { 44 | ensure!(unsafe { (self.load_json_str)(str.as_ptr(), str.len()) } == 0); 45 | Ok(()) 46 | } 47 | 48 | fn grow_for_undistort(&self, eye: Eye) -> Result<[f32; 4]> { 49 | let mut out = [0.0; 4]; 50 | ensure!(unsafe { (self.grow_for_undistort)(eye as u32, &mut out) } == 0); 51 | Ok(out) 52 | } 53 | fn intrinsic(&self, eye: Eye) -> Result<[f32; 8]> { 54 | let mut out = [0.0; 8]; 55 | ensure!(unsafe { (self.intrinsic)(eye as u32, &mut out) } == 0); 56 | Ok(out) 57 | } 58 | fn distort_uv(&self, eye: Eye, color: u32, uv: [f32; 2]) -> Result<[f32; 2]> { 59 | let mut a = 0.0; 60 | let mut b = 0.0; 61 | ensure!(unsafe { (self.distort_uv)(eye as u32, color, uv[0], uv[1], &mut a, &mut b) } == 0); 62 | Ok([a, b]) 63 | } 64 | } 65 | struct LensLibrary { 66 | m: LensLibraryMethods, 67 | _marker: *const (), 68 | } 69 | impl LensLibrary { 70 | unsafe fn new(library: impl AsRef, resolution: (u32, u32)) -> Result { 71 | ensure!( 72 | !CREATED.swap(true, Ordering::Relaxed), 73 | "only single LensLibrary may exist per process" 74 | ); 75 | let lib = Box::leak(Box::new( 76 | #[cfg(windows)] 77 | Library::from( 78 | libloading::os::windows::Library::load_with_flags( 79 | library, 80 | libloading::os::windows::LOAD_WITH_ALTERED_SEARCH_PATH, 81 | ) 82 | .context("failed to load library")?, 83 | ), 84 | #[cfg(not(windows))] 85 | Library::new(library).context("failed to load library")?, 86 | )); 87 | let m = LensLibraryMethods { 88 | init: lib.get(b"init")?, 89 | set_resolution: lib.get(b"setResolution")?, 90 | load_json_str: lib.get(b"loadJsonStr")?, 91 | distort_uv: lib.get(b"distortUV")?, 92 | grow_for_undistort: lib.get(b"getGrowForUndistort")?, 93 | intrinsic: lib.get(b"getIntrinsic")?, 94 | }; 95 | m.init()?; 96 | m.set_resolution(resolution.0, resolution.1)?; 97 | Ok(Self { 98 | m, 99 | _marker: std::ptr::null(), 100 | }) 101 | } 102 | fn set_config(&self, config: Value) -> Result<()> { 103 | let config_str = serde_json::to_string(&config)?; 104 | self.m.load_json_str(&config_str)?; 105 | Ok(()) 106 | } 107 | fn distort(&self, eye: Eye, uv: [f32; 2]) -> Result { 108 | Ok(DistortOutput { 109 | red: self.m.distort_uv(eye, 2, uv)?, 110 | green: self.m.distort_uv(eye, 1, uv)?, 111 | blue: self.m.distort_uv(eye, 0, uv)?, 112 | }) 113 | } 114 | fn projection_raw(&self, eye: Eye) -> Result { 115 | let mut g = self.m.grow_for_undistort(eye)?; 116 | for v in g.iter_mut() { 117 | *v += 1.0; 118 | } 119 | let i = self.m.intrinsic(eye)?; 120 | Ok(LeftRightTopBottom { 121 | left: (-1.0 - i[2]) * g[0] / i[0], 122 | right: (1.0 - i[2]) * g[1] / i[0], 123 | top: (1.0 - i[4 + 1]) * g[2] / i[4], 124 | bottom: (-1.0 - i[4 + 1]) * g[3] / i[4], 125 | }) 126 | } 127 | } 128 | 129 | #[tracing::instrument(err)] 130 | fn main() -> Result<()> { 131 | if args().skip(1).next() == Some("check".to_owned()) { 132 | exit(42); 133 | } 134 | 135 | tracing_subscriber::fmt() 136 | .without_time() 137 | // stdout is occupied for protocol, and stderr is available 138 | .with_writer(std::io::stderr) 139 | .init(); 140 | info!("hello from lens server"); 141 | let mut path = process_path::get_executable_path().context("failed to find executable path")?; 142 | path.pop(); 143 | 144 | let mut dll_path = path.clone(); 145 | dll_path.push("LibLensDistortion.dll"); 146 | info!("dll path: {dll_path:?}"); 147 | 148 | let mut server = Server::listen(); 149 | 150 | let library = unsafe { LensLibrary::new(dll_path, (2448, 2448))? }; 151 | 152 | loop { 153 | let req = server.recv().context("failed to read request")?; 154 | match req { 155 | Request::Init(config) => { 156 | info!("set config"); 157 | library.set_config(config)?; 158 | } 159 | Request::Distort(eye, uv) => { 160 | server.send(&library.distort(eye, uv)?)?; 161 | } 162 | Request::ProjectionRaw(eye) => { 163 | server.send(&library.projection_raw(eye)?)?; 164 | } 165 | Request::Ping(v) => { 166 | server.send(&v)?; 167 | } 168 | Request::Exit => { 169 | info!("received exit signal"); 170 | break; 171 | } 172 | } 173 | } 174 | Ok(()) 175 | } 176 | -------------------------------------------------------------------------------- /crates/.gitignore: -------------------------------------------------------------------------------- 1 | vulkantest 2 | -------------------------------------------------------------------------------- /crates/lens-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lens-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | lens-protocol = { path = "../lens-protocol" } 10 | process_path = "0.1.4" 11 | thiserror = "1.0.50" 12 | tracing = "0.1.40" 13 | serde_json = "1.0.108" 14 | 15 | [dev-dependencies] 16 | tracing-subscriber = "0.3.17" 17 | -------------------------------------------------------------------------------- /crates/lens-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env::{self, vars_os}, 3 | ffi::{OsStr, OsString}, 4 | io, 5 | path::{Path, PathBuf}, 6 | process::{Command, ExitStatus, Stdio}, 7 | result, 8 | }; 9 | 10 | use lens_protocol::ServerClient; 11 | use serde_json::Value; 12 | use tracing::{info, warn}; 13 | 14 | #[derive(thiserror::Error, Debug)] 15 | pub enum Error { 16 | #[error("io error: {0}")] 17 | Io(#[from] io::Error), 18 | #[error("lens protocol error: {0}")] 19 | Protocol(#[from] lens_protocol::Error), 20 | #[error("cant find server, specify LENS_SERVER_EXE")] 21 | CantFindServer, 22 | #[error("expected server exe at {0}, but it is not found")] 23 | ServerExeNotFound(PathBuf), 24 | #[error("can't find wine in PATH")] 25 | WineNotFound, 26 | #[error("proton failed with code {0} while creating prefix")] 27 | CantCreateProtonPrefix(ExitStatus), 28 | } 29 | 30 | type Result = result::Result; 31 | 32 | // TODO: Pass config on startup 33 | pub fn start_lens_server(config: Value) -> Result { 34 | let server = find_server().ok_or(Error::CantFindServer)?; 35 | info!("using lens server at {server:?}"); 36 | 37 | if let Some(wine) = env::var_os("WINE") { 38 | let res = start_lens_server_with(wine, false, server, config)?; 39 | info!("server is working"); 40 | Ok(res) 41 | } else { 42 | 'proton: { 43 | if let Some(steamvr_path) = env::var_os("STEAM_COMPAT_INSTALL_PATH") { 44 | let mut path = PathBuf::from(steamvr_path); 45 | // Drop /SteamVR 46 | path.pop(); 47 | let mut name = env::var_os("PROTON_VERSION").map(|v| { 48 | let mut full = OsString::from("Proton "); 49 | full.push(v.as_os_str()); 50 | full 51 | }); 52 | if name.is_none() { 53 | let readdir = std::fs::read_dir(&path) 54 | .expect("parent exists, there should be no other reason to fail"); 55 | let mut candidates = Vec::new(); 56 | for item in readdir { 57 | let Ok(entry) = item else { 58 | continue; 59 | }; 60 | // Proton is ascii name 61 | let file_name = entry.file_name(); 62 | let Some(name) = file_name.to_str() else { 63 | continue; 64 | }; 65 | if !name.starts_with("Proton ") { 66 | continue; 67 | } 68 | candidates.push((name.to_owned(), file_name)) 69 | } 70 | // Try to find latest version by semverish comparison 71 | candidates.sort_unstable_by(|a, b| a.0.cmp(&b.0)); 72 | if let Some(version) = candidates.into_iter().last() { 73 | name = Some(version.1) 74 | } 75 | } 76 | if let Some(name) = name { 77 | path.push(name); 78 | path.push("proton"); 79 | info!("trying {} as proton", path.display()); 80 | 81 | if let Some(steamvr_compat_path) = env::var_os("STEAM_COMPAT_DATA_PATH") { 82 | let mut proton_prefix_path = PathBuf::from(steamvr_compat_path); 83 | proton_prefix_path.push("pfx"); 84 | 85 | ensure_proton_prefix_created(&path, &proton_prefix_path)? 86 | } else { 87 | warn!("missing proton prefix environment variable"); 88 | } 89 | 90 | let res = start_lens_server_with(path, true, &server, config.clone()); 91 | match res { 92 | Err(Error::Io(io)) if io.kind() == io::ErrorKind::NotFound => { 93 | // Only possible if wine not exists 94 | break 'proton; 95 | } 96 | _ => {} 97 | } 98 | let res = res?; 99 | return Ok(res); 100 | } else { 101 | warn!("failed to find proton") 102 | } 103 | // .unwrap_or_else(|| { 104 | // }); 105 | } 106 | } 107 | for wine in ["wine64", "wine"] { 108 | info!("trying {wine} as wine"); 109 | let res = start_lens_server_with(wine, false, &server, config.clone()); 110 | match res { 111 | Err(Error::Io(io)) if io.kind() == io::ErrorKind::NotFound => { 112 | // Only possible if wine not exists 113 | continue; 114 | } 115 | _ => {} 116 | } 117 | let res = res?; 118 | return Ok(res); 119 | } 120 | Err(Error::WineNotFound) 121 | } 122 | } 123 | 124 | pub fn start_lens_server_with( 125 | wine: impl AsRef, 126 | is_proton: bool, 127 | server_path: impl AsRef, 128 | config: Value, 129 | ) -> Result { 130 | let server_path = server_path.as_ref(); 131 | if !server_path.exists() { 132 | return Err(Error::ServerExeNotFound(server_path.to_owned())); 133 | } 134 | 135 | let mut child = Command::new(wine); 136 | if is_proton { 137 | child 138 | .arg("runinprefix") 139 | .arg("start.exe") 140 | .arg("/wait") 141 | .arg("/i") 142 | .arg("/b") 143 | .arg("/unix"); 144 | } 145 | child 146 | // fixme slows down responses 147 | .env("WINEDEBUG", "fixme-all") 148 | .arg(server_path) 149 | .stdin(Stdio::piped()) 150 | .stdout(Stdio::piped()) 151 | .stderr(Stdio::inherit()); 152 | let child = child.spawn()?; 153 | 154 | Ok(ServerClient::open(child, config)?) 155 | } 156 | 157 | pub fn find_server() -> Option { 158 | if let Some(path) = env::var_os("LENS_SERVER_EXE") { 159 | return Some(PathBuf::from(path)); 160 | } 161 | let mut path = process_path::get_dylib_path()?; 162 | path.pop(); 163 | path.push("lens-server"); 164 | path.push("lens-server.exe"); 165 | Some(path) 166 | } 167 | 168 | fn ensure_proton_prefix_created(proton_path: &Path, prefix_path: &Path) -> Result<()> { 169 | if !prefix_path.exists() { 170 | info!("initializing proton prefix directory"); 171 | 172 | if let Ok(status) = Command::new(proton_path).arg("run").spawn()?.wait() { 173 | if status.success() { 174 | info!("prefix created successfully") 175 | } else { 176 | return Err(Error::CantCreateProtonPrefix(status)); 177 | } 178 | }; 179 | } 180 | 181 | Ok(()) 182 | } 183 | -------------------------------------------------------------------------------- /crates/lens-protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lens-protocol" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | thiserror = "1.0" 12 | postcard = { version = "1.0", features = ["use-std"], default-features = false } 13 | -------------------------------------------------------------------------------- /crates/lens-protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | io::{self, Read, StdinLock, StdoutLock, Write}, 4 | process::{Child, ChildStdin, ChildStdout}, 5 | result, 6 | }; 7 | 8 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 9 | use serde_json::Value; 10 | 11 | #[derive(thiserror::Error, Debug)] 12 | pub enum Error { 13 | #[error("postcard error: {0}")] 14 | Postcard(#[from] postcard::Error), 15 | #[error("io error: {0}")] 16 | Io(#[from] io::Error), 17 | #[error("pipe failed")] 18 | MissingPipe, 19 | #[error("failed to ping server")] 20 | PingFailed, 21 | } 22 | type Result = result::Result; 23 | 24 | #[derive(Debug, Serialize, Deserialize)] 25 | pub struct DistortOutput { 26 | pub red: [f32; 2], 27 | pub green: [f32; 2], 28 | pub blue: [f32; 2], 29 | } 30 | 31 | #[derive(Debug, Serialize, Deserialize)] 32 | pub struct LeftRightTopBottom { 33 | pub left: f32, 34 | pub right: f32, 35 | pub top: f32, 36 | pub bottom: f32, 37 | } 38 | 39 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 40 | #[repr(u32)] 41 | pub enum Eye { 42 | Left = 0, 43 | Right = 1, 44 | } 45 | 46 | mod json { 47 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 48 | use serde_json::Value; 49 | 50 | pub fn serialize(value: &Value, serializer: S) -> Result 51 | where 52 | S: Serializer, 53 | { 54 | let str = serde_json::to_string(&value).unwrap(); 55 | str.serialize(serializer) 56 | } 57 | pub fn deserialize<'de, D>(deserializer: D) -> Result 58 | where 59 | D: Deserializer<'de>, 60 | { 61 | let str = String::deserialize(deserializer).unwrap(); 62 | Ok(serde_json::from_str(&str).unwrap()) 63 | } 64 | } 65 | 66 | #[derive(Debug, Serialize, Deserialize)] 67 | pub enum Request { 68 | Init(#[serde(with = "json")] Value), 69 | Ping(u32), 70 | Distort(Eye, [f32; 2]), 71 | ProjectionRaw(Eye), 72 | Exit, 73 | } 74 | 75 | pub trait LensClient { 76 | fn ping(&self, v: u32) -> Result; 77 | fn project(&self, eye: Eye) -> Result; 78 | fn matrix_needs_inversion(&self) -> Result; 79 | fn distort(&self, eye: Eye, uv: [f32; 2]) -> Result; 80 | fn set_config(&self, config: Value) -> Result<()>; 81 | fn exit(&self) -> Result<()>; 82 | } 83 | 84 | pub struct StubClient; 85 | impl LensClient for StubClient { 86 | fn ping(&self, v: u32) -> Result { 87 | Ok(v) 88 | } 89 | 90 | fn project(&self, eye: Eye) -> Result { 91 | Ok(match eye { 92 | Eye::Left => LeftRightTopBottom { 93 | left: -1.667393, 94 | right: 0.821432, 95 | top: -1.116938, 96 | bottom: 1.122846, 97 | }, 98 | Eye::Right => LeftRightTopBottom { 99 | left: -0.822435, 100 | right: 1.635135, 101 | top: -1.138235, 102 | bottom: 1.107449, 103 | }, 104 | }) 105 | } 106 | 107 | fn matrix_needs_inversion(&self) -> Result { 108 | Ok(true) 109 | } 110 | 111 | fn distort(&self, _eye: Eye, uv: [f32; 2]) -> Result { 112 | Ok(DistortOutput { 113 | red: uv, 114 | green: uv, 115 | blue: uv, 116 | }) 117 | } 118 | 119 | fn set_config(&self, _config: Value) -> Result<()> { 120 | Ok(()) 121 | } 122 | 123 | fn exit(&self) -> Result<()> { 124 | Ok(()) 125 | } 126 | } 127 | 128 | pub struct ServerClientInner { 129 | stdin: ChildStdin, 130 | stdout: ChildStdout, 131 | child: Child, 132 | } 133 | impl ServerClientInner { 134 | fn request(&mut self, request: &Request) -> Result { 135 | self.send(request)?; 136 | let data = read_message(&mut self.stdout)?; 137 | Ok(postcard::from_bytes(&data)?) 138 | } 139 | pub fn send(&mut self, request: &Request) -> Result<()> { 140 | let data = postcard::to_stdvec(&request)?; 141 | write_message(&mut self.stdin, &data)?; 142 | self.stdin.flush()?; 143 | Ok(()) 144 | } 145 | } 146 | pub struct ServerClient(RefCell); 147 | impl LensClient for ServerClient { 148 | fn ping(&self, v: u32) -> Result { 149 | self.0.borrow_mut().request(&Request::Ping(v)) 150 | } 151 | fn project(&self, eye: Eye) -> Result { 152 | self.0.borrow_mut().request(&Request::ProjectionRaw(eye)) 153 | } 154 | fn matrix_needs_inversion(&self) -> Result { 155 | let v = self.project(Eye::Left)?; 156 | Ok(v.top > v.bottom) 157 | } 158 | fn distort(&self, eye: Eye, uv: [f32; 2]) -> Result { 159 | self.0.borrow_mut().request(&Request::Distort(eye, uv)) 160 | } 161 | 162 | fn set_config(&self, config: Value) -> Result<()> { 163 | self.0.borrow_mut().send(&Request::Init(config))?; 164 | Ok(()) 165 | } 166 | 167 | fn exit(&self) -> Result<()> { 168 | // Flush may fail in case if exit succeeded 169 | let _ = self.0.borrow_mut().send(&Request::Exit); 170 | self.0.borrow_mut().child.wait().unwrap(); 171 | Ok(()) 172 | } 173 | } 174 | impl ServerClient { 175 | pub fn open(mut child: Child, config: Value) -> Result { 176 | let res = Self(RefCell::new(ServerClientInner { 177 | stdin: child.stdin.take().ok_or(Error::MissingPipe)?, 178 | stdout: child.stdout.take().ok_or(Error::MissingPipe)?, 179 | child, 180 | })); 181 | 182 | if res.ping(0x12345678)? != 0x12345678 { 183 | return Err(Error::MissingPipe); 184 | } 185 | res.set_config(config)?; 186 | 187 | Ok(res) 188 | } 189 | pub fn exit(&mut self) {} 190 | } 191 | impl Drop for ServerClient { 192 | fn drop(&mut self) { 193 | self.exit() 194 | } 195 | } 196 | 197 | #[cfg(target_os = "windows")] 198 | #[link(name = "msvcrt")] 199 | extern "C" { 200 | fn _setmode(fd: i32, mode: i32) -> i32; 201 | } 202 | 203 | pub fn read_message(read: &mut impl Read) -> Result> { 204 | let mut len_buf = [0; 4]; 205 | read.read_exact(&mut len_buf)?; 206 | let len = u32::from_be_bytes(len_buf); 207 | // This protocol isn't talkative, its ok to allocate here. 208 | let mut data = vec![0; len as usize]; 209 | read.read_exact(&mut data)?; 210 | Ok(data) 211 | } 212 | pub fn write_message(write: &mut impl Write, v: &[u8]) -> Result<()> { 213 | write.write_all(&u32::to_be_bytes(v.len() as u32))?; 214 | write.write_all(v)?; 215 | Ok(()) 216 | } 217 | 218 | pub struct Server { 219 | stdin: StdinLock<'static>, 220 | stdout: StdoutLock<'static>, 221 | #[cfg(target_os = "windows")] 222 | modes: (i32, i32), 223 | } 224 | impl Server { 225 | pub fn listen() -> Self { 226 | #[cfg(target_os = "windows")] 227 | let modes = { 228 | let stdout = unsafe { _setmode(0, 0x8000) }; 229 | let stdin = unsafe { _setmode(1, 0x8000) }; 230 | assert!( 231 | stdout != -1 && stdin != -1, 232 | "binary mode should be accepted, and fds are correct" 233 | ); 234 | (stdout, stdin) 235 | }; 236 | 237 | let stdin = io::stdin().lock(); 238 | let stdout = io::stdout().lock(); 239 | 240 | Self { 241 | stdin, 242 | stdout, 243 | #[cfg(target_os = "windows")] 244 | modes, 245 | } 246 | } 247 | pub fn recv(&mut self) -> Result { 248 | let data = read_message(&mut self.stdin)?; 249 | Ok(postcard::from_bytes(&data)?) 250 | } 251 | pub fn send(&mut self, v: &impl Serialize) -> Result<()> { 252 | let data = postcard::to_stdvec(&v)?; 253 | write_message(&mut self.stdout, &data)?; 254 | self.stdout.flush()?; 255 | Ok(()) 256 | } 257 | } 258 | impl Drop for Server { 259 | fn drop(&mut self) { 260 | #[cfg(target_os = "windows")] 261 | { 262 | let stdout = unsafe { _setmode(0, self.modes.0) }; 263 | let stdin = unsafe { _setmode(1, self.modes.1) }; 264 | assert!( 265 | stdout != -1 && stdin != -1, 266 | "previous mode and fds should be correct" 267 | ); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /crates/openvr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openvr" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cppvtbl = "0.2.1" 8 | real_c_string = "1.0.1" 9 | -------------------------------------------------------------------------------- /crates/openvr/src/.gitignore: -------------------------------------------------------------------------------- 1 | /a.json 2 | /openvr.json 3 | -------------------------------------------------------------------------------- /crates/openvr/src/a.jsonnet: -------------------------------------------------------------------------------- 1 | // Generate file similar to openvr.json 2 | // Initally i wrote api generator from it, but then realized driver api isn't defined in it, 3 | // so i wrote this .cpp => openvr.json converter 4 | 5 | local a = import './a.json'; 6 | 7 | local 8 | merge(a, b) = 9 | if a == null then b 10 | else if b == null then a 11 | else if std.type(a) == 'array' && std.type(b) == 'array' then a + b 12 | else if std.type(a) == 'object' && std.type(b) == 'object' then { 13 | [k]: merge(std.get(a, k), std.get(b, k)) for k in std.set(std.objectFields(a) + std.objectFields(b)) 14 | } 15 | else error "can't merge %v and %v" % [a, b]; 16 | 17 | local 18 | makeValue(v) = 19 | if v.kind == "ImplicitCastExpr" then makeValue(v.inner[0]) 20 | else if v.kind == "ConstantExpr" || v.kind == "IntegerLiteral" then v.value 21 | else if v.kind == "StringLiteral" then v.value[1:std.length(v.value) - 1] 22 | else if v.kind == "InitListExpr" then "{%s}" % std.join(", ", std.map(makeValue, v.inner)) 23 | else if v.kind == "DeclRefExpr" then "REF" 24 | else if v.kind == "CXXNullPtrLiteralExpr" then "NULL" 25 | else if v.kind == "UnaryOperator" && v.opcode == "-" then "-%s" % makeValue(v.inner[0]) 26 | else if v.kind == "BinaryOperator" && v.opcode == "*" then "%s * %s" % [makeValue(v.inner[0]), makeValue(v.inner[1])] 27 | else error "" + v, 28 | makeNamespace(a, ns = '') = 29 | local 30 | makeTypedef(def) = { 31 | typedef: def.name, 32 | type: def.type.qualType, 33 | }, 34 | makeParam(param) = { 35 | paramname: param.name, 36 | paramtype: param.type.qualType, 37 | }, 38 | makeMethod(method) = { 39 | methodname: method.name, 40 | returntype: std.stripChars(method.type.qualType[:std.findSubstr("(", method.type.qualType)[0]], " "), 41 | params: std.map(makeParam, std.filter(function(p) p.kind == "ParmVarDecl", std.get(method, "inner", []))), 42 | }, 43 | makeClass(class) = 44 | local methods = std.map(makeMethod, std.filter(function(v) v.kind == "CXXMethodDecl" && !std.get(v, "isImplicit", false), std.get(class, "inner", []))); 45 | std.map(function(m) m { 46 | classname: class.name, 47 | }, methods), 48 | makeField(field) = { 49 | fieldname: field.name, 50 | fieldtype: field.type.qualType, 51 | }, 52 | makeConst(const) = { 53 | constname: const.name, 54 | consttype: const.type.qualType, 55 | constval: makeValue(const.inner[0]), 56 | }, 57 | makeStruct(struct) = { 58 | struct: struct.name, 59 | fields: std.map(makeField, std.filter(function(f) f.kind == "FieldDecl", struct.inner)), 60 | }, 61 | makeEnumValue(value) = { 62 | name: value.name, 63 | value: if !("inner" in value) then null else makeValue(value.inner[0]), 64 | }, 65 | makeEnum(enum) = { 66 | enumname: enum.name, 67 | values: std.map(makeEnumValue, std.filter(function(f) f.kind == "EnumConstantDecl", enum.inner)) 68 | }, 69 | makeUnion(union) = { 70 | values: std.map(makeField, std.filter(function(v) v.kind == "FieldDecl", union.inner)) 71 | }; 72 | std.foldr(merge, std.map(makeNamespace, std.filter(function(c) c.kind == "NamespaceDecl", a.inner)), {}) + { 73 | typedefs+: std.map(makeTypedef, std.filter(function(c) c.kind == "TypedefDecl" && !std.get(c, "isImplicit", false) && !std.startsWith(std.get(c.loc, "file", ""), "/nix/") && !("includedFrom" in c.loc), a.inner)), 74 | methods+: std.join([], std.map(makeClass, std.filter(function(c) c.kind == "CXXRecordDecl" && c.tagUsed == "class", a.inner))), 75 | consts+: std.map(makeConst, std.filter(function(c) c.kind == "VarDecl", a.inner)), 76 | structs+: std.map(makeStruct, std.filter(function(c) "name" in c && c.kind == "CXXRecordDecl" && c.tagUsed == "struct" && "inner" in c, a.inner)), 77 | enums+: std.map(makeEnum, std.filter(function(c) c.kind == "EnumDecl", a.inner)), 78 | /// In openvr.json there is no definition for EventData_t 79 | unions+: std.map(makeUnion, std.filter(function(c) c.kind == "CXXRecordDecl" && c.tagUsed == "union", a.inner)), 80 | }; 81 | 82 | makeNamespace(a) 83 | -------------------------------------------------------------------------------- /crates/openvr/src/gen_mod_rs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate AST, fails, as it also tries to compile 4 | clang -Xclang -ast-dump=json -stdlib=libc++ a.hpp > a.json || true 5 | ~/build/jrsonnet/target/release/jrsonnet a.jsonnet > openvr.json 6 | ~/build/jrsonnet/target/release/jrsonnet mod_rs.jsonnet -S > lib.rs 7 | 8 | rustfmt lib.rs 9 | -------------------------------------------------------------------------------- /crates/openvr/src/mod_rs.jsonnet: -------------------------------------------------------------------------------- 1 | local api = import './openvr.json'; 2 | 3 | local 4 | groupBy(field, array) = 5 | local set = std.set(std.map(function(e) e[field], array)); 6 | { 7 | [key]: std.filter(function(e) e[field] == key, array) for key in set 8 | }; 9 | 10 | local 11 | blacklistConsts = {k_InterfaceVersions: null}, 12 | blacklistFields = { 13 | TrackedControllerRole_Max: null, 14 | k_EButton_SteamVR_Touchpad: null, k_EButton_SteamVR_Trigger: null, 15 | k_EButton_Dashboard_Back: null, 16 | k_EButton_IndexController_A: null, k_EButton_IndexController_B: null, k_EButton_IndexController_JoyStick: null, 17 | VRSkeletalTrackingLevel_Count: null, VRSkeletalTrackingLevel_Max: null, VRInputString_All: null, 18 | }, 19 | fixedTypes = { 20 | void: "c_void", 21 | uint64_t: "u64", 22 | uint32_t: "u32", 23 | uint16_t: "u16", 24 | uint8_t: "u8", 25 | int32_t: "i32", 26 | int: "i32", 27 | float: "f32", 28 | double: "f64", 29 | char: "c_char", 30 | "unsigned short": "u16", 31 | _Bool: "bool", 32 | 33 | 'vr::IOBufferHandle_t *': '*const c_void', 34 | 'ID3D12Resource *': '*const c_void', 35 | 'ID3D12CommandQueue *': '*const c_void', 36 | 37 | 'uint32_t (*)[2]': '*mut [u32; 2]', 38 | 'const vr::IVRDriverDirectModeComponent::SubmitLayerPerEye_t (&)[2]': '*const c_void', 39 | 40 | 'const vr::IVRDriverDirectModeComponent::SwapTextureSetDesc_t *': '*const c_void', 41 | 'vr::IVRDriverDirectModeComponent::SwapTextureSet_t *': '*mut c_void', 42 | }, 43 | unions = { 44 | 'VREvent_Data_t': 'Union0', 45 | 'VROverlayIntersectionMaskPrimitive_Data_t': 'Union1', 46 | }; 47 | 48 | local 49 | cleanupDefName(name_) = 50 | local name = std.stripChars(name_, " "); 51 | if std.startsWith(name, "vr::") then cleanupDefName(name) 52 | else name, 53 | fixTypeName_(typ_) = 54 | local typ = std.stripChars(typ_, " "); 55 | if std.get(fixedTypes, typ) != null then fixedTypes[typ] 56 | else if std.startsWith(typ, "vr::") then fixTypeName_(typ[4:]) 57 | else if std.startsWith(typ, "const ") && (std.endsWith(typ, "*") || std.endsWith(typ, "&")) then "*const %s" % fixTypeName_(typ[5:std.length(typ) - 1]) 58 | else if std.endsWith(typ, "*const") then "*const %s" % fixTypeName_(typ[:std.length(typ) - 6]) 59 | else if std.endsWith(typ, "*") then "*mut %s" % fixTypeName_(typ[:std.length(typ) - 1]) 60 | else if std.endsWith(typ, "&") then "*mut %s" % fixTypeName_(typ[:std.length(typ) - 1]) 61 | else if std.endsWith(typ, "]") then local poss = std.findSubstr("[", typ), pos = poss[std.length(poss)-1]; 62 | "[%s; %s]" % [ 63 | fixTypeName_(typ[:pos]), 64 | typ[pos+1:std.length(typ) - 1], 65 | ] 66 | else typ, 67 | fixTypeName(typ_) = 68 | local typ = fixTypeName_(typ_); 69 | assert std.type(typ) == "string" : "%s => %s" % [ typ_, typ ]; 70 | if std.startsWith(typ, "*mut I") && !std.startsWith(typ, "*mut Input") then "*const VtableRef<%sVtable>" % typ[5:] 71 | else typ, 72 | 73 | fixFieldName(field) = 74 | if field == "type" then "typ" else field; 75 | 76 | local 77 | makeTypeDef(typ) = 78 | local type = fixTypeName(typ.type); 79 | "pub type %s = %s;" % [ 80 | cleanupDefName(typ.typedef), 81 | if std.startsWith(type, "union ") then unions[type[6:]] 82 | else if type == "&()" then "*const c_void" 83 | else type, 84 | ], 85 | makeEnumValue(val) = 86 | if val.value != null then "\t%s = %s," % [val.name, val.value] 87 | else "\t%s," % val.name, 88 | makeEnumDef(enum) = 89 | ("#[derive(Clone, Copy, PartialEq, Eq, Debug)]\n#[repr(i32)]\npub enum %s {\n" % cleanupDefName(enum.enumname)) + 90 | std.join("\n", std.map(makeEnumValue, std.filter(function(value) std.get(blacklistFields, value.name, false) == false, enum.values))) + 91 | "\n}", 92 | makeConst(const) = 93 | assert const.consttype[0:6] == "const "; 94 | (if const.constname in blacklistConsts then "// " else "") + 95 | if const.consttype == "const char *const" || const.consttype == "const char *" then 96 | "pub const %s: *const c_char = real_c_string!(\"%s\");" % [const.constname, const.constval] 97 | else 98 | "pub const %s: %s = %s;" % [const.constname, fixTypeName(const.consttype[6:]), const.constval], 99 | makeStructField(field) = 100 | "\tpub %s: %s," % [fixFieldName(field.fieldname), fixTypeName(field.fieldtype)], 101 | makeStruct(struct) = 102 | (if struct.struct == "vr::(anonymous)" then "/*" else "") + 103 | ("#[derive(Clone, Copy)]\n#[repr(C)]\npub struct %s {\n" % cleanupDefName(struct.struct)) + 104 | std.join("\n", std.map(makeStructField, struct.fields)) + 105 | "\n}" + 106 | (if struct.struct == "vr::(anonymous)" then "*/" else ""), 107 | makeParam(param) = 108 | "%s: %s" % [fixFieldName(param.paramname), fixTypeName(param.paramtype)], 109 | makeMethod(method) = 110 | local params = std.get(method, "params", []); 111 | "\tfn %s(&self%s)%s;" % [ 112 | method.methodname, 113 | if std.length(params) == 0 then "" else ", " + std.join(", ", std.map(makeParam, method.params)), 114 | local ret = fixTypeName(method.returntype); if ret == "c_void" then "" else " -> %s" % ret, 115 | ], 116 | makeClass(name, methods) = 117 | ("#[vtable]\npub trait %s {\n" % cleanupDefName(name)) + 118 | std.join("\n", std.map(makeMethod, methods)) + 119 | "\n}", 120 | makeUnionValue(value) = 121 | "\t%s: %s," % [fixFieldName(value.fieldname), fixTypeName(value.fieldtype)], 122 | makeUnion(union) = 123 | ("#[derive(Clone, Copy)]\npub union %s {\n" % cleanupDefName(union.name)) + 124 | std.join("\n", std.map(makeUnionValue, union.values)) + 125 | "\n}"; 126 | 127 | ||| 128 | #![allow( 129 | non_camel_case_types, 130 | dead_code, 131 | non_snake_case, 132 | non_upper_case_globals, 133 | clippy::not_unsafe_ptr_arg_deref, 134 | )] 135 | 136 | use cppvtbl::{vtable, VtableRef}; 137 | use real_c_string::real_c_string; 138 | use std::ffi::c_void; 139 | use std::os::raw::c_char; 140 | 141 | // Graphic api stubs 142 | type VkDevice_T = (); 143 | type VkInstance_T = (); 144 | type VkQueue_T = (); 145 | type VkPhysicalDevice_T = (); 146 | 147 | ||| + 148 | std.join('\n', std.map(makeUnion, std.makeArray(std.length(api.unions), function(i) api.unions[i] {name: 'Union%d' % i}))) + '\n' + 149 | std.join('\n', std.map(makeTypeDef, api.typedefs)) + '\n' + 150 | std.join('\n', std.map(makeEnumDef, api.enums)) + '\n' + 151 | std.join('\n', std.map(makeConst, api.consts)) + '\n' + 152 | std.join('\n', std.map(makeStruct, api.structs)) + '\n' + 153 | std.join('\n', std.objectValues(std.mapWithKey(makeClass, groupBy('classname', std.filter(function(m) !std.startsWith(m.classname, "C"), api.methods))))) 154 | -------------------------------------------------------------------------------- /crates/valve-pm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "valve-pm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | btleplug = "0.11.1" 10 | uuid = "1.5.0" 11 | 12 | ctrlc = "3.4.1" 13 | once_cell = "1.18.0" 14 | thiserror = "1.0.50" 15 | tracing = "0.1.40" 16 | tokio = { version = "1.34.0", features = ["macros"] } 17 | futures-util = "0.3.29" 18 | 19 | [dev-dependencies] 20 | tracing-subscriber = "0.3.17" 21 | -------------------------------------------------------------------------------- /crates/valve-pm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(try_blocks)] 2 | 3 | use btleplug::platform::Adapter; 4 | use btleplug::{ 5 | api::{Central, Manager as _, Peripheral, ScanFilter, WriteType}, 6 | platform::Manager, 7 | }; 8 | use futures_util::StreamExt; 9 | use once_cell::sync::Lazy; 10 | use std::sync::Arc; 11 | use std::{result, str::FromStr, time::Duration}; 12 | use tokio::{select, sync::mpsc, time::sleep}; 13 | use tracing::{error, info, warn, Instrument}; 14 | use uuid::Uuid; 15 | 16 | #[derive(thiserror::Error, Debug)] 17 | pub enum Error { 18 | #[error("bluetooth: {0}")] 19 | Bt(#[from] btleplug::Error), 20 | #[error("no adapter found")] 21 | NoAdapterFound, 22 | #[error("characteristic not found")] 23 | CharacteristicNotFound, 24 | } 25 | 26 | type Result = result::Result; 27 | 28 | const MODE_CHARACTERISTIC_ID: Lazy = 29 | Lazy::new(|| Uuid::from_str("00001525-1212-efde-1523-785feabcd124").expect("uuid is valid")); 30 | 31 | #[allow(dead_code)] 32 | const HTC_MAC_PREFIX: [u8; 3] = [0x74, 0xf6, 0x1c]; 33 | 34 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 35 | #[repr(u8)] 36 | pub enum StationState { 37 | Sleeping = 0x00, 38 | On = 0x01, 39 | Standby = 0x02, 40 | Unknown = 0xff, 41 | } 42 | impl StationState { 43 | fn from_id(id: u8) -> Self { 44 | match id { 45 | 0x00 => Self::Sleeping, 46 | 0x01 | 0x08 | 0x09 | 0x0a | 0x0b => Self::On, 47 | 0x02 => Self::Standby, 48 | _ => { 49 | warn!("unknown status: 0x{id:02x}"); 50 | Self::Unknown 51 | } 52 | } 53 | } 54 | } 55 | 56 | pub enum StationCommand { 57 | SetState(StationState), 58 | } 59 | 60 | pub async fn start_manager() -> Result, ()> { 61 | let manager = match Manager::new().await { 62 | Ok(manager) => manager, 63 | Err(err) => { 64 | error!("failed to create station manager: {err}"); 65 | return Err(()); 66 | } 67 | }; 68 | let adapters = match manager.adapters().await { 69 | Ok(adapters) => adapters, 70 | Err(err) => { 71 | error!("failed to enumerate adapters: {err}"); 72 | return Err(()); 73 | } 74 | }; 75 | if adapters.is_empty() { 76 | error!("no available bluetooth adapters"); 77 | return Err(()); 78 | } 79 | 80 | let adapter = adapters 81 | .iter() 82 | // Prefer HTC adapter, which is already embedded in link box 83 | // .find(|a| { 84 | // let addr = if let Ok(addr) = a.name() { 85 | // addr 86 | // } else { 87 | // return false; 88 | // }; 89 | // !addr.address.starts_with(&HTC_MAC_PREFIX) 90 | // }) 91 | // TODO: Ability to select adapter? 92 | // .or_else(|| adapters.first()) 93 | .next() 94 | .expect("len >= 1") 95 | .clone(); 96 | 97 | info!( 98 | "using adapter: {}", 99 | adapter 100 | .adapter_info() 101 | .await 102 | .unwrap_or_else(|_| "".to_owned()) 103 | ); 104 | 105 | let _ = adapter.stop_scan().await; 106 | if let Err(e) = adapter.start_scan(ScanFilter::default()).await { 107 | warn!("failed to start scan: {e}"); 108 | } 109 | 110 | Ok(Arc::new((manager, adapter))) 111 | } 112 | 113 | pub struct StationControl { 114 | handle: Option>, 115 | ctx: Option>, 116 | } 117 | impl StationControl { 118 | #[tracing::instrument(name = "station_control")] 119 | pub fn new( 120 | man_adapter: Arc<(Manager, Adapter)>, 121 | station_sn: String, 122 | initial_state: StationState, 123 | ) -> Self { 124 | let (ctx, mut crx) = mpsc::unbounded_channel(); 125 | let handle = tokio::task::spawn( 126 | async move { 127 | 128 | 'rescan: loop { 129 | let adapter = &man_adapter.1; 130 | let res: Result<()> = try { 131 | let peripherals = adapter.peripherals().await?; 132 | 133 | for peripheral in peripherals { 134 | let props = match peripheral.properties().await { 135 | Ok(props) => props, 136 | Err(err) => { 137 | warn!("failed to get peripheral properties: {err}"); 138 | continue; 139 | } 140 | }; 141 | let name = props.and_then(|p| p.local_name); 142 | if name.as_ref() == Some(&station_sn) { 143 | info!("station found"); 144 | } else { 145 | continue; 146 | } 147 | if !peripheral.is_connected().await? { 148 | info!("connecting"); 149 | peripheral.connect().await?; 150 | } 151 | 152 | // Against base station flakiness 153 | tokio::time::sleep(Duration::from_millis(1500)).await; 154 | 155 | if peripheral.characteristics().is_empty() { 156 | info!("discovering characteristics"); 157 | peripheral.discover_services().await?; 158 | } 159 | 160 | let characteristics = peripheral 161 | .characteristics(); 162 | let characteristic = characteristics 163 | .iter() 164 | .find(|c| c.uuid == *MODE_CHARACTERISTIC_ID) 165 | .ok_or(Error::CharacteristicNotFound)?; 166 | 167 | if let Err(e) = peripheral.write(&characteristic, &[initial_state as u8], WriteType::WithoutResponse).await { 168 | error!("failed to set initial state: {e}"); 169 | } 170 | 171 | peripheral.subscribe(characteristic).await?; 172 | 173 | let mut notifications = peripheral.notifications().await?; 174 | info!("waiting for commands"); 175 | loop { 176 | select! { 177 | notification = notifications.next() => { 178 | let Some(notification) = notification else { 179 | warn!("device was disconnected"); 180 | continue 'rescan; 181 | }; 182 | if notification.uuid == *MODE_CHARACTERISTIC_ID { 183 | let state = if notification.value.len() == 1 { 184 | StationState::from_id(notification.value[0]) 185 | } else { 186 | StationState::Unknown 187 | }; 188 | info!("station state changed to {:?}", state); 189 | } 190 | } 191 | command = crx.recv() => { 192 | let Some(command) = command else { 193 | warn!("command stream finished, moving device to sleep"); 194 | if let Err(e) = peripheral.write(&characteristic, &[StationState::Sleeping as u8], WriteType::WithoutResponse).await { 195 | error!("failed to make device sleep: {e}"); 196 | } 197 | if let Err(e) = peripheral.disconnect().await { 198 | error!("failed to disconnect: {e}"); 199 | } 200 | break 'rescan; 201 | }; 202 | match command { 203 | StationCommand::SetState(state) => { 204 | info!("changing station state to {state:?}"); 205 | if let Err(e) = peripheral.write(&characteristic, &[state as u8], WriteType::WithoutResponse).await { 206 | error!("failed to write station state: {e}"); 207 | } 208 | }, 209 | } 210 | } 211 | } 212 | } 213 | } 214 | }; 215 | if let Err(e) = res { 216 | warn!("communication failed: {e}"); 217 | } 218 | sleep(Duration::from_secs(5)).await; 219 | } 220 | } 221 | .in_current_span(), 222 | ); 223 | Self { 224 | handle: Some(handle), 225 | ctx: Some(ctx), 226 | } 227 | } 228 | pub fn send(&mut self, command: StationCommand) { 229 | self.ctx 230 | .as_mut() 231 | .expect("connection is not finished") 232 | .send(command) 233 | .ok() 234 | .expect("task can't end earlier") 235 | } 236 | pub async fn finish(mut self) { 237 | drop(self.ctx.take().expect("finish can only be called once")); 238 | 239 | self.handle 240 | .take() 241 | .expect("finish can only be called once") 242 | .await 243 | .expect("task should not fail"); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /crates/vive-hid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vive-hid" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | hidapi = { version = "2.4.1", default-features = false, features = [ 10 | "linux-static-hidraw", 11 | ] } 12 | once_cell = "1.18.0" 13 | thiserror = "1.0.50" 14 | flate2 = "1.0.28" 15 | serde = { version = "1.0.192", features = ["derive"] } 16 | serde_json = "1.0.108" 17 | tracing = "0.1.40" 18 | tracing-subscriber = "0.3.17" 19 | -------------------------------------------------------------------------------- /crates/vive-hid/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Read, result}; 2 | 3 | use flate2::read::ZlibDecoder; 4 | use hidapi::{HidApi, HidDevice, HidError}; 5 | use once_cell::sync::OnceCell; 6 | use serde::Deserialize; 7 | use serde_json::Value; 8 | use tracing::error; 9 | 10 | #[derive(thiserror::Error, Debug)] 11 | pub enum Error { 12 | #[error("hid error: {0}")] 13 | Hid(#[from] HidError), 14 | #[error("device not found")] 15 | DeviceNotFound, 16 | #[error("device is not a vive device")] 17 | NotAVive, 18 | #[error("config size mismatch")] 19 | ConfigSizeMismatch, 20 | #[error("failed to read config")] 21 | ConfigReadFailed, 22 | #[error("protocol error: {0}")] 23 | ProtocolError(&'static str), 24 | } 25 | 26 | type Result = result::Result; 27 | 28 | static HIDAPI: OnceCell = OnceCell::new(); 29 | pub fn get_hidapi() -> Result<&'static HidApi> { 30 | HIDAPI.get_or_try_init(HidApi::new).map_err(From::from) 31 | } 32 | 33 | const STEAM_VID: u16 = 0x28de; 34 | const STEAM_PID: u16 = 0x2300; 35 | 36 | #[derive(Deserialize, Debug)] 37 | pub struct ConfigDevice { 38 | pub eye_target_height_in_pixels: u32, 39 | pub eye_target_width_in_pixels: u32, 40 | } 41 | #[derive(Deserialize, Debug)] 42 | pub enum DistortType { 43 | #[serde(rename = "DISTORT_FTHETA")] 44 | DistortFtheta, 45 | } 46 | #[derive(Deserialize, Debug)] 47 | pub struct IntrinsicsDistort { 48 | pub center_x: f32, 49 | pub center_y: f32, 50 | pub coeffs: Vec, 51 | pub r#type: DistortType, 52 | } 53 | #[derive(Deserialize, Debug)] 54 | pub struct ConfigCameraIntrinsics { 55 | pub center_x: f32, 56 | pub center_y: f32, 57 | pub distort: IntrinsicsDistort, 58 | pub focal_x: f32, 59 | pub focal_y: f32, 60 | pub width: u32, 61 | pub height: u32, 62 | } 63 | #[derive(Deserialize, Debug)] 64 | pub struct ConfigCamera { 65 | pub name: String, 66 | pub intrinsics: ConfigCameraIntrinsics, 67 | pub extrinsics: Vec, 68 | } 69 | #[derive(Deserialize, Debug)] 70 | pub struct SteamConfig { 71 | pub device: ConfigDevice, 72 | pub tracked_cameras: Vec, 73 | pub direct_mode_edid_pid: u32, 74 | pub direct_mode_edid_vid: u32, 75 | pub seconds_from_photons_to_vblank: f64, 76 | pub seconds_from_vsync_to_photons: f64, 77 | /// SN of ViveDevice 78 | pub mb_serial_number: String, 79 | } 80 | 81 | pub struct SteamDevice(HidDevice); 82 | impl SteamDevice { 83 | pub fn open_first() -> Result { 84 | let api = get_hidapi()?; 85 | let device = api.open(STEAM_VID, STEAM_PID)?; 86 | Ok(Self(device)) 87 | } 88 | pub fn open(sn: &str) -> Result { 89 | let api = get_hidapi()?; 90 | let device = api 91 | .device_list() 92 | .find(|dev| dev.serial_number() == Some(sn)) 93 | .ok_or(Error::DeviceNotFound)?; 94 | if device.vendor_id() != STEAM_VID || device.product_id() != STEAM_PID { 95 | return Err(Error::NotAVive); 96 | } 97 | let open = api.open_serial(device.vendor_id(), device.product_id(), sn)?; 98 | Ok(Self(open)) 99 | } 100 | pub fn read_config(&self) -> Result { 101 | let mut report = [0u8; 64]; 102 | report[0] = 16; 103 | let mut read_retries = 0; 104 | while self.0.get_feature_report(&mut report).is_err() { 105 | if read_retries > 5 { 106 | return Err(Error::ConfigReadFailed); 107 | } 108 | read_retries += 1; 109 | } 110 | read_retries = 0; 111 | let mut out = Vec::new(); 112 | loop { 113 | report[0] = 17; 114 | if self.0.get_feature_report(&mut report).is_err() { 115 | if read_retries > 5 { 116 | return Err(Error::ConfigReadFailed); 117 | } 118 | read_retries += 1; 119 | continue; 120 | } 121 | read_retries = 0; 122 | if report[1] == 0 { 123 | break; 124 | } 125 | out.extend_from_slice(&report[2..2 + report[1] as usize]) 126 | } 127 | let mut dec = ZlibDecoder::new(out.as_slice()); 128 | let mut out = String::new(); 129 | dec.read_to_string(&mut out) 130 | .map_err(|_| Error::ConfigReadFailed)?; 131 | 132 | serde_json::from_str(&out).map_err(|_| Error::ConfigReadFailed) 133 | } 134 | } 135 | 136 | const VIVE_VID: u16 = 0x0bb4; 137 | const VIVE_PID: u16 = 0x0342; 138 | 139 | #[derive(Deserialize, Debug)] 140 | pub struct ViveConfig { 141 | pub device: ConfigDevice, 142 | pub direct_mode_edid_pid: u32, 143 | pub direct_mode_edid_vid: u32, 144 | pub seconds_from_photons_to_vblank: f64, 145 | pub seconds_from_vsync_to_photons: f64, 146 | /// Lets threat it as something opaque, anyway we directly feed this to lens-client 147 | pub inhouse_lens_correction: Value, 148 | } 149 | 150 | #[derive(Clone, Copy)] 151 | pub struct Mode { 152 | pub id: u8, 153 | 154 | pub width: u32, 155 | pub height: u32, 156 | pub frame_rate: f32, 157 | pub extra_photon_vsync: f32, 158 | } 159 | impl Mode { 160 | const fn new( 161 | id: u8, 162 | width: u32, 163 | height: u32, 164 | frame_rate: f32, 165 | extra_photon_vsync: f32, 166 | ) -> Self { 167 | Self { 168 | id, 169 | width, 170 | height, 171 | frame_rate, 172 | extra_photon_vsync, 173 | } 174 | } 175 | } 176 | 177 | const VIVE_PRO_2_MODES: [Mode; 6] = [ 178 | Mode::new(0, 2448, 1224, 90.0, 0.0), 179 | Mode::new(1, 2448, 1224, 120.0, 0.0), 180 | Mode::new(2, 3264, 1632, 90.0, 0.00297), 181 | Mode::new(3, 3672, 1836, 90.0, 0.00332), 182 | Mode::new(4, 4896, 2448, 90.0, 0.0), 183 | Mode::new(5, 4896, 2448, 120.0, 0.0), 184 | ]; 185 | 186 | pub struct ViveDevice(HidDevice); 187 | impl ViveDevice { 188 | pub fn open_first() -> Result { 189 | let api = get_hidapi()?; 190 | let device = api.open(VIVE_VID, VIVE_PID)?; 191 | Ok(Self(device)) 192 | } 193 | pub fn open(sn: &str) -> Result { 194 | let api = get_hidapi()?; 195 | let device = api 196 | .device_list() 197 | .find(|dev| dev.serial_number() == Some(sn)) 198 | .ok_or(Error::DeviceNotFound)?; 199 | if device.vendor_id() != VIVE_VID || device.product_id() != VIVE_PID { 200 | return Err(Error::NotAVive); 201 | } 202 | let open = api.open_serial(device.vendor_id(), device.product_id(), sn)?; 203 | Ok(Self(open)) 204 | } 205 | fn write(&self, id: u8, data: &[u8]) -> Result<()> { 206 | let mut report = [0u8; 64]; 207 | report[0] = id; 208 | report[1..1 + data.len()].copy_from_slice(data); 209 | self.0.write(&report)?; 210 | Ok(()) 211 | } 212 | fn write_feature(&self, id: u8, sub_id: u16, data: &[u8]) -> Result<()> { 213 | let mut report = [0u8; 64]; 214 | report[0] = id; 215 | report[1] = (sub_id & 0xff) as u8; 216 | report[2] = (sub_id >> 8) as u8; 217 | report[3] = data.len() as u8; 218 | report[4..][..data.len()].copy_from_slice(data); 219 | self.0.send_feature_report(&report)?; 220 | Ok(()) 221 | } 222 | fn read(&self, id: u8, strip_prefix: &[u8], out: &mut [u8]) -> Result { 223 | let mut data = [0u8; 64]; 224 | self.0.read(&mut data)?; 225 | if data[0] != id { 226 | error!("expected {id} but got {}\n{:02x?}", data[0], data); 227 | return Err(Error::ProtocolError("wrong report id")); 228 | } 229 | if &data[1..1 + strip_prefix.len()] != strip_prefix { 230 | error!( 231 | "expected {strip_prefix:x?}, got {:x?}", 232 | &data[1..1 + strip_prefix.len()] 233 | ); 234 | return Err(Error::ProtocolError("wrong prefix")); 235 | } 236 | let size = data[1 + strip_prefix.len()] as usize; 237 | if size > 62 { 238 | return Err(Error::ProtocolError("wrong size")); 239 | } 240 | out[..size].copy_from_slice(&data[strip_prefix.len() + 2..strip_prefix.len() + 2 + size]); 241 | Ok(size) 242 | } 243 | pub fn read_devsn(&self) -> Result { 244 | self.write(0x02, b"mfg-r-devsn")?; 245 | let mut out = [0u8; 62]; 246 | let size = self.read(0x02, &[], &mut out)?; 247 | Ok(std::str::from_utf8(&out[..size]) 248 | .map_err(|_| Error::ProtocolError("devsn is not a string"))? 249 | .to_string()) 250 | } 251 | pub fn read_reg(&self, reg: &str) -> Result { 252 | self.write(0x02, reg.as_bytes())?; 253 | let mut out = [0u8; 62]; 254 | let size = self.read(0x02, &[], &mut out)?; 255 | Ok(std::str::from_utf8(&out[..size]) 256 | .map_err(|_| Error::ProtocolError("result is not a string"))? 257 | .to_string()) 258 | } 259 | pub fn read_config(&self) -> Result { 260 | let mut buf = [0u8; 62]; 261 | // Request size 262 | let total_len = { 263 | self.write(0x01, &[0xea, 0xb1])?; 264 | let size = self.read(0x01, &[0xea, 0xb1], &mut buf)?; 265 | if size != 4 { 266 | return Err(Error::ProtocolError("config length has 4 bytes")); 267 | } 268 | let mut total_len = [0u8; 4]; 269 | total_len.copy_from_slice(&buf[0..4]); 270 | u32::from_le_bytes(total_len) as usize 271 | }; 272 | let mut read = 0; 273 | let mut out = Vec::::with_capacity(total_len); 274 | while read < total_len { 275 | let mut req = [0; 63]; 276 | req[0] = 0xeb; 277 | req[1] = 0xb1; 278 | req[2] = 0x04; 279 | req[3..7].copy_from_slice(&u32::to_le_bytes(read as u32)); 280 | 281 | self.write(0x01, &req)?; 282 | let size = self.read(0x01, &[0xeb, 0xb1], &mut buf)?; 283 | read += size; 284 | out.extend_from_slice(&buf[0..size]); 285 | } 286 | if read != total_len { 287 | return Err(Error::ProtocolError("config size mismatch")); 288 | } 289 | 290 | // First 128 bytes - something i can't decipher + sha256 hash (why?) 291 | let string = std::str::from_utf8(&out[128..]) 292 | .map_err(|_| Error::ProtocolError("config is not utf-8"))?; 293 | 294 | serde_json::from_str(string).map_err(|_| Error::ConfigReadFailed) 295 | } 296 | /// Always returns at least one mode 297 | pub fn query_modes(&self) -> Vec { 298 | VIVE_PRO_2_MODES.into_iter().collect() 299 | } 300 | pub fn set_mode(&self, resolution: u8) -> Result<(), Error> { 301 | self.write_feature(0x04, 0x2970, b"wireless,0")?; 302 | self.write_feature(0x04, 0x2970, format!("dtd,{}", resolution).as_bytes())?; 303 | self.write_feature(0x04, 0x2970, b"chipreset")?; 304 | // TODO: wait for reconnection 305 | Ok(()) 306 | } 307 | pub fn set_brightness(&self, brightness: u8) -> Result<(), Error> { 308 | self.write_feature( 309 | 0x04, 310 | 0x2970, 311 | format!("setbrightness,{}", brightness.min(130)).as_bytes(), 312 | ) 313 | } 314 | pub fn toggle_noise_canceling(&self, enabled: bool) -> Result<(), Error> { 315 | const ENABLE: &[&[u8]] = &[ 316 | b"codecreg=9c9,80".as_slice(), 317 | b"codecreg=9c8,a5", 318 | b"codecreg=9d0,a4", 319 | b"codecreg=1c008f,1", 320 | b"codecreg=1c0005,9", 321 | b"codecreg=1c0005,8000", 322 | ]; 323 | const DISABLE: &[&[u8]] = &[ 324 | b"codecreg=9c9,8c".as_slice(), 325 | b"codecreg=9c8,a4", 326 | b"codecreg=9d0,0", 327 | b"codecreg=1c008f,0", 328 | b"codecreg=1c0005,9", 329 | b"codecreg=1c0005,8000", 330 | ]; 331 | // I have no idea what those values mean, this is straight 332 | // copy-pasta from what original vive console sends 333 | let lines: &'static [&'static [u8]] = if enabled { ENABLE } else { DISABLE }; 334 | for line in lines { 335 | self.write_feature(0x04, 0x2971, line)?; 336 | } 337 | Ok(()) 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /dist-proxy/driver_lighthouse_real.sew: -------------------------------------------------------------------------------- 1 | # Driver requirement check 2 | -(?x-u) 3 | -\x41\xc7\x84\x24\xac\x00\x00\x00\xd6\x00\x00\x00 4 | 5 | +(?x) 6 | +\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90 7 | 8 | # Error message clarification 9 | -(?x-u) 10 | -\x77\x68\x69\x63\x68\x20\x69\x73\x20\x6e\x6f\x74\x20\x70\x72\x65\x73\x65\x6e\x74 11 | 12 | +(?x) 13 | +\x69\x67\x6e\x6f\x72\x69\x6e\x67\x2e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 14 | -------------------------------------------------------------------------------- /dist-proxy/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | SCRIPT=$(readlink -f "$0") 6 | SCRIPTPATH=$(dirname "$SCRIPT") 7 | 8 | # Patreon release contains bundled sewer, use it here 9 | PATH=$SCRIPTPATH/bin:$PATH 10 | 11 | echo "SteamVR proxy driver for Vive Pro 2" 12 | echo "Consider supporting developer on patreon: https://patreon.com/0lach" 13 | sleep 3 14 | 15 | STEAMVR="${STEAMVR:-$HOME/.local/share/Steam/steamapps/common/SteamVR}" 16 | if ! test -d "$STEAMVR"; then 17 | echo "SteamVR not found at $STEAMVR (Set \$STEAMVR manually?)" 18 | exit 1 19 | fi 20 | echo "SteamVR at $STEAMVR" 21 | 22 | LIGHTHOUSE_DRIVER=$STEAMVR/drivers/lighthouse/bin/linux64 23 | 24 | if ! test -f "$LIGHTHOUSE_DRIVER/driver_lighthouse.so"; then 25 | echo "Lighthouse driver not found, broken installation?" 26 | exit 1 27 | fi 28 | 29 | if ! test -f "$LIGHTHOUSE_DRIVER/driver_lighthouse_real.so"; then 30 | echo "= Moving original driver" 31 | cp "$LIGHTHOUSE_DRIVER/driver_lighthouse.so" "$LIGHTHOUSE_DRIVER/driver_lighthouse_real.so" 32 | elif ! grep -s "https://patreon.com/0lach" "$LIGHTHOUSE_DRIVER/driver_lighthouse.so"; then 33 | echo "Found both original driver, and old original driver, seems like SteamVR was updated" 34 | echo "= Moving updated original driver" 35 | cp "$LIGHTHOUSE_DRIVER/driver_lighthouse.so" "$LIGHTHOUSE_DRIVER/driver_lighthouse_real.so" 36 | else 37 | echo "= Proxy driver is already installed, updating" 38 | fi 39 | 40 | echo "= Patching real driver" 41 | sewer -v --backup "$LIGHTHOUSE_DRIVER/driver_lighthouse_real.so.bak" "$LIGHTHOUSE_DRIVER/driver_lighthouse_real.so" patch-file --partial "$SCRIPTPATH/driver_lighthouse_real.sew" || true 42 | 43 | echo "= Overriding current driver" 44 | rsync -a "$SCRIPTPATH/driver_lighthouse.so" "$LIGHTHOUSE_DRIVER/driver_lighthouse.so" 45 | 46 | echo "= Updating proxy server" 47 | rsync -ar "$SCRIPTPATH/lens-server/" "$LIGHTHOUSE_DRIVER/lens-server" 48 | 49 | echo "Installation finished, try to start SteamVR" 50 | -------------------------------------------------------------------------------- /dist-proxy/lens-server/LibLensDistortion.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CertainLach/VivePro2-Linux-Driver/cc39880a50655cfce2ee36b5147e79a26cbe3658/dist-proxy/lens-server/LibLensDistortion.dll -------------------------------------------------------------------------------- /dist-proxy/lens-server/opencv_world346.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CertainLach/VivePro2-Linux-Driver/cc39880a50655cfce2ee36b5147e79a26cbe3658/dist-proxy/lens-server/opencv_world346.dll -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1699728351, 24 | "narHash": "sha256-vj8hKhF3v4BxqkjnduimxCKRfcuEvd8Q4pMq0zp4w28=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "dbf126c73ccc9c2d2d2c081f2ade25a19f4f82dc", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "repo": "nixpkgs", 33 | "type": "github" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs", 40 | "rust-overlay": "rust-overlay" 41 | } 42 | }, 43 | "rust-overlay": { 44 | "inputs": { 45 | "flake-utils": [ 46 | "flake-utils" 47 | ], 48 | "nixpkgs": [ 49 | "nixpkgs" 50 | ] 51 | }, 52 | "locked": { 53 | "lastModified": 1699669856, 54 | "narHash": "sha256-OIb0WAoEMUA1EH70AwpWabdEpvYt/kJChBnb7XiXAJs=", 55 | "owner": "oxalica", 56 | "repo": "rust-overlay", 57 | "rev": "efd15e11c8954051a47679e7718b4c2a9b68ce27", 58 | "type": "github" 59 | }, 60 | "original": { 61 | "owner": "oxalica", 62 | "repo": "rust-overlay", 63 | "type": "github" 64 | } 65 | }, 66 | "systems": { 67 | "locked": { 68 | "lastModified": 1681028828, 69 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 73 | "type": "github" 74 | }, 75 | "original": { 76 | "owner": "nix-systems", 77 | "repo": "default", 78 | "type": "github" 79 | } 80 | } 81 | }, 82 | "root": "root", 83 | "version": 7 84 | } 85 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "VIVE Pro 2 support for linux"; 3 | inputs = { 4 | nixpkgs.url = "github:nixos/nixpkgs"; 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | rust-overlay = { 7 | url = "github:oxalica/rust-overlay"; 8 | inputs = { 9 | nixpkgs.follows = "nixpkgs"; 10 | flake-utils.follows = "flake-utils"; 11 | }; 12 | }; 13 | }; 14 | outputs = { 15 | nixpkgs, 16 | flake-utils, 17 | rust-overlay, 18 | ... 19 | }: 20 | flake-utils.lib.eachDefaultSystem ( 21 | system: let 22 | pkgs = import nixpkgs { 23 | inherit system; 24 | overlays = [ 25 | rust-overlay.overlays.default 26 | # (import ./nix/oldGlibc.nix) 27 | ( 28 | final: prev: let 29 | rust = 30 | (final.buildPackages.rustChannelOf { 31 | date = "2023-11-05"; 32 | channel = "nightly"; 33 | }) 34 | .default 35 | .override { 36 | extensions = ["rust-src" "rust-analyzer"]; 37 | targets = ["x86_64-unknown-linux-musl"]; 38 | }; 39 | in { 40 | rustDev = rust; 41 | rustPlatform = prev.makeRustPlatform { 42 | rustc = rust; 43 | cargo = rust; 44 | }; 45 | } 46 | ) 47 | ]; 48 | }; 49 | pkgs-mingw = import nixpkgs { 50 | inherit system; 51 | config.replaceStdenv = import ./nix/oldGlibcStdenv.nix; 52 | overlays = [ 53 | rust-overlay.overlays.default 54 | (final: prev: { 55 | # inherit rustPlatform; 56 | # https://github.com/NixOS/nixpkgs/issues/149593 57 | openssh = prev.openssh.overrideAttrs (prev: { 58 | doCheck = false; 59 | }); 60 | rustPlatform = let 61 | rust = 62 | (final.buildPackages.rustChannelOf { 63 | date = "2022-09-03"; 64 | channel = "nightly"; 65 | }) 66 | .default 67 | .override { 68 | targets = ["x86_64-pc-windows-gnu"]; 69 | }; 70 | in 71 | prev.makeRustPlatform { 72 | rustc = rust; 73 | cargo = rust; 74 | }; 75 | }) 76 | ]; 77 | crossSystem = { 78 | config = "x86_64-w64-mingw32"; 79 | arch = "x86_64"; 80 | libc = "msvcrt"; 81 | platform = {}; 82 | openssl.system = "mingw64"; 83 | }; 84 | config.allowUnsupportedSystem = true; 85 | }; 86 | in rec { 87 | kernelPatches = [ 88 | { 89 | name = "drm-edid-non-desktop"; 90 | patch = ./kernel-patches/0001-drm-edid-non-desktop.patch; 91 | } 92 | { 93 | name = "drm-edid-type-7-timings"; 94 | patch = ./kernel-patches/0002-drm-edid-type-7-timings.patch; 95 | } 96 | { 97 | name = "drm-edid-dsc-bpp-parse"; 98 | patch = ./kernel-patches/0003-drm-edid-dsc-bpp-parse.patch; 99 | } 100 | { 101 | name = "drm-amd-dsc-bpp-apply"; 102 | patch = ./kernel-patches/0004-drm-amd-dsc-bpp-apply.patch; 103 | } 104 | ]; 105 | packages = let 106 | version = "0.1.0"; 107 | src = builtins.path { 108 | path = ./.; 109 | filter = path: type: baseNameOf path != "flake.nix"; 110 | }; 111 | cargoLock.lockFile = ./Cargo.lock; 112 | in { 113 | driver-proxy = with pkgs; 114 | rustPlatform.buildRustPackage { 115 | inherit version src cargoLock; 116 | pname = "vivepro2-driver-proxy"; 117 | nativeBuildInputs = [pkg-config]; 118 | buildInputs = [udev dbus.dev]; 119 | }; 120 | sewer = with pkgs.pkgsStatic; 121 | rustPlatform.buildRustPackage { 122 | name = "sewer"; 123 | src = fetchFromGitHub { 124 | owner = "CertainLach"; 125 | repo = "sewer"; 126 | rev = "fb0d054e53e2afd4c64232318495e5351b446330"; 127 | hash = "sha256-2S2JXKLbRQsrQmt25djj/x284NXqPSGJjybDe9Uw7ZM="; 128 | }; 129 | cargoHash = "sha256-LZTAWRZbJktp5cDTkPWcBSPJwnG5fYDDRGkrVIVdWyU="; 130 | target = "x86_64-unknown-linux-musl"; 131 | doCheck = false; 132 | }; 133 | lens-server = with pkgs-mingw; 134 | rustPlatform.buildRustPackage { 135 | inherit version src cargoLock; 136 | pname = "vivepro2-lens-server"; 137 | buildAndTestSubdir = "bin/lens-server"; 138 | }; 139 | 140 | driver-proxy-release = with pkgs; 141 | stdenv.mkDerivation { 142 | inherit version src; 143 | pname = "vivepro2-driver-proxy-release"; 144 | installPhase = '' 145 | cp -r $src/dist-proxy/ $out/ 146 | chmod u+w -R $out 147 | mkdir $out/bin/ 148 | cp ${packages.sewer}/bin/sewer $out/bin/ 149 | cp ${packages.lens-server}/bin/lens-server.exe $out/lens-server/ 150 | cp ${packages.driver-proxy}/lib/libdriver_proxy.so $out/driver_lighthouse.so 151 | ''; 152 | patchPhase = "true"; 153 | fixupPhase = "true"; 154 | }; 155 | driver-proxy-release-tar-zstd = with pkgs; 156 | stdenv.mkDerivation { 157 | inherit (packages.driver-proxy-release) version pname; 158 | unpackPhase = "true"; 159 | patchPhase = "true"; 160 | fixupPhase = "true"; 161 | installPhase = '' 162 | mkdir $out/ 163 | cd ${packages.driver-proxy-release} 164 | tar -cv * | ${pkgs.zstd}/bin/zstd -9 > $out/driver.tar.zst 165 | ''; 166 | }; 167 | }; 168 | devShells = { 169 | default = pkgs.mkShell { 170 | nativeBuildInputs = with pkgs; [ 171 | rustDev 172 | cargo-edit 173 | pkg-config 174 | lld 175 | dbus.dev 176 | udev 177 | ]; 178 | LD_LIBRARY_PATH = "${pkgs.dbus.lib}/lib"; 179 | }; 180 | }; 181 | devShell = devShells.default; 182 | } 183 | ); 184 | } 185 | -------------------------------------------------------------------------------- /kernel-patches/.dockerignore: -------------------------------------------------------------------------------- 1 | # I keep cloned kernel source code here 2 | kernel 3 | -------------------------------------------------------------------------------- /kernel-patches/.gitignore: -------------------------------------------------------------------------------- 1 | linux.deb 2 | headers.deb 3 | linux.pkg.tar.zst 4 | headers.pkg.tar.zst 5 | # I keep cloned kernel source code here 6 | kernel 7 | -------------------------------------------------------------------------------- /kernel-patches/0001-drm-edid-Add-Vive-Cosmos-Vive-Pro-2-to-non-desktop-l.patch: -------------------------------------------------------------------------------- 1 | From 23842246dab6ece6464cc121fe31b6306c959b2a Mon Sep 17 00:00:00 2001 2 | From: Yaroslav Bolyukin 3 | Date: Sun, 30 Oct 2022 18:59:15 +0100 4 | Subject: [PATCH 1/3] drm/edid: Add Vive Cosmos/Vive Pro 2 to non-desktop list 5 | 6 | Signed-off-by: Yaroslav Bolyukin 7 | --- 8 | drivers/gpu/drm/drm_edid.c | 4 +++- 9 | 1 file changed, 3 insertions(+), 1 deletion(-) 10 | 11 | diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c 12 | index 3841aba17abd..767a3e3fa6e1 100644 13 | --- a/drivers/gpu/drm/drm_edid.c 14 | +++ b/drivers/gpu/drm/drm_edid.c 15 | @@ -205,9 +205,11 @@ static const struct edid_quirk { 16 | EDID_QUIRK('V', 'L', 'V', 0x91be, EDID_QUIRK_NON_DESKTOP), 17 | EDID_QUIRK('V', 'L', 'V', 0x91bf, EDID_QUIRK_NON_DESKTOP), 18 | 19 | - /* HTC Vive and Vive Pro VR Headsets */ 20 | + /* HTC Vive, Vive Cosmos, Vive Pro and Vive Pro 2 VR Headsets */ 21 | EDID_QUIRK('H', 'V', 'R', 0xaa01, EDID_QUIRK_NON_DESKTOP), 22 | EDID_QUIRK('H', 'V', 'R', 0xaa02, EDID_QUIRK_NON_DESKTOP), 23 | + EDID_QUIRK('H', 'V', 'R', 0xaa03, EDID_QUIRK_NON_DESKTOP), 24 | + EDID_QUIRK('H', 'V', 'R', 0xaa04, EDID_QUIRK_NON_DESKTOP), 25 | 26 | /* Oculus Rift DK1, DK2, CV1 and Rift S VR Headsets */ 27 | EDID_QUIRK('O', 'V', 'R', 0x0001, EDID_QUIRK_NON_DESKTOP), 28 | -- 29 | 2.38.1 30 | 31 | -------------------------------------------------------------------------------- /kernel-patches/0002-drm-edid-parse-DRM-VESA-dsc-bpp-target.patch: -------------------------------------------------------------------------------- 1 | From 83939c5f869bab148a970b6326626da80766c464 Mon Sep 17 00:00:00 2001 2 | From: Yaroslav Bolyukin 3 | Date: Sun, 30 Oct 2022 18:59:15 +0100 4 | Subject: [PATCH 2/3] drm/edid: parse DRM VESA dsc bpp target 5 | 6 | As per DisplayID v2.0 Errata E9 spec "DSC pass-through timing support" 7 | VESA vendor-specific data block may contain target DSC bits per pixel 8 | fields 9 | 10 | Signed-off-by: Yaroslav Bolyukin 11 | --- 12 | drivers/gpu/drm/drm_edid.c | 42 ++++++++++++++++++++++++------------- 13 | include/drm/drm_connector.h | 5 +++++ 14 | drivers/gpu/drm/drm_displayid_internal.h | 4 ++++ 15 | 3 files changed, 37 insertions(+), 14 deletions(-) 16 | 17 | diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c 18 | index 767a3e3fa6e1..db6953f0e33b 100644 19 | --- a/drivers/gpu/drm/drm_edid.c 20 | +++ b/drivers/gpu/drm/drm_edid.c 21 | @@ -6289,7 +6289,7 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, 22 | if (oui(vesa->oui[0], vesa->oui[1], vesa->oui[2]) != VESA_IEEE_OUI) 23 | return; 24 | 25 | - if (sizeof(*vesa) != sizeof(*block) + block->num_bytes) { 26 | + if (block->num_bytes < 5) { 27 | drm_dbg_kms(connector->dev, 28 | "[CONNECTOR:%d:%s] Unexpected VESA vendor block size\n", 29 | connector->base.id, connector->name); 30 | @@ -6312,24 +6312,37 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, 31 | break; 32 | } 33 | 34 | - if (!info->mso_stream_count) { 35 | - info->mso_pixel_overlap = 0; 36 | - return; 37 | - } 38 | + info->mso_pixel_overlap = 0; 39 | + 40 | + if (info->mso_stream_count) { 41 | + info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); 42 | + 43 | + if (info->mso_pixel_overlap > 8) { 44 | + drm_dbg_kms(connector->dev, 45 | + "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", 46 | + connector->base.id, connector->name, 47 | + info->mso_pixel_overlap); 48 | + info->mso_pixel_overlap = 8; 49 | + } 50 | 51 | - info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); 52 | - if (info->mso_pixel_overlap > 8) { 53 | drm_dbg_kms(connector->dev, 54 | - "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", 55 | - connector->base.id, connector->name, 56 | - info->mso_pixel_overlap); 57 | - info->mso_pixel_overlap = 8; 58 | + "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", 59 | + connector->base.id, connector->name, 60 | + info->mso_stream_count, info->mso_pixel_overlap); 61 | + } 62 | + 63 | + if (block->num_bytes < 7) { 64 | + /* DSC bpp is optional */ 65 | + return; 66 | } 67 | 68 | + info->dp_dsc_bpp = FIELD_GET(DISPLAYID_VESA_DSC_BPP_INT, vesa->dsc_bpp_int) * 16 + 69 | + FIELD_GET(DISPLAYID_VESA_DSC_BPP_FRACT, vesa->dsc_bpp_fract); 70 | + 71 | drm_dbg_kms(connector->dev, 72 | - "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", 73 | - connector->base.id, connector->name, 74 | - info->mso_stream_count, info->mso_pixel_overlap); 75 | + "[CONNECTOR:%d:%s] DSC bits per pixel %u\n", 76 | + connector->base.id, connector->name, 77 | + info->dp_dsc_bpp); 78 | } 79 | 80 | static void drm_update_mso(struct drm_connector *connector, 81 | @@ -6376,6 +6389,7 @@ static void drm_reset_display_info(struct drm_connector *connector) 82 | info->mso_stream_count = 0; 83 | info->mso_pixel_overlap = 0; 84 | info->max_dsc_bpp = 0; 85 | + info->dp_dsc_bpp = 0; 86 | } 87 | 88 | static u32 update_display_info(struct drm_connector *connector, 89 | diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h 90 | index 9037f1317aee..c1ca26142975 100644 91 | --- a/include/drm/drm_connector.h 92 | +++ b/include/drm/drm_connector.h 93 | @@ -721,6 +721,11 @@ struct drm_display_info { 94 | * monitor's default value is used instead. 95 | */ 96 | u32 max_dsc_bpp; 97 | + /** 98 | + * @dp_dsc_bpp: DP Display-Stream-Compression (DSC) timing's target 99 | + * DST bits per pixel in 6.4 fixed point format. 0 means undefined 100 | + */ 101 | + u16 dp_dsc_bpp; 102 | }; 103 | 104 | int drm_display_info_set_bus_formats(struct drm_display_info *info, 105 | diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h 106 | index 49649eb8447e..ada2f8e7681c 100644 107 | --- a/drivers/gpu/drm/drm_displayid_internal.h 108 | +++ b/drivers/gpu/drm/drm_displayid_internal.h 109 | @@ -131,12 +131,16 @@ struct displayid_detailed_timing_block { 110 | 111 | #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) 112 | #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) 113 | +#define DISPLAYID_VESA_DSC_BPP_INT GENMASK(5, 0) 114 | +#define DISPLAYID_VESA_DSC_BPP_FRACT GENMASK(3, 0) 115 | 116 | struct displayid_vesa_vendor_specific_block { 117 | struct displayid_block base; 118 | u8 oui[3]; 119 | u8 data_structure_type; 120 | u8 mso; 121 | + u8 dsc_bpp_int; 122 | + u8 dsc_bpp_fract; 123 | } __packed; 124 | 125 | /* DisplayID iteration */ 126 | -- 127 | 2.38.1 128 | 129 | -------------------------------------------------------------------------------- /kernel-patches/0003-drm-amd-use-fixed-dsc-bits-per-pixel-from-edid.patch: -------------------------------------------------------------------------------- 1 | From c33583995576e9ac532c4ad9e260324b1c4fa3a3 Mon Sep 17 00:00:00 2001 2 | From: Yaroslav Bolyukin 3 | Date: Sun, 30 Oct 2022 19:04:26 +0100 4 | Subject: [PATCH 3/3] drm/amd: use fixed dsc bits-per-pixel from edid 5 | 6 | VESA vendor header from DisplayID spec may contain fixed bit per pixel 7 | rate, it should be respected by drm driver 8 | 9 | Signed-off-by: Yaroslav Bolyukin 10 | Reviewed-by: Wayne Lin 11 | --- 12 | drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 2 ++ 13 | drivers/gpu/drm/amd/display/dc/core/dc_stream.c | 2 ++ 14 | drivers/gpu/drm/amd/display/dc/dc_types.h | 3 +++ 15 | 3 files changed, 7 insertions(+) 16 | 17 | diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c 18 | index e47098fa5aac..1c9c4925ed2f 100644 19 | --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c 20 | +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c 21 | @@ -87,6 +87,8 @@ enum dc_edid_status dm_helpers_parse_edid_caps( 22 | 23 | edid_caps->edid_hdmi = connector->display_info.is_hdmi; 24 | 25 | + edid_caps->dsc_fixed_bits_per_pixel_x16 = connector->display_info.dp_dsc_bpp; 26 | + 27 | sad_count = drm_edid_to_sad((struct edid *) edid->raw_edid, &sads); 28 | if (sad_count <= 0) 29 | return result; 30 | diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c 31 | index 38d71b5c1f2d..f2467b64268b 100644 32 | --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c 33 | +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c 34 | @@ -103,6 +103,8 @@ static bool dc_stream_construct(struct dc_stream_state *stream, 35 | 36 | /* EDID CAP translation for HDMI 2.0 */ 37 | stream->timing.flags.LTE_340MCSC_SCRAMBLE = dc_sink_data->edid_caps.lte_340mcsc_scramble; 38 | + stream->timing.dsc_fixed_bits_per_pixel_x16 = 39 | + dc_sink_data->edid_caps.dsc_fixed_bits_per_pixel_x16; 40 | 41 | memset(&stream->timing.dsc_cfg, 0, sizeof(stream->timing.dsc_cfg)); 42 | stream->timing.dsc_cfg.num_slices_h = 0; 43 | diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h 44 | index dc78e2404b48..65915a10ab48 100644 45 | --- a/drivers/gpu/drm/amd/display/dc/dc_types.h 46 | +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h 47 | @@ -231,6 +231,9 @@ struct dc_edid_caps { 48 | bool edid_hdmi; 49 | bool hdr_supported; 50 | 51 | + /* DisplayPort caps */ 52 | + uint32_t dsc_fixed_bits_per_pixel_x16; 53 | + 54 | struct dc_panel_patch panel_patch; 55 | }; 56 | 57 | -- 58 | 2.38.1 59 | 60 | -------------------------------------------------------------------------------- /kernel-patches/Dockerfile.archlinux-kernel: -------------------------------------------------------------------------------- 1 | FROM archlinux:latest 2 | RUN pacman -Sy && pacman -S --noconfirm git bc libelf pahole cpio perl tar xz coreutils kmod initramfs base-devel 3 | RUN useradd -m build 4 | USER build 5 | WORKDIR /home/build 6 | 7 | RUN mkdir kernel 8 | RUN cd kernel && git clone https://anongit.freedesktop.org/git/drm/drm-misc.git -b drm-misc-next --depth=1 archlinux-linux 9 | COPY config PKGBUILD *.patch kernel/ 10 | 11 | RUN cd kernel && makepkg --holdver 12 | RUN cd kernel && mv linux-*-headers-*.pkg.tar.zst headers.pkg.tar.zst && mv linux-*.pkg.tar.zst linux.pkg.tar.zst 13 | -------------------------------------------------------------------------------- /kernel-patches/Dockerfile.debian-kernel: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | RUN apt update && apt install -y build-essential linux-source bc kmod cpio flex libncurses5-dev libelf-dev libssl-dev dwarves bison git rsync python3 zstd 3 | 4 | RUN git clone https://anongit.freedesktop.org/git/drm/drm-misc.git -b drm-misc-next --depth=1 kernel 5 | RUN mkdir patches 6 | COPY *.patch patches/ 7 | RUN cd kernel && git apply ../patches/* 8 | COPY .config kernel/ 9 | 10 | RUN cd kernel && nice make -j`nproc` bindeb-pkg 11 | RUN rm linux-image-*-dbg_*.deb && mv linux-image-*.deb linux.deb && mv linux-headers-*.deb headers.deb 12 | -------------------------------------------------------------------------------- /kernel-patches/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Jan Alexander Steffens (heftig) 2 | 3 | pkgbase=linux-drm-misc-next-git 4 | pkgver=6.1.0 5 | pkgrel=1 6 | pkgdesc='Linux with patches requied to use steamvr proxy driver' 7 | _product="${pkgbase%-git}" 8 | _branch="${_product#linux-}" 9 | arch=(x86_64) 10 | license=(GPL2) 11 | makedepends=( 12 | bc libelf pahole cpio perl tar xz git 13 | ) 14 | options=('!strip') 15 | _srcname=archlinux-linux 16 | source=( 17 | "$_srcname::git+https://anongit.freedesktop.org/git/drm/drm-misc.git#branch=$_branch" 18 | config # the main kernel config file 19 | 0001-drm-edid-Add-Vive-Cosmos-Vive-Pro-2-to-non-desktop-l.patch 20 | 0002-drm-edid-parse-DRM-VESA-dsc-bpp-target.patch 21 | 0003-drm-amd-use-fixed-dsc-bits-per-pixel-from-edid.patch 22 | ) 23 | sha256sums=('SKIP' 24 | 'SKIP' 25 | 'SKIP' 26 | 'SKIP' 27 | 'SKIP') 28 | 29 | pkgver() { 30 | cd $_srcname 31 | local version="$(grep \^VERSION Makefile|cut -d"=" -f2|cut -d" " -f2)" 32 | local patch="$(grep \^PATCHLEVEL Makefile|cut -d"=" -f2|cut -d" " -f2)" 33 | 34 | printf "%s.%s.r%s.%s" "${version}" "${patch}" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 35 | } 36 | 37 | export KBUILD_BUILD_HOST=archlinux 38 | export KBUILD_BUILD_USER=$pkgbase 39 | export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" 40 | 41 | prepare() { 42 | cd $_srcname 43 | 44 | echo "Setting version..." 45 | scripts/setlocalversion --save-scmversion 46 | echo "-$pkgrel" > localversion.10-pkgrel 47 | echo "${pkgbase#linux}" > localversion.20-pkgname 48 | 49 | local src 50 | for src in "${source[@]}"; do 51 | src="${src%%::*}" 52 | src="${src##*/}" 53 | [[ $src = *.patch ]] || continue 54 | echo "Applying patch $src..." 55 | patch -Np1 < "../$src" 56 | done 57 | 58 | echo "Setting config..." 59 | cp ../config .config 60 | make olddefconfig 61 | diff -u ../config .config || : 62 | 63 | make -s kernelrelease > version 64 | echo "Prepared $pkgbase version $( linux.pkg.tar.zst 5 | docker run proxy-driver-archlinux-kernel cat kernel/headers.pkg.tar.zst > headers.pkg.tar.zst 6 | -------------------------------------------------------------------------------- /kernel-patches/build-debian.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | docker build . -f Dockerfile.debian-kernel -t proxy-driver-debian-kernel 4 | docker run proxy-driver-debian-kernel cat linux.deb > linux.deb 5 | docker run proxy-driver-debian-kernel cat headers.deb > headers.deb 6 | -------------------------------------------------------------------------------- /kernel-patches/config: -------------------------------------------------------------------------------- 1 | .config -------------------------------------------------------------------------------- /kernel-patches/merged/drm-edid-type-7-timings.patch: -------------------------------------------------------------------------------- 1 | drm/edid: Support type 7 timings 2 | 3 | Per VESA DisplayID Standard v2.0: Type VII Timing – Detailed Timing Data 4 | 5 | Definitions were already provided as type I, but not used 6 | 7 | Signed-off-by: Yaroslav Bolyukin 8 | --- 9 | drivers/gpu/drm/drm_edid.c | 26 +++++++++++++++++--------- 10 | include/drm/drm_displayid.h | 6 +++--- 11 | 2 files changed, 20 insertions(+), 12 deletions(-) 12 | 13 | diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c 14 | index 12893e7be..5fcefd9b5 100644 15 | --- a/drivers/gpu/drm/drm_edid.c 16 | +++ b/drivers/gpu/drm/drm_edid.c 17 | @@ -5404,13 +5404,17 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi 18 | return quirks; 19 | } 20 | 21 | -static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, 22 | - struct displayid_detailed_timings_1 *timings) 23 | +static struct drm_display_mode *drm_mode_displayid_detailed_1_7(struct drm_device *dev, 24 | + struct displayid_detailed_timings_1_7 *timings, 25 | + bool type_7) 26 | { 27 | struct drm_display_mode *mode; 28 | unsigned pixel_clock = (timings->pixel_clock[0] | 29 | (timings->pixel_clock[1] << 8) | 30 | (timings->pixel_clock[2] << 16)) + 1; 31 | + // type 7 allows higher precision pixel clock 32 | + if (!type_7) 33 | + pixel_clock *= 10; 34 | unsigned hactive = (timings->hactive[0] | timings->hactive[1] << 8) + 1; 35 | unsigned hblank = (timings->hblank[0] | timings->hblank[1] << 8) + 1; 36 | unsigned hsync = (timings->hsync[0] | (timings->hsync[1] & 0x7f) << 8) + 1; 37 | @@ -5426,7 +5430,7 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d 38 | if (!mode) 39 | return NULL; 40 | 41 | - mode->clock = pixel_clock * 10; 42 | + mode->clock = pixel_clock; 43 | mode->hdisplay = hactive; 44 | mode->hsync_start = mode->hdisplay + hsync; 45 | mode->hsync_end = mode->hsync_start + hsync_width; 46 | @@ -5449,10 +5453,12 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d 47 | return mode; 48 | } 49 | 50 | -static int add_displayid_detailed_1_modes(struct drm_connector *connector, 51 | - const struct displayid_block *block) 52 | +static int add_displayid_detailed_1_7_modes(struct drm_connector *connector, 53 | + const struct displayid_block *block, 54 | + bool type_7) 55 | { 56 | - struct displayid_detailed_timing_block *det = (struct displayid_detailed_timing_block *)block; 57 | + struct displayid_detailed_timing_1_7_block *det = 58 | + (struct displayid_detailed_timing_1_7_block *)block; 59 | int i; 60 | int num_timings; 61 | struct drm_display_mode *newmode; 62 | @@ -5463,9 +5469,9 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, 63 | 64 | num_timings = block->num_bytes / 20; 65 | for (i = 0; i < num_timings; i++) { 66 | - struct displayid_detailed_timings_1 *timings = &det->timings[i]; 67 | + struct displayid_detailed_timings_1_7 *timings = &det->timings[i]; 68 | 69 | - newmode = drm_mode_displayid_detailed(connector->dev, timings); 70 | + newmode = drm_mode_displayid_detailed_1_7(connector->dev, timings, type_7); 71 | if (!newmode) 72 | continue; 73 | 74 | @@ -5485,7 +5491,9 @@ static int add_displayid_detailed_modes(struct drm_connector *connector, 75 | displayid_iter_edid_begin(edid, &iter); 76 | displayid_iter_for_each(block, &iter) { 77 | if (block->tag == DATA_BLOCK_TYPE_1_DETAILED_TIMING) 78 | - num_modes += add_displayid_detailed_1_modes(connector, block); 79 | + num_modes += add_displayid_detailed_1_7_modes(connector, block, false); 80 | + else if (block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING) 81 | + num_modes += add_displayid_detailed_1_7_modes(connector, block, true); 82 | } 83 | displayid_iter_end(&iter); 84 | 85 | diff --git a/include/drm/drm_displayid.h b/include/drm/drm_displayid.h 86 | index 7ffbd9f7b..268ff5e1f 100644 87 | --- a/include/drm/drm_displayid.h 88 | +++ b/include/drm/drm_displayid.h 89 | @@ -111,7 +111,7 @@ struct displayid_tiled_block { 90 | u8 topology_id[8]; 91 | } __packed; 92 | 93 | -struct displayid_detailed_timings_1 { 94 | +struct displayid_detailed_timings_1_7 { 95 | u8 pixel_clock[3]; 96 | u8 flags; 97 | u8 hactive[2]; 98 | @@ -124,9 +124,9 @@ struct displayid_detailed_timings_1 { 99 | u8 vsw[2]; 100 | } __packed; 101 | 102 | -struct displayid_detailed_timing_block { 103 | +struct displayid_detailed_timing_1_7_block { 104 | struct displayid_block base; 105 | - struct displayid_detailed_timings_1 timings[]; 106 | + struct displayid_detailed_timings_1_7 timings[]; 107 | }; 108 | 109 | #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) 110 | 111 | base-commit: 99613159ad749543621da8238acf1a122880144e 112 | -- 113 | 2.34.1 114 | --------------------------------------------------------------------------------